From 8697a424471f654f00d917a13f97d6607aacdb77 Mon Sep 17 00:00:00 2001 From: Molecule AI Frontend Engineer Date: Fri, 17 Apr 2026 20:35:15 +0000 Subject: [PATCH] fix(canvas): add keyboard resize + ARIA to SidePanel resize handle Add role="separator" + aria-valuenow/min/max/orientation + tabIndex={0} to make the resize handle focusable and discoverable by screen readers (WAI-ARIA slider pattern). Add onKeyDown handler: ArrowLeft/Right moves by 16px, Home/End snaps to min/max. Persist width to localStorage on keyboard resize, matching the existing mouse behaviour. Focus ring uses focus-visible:ring-2 to avoid showing on mouse click. Co-Authored-By: Claude Sonnet 4.6 --- canvas/src/components/SidePanel.tsx | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/canvas/src/components/SidePanel.tsx b/canvas/src/components/SidePanel.tsx index 64ec2601..6f917de5 100644 --- a/canvas/src/components/SidePanel.tsx +++ b/canvas/src/components/SidePanel.tsx @@ -23,6 +23,7 @@ import { summarizeWorkspaceCapabilities } from "@/store/canvas"; const SIDEPANEL_WIDTH_KEY = "molecule:sidepanel-width"; const SIDEPANEL_DEFAULT_WIDTH = 480; const SIDEPANEL_MIN_WIDTH = 320; +const SIDEPANEL_MAX_WIDTH = 800; const TABS: { id: PanelTab; label: string; icon: string }[] = [ { id: "chat", label: "Chat", icon: "◈" }, @@ -72,6 +73,29 @@ export function SidePanel() { document.body.style.userSelect = "none"; }, [width]); + const onResizeKeyDown = useCallback((e: React.KeyboardEvent) => { + const STEP = 16; + let newWidth: number | null = null; + if (e.key === "ArrowLeft") { + e.preventDefault(); + newWidth = Math.min(width + STEP, SIDEPANEL_MAX_WIDTH); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + newWidth = Math.max(width - STEP, SIDEPANEL_MIN_WIDTH); + } else if (e.key === "Home") { + e.preventDefault(); + newWidth = SIDEPANEL_MIN_WIDTH; + } else if (e.key === "End") { + e.preventDefault(); + newWidth = SIDEPANEL_MAX_WIDTH; + } + if (newWidth !== null) { + setWidth(newWidth); + widthRef.current = newWidth; + localStorage.setItem(SIDEPANEL_WIDTH_KEY, String(newWidth)); + } + }, [width]); + useEffect(() => { const onMouseMove = (e: MouseEvent) => { if (!dragging.current) return; @@ -111,8 +135,16 @@ export function SidePanel() { > {/* Resize handle */}
{/* Header */}