({
defaultWidth,
minWidth,
maxWidth,
storageKey,
edge = 'left',
}: Options)
| 30 | * cursor/userSelect tweaks in the consumer. |
| 31 | */ |
| 32 | export function useResizableDrawer({ |
| 33 | defaultWidth, |
| 34 | minWidth, |
| 35 | maxWidth, |
| 36 | storageKey, |
| 37 | edge = 'left', |
| 38 | }: Options): Result { |
| 39 | const [width, setWidth] = useState(defaultWidth); |
| 40 | |
| 41 | // Hydrate from localStorage on mount (avoids SSR mismatch). |
| 42 | useEffect(() => { |
| 43 | if (typeof window === 'undefined') return; |
| 44 | const raw = window.localStorage.getItem(storageKey); |
| 45 | if (!raw) return; |
| 46 | const n = Number(raw); |
| 47 | if (Number.isNaN(n)) return; |
| 48 | setWidth(Math.min(maxWidth, Math.max(minWidth, n))); |
| 49 | }, [storageKey, minWidth, maxWidth]); |
| 50 | |
| 51 | // Persist on idle (debounced). |
| 52 | useEffect(() => { |
| 53 | if (typeof window === 'undefined') return; |
| 54 | const t = window.setTimeout(() => { |
| 55 | window.localStorage.setItem(storageKey, String(width)); |
| 56 | }, 300); |
| 57 | return () => window.clearTimeout(t); |
| 58 | }, [width, storageKey]); |
| 59 | |
| 60 | const dragRef = useRef<{ startX: number; startWidth: number } | null>(null); |
| 61 | |
| 62 | const onMouseDown = useCallback( |
| 63 | (e: React.MouseEvent) => { |
| 64 | e.preventDefault(); |
| 65 | dragRef.current = { startX: e.clientX, startWidth: width }; |
| 66 | |
| 67 | const onMove = (ev: MouseEvent) => { |
| 68 | if (!dragRef.current) return; |
| 69 | const dx = |
| 70 | edge === 'left' |
| 71 | ? dragRef.current.startX - ev.clientX |
| 72 | : ev.clientX - dragRef.current.startX; |
| 73 | const next = Math.min( |
| 74 | maxWidth, |
| 75 | Math.max(minWidth, dragRef.current.startWidth + dx), |
| 76 | ); |
| 77 | setWidth(next); |
| 78 | }; |
| 79 | |
| 80 | const onUp = () => { |
| 81 | dragRef.current = null; |
| 82 | document.body.style.cursor = ''; |
| 83 | document.body.style.userSelect = ''; |
| 84 | window.removeEventListener('mousemove', onMove); |
| 85 | window.removeEventListener('mouseup', onUp); |
| 86 | }; |
| 87 | |
| 88 | document.body.style.cursor = 'ew-resize'; |
| 89 | document.body.style.userSelect = 'none'; |
no test coverage detected