DuckSuite_Design_Language.md covers icons only — mascot geometry, lighting, one-idea-per-icon, readable at 64px. It says nothing about type, density or data, which is why the chrome looks considered and every data view does not. This is the companion document plus the primitives that enforce it. The diagnosis it is written against, from grepping two apps: 11 uses of text-[10px], 7 of text-[11px], 52 of text-xs and 3 of text-base, with no rule about which meant what; six radius values; hand-picked emerald/amber/red/purple next to unused --success/--warning/--destructive tokens; text-black/50 and ring-black/5, which are invisible in dark mode. None of that was a bad decision, it was the absence of one thirty times over — so the fix is to remove the choice rather than to have better taste. docs/design-language-interface.md sets four type ranks with a 12px floor, one focal point per row, five state tones, three radii and a spacing rhythm. The principles are lifted from the icon language rather than invented, because "one focal point, reads instantly, no unnecessary decorations, if it needs explanation it is too complicated" is already the right rule for a dense list. components/Data/ is how you spend that vocabulary: DataRow/DataList/RowMeta, StatusPill/StatusIcon, LoadingBlock/ErrorBlock/EmptyBlock, RelativeTime. Rules you have to remember are rules thirty views already broke, so the shape encodes them — DataRow takes exactly one title and everything else is meta, RowMeta puts separators between items so a trailing dot cannot appear, RelativeTime carries the absolute timestamp as a hover title. Adds --info (violet) as the fifth semantic tone, light and dark. "Merged" and "in progress" are neither good news nor bad, and painting them with --success makes a merged PR and an open one look like the same thing. Entirely additive: nothing imports these yet, so no existing view changes and there is no collision with the other agent working in this tree. Typechecks clean. Not yet rendered in a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
// The state vocabulary for data surfaces. See docs/design-language-interface.md § 3.
|
|
//
|
|
// Five tones and no others. The point of a closed set is that colour keeps meaning something: when a
|
|
// view can reach for any of Tailwind's twenty-two palettes, green stops reading as "open" and starts
|
|
// reading as "the author liked green here". Every tone maps to a semantic token, never a palette
|
|
// number, so it follows the theme and survives dark mode.
|
|
//
|
|
// Class strings are written out in full rather than composed (`text-${tone}`) because Tailwind's
|
|
// scanner reads source text — an interpolated class name is not in the output CSS at all.
|
|
|
|
export type Tone = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
|
|
|
|
/** Foreground only — for an icon or a word carrying state inside otherwise normal text. */
|
|
export const toneText: Record<Tone, string> = {
|
|
neutral: 'text-muted-foreground',
|
|
success: 'text-success',
|
|
warning: 'text-warning',
|
|
danger: 'text-destructive',
|
|
info: 'text-info',
|
|
};
|
|
|
|
/**
|
|
* The tinted fill used by pills and badges. Always /15 — a solid saturated fill on something this
|
|
* small fights the text on top of it and shouts louder than the state deserves.
|
|
*/
|
|
export const toneFill: Record<Tone, string> = {
|
|
neutral: 'bg-muted text-muted-foreground',
|
|
success: 'bg-success/15 text-success',
|
|
warning: 'bg-warning/15 text-warning',
|
|
danger: 'bg-destructive/15 text-destructive',
|
|
info: 'bg-info/15 text-info',
|
|
};
|
|
|
|
/** A left border or rule carrying state, for rows that mark status without a pill. */
|
|
export const toneBorder: Record<Tone, string> = {
|
|
neutral: 'border-border',
|
|
success: 'border-success',
|
|
warning: 'border-warning',
|
|
danger: 'border-destructive',
|
|
info: 'border-info',
|
|
};
|