Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 2x 2x 2x 2x 3x 3x 3x 1x 1x 1x 2x 1x 1x 3x 3x 3x 3x 3x 1x 2x 1x 1x 1x 1x 1x 1x 2x 2x 1x 3x 1x 2x | import { useState, useRef, useEffect, type JSX, type ReactNode } from "react";
import styles from "./Sidebar.module.scss";
/** Default sidebar width in pixels. */
const DEFAULT_SIDEBAR_WIDTH: number = 320;
/** Minimum sidebar width in pixels. */
const MIN_SIDEBAR_WIDTH: number = 220;
/** Maximum sidebar width in pixels. */
const MAX_SIDEBAR_WIDTH: number = 600;
/** localStorage key for persisted sidebar width. */
const STORAGE_KEY: string = "grackle-sidebar-width";
/** Read persisted sidebar width from localStorage, falling back to the default. */
function loadWidth(): number {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored !== null) {
const parsed = Number(stored);
if (Number.isFinite(parsed) && parsed >= MIN_SIDEBAR_WIDTH && parsed <= MAX_SIDEBAR_WIDTH) {
return parsed;
}
}
} catch {
// localStorage unavailable
}
return DEFAULT_SIDEBAR_WIDTH;
}
/** Persist sidebar width to localStorage. */
function saveWidth(width: number): void {
try {
localStorage.setItem(STORAGE_KEY, String(width));
} catch {
// localStorage unavailable
}
}
/** Props for the Sidebar component. */
export interface SidebarProps {
/** Content to render inside the sidebar slot. When undefined, the sidebar is hidden. */
content: ReactNode | undefined;
}
/** Left sidebar rendering slot content passed via props. */
export function Sidebar({ content }: SidebarProps): JSX.Element | undefined {
const [width] = useState<number>(loadWidth);
const containerRef = useRef<HTMLDivElement>(null);
/** Observe container resizes and persist width to localStorage. */
useEffect(() => {
const element = containerRef.current;
if (!element) {
return;
}
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const borderBox = entry.borderBoxSize[0];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- borderBoxSize[0] may be undefined in some browsers
if (borderBox) {
const boxWidth = Math.round(borderBox.inlineSize);
if (boxWidth >= MIN_SIDEBAR_WIDTH && boxWidth <= MAX_SIDEBAR_WIDTH) {
saveWidth(boxWidth);
}
}
}
});
observer.observe(element);
return () => {
observer.disconnect();
};
}, []);
if (content === undefined) {
return undefined;
}
return (
<div className={styles.container} ref={containerRef} data-testid="sidebar" style={{ width }}>
<div className={styles.content}>{content}</div>
</div>
);
}
|