mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 00:25:38 -04:00
- Add global MotionConfig reducedMotion="user" in root layout
- Add color-scheme: dark, theme-color meta, skip-to-main-content link
- Add aria-hidden={true} to ~60+ decorative icons across 33 files
- Add aria-label to all icon-only buttons (logout, star rating, play, delete, etc.)
- Replace transition-all with specific properties (11 files)
- Add motion-safe: prefix for CSS hover transforms and animate-pulse
- Add prefers-reduced-motion: reduce override in globals.css
- Add useReducedMotion guard to status-dot pulsing animation
- Fix focus-visible styles on auth form inputs (focus → focus-visible, stronger ring)
- Add text-balance to all headings (13 files)
- Replace "..." with "…" (U+2026) in all loading/placeholder text
- Add autocomplete, spellCheck, role="alert" to auth form
- Add aria-label to Switch controls and filmography select
- Fix heading hierarchy (h3 → h2 where h2 was skipped)
- Add break-words to title overview, overscroll-contain to drawer
- Add confirmation dialog for destructive library removal
- Add group-focus-within:opacity-100 for keyboard-accessible backup actions
- Add role="radio" + aria-checked to star rating buttons
- Add aria-label to carousel region, role="img" to TMDB logo SVG
- Add text-foreground to native select for dark mode consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
IconCheck,
|
|
IconPlayerPlayFilled,
|
|
IconPlus,
|
|
IconX,
|
|
} from "@tabler/icons-react";
|
|
import { AnimatePresence, motion } from "motion/react";
|
|
|
|
const watchingStyle = {
|
|
label: "Watching",
|
|
icon: IconPlayerPlayFilled,
|
|
class: "text-status-watching",
|
|
bgClass: "bg-status-watching/10 hover:bg-status-watching/15",
|
|
borderClass: "ring-status-watching/20",
|
|
};
|
|
|
|
const statusConfig = {
|
|
watchlist: watchingStyle,
|
|
in_progress: watchingStyle,
|
|
completed: {
|
|
label: "Completed",
|
|
icon: IconCheck,
|
|
class: "text-status-completed",
|
|
bgClass: "bg-status-completed/10 hover:bg-status-completed/15",
|
|
borderClass: "ring-status-completed/20",
|
|
},
|
|
} as const;
|
|
|
|
interface StatusButtonProps {
|
|
currentStatus: string | null;
|
|
onChange: (status: string | null) => void;
|
|
}
|
|
|
|
export function StatusButton({ currentStatus, onChange }: StatusButtonProps) {
|
|
const config =
|
|
statusConfig[currentStatus as keyof typeof statusConfig] ?? null;
|
|
|
|
return (
|
|
<AnimatePresence mode="wait" initial={false}>
|
|
{!config ? (
|
|
<motion.button
|
|
key="add"
|
|
type="button"
|
|
onClick={() => onChange("watchlist")}
|
|
initial={{ opacity: 0, y: 4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -4 }}
|
|
transition={{ duration: 0.15 }}
|
|
className="inline-flex h-9 items-center gap-2 rounded-lg bg-primary/10 px-4 text-sm font-medium text-primary ring-1 ring-primary/20 transition-all hover:bg-primary/15 hover:ring-primary/30 active:scale-[0.97]"
|
|
>
|
|
<IconPlus aria-hidden={true} className="size-3.5" strokeWidth={2.5} />
|
|
Watchlist
|
|
</motion.button>
|
|
) : (
|
|
<motion.button
|
|
key="status"
|
|
type="button"
|
|
onClick={() => onChange(null)}
|
|
initial={{ opacity: 0, y: 4 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -4 }}
|
|
transition={{ duration: 0.15 }}
|
|
title="Remove from library"
|
|
className={`group inline-flex h-9 items-center gap-2 rounded-lg px-4 text-sm font-medium ring-1 transition-all active:scale-[0.97] ${config.class} ${config.bgClass} ${config.borderClass} hover:ring-destructive/30 hover:bg-destructive/10 hover:text-destructive`}
|
|
>
|
|
<span className="grid [&>svg]:col-start-1 [&>svg]:row-start-1">
|
|
<config.icon
|
|
aria-hidden={true}
|
|
className="size-3.5 transition-opacity group-hover:opacity-0"
|
|
/>
|
|
<IconX
|
|
aria-hidden={true}
|
|
className="size-3.5 opacity-0 text-destructive transition-opacity group-hover:opacity-100"
|
|
/>
|
|
</span>
|
|
<span className="grid [&>span]:col-start-1 [&>span]:row-start-1">
|
|
<span className="transition-opacity group-hover:opacity-0">
|
|
{config.label}
|
|
</span>
|
|
<span className="opacity-0 transition-opacity group-hover:opacity-100">
|
|
Remove
|
|
</span>
|
|
</span>
|
|
</motion.button>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|