diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx index 36f29c4318..d150258a30 100644 --- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx +++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx @@ -45,13 +45,7 @@ import CorsIcon from '@mui/icons-material/StorageOutlined'; import BillingIcon from '@mui/icons-material/CreditCardOutlined'; import SignOutIcon from '@mui/icons-material/ExitToApp'; import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg'; -import { - type FC, - type ReactNode, - useCallback, - useEffect, - useState, -} from 'react'; +import { type FC, type ReactNode, useCallback, useEffect } from 'react'; import { getCondensedRoutes, getRoutes } from '../../../menu/routes'; import { useAdminRoutes } from '../../../admin/useAdminRoutes'; import { filterByConfig, mapRouteLink } from 'component/common/util'; @@ -64,6 +58,7 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import GitHubIcon from '@mui/icons-material/GitHub'; import LibraryBooksIcon from '@mui/icons-material/LibraryBooks'; import { basePath } from 'utils/formatPath'; +import { useLocalStorageState } from 'hooks/useLocalStorageState'; export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({ fill: theme.palette.neutral.main, @@ -276,7 +271,10 @@ const useShowBadge = () => { }; const useNavigationMode = () => { - const [mode, setMode] = useState<'mini' | 'full'>('full'); + const [mode, setMode] = useLocalStorageState<'mini' | 'full'>( + 'navigation-mode:v1', + 'full', + ); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'b' && (event.metaKey || event.ctrlKey)) { diff --git a/frontend/src/hooks/useLocalStorageState.test.tsx b/frontend/src/hooks/useLocalStorageState.test.tsx new file mode 100644 index 0000000000..8134125885 --- /dev/null +++ b/frontend/src/hooks/useLocalStorageState.test.tsx @@ -0,0 +1,61 @@ +import { useLocalStorageState } from './useLocalStorageState'; +import { createLocalStorage } from '../utils/createLocalStorage'; +import { render } from 'utils/testRenderer'; +import { screen, waitFor } from '@testing-library/react'; +import type { FC } from 'react'; + +const TestComponent: FC<{ + keyName: string; + initialValue: any; +}> = ({ keyName, initialValue }) => { + const [value, setValue] = useLocalStorageState(keyName, initialValue); + + return ( +
+ {value} +
+ ); +}; + +describe('useLocalStorageState', () => { + beforeEach(() => { + window.localStorage.clear(); + }); + + it('should initialize with the initial value if local storage is empty', () => { + render(); + + expect(screen.getByTestId('storedValue').textContent).toBe( + 'initialValue', + ); + }); + + it('updates the local storage and state when value changes', async () => { + render(); + + screen.getByTestId('updateButton').click(); + + expect(screen.getByTestId('storedValue').textContent).toBe( + 'updatedValue', + ); + await waitFor(() => { + const { value } = createLocalStorage('testKey', {}); + expect(value).toStrictEqual('updatedValue'); + }); + }); + + it('initializes with the value from local storage if available', async () => { + createLocalStorage('testKey', {}).setValue('storedValue'); + + render(); + + expect(screen.getByTestId('storedValue').textContent).toBe( + 'storedValue', + ); + }); +}); diff --git a/frontend/src/hooks/useLocalStorageState.ts b/frontend/src/hooks/useLocalStorageState.ts new file mode 100644 index 0000000000..8e3dd4dedc --- /dev/null +++ b/frontend/src/hooks/useLocalStorageState.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from 'react'; +import { createLocalStorage } from '../utils/createLocalStorage'; + +export const useLocalStorageState = ( + key: string, + initialValue: T, +) => { + const { value: initialStoredValue, setValue: setStoredValue } = + createLocalStorage(key, initialValue); + + const [localValue, setLocalValue] = useState(initialStoredValue); + + useEffect(() => { + setStoredValue(localValue); + }, [localValue]); + + return [localValue, setLocalValue] as const; +}; diff --git a/frontend/src/utils/createLocalStorage.ts b/frontend/src/utils/createLocalStorage.ts index 4ba52f8f24..f1d9e11562 100644 --- a/frontend/src/utils/createLocalStorage.ts +++ b/frontend/src/utils/createLocalStorage.ts @@ -1,7 +1,7 @@ import { basePath } from './formatPath'; import { getLocalStorageItem, setLocalStorageItem } from './storage'; -export const createLocalStorage = ( +export const createLocalStorage = ( key: string, initialValue: T, ) => {