diff --git a/src/components/KeycodeSelector.tsx b/src/components/KeycodeSelector.tsx index 667f2a1..52d2f6d 100644 --- a/src/components/KeycodeSelector.tsx +++ b/src/components/KeycodeSelector.tsx @@ -1,12 +1,12 @@ /** * KeycodeSelector Component * - * A modal dialog for selecting behaviors and configuring parameters. + * A sheet-style dialog for selecting behaviors and configuring parameters. * Behavior-first approach: select behavior, then configure parameters. * Supports various parameter types with dedicated UI selectors. * * Features: - * - Close on select: Automatically close the dialog after selecting the last parameter + * - Close on select: Automatically close the sheet after selecting the last parameter * (setting is persisted in localStorage) */ import { useState, useMemo, useCallback, useEffect } from "react"; @@ -598,8 +598,8 @@ export function KeycodeSelector({ return ( - - + + {/* Header with Cancel Button */}
diff --git a/src/components/PhysicalKey.tsx b/src/components/PhysicalKey.tsx index 42be3f3..9a67707 100644 --- a/src/components/PhysicalKey.tsx +++ b/src/components/PhysicalKey.tsx @@ -50,6 +50,7 @@ interface PhysicalKeyProps { } export function PhysicalKey({ + keyPosition, attrs, isModified, displayName, @@ -102,6 +103,7 @@ export function PhysicalKey({ // Key content const keyContent = (
(null); // Local UI state const [selectedLayerIndex, setSelectedLayerIndex] = useState(0); @@ -69,6 +77,43 @@ export function KeymapPage() { return currentLayer.bindings[selectedKeyPosition] ?? null; }, [selectedKeyPosition, currentLayer]); + const scrollSelectedKeyIntoPreview = useCallback((keyPosition: number) => { + const pageElement = pageRef.current; + if (!pageElement) return; + + const selectedKeyElement = pageElement.querySelector( + `[data-key-position="${keyPosition}"]`, + ); + if (!selectedKeyElement) return; + + const isWideViewport = + typeof window.matchMedia === "function" + ? window.matchMedia("(min-width: 768px)").matches + : window.innerWidth >= 768; + if (!isWideViewport) return; + + const pageRect = pageElement.getBoundingClientRect(); + const keyRect = selectedKeyElement.getBoundingClientRect(); + const sheetTop = window.innerHeight * 0.5; + const visibleTop = Math.max(pageRect.top, 0); + const visibleBottom = Math.min(pageRect.bottom, sheetTop); + + if (visibleBottom <= visibleTop) return; + + const targetCenterY = visibleTop + (visibleBottom - visibleTop) / 2; + const keyCenterY = keyRect.top + keyRect.height / 2; + const nextScrollTop = pageElement.scrollTop + keyCenterY - targetCenterY; + + if (typeof pageElement.scrollTo === "function") { + pageElement.scrollTo({ + top: Math.max(0, nextScrollTop), + behavior: "smooth", + }); + } else { + pageElement.scrollTop = Math.max(0, nextScrollTop); + } + }, []); + // Handle key click const handleKeyClick = useCallback((keyPosition: number) => { setSelectedKeyPosition(keyPosition); @@ -196,8 +241,18 @@ export function KeymapPage() { setSelectedLayerIndex(inputStream.activeLayerIndex); }, [inputStream.activeLayerIndex, keymap.keymap?.layers]); + useEffect(() => { + if (!showKeycodeSelector || selectedKeyPosition === null) return; + + const frame = window.requestAnimationFrame(() => { + scrollSelectedKeyIntoPreview(selectedKeyPosition); + }); + + return () => window.cancelAnimationFrame(frame); + }, [scrollSelectedKeyIntoPreview, selectedKeyPosition, showKeycodeSelector]); + return ( -
+
{/* Header */}