mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -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>
88 lines
2.1 KiB
TypeScript
88 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useReducedMotion } from "motion/react";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const colors = {
|
|
ok: { bg: "bg-green-500", shadow: "0 0 0 0px rgba(74,222,128,0.5)" },
|
|
error: { bg: "bg-destructive", shadow: "0 0 0 0px rgba(248,113,113,0.5)" },
|
|
warn: { bg: "bg-amber-500", shadow: "0 0 0 0px rgba(251,191,36,0.5)" },
|
|
inactive: { bg: "bg-muted-foreground/30", shadow: "" },
|
|
};
|
|
|
|
const pulseColors = {
|
|
ok: "0 0 0 4px rgba(74,222,128,0)",
|
|
error: "0 0 0 4px rgba(248,113,113,0)",
|
|
warn: "0 0 0 4px rgba(251,191,36,0)",
|
|
};
|
|
|
|
const defaultLabels: Record<string, string> = {
|
|
ok: "Healthy",
|
|
error: "Error",
|
|
warn: "Warning",
|
|
inactive: "Inactive",
|
|
};
|
|
|
|
/** Small colored status dot with a pulsing ring for active states. */
|
|
export function StatusDot({
|
|
status,
|
|
label,
|
|
className,
|
|
}: {
|
|
status: "ok" | "error" | "warn" | "inactive";
|
|
/** Tooltip text. Defaults to a label derived from the status. */
|
|
label?: string;
|
|
className?: string;
|
|
}) {
|
|
const { bg, shadow } = colors[status];
|
|
const pulse = status !== "inactive";
|
|
const tooltipText = label ?? defaultLabels[status];
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
const dotEl = pulse ? (
|
|
<motion.span
|
|
className={cn(
|
|
"inline-block h-2 w-2 shrink-0 rounded-full",
|
|
bg,
|
|
className,
|
|
)}
|
|
animate={
|
|
prefersReducedMotion
|
|
? {}
|
|
: {
|
|
boxShadow: [
|
|
shadow,
|
|
pulseColors[status as keyof typeof pulseColors],
|
|
],
|
|
}
|
|
}
|
|
transition={{
|
|
duration: 1.2,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: "easeOut",
|
|
repeatDelay: 0.3,
|
|
}}
|
|
/>
|
|
) : (
|
|
<span
|
|
className={cn(
|
|
"inline-block h-2 w-2 shrink-0 rounded-full",
|
|
bg,
|
|
className,
|
|
)}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger className="cursor-default">{dotEl}</TooltipTrigger>
|
|
<TooltipContent>{tooltipText}</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|