style(controls): unify buttons/inputs/selects/checkboxes/toggles onto design tokens (Phase 2) (#859)

* style(controls): Phase 2 — tokenize + unify buttons/inputs/checkboxes/toggles onto design tokens

Phase 2 of the borderless styling pass. Converts interactive controls to
design tokens with a cohesive, theme-tracking active/checked affordance,
building on Phase 1's borderless base. No behavior changes — visual/token only.

Shared primitives (highest leverage):
- ui/button.tsx: replace literal `hover:bg-white/[0.04]` (subtle/softGhost/
  chip/preset/iconBtn) with `hover:bg-[var(--chrome-hover-bg)]`.
- ui/toggle.tsx (seg): drop hardcoded `text-[#fff9ef]` active text and hover
  white literal for `text-fg` + `--chrome-hover-bg`.
- ui/Segmented.jsx: recessed track `bg-black/[0.28]` -> `bg-bg-elev-2`.
- index.css: native checkbox `accent-color` and range-input thumb/track/active
  moved off non-themed `--chrome-accent` / legacy `--text-primary`/`--primary`/
  raw rgba onto themed `--color-brand` / `--color-fg` / `--color-bg-elev-2` +
  radius/shadow/duration tokens. Checked state is now brand-tinted and recolors
  per [data-theme], matching sliders/segmented/primary buttons.
- SettingsToggle: on-state -> `--color-brand`, focus ring -> `--color-ring`,
  knob shadow -> `--shadow-sm`, radius -> `--radius-pill`.

Control call sites (exact-token swaps, remove hardcoded hex/rgba):
- Unified every checkbox `accent`/`accentColor` override onto `--color-brand`
  (DubbingDemo, DubRightColumn, DubLeftColumn, IdleSkeleton, DubFooter,
  DubSegmentRow, AppearancePanel range).
- FooterBtn blue/orange tones -> --color-info/--color-warn.
- MicButton danger tint/neutral fill -> tokens.
- DubLeftColumn install CTAs + engine chip -> brand tokens + --radius-pill.
- NetworkToggle: neutral fg/hover tokens; removed stray `#504945` fallback border.

Focus rings and the borderless guardrail (tests/test_no_literal_borders.py)
intact. build + format:check + lint (0 errors) + guardrail all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(dub): assert the tokenized brand-accent install button (bg-[var(--color-brand)]) after Phase 2

Phase 2 tokenized the highlighted Install CTA from the hardcoded #d3869b to
var(--color-brand); update the two assertions to match.

---------

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Palash Debnath
2026-07-01 20:50:04 +05:30
committed by GitHub
co-authored by mergetest Claude Opus 4.8
parent f7b7e2c13a
commit b2e578b21d
16 changed files with 46 additions and 39 deletions
+5 -2
View File
@@ -194,7 +194,6 @@ function DubSegmentRow({
onChange={(e) => onSelect(seg.id, idx, e.nativeEvent.shiftKey)}
onClick={(e) => onSelect(seg.id, idx, e.shiftKey)}
disabled={disabled}
style={{ accentColor: '#d3869b' }}
className="cursor-pointer justify-self-center"
title={t('segment.select_title')}
/>
@@ -423,7 +422,11 @@ function DubSegmentRow({
className="seg-gain-slider"
style={{
accentColor:
(seg.gain ?? 1.0) > 1.2 ? '#fb4934' : (seg.gain ?? 1.0) < 0.5 ? '#83a598' : '#a89984',
(seg.gain ?? 1.0) > 1.2
? 'var(--color-danger)'
: (seg.gain ?? 1.0) < 0.5
? 'var(--color-info)'
: 'var(--color-fg-muted)',
}}
/>
+1 -1
View File
@@ -116,7 +116,7 @@ export default function DubbingDemo({ onDismiss }) {
type="checkbox"
checked={syncPlay}
onChange={(e) => setSyncPlay(e.target.checked)}
className="accent-[#f3a5b6]"
className="accent-[var(--color-brand)]"
/>
{t('demo.dubbing_sync')}
</label>
+2 -2
View File
@@ -80,7 +80,7 @@ export default function NetworkToggle() {
return (
<div className="relative inline-flex items-center flex-shrink-0">
<button
className={`inline-flex items-center gap-[5px] py-[2px] px-[8px] h-[20px] rounded-sm font-medium text-[11px] [font-family:inherit] cursor-pointer [transition:all_0.1s] border border-solid disabled:opacity-50 disabled:cursor-not-allowed ${st.enabled ? 'bg-[rgba(184,187,38,0.12)] border-transparent text-[#b8bb26] hover:bg-[rgba(184,187,38,0.18)]' : 'bg-transparent border-transparent text-[#a89984] hover:bg-[rgba(255,255,255,0.04)] hover:text-fg'}`}
className={`inline-flex items-center gap-[5px] py-[2px] px-[8px] h-[20px] rounded-sm font-medium text-[11px] [font-family:inherit] cursor-pointer [transition:all_0.1s] border border-solid disabled:opacity-50 disabled:cursor-not-allowed ${st.enabled ? 'bg-[rgba(184,187,38,0.12)] border-transparent text-[#b8bb26] hover:bg-[rgba(184,187,38,0.18)]' : 'bg-transparent border-transparent text-[var(--color-fg-muted)] hover:bg-[var(--chrome-hover-bg)] hover:text-fg'}`}
onClick={st.enabled ? () => setOpen((o) => !o) : () => setConfirming((c) => !c)}
disabled={busy}
title={st.enabled ? t('network.sharing_on_title') : t('network.share_on_network')}
@@ -102,7 +102,7 @@ export default function NetworkToggle() {
<div className="flex gap-[6px] mt-[8px]">
<button
type="button"
className="bg-transparent border border-solid border-[var(--border,#504945)] [color:inherit] text-[11px] [font-family:inherit] py-[5px] px-[12px] rounded-md cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
className="bg-transparent border border-solid border-transparent [color:inherit] text-[11px] [font-family:inherit] py-[5px] px-[12px] rounded-md cursor-pointer hover:bg-[var(--chrome-hover-bg)] disabled:opacity-50 disabled:cursor-not-allowed"
onClick={() => setConfirming(false)}
disabled={busy}
>
+2 -2
View File
@@ -7,9 +7,9 @@ import { Sparkles, Square, Mic } from 'lucide-react';
const MIC_BASE =
'flex flex-col items-center justify-center gap-[var(--space-2)] px-4 py-2 min-w-[70px] rounded-[var(--radius-xl)] text-[length:var(--text-xs)] font-semibold cursor-pointer transition-all duration-[var(--dur-base)] ease-[var(--ease-out)]';
const MIC_IDLE =
'bg-white/[0.03] border border-transparent text-[var(--color-fg-muted)] hover:border-[var(--color-danger)] hover:text-[var(--color-danger)]';
'bg-[var(--chrome-hover-bg)] border border-transparent text-[var(--color-fg-muted)] hover:border-[var(--color-danger)] hover:text-[var(--color-danger)]';
const MIC_RECORDING =
'bg-[rgba(251,73,52,0.15)] border-2 border-[var(--color-danger)] text-[var(--color-danger)] animate-[pulse_1s_ease-in-out_infinite]';
'bg-[color-mix(in_srgb,var(--color-danger)_15%,transparent)] border-2 border-[var(--color-danger)] text-[var(--color-danger)] animate-[pulse_1s_ease-in-out_infinite]';
const MIC_CLEANING =
'bg-[rgba(184,187,38,0.10)] border border-transparent text-[#b8bb26] cursor-default';
+2 -2
View File
@@ -95,7 +95,7 @@ export default function DubFooter({
>
<input
type="checkbox"
className="accent-[var(--chrome-accent)]"
className="accent-[var(--color-brand)]"
checked={exportTracks['original'] !== false}
onChange={(e) => setExportTracks((prev) => ({ ...prev, original: e.target.checked }))}
/>
@@ -108,7 +108,7 @@ export default function DubFooter({
>
<input
type="checkbox"
className="accent-[var(--chrome-accent)]"
className="accent-[var(--color-brand)]"
checked={exportTracks[t] !== false}
onChange={(e) => setExportTracks((prev) => ({ ...prev, [t]: e.target.checked }))}
/>
@@ -40,12 +40,12 @@ const FIELD_LABEL =
'label-row !text-[0.58rem] !text-fg-muted !m-0 whitespace-nowrap overflow-hidden text-ellipsis';
const FIELD_INPUT = 'input-base !w-full !text-[0.65rem] !px-[5px] !py-[3px]';
const ENGINE_CHIP =
'ml-[6px] px-[6px] py-[1px] text-[0.55rem] leading-[1.4] bg-[rgba(211,134,155,0.14)] border border-transparent text-[#d3869b] rounded-[999px] whitespace-nowrap transition-colors';
// Highlighted accent Install affordance — brand accent (#d3869b) filled pill,
// deliberately louder than ENGINE_CHIP so an uninstalled selected engine is an
// obvious call to action rather than a muted footnote.
'ml-[6px] px-[6px] py-[1px] text-[0.55rem] leading-[1.4] bg-[color-mix(in_srgb,var(--color-brand)_14%,transparent)] border border-transparent text-[var(--color-brand)] rounded-[var(--radius-pill)] whitespace-nowrap transition-colors';
// Highlighted accent Install affordance — brand-filled pill, deliberately louder
// than ENGINE_CHIP so an uninstalled selected engine is an obvious call to action
// rather than a muted footnote.
const ENGINE_INSTALL_BTN =
'inline-flex items-center gap-[3px] ml-[6px] px-[7px] py-[1px] text-[0.55rem] font-semibold leading-[1.5] bg-[#d3869b] hover:bg-[#e0a0b3] text-[#1d2021] border border-transparent rounded-[999px] whitespace-nowrap cursor-pointer transition-colors shadow-[0_0_0_2px_rgba(211,134,155,0.25)] disabled:opacity-60 disabled:cursor-default';
'inline-flex items-center gap-[3px] ml-[6px] px-[7px] py-[1px] text-[0.55rem] font-semibold leading-[1.5] bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-[var(--color-fg-inverse)] border border-transparent rounded-[var(--radius-pill)] whitespace-nowrap cursor-pointer transition-colors shadow-[0_0_0_2px_color-mix(in_srgb,var(--color-brand)_25%,transparent)] disabled:opacity-60 disabled:cursor-default';
export default function DubLeftColumn({
hasDubbedTrack,
@@ -518,7 +518,7 @@ export default function DubLeftColumn({
)}
<button
type="button"
className="inline-flex items-center justify-center gap-[5px] px-[8px] py-[5px] text-[0.64rem] font-semibold bg-[#d3869b] hover:bg-[#e0a0b3] text-[#1d2021] border-none rounded-[6px] cursor-pointer transition-colors"
className="inline-flex items-center justify-center gap-[5px] px-[8px] py-[5px] text-[0.64rem] font-semibold bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-[var(--color-fg-inverse)] border-none rounded-[var(--radius-lg)] cursor-pointer transition-colors"
onClick={() => {
setTranslateProvider('argos');
setInstallPopoverOpen(false);
@@ -622,7 +622,7 @@ export default function DubLeftColumn({
<label className="flex items-center gap-[6px] text-[0.65rem] text-[var(--chrome-fg-muted)] cursor-pointer mb-[2px]">
<input
type="checkbox"
className="accent-[var(--chrome-accent)] cursor-pointer"
className="accent-[var(--color-brand)] cursor-pointer"
checked={multiLangMode}
onChange={(e) => setMultiLangMode(e.target.checked)}
/>
@@ -16,7 +16,7 @@ const OUT_LABEL =
'flex items-center gap-[var(--space-2)] cursor-pointer hover:text-[var(--chrome-fg)]';
const OUT_TITLE =
'font-[family-name:var(--chrome-font-mono)] text-[length:var(--chrome-label-size)] tracking-[var(--chrome-label-track)] uppercase text-[var(--chrome-fg-muted)] font-semibold';
const CHK = 'accent-[var(--chrome-accent)]';
const CHK = 'accent-[var(--color-brand)]';
const BULK_SELECT = 'input-base !text-[0.62rem] !px-[4px] !py-[2px]';
export default function DubRightColumn({
+3 -2
View File
@@ -22,11 +22,12 @@ const TONES = {
green:
'text-[var(--chrome-severity-ok)] border-transparent bg-[color-mix(in_srgb,var(--chrome-severity-ok)_10%,transparent)] hover:bg-[color-mix(in_srgb,var(--chrome-severity-ok)_18%,transparent)]',
pink: 'text-[var(--chrome-accent)] border-[var(--chrome-accent-border)] bg-[var(--chrome-accent-bg)] hover:bg-[color-mix(in_srgb,var(--chrome-accent)_20%,transparent)]',
blue: 'text-[#83a598] border-transparent bg-[color-mix(in_srgb,#83a598_10%,transparent)]',
blue: 'text-[var(--color-info)] border-transparent bg-[color-mix(in_srgb,var(--color-info)_10%,transparent)]',
lime: 'text-[#b8bb26] border-transparent bg-[color-mix(in_srgb,#b8bb26_10%,transparent)]',
amber:
'text-[var(--chrome-severity-warn)] border-transparent bg-[color-mix(in_srgb,var(--chrome-severity-warn)_10%,transparent)]',
orange: 'text-[#fe8019] border-transparent bg-[color-mix(in_srgb,#fe8019_10%,transparent)]',
orange:
'text-[var(--color-warn)] border-transparent bg-[color-mix(in_srgb,var(--color-warn)_10%,transparent)]',
};
const FooterBtn = React.forwardRef(function FooterBtn(
+1 -1
View File
@@ -366,7 +366,7 @@ export default function IdleSkeleton({
>
<input
type="checkbox"
className="m-0 accent-[#d3869b]"
className="m-0 accent-[var(--color-brand)]"
checked={fetchYtSubs}
onChange={(e) => setFetchYtSubs(e.target.checked)}
onClick={(e) => e.stopPropagation()}
@@ -65,7 +65,7 @@ export default function AppearancePanel() {
onChange={(e) => setUiScale(Number(e.target.value))}
aria-label={scaleLabel}
aria-valuetext={`${Math.round(uiScale * 100)}%`}
className="min-w-0 flex-1 cursor-pointer accent-[var(--chrome-accent)]"
className="min-w-0 flex-1 cursor-pointer accent-[var(--color-brand)]"
/>
<span className="min-w-[40px] text-right text-[length:var(--text-sm)] tabular-nums text-[var(--chrome-fg)]">
{Math.round(uiScale * 100)}%
@@ -46,10 +46,10 @@ export default function SettingsToggle({
{...rest}
/>
<span
className="absolute inset-0 rounded-[999px] bg-[var(--chrome-hover-bg)] transition-[background] duration-[160ms] ease-in-out [.is-on_&]:bg-[var(--chrome-accent)] peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-[var(--chrome-accent)] peer-focus-visible:outline-offset-2"
className="absolute inset-0 rounded-[var(--radius-pill)] bg-[var(--chrome-hover-bg)] transition-[background] duration-[160ms] ease-in-out [.is-on_&]:bg-[var(--color-brand)] peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-[var(--color-ring)] peer-focus-visible:outline-offset-2"
aria-hidden="true"
>
<span className="absolute top-[3px] left-[3px] w-[18px] h-[18px] rounded-full bg-[var(--chrome-bg)] shadow-[0_1px_2px_rgba(0,0,0,0.35)] transition-transform duration-[160ms] ease-in-out [.is-on_&]:translate-x-[18px]" />
<span className="absolute top-[3px] left-[3px] w-[18px] h-[18px] rounded-full bg-[var(--chrome-bg)] shadow-[var(--shadow-sm)] transition-transform duration-[160ms] ease-in-out [.is-on_&]:translate-x-[18px]" />
</span>
</label>
);
+5 -5
View File
@@ -44,19 +44,19 @@ const buttonVariants = cva(
primary:
'border border-transparent bg-primary text-primary-foreground font-semibold shadow-xs hover:bg-primary/90 active:scale-[0.98]',
subtle:
'border border-border bg-transparent text-muted-foreground hover:bg-white/[0.04] hover:text-foreground hover:border-transparent',
'border border-border bg-transparent text-muted-foreground hover:bg-[var(--chrome-hover-bg)] hover:text-foreground hover:border-transparent',
softGhost:
'border border-transparent bg-transparent text-muted-foreground hover:bg-white/[0.04] hover:text-foreground',
'border border-transparent bg-transparent text-muted-foreground hover:bg-[var(--chrome-hover-bg)] hover:text-foreground',
danger:
'text-destructive bg-destructive/10 border border-transparent hover:bg-destructive/20 hover:border-transparent',
chip: 'border border-border bg-transparent text-muted-foreground hover:bg-white/[0.04] hover:text-foreground hover:border-transparent',
chip: 'border border-border bg-transparent text-muted-foreground hover:bg-[var(--chrome-hover-bg)] hover:text-foreground hover:border-transparent',
chipActive: 'text-success bg-success/10 border border-transparent',
preset:
'justify-start text-left border border-border bg-transparent text-muted-foreground hover:bg-white/[0.04] hover:text-foreground hover:border-transparent',
'justify-start text-left border border-border bg-transparent text-muted-foreground hover:bg-[var(--chrome-hover-bg)] hover:text-foreground hover:border-transparent',
presetActive:
'justify-start text-left text-primary bg-primary/[0.12] border border-transparent',
iconBtn:
'border border-border bg-transparent text-muted-foreground hover:bg-white/[0.04] hover:text-foreground hover:border-transparent',
'border border-border bg-transparent text-muted-foreground hover:bg-[var(--chrome-hover-bg)] hover:text-foreground hover:border-transparent',
iconBtnActive: 'text-primary bg-primary/[0.12] border border-transparent',
},
size: {
+1 -1
View File
@@ -23,7 +23,7 @@ const toggleVariants = cva(
outline:
'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
// ── OmniVoice segmented option ──
seg: 'font-extrabold border-0 cursor-pointer rounded-[var(--radius-pill)] bg-transparent text-fg-subtle transition-[background,color] duration-[var(--dur-fast)] ease-[var(--ease-out)] data-[state=off]:hover:text-fg data-[state=off]:hover:bg-white/[0.04] data-[state=on]:bg-primary/25 data-[state=on]:text-[#fff9ef]',
seg: 'font-extrabold border-0 cursor-pointer rounded-[var(--radius-pill)] bg-transparent text-fg-subtle transition-[background,color] duration-[var(--dur-fast)] ease-[var(--ease-out)] data-[state=off]:hover:text-fg data-[state=off]:hover:bg-[var(--chrome-hover-bg)] data-[state=on]:bg-primary/25 data-[state=on]:text-fg',
},
size: {
default: 'h-9 px-2 min-w-9',
+10 -7
View File
@@ -910,30 +910,33 @@ select.input-base:hover {
}
/* ═══ CHECKBOX STYLING ═══ */
/* Checked fill uses the themed brand token so checkboxes recolor with every
[data-theme] and match the primary/active affordances (sliders, segmented,
toggles) — not the non-themed chrome-accent. */
input[type="checkbox"] {
accent-color: var(--chrome-accent);
accent-color: var(--color-brand);
width: 13px; height: 13px;
border-radius: 3px;
border-radius: var(--radius-sm);
}
/* ═══ RANGE INPUTS ═══ */
input[type="range"] {
-webkit-appearance: none; width: 100%; height: 3px;
background: rgba(255,255,255,0.08); border-radius: 2px; outline: none; margin-top: 3px;
background: var(--color-bg-elev-2); border-radius: var(--radius-xs); outline: none; margin-top: 3px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; width: 10px; height: 10px; border-radius: 50%;
background: var(--text-primary); cursor: pointer;
box-shadow: 0 0 4px rgba(0,0,0,0.4);
transition: transform var(--transition-fast);
background: var(--color-fg); cursor: pointer;
box-shadow: var(--shadow-sm);
transition: transform var(--dur-fast);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
input[type="range"]::-webkit-slider-thumb:active {
transform: scale(1.1);
background: var(--primary);
background: var(--color-brand);
}
/* .val-bubble → Tailwind utilities inline in clone/ActionBar.jsx (P4). */
@@ -100,7 +100,7 @@ describe('DubLeftColumn — translation-engine install affordance', () => {
// Highlighted accent button (not the muted chip): brand-accent bg class.
const btn = screen.getByRole('button', { name: /install deep_translator/i });
expect(btn.className).toMatch(/bg-\[#d3869b\]/);
expect(btn.className).toMatch(/bg-\[var\(--color-brand\)\]/);
fireEvent.click(btn);
expect(handleInstallEngine).toHaveBeenCalledWith('google');
@@ -122,7 +122,7 @@ describe('DubLeftColumn — translation-engine install affordance', () => {
// The highlighted trigger opens the escape-hatch popover.
const trigger = screen.getByRole('button', { name: /needs install/i });
expect(trigger.className).toMatch(/bg-\[#d3869b\]/);
expect(trigger.className).toMatch(/bg-\[var\(--color-brand\)\]/);
fireEvent.click(trigger);
const dialog = screen.getByRole('dialog');
+1 -1
View File
@@ -3,7 +3,7 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group.tsx';
import { cn } from '@/lib/utils';
const ROOT =
'ui-seg inline-flex gap-[2px] bg-black/[0.28] p-[3px] rounded-[var(--radius-pill)] border border-[color:var(--color-border)] shrink';
'ui-seg inline-flex gap-[2px] bg-bg-elev-2 p-[3px] rounded-[var(--radius-pill)] border border-transparent shrink';
const SIZE_MAP = { xs: 'segXs', sm: 'segSm' };