Files
ui/_docs/02_document/modules/src__hooks__useResizablePanel.md
T
Oleksandr Bezdieniezhnykh 510df68bcf [AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs
Captures the full output of autodev existing-code Phase A through
Step 4 (Code Testability Revision) for the Azaion UI workspace:

- Step 1 Document: _docs/02_document/ (FINAL_report, architecture,
  glossary, components/, modules/, diagrams/, system-flows,
  module-layout) plus _docs/00_problem/ + _docs/01_solution/ +
  _docs/legacy/ + _docs/how_to_test + README.
- Step 2 Architecture Baseline: architecture_compliance_baseline.md.
- Step 3 Test Spec: _docs/02_document/tests/ (environment,
  test-data, blackbox/performance/resilience/security/
  resource-limit tests, traceability-matrix), enum_spec_snapshot,
  expected_results/results_report.md (98 rows), plus the
  run-tests.sh + run-performance-tests.sh runners.
- Step 4 Code Testability Revision: 01-testability-refactoring/
  run dir (list-of-changes C01-C07, deferred_to_refactor,
  analysis/research_findings + refactoring_roadmap) and the 7
  child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/
  plus _dependencies_table.md.
- _docs/_autodev_state.md pins the cursor at Step 4 / refactor
  Phase 4 entry so /autodev resumes cleanly.

Epic AZ-447 (UI testability gates) tracks the 7 child tasks that
will land in subsequent commits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:38:49 +03:00

2.9 KiB

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

export function useResizablePanel(
  initialWidth: number,
  min?: number,   // default 100
  max?: number,   // default 600
): {
  width: number
  onMouseDown: (e: React.MouseEvent) => void
  setWidth: React.Dispatch<React.SetStateAction<number>>
}

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.