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
+9 -27
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Slider, Toggle } from "./plexPlayerControls";
import { clockParts, type PlexPlayerPrefs } from "../lib/plexPlayerPrefs";
import { Pressable } from "./ui/Pressable";
import { ColorInput, Checkbox } from "./ui/NativeControls";
type SettingsTab = "sync" | "playback" | "subtitle" | "clock";
@@ -160,36 +161,24 @@ export function PlexPlayerSettings(props: {
/>
<div className="flex items-center justify-between px-1 py-1.5">
<span className="text-[0.75rem] text-white/80">{t("plex.player.subColor")}</span>
<input
type="color"
value={prefs.subColor}
onChange={(e) => patchPrefs({ subColor: e.target.value })}
className="h-6 w-10 cursor-pointer rounded-sm bg-transparent"
/>
<ColorInput value={prefs.subColor} onChange={(v) => patchPrefs({ subColor: v })} />
</div>
<div className="flex items-center justify-between px-1 py-1.5">
<span className="text-[0.75rem] text-white/80">{t("plex.player.subBg")}</span>
<div className="flex items-center gap-2">
<label className="flex items-center gap-1 text-2xs text-white/70">
<input
type="checkbox"
<Checkbox
checked={prefs.subBg === "transparent"}
onChange={(e) =>
onChange={(checked) =>
patchPrefs({
subBg: e.target.checked ? "transparent" : "#000000",
subBg: checked ? "transparent" : "#000000",
})
}
className="accent-accent"
/>
{t("plex.player.subBgNone")}
</label>
{prefs.subBg !== "transparent" && (
<input
type="color"
value={prefs.subBg}
onChange={(e) => patchPrefs({ subBg: e.target.value })}
className="h-6 w-10 cursor-pointer rounded-sm bg-transparent"
/>
<ColorInput value={prefs.subBg} onChange={(v) => patchPrefs({ subBg: v })} />
)}
</div>
</div>
@@ -207,11 +196,9 @@ export function PlexPlayerSettings(props: {
<span className="text-[0.75rem] text-white/80">
{t("plex.player.subShadowColor")}
</span>
<input
type="color"
<ColorInput
value={prefs.subShadowColor}
onChange={(e) => patchPrefs({ subShadowColor: e.target.value })}
className="h-6 w-10 cursor-pointer rounded-sm bg-transparent"
onChange={(v) => patchPrefs({ subShadowColor: v })}
/>
</div>
)}
@@ -241,12 +228,7 @@ export function PlexPlayerSettings(props: {
/>
<div className="flex items-center justify-between px-1 py-1.5">
<span className="text-[0.75rem] text-white/80">{t("plex.player.clockColor")}</span>
<input
type="color"
value={prefs.clockColor}
onChange={(e) => patchPrefs({ clockColor: e.target.value })}
className="h-6 w-10 cursor-pointer rounded-sm bg-transparent"
/>
<ColorInput value={prefs.clockColor} onChange={(v) => patchPrefs({ clockColor: v })} />
</div>
<Toggle
label={t("plex.player.clockDate")}