Some checks failed
continuous-integration/drone/push Build encountered an error
183 lines
5.0 KiB
TypeScript
183 lines
5.0 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useState,
|
|
type CSSProperties,
|
|
type ReactNode,
|
|
} from 'react';
|
|
import { isDesktopViewport, useSidePanelMachine } from '../panels/useSidePanelMachine';
|
|
|
|
const RIGHT_SIDEBAR_WIDTH_KEY = 'authRightSidebarWidth';
|
|
const RIGHT_SIDEBAR_DEFAULT_WIDTH = 320;
|
|
const RIGHT_SIDEBAR_MIN_WIDTH = 260;
|
|
const RIGHT_SIDEBAR_MAX_WIDTH = 480;
|
|
|
|
export type RightSidebarSizing = {
|
|
defaultWidth?: number;
|
|
minWidth?: number;
|
|
maxWidth?: number;
|
|
};
|
|
|
|
export type RightSidebarContent = {
|
|
title: string;
|
|
content: ReactNode;
|
|
ariaLabel?: string;
|
|
};
|
|
|
|
export type RightSidebarStyle = CSSProperties & {
|
|
'--auth-right-sidebar-width': string;
|
|
};
|
|
|
|
type RightSidebarContextValue = {
|
|
isOpen: boolean;
|
|
content: RightSidebarContent | null;
|
|
openSidebar: (content?: RightSidebarContent) => void;
|
|
closeSidebar: () => void;
|
|
toggleSidebar: (content?: RightSidebarContent) => void;
|
|
setSidebarContent: (content: RightSidebarContent | null) => void;
|
|
desktopSidebarStyle: RightSidebarStyle;
|
|
startResize: ReturnType<typeof useSidePanelMachine>['startResize'];
|
|
};
|
|
|
|
type RightSidebarProviderProps = {
|
|
children: ReactNode;
|
|
closeOnPathname?: string;
|
|
onMobileOpenRequest?: () => void;
|
|
sizing?: RightSidebarSizing;
|
|
};
|
|
|
|
const RightSidebarContext = createContext<RightSidebarContextValue | undefined>(undefined);
|
|
|
|
function readSizingValue(value: number | undefined, fallback: number): number {
|
|
if (!Number.isFinite(value) || value == null || value <= 0) {
|
|
return fallback;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function resolveRightSidebarSizing(sizing: RightSidebarSizing | undefined) {
|
|
const requestedMinWidth = readSizingValue(sizing?.minWidth, RIGHT_SIDEBAR_MIN_WIDTH);
|
|
const requestedMaxWidth = readSizingValue(sizing?.maxWidth, RIGHT_SIDEBAR_MAX_WIDTH);
|
|
const minWidth = Math.min(requestedMinWidth, requestedMaxWidth);
|
|
const maxWidth = Math.max(requestedMinWidth, requestedMaxWidth);
|
|
const requestedDefaultWidth = readSizingValue(
|
|
sizing?.defaultWidth,
|
|
RIGHT_SIDEBAR_DEFAULT_WIDTH,
|
|
);
|
|
const defaultWidth = Math.min(maxWidth, Math.max(minWidth, requestedDefaultWidth));
|
|
|
|
return {
|
|
defaultWidth,
|
|
minWidth,
|
|
maxWidth,
|
|
};
|
|
}
|
|
|
|
export function RightSidebarProvider({
|
|
children,
|
|
closeOnPathname,
|
|
onMobileOpenRequest,
|
|
sizing,
|
|
}: Readonly<RightSidebarProviderProps>) {
|
|
const { defaultWidth, minWidth, maxWidth } = resolveRightSidebarSizing(sizing);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [content, setContent] = useState<RightSidebarContent | null>(null);
|
|
|
|
const closeSidebar = useCallback(() => {
|
|
setIsOpen(false);
|
|
setContent(null);
|
|
}, []);
|
|
|
|
const setSidebarContent = useCallback((nextContent: RightSidebarContent | null) => {
|
|
setContent(nextContent);
|
|
}, []);
|
|
|
|
const openSidebar = useCallback(
|
|
(nextContent?: RightSidebarContent) => {
|
|
const resolvedContent = nextContent ?? content;
|
|
if (!resolvedContent) {
|
|
return;
|
|
}
|
|
|
|
if (nextContent) {
|
|
setContent(nextContent);
|
|
}
|
|
if (!isDesktopViewport()) {
|
|
onMobileOpenRequest?.();
|
|
}
|
|
setIsOpen(true);
|
|
},
|
|
[content, onMobileOpenRequest],
|
|
);
|
|
|
|
const toggleSidebar = useCallback(
|
|
(nextContent?: RightSidebarContent) => {
|
|
if (isOpen) {
|
|
closeSidebar();
|
|
return;
|
|
}
|
|
|
|
openSidebar(nextContent);
|
|
},
|
|
[isOpen, closeSidebar, openSidebar],
|
|
);
|
|
|
|
const { width, startResize } = useSidePanelMachine({
|
|
storageKey: RIGHT_SIDEBAR_WIDTH_KEY,
|
|
defaultWidth,
|
|
minWidth,
|
|
maxWidth,
|
|
resizeAxis: 'from-right',
|
|
resizingBodyClass: 'auth-right-sidebar-resizing',
|
|
isOpen,
|
|
canResize: isOpen,
|
|
shouldPersistWidth: true,
|
|
closeOnPathname,
|
|
onCloseOnPathname: closeSidebar,
|
|
onEscape: closeSidebar,
|
|
});
|
|
|
|
const desktopSidebarStyle = useMemo<RightSidebarStyle>(
|
|
() => ({
|
|
'--auth-right-sidebar-width': `${width}px`,
|
|
}),
|
|
[width],
|
|
);
|
|
|
|
const value = useMemo<RightSidebarContextValue>(
|
|
() => ({
|
|
isOpen,
|
|
content,
|
|
openSidebar,
|
|
closeSidebar,
|
|
toggleSidebar,
|
|
setSidebarContent,
|
|
desktopSidebarStyle,
|
|
startResize,
|
|
}),
|
|
[
|
|
isOpen,
|
|
content,
|
|
openSidebar,
|
|
closeSidebar,
|
|
toggleSidebar,
|
|
setSidebarContent,
|
|
desktopSidebarStyle,
|
|
startResize,
|
|
],
|
|
);
|
|
|
|
return <RightSidebarContext.Provider value={value}>{children}</RightSidebarContext.Provider>;
|
|
}
|
|
|
|
export function useRightSidebar() {
|
|
const ctx = useContext(RightSidebarContext);
|
|
if (!ctx) {
|
|
throw new Error('useRightSidebar must be used within RightSidebarProvider');
|
|
}
|
|
return ctx;
|
|
}
|