@@ -79,11 +75,11 @@ export function MetaEditModal({ job, onClose }: { job: DownloadJob; onClose: ()
{links.map((l, i) => (
- setLink(i, e.target.value)}
placeholder="https://…"
- className={inputCls}
/>
removeLink(i)}
diff --git a/frontend/src/components/plexBrowseLists.tsx b/frontend/src/components/plexBrowseLists.tsx
index e42d5dcd..1bb23971 100644
--- a/frontend/src/components/plexBrowseLists.tsx
+++ b/frontend/src/components/plexBrowseLists.tsx
@@ -14,6 +14,7 @@ import PlexLibraryPicker from "./PlexLibraryPicker";
import NavGrid from "./ui/NavGrid";
import { Button } from "./ui/Button";
import { IconButton } from "./ui/IconButton";
+import { Input } from "./ui/Input";
// A collection tile's sub-line: item count · total runtime (each dropped when unknown, e.g. show-only
// collections have no movie runtime).
@@ -114,13 +115,13 @@ function NewCollectionPanel({
-
setName(e.target.value)}
placeholder={t("plex.collection.namePlaceholder")}
aria-label={t("plex.collection.newCollection")}
- className="glass-card w-full rounded-lg px-3 py-1.5 text-sm focus:outline-hidden focus:ring-2 focus:ring-accent/40"
/>
{dupHint ? (
{t("plex.collection.nameMaybeTaken")}
@@ -129,13 +130,13 @@ function NewCollectionPanel({
)}
- setTerm(e.target.value)}
disabled={!nameOk}
placeholder={t("plex.collection.addSearch")}
aria-label={t("plex.collection.addSearch")}
- className="glass-card flex-1 rounded-lg px-3 py-1.5 text-sm focus:outline-hidden focus:ring-2 focus:ring-accent/40 disabled:opacity-50"
/>
- setSearch(e.target.value)}
placeholder={t("plex.filter.collectionSearch")}
aria-label={t("plex.filter.collectionSearch")}
- className="glass-card w-full max-w-sm rounded-lg px-3 py-1.5 text-sm focus:outline-hidden focus:ring-2 focus:ring-accent/40"
/>
{isAdmin && !creating && (
} onClick={() => setCreating(true)}>
diff --git a/frontend/src/components/plexDetailUi.tsx b/frontend/src/components/plexDetailUi.tsx
index 47faf675..25790446 100644
--- a/frontend/src/components/plexDetailUi.tsx
+++ b/frontend/src/components/plexDetailUi.tsx
@@ -1,4 +1,4 @@
-import { useRef, useState, type ReactNode } from "react";
+import { useRef, useState, type ReactNode, type Ref } from "react";
import { useTranslation } from "react-i18next";
import {
ArrowLeft,
@@ -170,6 +170,7 @@ export function BackBtn({ onBack, label }: { onBack: () => void; label: string }
}
export function ActionBtn({
+ ref,
onClick,
icon: Icon,
label,
@@ -183,9 +184,11 @@ export function ActionBtn({
sub?: string;
primary?: boolean;
disabled?: boolean;
+ ref?: Ref;
}) {
return (
- onChange(Number(e.target.value))}
- className="w-full accent-accent"
- />
+ onChange(v)} />
);
}
@@ -117,12 +110,7 @@ export function Toggle({
return (
{label}
- onChange(e.target.checked)}
- className="h-4 w-4 accent-accent"
- />
+
);
}
diff --git a/frontend/src/components/ui/BackToTop.tsx b/frontend/src/components/ui/BackToTop.tsx
index f4a2bc0e..87e681ca 100644
--- a/frontend/src/components/ui/BackToTop.tsx
+++ b/frontend/src/components/ui/BackToTop.tsx
@@ -50,15 +50,17 @@ export default function BackToTop({ dep, threshold = 600 }: { dep?: unknown; thr
// No aria-label/title attributes: ad-block "annoyance" cosmetic filters (e.g. Brave Shields)
// commonly hide floating corner buttons via attribute selectors like [aria-label="Back to top"].
// The accessible name comes from a visually-hidden child instead, which those selectors can't match.
+ // Shape (docs/DESIGN.md A2): round because it floats over the content, 3rem (48) on mouse and touch —
+ // a lone corner button at a toolbar rung gets lost.
return createPortal(
-
+
{t("common.backToTop")}
,
document.body,
diff --git a/frontend/src/components/ui/Combobox.tsx b/frontend/src/components/ui/Combobox.tsx
index 6d314796..548ba7fd 100644
--- a/frontend/src/components/ui/Combobox.tsx
+++ b/frontend/src/components/ui/Combobox.tsx
@@ -1,9 +1,12 @@
+import clsx from "clsx";
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Check, ChevronDown, X } from "lucide-react";
import { PopoverSurface, PopoverItem } from "./AnchoredPopover";
import { useAnchoredPopover } from "./anchoredPopoverHooks";
import { useScrollFade } from "../../lib/useScrollFade";
+import { InnerInput } from "./Input";
+import { fieldClass } from "./fieldStyles";
// The universal combobox: a react-select / Overseerr-style "combi input" — selected values are
// removable chips that live INSIDE a text field you type into, and a checklist of options opens below.
@@ -176,7 +179,10 @@ export default function Combobox({
setOpenAnd(true);
}
}}
- className="glass-card flex min-h-9 w-full cursor-text flex-wrap items-center gap-1 rounded-lg border border-border px-1.5 py-1 transition focus-within:border-accent focus-within:ring-2 focus-within:ring-accent/30"
+ className={clsx(
+ fieldClass({ size: "free" }),
+ "flex min-h-8 w-full cursor-text flex-wrap items-center gap-1 px-1.5 py-0.5 text-sm",
+ )}
>
{selected.map((v) => (
@@ -196,7 +202,8 @@ export default function Combobox({
))}
-
{
@@ -226,7 +233,6 @@ export default function Combobox({
// made a single chip + the reserved input + the chevron overflow a narrow rail field — wrapping
// the chevron to a second line while the (empty) input filled the first. A small floor keeps the
// chip + input + chevron on one line; flex-1 still grows the input to fill whatever room is left.
- className="min-w-8 flex-1 bg-transparent px-1 py-0.5 text-sm outline-hidden placeholder:text-muted/60"
/>
diff --git a/frontend/src/components/ui/CommandPalette.tsx b/frontend/src/components/ui/CommandPalette.tsx
index 200cc6e1..29751e2f 100644
--- a/frontend/src/components/ui/CommandPalette.tsx
+++ b/frontend/src/components/ui/CommandPalette.tsx
@@ -1,3 +1,4 @@
+import clsx from "clsx";
import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Search } from "lucide-react";
@@ -5,6 +6,8 @@ import Modal from "./Modal";
import { closeTopOverlayThenRun } from "../../lib/history";
import { pageStep, pageTarget } from "../../lib/listPaging";
import { Pressable } from "./Pressable";
+import { InnerInput } from "./Input";
+import { fieldClass } from "./fieldStyles";
// The app's jump-to bar: one text field over every place you can go and every app-wide thing you can
// do, opened with Ctrl/Cmd+K. It is the "one-layer operation" half of E10 — reaching a channel three
@@ -191,9 +194,11 @@ function CommandPaletteBody({ onClose, groups }: { onClose: () => void; groups:
return (