Files
web-core/src/contexts/RightSidebarContext.tsx
Beatrice Dellacà 01bbaebe5b
Some checks failed
continuous-integration/drone/push Build encountered an error
expose sidebars sizing, v0.1.4
2026-02-24 10:53:29 +01:00

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;
}