Reader: resizable side panel
Drag the left edge of the verse side panel to resize it (arrow keys too, Shift for bigger steps; double-click resets). The text reflows to make room, and the width is remembered on this device. Reuses the Study page's resize handle; the hook now takes the CSS variable to drive. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cc2a8cd111
commit
e5812fd4ee
6 files changed
+47
-11
No files matched your search
@@ -6,7 +6,8 @@ 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 }) {
|
||||
// cssVar: the custom property on the container that carries the width while dragging.
|
||||
export function useResizableWidth({ storageKey, min = 320, maxAbsolute = 760, fallback = 448, reserve = 600, containerRef, cssVar = '--study-right' }) {
|
||||
const maxFor = () => {
|
||||
const w = containerRef.current?.getBoundingClientRect().width ?? 0;
|
||||
return w > 0 ? Math.max(min, Math.min(maxAbsolute, w - reserve)) : maxAbsolute;
|
||||
@@ -51,7 +52,7 @@ export function useResizableWidth({ storageKey, min = 320, maxAbsolute = 760, fa
|
||||
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
|
||||
containerRef.current?.style.setProperty(cssVar, `${next}px`); // smooth: no React render per move
|
||||
},
|
||||
onPointerUp: (e) => {
|
||||
if (!dragRef.current) return;
|
||||
@@ -65,9 +66,9 @@ export function useResizableWidth({ storageKey, min = 320, maxAbsolute = 760, fa
|
||||
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); }
|
||||
else if (e.key === 'Home' || e.key === 'Escape') { e.preventDefault(); containerRef.current?.style.removeProperty(cssVar); commit(null); }
|
||||
},
|
||||
onDoubleClick: () => { containerRef.current?.style.removeProperty('--study-right'); commit(null); },
|
||||
onDoubleClick: () => { containerRef.current?.style.removeProperty(cssVar); commit(null); },
|
||||
title: 'Drag to resize · double-click to reset',
|
||||
});
|
||||
|
||||
|
||||
@@ -79,3 +79,12 @@ test('the handle describes itself for assistive technology', () => {
|
||||
const props = setup().handle();
|
||||
expect(props).toMatchObject({ role: 'separator', 'aria-orientation': 'vertical', 'aria-label': 'Resize side panel', tabIndex: 0, 'aria-valuenow': 448 });
|
||||
});
|
||||
|
||||
test('a different panel can name its own CSS variable', () => {
|
||||
const h = setup({ cssVar: '--reader-pane' });
|
||||
act(() => { down(h); move(h, 1000); });
|
||||
expect(h.style.setProperty).toHaveBeenLastCalledWith('--reader-pane', '400px');
|
||||
act(() => up(h));
|
||||
act(() => { h.handle().onDoubleClick(); });
|
||||
expect(h.style.removeProperty).toHaveBeenLastCalledWith('--reader-pane');
|
||||
});
|
||||
Reference in new issue
Block a user