feat(ui): migrate chip/preset/tag classes to Button variants/utilities, trim index.css (P4) (#813)

P4 shadcn/Tailwind migration of the chip/preset/tag global class families out
of src/index.css and onto their components as Tailwind utilities.

- personality-chip (+ __icon, + .active): -> token utilities inline in
  clone/DesignMethodPanel.jsx (PCHIP_* consts). Active stays chrome-accent
  (pink); icon span -> inline-flex items-center. The cross-file
  `.starting-points__strip .personality-chip { flex:0 0 auto }` in
  CloneDesignTab.css moved onto the chip as the `flex-none` utility and the
  dead rule was removed.
- chip-group .chip (+ :hover/.active) and the chip-group container: chips ->
  token utilities (CHIP_* consts) in DesignMethodPanel.jsx; the container's
  flex layout -> `flex flex-wrap gap-1` utilities. The `chip-group` class name
  is KEPT on the container purely as a JS hook (CloneDesignTab's roving-tabindex
  keyboard nav does `closest('.chip-group')`).
- tag-btn (Insert-menu token chips): -> token utilities in clone/ScriptPanel.jsx
  (TAG_BTN const), preserving the mono face. Removing tag-btn's `!important`
  un-masks the intended `.clone-auto-extract-btn` green on the [CMU] button
  (author intent restored; palette-coherent).
- preset-btn: had ZERO usages -> both rule blocks deleted.
- The shared 10x a11y focus ring is reproduced on the migrated chips via a
  `focus-visible:[outline:2px_solid_var(--chrome-accent)]` utility, on top of
  the app's global `:focus-visible` ring.

Kept (irreducible): the shared `.personality-chip:focus-visible, .chip:focus-visible,
...` a11y rule (groups out-of-scope selectors); `.chip-auto`, `.preset-grid`,
`.tags-container`, `.personality-strip` (out of scope, still used).

index.css: +13 / -126 (net -113). Verified live (Clone "By design": personality
chips, identity chip-groups, Insert tag popover) before/after — pixel-coherent.
Gates: oxlint 0, oxfmt clean, vite build, vitest 641 pass, test:visual 48 pass,
bun --frozen-lockfile no change.

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-06-30 23:48:19 +05:30
committed by GitHub
co-authored by mergetest Claude Opus 4.8
parent 992eb67143
commit 3d17104228
4 changed files with 53 additions and 135 deletions
@@ -10,6 +10,26 @@ import {
} from '../../utils/voiceIcons';
import { buildDesignInstruct } from '../../utils/voiceInstruct';
// Chip / personality-chip class families migrated from index.css to Tailwind
// utilities (shadcn P4). The token utilities reference the same --chrome-* vars
// the old `.personality-chip` / `.chip-group .chip` rules used, so the look is
// unchanged and still recolors with every [data-theme]. Active = chrome accent
// (pink), matching the rest of the app's selection accent.
// `focus-visible` ring matches the studio's shared 10x a11y rule (index.css):
// an opaque accent outline at 1px offset, on top of the app's global ring.
const CHIP_FOCUS =
'focus-visible:[outline:2px_solid_var(--chrome-accent)] focus-visible:[outline-offset:1px]';
const PCHIP_BASE = `inline-flex items-center gap-[5px] px-[12px] py-[5px] font-[var(--font-sans)] text-[0.72rem] font-medium rounded-[var(--chrome-radius-pill)] border bg-transparent flex-none cursor-pointer transition-colors duration-[120ms] ${CHIP_FOCUS}`;
const PCHIP_INACTIVE =
'border-[var(--chrome-border)] text-[var(--chrome-fg-muted)] hover:bg-[var(--chrome-hover-bg)] hover:border-[var(--chrome-border-strong)] hover:text-[var(--chrome-fg)]';
const PCHIP_ACTIVE =
'bg-[var(--chrome-accent-bg)] border-[var(--chrome-accent-border)] text-[var(--chrome-accent)]';
const CHIP_BASE = `font-[var(--font-sans)] font-medium text-[0.68rem] px-[10px] py-[3px] rounded-[var(--chrome-radius-pill)] border bg-transparent whitespace-nowrap cursor-pointer transition-colors duration-[120ms] ${CHIP_FOCUS}`;
const CHIP_INACTIVE =
'border-[var(--chrome-border)] text-[var(--chrome-fg-muted)] hover:text-[var(--chrome-fg)] hover:bg-[var(--chrome-hover-bg)] hover:border-[var(--chrome-border-strong)]';
const CHIP_ACTIVE =
'bg-[var(--chrome-accent-bg)] border-[var(--chrome-accent-border)] text-[var(--chrome-accent)]';
export default function DesignMethodPanel({
t,
describeText,
@@ -76,10 +96,10 @@ export default function DesignMethodPanel({
<button
key={p.id}
type="button"
className={`personality-chip ${activePersonality === p.id ? 'active' : ''}`}
className={`${PCHIP_BASE} ${activePersonality === p.id ? PCHIP_ACTIVE : PCHIP_INACTIVE}`}
onClick={() => applyPersonality(p)}
>
<span className="personality-chip__icon">
<span className="inline-flex items-center">
<Icon size={13} />
</span>
{stripVoiceEmoji(t(`clone.personality_${p.id}`, { defaultValue: p.name }))}
@@ -92,10 +112,10 @@ export default function DesignMethodPanel({
<button
key={p.id}
type="button"
className="personality-chip"
className={`${PCHIP_BASE} ${PCHIP_INACTIVE}`}
onClick={() => applyPreset(p)}
>
<span className="personality-chip__icon">
<span className="inline-flex items-center">
<Icon size={13} />
</span>
{stripVoiceEmoji(t(`clone.preset_${p.id}`, { defaultValue: p.name }))}
@@ -157,7 +177,11 @@ export default function DesignMethodPanel({
))}
</select>
) : (
<div className="chip-group" role="radiogroup" aria-label={t(`clone.cat_${key}`)}>
<div
className="chip-group flex flex-wrap gap-1"
role="radiogroup"
aria-label={t(`clone.cat_${key}`)}
>
{options.map((opt, i) => {
const optTKey = `clone.opt_${opt.replace(/[ -]/g, '_')}`;
const optTl = t(optTKey);
@@ -173,7 +197,7 @@ export default function DesignMethodPanel({
role="radio"
aria-checked={checked}
tabIndex={roving ? 0 : -1}
className={`chip ${checked ? 'active' : ''}`}
className={`${CHIP_BASE} ${checked ? CHIP_ACTIVE : CHIP_INACTIVE}`}
onClick={() => setVdStates({ ...vdStates, [key]: opt })}
onKeyDown={(e) => onChipKeyDown(e, key, options)}
>
@@ -2,6 +2,12 @@ import { Command, Plus, ChevronDown } from 'lucide-react';
import DemoPresetGrid from '../DemoPresetGrid';
import { TAGS } from '../../utils/constants';
// `.tag-btn` (special-token chips in the Insert menu) migrated from index.css to
// Tailwind utilities (shadcn P4). Flat chrome pill, mono face — token utilities
// reference the same --chrome-* vars the old rule used, so the look is unchanged.
const TAG_BTN =
'border border-[var(--chrome-border)] bg-transparent text-[var(--chrome-fg-muted)] px-[9px] py-[3px] rounded-[var(--chrome-radius-pill)] font-[var(--chrome-font-mono)] font-medium text-[0.66rem] whitespace-nowrap cursor-pointer transition-colors duration-[120ms] hover:bg-[var(--chrome-hover-bg)] hover:text-[var(--chrome-fg)] hover:border-[var(--chrome-border-strong)]';
export default function ScriptPanel({
t,
defineMethod,
@@ -87,7 +93,7 @@ export default function ScriptPanel({
{TAGS.map((tag) => (
<button
key={tag}
className="tag-btn"
className={TAG_BTN}
role="menuitem"
onClick={() => {
insertTag(tag);
@@ -98,7 +104,7 @@ export default function ScriptPanel({
</button>
))}
<button
className="tag-btn clone-auto-extract-btn"
className={`${TAG_BTN} clone-auto-extract-btn`}
role="menuitem"
onClick={() => {
insertTag('[B EY1 S]');
+13 -126
View File
@@ -562,18 +562,7 @@ input[type="range"]::-webkit-slider-thumb:active {
/* ═══ TAGS ═══ */
.tags-container { display: flex; flex-wrap: wrap; gap: 3px; margin: 4px 0; }
.tag-btn {
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.07);
color: var(--text-secondary); padding: 2px 6px; border-radius: 3px;
font-size: 0.65rem; cursor: pointer; transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast); white-space: nowrap;
font-family: var(--chrome-font-mono);
}
.tag-btn:hover {
background: var(--chrome-hover-bg); color: var(--chrome-fg);
border-color: var(--chrome-border-strong);
transform: none;
}
.tag-btn:active { transform: none; }
/* .tag-btn → Tailwind utilities inline in clone/ScriptPanel.jsx (P4). */
.file-drag {
border: 1px dashed var(--chrome-border-strong); border-radius: var(--chrome-radius-pill); padding: 10px;
@@ -683,18 +672,8 @@ audio::-webkit-media-controls-time-remaining-display { color: var(--chrome-fg);
.preset-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 4px; margin-bottom: 6px;
}
.preset-btn {
background: rgba(94,106,210,0.06); border: 1px solid rgba(94,106,210,0.15);
color: #c7d2fe; padding: 3px 6px; border-radius: 4px; font-size: 0.65rem;
cursor: pointer; transition: all var(--transition-smooth); text-align: left;
display: flex; align-items: center; gap: 3px;
}
.preset-btn:hover {
background: rgba(94,106,210,0.15); border-color: #a78bfa;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(94,106,210,0.15);
}
.preset-btn:active { transform: translateY(0); }
/* .preset-btn removed (P4) — the class had zero remaining usages in the app
(the shadcn Button `preset` variant covers preset buttons now). */
/* ═══ SEGMENT TABLE ═══ */
.segment-table {
@@ -1135,28 +1114,8 @@ select:focus:not(:focus-visible) { outline: none; box-shadow: none; }
.personality-strip {
display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px;
}
.personality-chip {
display: inline-flex; align-items: center; gap: 5px;
padding: 5px 12px;
font-family: var(--font-sans); font-size: 0.72rem; font-weight: 500;
border-radius: var(--chrome-radius-pill);
background: transparent;
border: 1px solid var(--chrome-border);
color: var(--chrome-fg-muted); cursor: pointer;
transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
}
.personality-chip:hover {
background: var(--chrome-hover-bg);
border-color: var(--chrome-border-strong);
color: var(--chrome-fg);
}
.personality-chip.active {
background: var(--chrome-accent-bg);
border-color: var(--chrome-accent-border);
color: var(--chrome-accent);
}
.personality-chip__icon { display: inline-flex; align-items: center; }
.chip-group .chip .chip-auto,
/* .personality-chip / .personality-chip__icon → Tailwind utilities inline in
clone/DesignMethodPanel.jsx (P4). */
.chip-auto { display: inline-flex; align-items: center; gap: 3px; }
.personality-label {
font-family: var(--chrome-font-mono);
@@ -1332,93 +1291,24 @@ select:focus:not(:focus-visible) { outline: none; box-shadow: none; }
box-shadow: 0 0 0 3px rgba(243, 165, 182, 0.12), inset 0 1px 2px rgba(0,0,0,0.25);
}
/* Preset chips — flat chrome pills. No rotation, no gradient, no squircle. */
/* Preset chips — flat chrome pills. No rotation, no gradient, no squircle.
The `.preset-btn` rules were removed in P4 (zero usages); only the grid
layout remains. */
.preset-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 6px;
}
.preset-btn {
font-family: var(--font-sans) !important;
font-weight: 500 !important; font-size: 0.74rem !important;
letter-spacing: 0.02em !important;
padding: 6px 12px !important;
border-radius: var(--chrome-radius-pill) !important;
background: transparent !important;
border: 1px solid var(--chrome-border) !important;
color: var(--chrome-fg-muted) !important;
box-shadow: none;
transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
cursor: pointer;
display: inline-flex; align-items: center; gap: 5px;
}
.preset-btn:nth-child(3n+1),
.preset-btn:nth-child(3n+2),
.preset-btn:nth-child(3n+3) { transform: none; }
.preset-btn:hover:not(:disabled) {
transform: none;
background: var(--chrome-hover-bg);
color: var(--chrome-fg);
border-color: var(--chrome-border-strong);
box-shadow: none;
}
.preset-btn.profile-active {
background: var(--chrome-accent-bg) !important;
border-color: var(--chrome-accent-border) !important;
color: var(--chrome-accent) !important;
transform: none;
box-shadow: none;
}
/* Tag / special-token chips — flat chrome, mono, no rotation */
.tags-container {
display: flex; flex-wrap: wrap; gap: 4px;
}
.tag-btn {
transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
font-family: var(--chrome-font-mono) !important;
font-weight: 500 !important;
font-size: 0.66rem !important;
padding: 3px 9px !important;
border-radius: var(--chrome-radius-pill) !important;
background: transparent !important;
border: 1px solid var(--chrome-border) !important;
color: var(--chrome-fg-muted) !important;
cursor: pointer;
}
.tag-btn:hover {
transform: none;
background: var(--chrome-hover-bg) !important;
color: var(--chrome-fg) !important;
border-color: var(--chrome-border-strong) !important;
}
/* .tag-btn → Tailwind utilities inline in clone/ScriptPanel.jsx (P4). */
/* Chip-group for voice design attributes */
.chip-group {
display: flex; flex-wrap: wrap; gap: 4px;
}
.chip-group .chip {
font-family: var(--font-sans);
font-weight: 500; font-size: 0.68rem;
padding: 3px 10px;
border-radius: var(--chrome-radius-pill);
background: transparent;
border: 1px solid var(--chrome-border);
color: var(--chrome-fg-muted); cursor: pointer;
transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
white-space: nowrap;
}
.chip-group .chip:hover {
transform: none;
color: var(--chrome-fg);
background: var(--chrome-hover-bg);
border-color: var(--chrome-border-strong);
}
.chip-group .chip.active {
background: var(--chrome-accent-bg);
border-color: var(--chrome-accent-border);
color: var(--chrome-accent);
box-shadow: none;
}
/* .chip-group / .chip-group .chip → Tailwind utilities inline in
clone/DesignMethodPanel.jsx (P4). The `chip-group` class name is kept on the
container purely as a JS hook (CloneDesignTab's roving-tabindex keyboard nav
does `closest('.chip-group')`); its layout is now flex utilities on the div. */
/* Textarea — flat chrome surface for design/clone prompt box */
textarea.input-base {
@@ -1863,9 +1753,6 @@ div[role="dialog"].audio-trimmer {
.preset-grid { grid-template-columns: 1fr !important; }
}
/* ── Chip groups — wrap gracefully ── */
.chip-group { flex-wrap: wrap; }
/* ── Compare modal — stack on narrow ── */
@media (max-width: 800px) {
.ui-compare__grid {
+2 -1
View File
@@ -193,7 +193,8 @@
mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 18px), transparent);
}
.starting-points__strip::-webkit-scrollbar { display: none; }
.starting-points__strip .personality-chip { flex: 0 0 auto; }
/* The personality chips' non-shrink sizing (was `.personality-chip { flex:0 0 auto }`)
moved onto the chips as the `flex-none` utility in DesignMethodPanel.jsx (P4). */
/* One definition column: Prompt stacked over Voice Source (spec §1 restack).
The old side-by-side split crammed both panels once the right-side