diff --git a/.gitignore b/.gitignore index b93d8c0c..eb8b0576 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,7 @@ generated # Cursor .cursor + +# Reactuse + +**/registry.json diff --git a/packages/cli/eslint.config.mjs b/packages/cli/eslint.config.mjs index e328c19f..b9fd15c4 100644 --- a/packages/cli/eslint.config.mjs +++ b/packages/cli/eslint.config.mjs @@ -1,14 +1,14 @@ -import { eslint } from '@siberiacancode/eslint'; +import { eslint } from "@siberiacancode/eslint"; export default eslint( { - typescript: true + typescript: true, }, { - name: 'siberiacancode/cli/rewrite', + name: "siberiacancode/cli/rewrite", rules: { - 'node/prefer-global/process': 'off', - 'node/prefer-global/buffer': 'off' - } + "node/prefer-global/process": "off", + "node/prefer-global/buffer": "off", + }, } ); diff --git a/packages/cli/src/add.ts b/packages/cli/src/add.ts index 26509d5f..e6934e3c 100644 --- a/packages/cli/src/add.ts +++ b/packages/cli/src/add.ts @@ -6,6 +6,7 @@ import fs from 'node:fs'; import ora from 'ora'; import prompts from 'prompts'; import { createMatchPath, loadConfig } from 'tsconfig-paths'; +import path from 'node:path'; import type { AddOptionsSchema, Registry } from '@/utils/types'; @@ -14,23 +15,26 @@ import { APP_PATH, REPO_URLS } from '@/utils/constants'; import { addOptionsSchema } from '@/utils/types'; -const resolveDependencies = (registry: Registry, hook: string) => { - const hooks = new Set(); - const utils = new Set(); +const resolveDependencies = (registry: Registry, hooks: string[]) => { + const files = new Map(); const resolveDependency = (hook: string) => { const item = registry[hook]!; - hooks.add(hook); - if (item.utils.length) Array.from(item.utils).forEach((util) => utils.add(util)); + files.set(hook, { type: 'hook', name: hook, parent: item.name }); + if (item.utils.length) Array.from(item.utils).forEach((util) => files.set(util, { type: 'util', name: util, parent: item.name })); + if (item.local.length) Array.from(item.local).forEach((local) => files.set(local, { type: 'local', name: local, parent: item.name })); + if (item.packages.length) Array.from(item.packages).forEach((packag) => files.set(packag, { type: 'package', name: packag, parent: item.name })); for (const hook of item.hooks) { resolveDependency(hook); } }; - resolveDependency(hook); + for (const hook of hooks) { + resolveDependency(hook); + } - return { hooks: Array.from(hooks), utils: Array.from(utils) }; + return files; }; export const add = { @@ -128,64 +132,77 @@ export const add = { process.exit(1); } - const spinner = ora('Installing hooks...').start(); - const files = selectedHooks.reduce<{ utils: string[]; hooks: string[] }>( - (acc, hook) => { - const item = registry[hook]; + const dependencies = resolveDependencies(registry, selectedHooks); + const packages: string[] = []; + const files = Array.from(dependencies.values()).map((dependency) => { + if (dependency.type === 'hook') { + const filePath = `${dependency.name}/${dependency.name}`; + const directoryPath = `${pathToLoadHooks}/${filePath}.${language}`; + const registryPath = `${REPO_URLS[language.toUpperCase() as keyof typeof REPO_URLS]}/hooks/${filePath}.${language}`; + const indexPath = `${pathToLoadHooks}/index.${language}`; + return { name: dependency.name, directoryPath, registryPath, type: dependency.type, indexPath, filePath }; + } - if (!item) { - spinner.fail(`Hook ${hook} not found in the registry`); - return acc; - } + if (dependency.type === 'util') { + const filePath = `${dependency.name}`; + const directoryPath = `${pathToLoadUtils}/${filePath}.${language}`; + const registryPath = `${REPO_URLS[language.toUpperCase() as keyof typeof REPO_URLS]}/utils/helpers/${filePath}.${language}`; + const indexPath = `${pathToLoadUtils}/index.${language}`; + return { name: dependency.name, directoryPath, registryPath, type: dependency.type, indexPath, filePath }; + } + + if (dependency.type === 'local') { + const filePath = `${dependency.name}`; + const directoryPath = `${pathToLoadHooks}/${dependency.parent}/helpers/${filePath}.${language}`; + const registryPath = `${REPO_URLS[language.toUpperCase() as keyof typeof REPO_URLS]}/hooks/${dependency.parent}/helpers/${filePath}.${language}`; + const indexPath = `${pathToLoadHooks}/${dependency.parent}/helpers/index.${language}`; + return { name: dependency.name, filePath, registryPath, type: dependency.type, indexPath, directoryPath }; + } - const dependencies = resolveDependencies(registry, hook); - acc.utils.push(...dependencies.utils); - acc.hooks.push(...dependencies.hooks); - return acc; - }, - { utils: [], hooks: [] } - ); + if (dependency.type === 'package') { + packages.push(dependency.name); + return; + } - const utils = Array.from(new Set(files.utils)); - const hooks = Array.from(new Set(files.hooks)); + throw new Error(`Unknown dependency type: ${dependency.type}`); + }).filter(Boolean); - Promise.all([ - ...hooks.map(async (hook) => { - const directory = `${pathToLoadHooks}/${hook}`; - const path = `${directory}/${hook}.${language}`; - if (!fs.existsSync(directory)) fs.mkdirSync(directory, { recursive: true }); + const spinner = ora('Installing files...').start(); + for (const file of files) { + const { directoryPath, registryPath, indexPath, filePath, name } = file!; + spinner.text = `Installing ${name}...`; + const directory = path.dirname(directoryPath); - const hookFileResponse = await fetches.get( - `${REPO_URLS[language.toUpperCase() as keyof typeof REPO_URLS]}/hooks/${hook}/${hook}.${language}` - ); - const buffer = Buffer.from(hookFileResponse.data); + if (directory) { + spinner.stop() + const { overwrite } = await prompts({ + type: "confirm", + name: "overwrite", + message: `File ${name} already exists. Would you like to overwrite?`, + initial: false, + }); - await fs.writeFileSync(path, buffer); - }), - ...utils.map(async (util) => { - const directory = pathToLoadUtils; - const path = `${directory}/${util}.${language}`; + if (!overwrite) { + console.log(`Skipped ${name}. To overwrite, run with the ${chalk.green("--overwrite")} flag.`); + continue; + } - if (!fs.existsSync(directory)) fs.mkdirSync(directory, { recursive: true }); + spinner.start(`Installing ${name}...`) + } - const utilFileResponse = await fetches.get( - `${REPO_URLS[language.toUpperCase() as keyof typeof REPO_URLS]}/utils/helpers/${util}.${language}` - ); - const buffer = Buffer.from(utilFileResponse.data); + if (!fs.existsSync(directory)) fs.mkdirSync(directory, { recursive: true }); - await fs.writeFileSync(path, buffer); + const fileResponse = await fetches.get(registryPath); + await fs.writeFileSync(directoryPath, fileResponse.data); - const indexPath = `${pathToLoadUtils}/index.${language}`; - const indexExist = fs.existsSync(indexPath); - const exportStatement = `export * from './${util}';\n`; + const exportStatement = `export * from './${filePath}';\n`; - if (!indexExist) fs.writeFileSync(indexPath, ''); - const indexFileContent = fs.readFileSync(indexPath, 'utf-8'); - if (!indexFileContent.includes(exportStatement)) - fs.appendFileSync(indexPath, exportStatement, 'utf-8'); - }) - ]); + if (!fs.existsSync(indexPath)) fs.writeFileSync(indexPath, ''); + const indexFileContent = fs.readFileSync(indexPath, 'utf-8'); + if (!indexFileContent.includes(exportStatement)) + fs.appendFileSync(indexPath, exportStatement, 'utf-8'); + } spinner.succeed('Done.'); } diff --git a/packages/cli/src/registry/index.ts b/packages/cli/src/registry/index.ts index dcf65b94..a8a739b1 100644 --- a/packages/cli/src/registry/index.ts +++ b/packages/cli/src/registry/index.ts @@ -16,7 +16,6 @@ export const REGISTRY_PATH = path.join(DOCS_PUBLIC_PATH, 'registry.json'); export const registry = async () => { console.log('📦 Building registry'); - console.log('@', fetches); const hooksResponse = await fetches.get<{ name: string }[]>( 'https://api.github.com/repos/siberiacancode/reactuse/contents/packages/core/src/hooks' ); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 6167bcef..9881d25b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -17,10 +17,10 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "preserveWatchOutput": true, - - "isolatedModules": false, - "skipLibCheck": true + "isolatedModules": true, + "skipLibCheck": true, + "module": "ESNext" }, - "include": ["src/**/*.ts", "scripts/**/*.ts"], + "include": ["src"], "exclude": ["node_modules"] } diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs index b7350ab3..6bbb88ac 100644 --- a/packages/core/eslint.config.mjs +++ b/packages/core/eslint.config.mjs @@ -1,4 +1,4 @@ -import { eslint } from '@siberiacancode/eslint'; +import { eslint } from "@siberiacancode/eslint"; export default eslint( { @@ -6,20 +6,21 @@ export default eslint( javascript: true, react: true, jsx: true, - vue: true + vue: true, }, { - name: 'siberiacancode/core/hooks', - files: ['**/hooks/**/*.ts'], + name: "siberiacancode/core/hooks", + files: ["**/hooks/**/*.ts"], rules: { - 'jsdoc/no-defaults': 'off' - } + "jsdoc/no-defaults": "off", + "react-hooks/rules-of-hooks": "warn", + }, }, { - name: 'siberiacancode/core/demo', - files: ['**/*.demo.tsx'], + name: "siberiacancode/core/demo", + files: ["**/*.demo.tsx"], rules: { - 'no-alert': 'off' - } + "no-alert": "off", + }, } ); diff --git a/packages/core/src/bundle/hooks/useBreakpoints/helpers/breakpoints.js b/packages/core/src/bundle/hooks/useBreakpoints/helpers/breakpoints.js new file mode 100644 index 00000000..d1a48efb --- /dev/null +++ b/packages/core/src/bundle/hooks/useBreakpoints/helpers/breakpoints.js @@ -0,0 +1,80 @@ +/** Breakpoints from Material UI */ +export const BREAKPOINTS_MATERIAL_UI = { + xs: 0, + sm: 600, + md: 900, + lg: 1200, + xl: 1536 +}; +/** Breakpoints from Mantine */ +export const BREAKPOINTS_MANTINE = { + xs: 576, + sm: 768, + md: 992, + lg: 1200, + xl: 1408 +}; +/** Breakpoints from Tailwind */ +export const BREAKPOINTS_TAILWIND = { + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + '2xl': 1536 +}; +/** Breakpoints from Bootstrap V5 */ +export const BREAKPOINTS_BOOTSTRAP_V5 = { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1400 +}; +/** Breakpoints from Ant Design */ +export const BREAKPOINTS_ANT_DESIGN = { + xs: 480, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1600 +}; +/** Breakpoints from Quasar V2 */ +export const BREAKPOINTS_QUASAR_V2 = { + xs: 0, + sm: 600, + md: 1024, + lg: 1440, + xl: 1920 +}; +/** Sematic Breakpoints */ +export const BREAKPOINTS_SEMANTIC = { + mobileS: 320, + mobileM: 375, + mobileL: 425, + tablet: 768, + laptop: 1024, + laptopL: 1440, + desktop4K: 2560 +}; +/** Breakpoints from Master CSS */ +export const BREAKPOINTS_MASTER_CSS = { + '3xs': 360, + '2xs': 480, + xs: 600, + sm: 768, + md: 1024, + lg: 1280, + xl: 1440, + '2xl': 1600, + '3xl': 1920, + '4xl': 2560 +}; +/** Breakpoints from PrimeFlex */ +export const BREAKPOINTS_PRIME_FLEX = { + sm: 576, + md: 768, + lg: 992, + xl: 1200 +}; diff --git a/packages/core/src/bundle/hooks/useBreakpoints/helpers/index.js b/packages/core/src/bundle/hooks/useBreakpoints/helpers/index.js new file mode 100644 index 00000000..e45b59a3 --- /dev/null +++ b/packages/core/src/bundle/hooks/useBreakpoints/helpers/index.js @@ -0,0 +1 @@ +export * from './breakpoints'; diff --git a/packages/core/src/bundle/hooks/useBreakpoints/useBreakpoints.js b/packages/core/src/bundle/hooks/useBreakpoints/useBreakpoints.js index 132339f6..22c469bf 100644 --- a/packages/core/src/bundle/hooks/useBreakpoints/useBreakpoints.js +++ b/packages/core/src/bundle/hooks/useBreakpoints/useBreakpoints.js @@ -62,4 +62,4 @@ export const useBreakpoints = (breakpoints, strategy = 'mobile-first') => { ...breakpointsKeys }; }; -export * from './constants/breakpoints'; +export * from './helpers'; diff --git a/packages/core/src/bundle/hooks/useGamepad/helpers/index.js b/packages/core/src/bundle/hooks/useGamepad/helpers/index.js new file mode 100644 index 00000000..1ca29a20 --- /dev/null +++ b/packages/core/src/bundle/hooks/useGamepad/helpers/index.js @@ -0,0 +1 @@ +export * from './mapGamepadToXbox360Controller'; diff --git a/packages/core/src/bundle/hooks/useGamepad/useGamepad.js b/packages/core/src/bundle/hooks/useGamepad/useGamepad.js index a5e6f626..aaaf5602 100644 --- a/packages/core/src/bundle/hooks/useGamepad/useGamepad.js +++ b/packages/core/src/bundle/hooks/useGamepad/useGamepad.js @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react'; -import { useEvent } from '../useEvent/useEvent'; import { useRaf } from '../useRaf/useRaf'; /** * @name useGamepad @@ -14,7 +13,6 @@ import { useRaf } from '../useRaf/useRaf'; export const useGamepad = () => { const supported = typeof navigator !== 'undefined' && 'getGamepads' in navigator; const [gamepads, setGamepads] = useState({}); - const { active } = useRaf(() => { }, { enabled: !!Object.keys(gamepads).length }); const createGamepad = (gamepad) => { const hapticActuators = []; const vibrationActuator = 'vibrationActuator' in gamepad ? gamepad.vibrationActuator : null; @@ -27,23 +25,30 @@ export const useGamepad = () => { hapticActuators }; }; + const updateGamepadState = () => { + for (const gamepad of navigator.getGamepads() ?? []) { + if (gamepad && gamepads[gamepad.index]) + gamepads[gamepad.index] = createGamepad(gamepad); + } + }; + const { active } = useRaf(updateGamepadState, { enabled: !!Object.keys(gamepads).length }); useEffect(() => { if (!supported) return; const gamepads = navigator.getGamepads(); setGamepads(gamepads.reduce((acc, gamepad) => ({ ...acc, ...(gamepad && { [gamepad.index]: createGamepad(gamepad) }) }), {})); }, []); - const onConnected = useEvent((event) => { - const { gamepad } = event; - setGamepads({ ...gamepads, [gamepad.index]: createGamepad(gamepad) }); - }); - const onDisconnected = useEvent((event) => { - const { gamepad } = event; - const updatedGamepads = { ...gamepads }; - delete updatedGamepads[gamepad.index]; - setGamepads(updatedGamepads); - }); useEffect(() => { + const onConnected = (event) => { + const { gamepad } = event; + setGamepads({ ...gamepads, [gamepad.index]: createGamepad(gamepad) }); + }; + const onDisconnected = (event) => { + const { gamepad } = event; + const updatedGamepads = { ...gamepads }; + delete updatedGamepads[gamepad.index]; + setGamepads(updatedGamepads); + }; document.addEventListener('gamepadconnected', onConnected); document.addEventListener('gamepaddisconnected', onDisconnected); return () => { @@ -57,4 +62,4 @@ export const useGamepad = () => { gamepads: Object.values(gamepads) }; }; -export * from './helpers/mapGamepadToXbox360Controller'; +export * from './helpers'; diff --git a/packages/core/src/bundle/hooks/useLocalStorage/useLocalStorage.js b/packages/core/src/bundle/hooks/useLocalStorage/useLocalStorage.js index a055b79e..10496699 100644 --- a/packages/core/src/bundle/hooks/useLocalStorage/useLocalStorage.js +++ b/packages/core/src/bundle/hooks/useLocalStorage/useLocalStorage.js @@ -12,4 +12,8 @@ import { useStorage } from '../useStorage/useStorage'; * @example * const { value, set, remove } = useLocalStorage('key', 'value'); */ -export const useLocalStorage = (key, initialValue, options) => useStorage(key, { initialValue, storage: window.localStorage, ...options }); +export const useLocalStorage = (key, initialValue, options) => useStorage(key, { + ...options, + initialValue, + storage: typeof window !== 'undefined' ? window.localStorage : undefined +}); diff --git a/packages/core/src/bundle/hooks/useSessionStorage/useSessionStorage.js b/packages/core/src/bundle/hooks/useSessionStorage/useSessionStorage.js index 0e7d0f68..71d91c2a 100644 --- a/packages/core/src/bundle/hooks/useSessionStorage/useSessionStorage.js +++ b/packages/core/src/bundle/hooks/useSessionStorage/useSessionStorage.js @@ -12,4 +12,8 @@ import { useStorage } from '../useStorage/useStorage'; * @example * const { value, set, remove } = useSessionStorage('key', 'value'); */ -export const useSessionStorage = (key, initialValue, options) => useStorage(key, { initialValue, storage: window.sessionStorage, ...options }); +export const useSessionStorage = (key, initialValue, options) => useStorage(key, { + ...options, + initialValue, + storage: typeof window !== 'undefined' ? window.localStorage : undefined +}); diff --git a/packages/core/src/bundle/hooks/useStorage/useStorage.js b/packages/core/src/bundle/hooks/useStorage/useStorage.js index d02d36d0..d289c2f2 100644 --- a/packages/core/src/bundle/hooks/useStorage/useStorage.js +++ b/packages/core/src/bundle/hooks/useStorage/useStorage.js @@ -40,6 +40,10 @@ const getServerSnapshot = () => undefined; export const useStorage = (key, params) => { const options = (typeof params === 'object' ? params : undefined); const initialValue = (options ? options?.initialValue : params); + if (typeof window === 'undefined') + return { + value: initialValue instanceof Function ? initialValue() : initialValue + }; const serializer = (value) => { if (options?.serializer) return options.serializer(value); @@ -73,12 +77,6 @@ export const useStorage = (key, params) => { setStorageItem(storage, key, serializer(initialValue instanceof Function ? initialValue() : initialValue)); } }, [key]); - if (typeof window === 'undefined') - return { - value: initialValue instanceof Function ? initialValue() : initialValue, - set, - remove - }; return { value: store ? deserializer(store) : undefined, set, diff --git a/packages/core/src/hooks/useBreakpoints/constants/breakpoints.ts b/packages/core/src/hooks/useBreakpoints/helpers/breakpoints.ts similarity index 100% rename from packages/core/src/hooks/useBreakpoints/constants/breakpoints.ts rename to packages/core/src/hooks/useBreakpoints/helpers/breakpoints.ts diff --git a/packages/core/src/hooks/useBreakpoints/helpers/index.ts b/packages/core/src/hooks/useBreakpoints/helpers/index.ts new file mode 100644 index 00000000..e45b59a3 --- /dev/null +++ b/packages/core/src/hooks/useBreakpoints/helpers/index.ts @@ -0,0 +1 @@ +export * from './breakpoints'; diff --git a/packages/core/src/hooks/useBreakpoints/useBreakpoints.ts b/packages/core/src/hooks/useBreakpoints/useBreakpoints.ts index c0af0cf2..32ac28e5 100644 --- a/packages/core/src/hooks/useBreakpoints/useBreakpoints.ts +++ b/packages/core/src/hooks/useBreakpoints/useBreakpoints.ts @@ -114,4 +114,4 @@ export const useBreakpoints = ( }; }; -export * from './constants/breakpoints'; +export * from './helpers'; diff --git a/packages/core/src/hooks/useGamepad/helpers/index.ts b/packages/core/src/hooks/useGamepad/helpers/index.ts new file mode 100644 index 00000000..1ca29a20 --- /dev/null +++ b/packages/core/src/hooks/useGamepad/helpers/index.ts @@ -0,0 +1 @@ +export * from './mapGamepadToXbox360Controller'; diff --git a/packages/core/src/hooks/useGamepad/useGamepad.ts b/packages/core/src/hooks/useGamepad/useGamepad.ts index 3db95cc6..70614a57 100644 --- a/packages/core/src/hooks/useGamepad/useGamepad.ts +++ b/packages/core/src/hooks/useGamepad/useGamepad.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; -import { useEvent } from '../useEvent/useEvent'; import { useRaf } from '../useRaf/useRaf'; declare global { @@ -33,8 +32,6 @@ export const useGamepad = () => { const supported = typeof navigator !== 'undefined' && 'getGamepads' in navigator; const [gamepads, setGamepads] = useState>({}); - const { active } = useRaf(() => {}, { enabled: !!Object.keys(gamepads).length }); - const createGamepad = (gamepad: Gamepad) => { const hapticActuators = []; const vibrationActuator = 'vibrationActuator' in gamepad ? gamepad.vibrationActuator : null; @@ -48,6 +45,14 @@ export const useGamepad = () => { } as Gamepad; }; + const updateGamepadState = () => { + for (const gamepad of navigator.getGamepads() ?? []) { + if (gamepad && gamepads[gamepad.index]) gamepads[gamepad.index] = createGamepad(gamepad); + } + }; + + const { active } = useRaf(updateGamepadState, { enabled: !!Object.keys(gamepads).length }); + useEffect(() => { if (!supported) return; const gamepads = navigator.getGamepads(); @@ -59,19 +64,19 @@ export const useGamepad = () => { ); }, []); - const onConnected = useEvent((event: Event) => { - const { gamepad } = event as GamepadEvent; - setGamepads({ ...gamepads, [gamepad.index]: createGamepad(gamepad) }); - }); + useEffect(() => { + const onConnected = (event: Event) => { + const { gamepad } = event as GamepadEvent; + setGamepads({ ...gamepads, [gamepad.index]: createGamepad(gamepad) }); + }; - const onDisconnected = useEvent((event: Event) => { - const { gamepad } = event as GamepadEvent; - const updatedGamepads = { ...gamepads }; - delete updatedGamepads[gamepad.index]; - setGamepads(updatedGamepads); - }); + const onDisconnected = (event: Event) => { + const { gamepad } = event as GamepadEvent; + const updatedGamepads = { ...gamepads }; + delete updatedGamepads[gamepad.index]; + setGamepads(updatedGamepads); + }; - useEffect(() => { document.addEventListener('gamepadconnected', onConnected); document.addEventListener('gamepaddisconnected', onDisconnected); @@ -88,4 +93,4 @@ export const useGamepad = () => { }; }; -export * from './helpers/mapGamepadToXbox360Controller'; +export * from './helpers'; diff --git a/packages/core/src/hooks/useLocalStorage/useLocalStorage.test.ts b/packages/core/src/hooks/useLocalStorage/useLocalStorage.test.ts new file mode 100644 index 00000000..c773ba3f --- /dev/null +++ b/packages/core/src/hooks/useLocalStorage/useLocalStorage.test.ts @@ -0,0 +1,21 @@ +import { renderHook } from '@testing-library/react'; + +import { renderHookServer } from '@/tests'; + +import { useLocalStorage } from './useLocalStorage'; + +it('Should use session storage', () => { + const { result } = renderHook(() => useLocalStorage('key', 'initialValue')); + + expect(result.current.value).toBe('initialValue'); + expect(result.current.set).toBeTypeOf('function'); + expect(result.current.remove).toBeTypeOf('function'); +}); + +it('should use session storage on server side', () => { + const { result } = renderHookServer(() => useLocalStorage('key', 'initialValue')); + + expect(result.current.value).toBe('initialValue'); + expect(result.current.set).toBeTypeOf('undefined'); + expect(result.current.remove).toBeTypeOf('undefined'); +}); diff --git a/packages/core/src/hooks/useLocalStorage/useLocalStorage.ts b/packages/core/src/hooks/useLocalStorage/useLocalStorage.ts index 48afb21b..1916153c 100644 --- a/packages/core/src/hooks/useLocalStorage/useLocalStorage.ts +++ b/packages/core/src/hooks/useLocalStorage/useLocalStorage.ts @@ -19,4 +19,9 @@ export const useLocalStorage = ( key: string, initialValue?: UseStorageInitialValue, options?: UseStorageOptions -) => useStorage(key, { initialValue, storage: window.localStorage, ...options }); +) => + useStorage(key, { + ...options, + initialValue, + storage: typeof window !== 'undefined' ? window.localStorage : undefined + }); diff --git a/packages/core/src/hooks/useSessionStorage/useSessionStorage.test.ts b/packages/core/src/hooks/useSessionStorage/useSessionStorage.test.ts new file mode 100644 index 00000000..487c1a14 --- /dev/null +++ b/packages/core/src/hooks/useSessionStorage/useSessionStorage.test.ts @@ -0,0 +1,21 @@ +import { renderHook } from '@testing-library/react'; + +import { renderHookServer } from '@/tests'; + +import { useSessionStorage } from './useSessionStorage'; + +it('Should use session storage', () => { + const { result } = renderHook(() => useSessionStorage('key', 'initialValue')); + + expect(result.current.value).toBe('initialValue'); + expect(result.current.set).toBeTypeOf('function'); + expect(result.current.remove).toBeTypeOf('function'); +}); + +it('should use session storage on server side', () => { + const { result } = renderHookServer(() => useSessionStorage('key', 'initialValue')); + + expect(result.current.value).toBe('initialValue'); + expect(result.current.set).toBeTypeOf('undefined'); + expect(result.current.remove).toBeTypeOf('undefined'); +}); diff --git a/packages/core/src/hooks/useSessionStorage/useSessionStorage.ts b/packages/core/src/hooks/useSessionStorage/useSessionStorage.ts index 17a4b990..11c3f847 100644 --- a/packages/core/src/hooks/useSessionStorage/useSessionStorage.ts +++ b/packages/core/src/hooks/useSessionStorage/useSessionStorage.ts @@ -19,4 +19,9 @@ export const useSessionStorage = ( key: string, initialValue?: UseStorageInitialValue, options?: UseStorageOptions -) => useStorage(key, { initialValue, storage: window.sessionStorage, ...options }); +) => + useStorage(key, { + ...options, + initialValue, + storage: typeof window !== 'undefined' ? window.localStorage : undefined + }); diff --git a/packages/core/src/hooks/useStorage/useStorage.ts b/packages/core/src/hooks/useStorage/useStorage.ts index 5d4f14ee..b1b79ca4 100644 --- a/packages/core/src/hooks/useStorage/useStorage.ts +++ b/packages/core/src/hooks/useStorage/useStorage.ts @@ -79,6 +79,11 @@ export const useStorage = ( const options = (typeof params === 'object' ? params : undefined) as UseStorageOptions; const initialValue = (options ? options?.initialValue : params) as UseStorageInitialValue; + if (typeof window === 'undefined') + return { + value: initialValue instanceof Function ? initialValue() : initialValue + } as UseStorageReturn; + const serializer = (value: Value) => { if (options?.serializer) return options.serializer(value); return JSON.stringify(value); @@ -120,13 +125,6 @@ export const useStorage = ( } }, [key]); - if (typeof window === 'undefined') - return { - value: initialValue instanceof Function ? initialValue() : initialValue, - set, - remove - }; - return { value: store ? deserializer(store) : undefined, set, diff --git a/packages/docs/app/public/registry.json b/packages/docs/app/public/registry.json deleted file mode 100644 index 2b4731c2..00000000 --- a/packages/docs/app/public/registry.json +++ /dev/null @@ -1,842 +0,0 @@ -{ - "useActiveElement": { - "name": "useActiveElement", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useAsync": { - "name": "useAsync", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useBattery": { - "name": "useBattery", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useBluetooth": { - "name": "useBluetooth", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useBoolean": { - "name": "useBoolean", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useBreakpoints": { - "name": "useBreakpoints", - "hooks": ["useRerender"], - "utils": [], - "local": [], - "packages": [] - }, - "useBrowserLanguage": { - "name": "useBrowserLanguage", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useClickOutside": { - "name": "useClickOutside", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useClipboard": { - "name": "useClipboard", - "hooks": ["usePermission"], - "utils": [], - "local": [], - "packages": [] - }, - "useConst": { - "name": "useConst", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useCounter": { - "name": "useCounter", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useCssVar": { - "name": "useCssVar", - "hooks": ["useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useDebounceCallback": { - "name": "useDebounceCallback", - "hooks": ["useEvent"], - "utils": ["debounce"], - "local": [], - "packages": [] - }, - "useDebounceValue": { - "name": "useDebounceValue", - "hooks": ["useDebounceCallback"], - "utils": [], - "local": [], - "packages": [] - }, - "useDefault": { - "name": "useDefault", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useDeviceMotion": { - "name": "useDeviceMotion", - "hooks": [], - "utils": ["throttle"], - "local": [], - "packages": [] - }, - "useDeviceOrientation": { - "name": "useDeviceOrientation", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useDevicePixelRatio": { - "name": "useDevicePixelRatio", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useDidUpdate": { - "name": "useDidUpdate", - "hooks": ["useIsomorphicLayoutEffect"], - "utils": [], - "local": [], - "packages": [] - }, - "useDisclosure": { - "name": "useDisclosure", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useDisplayMedia": { - "name": "useDisplayMedia", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useDocumentEvent": { - "name": "useDocumentEvent", - "hooks": ["useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useDocumentTitle": { - "name": "useDocumentTitle", - "hooks": ["useIsomorphicLayoutEffect"], - "utils": [], - "local": [], - "packages": [] - }, - "useDocumentVisibility": { - "name": "useDocumentVisibility", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useElementSize": { - "name": "useElementSize", - "hooks": ["useIsomorphicLayoutEffect", "useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useEvent": { - "name": "useEvent", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useEventListener": { - "name": "useEventListener", - "hooks": ["useEvent", "useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useEyeDropper": { - "name": "useEyeDropper", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useFavicon": { - "name": "useFavicon", - "hooks": ["useDidUpdate", "useMount"], - "utils": [], - "local": [], - "packages": [] - }, - "useField": { - "name": "useField", - "hooks": ["useRerender"], - "utils": [], - "local": [], - "packages": [] - }, - "useFileDialog": { - "name": "useFileDialog", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useFocus": { - "name": "useFocus", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useFps": { - "name": "useFps", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useFullscreen": { - "name": "useFullscreen", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": ["screenfull"] - }, - "useGamepad": { - "name": "useGamepad", - "hooks": ["useEvent", "useRaf"], - "utils": [], - "local": ["mapGamepadToXbox360Controller"], - "packages": [] - }, - "useGeolocation": { - "name": "useGeolocation", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useHash": { - "name": "useHash", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useHotkeys": { - "name": "useHotkeys", - "hooks": ["useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useHover": { - "name": "useHover", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useIdle": { - "name": "useIdle", - "hooks": [], - "utils": ["throttle"], - "local": [], - "packages": [] - }, - "useImage": { - "name": "useImage", - "hooks": ["useQuery"], - "utils": [], - "local": [], - "packages": [] - }, - "useInfiniteScroll": { - "name": "useInfiniteScroll", - "hooks": ["useEvent", "useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useIntersectionObserver": { - "name": "useIntersectionObserver", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useInterval": { - "name": "useInterval", - "hooks": ["useEvent"], - "utils": [], - "local": [], - "packages": [] - }, - "useIsFirstRender": { - "name": "useIsFirstRender", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useIsomorphicLayoutEffect": { - "name": "useIsomorphicLayoutEffect", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useKeyPress": { - "name": "useKeyPress", - "hooks": ["useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useKeyPressEvent": { - "name": "useKeyPressEvent", - "hooks": ["useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useKeyboard": { - "name": "useKeyboard", - "hooks": ["useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useKeysPressed": { - "name": "useKeysPressed", - "hooks": ["useDidUpdate", "useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useLastChanged": { - "name": "useLastChanged", - "hooks": ["useDidUpdate"], - "utils": [], - "local": [], - "packages": [] - }, - "useLatest": { - "name": "useLatest", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useList": { - "name": "useList", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useLocalStorage": { - "name": "useLocalStorage", - "hooks": ["useStorage"], - "utils": [], - "local": [], - "packages": [] - }, - "useLogger": { - "name": "useLogger", - "hooks": ["useDidUpdate"], - "utils": [], - "local": [], - "packages": [] - }, - "useLongPress": { - "name": "useLongPress", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useMap": { - "name": "useMap", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useMeasure": { - "name": "useMeasure", - "hooks": ["useRefState", "useResizeObserver"], - "utils": [], - "local": [], - "packages": [] - }, - "useMediaQuery": { - "name": "useMediaQuery", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useMemory": { - "name": "useMemory", - "hooks": ["useInterval"], - "utils": [], - "local": [], - "packages": [] - }, - "useMount": { - "name": "useMount", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useMouse": { - "name": "useMouse", - "hooks": ["useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useMutation": { - "name": "useMutation", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useMutationObserver": { - "name": "useMutationObserver", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useNetwork": { - "name": "useNetwork", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useOffsetPagination": { - "name": "useOffsetPagination", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useOnline": { - "name": "useOnline", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useOperatingSystem": { - "name": "useOperatingSystem", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useOptimistic": { - "name": "useOptimistic", - "hooks": ["useDidUpdate"], - "utils": [], - "local": [], - "packages": [] - }, - "useOrientation": { - "name": "useOrientation", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useOtpCredential": { - "name": "useOtpCredential", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "usePageLeave": { - "name": "usePageLeave", - "hooks": ["useEvent"], - "utils": [], - "local": [], - "packages": [] - }, - "usePaint": { - "name": "usePaint", - "hooks": ["useEvent"], - "utils": ["getElement"], - "local": ["Pointer", "Paint"], - "packages": [] - }, - "useParallax": { - "name": "useParallax", - "hooks": ["useDeviceOrientation", "useMouse", "useRefState", "useScreenOrientation"], - "utils": [], - "local": [], - "packages": [] - }, - "usePermission": { - "name": "usePermission", - "hooks": ["useEvent"], - "utils": [], - "local": [], - "packages": [] - }, - "usePointerLock": { - "name": "usePointerLock", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "usePostMessage": { - "name": "usePostMessage", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "usePreferredColorScheme": { - "name": "usePreferredColorScheme", - "hooks": ["useMediaQuery"], - "utils": [], - "local": [], - "packages": [] - }, - "usePreferredContrast": { - "name": "usePreferredContrast", - "hooks": ["useMediaQuery"], - "utils": [], - "local": [], - "packages": [] - }, - "usePreferredDark": { - "name": "usePreferredDark", - "hooks": ["useMediaQuery"], - "utils": [], - "local": [], - "packages": [] - }, - "usePreferredLanguages": { - "name": "usePreferredLanguages", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "usePreferredReducedMotion": { - "name": "usePreferredReducedMotion", - "hooks": ["useMediaQuery"], - "utils": [], - "local": [], - "packages": [] - }, - "usePrevious": { - "name": "usePrevious", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useQuery": { - "name": "useQuery", - "hooks": ["useDidUpdate", "useMount"], - "utils": ["getRetry"], - "local": [], - "packages": [] - }, - "useQueue": { - "name": "useQueue", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useRaf": { - "name": "useRaf", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useRafValue": { - "name": "useRafValue", - "hooks": ["useUnmount"], - "utils": [], - "local": [], - "packages": [] - }, - "useRefState": { - "name": "useRefState", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useRenderCount": { - "name": "useRenderCount", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useRenderInfo": { - "name": "useRenderInfo", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useRerender": { - "name": "useRerender", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useResizeObserver": { - "name": "useResizeObserver", - "hooks": ["useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useScreenOrientation": { - "name": "useScreenOrientation", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useScript": { - "name": "useScript", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useScroll": { - "name": "useScroll", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useScrollIntoView": { - "name": "useScrollIntoView", - "hooks": ["useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useScrollTo": { - "name": "useScrollTo", - "hooks": ["useRefState"], - "utils": ["getElement", "isTarget"], - "local": [], - "packages": [] - }, - "useSessionStorage": { - "name": "useSessionStorage", - "hooks": ["useStorage"], - "utils": [], - "local": [], - "packages": [] - }, - "useSet": { - "name": "useSet", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useShare": { - "name": "useShare", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useStateHistory": { - "name": "useStateHistory", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useStep": { - "name": "useStep", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useStopwatch": { - "name": "useStopwatch", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useStorage": { - "name": "useStorage", - "hooks": ["useIsomorphicLayoutEffect"], - "utils": [], - "local": [], - "packages": [] - }, - "useTextDirection": { - "name": "useTextDirection", - "hooks": ["useIsomorphicLayoutEffect", "useRefState"], - "utils": ["getElement"], - "local": [], - "packages": [] - }, - "useTextSelection": { - "name": "useTextSelection", - "hooks": ["useRerender"], - "utils": [], - "local": [], - "packages": [] - }, - "useThrottleCallback": { - "name": "useThrottleCallback", - "hooks": ["useEvent"], - "utils": ["throttle"], - "local": [], - "packages": [] - }, - "useThrottleValue": { - "name": "useThrottleValue", - "hooks": ["useThrottleCallback"], - "utils": [], - "local": [], - "packages": [] - }, - "useTime": { - "name": "useTime", - "hooks": ["useInterval"], - "utils": ["getDate"], - "local": [], - "packages": [] - }, - "useTimeout": { - "name": "useTimeout", - "hooks": ["useEvent"], - "utils": [], - "local": [], - "packages": [] - }, - "useTimer": { - "name": "useTimer", - "hooks": ["useInterval"], - "utils": [], - "local": [], - "packages": [] - }, - "useToggle": { - "name": "useToggle", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useUnmount": { - "name": "useUnmount", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useVibrate": { - "name": "useVibrate", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useWebSocket": { - "name": "useWebSocket", - "hooks": ["useEvent"], - "utils": ["getRetry"], - "local": [], - "packages": [] - }, - "useWindowEvent": { - "name": "useWindowEvent", - "hooks": ["useEventListener"], - "utils": [], - "local": [], - "packages": [] - }, - "useWindowFocus": { - "name": "useWindowFocus", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useWindowScroll": { - "name": "useWindowScroll", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useWindowSize": { - "name": "useWindowSize", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - }, - "useWizard": { - "name": "useWizard", - "hooks": [], - "utils": [], - "local": [], - "packages": [] - } -}