├── .github
└── workflows
│ └── deploy.yaml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .prettierignore
├── .prettierrc.mjs
├── LICENSE
├── README.md
├── SECURITY.md
├── package.json
├── packages
├── cli
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── add.ts
│ │ ├── bin.ts
│ │ ├── cli.ts
│ │ ├── init.ts
│ │ ├── registry
│ │ │ ├── index.ts
│ │ │ └── utils
│ │ │ │ ├── extractDependencies.ts
│ │ │ │ └── index.ts
│ │ └── utils
│ │ │ ├── constants
│ │ │ ├── appPath.ts
│ │ │ ├── index.ts
│ │ │ └── urls.ts
│ │ │ ├── helpers
│ │ │ ├── getConfig.ts
│ │ │ ├── getPackageManager.ts
│ │ │ └── index.ts
│ │ │ └── types
│ │ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── core
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── bundle
│ │ │ ├── helpers
│ │ │ │ ├── createContext
│ │ │ │ │ └── createContext.js
│ │ │ │ ├── createContextSelector
│ │ │ │ │ ├── createContextSelector.js
│ │ │ │ │ └── createReactiveContext.js
│ │ │ │ ├── createReactiveContext
│ │ │ │ │ └── createReactiveContext.js
│ │ │ │ ├── createStore
│ │ │ │ │ └── createStore.js
│ │ │ │ └── index.js
│ │ │ ├── hooks
│ │ │ │ ├── index.js
│ │ │ │ ├── useActiveElement
│ │ │ │ │ └── useActiveElement.js
│ │ │ │ ├── useAsync
│ │ │ │ │ └── useAsync.js
│ │ │ │ ├── useBattery
│ │ │ │ │ └── useBattery.js
│ │ │ │ ├── useBluetooth
│ │ │ │ │ └── useBluetooth.js
│ │ │ │ ├── useBoolean
│ │ │ │ │ └── useBoolean.js
│ │ │ │ ├── useBreakpoints
│ │ │ │ │ ├── constants
│ │ │ │ │ │ └── breakpoints.js
│ │ │ │ │ ├── helpers
│ │ │ │ │ │ ├── breakpoints.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ └── useBreakpoints.js
│ │ │ │ ├── useBrowserLanguage
│ │ │ │ │ └── useBrowserLanguage.js
│ │ │ │ ├── useClickOutside
│ │ │ │ │ └── useClickOutside.js
│ │ │ │ ├── useClipboard
│ │ │ │ │ └── useClipboard.js
│ │ │ │ ├── useConst
│ │ │ │ │ └── useConst.js
│ │ │ │ ├── useCookie
│ │ │ │ │ ├── helpers
│ │ │ │ │ │ ├── clearCookies.js
│ │ │ │ │ │ ├── getCookies.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── removeCookie.js
│ │ │ │ │ │ └── setCookie.js
│ │ │ │ │ └── useCookie.js
│ │ │ │ ├── useCookies
│ │ │ │ │ └── useCookies.js
│ │ │ │ ├── useCopy
│ │ │ │ │ └── useCopy.js
│ │ │ │ ├── useCounter
│ │ │ │ │ └── useCounter.js
│ │ │ │ ├── useCssVar
│ │ │ │ │ └── useCssVar.js
│ │ │ │ ├── useDebounceCallback
│ │ │ │ │ └── useDebounceCallback.js
│ │ │ │ ├── useDebounceValue
│ │ │ │ │ └── useDebounceValue.js
│ │ │ │ ├── useDefault
│ │ │ │ │ └── useDefault.js
│ │ │ │ ├── useDeviceMotion
│ │ │ │ │ └── useDeviceMotion.js
│ │ │ │ ├── useDeviceOrientation
│ │ │ │ │ └── useDeviceOrientation.js
│ │ │ │ ├── useDevicePixelRatio
│ │ │ │ │ └── useDevicePixelRatio.js
│ │ │ │ ├── useDidUpdate
│ │ │ │ │ └── useDidUpdate.js
│ │ │ │ ├── useDisclosure
│ │ │ │ │ └── useDisclosure.js
│ │ │ │ ├── useDisplayMedia
│ │ │ │ │ └── useDisplayMedia.js
│ │ │ │ ├── useDocumentEvent
│ │ │ │ │ └── useDocumentEvent.js
│ │ │ │ ├── useDocumentTitle
│ │ │ │ │ └── useDocumentTitle.js
│ │ │ │ ├── useDocumentVisibility
│ │ │ │ │ └── useDocumentVisibility.js
│ │ │ │ ├── useDoubleClick
│ │ │ │ │ └── useDoubleClick.js
│ │ │ │ ├── useDropZone
│ │ │ │ │ └── useDropZone.js
│ │ │ │ ├── useElementSize
│ │ │ │ │ └── useElementSize.js
│ │ │ │ ├── useEvent
│ │ │ │ │ └── useEvent.js
│ │ │ │ ├── useEventListener
│ │ │ │ │ └── useEventListener.js
│ │ │ │ ├── useEventSource
│ │ │ │ │ └── useEventSource.js
│ │ │ │ ├── useEyeDropper
│ │ │ │ │ └── useEyeDropper.js
│ │ │ │ ├── useFavicon
│ │ │ │ │ └── useFavicon.js
│ │ │ │ ├── useField
│ │ │ │ │ └── useField.js
│ │ │ │ ├── useFileDialog
│ │ │ │ │ └── useFileDialog.js
│ │ │ │ ├── useFocus
│ │ │ │ │ └── useFocus.js
│ │ │ │ ├── useFps
│ │ │ │ │ └── useFps.js
│ │ │ │ ├── useFul
│ │ │ │ │ └── useFul.js
│ │ │ │ ├── useFullscreen
│ │ │ │ │ └── useFullscreen.js
│ │ │ │ ├── useGamepad
│ │ │ │ │ ├── helpers
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── mapGamepadToXbox360Controller.js
│ │ │ │ │ └── useGamepad.js
│ │ │ │ ├── useGeolocation
│ │ │ │ │ └── useGeolocation.js
│ │ │ │ ├── useHash
│ │ │ │ │ └── useHash.js
│ │ │ │ ├── useHotkeys
│ │ │ │ │ └── useHotkeys.js
│ │ │ │ ├── useHover
│ │ │ │ │ └── useHover.js
│ │ │ │ ├── useIdle
│ │ │ │ │ └── useIdle.js
│ │ │ │ ├── useImage
│ │ │ │ │ └── useImage.js
│ │ │ │ ├── useInfiniteScroll
│ │ │ │ │ └── useInfiniteScroll.js
│ │ │ │ ├── useIntersectionObserver
│ │ │ │ │ └── useIntersectionObserver.js
│ │ │ │ ├── useInterval
│ │ │ │ │ └── useInterval.js
│ │ │ │ ├── useIsFirstRender
│ │ │ │ │ └── useIsFirstRender.js
│ │ │ │ ├── useIsomorphicLayoutEffect
│ │ │ │ │ └── useIsomorphicLayoutEffect.js
│ │ │ │ ├── useKeyPress
│ │ │ │ │ └── useKeyPress.js
│ │ │ │ ├── useKeyPressEvent
│ │ │ │ │ └── useKeyPressEvent.js
│ │ │ │ ├── useKeyboard
│ │ │ │ │ └── useKeyboard.js
│ │ │ │ ├── useKeysPressed
│ │ │ │ │ └── useKeysPressed.js
│ │ │ │ ├── useLastChanged
│ │ │ │ │ └── useLastChanged.js
│ │ │ │ ├── useLatest
│ │ │ │ │ └── useLatest.js
│ │ │ │ ├── useLess
│ │ │ │ │ └── useLess.js
│ │ │ │ ├── useList
│ │ │ │ │ └── useList.js
│ │ │ │ ├── useLocalStorage
│ │ │ │ │ └── useLocalStorage.js
│ │ │ │ ├── useLockCallback
│ │ │ │ │ └── useLockCallback.js
│ │ │ │ ├── useLogger
│ │ │ │ │ └── useLogger.js
│ │ │ │ ├── useLongPress
│ │ │ │ │ └── useLongPress.js
│ │ │ │ ├── useMap
│ │ │ │ │ └── useMap.js
│ │ │ │ ├── useMeasure
│ │ │ │ │ └── useMeasure.js
│ │ │ │ ├── useMediaQuery
│ │ │ │ │ └── useMediaQuery.js
│ │ │ │ ├── useMemory
│ │ │ │ │ └── useMemory.js
│ │ │ │ ├── useMount
│ │ │ │ │ └── useMount.js
│ │ │ │ ├── useMouse
│ │ │ │ │ └── useMouse.js
│ │ │ │ ├── useMutation
│ │ │ │ │ └── useMutation.js
│ │ │ │ ├── useMutationObserver
│ │ │ │ │ └── useMutationObserver.js
│ │ │ │ ├── useNetwork
│ │ │ │ │ └── useNetwork.js
│ │ │ │ ├── useOffsetPagination
│ │ │ │ │ └── useOffsetPagination.js
│ │ │ │ ├── useOnce
│ │ │ │ │ └── useOnce.js
│ │ │ │ ├── useOnline
│ │ │ │ │ └── useOnline.js
│ │ │ │ ├── useOperatingSystem
│ │ │ │ │ └── useOperatingSystem.js
│ │ │ │ ├── useOptimistic
│ │ │ │ │ └── useOptimistic.js
│ │ │ │ ├── useOrientation
│ │ │ │ │ └── useOrientation.js
│ │ │ │ ├── useOtpCredential
│ │ │ │ │ └── useOtpCredential.js
│ │ │ │ ├── usePageLeave
│ │ │ │ │ └── usePageLeave.js
│ │ │ │ ├── usePaint
│ │ │ │ │ ├── helpers
│ │ │ │ │ │ ├── Paint.js
│ │ │ │ │ │ ├── Pointer.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ └── usePaint.js
│ │ │ │ ├── useParallax
│ │ │ │ │ └── useParallax.js
│ │ │ │ ├── usePerformanceObserver
│ │ │ │ │ └── usePerformanceObserver.js
│ │ │ │ ├── usePermission
│ │ │ │ │ └── usePermission.js
│ │ │ │ ├── usePointerLock
│ │ │ │ │ └── usePointerLock.js
│ │ │ │ ├── usePostMessage
│ │ │ │ │ └── usePostMessage.js
│ │ │ │ ├── usePreferredColorScheme
│ │ │ │ │ └── usePreferredColorScheme.js
│ │ │ │ ├── usePreferredContrast
│ │ │ │ │ └── usePreferredContrast.js
│ │ │ │ ├── usePreferredDark
│ │ │ │ │ └── usePreferredDark.js
│ │ │ │ ├── usePreferredLanguages
│ │ │ │ │ └── usePreferredLanguages.js
│ │ │ │ ├── usePreferredReducedMotion
│ │ │ │ │ └── usePreferredReducedMotion.js
│ │ │ │ ├── usePrevious
│ │ │ │ │ └── usePrevious.js
│ │ │ │ ├── useQuery
│ │ │ │ │ └── useQuery.js
│ │ │ │ ├── useQueue
│ │ │ │ │ └── useQueue.js
│ │ │ │ ├── useRaf
│ │ │ │ │ └── useRaf.js
│ │ │ │ ├── useRafValue
│ │ │ │ │ └── useRafValue.js
│ │ │ │ ├── useRefState
│ │ │ │ │ └── useRefState.js
│ │ │ │ ├── useRenderCount
│ │ │ │ │ └── useRenderCount.js
│ │ │ │ ├── useRenderInfo
│ │ │ │ │ └── useRenderInfo.js
│ │ │ │ ├── useRerender
│ │ │ │ │ └── useRerender.js
│ │ │ │ ├── useResizeObserver
│ │ │ │ │ └── useResizeObserver.js
│ │ │ │ ├── useScreenOrientation
│ │ │ │ │ └── useScreenOrientation.js
│ │ │ │ ├── useScript
│ │ │ │ │ └── useScript.js
│ │ │ │ ├── useScroll
│ │ │ │ │ └── useScroll.js
│ │ │ │ ├── useScrollIntoView
│ │ │ │ │ └── useScrollIntoView.js
│ │ │ │ ├── useScrollTo
│ │ │ │ │ └── useScrollTo.js
│ │ │ │ ├── useSessionStorage
│ │ │ │ │ └── useSessionStorage.js
│ │ │ │ ├── useSet
│ │ │ │ │ └── useSet.js
│ │ │ │ ├── useShare
│ │ │ │ │ └── useShare.js
│ │ │ │ ├── useSpeechRecognition
│ │ │ │ │ └── useSpeechRecognition.js
│ │ │ │ ├── useSpeechSynthesis
│ │ │ │ │ └── useSpeechSynthesis.js
│ │ │ │ ├── useStateHistory
│ │ │ │ │ └── useStateHistory.js
│ │ │ │ ├── useStep
│ │ │ │ │ └── useStep.js
│ │ │ │ ├── useSticky
│ │ │ │ │ └── useSticky.js
│ │ │ │ ├── useStopwatch
│ │ │ │ │ └── useStopwatch.js
│ │ │ │ ├── useStorage
│ │ │ │ │ └── useStorage.js
│ │ │ │ ├── useTextDirection
│ │ │ │ │ └── useTextDirection.js
│ │ │ │ ├── useTextSelection
│ │ │ │ │ └── useTextSelection.js
│ │ │ │ ├── useThrottleCallback
│ │ │ │ │ └── useThrottleCallback.js
│ │ │ │ ├── useThrottleValue
│ │ │ │ │ └── useThrottleValue.js
│ │ │ │ ├── useTime
│ │ │ │ │ └── useTime.js
│ │ │ │ ├── useTimeout
│ │ │ │ │ └── useTimeout.js
│ │ │ │ ├── useTimer
│ │ │ │ │ └── useTimer.js
│ │ │ │ ├── useToggle
│ │ │ │ │ └── useToggle.js
│ │ │ │ ├── useUnmount
│ │ │ │ │ └── useUnmount.js
│ │ │ │ ├── useUrlSearchParams
│ │ │ │ │ └── useUrlSearchParams.js
│ │ │ │ ├── useVibrate
│ │ │ │ │ └── useVibrate.js
│ │ │ │ ├── useWakeLock
│ │ │ │ │ └── useWakeLock.js
│ │ │ │ ├── useWebSocket
│ │ │ │ │ └── useWebSocket.js
│ │ │ │ ├── useWindowEvent
│ │ │ │ │ └── useWindowEvent.js
│ │ │ │ ├── useWindowFocus
│ │ │ │ │ └── useWindowFocus.js
│ │ │ │ ├── useWindowScroll
│ │ │ │ │ └── useWindowScroll.js
│ │ │ │ ├── useWindowSize
│ │ │ │ │ └── useWindowSize.js
│ │ │ │ └── useWizard
│ │ │ │ │ └── useWizard.js
│ │ │ ├── index.js
│ │ │ └── utils
│ │ │ │ └── helpers
│ │ │ │ ├── cookie.js
│ │ │ │ ├── copy.js
│ │ │ │ ├── debounce.js
│ │ │ │ ├── getDate.js
│ │ │ │ ├── getElement.js
│ │ │ │ ├── getRetry.js
│ │ │ │ ├── index.js
│ │ │ │ ├── isTarget.js
│ │ │ │ ├── throttle.js
│ │ │ │ └── time
│ │ │ │ └── getDate.js
│ │ ├── helpers
│ │ │ ├── createContext
│ │ │ │ ├── createContext.demo.tsx
│ │ │ │ ├── createContext.test.tsx
│ │ │ │ └── createContext.tsx
│ │ │ ├── createReactiveContext
│ │ │ │ ├── createReactiveContext.demo.tsx
│ │ │ │ ├── createReactiveContext.test.tsx
│ │ │ │ └── createReactiveContext.ts
│ │ │ ├── createStore
│ │ │ │ ├── createStore.demo.tsx
│ │ │ │ ├── createStore.test.ts
│ │ │ │ └── createStore.ts
│ │ │ └── index.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useActiveElement
│ │ │ │ ├── useActiveElement.demo.tsx
│ │ │ │ └── useActiveElement.ts
│ │ │ ├── useAsync
│ │ │ │ ├── useAsync.demo.tsx
│ │ │ │ └── useAsync.ts
│ │ │ ├── useBattery
│ │ │ │ ├── useBattery.demo.tsx
│ │ │ │ ├── useBattery.test.ts
│ │ │ │ └── useBattery.ts
│ │ │ ├── useBluetooth
│ │ │ │ ├── useBluetooth.demo.tsx
│ │ │ │ └── useBluetooth.ts
│ │ │ ├── useBoolean
│ │ │ │ ├── useBoolean.demo.tsx
│ │ │ │ ├── useBoolean.test.ts
│ │ │ │ └── useBoolean.ts
│ │ │ ├── useBreakpoints
│ │ │ │ ├── useBreakpoints.demo.tsx
│ │ │ │ └── useBreakpoints.ts
│ │ │ ├── useBrowserLanguage
│ │ │ │ ├── useBrowserLanguage.demo.tsx
│ │ │ │ └── useBrowserLanguage.ts
│ │ │ ├── useClickOutside
│ │ │ │ ├── useClickOutside.demo.tsx
│ │ │ │ ├── useClickOutside.test.ts
│ │ │ │ └── useClickOutside.ts
│ │ │ ├── useClipboard
│ │ │ │ ├── useClipboard.demo.tsx
│ │ │ │ ├── useClipboard.test.ts
│ │ │ │ └── useClipboard.ts
│ │ │ ├── useConst
│ │ │ │ ├── useConst.demo.tsx
│ │ │ │ ├── useConst.test.ts
│ │ │ │ └── useConst.ts
│ │ │ ├── useCookie
│ │ │ │ ├── useCookie.demo.tsx
│ │ │ │ └── useCookie.ts
│ │ │ ├── useCookies
│ │ │ │ ├── useCookies.demo.tsx
│ │ │ │ └── useCookies.ts
│ │ │ ├── useCopy
│ │ │ │ ├── useCopy.demo.tsx
│ │ │ │ └── useCopy.ts
│ │ │ ├── useCounter
│ │ │ │ ├── useCounter.demo.tsx
│ │ │ │ ├── useCounter.test.ts
│ │ │ │ └── useCounter.ts
│ │ │ ├── useCssVar
│ │ │ │ ├── useCssVar.demo.tsx
│ │ │ │ └── useCssVar.ts
│ │ │ ├── useDebounceCallback
│ │ │ │ ├── useDebounceCallback.demo.tsx
│ │ │ │ └── useDebounceCallback.ts
│ │ │ ├── useDebounceValue
│ │ │ │ ├── useDebounceValue.demo.tsx
│ │ │ │ └── useDebounceValue.ts
│ │ │ ├── useDefault
│ │ │ │ ├── useDefault.demo.tsx
│ │ │ │ ├── useDefault.test.ts
│ │ │ │ └── useDefault.ts
│ │ │ ├── useDeviceMotion
│ │ │ │ ├── useDeviceMotion.demo.tsx
│ │ │ │ └── useDeviceMotion.ts
│ │ │ ├── useDeviceOrientation
│ │ │ │ ├── useDeviceOrientation.demo.tsx
│ │ │ │ ├── useDeviceOrientation.test.ts
│ │ │ │ └── useDeviceOrientation.ts
│ │ │ ├── useDevicePixelRatio
│ │ │ │ ├── useDevicePixelRatio.demo.tsx
│ │ │ │ ├── useDevicePixelRatio.test.ts
│ │ │ │ └── useDevicePixelRatio.ts
│ │ │ ├── useDidUpdate
│ │ │ │ ├── useDidUpdate.demo.tsx
│ │ │ │ ├── useDidUpdate.test.ts
│ │ │ │ └── useDidUpdate.ts
│ │ │ ├── useDisclosure
│ │ │ │ ├── useDisclosure.demo.tsx
│ │ │ │ ├── useDisclosure.test.ts
│ │ │ │ └── useDisclosure.ts
│ │ │ ├── useDisplayMedia
│ │ │ │ ├── useDisplayMedia.demo.tsx
│ │ │ │ ├── useDisplayMedia.test.ts
│ │ │ │ └── useDisplayMedia.ts
│ │ │ ├── useDocumentEvent
│ │ │ │ ├── useDocumentEvent.demo.tsx
│ │ │ │ ├── useDocumentEvent.test.ts
│ │ │ │ └── useDocumentEvent.ts
│ │ │ ├── useDocumentTitle
│ │ │ │ ├── useDocumentTitle.demo.tsx
│ │ │ │ ├── useDocumentTitle.test.ts
│ │ │ │ └── useDocumentTitle.ts
│ │ │ ├── useDocumentVisibility
│ │ │ │ ├── useDocumentVisibility.demo.tsx
│ │ │ │ ├── useDocumentVisibility.test.ts
│ │ │ │ └── useDocumentVisibility.ts
│ │ │ ├── useDoubleClick
│ │ │ │ ├── useDoubleClick.demo.tsx
│ │ │ │ └── useDoubleClick.ts
│ │ │ ├── useDropZone
│ │ │ │ ├── useDropZone.demo.tsx
│ │ │ │ └── useDropZone.ts
│ │ │ ├── useElementSize
│ │ │ │ ├── useElementSize.demo.tsx
│ │ │ │ ├── useElementSize.test.ts
│ │ │ │ └── useElementSize.ts
│ │ │ ├── useEvent
│ │ │ │ ├── useEvent.demo.tsx
│ │ │ │ ├── useEvent.test.ts
│ │ │ │ └── useEvent.ts
│ │ │ ├── useEventListener
│ │ │ │ ├── useEventListener.demo.tsx
│ │ │ │ └── useEventListener.ts
│ │ │ ├── useEventSource
│ │ │ │ ├── useEventSource.demo.tsx
│ │ │ │ └── useEventSource.ts
│ │ │ ├── useEyeDropper
│ │ │ │ ├── useEyeDropper.demo.tsx
│ │ │ │ └── useEyeDropper.ts
│ │ │ ├── useFavicon
│ │ │ │ ├── useFavicon.demo.tsx
│ │ │ │ ├── useFavicon.test.ts
│ │ │ │ └── useFavicon.ts
│ │ │ ├── useField
│ │ │ │ ├── useField.demo.tsx
│ │ │ │ └── useField.ts
│ │ │ ├── useFileDialog
│ │ │ │ ├── useFileDialog.demo.tsx
│ │ │ │ └── useFileDialog.ts
│ │ │ ├── useFocus
│ │ │ │ ├── useFocus.demo.tsx
│ │ │ │ └── useFocus.ts
│ │ │ ├── useFps
│ │ │ │ ├── useFps.demo.tsx
│ │ │ │ └── useFps.ts
│ │ │ ├── useFul
│ │ │ │ ├── useFul.demo.tsx
│ │ │ │ ├── useFul.test.ts
│ │ │ │ └── useFul.ts
│ │ │ ├── useFullscreen
│ │ │ │ ├── useFullscreen.demo.tsx
│ │ │ │ └── useFullscreen.ts
│ │ │ ├── useGamepad
│ │ │ │ ├── useGamepad.demo.tsx
│ │ │ │ └── useGamepad.ts
│ │ │ ├── useGeolocation
│ │ │ │ ├── useGeolocation.demo.tsx
│ │ │ │ ├── useGeolocation.test.ts
│ │ │ │ └── useGeolocation.ts
│ │ │ ├── useHash
│ │ │ │ ├── useHash.demo.tsx
│ │ │ │ └── useHash.ts
│ │ │ ├── useHotkeys
│ │ │ │ ├── useHotkeys.demo.tsx
│ │ │ │ └── useHotkeys.ts
│ │ │ ├── useHover
│ │ │ │ ├── useHover.demo.tsx
│ │ │ │ ├── useHover.test.ts
│ │ │ │ └── useHover.ts
│ │ │ ├── useIdle
│ │ │ │ ├── useIdle.demo.tsx
│ │ │ │ ├── useIdle.test.ts
│ │ │ │ └── useIdle.ts
│ │ │ ├── useImage
│ │ │ │ ├── useImage.demo.tsx
│ │ │ │ └── useImage.ts
│ │ │ ├── useInfiniteScroll
│ │ │ │ ├── useInfiniteScroll.demo.tsx
│ │ │ │ └── useInfiniteScroll.ts
│ │ │ ├── useIntersectionObserver
│ │ │ │ ├── useIntersectionObserver.demo.tsx
│ │ │ │ └── useIntersectionObserver.ts
│ │ │ ├── useInterval
│ │ │ │ ├── useInterval.demo.tsx
│ │ │ │ ├── useInterval.test.ts
│ │ │ │ └── useInterval.ts
│ │ │ ├── useIsFirstRender
│ │ │ │ ├── useIsFirstRender.demo.tsx
│ │ │ │ ├── useIsFirstRender.test.ts
│ │ │ │ └── useIsFirstRender.ts
│ │ │ ├── useIsomorphicLayoutEffect
│ │ │ │ ├── useIsomorphicLayoutEffect-node.test.ts
│ │ │ │ ├── useIsomorphicLayoutEffect.demo.tsx
│ │ │ │ ├── useIsomorphicLayoutEffect.test.ts
│ │ │ │ └── useIsomorphicLayoutEffect.ts
│ │ │ ├── useKeyPress
│ │ │ │ ├── useKeyPress.demo.tsx
│ │ │ │ └── useKeyPress.ts
│ │ │ ├── useKeyPressEvent
│ │ │ │ ├── useKeyPressEvent.demo.tsx
│ │ │ │ └── useKeyPressEvent.ts
│ │ │ ├── useKeyboard
│ │ │ │ ├── useKeyboard.demo.tsx
│ │ │ │ └── useKeyboard.ts
│ │ │ ├── useKeysPressed
│ │ │ │ ├── useKeysPressed.demo.tsx
│ │ │ │ └── useKeysPressed.ts
│ │ │ ├── useLastChanged
│ │ │ │ ├── useLastChanged.demo.tsx
│ │ │ │ ├── useLastChanged.test.ts
│ │ │ │ └── useLastChanged.ts
│ │ │ ├── useLatest
│ │ │ │ ├── useLatest.demo.tsx
│ │ │ │ ├── useLatest.test.ts
│ │ │ │ └── useLatest.ts
│ │ │ ├── useLess
│ │ │ │ ├── useLess.demo.tsx
│ │ │ │ ├── useLess.test.ts
│ │ │ │ └── useLess.ts
│ │ │ ├── useList
│ │ │ │ ├── useList.demo.tsx
│ │ │ │ └── useList.ts
│ │ │ ├── useLocalStorage
│ │ │ │ ├── useLocalStorage.demo.tsx
│ │ │ │ ├── useLocalStorage.test.ts
│ │ │ │ └── useLocalStorage.ts
│ │ │ ├── useLockCallback
│ │ │ │ ├── useLockCallback.demo.tsx
│ │ │ │ ├── useLockCallback.test.ts
│ │ │ │ └── useLockCallback.ts
│ │ │ ├── useLogger
│ │ │ │ ├── useLogger.demo.tsx
│ │ │ │ ├── useLogger.test.ts
│ │ │ │ └── useLogger.ts
│ │ │ ├── useLongPress
│ │ │ │ ├── useLongPress.demo.tsx
│ │ │ │ └── useLongPress.ts
│ │ │ ├── useMap
│ │ │ │ ├── useMap.demo.tsx
│ │ │ │ ├── useMap.test.ts
│ │ │ │ └── useMap.ts
│ │ │ ├── useMeasure
│ │ │ │ ├── useMeasure.demo.tsx
│ │ │ │ └── useMeasure.ts
│ │ │ ├── useMediaQuery
│ │ │ │ ├── useMediaQuery.demo.tsx
│ │ │ │ ├── useMediaQuery.test.ts
│ │ │ │ └── useMediaQuery.ts
│ │ │ ├── useMemory
│ │ │ │ ├── useMemory.demo.tsx
│ │ │ │ ├── useMemory.test.ts
│ │ │ │ └── useMemory.ts
│ │ │ ├── useMount
│ │ │ │ ├── useMount.demo.tsx
│ │ │ │ ├── useMount.test.ts
│ │ │ │ └── useMount.ts
│ │ │ ├── useMouse
│ │ │ │ ├── useMouse.demo.tsx
│ │ │ │ └── useMouse.ts
│ │ │ ├── useMutation
│ │ │ │ ├── useMutation.demo.tsx
│ │ │ │ └── useMutation.ts
│ │ │ ├── useMutationObserver
│ │ │ │ ├── useMutationObserver.demo.tsx
│ │ │ │ └── useMutationObserver.ts
│ │ │ ├── useNetwork
│ │ │ │ ├── useNetwork.demo.tsx
│ │ │ │ ├── useNetwork.test.ts
│ │ │ │ └── useNetwork.ts
│ │ │ ├── useOffsetPagination
│ │ │ │ ├── useOffsetPagination.demo.tsx
│ │ │ │ └── useOffsetPagination.ts
│ │ │ ├── useOnce
│ │ │ │ ├── useOnce.demo.tsx
│ │ │ │ ├── useOnce.test.ts
│ │ │ │ └── useOnce.ts
│ │ │ ├── useOnline
│ │ │ │ ├── useOnline.demo.tsx
│ │ │ │ ├── useOnline.test.ts
│ │ │ │ └── useOnline.ts
│ │ │ ├── useOperatingSystem
│ │ │ │ ├── useOperatingSystem.demo.tsx
│ │ │ │ ├── useOperatingSystem.test.ts
│ │ │ │ └── useOperatingSystem.ts
│ │ │ ├── useOptimistic
│ │ │ │ ├── useOptimistic.demo.tsx
│ │ │ │ └── useOptimistic.ts
│ │ │ ├── useOrientation
│ │ │ │ ├── useOrientation.demo.tsx
│ │ │ │ ├── useOrientation.test.ts
│ │ │ │ └── useOrientation.ts
│ │ │ ├── useOtpCredential
│ │ │ │ ├── useOtpCredential.demo.tsx
│ │ │ │ └── useOtpCredential.ts
│ │ │ ├── usePageLeave
│ │ │ │ ├── usePageLeave.demo.tsx
│ │ │ │ ├── usePageLeave.test.ts
│ │ │ │ └── usePageLeave.ts
│ │ │ ├── usePaint
│ │ │ │ ├── usePaint.demo.tsx
│ │ │ │ └── usePaint.ts
│ │ │ ├── useParallax
│ │ │ │ ├── useParallax.demo.tsx
│ │ │ │ └── useParallax.ts
│ │ │ ├── usePerformanceObserver
│ │ │ │ ├── usePerformanceObserver.demo.tsx
│ │ │ │ └── usePerformanceObserver.ts
│ │ │ ├── usePermission
│ │ │ │ ├── usePermission.demo.tsx
│ │ │ │ └── usePermission.ts
│ │ │ ├── usePointerLock
│ │ │ │ ├── usePointerLock.demo.tsx
│ │ │ │ └── usePointerLock.ts
│ │ │ ├── usePostMessage
│ │ │ │ ├── usePostMessage.demo.tsx
│ │ │ │ └── usePostMessage.ts
│ │ │ ├── usePreferredColorScheme
│ │ │ │ ├── usePreferredColorScheme.demo.tsx
│ │ │ │ └── usePreferredColorScheme.ts
│ │ │ ├── usePreferredContrast
│ │ │ │ ├── usePreferredContrast.demo.tsx
│ │ │ │ └── usePreferredContrast.ts
│ │ │ ├── usePreferredDark
│ │ │ │ ├── usePreferredDark.demo.tsx
│ │ │ │ └── usePreferredDark.ts
│ │ │ ├── usePreferredLanguages
│ │ │ │ ├── usePreferredLanguages.demo.tsx
│ │ │ │ ├── usePreferredLanguages.test.ts
│ │ │ │ └── usePreferredLanguages.ts
│ │ │ ├── usePreferredReducedMotion
│ │ │ │ ├── usePreferredReducedMotion.demo.tsx
│ │ │ │ └── usePreferredReducedMotion.ts
│ │ │ ├── usePrevious
│ │ │ │ ├── usePrevious.demo.tsx
│ │ │ │ ├── usePrevious.test.ts
│ │ │ │ └── usePrevious.ts
│ │ │ ├── useQuery
│ │ │ │ ├── useQuery.demo.tsx
│ │ │ │ ├── useQuery.test.ts
│ │ │ │ └── useQuery.ts
│ │ │ ├── useQueue
│ │ │ │ ├── useQueue.demo.tsx
│ │ │ │ ├── useQueue.test.ts
│ │ │ │ └── useQueue.ts
│ │ │ ├── useRaf
│ │ │ │ ├── useRaf.demo.tsx
│ │ │ │ └── useRaf.ts
│ │ │ ├── useRafValue
│ │ │ │ ├── useRafValue.demo.tsx
│ │ │ │ └── useRafValue.ts
│ │ │ ├── useRefState
│ │ │ │ ├── useRefState.demo.tsx
│ │ │ │ ├── useRefState.test.ts
│ │ │ │ └── useRefState.ts
│ │ │ ├── useRenderCount
│ │ │ │ ├── useRenderCount.demo.tsx
│ │ │ │ ├── useRenderCount.test.ts
│ │ │ │ └── useRenderCount.ts
│ │ │ ├── useRenderInfo
│ │ │ │ ├── useRenderInfo.demo.tsx
│ │ │ │ ├── useRenderInfo.test.ts
│ │ │ │ └── useRenderInfo.ts
│ │ │ ├── useRerender
│ │ │ │ ├── useRerender.demo.tsx
│ │ │ │ ├── useRerender.test.ts
│ │ │ │ └── useRerender.ts
│ │ │ ├── useResizeObserver
│ │ │ │ ├── useResizeObserver.demo.tsx
│ │ │ │ └── useResizeObserver.ts
│ │ │ ├── useScreenOrientation
│ │ │ │ ├── useScreenOrientation.demo.tsx
│ │ │ │ └── useScreenOrientation.ts
│ │ │ ├── useScript
│ │ │ │ ├── useScript.demo.tsx
│ │ │ │ ├── useScript.test.ts
│ │ │ │ └── useScript.ts
│ │ │ ├── useScroll
│ │ │ │ ├── useScroll.demo.tsx
│ │ │ │ └── useScroll.ts
│ │ │ ├── useScrollIntoView
│ │ │ │ ├── useScrollIntoView.demo.tsx
│ │ │ │ └── useScrollIntoView.ts
│ │ │ ├── useScrollTo
│ │ │ │ ├── useScrollTo.demo.tsx
│ │ │ │ └── useScrollTo.ts
│ │ │ ├── useSessionStorage
│ │ │ │ ├── useSessionStorage.demo.tsx
│ │ │ │ ├── useSessionStorage.test.ts
│ │ │ │ └── useSessionStorage.ts
│ │ │ ├── useSet
│ │ │ │ ├── useSet.demo.tsx
│ │ │ │ ├── useSet.test.ts
│ │ │ │ └── useSet.ts
│ │ │ ├── useShare
│ │ │ │ ├── useShare.demo.tsx
│ │ │ │ ├── useShare.test.ts
│ │ │ │ └── useShare.ts
│ │ │ ├── useSpeechRecognition
│ │ │ │ ├── useSpeechRecognition.demo.tsx
│ │ │ │ └── useSpeechRecognition.ts
│ │ │ ├── useSpeechSynthesis
│ │ │ │ ├── useSpeechSynthesis.demo.tsx
│ │ │ │ └── useSpeechSynthesis.ts
│ │ │ ├── useStateHistory
│ │ │ │ ├── useStateHistory.demo.tsx
│ │ │ │ ├── useStateHistory.test.ts
│ │ │ │ └── useStateHistory.ts
│ │ │ ├── useStep
│ │ │ │ ├── useStep.demo.tsx
│ │ │ │ ├── useStep.test.ts
│ │ │ │ └── useStep.ts
│ │ │ ├── useSticky
│ │ │ │ ├── useSticky.demo.tsx
│ │ │ │ └── useSticky.ts
│ │ │ ├── useStopwatch
│ │ │ │ ├── useStopwatch.demo.tsx
│ │ │ │ └── useStopwatch.ts
│ │ │ ├── useStorage
│ │ │ │ ├── useStorage.demo.tsx
│ │ │ │ └── useStorage.ts
│ │ │ ├── useTextDirection
│ │ │ │ ├── useTextDirection.demo.tsx
│ │ │ │ ├── useTextDirection.test.ts
│ │ │ │ └── useTextDirection.ts
│ │ │ ├── useTextSelection
│ │ │ │ ├── useTextSelection.demo.tsx
│ │ │ │ └── useTextSelection.ts
│ │ │ ├── useThrottleCallback
│ │ │ │ ├── useThrottleCallback.demo.tsx
│ │ │ │ └── useThrottleCallback.ts
│ │ │ ├── useThrottleValue
│ │ │ │ ├── useThrottleValue.demo.tsx
│ │ │ │ └── useThrottleValue.ts
│ │ │ ├── useTime
│ │ │ │ ├── useTime.demo.tsx
│ │ │ │ ├── useTime.test.ts
│ │ │ │ └── useTime.ts
│ │ │ ├── useTimeout
│ │ │ │ ├── useTimeout.demo.tsx
│ │ │ │ ├── useTimeout.test.ts
│ │ │ │ └── useTimeout.ts
│ │ │ ├── useTimer
│ │ │ │ ├── useTimer.demo.tsx
│ │ │ │ ├── useTimer.test.ts
│ │ │ │ └── useTimer.ts
│ │ │ ├── useToggle
│ │ │ │ ├── useToggle.demo.tsx
│ │ │ │ ├── useToggle.test.ts
│ │ │ │ └── useToggle.ts
│ │ │ ├── useUnmount
│ │ │ │ ├── useUnmount.demo.tsx
│ │ │ │ ├── useUnmount.test.ts
│ │ │ │ └── useUnmount.ts
│ │ │ ├── useUrlSearchParams
│ │ │ │ ├── useUrlSearchParams.demo.tsx
│ │ │ │ └── useUrlSearchParams.ts
│ │ │ ├── useVibrate
│ │ │ │ ├── useVibrate.demo.tsx
│ │ │ │ └── useVibrate.ts
│ │ │ ├── useWakeLock
│ │ │ │ ├── useWakeLock.demo.tsx
│ │ │ │ └── useWakeLock.ts
│ │ │ ├── useWebSocket
│ │ │ │ ├── useWebSocket.demo.tsx
│ │ │ │ └── useWebSocket.ts
│ │ │ ├── useWindowEvent
│ │ │ │ ├── useWindowEvent.demo.tsx
│ │ │ │ ├── useWindowEvent.test.ts
│ │ │ │ └── useWindowEvent.ts
│ │ │ ├── useWindowFocus
│ │ │ │ ├── useWindowFocus.demo.tsx
│ │ │ │ └── useWindowFocus.ts
│ │ │ ├── useWindowScroll
│ │ │ │ ├── useWindowScroll.demo.tsx
│ │ │ │ └── useWindowScroll.ts
│ │ │ ├── useWindowSize
│ │ │ │ ├── useWindowSize.demo.tsx
│ │ │ │ ├── useWindowSize.test.ts
│ │ │ │ └── useWindowSize.ts
│ │ │ └── useWizard
│ │ │ │ ├── useWizard.demo.tsx
│ │ │ │ └── useWizard.ts
│ │ ├── index.ts
│ │ └── utils
│ │ │ └── helpers
│ │ │ ├── copy.ts
│ │ │ ├── debounce.ts
│ │ │ ├── getDate.test.ts
│ │ │ ├── getDate.ts
│ │ │ ├── getElement.ts
│ │ │ ├── getRetry.ts
│ │ │ ├── index.ts
│ │ │ ├── isTarget.ts
│ │ │ └── throttle.ts
│ ├── tests
│ │ ├── createTrigger.ts
│ │ ├── index.ts
│ │ ├── renderHookServer.tsx
│ │ └── setupTests.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── vite.config.mts
│ └── vitest.config.mts
└── docs
│ ├── README.md
│ ├── app
│ ├── .vitepress
│ │ ├── config.mts
│ │ └── theme
│ │ │ ├── global.css
│ │ │ └── index.ts
│ ├── cli.md
│ ├── functions
│ │ └── hooks
│ │ │ ├── [name].md
│ │ │ └── [name].paths.mts
│ ├── index.md
│ ├── installation.md
│ ├── installation
│ │ ├── nextjs.md
│ │ └── vite.md
│ ├── introduction.md
│ ├── public
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── logo.svg
│ │ └── manifest.json
│ ├── reactuse-json.md
│ └── target.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── api.vue
│ │ ├── badges.vue
│ │ ├── code.vue
│ │ ├── contributors.vue
│ │ ├── demo.vue
│ │ ├── framework.vue
│ │ ├── meta.vue
│ │ └── source.vue
│ └── utils
│ │ ├── docs
│ │ ├── checkTest.ts
│ │ ├── getContent.ts
│ │ ├── getContentFile.ts
│ │ ├── getContentItems.ts
│ │ └── index.ts
│ │ ├── index.ts
│ │ ├── isDefaultType.ts
│ │ ├── matchJsdoc.ts
│ │ ├── parseHookJsdoc.ts
│ │ └── utils
│ │ ├── cn.ts
│ │ └── index.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | emoji=$(git config user.emoji || echo "🧊")
2 | branch=$(git symbolic-ref --short HEAD)
3 | message=$(cat "$1")
4 |
5 | if echo "$message" | (! grep -q "^${branch} ${emoji}*") &&\
6 | echo "$message" | (! grep -q "^Merge branch*") &&\
7 | echo "$message" | (! grep -q "^Merge remote-tracking branch*");\
8 | then
9 | echo "$branch $emoji $(cat $1)" > "$1"
10 | fi
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | pnpm lint-staged
4 |
5 | if git diff --quiet HEAD -- packages/core; then
6 | echo "No changes in packages/core. Skipping build."
7 | else
8 | echo "Running tests for packages/core..."
9 | pnpm unit-test run
10 |
11 | echo "Changes detected in packages/core. Running build:js..."
12 | pnpm core:build:js
13 |
14 | git add packages/core/src/bundle
15 | echo "Built files added to git."
16 |
17 | git add packages/cli/src/registry
18 | echo "Registry built and added to git."
19 | fi
20 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/dist
2 | **/node_modules
3 | **/cache
4 | **/pnpm-lock.yaml
--------------------------------------------------------------------------------
/.prettierrc.mjs:
--------------------------------------------------------------------------------
1 | import { prettier } from '@siberiacancode/prettier';
2 |
3 | /** @type {import('prettier').Config} */
4 | export default { ...prettier, plugins: ['prettier-plugin-tailwindcss'] };
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 siberiacancode
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 React Use
2 |
3 | the largest and most useful hook library
4 |
5 | ## 🦉 Philosophy
6 |
7 | **🚀 React Use** this is a library that will allow you to easy and simple to use React hooks. Unlike its competitors, this package takes into account the features of React and also contains a huge number of useful hooks.
8 |
9 | ## Features
10 |
11 | - **TypeScript support out of the box** - full typed package
12 | - **SSR** - package work with server side render
13 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you believe you have found a security vulnerability in our library, we encourage you to let us know right away. We will investigate all legitimate reports and do our best to quickly fix the problem.
6 |
7 | Please let us know by creating an issue in this repository
8 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # useverse
2 |
3 | A CLI for adding react hooks to your project.
4 |
5 | ## Usage
6 |
7 | Use the `init` command to initialize dependencies for a new project.
8 |
9 | The `init` command installs dependencies, adds the `cn` util, configures `tailwind.config.js`, and CSS variables for the project.
10 |
11 | ```bash
12 | npx useverse init
13 | ```
14 |
15 | ## add
16 |
17 | Use the `add` command to add components to your project.
18 |
19 | The `add` command adds a hook to your project and installs all required dependencies.
20 |
21 | ```bash
22 | npx useverse add [hook]
23 | ```
24 |
25 | ### Example
26 |
27 | ```bash
28 | npx useverse add useCounter
29 | ```
30 |
31 | You can also run the command without any arguments to view a list of all available hooks:
32 |
33 | ```bash
34 | npx useverse add
35 | ```
36 |
--------------------------------------------------------------------------------
/packages/cli/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { eslint } from '@siberiacancode/eslint';
2 |
3 | /** @type {import('eslint').Linter.FlatConfig} */
4 | export default eslint(
5 | {
6 | typescript: true
7 | },
8 | {
9 | name: 'siberiacancode/cli/rewrite',
10 | rules: {
11 | 'node/prefer-global/process': 'off',
12 | 'node/prefer-global/buffer': 'off'
13 | }
14 | }
15 | );
16 |
--------------------------------------------------------------------------------
/packages/cli/src/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { cli } from './cli.js';
4 |
5 | cli();
6 |
7 | export {};
8 |
--------------------------------------------------------------------------------
/packages/cli/src/cli.ts:
--------------------------------------------------------------------------------
1 | import yargs from 'yargs';
2 | import { hideBin } from 'yargs/helpers';
3 |
4 | import { add } from './add';
5 | import { init } from './init';
6 |
7 | export const cli = () => {
8 | const processArgv = hideBin(process.argv);
9 |
10 | if (processArgv.includes('init')) return init();
11 |
12 | yargs(processArgv)
13 | .scriptName('reactuse')
14 | .usage('$0 [args]')
15 | .command(add)
16 | .epilogue('More info: https://siberiacancode.github.io/reactuse/cli')
17 | .version()
18 | .alias('v', 'version')
19 | .help()
20 | .alias('h', 'help')
21 | .parse();
22 | };
23 |
--------------------------------------------------------------------------------
/packages/cli/src/registry/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './extractDependencies';
2 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/constants/appPath.ts:
--------------------------------------------------------------------------------
1 | export const APP_PATH = process.cwd();
2 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from './appPath';
2 | export * from './urls';
3 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/constants/urls.ts:
--------------------------------------------------------------------------------
1 | export const BASE_URL = 'https://raw.githubusercontent.com/siberiacancode/reactuse/main';
2 | export const REPO_URLS = {
3 | TS: `${BASE_URL}/packages/core/src`,
4 | JS: `${BASE_URL}/packages/core/src/bundle`
5 | };
6 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/helpers/getConfig.ts:
--------------------------------------------------------------------------------
1 | import { cosmiconfig } from 'cosmiconfig';
2 |
3 | import { configSchema } from '@/utils/types';
4 |
5 | export const getConfig = async (cwd: string) => {
6 | const explorer = cosmiconfig('configHooks', {
7 | searchPlaces: ['reactuse.json']
8 | });
9 |
10 | try {
11 | const configResult = (await explorer.search(cwd))!;
12 | return configSchema.parse(configResult.config);
13 | } catch (error) {
14 | throw new Error(`Invalid configuration found in ${cwd}/reactuse.json. Error - ${error}`);
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/helpers/getPackageManager.ts:
--------------------------------------------------------------------------------
1 | import { detect } from '@antfu/ni';
2 |
3 | export const getPackageManager = async (targetDir: string) => {
4 | const packageManager = await detect({ programmatic: true, cwd: targetDir });
5 |
6 | if (packageManager === 'yarn@berry') return 'yarn';
7 | if (packageManager === 'pnpm@6') return 'pnpm';
8 | if (packageManager === 'bun') return 'bun';
9 | if (packageManager === 'deno') return 'deno';
10 |
11 | return packageManager ?? 'npm';
12 | };
13 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getConfig';
2 | export * from './getPackageManager';
3 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/types/index.ts:
--------------------------------------------------------------------------------
1 | import * as z from 'zod';
2 |
3 | export interface HookRegistry {
4 | hooks: string[];
5 | name: string;
6 | packages: string[];
7 | utils: string[];
8 | }
9 |
10 | export interface Registry {
11 | [key: string]: HookRegistry;
12 | }
13 |
14 | export const addOptionsSchema = z.object({
15 | hooks: z.array(z.string()),
16 | all: z.boolean(),
17 | registry: z.string(),
18 | overwrite: z.boolean(),
19 | cwd: z.string()
20 | });
21 |
22 | export type AddOptionsSchema = z.infer;
23 |
24 | export const configSchema = z
25 | .object({
26 | ts: z.boolean().optional(),
27 | aliases: z.object({
28 | hooks: z.string(),
29 | utils: z.string()
30 | })
31 | })
32 | .strict();
33 |
34 | export type ConfigSchema = z.infer;
35 |
--------------------------------------------------------------------------------
/packages/cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "composite": false,
5 | "baseUrl": ".",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | },
11 | "resolveJsonModule": true,
12 | "strict": true,
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "inlineSources": false,
18 | "esModuleInterop": true,
19 | "forceConsistentCasingInFileNames": true,
20 | "preserveWatchOutput": true,
21 | "isolatedModules": true,
22 | "skipLibCheck": true
23 | },
24 | "include": ["src"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/cli/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig({
4 | clean: true,
5 | dts: true,
6 | entry: ['src/bin.ts'],
7 | format: ['esm'],
8 | sourcemap: true,
9 | minify: true,
10 | target: 'esnext',
11 | outDir: 'dist'
12 | });
13 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 reactuse core
2 |
3 | the largest and most useful hook library
4 |
5 | ## 🦉 Philosophy
6 |
7 | **🚀 React Use** this is a library that will allow you to easy and simple to use React hooks. Unlike its competitors, this package takes into account the features of React and also contains a huge number of useful hooks.
8 |
9 | ## Features
10 |
11 | - **TypeScript support out of the box** - full typed package
12 | - **SSR** - package work with server side render
13 |
--------------------------------------------------------------------------------
/packages/core/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { eslint } from '@siberiacancode/eslint';
2 |
3 | /** @type {import('eslint').Linter.FlatConfig} */
4 | export default eslint(
5 | {
6 | typescript: true,
7 | javascript: true,
8 | react: true,
9 | jsx: true,
10 | vue: true
11 | },
12 | {
13 | name: 'siberiacancode/core/ignores',
14 | ignores: ['**/bundle/**/*.js']
15 | },
16 | {
17 | name: 'siberiacancode/core/hooks',
18 | files: ['**/{hooks,helpers}/**/*.{ts,tsx}'],
19 | rules: {
20 | 'react-dom/no-flush-sync': 'warn',
21 | 'jsdoc/no-defaults': 'off',
22 | 'react-hooks/rules-of-hooks': 'warn',
23 | 'react/no-use-context': 'off',
24 | 'react/no-context-provider': 'off'
25 | }
26 | },
27 | {
28 | name: 'siberiacancode/core/demo',
29 | files: ['**/*.demo.tsx'],
30 | rules: {
31 | 'no-alert': 'off'
32 | }
33 | }
34 | );
35 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './createContext/createContext';
2 | export * from './createReactiveContext/createReactiveContext';
3 | export * from './createStore/createStore';
4 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useBoolean/useBoolean.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | /**
3 | * @name useBoolean
4 | * @description - Hook provides opportunity to manage boolean state
5 | * @category Utilities
6 | *
7 | * @param {boolean} [initialValue=false] The initial boolean value
8 | * @returns {UseBooleanReturn} An object containing the boolean state value and utility functions to manipulate the state
9 | *
10 | * @example
11 | * const [on, toggle] = useBoolean()
12 | */
13 | export const useBoolean = (initialValue = false) => {
14 | const [value, setValue] = useState(initialValue);
15 | const toggle = (value) => setValue((prevValue) => value ?? !prevValue);
16 | return [value, toggle];
17 | };
18 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useBreakpoints/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './breakpoints';
2 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useBrowserLanguage/useBrowserLanguage.js:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 | const getSnapshot = () => navigator.language;
3 | const getServerSnapshot = () => 'undetermined';
4 | const subscribe = (callback) => {
5 | window.addEventListener('languagechange', callback);
6 | return () => window.removeEventListener('languagechange', callback);
7 | };
8 | /**
9 | * @name useBrowserLanguage
10 | * @description - Hook that returns the current browser language
11 | * @category Browser
12 | *
13 | * @browserapi navigator.language https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
14 | *
15 | * @returns {string} The current browser language
16 | *
17 | * @example
18 | * const browserLanguage = useBrowserLanguage();
19 | */
20 | export const useBrowserLanguage = () =>
21 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useConst/useConst.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | /**
3 | * @name useConst
4 | * @description - Hook that returns the constant value
5 | * @category Utilities
6 | *
7 | * @template Value The type of the value
8 | * @param {(() => Value) | Value} initialValue The initial value of the constant
9 | * @returns {Value} The constant value
10 | *
11 | * @example
12 | * const value = useConst('value');
13 | */
14 | export const useConst = (initialValue) =>
15 | useRef(typeof initialValue === 'function' ? initialValue() : initialValue).current;
16 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCookie/helpers/clearCookies.js:
--------------------------------------------------------------------------------
1 | import { removeCookie } from './removeCookie';
2 | export const clearCookies = () => {
3 | document.cookie.split('; ').forEach((cookie) => {
4 | const [name] = cookie.split('=');
5 | removeCookie(name);
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCookie/helpers/getCookies.js:
--------------------------------------------------------------------------------
1 | export const getCookies = () =>
2 | Object.fromEntries(
3 | document.cookie.split('; ').map((cookie) => {
4 | const [key, ...value] = cookie.split('=');
5 | const decodedValue = decodeURIComponent(value.join('='));
6 | return [key, decodedValue];
7 | })
8 | );
9 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCookie/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './clearCookies';
2 | export * from './getCookies';
3 | export * from './removeCookie';
4 | export * from './setCookie';
5 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCookie/helpers/removeCookie.js:
--------------------------------------------------------------------------------
1 | export const removeCookie = (key, options = {}) => {
2 | document.cookie = `${encodeURIComponent(key)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT${options.path ? `; path=${options.path}` : ''}${options.domain ? `; domain=${options.domain}` : ''}${options.maxAge ? `; max-age=0` : ''}${options.expires ? `; expires=Thu, 01 Jan 1970 00:00:00 GMT` : ''}${options.secure ? `; secure` : ''}${options.sameSite ? `; samesite=${options.sameSite}` : ''}`;
3 | };
4 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCookie/helpers/setCookie.js:
--------------------------------------------------------------------------------
1 | export const setCookie = (key, value, options = {}) => {
2 | const cookie = [`${encodeURIComponent(key)}=${encodeURIComponent(value)}`];
3 | if (options.path) cookie.push(`path=${options.path}`);
4 | if (options.domain) cookie.push(`domain=${options.domain}`);
5 | if (typeof options.maxAge === 'number') cookie.push(`max-age=${options.maxAge}`);
6 | if (options.expires) cookie.push(`expires=${options.expires.toUTCString()}`);
7 | if (options.secure) cookie.push(`secure`);
8 | if (options.httpOnly) cookie.push(`httpOnly`);
9 | if (options.sameSite) cookie.push(`samesite=${options.sameSite}`);
10 | document.cookie = cookie.join('; ');
11 | };
12 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useCopy/useCopy.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { copy } from '@/utils/helpers';
3 | /**
4 | * @name useCopy
5 | * @description - Hook that manages copying text with status reset
6 | * @category Browser
7 | *
8 | * @browserapi navigator.clipboard https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard
9 | *
10 | * @param {number} [delay=1000] Delay in ms before resetting copied status
11 | * @returns {UseCopyReturn} An object containing the copied value, status and copy function
12 | *
13 | * @example
14 | * const { copied, value, copy } = useCopy();
15 | */
16 | export const useCopy = (delay = 1000) => {
17 | const [value, setValue] = useState(null);
18 | const [copied, setCopied] = useState(false);
19 | const copyToClipboard = async (text) => {
20 | await copy(text);
21 | setValue(text);
22 | setCopied(true);
23 | setTimeout(() => setCopied(false), delay);
24 | };
25 | return { value, copied, copy: copyToClipboard };
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDebounceCallback/useDebounceCallback.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { debounce } from '@/utils/helpers';
3 | import { useEvent } from '../useEvent/useEvent';
4 | /**
5 | * @name useDebounceCallback
6 | * @description - Hook that creates a debounced callback
7 | * @category Utilities
8 | *
9 | * @template Params The type of the params
10 | * @template Return The type of the return
11 | * @param {(...args: Params) => Return} callback The callback function
12 | * @param {number} delay The delay in milliseconds
13 | * @returns {(...args: Params) => Return} The callback with debounce
14 | *
15 | * @example
16 | * const debouncedCallback = useDebounceCallback(() => console.log('callback'), 500);
17 | */
18 | export const useDebounceCallback = (callback, delay) => {
19 | const internalCallback = useEvent(callback);
20 | const debounced = useMemo(() => debounce(internalCallback, delay), [delay]);
21 | return debounced;
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDebounceValue/useDebounceValue.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import { useDebounceCallback } from '../useDebounceCallback/useDebounceCallback';
3 | /**
4 | * @name useDebounceValue
5 | * @description - Hook that creates a debounced value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {Value} value The value to be debounced
10 | * @param {number} delay The delay in milliseconds
11 | * @returns {Value} The debounced value
12 | *
13 | * @example
14 | * const debouncedValue = useDebounceValue(value, 500);
15 | */
16 | export const useDebounceValue = (value, delay) => {
17 | const previousValueRef = useRef(value);
18 | const [debouncedValue, setDebounceValue] = useState(value);
19 | const debouncedSetState = useDebounceCallback(setDebounceValue, delay);
20 | useEffect(() => {
21 | if (previousValueRef.current === value) return;
22 | debouncedSetState(value);
23 | previousValueRef.current = value;
24 | }, [value]);
25 | return debouncedValue;
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDefault/useDefault.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | /**
3 | * @name useDefault
4 | * @description - Hook that returns the default value
5 | * @category Utilities
6 | *
7 | * @template Value The type of the value
8 | * @param {Value} initialValue The initial value
9 | * @param {Value} defaultValue The default value
10 | * @returns {[Value, (value: Value) => void]} An array containing the current value and a function to set the value
11 | *
12 | * @example
13 | * const [value, setValue] = useDefault(initialValue, defaultValue);
14 | */
15 | export const useDefault = (initialValue, defaultValue) => {
16 | const [value, setValue] = useState(initialValue);
17 | return [value === undefined || value === null ? defaultValue : value, setValue];
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDidUpdate/useDidUpdate.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
3 | /**
4 | * @name useDidUpdate
5 | * @description – Hook that triggers the effect callback on updates
6 | * @category Lifecycle
7 | *
8 | * @param {EffectCallback} effect The effect callback
9 | * @param {DependencyList} [deps] The dependencies list for the effect
10 | *
11 | * @example
12 | * useDidUpdate(() => console.log("effect runs on updates"), deps);
13 | */
14 | export const useDidUpdate = (effect, deps) => {
15 | const mounted = useRef(false);
16 | useIsomorphicLayoutEffect(
17 | () => () => {
18 | mounted.current = false;
19 | },
20 | []
21 | );
22 | useIsomorphicLayoutEffect(() => {
23 | if (mounted.current) {
24 | return effect();
25 | }
26 | mounted.current = true;
27 | return undefined;
28 | }, deps);
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDocumentEvent/useDocumentEvent.js:
--------------------------------------------------------------------------------
1 | import { target } from '@/utils/helpers';
2 | import { useEventListener } from '../useEventListener/useEventListener';
3 | /**
4 | * @name useDocumentEvent
5 | * @description - Hook attaches an event listener to the document object for the specified event
6 | * @category Browser
7 | *
8 | * @template Event Key of document event map.
9 | * @param {Event} event The event to listen for.
10 | * @param {(event: DocumentEventMap[Event]) => void} listener The callback function to be executed when the event is triggered
11 | * @param {UseEventListenerOptions} [options] The options for the event listener
12 | * @returns {void}
13 | *
14 | * @example
15 | * useDocumentEvent('click', () => console.log('clicked'));
16 | */
17 | export const useDocumentEvent = (event, listener, options) =>
18 | useEventListener(target(document), event, listener, options);
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useDocumentVisibility/useDocumentVisibility.js:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 | const getSnapshot = () => document.visibilityState;
3 | const getServerSnapshot = () => 'hidden';
4 | const subscribe = (callback) => {
5 | document.addEventListener('visibilitychange', callback);
6 | return () => {
7 | document.removeEventListener('visibilitychange', callback);
8 | };
9 | };
10 | /**
11 | * @name useDocumentVisibility
12 | * @description – Hook that provides the current visibility state of the document
13 | * @category Browser
14 | *
15 | * @returns {DocumentVisibilityState} The current visibility state of the document, which can be 'visible' or 'hidden'
16 | *
17 | * @example
18 | * const visibilityState = useDocumentVisibility();
19 | */
20 | export const useDocumentVisibility = () =>
21 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useEvent/useEvent.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react';
2 | /**
3 | * @name useEvent
4 | * @description - Hook that creates an event and returns a stable reference of it
5 | * @category Browser
6 | *
7 | * @template Params The type of the params
8 | * @template Return The type of the return
9 | * @param {(...args: Params) => Return} callback The callback function
10 | * @returns {(...args: Params) => Return} The callback
11 | *
12 | * @example
13 | * const onClick = useEvent(() => console.log('clicked'));
14 | */
15 | export const useEvent = (callback) => {
16 | const callbackRef = useRef(callback);
17 | callbackRef.current = callback;
18 | return useCallback((...args) => {
19 | const fn = callbackRef.current;
20 | return fn(...args);
21 | }, []);
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useFul/useFul.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | /**
3 | * @name useFul
4 | * @description - Hook that can be so useful
5 | * @category Humor
6 | *
7 | * @warning - This hook is a joke. Please do not use it in production code!
8 | *
9 | * @template Value The type of the value
10 | * @param {Value} [value] The value to be returned
11 | * @returns {Value} The value passed to the hook
12 | *
13 | * @example
14 | * const value = useFul(state);
15 | */
16 | export const useFul = (value) => {
17 | useEffect(() => {
18 | console.warn("Warning: You forgot to delete the 'useFul' hook.");
19 | }, []);
20 | return value;
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useGamepad/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './mapGamepadToXbox360Controller';
2 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useGamepad/helpers/mapGamepadToXbox360Controller.js:
--------------------------------------------------------------------------------
1 | export const mapGamepadToXbox360Controller = (gamepad) => ({
2 | buttons: {
3 | a: gamepad.buttons[0],
4 | b: gamepad.buttons[1],
5 | x: gamepad.buttons[2],
6 | y: gamepad.buttons[3]
7 | },
8 | bumper: {
9 | left: gamepad.buttons[4],
10 | right: gamepad.buttons[5]
11 | },
12 | triggers: {
13 | left: gamepad.buttons[6],
14 | right: gamepad.buttons[7]
15 | },
16 | stick: {
17 | left: {
18 | horizontal: gamepad.axes[0],
19 | vertical: gamepad.axes[1],
20 | button: gamepad.buttons[10]
21 | },
22 | right: {
23 | horizontal: gamepad.axes[2],
24 | vertical: gamepad.axes[3],
25 | button: gamepad.buttons[11]
26 | }
27 | },
28 | dpad: {
29 | up: gamepad.buttons[12],
30 | down: gamepad.buttons[13],
31 | left: gamepad.buttons[14],
32 | right: gamepad.buttons[15]
33 | },
34 | back: gamepad.buttons[8],
35 | start: gamepad.buttons[9]
36 | });
37 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useHash/useHash.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | const getHash = () => decodeURIComponent(window.location.hash.replace('#', ''));
3 | /**
4 | * @name useHash
5 | * @description - Hook that manages the hash value
6 | * @category Browser
7 | *
8 | * @returns {UseHashReturn} An array containing the hash value and a function to set the hash value
9 | *
10 | * @example
11 | * const [hash, setHash] = useHash();
12 | */
13 | export const useHash = () => {
14 | const [hash, setHash] = useState(window ? getHash() : '');
15 | const set = (value) => {
16 | window.location.hash = value;
17 | setHash(value);
18 | };
19 | useEffect(() => {
20 | const onHashChange = () => setHash(getHash());
21 | window.addEventListener('hashchange', onHashChange);
22 | return () => {
23 | window.removeEventListener('hashchange', onHashChange);
24 | };
25 | });
26 | return [hash, set];
27 | };
28 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useIsFirstRender/useIsFirstRender.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | /**
3 | * @name useIsFirstRender
4 | * @description - Hook that returns true if the component is first render
5 | * @category Lifecycle
6 | *
7 | * @returns {boolean} True if the component is first render
8 | *
9 | * @example
10 | * const isFirstRender = useIsFirstRender();
11 | */
12 | export const useIsFirstRender = () => {
13 | const renderRef = useRef(true);
14 | if (renderRef.current === true) {
15 | renderRef.current = false;
16 | return true;
17 | }
18 | return renderRef.current;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from 'react';
2 | /**
3 | * @name useIsomorphicLayoutEffect
4 | * @description - Hook conditionally selects either `useLayoutEffect` or `useEffect` based on the environment
5 | * @category Lifecycle
6 | *
7 | * @example
8 | * useIsomorphicLayoutEffect(() => console.log('effect'), [])
9 | */
10 | export const useIsomorphicLayoutEffect =
11 | typeof window !== 'undefined' ? useLayoutEffect : useEffect;
12 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLastChanged/useLastChanged.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useDidUpdate } from '../useDidUpdate/useDidUpdate';
3 | /**
4 | * @name useLastChanged
5 | * @description - Hook for records the timestamp of the last change
6 | * @category Time
7 | *
8 | * @param {any} source The source of the last change
9 | * @param {number | null} [options.initialValue=null] The initial value
10 | * @returns {number | null} Return timestamp of the last change
11 | *
12 | * @example
13 | * const lastChanged = useLastChanged(source);
14 | */
15 | export const useLastChanged = (source, options) => {
16 | const [lastChanged, setLastChanged] = useState(options?.initialValue ?? null);
17 | useDidUpdate(() => setLastChanged(Date.now()), [source]);
18 | return lastChanged;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLatest/useLatest.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | /**
3 | * @name useLatest
4 | * @description - Hook that returns the stable reference of the value
5 | * @category Utilities
6 | *
7 | * @template Value The type of the value
8 | * @param {Value} value The value to get the previous value
9 | * @returns {Value} The previous value
10 | *
11 | * @example
12 | * const latestValue = useLatest(value);
13 | */
14 | export const useLatest = (value) => {
15 | const valueRef = useRef(value);
16 | valueRef.current = value;
17 | return valueRef.current;
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLess/useLess.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | /**
3 | * @name useLess
4 | * @description - Hook that can be so useless
5 | * @category Humor
6 | *
7 | * @warning - This hook is a joke. Please do not use it in production code!
8 | *
9 | * @template Value The type of the value
10 | * @param {Value} [value] The value to be returned
11 | * @returns {Value} The value passed to the hook
12 | *
13 | * @example
14 | * const value = useLess(state);
15 | */
16 | export const useLess = (value) => {
17 | useEffect(() => {
18 | console.warn("Warning: You forgot to delete the 'useLess' hook.");
19 | }, []);
20 | return value;
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLocalStorage/useLocalStorage.js:
--------------------------------------------------------------------------------
1 | import { useStorage } from '../useStorage/useStorage';
2 | /**
3 | * @name useLocalStorage
4 | * @description - Hook that manages local storage value
5 | * @category Browser
6 | *
7 | * @browserapi localStorage https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
8 | *
9 | * @template Value The type of the value
10 | * @param {string} key The key of the storage
11 | * @param {UseStorageInitialValue} [initialValue] The initial value of the storage
12 | * @param {UseStorageOptions} [options] The options of the storage
13 | *
14 | * @example
15 | * const { value, set, remove } = useLocalStorage('key', 'value');
16 | */
17 | export const useLocalStorage = (key, initialValue, options) =>
18 | useStorage(key, {
19 | ...options,
20 | initialValue,
21 | storage: typeof window !== 'undefined' ? window.localStorage : undefined
22 | });
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLockCallback/useLockCallback.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | /**
3 | * @name useLockCallback
4 | * @description - Hook that prevents a callback from being executed multiple times simultaneously
5 | * @category Utilities
6 | *
7 | * @param {Function} callback The callback to be locked
8 | * @returns {Function} The locked callback
9 | *
10 | * @example
11 | * const lockedCallback = useLockCallback(() => promise());
12 | */
13 | export const useLockCallback = (callback) => {
14 | const lockRef = useRef(false);
15 | const callbackRef = useRef(callback);
16 | callbackRef.current = callback;
17 | return async (...args) => {
18 | if (lockRef.current) return;
19 | lockRef.current = true;
20 | try {
21 | return await callbackRef.current(...args);
22 | } finally {
23 | lockRef.current = false;
24 | }
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useLogger/useLogger.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDidUpdate } from '../useDidUpdate/useDidUpdate';
3 | /**
4 | * @name useLogger
5 | * @description - Hook for debugging lifecycle
6 | * @category Lifecycle
7 | *
8 | * @param {string} name The name or identifier for the logger
9 | * @param {unknown[]} params Additional arguments to be logged
10 | *
11 | * @example
12 | * useLogger('Component', [1, 2, 3]);
13 | */
14 | export const useLogger = (name, params) => {
15 | useEffect(() => {
16 | console.log(`${name} mounted`, ...params);
17 | return () => console.log(`${name} unmounted`);
18 | }, []);
19 | useDidUpdate(() => {
20 | console.log(`${name} updated`, ...params);
21 | }, params);
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useMediaQuery/useMediaQuery.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useSyncExternalStore } from 'react';
2 | const getServerSnapshot = () => false;
3 | /**
4 | * @name useMediaQuery
5 | * @description - Hook that manages a media query
6 | * @category Browser
7 | *
8 | * @browserapi window.matchMedia https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
9 | *
10 | * @param {string} query The media query string
11 | * @returns {boolean} A boolean indicating if the media query matches
12 | *
13 | * @example
14 | * const matches = useMediaQuery('(max-width: 768px)');
15 | */
16 | export const useMediaQuery = (query) => {
17 | const subscribe = useCallback(
18 | (callback) => {
19 | const matchMedia = window.matchMedia(query);
20 | matchMedia.addEventListener('change', callback);
21 | return () => {
22 | matchMedia.removeEventListener('change', callback);
23 | };
24 | },
25 | [query]
26 | );
27 | const getSnapshot = () => window.matchMedia(query).matches;
28 | return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useMemory/useMemory.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useInterval } from '../useInterval/useInterval';
3 | /**
4 | * @name useMemory
5 | * @description - Hook that gives you current memory usage
6 | * @category Browser
7 | *
8 | * @browserapi performance.memory https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory
9 | *
10 | * @returns {UseMemoryReturn} An object containing the current memory usage
11 | *
12 | * @example
13 | * const { supported, value } = useMemory();
14 | */
15 | export const useMemory = () => {
16 | const supported = performance && 'memory' in performance && !!performance.memory;
17 | const [value, setValue] = useState(
18 | performance?.memory ?? {
19 | jsHeapSizeLimit: 0,
20 | totalJSHeapSize: 0,
21 | usedJSHeapSize: 0
22 | }
23 | );
24 | useInterval(() => setValue(performance.memory), 1000, {
25 | immediately: supported
26 | });
27 | return { supported, value };
28 | };
29 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useMount/useMount.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | /**
3 | * @name useMount
4 | * @description - Hook that executes a callback when the component mounts
5 | * @category Lifecycle
6 | *
7 | * @param {EffectCallback} effect The callback to execute
8 | *
9 | * @example
10 | * useMount(() => console.log('This effect runs on the initial render'));
11 | */
12 | export const useMount = (effect) => useEffect(effect, []);
13 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useOnce/useOnce.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | /**
3 | * @name useEffectOnce
4 | * @description - Hook that runs an effect only once. Please do not use it in production code!
5 | * @category Humor
6 | *
7 | * @warning - This hook will run effect only once even in strict mode. Please do not use it in production code!
8 | *
9 | * @param {EffectCallback} effect The effect to run
10 | *
11 | * @example
12 | * useOnce(() => console.log('effect once'));
13 | */
14 | export function useOnce(effect) {
15 | const cleanupRef = useRef(undefined);
16 | const hasRunRef = useRef(false);
17 | const hasRenderedAfterRun = useRef(false);
18 | if (hasRunRef.current) {
19 | hasRenderedAfterRun.current = true;
20 | }
21 | useEffect(() => {
22 | if (hasRunRef.current) return;
23 | hasRunRef.current = true;
24 | cleanupRef.current = effect();
25 | return () => {
26 | if (!hasRenderedAfterRun.current) return;
27 | if (typeof cleanupRef.current === 'function') {
28 | cleanupRef.current();
29 | }
30 | };
31 | }, []);
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useOnline/useOnline.js:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 | const getSnapshot = () => navigator.onLine;
3 | const getServerSnapshot = () => false;
4 | const subscribe = (callback) => {
5 | window.addEventListener('online', callback);
6 | window.addEventListener('offline', callback);
7 | return () => {
8 | window.removeEventListener('online', callback);
9 | window.removeEventListener('offline', callback);
10 | };
11 | };
12 | /**
13 | * @name useOnline
14 | * @description - Hook that manages if the user is online
15 | * @category Sensors
16 | *
17 | * @browserapi navigator.onLine https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
18 | *
19 | * @returns {boolean} A boolean indicating if the user is online
20 | *
21 | * @example
22 | * const online = useOnline();
23 | */
24 | export const useOnline = () => useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
25 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useOperatingSystem/useOperatingSystem.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | export const getOperatingSystem = () => {
3 | if (typeof window === 'undefined') return 'undetermined';
4 | const { userAgent } = window.navigator;
5 | if (/Macintosh|MacIntel|MacPPC|Mac68K/i.test(userAgent)) return 'macos';
6 | if (/iPhone|iPad|iPod/i.test(userAgent)) return 'ios';
7 | if (/Win32|Win64|Windows|WinCE/i.test(userAgent)) return 'windows';
8 | if (/Android/i.test(userAgent)) return 'android';
9 | if (/Linux/i.test(userAgent)) return 'linux';
10 | return 'undetermined';
11 | };
12 | /**
13 | * @name useOperatingSystem
14 | * @description - Hook that returns the operating system of the current browser
15 | * @category Browser
16 | *
17 | * @returns {OperatingSystem} The operating system
18 | *
19 | * @example
20 | * const operatingSystem = useOperatingSystem();
21 | */
22 | export const useOperatingSystem = () => {
23 | const [osOperatingSystem] = useState(getOperatingSystem());
24 | return osOperatingSystem;
25 | };
26 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useOrientation/useOrientation.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | /**
3 | * @name useOrientation
4 | * @description - Hook that returns the current screen orientation
5 | * @category Browser
6 | *
7 | * @browserapi window.screen.orientation https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation
8 | *
9 | * @returns {UseOrientationReturn} An object containing the current screen orientation
10 | *
11 | * @example
12 | * const { angle, type } = useOrientation();
13 | */
14 | export const useOrientation = () => {
15 | const [orientation, setOrientation] = useState({ angle: 0, type: 'landscape-primary' });
16 | useEffect(() => {
17 | const onChange = () => setOrientation(window.screen.orientation);
18 | window.screen.orientation.addEventListener('change', onChange);
19 | return () => {
20 | window.screen.orientation.removeEventListener('change', onChange);
21 | };
22 | }, []);
23 | return orientation;
24 | };
25 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePaint/helpers/Pointer.js:
--------------------------------------------------------------------------------
1 | export class Pointer {
2 | x;
3 | y;
4 | constructor(x, y) {
5 | this.x = x;
6 | this.y = y;
7 | }
8 | update(point) {
9 | this.x = point.x;
10 | this.y = point.y;
11 | }
12 | getDifferenceTo(point) {
13 | return new Pointer(this.x - point.x, this.y - point.y);
14 | }
15 | getDistanceTo(point) {
16 | const diff = this.getDifferenceTo(point);
17 | return Math.sqrt(diff.x ** 2 + diff.y ** 2);
18 | }
19 | getAngleTo(point) {
20 | const diff = this.getDifferenceTo(point);
21 | return Math.atan2(diff.y, diff.x);
22 | }
23 | equalsTo(point) {
24 | return this.x === point.x && this.y === point.y;
25 | }
26 | moveByAngle(
27 | // The angle in radians
28 | angle,
29 | // How much the point should be moved
30 | distance
31 | ) {
32 | // Rotate the angle based on the browser coordinate system ([0,0] in the top left)
33 | const angleRotated = angle + Math.PI / 2;
34 | this.x += Math.sin(angleRotated) * distance;
35 | this.y -= Math.cos(angleRotated) * distance;
36 | return this;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePaint/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './Paint';
2 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePreferredColorScheme/usePreferredColorScheme.js:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 | /**
3 | * @name usePreferredColorScheme
4 | * @description - Hook that returns user preferred color scheme
5 | * @category Browser
6 | *
7 | * @returns {UsePreferredColorSchemeReturn} String of preferred color scheme
8 | *
9 | * @example
10 | * const colorScheme = usePreferredColorScheme();
11 | */
12 | export const usePreferredColorScheme = () => {
13 | const isLight = useMediaQuery('(prefers-color-scheme: light)');
14 | const isDark = useMediaQuery('(prefers-color-scheme: dark)');
15 | if (isLight) return 'light';
16 | if (isDark) return 'dark';
17 | return 'no-preference';
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePreferredContrast/usePreferredContrast.js:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 | /**
3 | * @name usePreferredContrast
4 | * @description - Hook that returns the contrast preference
5 | * @category Browser
6 | *
7 | * @returns {UsePreferredContrastReturn} The contrast preference
8 | *
9 | * @example
10 | * const contrast = usePreferredContrast();
11 | */
12 | export const usePreferredContrast = () => {
13 | const more = useMediaQuery('(prefers-contrast: more)');
14 | const less = useMediaQuery('(prefers-contrast: less)');
15 | const custom = useMediaQuery('(prefers-contrast: custom)');
16 | return more ? 'more' : less ? 'less' : custom ? 'custom' : 'no-preference';
17 | };
18 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePreferredDark/usePreferredDark.js:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 | /**
3 | * @name usePreferredDark
4 | * @description - Hook that returns if the user prefers dark mode
5 | * @category Browser
6 | *
7 | * @example
8 | * const isDark = usePreferredDark();
9 | */
10 | export const usePreferredDark = () => useMediaQuery('(prefers-color-scheme: dark)');
11 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePreferredLanguages/usePreferredLanguages.js:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 | const getSnapshot = () => window.navigator.languages;
3 | const getServerSnapshot = () => [];
4 | const subscribe = (callback) => {
5 | window.addEventListener('languagechange', callback);
6 | return () => {
7 | window.removeEventListener('languagechange', callback);
8 | };
9 | };
10 | /**
11 | * @name usePreferredLanguages
12 | * @description Hook that returns a browser preferred languages from navigator
13 | * @category Browser
14 | *
15 | * @browserapi navigator.languages https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages
16 | *
17 | * @returns {readonly string[]} An array of strings representing the user's preferred languages
18 | *
19 | * @example
20 | * const languages = usePreferredLanguages();
21 | */
22 | export const usePreferredLanguages = () =>
23 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
24 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePreferredReducedMotion/usePreferredReducedMotion.js:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 | /**
3 | * @name usePreferredReducedMotion
4 | * @description - Hook that returns the reduced motion preference
5 | * @category Browser
6 | *
7 | * @returns {UsePreferredReducedMotionReturn} The reduced motion preference
8 | *
9 | * @example
10 | * const reduced = usePreferredReducedMotion();
11 | */
12 | export const usePreferredReducedMotion = () => {
13 | const reduced = useMediaQuery('(prefers-reduced-motion: reduce)');
14 | return reduced ? 'reduce' : 'no-preference';
15 | };
16 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/usePrevious/usePrevious.js:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | /**
3 | * @name usePrevious
4 | * @description - Hook that returns the previous value
5 | * @category Utilities
6 | *
7 | * @template Value The type of the value
8 | * @param {Value} value The value to get the previous value
9 | * @param {(a: Value, b: Value) => boolean} [options.equality] The custom equality function to determine if the value has changed
10 | * @returns {Value | undefined} The previous value
11 | *
12 | * @example
13 | * const prevValue = usePrevious(value);
14 | */
15 | export const usePrevious = (value, options) => {
16 | const currentRef = useRef(value);
17 | const previousRef = useRef(undefined);
18 | const equality = options?.equality ?? Object.is;
19 | if (!equality(value, currentRef.current)) {
20 | previousRef.current = currentRef.current;
21 | currentRef.current = value;
22 | }
23 | return previousRef.current;
24 | };
25 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useRafValue/useRafValue.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react';
2 | import { useUnmount } from '../useUnmount/useUnmount';
3 | /**
4 | * @name useRafValue
5 | * @description - Hook that returns the value and a function to set the value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {Value} initialValue The initial value
10 | * @returns {UseRafValueReturn} An array containing the value and a function to set the value
11 | *
12 | * @example
13 | * const [value, setValue] = useRafValue(initialValue);
14 | */
15 | export const useRafValue = (initialValue) => {
16 | const rafIdRef = useRef(0);
17 | const [value, setValue] = useState(initialValue);
18 | const set = (value) => {
19 | cancelAnimationFrame(rafIdRef.current);
20 | rafIdRef.current = requestAnimationFrame(() => setValue(value));
21 | };
22 | useUnmount(() => cancelAnimationFrame(rafIdRef.current));
23 | return [value, set];
24 | };
25 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useRenderCount/useRenderCount.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | /**
3 | * @name useRenderCount
4 | * @description - Hook returns count component render times
5 | * @category Lifecycle
6 | *
7 | * @returns {number} A number which determines how many times component renders
8 | *
9 | * @example
10 | * const renderCount = useRenderCount();
11 | */
12 | export const useRenderCount = () => {
13 | const renderCountRef = useRef(0);
14 | useEffect(() => {
15 | renderCountRef.current += 1;
16 | });
17 | return renderCountRef.current;
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useRerender/useRerender.js:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 | /**
3 | * @name useRerender
4 | * @description - Hook that defines the logic to force rerender a component
5 | * @category Lifecycle
6 | *
7 | * @returns {UseRerenderReturn} The rerender function
8 | *
9 | * @example
10 | * const rerender = useRerender();
11 | */
12 | export const useRerender = () => {
13 | const rerender = useReducer(() => ({}), {})[1];
14 | return rerender;
15 | };
16 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useSessionStorage/useSessionStorage.js:
--------------------------------------------------------------------------------
1 | import { useStorage } from '../useStorage/useStorage';
2 | /**
3 | * @name useSessionStorage
4 | * @description - Hook that manages session storage value
5 | * @category Browser
6 | *
7 | * @browserapi sessionStorage https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
8 | *
9 | * @template Value The type of the value
10 | * @param {string} key The key of the storage
11 | * @param {UseStorageInitialValue} [initialValue] The initial value of the storage
12 | * @param {UseStorageOptions} [options] The options of the storage
13 | *
14 | * @example
15 | * const { value, set, remove } = useSessionStorage('key', 'value');
16 | */
17 | export const useSessionStorage = (key, initialValue, options) =>
18 | useStorage(key, {
19 | ...options,
20 | initialValue,
21 | storage: typeof window !== 'undefined' ? window.sessionStorage : undefined
22 | });
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useShare/useShare.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name useShare
3 | * @description - Hook that utilizes the share api
4 | * @category Browser
5 | *
6 | * @param {UseShareParams} [params] The use share options
7 | * @returns {UseShareReturn}
8 | *
9 | * @example
10 | * const { share, supported } = useShare();
11 | */
12 | export const useShare = (params) => {
13 | const supported = typeof navigator !== 'undefined' && 'share' in navigator;
14 | const share = async (shareParams) => {
15 | if (!supported) return;
16 | const data = {
17 | ...params,
18 | ...shareParams
19 | };
20 | if (data.files && navigator.canShare({ files: data.files })) navigator.share(data);
21 | return navigator.share(data);
22 | };
23 | return { share, supported };
24 | };
25 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useThrottleCallback/useThrottleCallback.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { throttle } from '@/utils/helpers';
3 | import { useEvent } from '../useEvent/useEvent';
4 | /**
5 | * @name useThrottleCallback
6 | * @description - Hook that creates a throttled callback
7 | * @category Utilities
8 | *
9 | * @template Params The type of the params
10 | * @template Return The type of the return
11 | * @param {(...args: Params) => Return} callback The callback function
12 | * @param {number} delay The delay in milliseconds
13 | * @returns {(...args: Params) => Return} The callback with throttle
14 | *
15 | * @example
16 | * const throttled = useThrottleCallback(() => console.log('callback'), 500);
17 | */
18 | export const useThrottleCallback = (callback, delay) => {
19 | const internalCallback = useEvent(callback);
20 | const throttled = useMemo(() => throttle(internalCallback, delay), [delay]);
21 | return throttled;
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useThrottleValue/useThrottleValue.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import { useThrottleCallback } from '../useThrottleCallback/useThrottleCallback';
3 | /**
4 | * @name useThrottleValue
5 | * @description - Hook that creates a throttled value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {Value} value The value to be throttled
10 | * @param {number} delay The delay in milliseconds
11 | * @returns {Value} The throttled value
12 | *
13 | * @example
14 | * const throttledValue = useThrottleValue(value, 500);
15 | */
16 | export const useThrottleValue = (value, delay) => {
17 | const previousValueRef = useRef(value);
18 | const [throttledValue, setThrottleValue] = useState(value);
19 | const throttledSetState = useThrottleCallback(setThrottleValue, delay);
20 | useEffect(() => {
21 | if (previousValueRef.current === value) return;
22 | throttledSetState(value);
23 | previousValueRef.current = value;
24 | }, [value]);
25 | return throttledValue;
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useTime/useTime.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { getDate } from '@/utils/helpers';
3 | import { useInterval } from '../useInterval/useInterval';
4 | /**
5 | * @name useTime
6 | * @description - Hook that gives you current time in different values
7 | * @category Time
8 | *
9 | * @returns {UseTimeReturn} An object containing the current time
10 | *
11 | * @example
12 | * const { seconds, minutes, hours, meridiemHours, day, month, year, timestamp } = useTime();
13 | */
14 | export const useTime = () => {
15 | const [time, setTime] = useState(getDate());
16 | useInterval(() => setTime(getDate()), 1000);
17 | return time;
18 | };
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useToggle/useToggle.js:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 | /**
3 | * @name useToggle
4 | * @description - Hook that create toggle
5 | * @category Utilities
6 | *
7 | * @template Value The type of the value
8 | * @param {Value[]} [values=[false, true]] The values to toggle
9 | *
10 | * @example
11 | * const [on, toggle] = useToggle();
12 | *
13 | * @example
14 | * const [value, toggle] = useToggle(['light', 'dark'] as const);
15 | */
16 | export const useToggle = (values = [false, true]) => {
17 | const [[option], toggle] = useReducer((state, action) => {
18 | const value = typeof action === 'function' ? action(state[0]) : action;
19 | const index = Math.abs(state.indexOf(value));
20 | return state.slice(index).concat(state.slice(0, index));
21 | }, values);
22 | return [option, toggle];
23 | };
24 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useUnmount/useUnmount.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | /**
3 | * @name useUnmount
4 | * @description - Hook that defines the logic when unmounting a component
5 | * @category Lifecycle
6 | *
7 | * @param {() => void} callback The callback function to be invoked on component unmount
8 | * @returns {void}
9 | *
10 | * @example
11 | * useUnmount(() => console.log('This effect runs on component unmount'));
12 | */
13 | export const useUnmount = (callback) => {
14 | const internalCallbackRef = useRef(callback);
15 | internalCallbackRef.current = callback;
16 | useEffect(
17 | () => () => {
18 | internalCallbackRef.current();
19 | },
20 | []
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useWindowEvent/useWindowEvent.js:
--------------------------------------------------------------------------------
1 | import { target } from '@/utils/helpers';
2 | import { useEventListener } from '../useEventListener/useEventListener';
3 | /**
4 | * @name useWindowEvent
5 | * @description - Hook attaches an event listener to the window object for the specified event
6 | * @category Browser
7 | *
8 | * @template Event Key of window event map.
9 | * @param {Event} event The event to listen for.
10 | * @param {(event: WindowEventMap[Event]) => void} listener The callback function to be executed when the event is triggered
11 | * @param {UseEventListenerOptions} [options] The options for the event listener
12 | * @returns {void}
13 | *
14 | * @example
15 | * useWindowEvent('click', () => console.log('clicked'));
16 | */
17 | export const useWindowEvent = (event, listener, options) =>
18 | useEventListener(target(window), event, listener, options);
19 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/hooks/useWindowFocus/useWindowFocus.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | /**
3 | * @name useWindowFocus
4 | * @description - Hook that provides the current focus state of the window
5 | * @category Elements
6 | *
7 | * @returns {boolean} The current focus state of the window
8 | *
9 | * @example
10 | * const focused = useWindowFocus();
11 | *
12 | * @see {@link https://siberiacancode.github.io/reactuse/functions/hooks/useWindowFocus.html}
13 | */
14 | export const useWindowFocus = () => {
15 | const [focused, setFocused] = useState(false);
16 | useEffect(() => {
17 | const onFocus = () => setFocused(true);
18 | const onBlur = () => setFocused(false);
19 | window.addEventListener('focus', onFocus);
20 | window.addEventListener('blur', onBlur);
21 | return () => {
22 | window.removeEventListener('focus', onFocus);
23 | window.removeEventListener('blur', onBlur);
24 | };
25 | });
26 | return focused;
27 | };
28 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/index.js:
--------------------------------------------------------------------------------
1 | export * from './helpers';
2 | export * from './hooks';
3 | export * from './utils/helpers';
4 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/copy.js:
--------------------------------------------------------------------------------
1 | export const legacyCopyToClipboard = (value) => {
2 | const tempTextArea = document.createElement('textarea');
3 | tempTextArea.value = value;
4 | tempTextArea.readOnly = true;
5 | tempTextArea.style.fontSize = '16px';
6 | document.body.appendChild(tempTextArea);
7 | tempTextArea.select();
8 | document.execCommand('copy');
9 | document.body.removeChild(tempTextArea);
10 | };
11 | export const copy = async (value) => {
12 | try {
13 | try {
14 | await navigator.clipboard.writeText(value);
15 | } catch {
16 | return legacyCopyToClipboard(value);
17 | }
18 | } catch {
19 | return legacyCopyToClipboard(value);
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/debounce.js:
--------------------------------------------------------------------------------
1 | export function debounce(callback, delay) {
2 | let timer;
3 | return function (...args) {
4 | clearTimeout(timer);
5 | timer = setTimeout(() => callback.apply(this, args), delay);
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/getDate.js:
--------------------------------------------------------------------------------
1 | export const getDate = (now = new Date()) => {
2 | const seconds = now.getSeconds();
3 | const minutes = now.getMinutes();
4 | const hours = now.getHours();
5 | const meridiemHours = hours % 12 === 0 ? 12 : hours % 12;
6 | const meridiemType = hours >= 12 ? 'pm' : 'am';
7 | const day = now.getDate();
8 | const month = now.getMonth() + 1;
9 | const year = now.getFullYear();
10 | const timestamp = now.getTime();
11 | return {
12 | seconds,
13 | minutes,
14 | hours,
15 | meridiemHours: { value: meridiemHours, type: meridiemType },
16 | day,
17 | month,
18 | year,
19 | timestamp
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/getElement.js:
--------------------------------------------------------------------------------
1 | export const targetSymbol = Symbol('target');
2 | export const target = (target) => ({
3 | value: target,
4 | type: targetSymbol
5 | });
6 | export const getElement = (target) => {
7 | if ('current' in target) {
8 | return target.current;
9 | }
10 | if (typeof target.value === 'function') {
11 | return target.value();
12 | }
13 | if (typeof target.value === 'string') {
14 | return document.querySelector(target.value);
15 | }
16 | if (target.value instanceof Document) {
17 | return target.value;
18 | }
19 | if (target.value instanceof Window) {
20 | return target.value;
21 | }
22 | if (target.value instanceof Element) {
23 | return target.value;
24 | }
25 | return target.value;
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/getRetry.js:
--------------------------------------------------------------------------------
1 | export const getRetry = (retry) => {
2 | if (typeof retry === 'number') return retry;
3 | return retry ? 1 : 0;
4 | };
5 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/index.js:
--------------------------------------------------------------------------------
1 | export * from './copy';
2 | export * from './debounce';
3 | export * from './getDate';
4 | export * from './getElement';
5 | export * from './getRetry';
6 | export * from './isTarget';
7 | export * from './throttle';
8 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/isTarget.js:
--------------------------------------------------------------------------------
1 | import { targetSymbol } from './getElement';
2 | export const isTarget = (target) =>
3 | typeof target === 'object' && ('current' in target || target.type === targetSymbol);
4 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/throttle.js:
--------------------------------------------------------------------------------
1 | export const throttle = (callback, delay) => {
2 | let isCalled = false;
3 | let lastArgs = null;
4 | const timer = () => {
5 | if (!lastArgs) {
6 | isCalled = false;
7 | return;
8 | }
9 | callback.apply(this, lastArgs);
10 | lastArgs = null;
11 | setTimeout(timer, delay);
12 | };
13 | return function (...args) {
14 | if (isCalled) {
15 | lastArgs = args;
16 | return;
17 | }
18 | callback.apply(this, args);
19 | isCalled = true;
20 | setTimeout(timer, delay);
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/bundle/utils/helpers/time/getDate.js:
--------------------------------------------------------------------------------
1 | export const getDate = (now = new Date()) => {
2 | const seconds = now.getSeconds();
3 | const minutes = now.getMinutes();
4 | const hours = now.getHours();
5 | const meridiemHours = hours % 12 === 0 ? 12 : hours % 12;
6 | const meridiemType = hours >= 12 ? 'pm' : 'am';
7 | const day = now.getDate();
8 | const month = now.getMonth() + 1;
9 | const year = now.getFullYear();
10 | const timestamp = now.getTime();
11 | return {
12 | seconds,
13 | minutes,
14 | hours,
15 | meridiemHours: { value: meridiemHours, type: meridiemType },
16 | day,
17 | month,
18 | year,
19 | timestamp
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/helpers/createContext/createContext.test.tsx:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react';
2 |
3 | import { createContext } from './createContext';
4 |
5 | const countContext = createContext(0);
6 |
7 | it('Should return initial value from provider', () => {
8 | const { result } = renderHook(() => countContext.useSelect(), {
9 | wrapper: ({ children }) => (
10 | {children}
11 | )
12 | });
13 |
14 | expect(result.current.value).toBe(1);
15 | expect(result.current.set).toBeTypeOf('function');
16 | });
17 |
18 | it('Should update value', () => {
19 | const { result } = renderHook(() => countContext.useSelect(), {
20 | wrapper: ({ children }) => (
21 | {children}
22 | )
23 | });
24 |
25 | expect(result.current.value).toBe(1);
26 |
27 | act(() => result.current.set(2));
28 |
29 | expect(result.current.value).toBe(2);
30 | });
31 |
--------------------------------------------------------------------------------
/packages/core/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createContext/createContext';
2 | export * from './createReactiveContext/createReactiveContext';
3 | export * from './createStore/createStore';
4 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useActiveElement/useActiveElement.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useActiveElement } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const activeElement = useActiveElement();
5 | const activeElementId = activeElement?.dataset?.id ?? 'null';
6 |
7 | return (
8 | <>
9 |
10 | {Array.from({ length: 6 }, (_, i) => i + 1).map((id) => (
11 |
18 | ))}
19 |
20 |
21 |
22 | current active element: {activeElementId}
23 |
24 | >
25 | );
26 | };
27 |
28 | export default Demo;
29 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useBoolean/useBoolean.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useBoolean } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const [on, toggle] = useBoolean();
5 |
6 | return (
7 |
8 |
9 | Value: {String(on)}
10 |
11 |
toggle()}>
12 | Toggle
13 |
14 |
toggle(true)}>
15 | Set (true)
16 |
17 |
toggle(false)}>
18 | Set (false)
19 |
20 |
21 | );
22 | };
23 |
24 | export default Demo;
25 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useBoolean/useBoolean.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react';
2 |
3 | import { useBoolean } from './useBoolean';
4 |
5 | it('Should use counter', () => {
6 | const { result } = renderHook(useBoolean);
7 | const [on, toggle] = result.current;
8 |
9 | expect(on).toBeFalsy();
10 | expect(toggle).toBeTypeOf('function');
11 | });
12 |
13 | it('Should set initial value', () => {
14 | const { result } = renderHook(() => useBoolean(true));
15 | const [on] = result.current;
16 |
17 | expect(on).toBeTruthy();
18 | });
19 |
20 | it('Should toggle boolean', () => {
21 | const { result } = renderHook(useBoolean);
22 | const toggle = result.current[1];
23 |
24 | act(toggle);
25 | expect(result.current[0]).toBeTruthy();
26 |
27 | act(toggle);
28 | expect(result.current[0]).toBeFalsy();
29 |
30 | act(() => toggle(false));
31 | expect(result.current[0]).toBeFalsy();
32 |
33 | act(() => toggle(true));
34 | expect(result.current[0]).toBeTruthy();
35 | });
36 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useBoolean/useBoolean.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | /** The use boolean return type */
4 | export type UseBooleanReturn = [
5 | /** The current boolean state value */
6 | value: boolean,
7 | /** Function to toggle the boolean state */
8 | toggle: (value?: boolean) => void
9 | ];
10 |
11 | /**
12 | * @name useBoolean
13 | * @description - Hook provides opportunity to manage boolean state
14 | * @category Utilities
15 | *
16 | * @param {boolean} [initialValue=false] The initial boolean value
17 | * @returns {UseBooleanReturn} An object containing the boolean state value and utility functions to manipulate the state
18 | *
19 | * @example
20 | * const [on, toggle] = useBoolean()
21 | */
22 | export const useBoolean = (initialValue = false): UseBooleanReturn => {
23 | const [value, setValue] = useState(initialValue);
24 | const toggle = (value?: boolean) => setValue((prevValue) => value ?? !prevValue);
25 |
26 | return [value, toggle];
27 | };
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useBrowserLanguage/useBrowserLanguage.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useBrowserLanguage } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const browserLanguage = useBrowserLanguage();
5 |
6 | return (
7 |
8 | Browser language: {browserLanguage}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useBrowserLanguage/useBrowserLanguage.ts:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 |
3 | const getSnapshot = () => navigator.language;
4 | const getServerSnapshot = () => 'undetermined';
5 | const subscribe = (callback: () => void) => {
6 | window.addEventListener('languagechange', callback);
7 | return () => window.removeEventListener('languagechange', callback);
8 | };
9 |
10 | /**
11 | * @name useBrowserLanguage
12 | * @description - Hook that returns the current browser language
13 | * @category Browser
14 | *
15 | * @browserapi navigator.language https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
16 | *
17 | * @returns {string} The current browser language
18 | *
19 | * @example
20 | * const browserLanguage = useBrowserLanguage();
21 | */
22 | export const useBrowserLanguage = () =>
23 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useClickOutside/useClickOutside.demo.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@siberiacancode/docs/utils';
2 | import { useClickOutside, useCounter } from '@siberiacancode/reactuse';
3 |
4 | const Demo = () => {
5 | const counter = useCounter();
6 |
7 | const clickOutsideRef = useClickOutside(() => {
8 | console.log('click outside');
9 | counter.inc();
10 | });
11 |
12 | return (
13 |
14 |
15 | Click more than five times: {counter.value}
16 |
17 |
18 |
5 }
23 | )}
24 | >
25 | {counter.value <= 5 && 'Click outside'}
26 | {counter.value > 5 && counter.value <= 25 && 'Nice work'}
27 | {counter.value > 25 && 'That are a lot of clicks'}
28 |
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useClipboard/useClipboard.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useClipboard, useField } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const clipboard = useClipboard();
5 | const textField = useField();
6 |
7 | return (
8 | <>
9 |
10 | Copied value: {clipboard.value || 'Nothing is copied yet!'}
11 |
12 |
13 |
14 | clipboard.copy(textField.getValue())}>
15 | Copy
16 |
17 | >
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useConst/useConst.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useConst } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const mountTime = useConst(() => new Date().toTimeString());
5 | const obj = useConst({ a: Math.random() });
6 |
7 | return (
8 |
9 |
10 | Mount time: {mountTime}
11 |
12 |
13 | Value from constant object: {obj.a}
14 |
15 |
16 | );
17 | };
18 |
19 | export default Demo;
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useConst/useConst.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useConst } from './useConst';
4 |
5 | it('Should use const', () => {
6 | const { result } = renderHook(() => useConst('value'));
7 |
8 | expect(result.current).toBe('value');
9 | });
10 |
11 | it('should return the same constant value', () => {
12 | const { result, rerender } = renderHook(() => useConst('value'));
13 | expect(result.current).toBe('value');
14 |
15 | rerender();
16 |
17 | expect(result.current).toBe('value');
18 | });
19 |
20 | it('Should call initializer function', () => {
21 | const init = vitest.fn(() => 99);
22 | const { result } = renderHook(() => useConst(init));
23 |
24 | expect(result.current).toBe(99);
25 | expect(init).toHaveBeenCalledOnce();
26 | });
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useConst/useConst.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | /**
4 | * @name useConst
5 | * @description - Hook that returns the constant value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {(() => Value) | Value} initialValue The initial value of the constant
10 | * @returns {Value} The constant value
11 | *
12 | * @example
13 | * const value = useConst('value');
14 | */
15 | export const useConst = (initialValue: (() => Value) | Value) =>
16 | useRef(typeof initialValue === 'function' ? (initialValue as () => Value)() : initialValue)
17 | .current;
18 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useCookie/useCookie.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCookie } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { value, set, remove } = useCookie('siberiacancode-use-cookie', 0);
5 |
6 | return (
7 |
8 |
9 | Count: {value ?? 'value is undefined'}
10 |
11 | {value !== undefined && (
12 | <>
13 |
set(value + 1)}>
14 | Increment
15 |
16 |
set(value - 1)}>
17 | Decrement
18 |
19 | >
20 | )}
21 | {value === undefined && (
22 |
set(0)}>
23 | Set
24 |
25 | )}
26 |
remove()}>
27 | Remove
28 |
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useCookies/useCookies.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCookie, useCookies } from '@siberiacancode/reactuse';
2 |
3 | const POKEMONS = [
4 | { name: 'Pikachu', index: 25 },
5 | { name: 'Bulbasaur', index: 1 },
6 | { name: 'Charmander', index: 4 },
7 | { name: 'Squirtle', index: 7 },
8 | { name: 'Jigglypuff', index: 39 },
9 | { name: 'Gengar', index: 94 },
10 | { name: 'Mewtwo', index: 150 },
11 | { name: 'Mew', index: 151 },
12 | { name: 'Charizard', index: 6 },
13 | { name: 'Blastoise', index: 9 },
14 | { name: 'Venusaur', index: 3 }
15 | ];
16 |
17 | const Demo = () => {
18 | const pokemonsCookie = useCookie('pokemon', POKEMONS[0]);
19 | const cookies = useCookies<{ name: string; id: number }>();
20 |
21 | return (
22 |
23 |
Cookies
24 |
{JSON.stringify(cookies.value, null, 2)}
25 |
26 |
pokemonsCookie.set(POKEMONS[Math.floor(Math.random() * POKEMONS.length)])}
28 | >
29 | Change user
30 |
31 |
Clear
32 |
33 | );
34 | };
35 |
36 | export default Demo;
37 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useCounter/useCounter.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 |
6 | return (
7 | <>
8 |
9 | Count: {counter.value}
10 |
11 | counter.inc()}>
12 | Increment
13 |
14 | counter.dec()}>
15 | Decrement
16 |
17 | counter.set(5)}>
18 | Set (5)
19 |
20 |
21 | Reset
22 |
23 | >
24 | );
25 | };
26 |
27 | export default Demo;
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useCssVar/useCssVar.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCssVar } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const colorVar = useCssVar('--color', '#7fa998');
5 |
6 | const switchColor = () => {
7 | if (colorVar.value === '#df8543') colorVar.set('#7fa998');
8 | else colorVar.set('#df8543');
9 | };
10 |
11 | return (
12 | <>
13 | Sample text, {colorVar.value}
14 |
15 | Change Color
16 |
17 | >
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDebounceCallback/useDebounceCallback.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useDebounceCallback } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 |
6 | const debouncedCounterInc = useDebounceCallback(counter.inc, 500);
7 | const debouncedCounterDec = useDebounceCallback(counter.dec, 500);
8 |
9 | return (
10 |
11 |
12 | Value: {counter.value}
13 |
14 |
debouncedCounterInc()}>
15 | Debounced increment
16 |
17 |
debouncedCounterDec()}>
18 | Debounced decrement
19 |
20 |
21 | );
22 | };
23 |
24 | export default Demo;
25 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDebounceCallback/useDebounceCallback.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { debounce } from '@/utils/helpers';
4 |
5 | import { useEvent } from '../useEvent/useEvent';
6 |
7 | /**
8 | * @name useDebounceCallback
9 | * @description - Hook that creates a debounced callback
10 | * @category Utilities
11 | *
12 | * @template Params The type of the params
13 | * @template Return The type of the return
14 | * @param {(...args: Params) => Return} callback The callback function
15 | * @param {number} delay The delay in milliseconds
16 | * @returns {(...args: Params) => Return} The callback with debounce
17 | *
18 | * @example
19 | * const debouncedCallback = useDebounceCallback(() => console.log('callback'), 500);
20 | */
21 | export const useDebounceCallback = (
22 | callback: (...args: Params) => Return,
23 | delay: number
24 | ) => {
25 | const internalCallback = useEvent(callback);
26 | const debounced = useMemo(() => debounce(internalCallback, delay), [delay]);
27 |
28 | return debounced;
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDebounceValue/useDebounceValue.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useDebounceValue } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 |
6 | const debouncedCounterCount = useDebounceValue(counter.value, 500);
7 |
8 | return (
9 |
10 |
11 | Value: {counter.value}
12 |
13 |
14 | Debounced value: {debouncedCounterCount}
15 |
16 |
counter.inc()}>
17 | Increment
18 |
19 |
counter.dec()}>
20 | Decrement
21 |
22 |
23 | );
24 | };
25 |
26 | export default Demo;
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDebounceValue/useDebounceValue.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 |
3 | import { useDebounceCallback } from '../useDebounceCallback/useDebounceCallback';
4 |
5 | /**
6 | * @name useDebounceValue
7 | * @description - Hook that creates a debounced value
8 | * @category Utilities
9 | *
10 | * @template Value The type of the value
11 | * @param {Value} value The value to be debounced
12 | * @param {number} delay The delay in milliseconds
13 | * @returns {Value} The debounced value
14 | *
15 | * @example
16 | * const debouncedValue = useDebounceValue(value, 500);
17 | */
18 | export const useDebounceValue = (value: Value, delay: number) => {
19 | const previousValueRef = useRef(value);
20 | const [debouncedValue, setDebounceValue] = useState(value);
21 |
22 | const debouncedSetState = useDebounceCallback(setDebounceValue, delay);
23 |
24 | useEffect(() => {
25 | if (previousValueRef.current === value) return;
26 | debouncedSetState(value);
27 | previousValueRef.current = value;
28 | }, [value]);
29 |
30 | return debouncedValue;
31 | };
32 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDefault/useDefault.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDefault } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const initialUser = { name: 'Dima' };
5 | const defaultUser = { name: 'Danila' };
6 | const [user, setUser] = useDefault(initialUser, defaultUser);
7 |
8 | return (
9 |
10 |
11 | User: {user.name}
12 |
13 |
setUser({ name: event.target.value })} />
14 |
setUser(null)}>
15 | Clear user
16 |
17 |
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDefault/useDefault.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | /**
4 | * @name useDefault
5 | * @description - Hook that returns the default value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {Value} initialValue The initial value
10 | * @param {Value} defaultValue The default value
11 | * @returns {[Value, (value: Value) => void]} An array containing the current value and a function to set the value
12 | *
13 | * @example
14 | * const [value, setValue] = useDefault(initialValue, defaultValue);
15 | */
16 | export const useDefault = (initialValue: (() => Value) | Value, defaultValue: Value) => {
17 | const [value, setValue] = useState(initialValue);
18 | return [value === undefined || value === null ? defaultValue : value, setValue] as const;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDeviceMotion/useDeviceMotion.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDeviceMotion } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const deviceMotion = useDeviceMotion();
5 |
6 | return (
7 |
8 | Device motion data:
9 | {JSON.stringify(deviceMotion, null, 2)}
10 |
11 | );
12 | };
13 |
14 | export default Demo;
15 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDeviceOrientation/useDeviceOrientation.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDeviceOrientation } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const deviceOrientation = useDeviceOrientation();
5 |
6 | if (!deviceOrientation.supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 |
23 | Device orientation data:
24 | {JSON.stringify(deviceOrientation.value, null, 2)}
25 |
26 | );
27 | };
28 |
29 | export default Demo;
30 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDevicePixelRatio/useDevicePixelRatio.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDevicePixelRatio } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { supported, ratio } = useDevicePixelRatio();
5 |
6 | if (!supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 |
23 | Device pixel ratio (try to zoom page in and out): {ratio}
24 |
25 | );
26 | };
27 |
28 | export default Demo;
29 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDidUpdate/useDidUpdate.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useDidUpdate } from '@siberiacancode/reactuse';
2 | import { useEffect, useState } from 'react';
3 |
4 | const Demo = () => {
5 | const counter = useCounter();
6 | const [useEffectTriggered, setUseEffectTriggered] = useState(false);
7 | const [useDidUpdateTriggered, setUseDidUpdateTriggered] = useState(false);
8 |
9 | useDidUpdate(() => setUseDidUpdateTriggered(true), [counter.value]);
10 |
11 | useEffect(() => {
12 | setUseEffectTriggered(true);
13 | }, [counter.value]);
14 |
15 | return (
16 |
17 |
18 | Count: {counter.value}
19 |
20 |
21 |
22 | Use effect triggered: {String(useEffectTriggered)}
, use non initial effect
23 | triggered: {String(useDidUpdateTriggered)}
24 |
25 |
26 |
counter.inc()}>
27 | Update
28 |
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDidUpdate/useDidUpdate.ts:
--------------------------------------------------------------------------------
1 | import type { DependencyList, EffectCallback } from 'react';
2 |
3 | import { useRef } from 'react';
4 |
5 | import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
6 |
7 | /**
8 | * @name useDidUpdate
9 | * @description – Hook that triggers the effect callback on updates
10 | * @category Lifecycle
11 | *
12 | * @param {EffectCallback} effect The effect callback
13 | * @param {DependencyList} [deps] The dependencies list for the effect
14 | *
15 | * @example
16 | * useDidUpdate(() => console.log("effect runs on updates"), deps);
17 | */
18 | export const useDidUpdate = (effect: EffectCallback, deps?: DependencyList) => {
19 | const mounted = useRef(false);
20 |
21 | useIsomorphicLayoutEffect(
22 | () => () => {
23 | mounted.current = false;
24 | },
25 | []
26 | );
27 |
28 | useIsomorphicLayoutEffect(() => {
29 | if (mounted.current) {
30 | return effect();
31 | }
32 |
33 | mounted.current = true;
34 | return undefined;
35 | }, deps);
36 | };
37 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDisclosure/useDisclosure.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDisclosure } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const modal = useDisclosure();
5 |
6 | return (
7 |
8 |
9 | Opened: {String(modal.opened)}
10 |
11 | {modal.opened && (
12 |
13 | Modal content
14 |
15 | )}
16 |
modal.open()}>
17 | Open
18 |
19 |
modal.close()}>
20 | Close
21 |
22 |
modal.toggle()}>
23 | Toggle
24 |
25 |
26 | );
27 | };
28 |
29 | export default Demo;
30 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDocumentEvent/useDocumentEvent.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDocumentEvent } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 |
7 | useDocumentEvent('click', () => setCount(count + 1));
8 |
9 | return Document click count: {count}
;
10 | };
11 |
12 | export default Demo;
13 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDocumentEvent/useDocumentEvent.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { vi } from 'vitest';
3 |
4 | import { useDocumentEvent } from './useDocumentEvent';
5 |
6 | it('Should use document event', () => {
7 | const listener = vi.fn();
8 | renderHook(() => useDocumentEvent('click', listener));
9 | document.dispatchEvent(new Event('click'));
10 | expect(listener).toHaveBeenCalled();
11 | });
12 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDocumentTitle/useDocumentTitle.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDocumentTitle } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const documentTitle = useDocumentTitle();
5 |
6 | return (
7 |
8 |
Title: {documentTitle.value}
9 |
documentTitle.set(event.target.value)}
12 | />
13 |
14 | );
15 | };
16 |
17 | export default Demo;
18 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDocumentVisibility/useDocumentVisibility.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useDidUpdate, useDocumentVisibility, useTimer } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const START_MESSAGE = '💡 Minimize the page or switch tab then return';
5 |
6 | const Demo = () => {
7 | const [message, setMessage] = useState(START_MESSAGE);
8 | const documentVisibility = useDocumentVisibility();
9 |
10 | const timer = useTimer(3000, () => {
11 | setMessage(START_MESSAGE);
12 | });
13 |
14 | useDidUpdate(() => {
15 | if (documentVisibility === 'visible') {
16 | setMessage('🎉 Welcome back!');
17 | timer.start();
18 | }
19 | }, [documentVisibility]);
20 |
21 | return {message}
;
22 | };
23 |
24 | export default Demo;
25 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDocumentVisibility/useDocumentVisibility.ts:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 |
3 | const getSnapshot = () => document.visibilityState;
4 | const getServerSnapshot = () => 'hidden' as const;
5 | const subscribe = (callback: () => void) => {
6 | document.addEventListener('visibilitychange', callback);
7 | return () => {
8 | document.removeEventListener('visibilitychange', callback);
9 | };
10 | };
11 |
12 | /**
13 | * @name useDocumentVisibility
14 | * @description – Hook that provides the current visibility state of the document
15 | * @category Browser
16 | *
17 | * @returns {DocumentVisibilityState} The current visibility state of the document, which can be 'visible' or 'hidden'
18 | *
19 | * @example
20 | * const visibilityState = useDocumentVisibility();
21 | */
22 | export const useDocumentVisibility = () =>
23 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useDoubleClick/useDoubleClick.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useDoubleClick } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const doubleClickRef = useDoubleClick(() => counter.inc());
6 |
7 | return (
8 | <>
9 |
10 | Double clicked {counter.value}
times
11 |
12 |
13 | Double click me
14 |
15 | >
16 | );
17 | };
18 |
19 | export default Demo;
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useElementSize/useElementSize.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useElementSize } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const elementSize = useElementSize();
5 |
6 | return (
7 |
8 |
Resize the box to see changes
9 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useEvent/useEvent.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useEvent, useRenderCount } from '@siberiacancode/reactuse';
2 | import { memo } from 'react';
3 |
4 | interface MemoComponentProps {
5 | onClick: () => void;
6 | }
7 |
8 | const MemoComponent = memo(({ onClick }: MemoComponentProps) => {
9 | const renderCount = useRenderCount();
10 |
11 | return (
12 | <>
13 |
14 | Memo component rerender count: {renderCount}
15 |
16 |
17 | Send message
18 |
19 | >
20 | );
21 | });
22 | MemoComponent.displayName = 'MemoComponent';
23 |
24 | const Demo = () => {
25 | const counter = useCounter();
26 |
27 | const onClick = useEvent(() => counter.inc());
28 |
29 | return (
30 | <>
31 |
32 | Count is: {counter.value}
33 |
34 |
35 | >
36 | );
37 | };
38 |
39 | export default Demo;
40 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useEvent/useEvent.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react';
2 |
3 | /**
4 | * @name useEvent
5 | * @description - Hook that creates an event and returns a stable reference of it
6 | * @category Browser
7 | *
8 | * @template Params The type of the params
9 | * @template Return The type of the return
10 | * @param {(...args: Params) => Return} callback The callback function
11 | * @returns {(...args: Params) => Return} The callback
12 | *
13 | * @example
14 | * const onClick = useEvent(() => console.log('clicked'));
15 | */
16 | export const useEvent = (
17 | callback: (...args: Params) => Return
18 | ): ((...args: Params) => Return) => {
19 | const callbackRef = useRef(callback);
20 | callbackRef.current = callback;
21 |
22 | return useCallback((...args) => {
23 | const fn = callbackRef.current;
24 | return fn(...args);
25 | }, []);
26 | };
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useEventListener/useEventListener.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useEventListener } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 |
7 | useEventListener('click', () => setCount(count + 1));
8 |
9 | return (
10 |
11 | Click count: {count}
12 |
13 | );
14 | };
15 |
16 | export default Demo;
17 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useEventSource/useEventSource.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useEventSource } from './useEventSource';
2 |
3 | const Demo = () => {
4 | const eventSource = useEventSource('https://sse.dev/test', ['update', 'message']);
5 |
6 | return (
7 |
8 |
9 | Status:{' '}
10 |
11 | {eventSource.isConnecting ? 'CONNECTING' : eventSource.isOpen ? 'OPEN' : 'CLOSED'}
12 |
13 |
14 | {eventSource.isConnecting &&
Connecting...
}
15 | {eventSource.isOpen &&
Data: {eventSource.data}
}
16 |
17 | {eventSource.isOpen &&
Close }
18 | {!eventSource.isOpen &&
Reconnect }
19 |
20 | );
21 | };
22 |
23 | export default Demo;
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useEyeDropper/useEyeDropper.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useEyeDropper } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const eyeDropper = useEyeDropper();
5 |
6 | if (!eyeDropper.supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 | <>
23 |
24 | value:{' '}
25 |
26 | {eyeDropper.value ? eyeDropper.value : 'choose color'}
27 |
28 |
29 | eyeDropper.open()}>
30 | Open eye dropper
31 |
32 | >
33 | );
34 | };
35 |
36 | export default Demo;
37 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFileDialog/useFileDialog.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useFileDialog } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const fileDialog = useFileDialog();
5 |
6 | return (
7 |
8 | fileDialog.open()}>
9 | Choose files
10 |
11 | fileDialog.reset()}>
12 | Reset
13 |
14 | {fileDialog.value && (
15 |
16 |
17 | You have selected: {fileDialog.value.length} files
18 | {Array.from(fileDialog.value).map((file) => (
19 |
20 | {file.name}
21 |
22 | ))}
23 |
24 |
25 | )}
26 |
27 | );
28 | };
29 |
30 | export default Demo;
31 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFocus/useFocus.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useFocus } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const textFocus = useFocus();
5 | const inputFocus = useFocus({ initialValue: true });
6 |
7 | return (
8 | <>
9 |
14 | Paragraph that can be focused
15 |
16 |
17 |
18 | Focus text
19 |
20 |
21 | Focus input
22 |
23 | >
24 | );
25 | };
26 |
27 | export default Demo;
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFps/useFps.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useFps } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const fps = useFps();
5 |
6 | return (
7 |
8 | FPS: {fps}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFul/useFul.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useFul } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const value = useFul(counter.value);
6 |
7 | return (
8 |
9 |
10 | Useful value is {value}
11 |
12 |
13 |
counter.inc()}>
14 | Increment
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFul/useFul.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useFul } from './useFul';
4 |
5 | it('Should use ful', () => {
6 | const { result } = renderHook(() => useFul('value'));
7 |
8 | expect(result.current).toBe('value');
9 | });
10 |
11 | it('Should log a warning when useFul is used', () => {
12 | const mockConsoleWarn = vi.spyOn(console, 'warn');
13 | renderHook(() => useFul());
14 |
15 | expect(mockConsoleWarn).toHaveBeenCalledWith("Warning: You forgot to delete the 'useFul' hook.");
16 | mockConsoleWarn.mockRestore();
17 | });
18 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFul/useFul.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | /**
4 | * @name useFul
5 | * @description - Hook that can be so useful
6 | * @category Humor
7 | *
8 | * @warning - This hook is a joke. Please do not use it in production code!
9 | *
10 | * @template Value The type of the value
11 | * @param {Value} [value] The value to be returned
12 | * @returns {Value} The value passed to the hook
13 | *
14 | * @example
15 | * const value = useFul(state);
16 | */
17 | export const useFul = (value?: Value) => {
18 | useEffect(() => {
19 | console.warn("Warning: You forgot to delete the 'useFul' hook.");
20 | }, []);
21 |
22 | return value;
23 | };
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useFullscreen/useFullscreen.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useFullscreen } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const fullscreen = useFullscreen();
5 |
6 | return (
7 |
8 |
9 |
10 |
18 |
19 |
20 | Go Fullscreen
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Demo;
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useGeolocation/useGeolocation.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useGeolocation } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const geolocation = useGeolocation();
5 |
6 | return (
7 |
8 |
{JSON.stringify(geolocation, null, 2)}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useHash/useHash.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useHash, useMount } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const [hash, setHash] = useHash();
5 |
6 | useMount(() => setHash('path/to/page?userId=123'));
7 |
8 | return (
9 |
10 |
window.location.href:
11 |
12 |
{window.location.href}
13 |
14 |
Edit hash:
15 |
16 | setHash(event.target.value)} />
17 |
18 |
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useHash/useHash.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | const getHash = () => decodeURIComponent(window.location.hash.replace('#', ''));
4 |
5 | /** The use hash return type */
6 | type UseHashReturn = [string, (value: string) => void];
7 |
8 | /**
9 | * @name useHash
10 | * @description - Hook that manages the hash value
11 | * @category Browser
12 | *
13 | * @returns {UseHashReturn} An array containing the hash value and a function to set the hash value
14 | *
15 | * @example
16 | * const [hash, setHash] = useHash();
17 | */
18 | export const useHash = (): UseHashReturn => {
19 | const [hash, setHash] = useState(window ? getHash() : '');
20 |
21 | const set = (value: string) => {
22 | window.location.hash = value;
23 | setHash(value);
24 | };
25 |
26 | useEffect(() => {
27 | const onHashChange = () => setHash(getHash());
28 | window.addEventListener('hashchange', onHashChange);
29 | return () => {
30 | window.removeEventListener('hashchange', onHashChange);
31 | };
32 | });
33 |
34 | return [hash, set] as const;
35 | };
36 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useHotkeys/useHotkeys.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useHotkeys } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 | useHotkeys('control+a', () => setCount(count + 1));
7 |
8 | return (
9 |
10 |
11 | Press hot keys ctrl left
+ a
{' '}
12 |
13 |
14 | Count of key presses: {count}
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useHover/useHover.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useHover } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const hover = useHover(() => console.log('callback'));
5 |
6 | return (
7 |
8 | The current div is {hover.value ? 'hovered' : 'unhovered'}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIdle/useIdle.demo.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@siberiacancode/docs/utils';
2 | import { useIdle } from '@siberiacancode/reactuse';
3 |
4 | const Demo = () => {
5 | const { idle, lastActive } = useIdle(1000);
6 |
7 | return (
8 | <>
9 |
10 | Status:{' '}
11 |
12 |
13 | {idle ? 'idle' : 'active'}
14 |
15 |
16 |
17 | Last active: {lastActive}
18 | >
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useImage/useImage.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useImage, useToggle } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const [color, toggle] = useToggle(['ffffff', '000000', '5f0caa']);
5 | const image = useImage(`https://place-hold.it/300x200/${color}`);
6 |
7 | return (
8 |
9 | {image.isLoading &&
Loading...
}
10 | {!image.isLoading && image.data && (
11 |
12 | )}
13 | toggle()}>
14 | Change
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsFirstRender/useIsFirstRender.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useIsFirstRender } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const isFirstRender = useIsFirstRender();
6 | const [count, setCount] = useState(0);
7 |
8 | return (
9 |
10 |
Rerendered {count} times
11 |
12 | Is first render: {isFirstRender ? 'yes' : 'no'}
13 |
14 |
setCount(count + 1)}>
15 | Rerender
16 |
17 |
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsFirstRender/useIsFirstRender.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useIsFirstRender } from './useIsFirstRender';
4 |
5 | it('Should use is first render', () => {
6 | const { result } = renderHook(useIsFirstRender);
7 |
8 | expect(result.current).toBeTruthy();
9 | });
10 |
11 | it('Should return false after render', () => {
12 | const { result, rerender } = renderHook(useIsFirstRender);
13 |
14 | expect(result.current).toBeTruthy();
15 |
16 | rerender();
17 | expect(result.current).toBeFalsy();
18 |
19 | rerender();
20 | expect(result.current).toBeFalsy();
21 | });
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsFirstRender/useIsFirstRender.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | /**
4 | * @name useIsFirstRender
5 | * @description - Hook that returns true if the component is first render
6 | * @category Lifecycle
7 | *
8 | * @returns {boolean} True if the component is first render
9 | *
10 | * @example
11 | * const isFirstRender = useIsFirstRender();
12 | */
13 | export const useIsFirstRender = () => {
14 | const renderRef = useRef(true);
15 |
16 | if (renderRef.current === true) {
17 | renderRef.current = false;
18 | return true;
19 | }
20 |
21 | return renderRef.current;
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect-node.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment node
3 | */
4 |
5 | import { useEffect } from 'react';
6 |
7 | import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
8 |
9 | it('Should use effect', () => {
10 | expect(useIsomorphicLayoutEffect).toBe(useEffect);
11 | });
12 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useIsomorphicLayoutEffect } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | useIsomorphicLayoutEffect(() => {
5 | console.log(`log: useLayoutEffect`);
6 | }, []);
7 |
8 | return (
9 |
10 | I am useLayoutEffect
11 |
12 | );
13 | };
14 |
15 | export default Demo;
16 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.test.ts:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect } from 'react';
2 |
3 | import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
4 |
5 | it('Should use layout effect', () => {
6 | expect(useIsomorphicLayoutEffect).toBe(useLayoutEffect);
7 | });
8 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from 'react';
2 |
3 | /**
4 | * @name useIsomorphicLayoutEffect
5 | * @description - Hook conditionally selects either `useLayoutEffect` or `useEffect` based on the environment
6 | * @category Lifecycle
7 | *
8 | * @example
9 | * useIsomorphicLayoutEffect(() => console.log('effect'), [])
10 | */
11 | export const useIsomorphicLayoutEffect =
12 | typeof window !== 'undefined' ? useLayoutEffect : useEffect;
13 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useKeyPress/useKeyPress.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useKeyPress } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const pressedKey = useKeyPress('a');
5 |
6 | return (
7 |
8 | Press{pressedKey.pressed && 'ed'} A
keyboard button
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useKeyPressEvent/useKeyPressEvent.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useKeyPressEvent } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter(0);
5 |
6 | useKeyPressEvent('Enter', () => counter.inc());
7 |
8 | return (
9 |
10 |
11 | Press Enter
key
12 |
13 |
14 | Count of key presses: {counter.value}
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useKeyboard/useKeyboard.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useKeyboard } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [pressedKeys, setPressedKeys] = useState([]);
6 |
7 | useKeyboard({
8 | onKeyDown: (event) => {
9 | event.preventDefault();
10 | setPressedKeys((prevPressedKeys) => {
11 | if (!prevPressedKeys.includes(event.key)) {
12 | return [...prevPressedKeys, event.key];
13 | }
14 | return prevPressedKeys;
15 | });
16 | },
17 | onKeyUp: (event) => {
18 | event.preventDefault();
19 | setPressedKeys((prevPressedKeys) =>
20 | prevPressedKeys.filter((key) => key.toLowerCase() !== event.key.toLowerCase())
21 | );
22 | }
23 | });
24 |
25 | return (
26 |
27 |
Currently pressed keys:
28 |
29 | {pressedKeys.map((key) => (
30 | {key}
31 | ))}
32 |
33 |
34 | );
35 | };
36 |
37 | export default Demo;
38 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useKeysPressed/useKeysPressed.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useKeysPressed } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const keysPressed = useKeysPressed();
5 |
6 | return (
7 |
8 |
Currently pressed keys:
9 |
10 | {keysPressed.value.map(({ key }) => (
11 | {key}
12 | ))}
13 |
14 |
15 | );
16 | };
17 |
18 | export default Demo;
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLastChanged/useLastChanged.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useLastChanged } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [inputValue, setInputValue] = useState('');
6 | const value = useLastChanged(inputValue);
7 |
8 | return (
9 |
10 |
setInputValue(event.target.value)}
13 | placeholder='Type something...'
14 | />
15 |
Last changed: {value}
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLastChanged/useLastChanged.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useLastChanged } from './useLastChanged';
4 |
5 | it('Should use last changed', () => {
6 | const { result } = renderHook(useLastChanged);
7 |
8 | expect(result.current).toBeNull();
9 | });
10 |
11 | it('Should return timestamp when value changes', () => {
12 | const { result, rerender } = renderHook((text = 'init text') => useLastChanged(text));
13 |
14 | rerender('changed text');
15 |
16 | expect(result.current).toBeCloseTo(Date.now(), -1);
17 | });
18 |
19 | it('Should return initial value', () => {
20 | const initialValue = Date.now();
21 |
22 | const { result } = renderHook(() => useLastChanged(null, { initialValue }));
23 |
24 | expect(result.current).toBe(initialValue);
25 | });
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLastChanged/useLastChanged.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import { useDidUpdate } from '../useDidUpdate/useDidUpdate';
4 |
5 | /** The use last changed options type */
6 | export interface UseLastChangedOptions {
7 | initialValue?: number;
8 | }
9 |
10 | /**
11 | * @name useLastChanged
12 | * @description - Hook for records the timestamp of the last change
13 | * @category Time
14 | *
15 | * @param {any} source The source of the last change
16 | * @param {number | null} [options.initialValue=null] The initial value
17 | * @returns {number | null} Return timestamp of the last change
18 | *
19 | * @example
20 | * const lastChanged = useLastChanged(source);
21 | */
22 | export const useLastChanged = (source: any, options?: UseLastChangedOptions): number | null => {
23 | const [lastChanged, setLastChanged] = useState(options?.initialValue ?? null);
24 |
25 | useDidUpdate(() => setLastChanged(Date.now()), [source]);
26 |
27 | return lastChanged;
28 | };
29 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLatest/useLatest.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useLatest } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const latestCount = useLatest(counter.value);
6 |
7 | return (
8 |
9 |
10 | You clicked {counter.value}
times, latest count is {latestCount}
11 |
12 |
13 |
counter.inc()}>
14 | Increment
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLatest/useLatest.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useLatest } from './useLatest';
4 |
5 | it('Should use latest', () => {
6 | const { result } = renderHook(() => useLatest('value'));
7 |
8 | expect(result.current).toBe('value');
9 | });
10 |
11 | it('Should maintain reference stability', () => {
12 | const { result, rerender } = renderHook((value) => useLatest(value), {
13 | initialProps: 'value'
14 | });
15 |
16 | rerender('newValue');
17 | expect(result.current).toEqual('newValue');
18 | });
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLatest/useLatest.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | /**
4 | * @name useLatest
5 | * @description - Hook that returns the stable reference of the value
6 | * @category Utilities
7 | *
8 | * @template Value The type of the value
9 | * @param {Value} value The value to get the previous value
10 | * @returns {Value} The previous value
11 | *
12 | * @example
13 | * const latestValue = useLatest(value);
14 | */
15 | export const useLatest = (value: Value) => {
16 | const valueRef = useRef(value);
17 | valueRef.current = value;
18 | return valueRef.current;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLess/useLess.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useLess } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const value = useLess(counter.value);
6 |
7 | return (
8 |
9 |
10 | Useless value is {value}
11 |
12 |
13 |
counter.inc()}>
14 | Increment
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLess/useLess.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useLess } from './useLess';
4 |
5 | it('Should use less', () => {
6 | const { result } = renderHook(() => useLess('value'));
7 |
8 | expect(result.current).toBe('value');
9 | });
10 |
11 | it('Should log a warning when useLess is used', () => {
12 | const mockConsoleWarn = vi.spyOn(console, 'warn');
13 | renderHook(() => useLess());
14 |
15 | expect(mockConsoleWarn).toHaveBeenCalledWith("Warning: You forgot to delete the 'useLess' hook.");
16 | mockConsoleWarn.mockRestore();
17 | });
18 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLess/useLess.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | /**
4 | * @name useLess
5 | * @description - Hook that can be so useless
6 | * @category Humor
7 | *
8 | * @warning - This hook is a joke. Please do not use it in production code!
9 | *
10 | * @template Value The type of the value
11 | * @param {Value} [value] The value to be returned
12 | * @returns {Value} The value passed to the hook
13 | *
14 | * @example
15 | * const value = useLess(state);
16 | */
17 | export const useLess = (value?: Value) => {
18 | useEffect(() => {
19 | console.warn("Warning: You forgot to delete the 'useLess' hook.");
20 | }, []);
21 |
22 | return value;
23 | };
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useList/useList.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useList } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const list = useList([1, 2, 3, 4, 5]);
5 |
6 | return (
7 |
8 |
list.set([1, 2, 3])}>
9 | Set to [1, 2, 3]
10 |
11 |
list.push(Date.now())}>
12 | Push timestamp
13 |
14 |
list.updateAt(1, Date.now())}>
15 | Update value at index 1
16 |
17 |
list.removeAt(1)}>
18 | Remove element at index 1
19 |
20 |
21 | Clear
22 |
23 |
24 | Reset
25 |
26 |
{JSON.stringify(list, null, 2)}
27 |
28 | );
29 | };
30 |
31 | export default Demo;
32 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLocalStorage/useLocalStorage.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useLocalStorage } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { value, set, remove } = useLocalStorage('siberiacancode-use-local-storage', 0);
5 |
6 | return (
7 |
8 |
9 | Count: {value ?? 'value is undefined'}
10 |
11 | {value !== undefined && (
12 | <>
13 |
set(value + 1)}>
14 | Increment
15 |
16 |
set(value - 1)}>
17 | Decrement
18 |
19 | >
20 | )}
21 | {value === undefined && (
22 |
set(0)}>
23 | Set
24 |
25 | )}
26 |
27 | Remove
28 |
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLocalStorage/useLocalStorage.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { renderHookServer } from '@/tests';
4 |
5 | import { useLocalStorage } from './useLocalStorage';
6 |
7 | it('Should use session storage', () => {
8 | const { result } = renderHook(() => useLocalStorage('key', 'initialValue'));
9 |
10 | expect(result.current.value).toBe('initialValue');
11 | expect(result.current.set).toBeTypeOf('function');
12 | expect(result.current.remove).toBeTypeOf('function');
13 | });
14 |
15 | it('should use session storage on server side', () => {
16 | const { result } = renderHookServer(() => useLocalStorage('key', 'initialValue'));
17 |
18 | expect(result.current.value).toBe('initialValue');
19 | expect(result.current.set).toBeTypeOf('undefined');
20 | expect(result.current.remove).toBeTypeOf('undefined');
21 | });
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLockCallback/useLockCallback.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useLockCallback } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 | const [loading, setLoading] = useState(false);
7 |
8 | const handleClick = useLockCallback(async () => {
9 | setLoading(true);
10 | await new Promise((resolve) => setTimeout(resolve, 2000));
11 | setCount((prevCount) => prevCount + 1);
12 | setLoading(false);
13 | });
14 |
15 | return (
16 |
17 |
18 |
19 | Count: {count}
20 |
21 |
22 |
23 |
24 | The callback is locked until the async operation completes{' '}
25 | {loading ? 'loading' : 'idle'}
26 |
27 |
28 |
29 | Increment
30 |
31 |
32 | );
33 | };
34 |
35 | export default Demo;
36 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLockCallback/useLockCallback.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | /**
4 | * @name useLockCallback
5 | * @description - Hook that prevents a callback from being executed multiple times simultaneously
6 | * @category Utilities
7 | *
8 | * @param {Function} callback The callback to be locked
9 | * @returns {Function} The locked callback
10 | *
11 | * @example
12 | * const lockedCallback = useLockCallback(() => promise());
13 | */
14 | export const useLockCallback = any>(callback: Callback) => {
15 | const lockRef = useRef(false);
16 | const callbackRef = useRef(callback);
17 | callbackRef.current = callback;
18 |
19 | return async (...args: Parameters) => {
20 | if (lockRef.current) return;
21 | lockRef.current = true;
22 |
23 | try {
24 | return await callbackRef.current(...args);
25 | } finally {
26 | lockRef.current = false;
27 | }
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLogger/useLogger.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useLogger } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 |
7 | useLogger('Demo', [count, { foo: 'bar' }]);
8 |
9 | return (
10 |
11 |
12 | Open the console
to see the logger output
13 |
14 |
setCount(count + 1)}>
15 | Update state
16 |
17 |
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLogger/useLogger.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { useDidUpdate } from '../useDidUpdate/useDidUpdate';
4 |
5 | /**
6 | * @name useLogger
7 | * @description - Hook for debugging lifecycle
8 | * @category Lifecycle
9 | *
10 | * @param {string} name The name or identifier for the logger
11 | * @param {unknown[]} params Additional arguments to be logged
12 | *
13 | * @example
14 | * useLogger('Component', [1, 2, 3]);
15 | */
16 | export const useLogger = (name: string, params: unknown[]) => {
17 | useEffect(() => {
18 | console.log(`${name} mounted`, ...params);
19 | return () => console.log(`${name} unmounted`);
20 | }, []);
21 |
22 | useDidUpdate(() => {
23 | console.log(`${name} updated`, ...params);
24 | }, params);
25 | };
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useLongPress/useLongPress.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useLongPress } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const longPress = useLongPress(() => counter.inc());
6 |
7 | return (
8 | <>
9 |
10 | Long pressed: {longPress.pressed.toString()}
11 |
12 |
13 | Clicked: {counter.value}
14 |
15 |
16 | Long press
17 |
18 | >
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMap/useMap.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useField, useMap } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const nameInput = useField();
5 | const ageInput = useField();
6 |
7 | const users = useMap([
8 | ['Dima', 25],
9 | ['Danila', 1]
10 | ]);
11 |
12 | return (
13 | <>
14 |
15 |
16 |
17 |
18 | {
21 | users.set(nameInput.getValue(), +ageInput.getValue());
22 | nameInput.reset();
23 | ageInput.reset();
24 | }}
25 | >
26 | Add
27 |
28 |
29 |
30 |
31 | {Array.from(users.value).map(([name, age], index) => (
32 |
33 | {name}: {age}
34 |
35 | ))}
36 |
37 | >
38 | );
39 | };
40 |
41 | export default Demo;
42 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMeasure/useMeasure.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useMeasure } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const measure = useMeasure();
5 |
6 | return (
7 |
15 |
16 | width: {Math.floor(measure.width)}
17 |
18 |
19 | height: {Math.floor(measure.height)}
20 |
21 |
22 | right: {Math.floor(measure.right)}
23 |
24 |
25 | bottom: {Math.floor(measure.bottom)}
26 |
27 |
28 | );
29 | };
30 |
31 | export default Demo;
32 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMediaQuery/useMediaQuery.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const matches = useMediaQuery('(max-width: 768px)');
5 |
6 | return (
7 |
8 | This is {matches ? 'mobile' : 'desktop'}
screen
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMount/useMount.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useBoolean, useMount } from '@siberiacancode/reactuse';
2 |
3 | const Component = () => {
4 | useMount(() => alert('mount'));
5 |
6 | return Hello World!
;
7 | };
8 |
9 | const Demo = () => {
10 | const [on, toggle] = useBoolean(false);
11 |
12 | return (
13 | <>
14 | toggle()}>
15 | {on ? 'Unmount' : 'Mount'}
16 |
17 | {on && }
18 | >
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMount/useMount.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useMount } from './useMount';
4 |
5 | it('Should use mount', () => {
6 | const callback = vi.fn();
7 | renderHook(() => useMount(callback));
8 |
9 | expect(callback).toHaveBeenCalled();
10 | });
11 |
12 | it('Should not call callback after rerender', () => {
13 | const callback = vi.fn();
14 | const { rerender } = renderHook(() => useMount(callback));
15 |
16 | expect(callback).toHaveBeenCalledOnce();
17 |
18 | rerender();
19 |
20 | expect(callback).toHaveBeenCalledOnce();
21 | });
22 |
23 | it('Should call callback on unmount', () => {
24 | const callback = vi.fn();
25 | const { unmount } = renderHook(() => useMount(() => callback));
26 |
27 | expect(callback).not.toHaveBeenCalled();
28 |
29 | unmount();
30 |
31 | expect(callback).toHaveBeenCalledOnce();
32 | });
33 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMount/useMount.ts:
--------------------------------------------------------------------------------
1 | import type { EffectCallback } from 'react';
2 |
3 | import { useEffect } from 'react';
4 |
5 | /**
6 | * @name useMount
7 | * @description - Hook that executes a callback when the component mounts
8 | * @category Lifecycle
9 | *
10 | * @param {EffectCallback} effect The callback to execute
11 | *
12 | * @example
13 | * useMount(() => console.log('This effect runs on the initial render'));
14 | */
15 | export const useMount = (effect: EffectCallback) => useEffect(effect, []);
16 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useMutation/useMutation.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useField, useList, useMutation } from '@siberiacancode/reactuse';
2 |
3 | const createUser = (name: string) => Promise.resolve({ name });
4 |
5 | const Demo = () => {
6 | const nameField = useField({ initialValue: '' });
7 | const userList = useList([{ name: 'John' }]);
8 |
9 | const createUserMutation = useMutation(createUser);
10 |
11 | const name = nameField.watch();
12 |
13 | return (
14 | <>
15 | User list
16 |
17 | {
21 | const createUserResponse = await createUserMutation.mutateAsync(name);
22 | userList.push(createUserResponse);
23 | nameField.setValue('');
24 | }}
25 | >
26 | Create
27 |
28 |
29 |
30 | {userList.value.map((user) => (
31 | {user.name}
32 | ))}
33 |
34 | >
35 | );
36 | };
37 |
38 | export default Demo;
39 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useNetwork/useNetwork.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useNetwork } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const network = useNetwork();
5 |
6 | return (
7 |
8 |
Network status
9 |
10 |
11 | online: {String(network.online)}
12 |
13 |
14 | saveData: {String(network.saveData)}
15 |
16 |
17 | type: {String(network.type)}
18 |
19 |
20 | downlink: {network.downlink}
21 |
22 |
23 | downlinkMax: {String(network.downlinkMax)}
24 |
25 |
26 | effectiveType: {network.effectiveType}
27 |
28 |
29 | rtt: {network.rtt}
30 |
31 |
32 | saveData: {String(network.saveData)}
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default Demo;
40 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOnce/useOnce.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useBoolean, useOnce } from '@siberiacancode/reactuse';
2 |
3 | const Component = () => {
4 | useOnce(() => alert('mount'));
5 |
6 | return Hello World!
;
7 | };
8 |
9 | const Demo = () => {
10 | const [on, toggle] = useBoolean(false);
11 |
12 | return (
13 | <>
14 | toggle()}>
15 | {on ? 'Unmount' : 'Mount'}
16 |
17 | {on && }
18 |
19 |
20 | effect runs only once (will not run twice in strict mode)
21 |
22 | >
23 | );
24 | };
25 |
26 | export default Demo;
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOnline/useOnline.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useOnline } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const online = useOnline();
5 |
6 | return (
7 |
8 | User is {online ? 'online' : 'offline'}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOnline/useOnline.ts:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 |
3 | const getSnapshot = () => navigator.onLine;
4 | const getServerSnapshot = () => false;
5 | const subscribe = (callback: () => void) => {
6 | window.addEventListener('online', callback);
7 | window.addEventListener('offline', callback);
8 | return () => {
9 | window.removeEventListener('online', callback);
10 | window.removeEventListener('offline', callback);
11 | };
12 | };
13 |
14 | /**
15 | * @name useOnline
16 | * @description - Hook that manages if the user is online
17 | * @category Sensors
18 | *
19 | * @browserapi navigator.onLine https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
20 | *
21 | * @returns {boolean} A boolean indicating if the user is online
22 | *
23 | * @example
24 | * const online = useOnline();
25 | */
26 | export const useOnline = () => useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOperatingSystem/useOperatingSystem.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useOperatingSystem } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const operatingSystem = useOperatingSystem();
5 | return (
6 |
7 | Your os is {operatingSystem}
8 |
9 | );
10 | };
11 |
12 | export default Demo;
13 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOrientation/useOrientation.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useOrientation } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { angle, type } = useOrientation();
5 |
6 | return (
7 | <>
8 |
9 | Angle: {angle}
10 |
11 |
12 | Type: {type}
13 |
14 | >
15 | );
16 | };
17 |
18 | export default Demo;
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useOtpCredential/useOtpCredential.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useOtpCredential } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const otpCredential = useOtpCredential();
5 |
6 | const getOtpCode = async () => {
7 | const credential = await otpCredential.get();
8 | console.log('credential', credential);
9 | };
10 |
11 | if (!otpCredential.supported)
12 | return (
13 |
14 | Api not supported, make sure to check for compatibility with different browsers when using
15 | this{' '}
16 |
21 | api
22 |
23 |
24 | );
25 |
26 | return (
27 |
28 | Get otp
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePageLeave/usePageLeave.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, usePageLeave } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter(0);
5 |
6 | const isLeft = usePageLeave(() => counter.inc());
7 |
8 | return (
9 | <>
10 |
11 | Mouse left the page: {String(isLeft)}
12 |
13 | Count of left the page {counter.value} times
14 | >
15 | );
16 | };
17 |
18 | export default Demo;
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePageLeave/usePageLeave.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react';
2 |
3 | import { usePageLeave } from './usePageLeave';
4 |
5 | it('Should use page leave', () => {
6 | const callback = vi.fn();
7 | const { result } = renderHook(() => usePageLeave(callback));
8 | expect(typeof result.current).toBe('boolean');
9 | });
10 |
11 | it('Should call the callback on page leave', () => {
12 | const callback = vi.fn();
13 | const { result } = renderHook(() => usePageLeave(callback));
14 | expect(result.current).toBeFalsy();
15 |
16 | act(() => document.dispatchEvent(new Event('mouseleave')));
17 | expect(callback).toBeCalledTimes(1);
18 | expect(result.current).toBeTruthy();
19 |
20 | act(() => document.dispatchEvent(new Event('mouseenter')));
21 | expect(result.current).toBeFalsy();
22 |
23 | act(() => document.dispatchEvent(new Event('mouseleave')));
24 | expect(callback).toBeCalledTimes(2);
25 | expect(result.current).toBeTruthy();
26 | });
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredColorScheme/usePreferredColorScheme.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredColorScheme } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const preferredColorScheme = usePreferredColorScheme();
5 |
6 | return (
7 |
8 | Preferred color scheme: {preferredColorScheme}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredColorScheme/usePreferredColorScheme.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 |
3 | /** The use preferred color scheme return type */
4 | export type UsePreferredColorSchemeReturn = 'dark' | 'light' | 'no-preference';
5 |
6 | /**
7 | * @name usePreferredColorScheme
8 | * @description - Hook that returns user preferred color scheme
9 | * @category Browser
10 | *
11 | * @returns {UsePreferredColorSchemeReturn} String of preferred color scheme
12 | *
13 | * @example
14 | * const colorScheme = usePreferredColorScheme();
15 | */
16 | export const usePreferredColorScheme = (): UsePreferredColorSchemeReturn => {
17 | const isLight = useMediaQuery('(prefers-color-scheme: light)');
18 | const isDark = useMediaQuery('(prefers-color-scheme: dark)');
19 |
20 | if (isLight) return 'light';
21 | if (isDark) return 'dark';
22 | return 'no-preference';
23 | };
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredContrast/usePreferredContrast.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredContrast } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const contrast = usePreferredContrast();
5 |
6 | return (
7 |
8 | Preferred contrast {String(contrast)}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredContrast/usePreferredContrast.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 |
3 | /** The use preferred contrast return type */
4 | export type UsePreferredContrastReturn = 'custom' | 'less' | 'more' | 'no-preference';
5 |
6 | /**
7 | * @name usePreferredContrast
8 | * @description - Hook that returns the contrast preference
9 | * @category Browser
10 | *
11 | * @returns {UsePreferredContrastReturn} The contrast preference
12 | *
13 | * @example
14 | * const contrast = usePreferredContrast();
15 | */
16 | export const usePreferredContrast = (): UsePreferredContrastReturn => {
17 | const more = useMediaQuery('(prefers-contrast: more)');
18 | const less = useMediaQuery('(prefers-contrast: less)');
19 | const custom = useMediaQuery('(prefers-contrast: custom)');
20 | return more ? 'more' : less ? 'less' : custom ? 'custom' : 'no-preference';
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredDark/usePreferredDark.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredDark } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const isDark = usePreferredDark();
5 |
6 | return (
7 |
8 | Prefers dark scheme {String(isDark)}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredDark/usePreferredDark.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 |
3 | /**
4 | * @name usePreferredDark
5 | * @description - Hook that returns if the user prefers dark mode
6 | * @category Browser
7 | *
8 | * @example
9 | * const isDark = usePreferredDark();
10 | */
11 | export const usePreferredDark = () => useMediaQuery('(prefers-color-scheme: dark)');
12 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredLanguages/usePreferredLanguages.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredLanguages } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const languages = usePreferredLanguages();
5 |
6 | return (
7 |
8 | {languages.map((language) => (
9 |
10 | {language}
locale date -{' '}
11 |
12 | {new Date(Date.now()).toLocaleDateString(language)}
13 |
14 |
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredLanguages/usePreferredLanguages.ts:
--------------------------------------------------------------------------------
1 | import { useSyncExternalStore } from 'react';
2 |
3 | const getSnapshot = () => window.navigator.languages;
4 | const getServerSnapshot = () => [] as const;
5 | const subscribe = (callback: () => void) => {
6 | window.addEventListener('languagechange', callback);
7 | return () => {
8 | window.removeEventListener('languagechange', callback);
9 | };
10 | };
11 |
12 | /**
13 | * @name usePreferredLanguages
14 | * @description Hook that returns a browser preferred languages from navigator
15 | * @category Browser
16 | *
17 | * @browserapi navigator.languages https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages
18 | *
19 | * @returns {readonly string[]} An array of strings representing the user's preferred languages
20 | *
21 | * @example
22 | * const languages = usePreferredLanguages();
23 | */
24 | export const usePreferredLanguages = () =>
25 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredReducedMotion/usePreferredReducedMotion.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePreferredReducedMotion } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const motion = usePreferredReducedMotion();
5 |
6 | return (
7 |
8 | Preferred reduced motion {String(motion)}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePreferredReducedMotion/usePreferredReducedMotion.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from '../useMediaQuery/useMediaQuery';
2 |
3 | /** The use preferred reduced motion return type */
4 | export type UsePreferredReducedMotionReturn = 'no-preference' | 'reduce';
5 |
6 | /**
7 | * @name usePreferredReducedMotion
8 | * @description - Hook that returns the reduced motion preference
9 | * @category Browser
10 | *
11 | * @returns {UsePreferredReducedMotionReturn} The reduced motion preference
12 | *
13 | * @example
14 | * const reduced = usePreferredReducedMotion();
15 | */
16 | export const usePreferredReducedMotion = (): UsePreferredReducedMotionReturn => {
17 | const reduced = useMediaQuery('(prefers-reduced-motion: reduce)');
18 | return reduced ? 'reduce' : 'no-preference';
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePrevious/usePrevious.demo.tsx:
--------------------------------------------------------------------------------
1 | import { usePrevious } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 | const prevCount = usePrevious(count);
7 |
8 | return (
9 | <>
10 |
11 | Value now: {count}
, value before: {prevCount ?? 'undefined'}
12 |
13 | setCount(count + 1)}>
14 | +
15 |
16 | setCount(count - 1)}>
17 | -
18 |
19 | >
20 | );
21 | };
22 |
23 | export default Demo;
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePrevious/usePrevious.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | export interface UsePreviousOptions {
4 | equality: (a: Value, b: Value) => boolean;
5 | }
6 |
7 | /**
8 | * @name usePrevious
9 | * @description - Hook that returns the previous value
10 | * @category Utilities
11 | *
12 | * @template Value The type of the value
13 | * @param {Value} value The value to get the previous value
14 | * @param {(a: Value, b: Value) => boolean} [options.equality] The custom equality function to determine if the value has changed
15 | * @returns {Value | undefined} The previous value
16 | *
17 | * @example
18 | * const prevValue = usePrevious(value);
19 | */
20 | export const usePrevious = (value: Value, options?: UsePreviousOptions) => {
21 | const currentRef = useRef(value);
22 | const previousRef = useRef(undefined);
23 |
24 | const equality = options?.equality ?? Object.is;
25 |
26 | if (!equality(value, currentRef.current)) {
27 | previousRef.current = currentRef.current;
28 | currentRef.current = value;
29 | }
30 |
31 | return previousRef.current;
32 | };
33 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useQueue/useQueue.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useQueue } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { queue, add, remove, first, last, size } = useQueue();
5 |
6 | return (
7 |
8 |
9 |
10 | first: {String(first)}
11 |
12 |
13 | last: {String(last)}
14 |
15 |
16 | size: {String(size)}
17 |
18 |
19 |
20 |
21 | Queue: {JSON.stringify(queue)}
22 |
23 |
24 |
add(last === undefined ? 0 : last + 1)}>
25 | Add
26 |
27 |
28 | Remove
29 |
30 |
31 | );
32 | };
33 |
34 | export default Demo;
35 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRaf/useRaf.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useRaf } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 | const { active, resume, pause } = useRaf(() => counter.inc());
6 |
7 | return (
8 | <>
9 |
10 | Count: {counter.value}
11 |
12 |
13 | {active ? (
14 |
15 | Pause
16 |
17 | ) : (
18 |
19 | Resume
20 |
21 | )}
22 | >
23 | );
24 | };
25 |
26 | export default Demo;
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRafValue/useRafValue.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRafValue } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const [count, setCount] = useRafValue(0);
5 |
6 | return (
7 | <>
8 |
9 | Count: {count}
10 |
11 | setCount(count + 1)}>
12 | Increment
13 |
14 | >
15 | );
16 | };
17 |
18 | export default Demo;
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRefState/useRefState.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRefState } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const internalRefState = useRefState(0);
5 |
6 | return (
7 |
8 |
9 | Render count: {internalRefState.current}
10 |
11 |
{
14 | internalRefState.current += 1;
15 | }}
16 | >
17 | Ref Update
18 |
19 |
20 | );
21 | };
22 |
23 | export default Demo;
24 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRenderCount/useRenderCount.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRenderCount } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const renderCount = useRenderCount();
6 | const [value, setValue] = useState(0);
7 |
8 | return (
9 |
10 |
Render count: {renderCount}
11 |
setValue(value + 1)}>
12 | Force Update
13 |
14 |
15 | );
16 | };
17 |
18 | export default Demo;
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRenderCount/useRenderCount.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { useRenderCount } from './useRenderCount';
4 |
5 | it('Should use render count', () => {
6 | const { result } = renderHook(useRenderCount);
7 |
8 | expect(result.current).toBe(0);
9 | });
10 |
11 | it('Should increment render count after component rerender', () => {
12 | const { result, rerender } = renderHook(useRenderCount);
13 |
14 | rerender();
15 | expect(result.current).toBe(1);
16 |
17 | rerender();
18 | expect(result.current).toBe(2);
19 | });
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRenderCount/useRenderCount.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | /**
4 | * @name useRenderCount
5 | * @description - Hook returns count component render times
6 | * @category Lifecycle
7 | *
8 | * @returns {number} A number which determines how many times component renders
9 | *
10 | * @example
11 | * const renderCount = useRenderCount();
12 | */
13 | export const useRenderCount = () => {
14 | const renderCountRef = useRef(0);
15 |
16 | useEffect(() => {
17 | renderCountRef.current += 1;
18 | });
19 |
20 | return renderCountRef.current;
21 | };
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRenderInfo/useRenderInfo.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRenderInfo, useRerender } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const rerender = useRerender();
5 | const renderInfo = useRenderInfo('Component');
6 |
7 | return (
8 | <>
9 |
10 | Name: {renderInfo.component}
11 |
12 |
13 | Count renders: {renderInfo.renders}
14 |
15 |
16 | Since last render: {renderInfo.sinceLast} ms
17 |
18 |
19 | Timestamp: {renderInfo.timestamp}
20 |
21 |
22 | Rerender
23 |
24 | >
25 | );
26 | };
27 |
28 | export default Demo;
29 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRerender/useRerender.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useRenderCount, useRerender } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const rerender = useRerender();
5 | const renderCount = useRenderCount();
6 |
7 | return (
8 | <>
9 |
10 | Render count: {renderCount}
11 |
12 |
13 |
14 | Rerender
15 |
16 | >
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRerender/useRerender.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from '@testing-library/react';
2 |
3 | import { useRerender } from './useRerender';
4 |
5 | it('Should use rerender', () => {
6 | const { result } = renderHook(useRerender);
7 |
8 | expect(result.current).toBeTypeOf('function');
9 | });
10 |
11 | it('Should trigger rerender when call rerender function', () => {
12 | let renderCount = 0;
13 |
14 | const { result } = renderHook(() => {
15 | renderCount++;
16 | return useRerender();
17 | });
18 |
19 | expect(renderCount).toBe(1);
20 |
21 | act(result.current);
22 |
23 | expect(renderCount).toBe(2);
24 | });
25 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useRerender/useRerender.ts:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 |
3 | /** The use rerender return type */
4 | type UseRerenderReturn = () => void;
5 |
6 | /**
7 | * @name useRerender
8 | * @description - Hook that defines the logic to force rerender a component
9 | * @category Lifecycle
10 | *
11 | * @returns {UseRerenderReturn} The rerender function
12 | *
13 | * @example
14 | * const rerender = useRerender();
15 | */
16 | export const useRerender = (): UseRerenderReturn => {
17 | const rerender = useReducer(() => ({}), {})[1];
18 | return rerender;
19 | };
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useResizeObserver/useResizeObserver.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useResizeObserver } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [text, setText] = useState('');
6 | const resizeObserver = useResizeObserver({
7 | onChange: ([entry]) => {
8 | const { width, height } = entry.contentRect;
9 | setText(`width: ${width}\nheight: ${height}`);
10 | }
11 | });
12 |
13 | return (
14 |
15 |
Resize the box to see changes
16 |
23 |
24 | );
25 | };
26 |
27 | export default Demo;
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useScript/useScript.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useScript } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const status = useScript('https://unpkg.com/react@18/umd/react.development.js', {
5 | async: true,
6 | onLoad: () => console.log('script is ready'),
7 | onError: () => console.error('script failed to load')
8 | });
9 |
10 | return (
11 |
12 |
13 | Current status: {status}
14 |
15 |
16 | {status === 'loading' &&
Loading...
}
17 | {status === 'ready' &&
You can use the script
}
18 |
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useScrollIntoView/useScrollIntoView.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useScrollIntoView } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const scrollIntoView = useScrollIntoView({ behavior: 'smooth', block: 'center' });
5 |
6 | return (
7 |
11 |
Scroll into view block
12 |
13 | scrollIntoView.trigger({ behavior: 'smooth', block: 'center' })}
17 | >
18 | Click to scroll into view
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Demo;
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useSessionStorage/useSessionStorage.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useSessionStorage } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { value, set, remove } = useSessionStorage('siberiacancode-use-local-storage', 0);
5 |
6 | return (
7 |
8 |
9 | Count: {value ?? 'value is undefined'}
10 |
11 | {value !== undefined && (
12 | <>
13 |
set(value + 1)}>
14 | Increment
15 |
16 |
set(value - 1)}>
17 | Decrement
18 |
19 | >
20 | )}
21 | {value === undefined && (
22 |
set(0)}>
23 | Set
24 |
25 | )}
26 |
27 | Remove
28 |
29 |
30 | );
31 | };
32 |
33 | export default Demo;
34 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useSessionStorage/useSessionStorage.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 |
3 | import { renderHookServer } from '@/tests';
4 |
5 | import { useSessionStorage } from './useSessionStorage';
6 |
7 | it('Should use session storage', () => {
8 | const { result } = renderHook(() => useSessionStorage('key', 'initialValue'));
9 |
10 | expect(result.current.value).toBe('initialValue');
11 | expect(result.current.set).toBeTypeOf('function');
12 | expect(result.current.remove).toBeTypeOf('function');
13 | });
14 |
15 | it('should use session storage on server side', () => {
16 | const { result } = renderHookServer(() => useSessionStorage('key', 'initialValue'));
17 |
18 | expect(result.current.value).toBe('initialValue');
19 | expect(result.current.set).toBeTypeOf('undefined');
20 | expect(result.current.remove).toBeTypeOf('undefined');
21 | });
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useSet/useSet.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useField, useSet } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const scopeInput = useField({ initialValue: '' });
5 | const scopes = useSet(['@siberiacancode', '@siberiacancode-tests', '@shared']);
6 |
7 | return (
8 | <>
9 |
10 |
11 |
12 | {
15 | scopes.add(scopeInput.getValue().trim().toLowerCase());
16 | scopeInput.reset();
17 | }}
18 | >
19 | Add
20 |
21 |
22 |
23 |
24 | {Array.from(scopes.value).map((scope, index) => (
25 |
scopes.remove(scope)}>
26 | {scope}
27 |
28 | ))}
29 |
30 | >
31 | );
32 | };
33 |
34 | export default Demo;
35 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useShare/useShare.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useShare } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { share, supported } = useShare();
5 |
6 | if (!supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 | <>
23 | Click the button to share the content
24 |
27 | share({
28 | title: '@siberiacancode/reactuse',
29 | text: '@siberiacancode/reactuse is awesome',
30 | url: 'https://siberiacancode.github.io/reactuse'
31 | })
32 | }
33 | >
34 | Share
35 |
36 | >
37 | );
38 | };
39 |
40 | export default Demo;
41 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useSticky/useSticky.demo.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@siberiacancode/docs/utils';
2 | import { useSticky } from '@siberiacancode/reactuse';
3 | import { useRef } from 'react';
4 |
5 | const Demo = () => {
6 | const rootRef = useRef(null);
7 | const { ref, stuck } = useSticky({
8 | root: rootRef
9 | });
10 |
11 | return (
12 |
13 |
14 | Scroll down to see the element stick to the top
15 |
16 |
17 |
24 | {stuck ? 'Stuck' : 'Not stuck'}
25 |
26 |
27 |
More content here
28 |
29 | );
30 | };
31 |
32 | export default Demo;
33 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useStopwatch/useStopwatch.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useStopwatch } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const stopwatch = useStopwatch();
5 |
6 | return (
7 |
8 |
9 | {stopwatch.minutes} m
:{stopwatch.seconds} s
10 |
11 |
12 | Start
13 |
14 |
15 | Pause
16 |
17 |
18 | Reset
19 |
20 |
21 | );
22 | };
23 |
24 | export default Demo;
25 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useStorage/useStorage.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useLocalStorage, useStorage } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | useLocalStorage('test2');
5 | const { value, set, remove } = useStorage('siberiacancode-use-storage', '');
6 |
7 | return (
8 |
9 |
10 | String: {value || 'value is empty'}
11 |
12 |
set(event.target.value)} />
13 |
14 | Remove
15 |
16 |
17 | );
18 | };
19 |
20 | export default Demo;
21 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTextDirection/useTextDirection.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useTextDirection } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const textDirection = useTextDirection();
5 |
6 | return (
7 |
8 |
9 | {textDirection.value === 'ltr' && 'This paragraph is left-to-right text.'}
10 | {textDirection.value === 'rtl' && 'This paragraph is right-to-left text.'}
11 |
12 |
13 |
14 | textDirection.set(textDirection.value === 'ltr' ? 'rtl' : 'ltr')}
17 | >
18 | {textDirection.value === 'ltr' && 'LTR'}
19 | {textDirection.value === 'rtl' && 'RTL'}
20 |
21 | Click to change the direction
22 |
23 |
24 | );
25 | };
26 |
27 | export default Demo;
28 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTextSelection/useTextSelection.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useTextSelection } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const textSelection = useTextSelection();
5 |
6 | return (
7 | <>
8 | You can select any text on the page
9 |
10 | Selected Text:
11 | {textSelection.text || 'No selected'}
12 |
13 |
14 |
Selected rects:
15 |
{JSON.stringify(textSelection.rects, null, 2)}
16 |
17 | >
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useThrottleCallback/useThrottleCallback.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useThrottleCallback } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const clickCounter = useCounter();
5 | const throttledCounter = useCounter();
6 |
7 | const throttledIncrement = useThrottleCallback(throttledCounter.inc, 1000);
8 |
9 | const onClick = () => {
10 | throttledIncrement();
11 | clickCounter.inc();
12 | };
13 |
14 | return (
15 |
16 |
17 | Button clicked: {clickCounter.value}
18 |
19 |
20 | Event handler called: {throttledCounter.value}
21 |
22 |
Delay is set to 1000ms for this demo.
23 |
24 | Smash me my friend
25 |
26 |
27 | );
28 | };
29 |
30 | export default Demo;
31 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useThrottleCallback/useThrottleCallback.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 |
3 | import { throttle } from '@/utils/helpers';
4 |
5 | import { useEvent } from '../useEvent/useEvent';
6 |
7 | /**
8 | * @name useThrottleCallback
9 | * @description - Hook that creates a throttled callback
10 | * @category Utilities
11 | *
12 | * @template Params The type of the params
13 | * @template Return The type of the return
14 | * @param {(...args: Params) => Return} callback The callback function
15 | * @param {number} delay The delay in milliseconds
16 | * @returns {(...args: Params) => Return} The callback with throttle
17 | *
18 | * @example
19 | * const throttled = useThrottleCallback(() => console.log('callback'), 500);
20 | */
21 | export const useThrottleCallback = (
22 | callback: (...args: Params) => Return,
23 | delay: number
24 | ) => {
25 | const internalCallback = useEvent(callback);
26 | const throttled = useMemo(() => throttle(internalCallback, delay), [delay]);
27 |
28 | return throttled;
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useThrottleValue/useThrottleValue.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useCounter, useThrottleValue } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const counter = useCounter();
5 |
6 | const throttledCounterCount = useThrottleValue(counter.value, 1000);
7 |
8 | return (
9 |
10 |
11 | Value: {counter.value}
12 |
13 |
14 | Throttled value: {throttledCounterCount}
15 |
16 |
counter.inc()}>
17 | Increment
18 |
19 |
counter.dec()}>
20 | Decrement
21 |
22 |
23 | );
24 | };
25 |
26 | export default Demo;
27 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useThrottleValue/useThrottleValue.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 |
3 | import { useThrottleCallback } from '../useThrottleCallback/useThrottleCallback';
4 |
5 | /**
6 | * @name useThrottleValue
7 | * @description - Hook that creates a throttled value
8 | * @category Utilities
9 | *
10 | * @template Value The type of the value
11 | * @param {Value} value The value to be throttled
12 | * @param {number} delay The delay in milliseconds
13 | * @returns {Value} The throttled value
14 | *
15 | * @example
16 | * const throttledValue = useThrottleValue(value, 500);
17 | */
18 | export const useThrottleValue = (value: Value, delay: number) => {
19 | const previousValueRef = useRef(value);
20 | const [throttledValue, setThrottleValue] = useState(value);
21 |
22 | const throttledSetState = useThrottleCallback(setThrottleValue, delay);
23 |
24 | useEffect(() => {
25 | if (previousValueRef.current === value) return;
26 | throttledSetState(value);
27 | previousValueRef.current = value;
28 | }, [value]);
29 |
30 | return throttledValue;
31 | };
32 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTime/useTime.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useTime } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const { seconds, minutes, hours, meridiemHours, day, month, year } = useTime();
5 |
6 | return (
7 |
8 |
9 | Date{' '}
10 |
11 | {String(month).padStart(2, '0')}/{String(day).padStart(2, '0')}/
12 | {String(year).padStart(2, '0')}
13 |
14 |
15 |
16 | Time{' '}
17 |
18 | {String(hours).padStart(2, '0')}:{String(minutes).padStart(2, '0')}:
19 | {String(seconds).padStart(2, '0')}
20 |
21 |
22 |
23 |
24 | Meridiem hours:{' '}
25 |
26 |
27 | {meridiemHours.value}
28 | {meridiemHours.type}
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Demo;
37 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTime/useTime.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import { getDate } from '@/utils/helpers';
4 |
5 | import { useInterval } from '../useInterval/useInterval';
6 |
7 | export interface UseTimeReturn {
8 | day: number;
9 | hours: number;
10 | meridiemHours: { value: number; type: string };
11 | minutes: number;
12 | month: number;
13 | seconds: number;
14 | timestamp: number;
15 | year: number;
16 | }
17 |
18 | /**
19 | * @name useTime
20 | * @description - Hook that gives you current time in different values
21 | * @category Time
22 | *
23 | * @returns {UseTimeReturn} An object containing the current time
24 | *
25 | * @example
26 | * const { seconds, minutes, hours, meridiemHours, day, month, year, timestamp } = useTime();
27 | */
28 | export const useTime = (): UseTimeReturn => {
29 | const [time, setTime] = useState(getDate());
30 | useInterval(() => setTime(getDate()), 1000);
31 | return time;
32 | };
33 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTimeout/useTimeout.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useTimeout } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const timeout = useTimeout(() => console.log('ready'), 5000);
5 |
6 | return (
7 |
8 |
9 | Timeout ready: {String(timeout.ready)}
10 |
11 |
Clear
12 |
13 | );
14 | };
15 |
16 | export default Demo;
17 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTimer/useTimer.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useTimer } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const timer = useTimer(3600);
5 |
6 | return (
7 | <>
8 |
9 | Time{' '}
10 |
11 | {String(timer.hours).padStart(2, '0')}:{String(timer.minutes).padStart(2, '0')}:
12 | {String(timer.seconds).padStart(2, '0')}
13 |
14 |
15 |
16 | Timer running: {String(timer.active)}
17 |
18 |
19 |
20 | Start
21 |
22 | timer.restart(5000)}>
23 | Restart
24 |
25 |
26 | Toggle
27 |
28 |
29 | Pause
30 |
31 |
32 | Clear
33 |
34 | >
35 | );
36 | };
37 |
38 | export default Demo;
39 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useToggle/useToggle.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useToggle } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const [value, toggle] = useToggle(['blue', 'orange', 'black', 'teal', 'purple'] as const);
5 |
6 | return (
7 | toggle()}>
8 | {value}
9 |
10 | );
11 | };
12 |
13 | export default Demo;
14 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useUnmount/useUnmount.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useBoolean, useUnmount } from '@siberiacancode/reactuse';
2 |
3 | const Component = () => {
4 | useUnmount(() => alert('unmount'));
5 |
6 | return Hello World!
;
7 | };
8 |
9 | const Demo = () => {
10 | const [on, toggle] = useBoolean(true);
11 |
12 | return (
13 | <>
14 | toggle()}>
15 | {on ? 'Unmount' : 'Mount'}
16 |
17 | {on && }
18 | >
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useUnmount/useUnmount.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | /**
4 | * @name useUnmount
5 | * @description - Hook that defines the logic when unmounting a component
6 | * @category Lifecycle
7 | *
8 | * @param {() => void} callback The callback function to be invoked on component unmount
9 | * @returns {void}
10 | *
11 | * @example
12 | * useUnmount(() => console.log('This effect runs on component unmount'));
13 | */
14 |
15 | export const useUnmount = (callback: () => void) => {
16 | const internalCallbackRef = useRef(callback);
17 | internalCallbackRef.current = callback;
18 |
19 | useEffect(
20 | () => () => {
21 | internalCallbackRef.current();
22 | },
23 | []
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useVibrate/useVibrate.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useVibrate } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const vibrate = useVibrate([300, 100, 200, 100, 1000, 300]);
5 |
6 | if (!vibrate.supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 | <>
23 | Vibration
24 |
25 | Most modern mobile devices include vibration hardware, which lets software code provides
26 | physical feedback to the user by causing the device to shake.
27 |
28 | vibrate.trigger()}>
29 | Vibrate
30 |
31 | >
32 | );
33 | };
34 |
35 | export default Demo;
36 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWakeLock/useWakeLock.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useWakeLock } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const wakeLock = useWakeLock();
5 |
6 | if (!wakeLock.supported)
7 | return (
8 |
9 | Api not supported, make sure to check for compatibility with different browsers when using
10 | this{' '}
11 |
16 | api
17 |
18 |
19 | );
20 |
21 | return (
22 | <>
23 |
24 | Is active: {wakeLock.active ? 'yes' : 'no'}
25 |
26 |
27 | (!wakeLock.active ? wakeLock.request() : wakeLock.release())}>
28 | {!wakeLock.active ? 'On' : 'Off'}
29 |
30 | >
31 | );
32 | };
33 |
34 | export default Demo;
35 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowEvent/useWindowEvent.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useWindowEvent } from '@siberiacancode/reactuse';
2 | import { useState } from 'react';
3 |
4 | const Demo = () => {
5 | const [count, setCount] = useState(0);
6 |
7 | useWindowEvent('click', () => setCount(count + 1));
8 |
9 | return (
10 |
11 | Window click count: {count}
12 |
13 | );
14 | };
15 |
16 | export default Demo;
17 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowEvent/useWindowEvent.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { vi } from 'vitest';
3 |
4 | import { useWindowEvent } from './useWindowEvent';
5 |
6 | it('Should use window event', () => {
7 | const listener = vi.fn();
8 | renderHook(() => useWindowEvent('click', listener));
9 | window.dispatchEvent(new Event('click'));
10 | expect(listener).toHaveBeenCalled();
11 | });
12 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowEvent/useWindowEvent.ts:
--------------------------------------------------------------------------------
1 | import { target } from '@/utils/helpers';
2 |
3 | import type { UseEventListenerOptions } from '../useEventListener/useEventListener';
4 |
5 | import { useEventListener } from '../useEventListener/useEventListener';
6 |
7 | /**
8 | * @name useWindowEvent
9 | * @description - Hook attaches an event listener to the window object for the specified event
10 | * @category Browser
11 | *
12 | * @template Event Key of window event map.
13 | * @param {Event} event The event to listen for.
14 | * @param {(event: WindowEventMap[Event]) => void} listener The callback function to be executed when the event is triggered
15 | * @param {UseEventListenerOptions} [options] The options for the event listener
16 | * @returns {void}
17 | *
18 | * @example
19 | * useWindowEvent('click', () => console.log('clicked'));
20 | */
21 | export const useWindowEvent = (
22 | event: Event,
23 | listener: (this: Window, event: WindowEventMap[Event]) => any,
24 | options?: UseEventListenerOptions
25 | ) => useEventListener(target(window), event, listener, options);
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowFocus/useWindowFocus.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useWindowFocus } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const windowFocused = useWindowFocus();
5 |
6 | return (
7 |
8 | {windowFocused && (
9 | <>
10 | 💡 Click somewhere outside of the document to unfocus
11 | >
12 | )}
13 | {!windowFocused && (
14 | <>
15 | ℹ Tab is unfocused
16 | >
17 | )}
18 |
19 | );
20 | };
21 |
22 | export default Demo;
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowFocus/useWindowFocus.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | /**
4 | * @name useWindowFocus
5 | * @description - Hook that provides the current focus state of the window
6 | * @category Elements
7 | *
8 | * @returns {boolean} The current focus state of the window
9 | *
10 | * @example
11 | * const focused = useWindowFocus();
12 | *
13 | * @see {@link https://siberiacancode.github.io/reactuse/functions/hooks/useWindowFocus.html}
14 | */
15 | export const useWindowFocus = () => {
16 | const [focused, setFocused] = useState(false);
17 |
18 | useEffect(() => {
19 | const onFocus = () => setFocused(true);
20 | const onBlur = () => setFocused(false);
21 |
22 | window.addEventListener('focus', onFocus);
23 | window.addEventListener('blur', onBlur);
24 |
25 | return () => {
26 | window.removeEventListener('focus', onFocus);
27 | window.removeEventListener('blur', onBlur);
28 | };
29 | });
30 |
31 | return focused;
32 | };
33 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowScroll/useWindowScroll.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useWindowScroll } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const windowScroll = useWindowScroll();
5 |
6 | return (
7 |
8 |
9 | Scroll position x: {Math.floor(windowScroll.value.x)}
, y:{' '}
10 | {Math.floor(windowScroll.value.y)}
11 |
12 |
13 | windowScroll.scrollTo({ y: 0 })}>
14 | Scroll to top
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useWindowSize/useWindowSize.demo.tsx:
--------------------------------------------------------------------------------
1 | import { useWindowSize } from '@siberiacancode/reactuse';
2 |
3 | const Demo = () => {
4 | const size = useWindowSize();
5 |
6 | return (
7 |
8 |
Current window size:
9 |
10 |
11 | width: {size.width}
12 | {' '}
13 |
14 | height: {size.height}
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Demo;
22 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './helpers';
2 | export * from './hooks';
3 | export * from './utils/helpers';
4 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/copy.ts:
--------------------------------------------------------------------------------
1 | export const legacyCopyToClipboard = (value: string) => {
2 | const tempTextArea = document.createElement('textarea');
3 | tempTextArea.value = value;
4 | tempTextArea.readOnly = true;
5 | tempTextArea.style.fontSize = '16px';
6 | document.body.appendChild(tempTextArea);
7 | tempTextArea.select();
8 | document.execCommand('copy');
9 | document.body.removeChild(tempTextArea);
10 | };
11 |
12 | export const copy = async (value: string) => {
13 | try {
14 | try {
15 | await navigator.clipboard.writeText(value);
16 | } catch {
17 | return legacyCopyToClipboard(value);
18 | }
19 | } catch {
20 | return legacyCopyToClipboard(value);
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/debounce.ts:
--------------------------------------------------------------------------------
1 | export function debounce(
2 | callback: (...args: Params) => void,
3 | delay: number
4 | ): (...args: Params) => void {
5 | let timer: ReturnType;
6 |
7 | return function (this: any, ...args: Params) {
8 | clearTimeout(timer);
9 | timer = setTimeout(() => callback.apply(this, args), delay);
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/getDate.ts:
--------------------------------------------------------------------------------
1 | export const getDate = (now: Date = new Date()) => {
2 | const seconds = now.getSeconds();
3 | const minutes = now.getMinutes();
4 | const hours = now.getHours();
5 | const meridiemHours = hours % 12 === 0 ? 12 : hours % 12;
6 | const meridiemType = hours >= 12 ? 'pm' : 'am';
7 | const day = now.getDate();
8 | const month = now.getMonth() + 1;
9 | const year = now.getFullYear();
10 | const timestamp = now.getTime();
11 |
12 | return {
13 | seconds,
14 | minutes,
15 | hours,
16 | meridiemHours: { value: meridiemHours, type: meridiemType },
17 | day,
18 | month,
19 | year,
20 | timestamp
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/getElement.ts:
--------------------------------------------------------------------------------
1 | import type { RefObject } from 'react';
2 |
3 | export type Target = (() => Element) | string | Document | Element | Window;
4 | export type HookTarget =
5 | | RefObject
6 | | {
7 | value: Target;
8 | type: symbol;
9 | };
10 |
11 | export const targetSymbol = Symbol('target');
12 | export const target = (target: Target) => ({
13 | value: target,
14 | type: targetSymbol
15 | });
16 |
17 | export const getElement = (target: HookTarget) => {
18 | if ('current' in target) {
19 | return target.current;
20 | }
21 |
22 | if (typeof target.value === 'function') {
23 | return target.value();
24 | }
25 |
26 | if (typeof target.value === 'string') {
27 | return document.querySelector(target.value);
28 | }
29 |
30 | if (target.value instanceof Document) {
31 | return target.value;
32 | }
33 |
34 | if (target.value instanceof Window) {
35 | return target.value;
36 | }
37 |
38 | if (target.value instanceof Element) {
39 | return target.value;
40 | }
41 |
42 | return target.value;
43 | };
44 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/getRetry.ts:
--------------------------------------------------------------------------------
1 | export const getRetry = (retry: boolean | number) => {
2 | if (typeof retry === 'number') return retry;
3 | return retry ? 1 : 0;
4 | };
5 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './copy';
2 | export * from './debounce';
3 | export * from './getDate';
4 | export * from './getElement';
5 | export * from './getRetry';
6 | export * from './isTarget';
7 | export * from './throttle';
8 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/isTarget.ts:
--------------------------------------------------------------------------------
1 | import type { HookTarget } from './getElement';
2 |
3 | import { targetSymbol } from './getElement';
4 |
5 | export const isTarget = (target: HookTarget) =>
6 | typeof target === 'object' && ('current' in target || target.type === targetSymbol);
7 |
--------------------------------------------------------------------------------
/packages/core/src/utils/helpers/throttle.ts:
--------------------------------------------------------------------------------
1 | export const throttle = (
2 | callback: (...args: Params) => void,
3 | delay: number
4 | ): ((...args: Params) => void) => {
5 | let isCalled = false;
6 | let lastArgs: Params | null = null;
7 |
8 | const timer = () => {
9 | if (!lastArgs) {
10 | isCalled = false;
11 | return;
12 | }
13 |
14 | callback.apply(this, lastArgs);
15 | lastArgs = null;
16 | setTimeout(timer, delay);
17 | };
18 |
19 | return function (this: any, ...args: Params) {
20 | if (isCalled) {
21 | lastArgs = args;
22 | return;
23 | }
24 |
25 | callback.apply(this, args);
26 | isCalled = true;
27 | setTimeout(timer, delay);
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/packages/core/tests/createTrigger.ts:
--------------------------------------------------------------------------------
1 | export function createTrigger void>() {
2 | const observers = new Map();
3 | return {
4 | callback(key: Key, ...args: Partial>) {
5 | const observe = observers.get(key);
6 | if (!observe) return;
7 | observe(...args);
8 | },
9 | add(key: Key, callback: Callback) {
10 | observers.set(key, callback);
11 | },
12 | delete(key: Key) {
13 | observers.delete(key);
14 | },
15 | get(key: Key) {
16 | return observers.get(key);
17 | }
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/tests/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createTrigger';
2 | export * from './renderHookServer';
3 |
--------------------------------------------------------------------------------
/packages/core/tests/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { TextEncoder } from 'node:util';
2 |
3 | globalThis.TextEncoder = TextEncoder;
4 |
5 | if (typeof document !== 'undefined') {
6 | const target = document.createElement('div');
7 | target.id = 'target';
8 | document.body.appendChild(target);
9 | }
10 |
11 | afterEach(() => {
12 | vi.clearAllMocks();
13 | vi.resetAllMocks();
14 | });
15 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "jsx": "react-jsx",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "baseUrl": ".",
7 | "module": "esnext",
8 | "moduleResolution": "bundler",
9 | "paths": {
10 | "@/*": ["./src/*"]
11 | },
12 | "strict": true,
13 | "declaration": false,
14 | "emitDeclarationOnly": false,
15 | "noEmit": false,
16 | "outDir": "src/bundle",
17 | "removeComments": false,
18 | "sourceMap": false,
19 | "esModuleInterop": true
20 | },
21 | "include": ["src"],
22 | "exclude": ["**/*.test.*", "**/*.demo.*"]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "jsx": "react-jsx",
6 | "lib": ["dom", "dom.iterable", "esnext"],
7 | "baseUrl": ".",
8 | "module": "esnext",
9 | "moduleResolution": "bundler",
10 | "paths": {
11 | "@siberiacancode/docs/*": ["../docs/src/*"],
12 | "@/*": ["./src/*"],
13 | "@/tests": ["./tests"]
14 | },
15 | "resolveJsonModule": true,
16 | "types": ["vitest/globals", "@types/web-bluetooth", "@types/dom-speech-recognition"],
17 | "allowJs": true,
18 | "strict": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "newLine": "lf",
23 | "noEmit": true,
24 | "sourceMap": true,
25 | "allowSyntheticDefaultImports": true,
26 | "esModuleInterop": true,
27 | "forceConsistentCasingInFileNames": true,
28 | "isolatedModules": true,
29 | "skipLibCheck": true
30 | },
31 | "exclude": ["dist", "node_modules"]
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/vitest.config.mts:
--------------------------------------------------------------------------------
1 | import { vitest } from '@siberiacancode/vitest';
2 | import path from 'node:path';
3 | import { defineConfig } from 'vitest/config';
4 |
5 | export default defineConfig({
6 | test: {
7 | ...vitest,
8 | setupFiles: './tests/setupTests.ts'
9 | },
10 | resolve: {
11 | alias: {
12 | '@/tests': path.resolve(__dirname, './tests'),
13 | '@': path.resolve(__dirname, './src')
14 | }
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/packages/docs/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 reactuse docs
2 |
3 | the largest and most useful hook library
4 |
--------------------------------------------------------------------------------
/packages/docs/app/.vitepress/theme/global.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 | @custom-variant dark (&:where(.dark, .dark *));
3 |
4 | :root {
5 | --vp-home-hero-name-color: transparent;
6 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #61dafb, #3477d0);
7 | --vp-home-hero-image-background-image: linear-gradient(130deg, #006eff, #00c8ff);
8 | --vp-home-hero-image-filter: blur(40px) opacity(0.35);
9 |
10 | --vp-c-bg: #ffffff;
11 |
12 | --vp-c-brand-1: #087ea4;
13 | --vp-c-brand-2: #4ca8c6;
14 | --vp-c-brand-3: #52bde9;
15 | }
16 |
17 | .dark {
18 | --vp-c-brand-1: #58c4dc;
19 | --vp-c-bg: #181818;
20 | --vp-c-bg-soft: #1f1f1f;
21 | --vp-c-bg-alt: #1d1c1c;
22 | --vp-c-bg-elv: #2a2a2a;
23 | }
24 |
25 | input[type='number'] {
26 | min-width: 20rem;
27 | }
28 |
--------------------------------------------------------------------------------
/packages/docs/app/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import type { EnhanceAppContext } from 'vitepress';
2 |
3 | import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client';
4 | import Theme from 'vitepress/theme';
5 |
6 | import '@shikijs/vitepress-twoslash/style.css';
7 | import './global.css';
8 |
9 | export default {
10 | extends: Theme,
11 | enhanceApp({ app }: EnhanceAppContext) {
12 | app.use(TwoslashFloatingVue);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/packages/docs/app/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: reactuse
7 | text: the largest and most useful hook library
8 | tagline: Improve your react applications with our library 📦 designed for comfort and speed
9 | image:
10 | src: /logo.svg
11 | alt: reactuse
12 | actions:
13 | - theme: brand
14 | text: Get Started
15 | link: /introduction
16 | - theme: alt
17 | text: View on GitHub
18 | link: https://github.com/siberiacancode/reactuse
19 | ---
20 |
--------------------------------------------------------------------------------
/packages/docs/app/installation.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | # Installation
6 |
7 | How to install dependencies and structure your app.
8 |
9 | ## Install package
10 |
11 | ::: code-group
12 |
13 | ```bash [npm]
14 | npm install @siberiacancode/reactuse
15 | ```
16 |
17 | ```bash [yarn]
18 | yarn add @siberiacancode/reactuse
19 | ```
20 |
21 | ```bash [pnpm]
22 | pnpm add @siberiacancode/reactuse
23 | ```
24 |
25 | ```bash [bun]
26 | bun add @siberiacancode/reactuse
27 | ```
28 |
29 | :::
30 |
31 | ## Inject code to your framework
32 |
33 | How to install dependencies and structure your app with [cli](./cli.md) and [useverse](https://www.npmjs.com/package/useverse).
34 |
35 |
43 |
--------------------------------------------------------------------------------
/packages/docs/app/introduction.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | ## 🦉 Philosophy
4 |
5 | 🚀 React Use this is a library that will allow you to easy and simple to use React hooks. Unlike its competitors, this package takes into account the features of React and also contains a huge number of useful hooks.
6 |
7 | ## Usage
8 |
9 | You can use this library either as an npm package or by injecting the code directly into your project, giving you the flexibility to integrate it in a way that best suits your workflow.
10 |
11 | Designed with ease of use in mind, it allows you to quickly access its features without unnecessary setup, making development smoother and more efficient. Whether you prefer managing dependencies through npm or simply copying the necessary code, this approach ensures that you can start using it with minimal effort while maintaining full control over how it fits into your project.
12 |
--------------------------------------------------------------------------------
/packages/docs/app/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/docs/app/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/packages/docs/app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/docs/app/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/docs/app/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/docs/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siberiacancode/reactuse/922486fd87283f2be4ca3067deba3d35add8db8b/packages/docs/app/public/favicon.ico
--------------------------------------------------------------------------------
/packages/docs/app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactuse",
3 | "short_name": "reactuse",
4 | "description": "The largest and most useful hook library",
5 | "icons": [
6 | { "src": "/reactuse/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
7 | { "src": "/reactuse/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
8 | ],
9 | "theme_color": "#ffffff",
10 | "background_color": "#ffffff",
11 | "display": "standalone"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/docs/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { eslint } from '@siberiacancode/eslint';
2 |
3 | /** @type {import('eslint').Linter.FlatConfig} */
4 | export default eslint(
5 | {
6 | typescript: true,
7 | react: true,
8 | jsx: true,
9 | vue: true
10 | },
11 | {
12 | name: 'siberiacancode/reactuse/md',
13 | files: ['**/*.md'],
14 | rules: {
15 | 'style/max-len': 'off'
16 | }
17 | },
18 | {
19 | name: 'siberiacancode/reactuse/docs',
20 | files: ['**/src/**/*.ts'],
21 | rules: {
22 | 'regexp/no-super-linear-backtracking': 'off'
23 | }
24 | }
25 | );
26 |
--------------------------------------------------------------------------------
/packages/docs/src/components/code.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
{{ props.lang }}
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/docs/src/components/contributors.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
14 |
15 |
{{ contributor.name }}
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/packages/docs/src/components/source.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | Source •
15 | Demo
16 |
17 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/docs/checkTest.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 |
3 | export const checkTest = async (element: { type: string; name: string }) => {
4 | const files = await fs.promises.readdir(
5 | `../core/src/${element.type}s/${element.name}`
6 | );
7 |
8 | return files.some((file) => file.includes(`${element.name}.test`));
9 | };
10 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/docs/getContent.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 |
3 | export const getContent = async (type: "hook" | "helper") => {
4 | const files = await fs.promises.readdir(`../../packages/core/src/${type}s`, {
5 | withFileTypes: true,
6 | });
7 |
8 | return files
9 | .filter((file) => file.isDirectory())
10 | .map((file) => {
11 | return {
12 | type,
13 | name: file.name,
14 | };
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/docs/getContentFile.ts:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import path from "node:path";
3 |
4 | export const getContentFile = async (type: "hook" | "helper", name: string) => {
5 | try {
6 | const basePath = `../../packages/core/src/${type}s/${name}/${name}`;
7 | const dirPath = path.dirname(basePath);
8 |
9 | const files = await fs.promises.readdir(dirPath);
10 |
11 | const fileName = files.find(
12 | (file) =>
13 | file.includes(name) &&
14 | !file.includes(".test") &&
15 | !file.includes(".demo")
16 | );
17 |
18 | if (!fileName) {
19 | throw new Error(`No matching file found for ${name}`);
20 | }
21 |
22 | const filePath = path.join(dirPath, fileName);
23 | const content = await fs.promises.readFile(filePath, "utf-8");
24 |
25 | return content;
26 | } catch (error) {
27 | console.error(`Error reading file: ${error}`);
28 | throw error;
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/docs/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./checkTest";
2 | export * from "./getContent";
3 | export * from "./getContentFile";
4 | export * from "./getContentItems";
5 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./isDefaultType";
2 | export * from "./matchJsdoc";
3 | export * from "./parseHookJsdoc";
4 | export * from "./utils";
5 | export * from "./docs";
6 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/isDefaultType.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_TYPES = [
2 | 'string',
3 | 'number',
4 | 'bigint',
5 | 'boolean',
6 | 'symbol',
7 | 'undefined',
8 | 'object',
9 | 'function',
10 | 'null',
11 | 'array',
12 | 'map',
13 | 'set',
14 | 'weakset',
15 | 'weakmap',
16 | 'error',
17 | 'date'
18 | ];
19 |
20 | export const isDefaultType = (type: string) => DEFAULT_TYPES.includes(type);
21 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/matchJsdoc.ts:
--------------------------------------------------------------------------------
1 | export const matchJsdoc = (file: string) => {
2 | const jsdocCommentRegex = /\/\*\*\s*\n([^\\*]|(\*(?!\/)))*\*\//;
3 | const match = file.match(jsdocCommentRegex);
4 | return match ? match[0].trim() : undefined;
5 | };
6 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/parseHookJsdoc.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'comment-parser';
2 |
3 | export const parseHookJsdoc = (file: string) => {
4 | const jsdoc = parse(file)[0];
5 | const description = jsdoc.tags.find(({ tag }) => tag === 'description');
6 | const usages = jsdoc.tags.filter(({ tag }) => tag === 'example');
7 | const deprecated = jsdoc.tags.find(({ tag }) => tag === 'deprecated');
8 | const category = jsdoc.tags.find(({ tag }) => tag === 'category');
9 | const warning = jsdoc.tags.find(({ tag }) => tag === 'warning');
10 | const browserapi = jsdoc.tags.find(({ tag }) => tag === 'browserapi');
11 | const apiParameters = jsdoc.tags.filter(
12 | ({ tag }) => tag === 'param' || tag === 'overload' || tag === 'returns'
13 | );
14 |
15 | return { description, usages, apiParameters, deprecated, category, browserapi, warning };
16 | };
17 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import type { ClassValue } from 'clsx';
2 |
3 | import { clsx } from 'clsx';
4 | import { twMerge } from 'tailwind-merge';
5 |
6 | export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
7 |
--------------------------------------------------------------------------------
/packages/docs/src/utils/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './cn';
2 |
--------------------------------------------------------------------------------
/packages/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "jsx": "react-jsx",
6 | "lib": ["dom", "dom.iterable", "esnext"],
7 | "baseUrl": ".",
8 | "module": "esnext",
9 | "moduleResolution": "bundler",
10 | "paths": {
11 | "@siberiacancode/docs": ["*"]
12 | },
13 | "resolveJsonModule": true,
14 | "allowJs": true,
15 | "strict": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "newLine": "lf",
20 | "noEmit": true,
21 | "sourceMap": true,
22 | "allowSyntheticDefaultImports": true,
23 | "esModuleInterop": true,
24 | "forceConsistentCasingInFileNames": true,
25 | "isolatedModules": true,
26 | "skipLibCheck": true
27 | },
28 | "exclude": ["dist", "node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 |
--------------------------------------------------------------------------------