122 lines
3.6 KiB
TypeScript
Executable File
122 lines
3.6 KiB
TypeScript
Executable File
import {ComponentPropsWithoutRef, useCallback, useMemo} from 'react';
|
|
import {
|
|
DashboardLayoutContext,
|
|
DashboardSidenavStatus,
|
|
} from './dashboard-layout-context';
|
|
import {Underlay} from '../overlays/underlay';
|
|
import {AnimatePresence} from 'framer-motion';
|
|
import {useControlledState} from '@react-stately/utils';
|
|
import {useMediaQuery} from '../../utils/hooks/use-media-query';
|
|
import {
|
|
getFromLocalStorage,
|
|
setInLocalStorage,
|
|
} from '../../utils/hooks/local-storage';
|
|
import {useBlockBodyOverflow} from '../../utils/hooks/use-block-body-overflow';
|
|
import clsx from 'clsx';
|
|
|
|
interface DashboardLayoutProps extends ComponentPropsWithoutRef<'div'> {
|
|
name: string;
|
|
leftSidenavCanBeCompact?: boolean;
|
|
leftSidenavStatus?: DashboardSidenavStatus;
|
|
onLeftSidenavChange?: (status: DashboardSidenavStatus) => void;
|
|
rightSidenavStatus?: DashboardSidenavStatus;
|
|
initialRightSidenavStatus?: DashboardSidenavStatus;
|
|
onRightSidenavChange?: (status: DashboardSidenavStatus) => void;
|
|
height?: string;
|
|
gridClassName?: string;
|
|
blockBodyOverflow?: boolean;
|
|
}
|
|
export function DashboardLayout({
|
|
children,
|
|
leftSidenavStatus: leftSidenav,
|
|
onLeftSidenavChange,
|
|
rightSidenavStatus: rightSidenav,
|
|
initialRightSidenavStatus,
|
|
onRightSidenavChange,
|
|
name,
|
|
leftSidenavCanBeCompact,
|
|
height = 'h-screen',
|
|
className,
|
|
gridClassName = 'dashboard-grid',
|
|
blockBodyOverflow = true,
|
|
...domProps
|
|
}: DashboardLayoutProps) {
|
|
useBlockBodyOverflow(!blockBodyOverflow);
|
|
const isMobile = useMediaQuery('(max-width: 1024px)');
|
|
|
|
const isCompactModeInitially = useMemo(() => {
|
|
return !name ? false : getFromLocalStorage(`${name}.sidenav.compact`);
|
|
}, [name]);
|
|
const defaultLeftSidenavStatus = isCompactModeInitially ? 'compact' : 'open';
|
|
const [leftSidenavStatus, setLeftSidenavStatus] = useControlledState(
|
|
leftSidenav,
|
|
isMobile ? 'closed' : defaultLeftSidenavStatus,
|
|
onLeftSidenavChange,
|
|
);
|
|
|
|
const rightSidenavStatusDefault = useMemo(() => {
|
|
if (isMobile) {
|
|
return 'closed';
|
|
}
|
|
if (initialRightSidenavStatus != null) {
|
|
return initialRightSidenavStatus;
|
|
}
|
|
const userSelected = getFromLocalStorage(
|
|
`${name}.sidenav.right.position`,
|
|
'open',
|
|
);
|
|
if (userSelected != null) {
|
|
return userSelected;
|
|
}
|
|
return initialRightSidenavStatus || 'closed';
|
|
}, [isMobile, name, initialRightSidenavStatus]);
|
|
const [rightSidenavStatus, _setRightSidenavStatus] = useControlledState(
|
|
rightSidenav,
|
|
rightSidenavStatusDefault,
|
|
onRightSidenavChange,
|
|
);
|
|
const setRightSidenavStatus = useCallback(
|
|
(status: DashboardSidenavStatus) => {
|
|
_setRightSidenavStatus(status);
|
|
setInLocalStorage(`${name}.sidenav.right.position`, status);
|
|
},
|
|
[_setRightSidenavStatus, name],
|
|
);
|
|
|
|
const shouldShowUnderlay =
|
|
isMobile && (leftSidenavStatus === 'open' || rightSidenavStatus === 'open');
|
|
|
|
return (
|
|
<DashboardLayoutContext.Provider
|
|
value={{
|
|
leftSidenavStatus,
|
|
setLeftSidenavStatus,
|
|
rightSidenavStatus,
|
|
setRightSidenavStatus,
|
|
leftSidenavCanBeCompact,
|
|
name,
|
|
isMobileMode: isMobile,
|
|
}}
|
|
>
|
|
<div
|
|
{...domProps}
|
|
className={clsx('relative isolate', gridClassName, className, height)}
|
|
>
|
|
{children}
|
|
<AnimatePresence>
|
|
{shouldShowUnderlay && (
|
|
<Underlay
|
|
position="fixed"
|
|
key="dashboard-underlay"
|
|
onClick={() => {
|
|
setLeftSidenavStatus('closed');
|
|
setRightSidenavStatus('closed');
|
|
}}
|
|
/>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</DashboardLayoutContext.Provider>
|
|
);
|
|
}
|