Files
AFFiNE-Mirror/packages/component/src/components/app-sidebar/index.tsx
2023-06-01 03:23:38 +00:00

138 lines
3.9 KiB
TypeScript

import { env } from '@affine/env';
import { Skeleton } from '@mui/material';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { useAtom, useAtomValue } from 'jotai';
import type { PropsWithChildren, ReactElement } from 'react';
import { useEffect, useRef, useState } from 'react';
import { fallbackHeaderStyle, fallbackStyle } from './fallback.css';
import {
floatingMaxWidth,
navBodyStyle,
navStyle,
navWidthVar,
navWrapperStyle,
sidebarFloatMaskStyle,
} from './index.css';
import {
APP_SIDEBAR_OPEN,
appSidebarFloatingAtom,
appSidebarOpenAtom,
appSidebarResizingAtom,
appSidebarWidthAtom,
} from './index.jotai';
import { ResizeIndicator } from './resize-indicator';
import type { SidebarHeaderProps } from './sidebar-header';
import { SidebarHeader } from './sidebar-header';
export type AppSidebarProps = PropsWithChildren<SidebarHeaderProps>;
function useEnableAnimation() {
const [enable, setEnable] = useState(false);
useEffect(() => {
setTimeout(() => {
setEnable(true);
}, 500);
}, []);
return enable;
}
export type History = {
stack: string[];
current: number;
};
export function AppSidebar(props: AppSidebarProps): ReactElement {
const [open, setOpen] = useAtom(appSidebarOpenAtom);
const appSidebarWidth = useAtomValue(appSidebarWidthAtom);
const [appSidebarFloating, setAppSidebarFloating] = useAtom(
appSidebarFloatingAtom
);
const initialRender = open === undefined;
const isResizing = useAtomValue(appSidebarResizingAtom);
const navRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function onResize() {
const { matches } = window.matchMedia(
`(max-width: ${floatingMaxWidth}px)`
);
if (
open === undefined &&
localStorage.getItem(APP_SIDEBAR_OPEN) === null
) {
// give the initial value,
// so that the sidebar can be closed on mobile by default
setOpen(!matches);
}
setAppSidebarFloating(matches && !!open);
}
onResize();
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, [open, setAppSidebarFloating, setOpen]);
// disable animation to avoid UI flash
const enableAnimation = useEnableAnimation();
const isMacosDesktop = env.isDesktop && env.isMacOs;
if (initialRender) {
// avoid the UI flash
return <div />;
}
return (
<>
<div
style={assignInlineVars({
[navWidthVar]: `${appSidebarWidth}px`,
})}
className={navWrapperStyle}
data-open={open}
data-is-macos-electron={isMacosDesktop}
data-enable-animation={enableAnimation && !isResizing}
>
<nav className={navStyle} ref={navRef} data-testid="app-sidebar">
<SidebarHeader router={props.router} />
<div className={navBodyStyle} data-testid="sliderBar-inner">
{props.children}
</div>
</nav>
<ResizeIndicator targetElement={navRef.current} />
</div>
<div
data-testid="app-sidebar-float-mask"
data-open={open}
data-is-floating={appSidebarFloating}
className={sidebarFloatMaskStyle}
onClick={() => setOpen(false)}
/>
</>
);
}
export const AppSidebarFallback = (): ReactElement | null => {
return (
<AppSidebar>
<div className={fallbackStyle}>
<div className={fallbackHeaderStyle}>
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={150} height={40} />
</div>
</div>
</AppSidebar>
);
};
export * from './add-page-button';
export * from './app-updater-button';
export * from './category-divider';
export * from './menu-item';
export * from './quick-search-input';
export * from './sidebar-containers';
export { appSidebarFloatingAtom, appSidebarOpenAtom, appSidebarResizingAtom };