Resizable right column in the Study stage; start on the chosen page without a flash
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
The Study stage's right column has a drag handle (mouse, arrow keys, double-click or Home to reset); the width is remembered per device. The app now starts on the user's chosen home page from the first render instead of showing My Studies first. Tests wait on page headings so a lazily loaded page can't be confused with hidden text from the previous one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c4f54e033
commit
7a010fcda8
5 files changed
+226
-20
No files matched your search
@@ -0,0 +1,75 @@
|
||||
// A drag-to-resize width for a side panel (the Study stage's right column). The handle owns
|
||||
// pointer dragging, arrow keys and double-click reset; the width is remembered per device.
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
const STEP = 24;
|
||||
const BIG_STEP = 96;
|
||||
|
||||
// containerRef: the element the panel sits at the right edge of (for measuring and clamping).
|
||||
export function useResizableWidth({ storageKey, min = 320, maxAbsolute = 760, fallback = 448, reserve = 600, containerRef }) {
|
||||
const maxFor = () => {
|
||||
const w = containerRef.current?.getBoundingClientRect().width ?? 0;
|
||||
return w > 0 ? Math.max(min, Math.min(maxAbsolute, w - reserve)) : maxAbsolute;
|
||||
};
|
||||
const clamp = (n) => Math.round(Math.min(maxFor(), Math.max(min, n)));
|
||||
|
||||
const [width, setWidthState] = useState(() => {
|
||||
try {
|
||||
const n = Number(localStorage.getItem(storageKey));
|
||||
return Number.isFinite(n) && n > 0 ? Math.min(maxAbsolute, Math.max(min, Math.round(n))) : null;
|
||||
} catch { return null; }
|
||||
});
|
||||
const dragRef = useRef(null);
|
||||
|
||||
const commit = useCallback((n) => {
|
||||
setWidthState(n);
|
||||
try {
|
||||
if (n == null) localStorage.removeItem(storageKey);
|
||||
else localStorage.setItem(storageKey, String(n));
|
||||
} catch { /* storage unavailable */ }
|
||||
}, [storageKey]);
|
||||
|
||||
// Width of the panel right now (state, else measured from the panel element, else a default).
|
||||
const current = (panelEl) => width ?? (panelEl?.getBoundingClientRect().width || fallback);
|
||||
|
||||
const handleProps = (panelRef) => ({
|
||||
role: 'separator',
|
||||
'aria-orientation': 'vertical',
|
||||
'aria-label': 'Resize side panel',
|
||||
'aria-valuemin': min,
|
||||
'aria-valuemax': maxAbsolute,
|
||||
'aria-valuenow': Math.round(current(panelRef.current)),
|
||||
tabIndex: 0,
|
||||
onPointerDown: (e) => {
|
||||
if (e.button !== undefined && e.button !== 0) return;
|
||||
e.currentTarget.setPointerCapture?.(e.pointerId);
|
||||
dragRef.current = { id: e.pointerId };
|
||||
document.body.style.userSelect = 'none';
|
||||
},
|
||||
onPointerMove: (e) => {
|
||||
if (!dragRef.current) return;
|
||||
const right = containerRef.current?.getBoundingClientRect().right ?? window.innerWidth;
|
||||
const next = clamp(right - e.clientX);
|
||||
dragRef.current.width = next;
|
||||
containerRef.current?.style.setProperty('--study-right', `${next}px`); // smooth: no React render per move
|
||||
},
|
||||
onPointerUp: (e) => {
|
||||
if (!dragRef.current) return;
|
||||
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
||||
const done = dragRef.current.width;
|
||||
dragRef.current = null;
|
||||
document.body.style.userSelect = '';
|
||||
if (done != null) commit(done);
|
||||
},
|
||||
onKeyDown: (e) => {
|
||||
const step = e.shiftKey ? BIG_STEP : STEP;
|
||||
if (e.key === 'ArrowLeft') { e.preventDefault(); commit(clamp(current(panelRef.current) + step)); }
|
||||
else if (e.key === 'ArrowRight') { e.preventDefault(); commit(clamp(current(panelRef.current) - step)); }
|
||||
else if (e.key === 'Home' || e.key === 'Escape') { e.preventDefault(); containerRef.current?.style.removeProperty('--study-right'); commit(null); }
|
||||
},
|
||||
onDoubleClick: () => { containerRef.current?.style.removeProperty('--study-right'); commit(null); },
|
||||
title: 'Drag to resize · double-click to reset',
|
||||
});
|
||||
|
||||
return { width, handleProps };
|
||||
}
|
||||
Reference in new issue
Block a user