diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 90b02d4..92d570f 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,6 +1,7 @@ import { Outlet } from "react-router-dom"; import { useIsFetching } from "@tanstack/react-query"; import { Sidebar } from "./Sidebar"; +import { SkipLink } from "@/components/ui/skip-link"; export function Layout() { // useIsFetching() returns the number of in-flight queries and is 0 on @@ -11,6 +12,9 @@ export function Layout() { return (
+ {/* Skip link — first Tab stop, jumps past the sidebar. + Hidden until focused (see .skip-link in index.css). */} + {/* Scan element — always in the DOM (no layout shift), 1px tall, full width. pointer-events-none so it never blocks clicks. The inner @@ -28,7 +32,11 @@ export function Layout() { />
-
+
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 7f2526c..f9b0acc 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const badgeVariants = cva( - "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background", + "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", { variants: { variant: { diff --git a/src/components/ui/claim-state-badge.tsx b/src/components/ui/claim-state-badge.tsx index e4832f0..4d47314 100644 --- a/src/components/ui/claim-state-badge.tsx +++ b/src/components/ui/claim-state-badge.tsx @@ -75,7 +75,7 @@ export function ClaimStateBadge({ className, )} > - + {cfg.label} ); diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 91ffca3..a74d63d 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -38,8 +38,11 @@ const DialogContent = React.forwardRef< {...props} > {children} - - + + Close diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 124ba90..074bdba 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -14,7 +14,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex h-9 w-full items-center justify-between rounded-md border border-input bg-background/60 px-3 py-1 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className )} {...props} @@ -64,7 +64,7 @@ const SelectItem = React.forwardRef< & { + /** id of the element the link should jump to (target of the focus). */ + targetId?: string; +}; + +/** + * "Skip to main content" link. + * + * Renders off-screen by default so it does not intrude on the visual + * design, and slides into view when it receives keyboard focus. Used at + * the very top of so keyboard users can bypass the sidebar and + * the global scan element on the first Tab. + * + * Voice: instrument-label eyebrow caps. Same hairline surface as the + * sidebar so it feels native when it appears. + */ +export function SkipLink({ + targetId = "main-content", + className, + children = "Skip to main content", + ...props +}: SkipLinkProps) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 194f656..4de0695 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -45,7 +45,7 @@ const TableRow = React.forwardRef< ->(({ className, ...props }, ref) => ( +>(({ className, scope = "col", ...props }, ref) => (