diff --git a/packages/hooks/index.ts b/packages/hooks/index.ts new file mode 100644 index 000000000..aefea5e39 --- /dev/null +++ b/packages/hooks/index.ts @@ -0,0 +1 @@ +export { useStorageToggle } from './src/useStorageToggle'; diff --git a/packages/hooks/package.json b/packages/hooks/package.json new file mode 100644 index 000000000..169a8e6f8 --- /dev/null +++ b/packages/hooks/package.json @@ -0,0 +1,45 @@ +{ + "name": "@navikt/ft-hooks", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "test": "vitest", + "test:watch": "vitest --watch=true", + "test:coverage": "vitest --coverage", + "tsc": "tsc --pretty", + "eslint": "eslint \"src/**/*.ts*\" --color", + "eslint:fix": "eslint --fix \"src/**/*.ts*\" --color", + "prettier": "prettier --write src", + "dev": "vite build --watch --mode development", + "build": "vite build", + "clean": "rm -rf ./dist ./node_modules" + }, + "peerDependencies": { + "react": "18.3.1" + }, + "devDependencies": { + "eslint": "9.13.0", + "typescript": "5.6.3", + "vite": "5.4.10", + "vitest": "2.1.3" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/navikt/ft-frontend-saksbehandling" + }, + "exports": { + ".": { + "import": "./dist/index.es.js", + "types": "./dist/index.d.ts", + "require": "./dist/index.umd.js" + } + } +} diff --git a/packages/hooks/src/useStorageToggle.spec.tsx b/packages/hooks/src/useStorageToggle.spec.tsx new file mode 100644 index 000000000..41f0fe351 --- /dev/null +++ b/packages/hooks/src/useStorageToggle.spec.tsx @@ -0,0 +1,54 @@ +import { renderHook } from '@testing-library/react'; +import { defaultStorageKey, useStorageToggle } from './useStorageToggle'; +import { expect } from 'vitest'; + +describe('useStorageToggle', () => { + it('skal gi false for default oppførsel uten verdi i default storage', () => { + const { result } = renderHook(() => useStorageToggle()); + expect(result.current).toBe(false); + }); + + it('skal gi true når default key er satt i default storage', () => { + localStorage.setItem(defaultStorageKey, 'true'); + const { result } = renderHook(() => useStorageToggle()); + expect(result.current).toBe(true); + }); + + describe.each([ + ['localStorage', window.localStorage], + ['sessionStorage', window.sessionStorage], + ])('med %s', (name, storageArea) => { + beforeEach(() => { + storageArea.clear(); + }); + + it(`skal gi false når default key ikke er satt i ${name}`, () => { + const { result } = renderHook(() => useStorageToggle({ storageArea })); + expect(result.current).toBe(false); + }); + + it(`skal gi true når default key er satt i ${name}`, () => { + storageArea.setItem(defaultStorageKey, 'true'); + const { result } = renderHook(() => useStorageToggle({ storageArea })); + expect(result.current).toBe(true); + }); + + it(`skal gi false når default key er satt til false i ${name}`, () => { + storageArea.setItem(defaultStorageKey, 'false'); + const { result } = renderHook(() => useStorageToggle({ storageArea })); + expect(result.current).toBe(false); + }); + + it(`skal gi false når default key er satt til noe annet enn true i ${name}`, () => { + storageArea.setItem(defaultStorageKey, 'hei'); + const { result } = renderHook(() => useStorageToggle({ storageArea })); + expect(result.current).toBe(false); + }); + + it(`skal gi true når custom key er satt til true i ${name}`, () => { + storageArea.setItem('devmode', 'true'); + const { result } = renderHook(() => useStorageToggle({ key: 'devmode', storageArea })); + expect(result.current).toBe(true); + }); + }); +}); diff --git a/packages/hooks/src/useStorageToggle.tsx b/packages/hooks/src/useStorageToggle.tsx new file mode 100644 index 000000000..e150b543e --- /dev/null +++ b/packages/hooks/src/useStorageToggle.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react'; + +interface Props { + key?: string; + storageArea?: Storage; +} + +export const defaultStorageKey = 'storagetoggle'; + +/** + * Egendefinert hook for å toggle funksjonalitet av og på ved å sette boolske-flagg i browser-storage. + * + * @param {string} [key='storagetoggle'] - Nøkkelen som brukes til å lagre utviklingsmodus-flagget. + * @param {Storage} [storageArea=localStorage] - Storage-området som brukes til å lagre utviklingsmodus-flagget, + * dette vil være `localStorage` eller `sessionStorage`. + * @returns {boolean} - Returnerer `true` hvis utviklingsmodus er aktivert, ellers `false`. + * + * Denne hooken lytter etter endringer for den spesifiserte storage-nøkkelen og oppdaterer tilstanden deretter. + * + * Eksempel på bruk: + * ```jsx + * const isDevMode = useStorageToggle({ key: 'devmode' }); + * if (isDevMode) { + * // Utfør handlinger som er spesifikke for utviklingsmodus + * } + */ +export const useStorageToggle = ({ key = defaultStorageKey, storageArea = localStorage }: Props = {}): boolean => { + const [toggleState, setToggleState] = useState(storageArea.getItem(key) === 'true'); + + useEffect(() => { + const handleStorageEvent = (event: StorageEvent) => { + if (event.key === key) setToggleState(event.newValue === 'true'); + }; + window.addEventListener('storage', handleStorageEvent); + return () => { + window.removeEventListener('storage', handleStorageEvent); + }; + }, []); + + return toggleState; +}; diff --git a/packages/hooks/tsconfig.json b/packages/hooks/tsconfig.json new file mode 100644 index 000000000..1681dd7b0 --- /dev/null +++ b/packages/hooks/tsconfig.json @@ -0,0 +1,8 @@ +{ + "include": ["./", "../../@types/externals.d.ts"], + "exclude": ["node_modules", "**.spec.ts", "dist"], + "extends": "../../tsconfig.json", + "compilerOptions": { + "strict": true + } +} diff --git a/packages/hooks/vite.config.js b/packages/hooks/vite.config.js new file mode 100644 index 000000000..4aff819de --- /dev/null +++ b/packages/hooks/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config'; +import { mergeConfig } from 'vite'; +import { peerDependencies } from './package.json'; +import commonConfig from '../../vite.config'; + +const config = defineConfig({ + build: { + lib: { + name: '@navikt/ft-hooks', + }, + rollupOptions: { + external: Object.keys(peerDependencies), + }, + }, +}); + +export default mergeConfig(commonConfig, config); diff --git a/yarn.lock b/yarn.lock index 2664200c0..afc5bc130 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3533,6 +3533,19 @@ __metadata: languageName: unknown linkType: soft +"@navikt/ft-hooks@workspace:packages/hooks": + version: 0.0.0-use.local + resolution: "@navikt/ft-hooks@workspace:packages/hooks" + dependencies: + eslint: 9.13.0 + typescript: 5.6.3 + vite: 5.4.10 + vitest: 2.1.3 + peerDependencies: + react: 18.3.1 + languageName: unknown + linkType: soft + "@navikt/ft-kodeverk@^2.5.5, @navikt/ft-kodeverk@workspace:^, @navikt/ft-kodeverk@workspace:packages/kodeverk": version: 0.0.0-use.local resolution: "@navikt/ft-kodeverk@workspace:packages/kodeverk"