From 76f8de258cbfd1d9afc1d199f3026acc34a52d9e Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 11 Aug 2026 04:04:06 +0200 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20R10=20=E2=80=94=20.btn-primary?= =?UTF-8?q?=20recipe=20for=20the=20primary/action=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a paint-only .btn-primary recipe (solid accent bg + accent-fg text + hover:opacity dim) in index.css @layer components, alongside the selection-paint recipes (.pill/.chip/.is-current). Migrate ~38 primary/action buttons across 24 files off the hand-rolled 'bg-accent text-accent-fg hover:opacity-90', keeping each site's geometry (padding/radius/font/gap/disabled/shadow). Deliberately NOT migrated: accent BADGES/labels (spans), the chat message bubble, selection toggles (tableHeader — R8 popover territory), and the round play FAB (hover:scale, not the opacity-dim primary idiom). Three previously hover-less primary buttons (SettingsPanel/SetupWizard/DraftSaveBar) gain the standard hover dim for consistency. Gate green (tsc/eslint-0/prettier/knip/vitest 79); self-E2E confirmed a migrated button computes to accent/accent-fg and both recipe rules load. --- frontend/src/components/About.tsx | 2 +- frontend/src/components/Banner.tsx | 2 +- frontend/src/components/ChannelPage.tsx | 2 +- frontend/src/components/ChatThread.tsx | 2 +- frontend/src/components/ConfirmProvider.tsx | 6 +----- frontend/src/components/DownloadCenter.tsx | 8 ++++---- frontend/src/components/DownloadDialog.tsx | 2 +- frontend/src/components/ErrorDialog.tsx | 2 +- frontend/src/components/Feed.tsx | 6 +++--- frontend/src/components/KeyGate.tsx | 2 +- frontend/src/components/Messages.tsx | 2 +- frontend/src/components/OnboardingWizard.tsx | 6 +++--- frontend/src/components/PlayerModal.tsx | 4 ++-- frontend/src/components/Playlists.tsx | 2 +- frontend/src/components/PlexBrowse.tsx | 2 +- frontend/src/components/ProfileEditor.tsx | 4 ++-- frontend/src/components/SettingsPanel.tsx | 2 +- frontend/src/components/SetupWizard.tsx | 2 +- frontend/src/components/ShareDialog.tsx | 4 ++-- frontend/src/components/VideoCard.tsx | 2 +- frontend/src/components/VideoEditor.tsx | 5 +---- frontend/src/components/WatchPage.tsx | 2 +- frontend/src/components/Welcome.tsx | 2 +- frontend/src/components/ui/DraftSaveBar.tsx | 2 +- frontend/src/index.css | 12 ++++++++++++ 25 files changed, 46 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/About.tsx b/frontend/src/components/About.tsx index 25c1c3dc..46d2888c 100644 --- a/frontend/src/components/About.tsx +++ b/frontend/src/components/About.tsx @@ -72,7 +72,7 @@ export default function About({ diff --git a/frontend/src/components/Banner.tsx b/frontend/src/components/Banner.tsx index 888b8f7d..e0fae05f 100644 --- a/frontend/src/components/Banner.tsx +++ b/frontend/src/components/Banner.tsx @@ -8,7 +8,7 @@ const TONES: Record = accent: { bar: "bg-accent/15 border-accent/30", icon: "text-accent", - action: "bg-accent text-accent-fg hover:opacity-90", + action: "btn-primary", }, warning: { bar: "bg-warn/15 border-warn/30", diff --git a/frontend/src/components/ChannelPage.tsx b/frontend/src/components/ChannelPage.tsx index 34b6682a..318518b7 100644 --- a/frontend/src/components/ChannelPage.tsx +++ b/frontend/src/components/ChannelPage.tsx @@ -460,7 +460,7 @@ export default function ChannelPage({ diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx index ed0cbefe..cf4a61a9 100644 --- a/frontend/src/components/DownloadCenter.tsx +++ b/frontend/src/components/DownloadCenter.tsx @@ -390,7 +390,7 @@ function MetaEditModal({ job, onClose }: { job: DownloadJob; onClose: () => void @@ -448,7 +448,7 @@ function ErrorModal({ onRetry(); onClose(); }} - className="px-3 py-1.5 rounded-lg text-sm font-medium bg-accent text-accent-fg hover:opacity-90 transition" + className="px-3 py-1.5 rounded-lg text-sm font-medium btn-primary transition" > {t("downloads.actions.retry")} @@ -607,7 +607,7 @@ function QuotaModal({ @@ -894,7 +894,7 @@ export default function DownloadCenter() { diff --git a/frontend/src/components/DownloadDialog.tsx b/frontend/src/components/DownloadDialog.tsx index 36caadd7..0fc3f72e 100644 --- a/frontend/src/components/DownloadDialog.tsx +++ b/frontend/src/components/DownloadDialog.tsx @@ -98,7 +98,7 @@ export default function DownloadDialog({ diff --git a/frontend/src/components/ErrorDialog.tsx b/frontend/src/components/ErrorDialog.tsx index c212cfa7..6ba217a6 100644 --- a/frontend/src/components/ErrorDialog.tsx +++ b/frontend/src/components/ErrorDialog.tsx @@ -14,7 +14,7 @@ export default function ErrorDialog() {
diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 5d49bcf6..f932e961 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -522,7 +522,7 @@ export default function Feed({ {isDemo ? ( @@ -530,7 +530,7 @@ export default function Feed({ <> @@ -711,7 +711,7 @@ export default function Feed({ {filters.q.trim() && !isDemo && ( @@ -221,7 +221,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
@@ -252,7 +252,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: () endOnboarding(false); onClose(); }} - className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition w-full" + className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold btn-primary transition w-full" > {t("onboarding.done.done")} diff --git a/frontend/src/components/PlayerModal.tsx b/frontend/src/components/PlayerModal.tsx index bb403baf..0b22c319 100644 --- a/frontend/src/components/PlayerModal.tsx +++ b/frontend/src/components/PlayerModal.tsx @@ -1088,7 +1088,7 @@ export default function PlayerModal({ href={`https://www.youtube.com/watch?v=${currentVideoId}`} target="_blank" rel="noreferrer" - className="inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition" + className="inline-flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-semibold btn-primary transition" > {t("player.openOnYoutube")} @@ -1314,7 +1314,7 @@ export default function PlayerModal({ className={ "shrink-0 inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg transition " + (watched - ? "bg-accent text-accent-fg shadow-xs hover:opacity-90" + ? "btn-primary shadow-xs" : "text-muted hover:text-fg hover:bg-surface border border-border") } > diff --git a/frontend/src/components/Playlists.tsx b/frontend/src/components/Playlists.tsx index 95e1fe29..a1bb8774 100644 --- a/frontend/src/components/Playlists.tsx +++ b/frontend/src/components/Playlists.tsx @@ -630,7 +630,7 @@ export default function Playlists() { diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index 02892b03..4a871832 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -673,7 +673,7 @@ function ActionBtn({ onClick={onClick} disabled={disabled} className={`inline-flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium transition disabled:opacity-50 ${ - primary ? "bg-accent text-accent-fg shadow-xs hover:opacity-90" : "glass-card glass-hover" + primary ? "btn-primary shadow-xs" : "glass-card glass-hover" }`} > diff --git a/frontend/src/components/ProfileEditor.tsx b/frontend/src/components/ProfileEditor.tsx index 685436eb..8822ca19 100644 --- a/frontend/src/components/ProfileEditor.tsx +++ b/frontend/src/components/ProfileEditor.tsx @@ -147,7 +147,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { {!formOpen ? ( @@ -272,7 +272,7 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 8b88677c..eab9b47b 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -480,7 +480,7 @@ function SignInMethods({ me }: { me: Me }) { diff --git a/frontend/src/components/SetupWizard.tsx b/frontend/src/components/SetupWizard.tsx index 7c863756..9b53654d 100644 --- a/frontend/src/components/SetupWizard.tsx +++ b/frontend/src/components/SetupWizard.tsx @@ -278,7 +278,7 @@ export default function SetupWizard() { @@ -352,7 +352,7 @@ function LinkShare({ job }: { job: DownloadJob }) { diff --git a/frontend/src/components/VideoCard.tsx b/frontend/src/components/VideoCard.tsx index 749045c5..b2a1f77b 100644 --- a/frontend/src/components/VideoCard.tsx +++ b/frontend/src/components/VideoCard.tsx @@ -277,7 +277,7 @@ function Thumb({ title={t("card.continueTitle")} aria-label={t("card.continue")} className={clsx( - "inline-flex items-center justify-center gap-1.5 font-semibold bg-accent text-accent-fg shadow-lg hover:opacity-90 transition", + "inline-flex items-center justify-center gap-1.5 font-semibold btn-primary shadow-lg transition", small ? "w-9 h-9 rounded-full" : "px-3 py-1.5 rounded-lg text-sm", )} > diff --git a/frontend/src/components/VideoEditor.tsx b/frontend/src/components/VideoEditor.tsx index a2070ab8..b23de353 100644 --- a/frontend/src/components/VideoEditor.tsx +++ b/frontend/src/components/VideoEditor.tsx @@ -630,10 +630,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos diff --git a/frontend/src/components/Welcome.tsx b/frontend/src/components/Welcome.tsx index 0671e38d..5e2dd325 100644 --- a/frontend/src/components/Welcome.tsx +++ b/frontend/src/components/Welcome.tsx @@ -501,7 +501,7 @@ function AuthCard() { diff --git a/frontend/src/components/ui/DraftSaveBar.tsx b/frontend/src/components/ui/DraftSaveBar.tsx index 9c15fd20..2dd3dd5a 100644 --- a/frontend/src/components/ui/DraftSaveBar.tsx +++ b/frontend/src/components/ui/DraftSaveBar.tsx @@ -71,7 +71,7 @@ export function DraftSaveBar({ data-testid="draft-save" onClick={onSave} disabled={saving || !dirty} - className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-accent text-accent-fg font-medium shadow-md shadow-accent/20 disabled:opacity-40 transition" + className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm btn-primary font-medium shadow-md shadow-accent/20 disabled:opacity-40 transition" > {saving ? labels.saving : labels.save} diff --git a/frontend/src/index.css b/frontend/src/index.css index 3ae68bfb..9fce8e50 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -496,6 +496,18 @@ body { color: var(--accent-fg); border-color: var(--accent); } + /* .btn-primary — the primary/ACTION button paint (solid accent). Distinct from the selection + recipes above: this is "the primary action", not "selected among options". PAINT-ONLY like + .chip-solid — a site keeps its own geometry (padding/radius/font/gap/disabled:opacity/transition). + Migrated sites MUST drop `bg-accent text-accent-fg hover:opacity-90` so this @layer-components + rule isn't beaten by those utilities. No focus-visible — @layer base already rings every button. */ + .btn-primary { + background-color: var(--accent); + color: var(--accent-fg); + } + .btn-primary:hover:not(:disabled) { + opacity: 0.9; + } } .glass-hover:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent);