mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: Persist navigation settings (#7144)
This commit is contained in:
parent
81c6caf211
commit
68427f841b
@ -45,13 +45,7 @@ import CorsIcon from '@mui/icons-material/StorageOutlined';
|
|||||||
import BillingIcon from '@mui/icons-material/CreditCardOutlined';
|
import BillingIcon from '@mui/icons-material/CreditCardOutlined';
|
||||||
import SignOutIcon from '@mui/icons-material/ExitToApp';
|
import SignOutIcon from '@mui/icons-material/ExitToApp';
|
||||||
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';
|
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';
|
||||||
import {
|
import { type FC, type ReactNode, useCallback, useEffect } from 'react';
|
||||||
type FC,
|
|
||||||
type ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { getCondensedRoutes, getRoutes } from '../../../menu/routes';
|
import { getCondensedRoutes, getRoutes } from '../../../menu/routes';
|
||||||
import { useAdminRoutes } from '../../../admin/useAdminRoutes';
|
import { useAdminRoutes } from '../../../admin/useAdminRoutes';
|
||||||
import { filterByConfig, mapRouteLink } from 'component/common/util';
|
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 GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
|
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
|
||||||
import { basePath } from 'utils/formatPath';
|
import { basePath } from 'utils/formatPath';
|
||||||
|
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||||
|
|
||||||
export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
|
export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
|
||||||
fill: theme.palette.neutral.main,
|
fill: theme.palette.neutral.main,
|
||||||
@ -276,7 +271,10 @@ const useShowBadge = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useNavigationMode = () => {
|
const useNavigationMode = () => {
|
||||||
const [mode, setMode] = useState<'mini' | 'full'>('full');
|
const [mode, setMode] = useLocalStorageState<'mini' | 'full'>(
|
||||||
|
'navigation-mode:v1',
|
||||||
|
'full',
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'b' && (event.metaKey || event.ctrlKey)) {
|
if (event.key === 'b' && (event.metaKey || event.ctrlKey)) {
|
||||||
|
61
frontend/src/hooks/useLocalStorageState.test.tsx
Normal file
61
frontend/src/hooks/useLocalStorageState.test.tsx
Normal file
@ -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
frontend/src/hooks/useLocalStorageState.ts
Normal file
18
frontend/src/hooks/useLocalStorageState.ts
Normal file
@ -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;
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { basePath } from './formatPath';
|
import { basePath } from './formatPath';
|
||||||
import { getLocalStorageItem, setLocalStorageItem } from './storage';
|
import { getLocalStorageItem, setLocalStorageItem } from './storage';
|
||||||
|
|
||||||
export const createLocalStorage = <T extends object>(
|
export const createLocalStorage = <T extends object | string>(
|
||||||
key: string,
|
key: string,
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
) => {
|
) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user