From 01bbaebe5b395e74bfbf1c886158b5652edf64ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Tue, 24 Feb 2026 10:53:29 +0100 Subject: [PATCH] expose sidebars sizing, v0.1.4 --- package.json | 2 +- src/contexts/LeftMenuContext.tsx | 49 +++++++++++++++++++++++++--- src/contexts/RightSidebarContext.tsx | 41 +++++++++++++++++++++-- src/index.ts | 7 +++- 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index be980ca..25faaab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@panic/web-core", - "version": "0.1.3", + "version": "0.1.4", "license": "AGPL-3.0-only", "description": "Core auth and utilities for panic.haus web applications", "type": "module", diff --git a/src/contexts/LeftMenuContext.tsx b/src/contexts/LeftMenuContext.tsx index a4b7efe..1229830 100644 --- a/src/contexts/LeftMenuContext.tsx +++ b/src/contexts/LeftMenuContext.tsx @@ -18,6 +18,13 @@ const SIDEBAR_MIN_WIDTH = 220; const SIDEBAR_MAX_WIDTH = 420; const SIDEBAR_COLLAPSED_WIDTH = 56; +export type LeftMenuSizing = { + defaultWidth?: number; + minWidth?: number; + maxWidth?: number; + collapsedWidth?: number; +}; + export type LeftMenuRenderState = { collapsed: boolean; mobileOpen: boolean; @@ -53,10 +60,40 @@ type LeftMenuProviderProps = { children: ReactNode; defaultContent: LeftMenuContent; closeOnPathname?: string; + sizing?: LeftMenuSizing; }; const LeftMenuContext = createContext(undefined); +function readSizingValue(value: number | undefined, fallback: number): number { + if (!Number.isFinite(value) || value == null || value <= 0) { + return fallback; + } + + return value; +} + +function resolveLeftMenuSizing(sizing: LeftMenuSizing | undefined) { + const requestedMinWidth = readSizingValue(sizing?.minWidth, SIDEBAR_MIN_WIDTH); + const requestedMaxWidth = readSizingValue(sizing?.maxWidth, SIDEBAR_MAX_WIDTH); + const minWidth = Math.min(requestedMinWidth, requestedMaxWidth); + const maxWidth = Math.max(requestedMinWidth, requestedMaxWidth); + const requestedDefaultWidth = readSizingValue(sizing?.defaultWidth, SIDEBAR_DEFAULT_WIDTH); + const defaultWidth = Math.min(maxWidth, Math.max(minWidth, requestedDefaultWidth)); + const requestedCollapsedWidth = readSizingValue( + sizing?.collapsedWidth, + SIDEBAR_COLLAPSED_WIDTH, + ); + const collapsedWidth = Math.min(minWidth, requestedCollapsedWidth); + + return { + defaultWidth, + minWidth, + maxWidth, + collapsedWidth, + }; +} + function readStoredCollapsed(): boolean { if (!globalThis.window) { return false; @@ -69,7 +106,9 @@ export function LeftMenuProvider({ children, defaultContent, closeOnPathname, + sizing, }: Readonly) { + const { defaultWidth, minWidth, maxWidth, collapsedWidth } = resolveLeftMenuSizing(sizing); const [collapsed, setCollapsed] = useState(() => readStoredCollapsed()); const [mobileOpen, setMobileOpen] = useState(false); const [content, setContent] = useState(defaultContent); @@ -162,9 +201,9 @@ export function LeftMenuProvider({ const { width, startResize } = useSidePanelMachine({ storageKey: SIDEBAR_WIDTH_KEY, - defaultWidth: SIDEBAR_DEFAULT_WIDTH, - minWidth: SIDEBAR_MIN_WIDTH, - maxWidth: SIDEBAR_MAX_WIDTH, + defaultWidth, + minWidth, + maxWidth, resizeAxis: 'from-left', resizingBodyClass: 'auth-sidebar-resizing', isOpen: mobileOpen, @@ -177,9 +216,9 @@ export function LeftMenuProvider({ const desktopMenuStyle = useMemo( () => ({ - '--auth-sidebar-width': `${collapsed ? SIDEBAR_COLLAPSED_WIDTH : width}px`, + '--auth-sidebar-width': `${collapsed ? collapsedWidth : width}px`, }), - [collapsed, width], + [collapsed, collapsedWidth, width], ); const value = useMemo( diff --git a/src/contexts/RightSidebarContext.tsx b/src/contexts/RightSidebarContext.tsx index 5551487..7ee07b9 100644 --- a/src/contexts/RightSidebarContext.tsx +++ b/src/contexts/RightSidebarContext.tsx @@ -14,6 +14,12 @@ 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; @@ -39,15 +45,44 @@ type RightSidebarProviderProps = { children: ReactNode; closeOnPathname?: string; onMobileOpenRequest?: () => void; + sizing?: RightSidebarSizing; }; const RightSidebarContext = createContext(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) { + const { defaultWidth, minWidth, maxWidth } = resolveRightSidebarSizing(sizing); const [isOpen, setIsOpen] = useState(false); const [content, setContent] = useState(null); @@ -92,9 +127,9 @@ export function RightSidebarProvider({ const { width, startResize } = useSidePanelMachine({ storageKey: RIGHT_SIDEBAR_WIDTH_KEY, - defaultWidth: RIGHT_SIDEBAR_DEFAULT_WIDTH, - minWidth: RIGHT_SIDEBAR_MIN_WIDTH, - maxWidth: RIGHT_SIDEBAR_MAX_WIDTH, + defaultWidth, + minWidth, + maxWidth, resizeAxis: 'from-right', resizingBodyClass: 'auth-right-sidebar-resizing', isOpen, diff --git a/src/index.ts b/src/index.ts index 3bc8b16..81fc938 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,10 +35,15 @@ export { LeftMenuProvider, useLeftMenu } from './contexts/LeftMenuContext'; export type { LeftMenuContent, LeftMenuRenderState, + LeftMenuSizing, LeftMenuStyle, } from './contexts/LeftMenuContext'; export { RightSidebarProvider, useRightSidebar } from './contexts/RightSidebarContext'; -export type { RightSidebarContent, RightSidebarStyle } from './contexts/RightSidebarContext'; +export type { + RightSidebarContent, + RightSidebarSizing, + RightSidebarStyle, +} from './contexts/RightSidebarContext'; export { formatDate, capitalize, splitAndCapitalize } from './utils/formatting'; export type { SplitMode } from './utils/formatting';