feat(design): the field family, the dropdown trigger, native controls, the no-raw-input lint rule (E28 S3c)

- ui/Input, Textarea, InnerInput + fieldClass (A4): the >= 3:1 edge, a 2px accent
  focus edge, the danger edge for invalid, md/sm/xs/heading rungs, 1rem and 44 on touch
- ui/SelectTrigger (A5): SortControl, FilterSelect, the view switcher and the date
  filter on one trigger, a pill in a toolbar, r8 in a form
- ui/NativeControls (A6): Checkbox, CheckRow, Slider, ColorInput
- every raw input swept (~100 sites); inputCls retired; local/input-primitive
- useReturnFocus: an in-place editor hands the focus back to its trigger on
  Enter/Escape (the playlist and Plex title renames, the priority number);
  the Plex playlist rename gains Escape and an accessible name
- back to top: a round 48 FAB (owner decision); DESIGN.md A2 and the log
This commit is contained in:
2026-09-24 23:48:22 +02:00
parent 81936e9f56
commit dac9d89f91
63 changed files with 923 additions and 416 deletions
+3 -15
View File
@@ -2,6 +2,7 @@ import { type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { RotateCcw } from "lucide-react";
import { IconButton } from "./ui/IconButton";
import { Slider as Range, Checkbox } from "./ui/NativeControls";
// ±10s skip glyph: an outline circular arrow with a "10" in the centre — the YouTube/Plex-native
// muscle-memory shape. Deliberately distinct from the episode-level SkipBack/SkipForward (double
@@ -93,15 +94,7 @@ export function Slider({
)}
</span>
</div>
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={(e) => onChange(Number(e.target.value))}
className="w-full accent-accent"
/>
<Range min={min} max={max} step={step} value={value} onChange={(v) => onChange(v)} />
</div>
);
}
@@ -117,12 +110,7 @@ export function Toggle({
return (
<label className="flex cursor-pointer items-center justify-between px-1 py-1.5">
<span className="text-[0.75rem] text-white/80">{label}</span>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange(e.target.checked)}
className="h-4 w-4 accent-accent"
/>
<Checkbox checked={checked} onChange={onChange} />
</label>
);
}