diff --git a/src/components/RotateControl.tsx b/src/components/RotateControl.tsx index e86d6b0c..a5bb6198 100644 --- a/src/components/RotateControl.tsx +++ b/src/components/RotateControl.tsx @@ -1,5 +1,6 @@ "use client"; +import { useRef } from "react"; import { EditRecipe } from "@/lib/types"; import { RotateCw } from "lucide-react"; import BaseButton from "./ui/BaseButton"; @@ -13,21 +14,64 @@ interface Props { const ROTATIONS = [0, 90, 180, 270] as const; export default function RotateControl({ recipe, onChange }: Props) { + const refs = useRef<(HTMLButtonElement | HTMLAnchorElement | null)[]>([]); + const handleKeyDown = (e: React.KeyboardEvent, index: number) => { + let nextIndex = index; + + if (e.key === "ArrowRight") { + nextIndex = (index + 1) % ROTATIONS.length; + e.preventDefault(); + } + + if (e.key === "ArrowLeft") { + nextIndex = (index - 1 + ROTATIONS.length) % ROTATIONS.length; + e.preventDefault(); + } + + if (nextIndex !== index) { + onChange({ rotate: ROTATIONS[nextIndex] }); + + requestAnimationFrame(() => { + refs.current[nextIndex]?.focus(); + }); + } + }; + return ( -
- {ROTATIONS.map((deg) => { +
+ {ROTATIONS.map((deg, index) => { const active = recipe.rotate === deg; + const noneSelected = !ROTATIONS.includes( + recipe.rotate as 0 | 90 | 180 | 270, + ); + return ( { + refs.current[index] = el; + }} onClick={() => onChange({ rotate: deg })} + role="radio" + aria-checked={active} + tabIndex={active || (noneSelected && index === 0) ? 0 : -1} + // tabIndex={active ? 0 : -1} + + onKeyDown={(e) => handleKeyDown(e, index)} aria-label={`Rotate video to ${deg} degrees`} - aria-pressed={active} active={active} className="flex-1 flex flex-col items-center gap-1.5 py-3" > - ); diff --git a/src/components/ui/BaseButton.tsx b/src/components/ui/BaseButton.tsx index e889f045..b8910249 100644 --- a/src/components/ui/BaseButton.tsx +++ b/src/components/ui/BaseButton.tsx @@ -14,16 +14,32 @@ interface BaseButtonProps extends ButtonHTMLAttributes { rel?: string; } -const BaseButton = forwardRef( - ({ className, variant = "secondary", size = "md", active, children, as = "button", ...props }, ref) => { +const BaseButton = forwardRef< + HTMLButtonElement | HTMLAnchorElement, + BaseButtonProps +>( + ( + { + className, + variant = "secondary", + size = "md", + active, + children, + as = "button", + ...props + }, + ref, + ) => { const Component = as as any; const variants = { - primary: "bg-film-600 text-white shadow-lg shadow-film-200 dark:shadow-none hover:bg-film-700", + primary: + "bg-film-600 text-white shadow-lg shadow-film-200 dark:shadow-none hover:bg-film-700", secondary: active ? "border-film-500 bg-film-50 text-film-700 font-heading font-semibold dark:bg-film-900/20 dark:text-film-400 dark:border-film-700" : "border-[var(--border)] bg-[var(--surface)] text-[var(--muted)] hover:border-film-300 hover:bg-film-50/30 dark:hover:bg-film-900/10", ghost: "bg-transparent hover:bg-[var(--surface)]", - outline: "border border-[var(--border)] bg-transparent hover:border-film-300", + outline: + "border border-[var(--border)] bg-transparent hover:border-film-300", }; const sizes = { @@ -41,14 +57,14 @@ const BaseButton = forwardRef {children} ); - } + }, ); BaseButton.displayName = "BaseButton";