diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 6c9d1bd..a297501 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -103,7 +103,7 @@ export default async function LandingPage() { ) : ( Sign in )} - Read the docs → + Read the docs →
diff --git a/src/app/globals.css b/src/app/globals.css index ceb7236..284d34c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -220,6 +220,7 @@ html.custom { --accent-strong: color-mix(in srgb, var(--accent) 88%, var(--foreground)); --danger: #dc2626; + --danger-strong: #b91c1c; --danger-wash: color-mix(in srgb, var(--danger) 12%, transparent); --elev-1: 0 1px 2px rgb(var(--shadow-rgb) / 0.10); diff --git a/src/components/admin/AdminProvidersPanel.tsx b/src/components/admin/AdminProvidersPanel.tsx index 8960fe9..55dfc0f 100644 --- a/src/components/admin/AdminProvidersPanel.tsx +++ b/src/components/admin/AdminProvidersPanel.tsx @@ -370,7 +370,6 @@ export function AdminProvidersPanel() { onClick={startCreate} variant="primary" size="icon" - className="h-7 w-7" aria-label="Add provider" title="Add provider" > diff --git a/src/components/auth/UserMenu.tsx b/src/components/auth/UserMenu.tsx index e780d2a..d9e44ea 100644 --- a/src/components/auth/UserMenu.tsx +++ b/src/components/auth/UserMenu.tsx @@ -86,9 +86,7 @@ export function UserMenu({ )} - > - {session.user.email || 'Account'} - + /> ); } diff --git a/src/components/documents/DexieMigrationModal.tsx b/src/components/documents/DexieMigrationModal.tsx index d065adf..df469d1 100644 --- a/src/components/documents/DexieMigrationModal.tsx +++ b/src/components/documents/DexieMigrationModal.tsx @@ -133,8 +133,6 @@ export function DexieMigrationModal({ } }, [loadLocalDexieDocs, onComplete, refreshDocuments]); - if (!isOpen) return null; - return ( { - // We wrap in a handler to stop propagation if needed, - // but ZoomControl buttons handle their own clicks. - // However, Menu might close on click? - // Headless UI Menu closes on click inside MenuItem, but these are just buttons in a div. - // It should NOT close unless we click a MenuItem. - onZoomIncrease(); - }} + onIncrease={onZoomIncrease} onDecrease={onZoomDecrease} min={minZoom} max={maxZoom} diff --git a/src/components/documents/DocumentSettings.tsx b/src/components/documents/DocumentSettings.tsx index 6632e09..07442c2 100644 --- a/src/components/documents/DocumentSettings.tsx +++ b/src/components/documents/DocumentSettings.tsx @@ -203,7 +203,7 @@ export function DocumentSettings({ isOpen, setIsOpen, epub, html, pdf }: { {pdf.parseStatus ?? 'pending'} - typeof window !== 'undefined' ? window.matchMedia(NARROW_QUERY).matches : false, - ); + const [isNarrow, setIsNarrow] = useState(false); useEffect(() => { if (typeof window === 'undefined') return; const mq = window.matchMedia(NARROW_QUERY); @@ -48,9 +46,11 @@ export function LibraryFrame({
{children}
{statusBar} - - {sidebar} - + {isNarrow && ( + + {sidebar} + + )} ); } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index b1ca111..d31b6b0 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -18,7 +18,7 @@ export const buttonStyles = variants({ primary: 'bg-accent text-background hover:bg-secondary-accent', secondary: 'border border-line bg-surface text-foreground hover:bg-accent-wash', outline: 'border border-line bg-surface-sunken text-foreground hover:bg-accent-wash hover:text-accent', - danger: 'border border-danger bg-danger text-background hover:bg-danger', + danger: 'border border-danger bg-danger text-background hover:bg-danger-strong hover:border-danger-strong', ghost: 'bg-transparent text-foreground hover:bg-accent-wash hover:text-accent', }, size: { diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index d12917f..4df9dc2 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -135,7 +135,7 @@ export function DrawerFrame({ leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
) { +}: Omit, 'type'>) { const rangeStyle: RangeStyle = { ...style, '--range-progress': `${resolveRangeProgress(props)}%`, diff --git a/src/components/ui/search-field.tsx b/src/components/ui/search-field.tsx index be3b353..6a5b44a 100644 --- a/src/components/ui/search-field.tsx +++ b/src/components/ui/search-field.tsx @@ -1,6 +1,6 @@ import type { InputHTMLAttributes, ReactNode } from 'react'; import { cn } from './cn'; -import { focusRing, motionColors } from './tokens'; +import { motionColors } from './tokens'; export function SearchField({ icon, @@ -16,7 +16,6 @@ export function SearchField({ className={cn( 'flex min-w-0 items-center gap-1.5 rounded-md border border-line bg-surface-sunken px-2 py-1', 'focus-within:border-accent-line focus-within:ring-1 focus-within:ring-accent-line hover:border-accent-line', - focusRing, motionColors, className, )} diff --git a/src/components/ui/segmented-control.tsx b/src/components/ui/segmented-control.tsx index fbfcba1..9033cc1 100644 --- a/src/components/ui/segmented-control.tsx +++ b/src/components/ui/segmented-control.tsx @@ -1,4 +1,6 @@ -import type { ReactNode } from 'react'; +'use client'; + +import { useRef, type KeyboardEvent, type ReactNode } from 'react'; import { cn } from './cn'; import { focusRing, motionColors } from './tokens'; @@ -25,17 +27,54 @@ export function SegmentedControl({ ariaLabel: string; className?: string; }) { + const buttonRefs = useRef>([]); + + const focusOption = (index: number) => { + const option = options[index]; + if (!option) return; + onChange(option.value); + buttonRefs.current[index]?.focus(); + }; + + const handleKeyDown = (event: KeyboardEvent, index: number) => { + switch (event.key) { + case 'ArrowLeft': + case 'ArrowUp': + event.preventDefault(); + focusOption((index - 1 + options.length) % options.length); + break; + case 'ArrowRight': + case 'ArrowDown': + event.preventDefault(); + focusOption((index + 1) % options.length); + break; + case 'Home': + event.preventDefault(); + focusOption(0); + break; + case 'End': + event.preventDefault(); + focusOption(options.length - 1); + break; + default: + break; + } + }; + return (
- {options.map((option) => { + {options.map((option, index) => { const active = value === option.value; return (