Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Persist navigation settings #7144

Merged
merged 5 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down
61 changes: 61 additions & 0 deletions frontend/src/hooks/useLocalStorageState.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<span data-testid='storedValue'>{value}</span>
<button
type='submit'
onClick={() => setValue('updatedValue')}
data-testid='updateButton'
/>
</div>
);
};

describe('useLocalStorageState', () => {
beforeEach(() => {
window.localStorage.clear();
});

it('should initialize with the initial value if local storage is empty', () => {
render(<TestComponent keyName='testKey' initialValue='initialValue' />);

expect(screen.getByTestId('storedValue').textContent).toBe(
'initialValue',
);
});

it('updates the local storage and state when value changes', async () => {
render(<TestComponent keyName='testKey' initialValue='initialValue' />);

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(<TestComponent keyName='testKey' initialValue='initialValue' />);

expect(screen.getByTestId('storedValue').textContent).toBe(
'storedValue',
);
});
});
18 changes: 18 additions & 0 deletions frontend/src/hooks/useLocalStorageState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect, useState } from 'react';
import { createLocalStorage } from '../utils/createLocalStorage';

export const useLocalStorageState = <T extends object | string>(
key: string,
initialValue: T,
) => {
const { value: initialStoredValue, setValue: setStoredValue } =
createLocalStorage<T>(key, initialValue);

const [localValue, setLocalValue] = useState<T>(initialStoredValue);

useEffect(() => {
setStoredValue(localValue);
}, [localValue]);

return [localValue, setLocalValue] as const;
};
2 changes: 1 addition & 1 deletion frontend/src/utils/createLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { basePath } from './formatPath';
import { getLocalStorageItem, setLocalStorageItem } from './storage';

export const createLocalStorage = <T extends object>(
export const createLocalStorage = <T extends object | string>(
key: string,
initialValue: T,
) => {
Expand Down
Loading