# Module: `src/hooks/useResizablePanel.ts` > **Source**: `src/hooks/useResizablePanel.ts` (33 lines) > **Topo batch**: B1 (leaf) ## Purpose React hook that backs a draggable splitter between two horizontally-arranged panels. Owns the panel width as state and exposes a mouse handler the host attaches to the splitter element. ## Public interface ```ts export function useResizablePanel( initialWidth: number, min?: number, // default 100 max?: number, // default 600 ): { width: number onMouseDown: (e: React.MouseEvent) => void setWidth: React.Dispatch> } ``` `width` is the current panel width (px). `onMouseDown` should be wired to the splitter element. `setWidth` is exposed for programmatic resets — currently unused by callers but kept for parity with the persistence story (see Notes). ## Internal logic 1. `useState(initialWidth)` for `width`. 2. Drag bookkeeping is held in three `useRef` cells (`dragging`, `startX`, `startWidth`) — kept out of state so the move/up handlers never re-render the host. 3. `onMouseDown` (memoized via `useCallback([width])`) snapshots `clientX` and `width`, marks `dragging = true`, and calls `e.preventDefault()` to avoid triggering text selection. 4. A `useEffect` registers global `mousemove` / `mouseup` listeners on `window`. While `dragging.current === true`, `mousemove` updates `width` to `clamp(startWidth + (e.clientX - startX), min, max)`. `mouseup` flips `dragging` back to `false`. 5. The effect cleans up on unmount. ## Dependencies - **Internal**: none. - **External**: `react` (`useState`, `useCallback`, `useRef`, `useEffect`). ## Consumers (intra-repo) - `src/features/annotations/AnnotationsPage.tsx` — left + right panel widths. - `src/features/dataset/DatasetPage.tsx` — left + right panel widths. Both pages persist their layout server-side via `UserSettings.{annotationsLeftPanelWidth, annotationsRightPanelWidth, datasetLeftPanelWidth, datasetRightPanelWidth}` (see `src/types/index.ts`). The persistence read/write happens in the page, not in this hook. ## Data models None. ## Configuration None. ## External integrations DOM only — `window.addEventListener('mousemove' / 'mouseup')`. ## Security None. ## Tests None. ## Notes / open questions - The hook is keyboard- and touch-blind: only mouse drag is supported, so accessibility for non-pointer input is missing. Track for the broader a11y pass; out of scope for `/document`. - `setWidth` is exposed but no current caller hydrates `initialWidth` from `UserSettings` via it — they pass the persisted width directly to `useResizablePanel(persistedWidth)` on mount. If the persisted width arrives **after** mount (asynchronous load), the panel jumps. Flag for the consumers' module docs (B8). - Default bounds (100 / 600) are arbitrary; consumer pages may want different ceilings (e.g., the annotations sidebar). Currently both consumers accept the defaults.