@momoi-labs/kiso 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kiso/docs/components/README.md +4 -2
- package/kiso/docs/components/app-shell.md +51 -12
- package/kiso/docs/components/chip-input.md +194 -0
- package/kiso/docs/components/sparkline.md +93 -0
- package/kiso/docs/components/table.md +3 -1
- package/kiso/docs/evolution.md +10 -1
- package/kiso/docs/patterns/dashboard.md +3 -2
- package/kiso/docs/patterns/settings.md +1 -0
- package/kiso/docs/tokens.md +54 -36
- package/kiso/ui.css +135 -4
- package/package.json +1 -1
- package/tokens/build/tokens.css +55 -43
- package/tokens/build/tokens.d.ts +34 -12
- package/tokens/build/tokens.json +67 -55
- package/tokens/build/tokens.scss +69 -57
package/kiso/docs/tokens.md
CHANGED
|
@@ -44,23 +44,32 @@ Import `tokens.css`; application CSS should need no raw color values.
|
|
|
44
44
|
|
|
45
45
|
## Semantic colors
|
|
46
46
|
|
|
47
|
+
The dark theme draws its surfaces from a dedicated warm dark ramp
|
|
48
|
+
(`color.dark.*`), not from the tail of the light ramp: one hue family for both
|
|
49
|
+
themes, with layer spacing that lets the sidebar sink below the canvas and the
|
|
50
|
+
card lift above it. The accent is split into two roles. The **ink**
|
|
51
|
+
(`accent.base`, flowing into `link` and `focus`) is the text-eligible lilac for
|
|
52
|
+
links, focus rings, and active indicators. The **fill** (`primary` and its
|
|
53
|
+
hover and surface tints) is the deep violet behind `primary-foreground`,
|
|
54
|
+
never text on dark.
|
|
55
|
+
|
|
47
56
|
| Role | Meaning and use | Dark primitive | Light primitive |
|
|
48
57
|
| --- | --- | --- | --- |
|
|
49
|
-
| `background` | Application canvas; never text. | `
|
|
50
|
-
| `surface` | Cards, panels, and table rows. | `
|
|
51
|
-
| `elevated-surface` | Menus, popovers, and dialogs. | `
|
|
52
|
-
| `foreground` | Primary text and content that must carry the strongest hierarchy. | `
|
|
53
|
-
| `muted-foreground` | Secondary text and labels. It remains normal-text eligible. | `
|
|
54
|
-
| `subtle-foreground` | Placeholders, timestamps, and non-essential hints; large text only, never body copy. | `
|
|
55
|
-
| `border` | Dividers and control outlines; never text. | `
|
|
56
|
-
| `primary` | The
|
|
58
|
+
| `background` | Application canvas; never text. | `dark.900` | `neutral.200` |
|
|
59
|
+
| `surface` | Cards, panels, and table rows. | `dark.800` | `neutral.100` |
|
|
60
|
+
| `elevated-surface` | Menus, popovers, and dialogs. | `dark.700` | `white` |
|
|
61
|
+
| `foreground` | Primary text and content that must carry the strongest hierarchy. | `dark.100` | `neutral.900` |
|
|
62
|
+
| `muted-foreground` | Secondary text and labels. It remains normal-text eligible. | `dark.400` | `neutral.600` |
|
|
63
|
+
| `subtle-foreground` | Placeholders, timestamps, and non-essential hints; large text only, never body copy. | `dark.450` | `neutral.500` |
|
|
64
|
+
| `border` | Dividers and control outlines; never text. | `dark.600` | `neutral.300` |
|
|
65
|
+
| `primary` | The primary fill: primary buttons, solid badges, checked controls, the brand mark. Not text-eligible on dark. | `#684bb5` | `#5b3fc4` |
|
|
57
66
|
| `accent` | Secondary emphasis and highlights, not the page's main action. | `accent.300` | `accent.800` |
|
|
58
67
|
| `success` | Positive or completed state. | `status.success` | `status.success` |
|
|
59
68
|
| `warning` | Caution or a condition needing attention. | `status.warning` | `status.warning` |
|
|
60
69
|
| `danger` | Error or destructive action. | `status.danger` | `status.danger` |
|
|
61
70
|
| `info` | Neutral informational state. | `status.info` | `status.info` |
|
|
62
|
-
| `focus` | Keyboard focus ring; never text. | `accent.
|
|
63
|
-
| `disabled` | Disabled text and controls only. | `
|
|
71
|
+
| `focus` | Keyboard focus ring; never text. The light ink. | `accent.base` | `accent.base` |
|
|
72
|
+
| `disabled` | Disabled text and controls only. | `dark.600` | `neutral.400` |
|
|
64
73
|
|
|
65
74
|
### Fills and their foregrounds
|
|
66
75
|
|
|
@@ -70,46 +79,48 @@ outline — which is how a violet design system ends up rendering grey.
|
|
|
70
79
|
|
|
71
80
|
| Role | Meaning and use | Dark primitive | Light primitive |
|
|
72
81
|
| --- | --- | --- | --- |
|
|
73
|
-
| `primary-foreground` | Text and icons on a `primary` fill. | `
|
|
74
|
-
| `primary-hover` | `primary` fill on hover. | `
|
|
75
|
-
| `danger-foreground` | Text and icons on a `danger` fill. | `
|
|
76
|
-
| `secondary` | Neutral button fill, range track, count badge; never text. | `
|
|
82
|
+
| `primary-foreground` | Text and icons on a `primary` fill. | near-white `#f7f5fe` | `white` |
|
|
83
|
+
| `primary-hover` | `primary` fill on hover. | `#795dc9` | `accent.800` |
|
|
84
|
+
| `danger-foreground` | Text and icons on a `danger` fill. | `dark.950` | `white` |
|
|
85
|
+
| `secondary` | Neutral button fill, range track, count badge; never text. | `dark.700` | `neutral.300` |
|
|
77
86
|
| `secondary-foreground` | Text on a `secondary` fill. | `foreground` | `foreground` |
|
|
78
|
-
| `secondary-hover` | `secondary` fill on hover; never text. | `
|
|
79
|
-
| `selected` | Selected row, active nav item, highlighted result; never text. One step past `accent-surface-hover`, so selection and hover stay apart. | `
|
|
87
|
+
| `secondary-hover` | `secondary` fill on hover; never text. | `dark.600` | `neutral.400` |
|
|
88
|
+
| `selected` | Selected row, active nav item, highlighted result; never text. One step past `accent-surface-hover`, so selection and hover stay apart. | `#49406d` | `accent.300` |
|
|
80
89
|
| `selected-foreground` | Text on a `selected` fill. | `foreground` | `foreground` |
|
|
81
90
|
|
|
82
|
-
`primary-foreground`
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
`primary-foreground` is near-white (`#f7f5fe`) on the deep violet fill in both
|
|
92
|
+
themes; the fill no longer flips to a light lilac on dark, so the foreground
|
|
93
|
+
no longer inverts. `danger-foreground` still inverts with its fill: white on
|
|
94
|
+
the light-theme red, near-black on the dark-theme salmon. Both are gated at
|
|
95
|
+
4.5:1 **against their own fill**, not against a surface.
|
|
85
96
|
|
|
86
97
|
### Component surfaces
|
|
87
98
|
|
|
88
99
|
| Role | Meaning and use | Dark primitive | Light primitive |
|
|
89
100
|
| --- | --- | --- | --- |
|
|
90
|
-
| `card` | Panel and card fill. Alias of `surface`. | `
|
|
101
|
+
| `card` | Panel and card fill. Alias of `surface`. | `dark.800` | `neutral.100` |
|
|
91
102
|
| `card-foreground` | Text on `card`. Alias of `foreground`. | `foreground` | `foreground` |
|
|
92
|
-
| `popover` | Menus, dialogs, palette. Alias of `elevated-surface`. | `
|
|
103
|
+
| `popover` | Menus, dialogs, palette. Alias of `elevated-surface`. | `dark.700` | `white` |
|
|
93
104
|
| `popover-foreground` | Text on `popover`. Alias of `foreground`. | `foreground` | `foreground` |
|
|
94
|
-
| `muted` | Recessed fill one step off `card`: table headers, card footers, segmented tracks. | `
|
|
95
|
-
| `sidebar` | Application shell navigation column. | `
|
|
105
|
+
| `muted` | Recessed fill one step off `card`: table headers, card footers, segmented tracks. | `dark.700` | `neutral.200` |
|
|
106
|
+
| `sidebar` | Application shell navigation column. | `dark.950` | `neutral.100` |
|
|
96
107
|
| `sidebar-border` | Divider between sidebar and content. Alias of `border`. | `border` | `border` |
|
|
97
|
-
| `disabled-surface` | Fill of a disabled control. | `
|
|
98
|
-
| `skeleton` | Loading placeholder fill. | `
|
|
108
|
+
| `disabled-surface` | Fill of a disabled control. | `dark.800` | `neutral.200` |
|
|
109
|
+
| `skeleton` | Loading placeholder fill. | `dark.700` | `neutral.300` |
|
|
99
110
|
| `overlay` | Scrim behind a modal layer. | `black` at 60% | `black` at 40% |
|
|
100
111
|
|
|
101
112
|
### Lines, tints, and state
|
|
102
113
|
|
|
103
114
|
| Role | Meaning and use | Dark primitive | Light primitive |
|
|
104
115
|
| --- | --- | --- | --- |
|
|
105
|
-
| `border-strong` | Hover outlines, switch tracks, gridlines, corner marks; never text. | `
|
|
116
|
+
| `border-strong` | Hover outlines, switch tracks, gridlines, corner marks; never text. | `dark.450` | `neutral.500` |
|
|
106
117
|
| `input` | Form control outline at rest. Alias of `border`. | `border` | `border` |
|
|
107
118
|
| `corner-mark` | Corner registration marks. Alias of `border-strong`. | `border-strong` | `border-strong` |
|
|
108
|
-
| `hatch` | Hatch stripe over a region that is not data. | `
|
|
109
|
-
| `accent-surface` | Faintest accent tint: row hover, ghost hover; never text. | `
|
|
110
|
-
| `accent-surface-hover` | Accent tint one step stronger; never text. | `
|
|
119
|
+
| `hatch` | Hatch stripe over a region that is not data. | `dark.700` | `neutral.300` |
|
|
120
|
+
| `accent-surface` | Faintest accent tint: row hover, ghost hover; never text. | `#2d2843` | `accent.50` |
|
|
121
|
+
| `accent-surface-hover` | Accent tint one step stronger; never text. | `#3a3356` | `accent.200` |
|
|
111
122
|
| `ring` | Focus ring, active drag handle. Alias of `focus`. | `focus` | `focus` |
|
|
112
|
-
| `link` | Inline and standalone links.
|
|
123
|
+
| `link` | Inline and standalone links. The light ink: alias of `accent.base`, text-eligible where the `primary` fill is not. | `accent.base` | `accent.base` |
|
|
113
124
|
| `shadow-hairline` | Shadow colour for a resting control's contact line. | `black` at 30% | `black` at 5% |
|
|
114
125
|
| `shadow-contact` | Shadow colour for separated and floating layers. | `black` at 40% | `black` at 8% |
|
|
115
126
|
|
|
@@ -132,23 +143,30 @@ terminals — which cannot follow `color-scheme`, so their text cannot either.
|
|
|
132
143
|
|
|
133
144
|
Use `foreground` for default reading, `muted-foreground` when content is
|
|
134
145
|
secondary but still needs normal-text contrast, and `subtle-foreground` only
|
|
135
|
-
for large or non-essential supporting copy. Use `primary` for the
|
|
136
|
-
drives the current task; use `
|
|
137
|
-
|
|
146
|
+
for large or non-essential supporting copy. Use `primary` for the fill of the
|
|
147
|
+
action that drives the current task; use `link` for anything that reads as a
|
|
148
|
+
link or an active indicator, because the fill is not text-eligible on dark.
|
|
149
|
+
Use `accent` to draw secondary attention without creating another primary
|
|
150
|
+
action.
|
|
138
151
|
|
|
139
152
|
The semantic aliases deliberately point at different primitives by theme.
|
|
140
153
|
Status primitives and `accent.base` are themselves mode-aware, so the same
|
|
141
154
|
semantic role preserves its meaning and contrast rather than preserving a
|
|
142
|
-
literal color.
|
|
155
|
+
literal color. `border-strong` and `subtle-foreground` sit on `dark.450` on
|
|
156
|
+
dark because `dark.500` misses the 3:1 non-text and large-text gates on
|
|
157
|
+
`dark.800` and `dark.700`; they share a step on dark exactly as they share
|
|
158
|
+
`neutral.500` on light.
|
|
143
159
|
|
|
144
160
|
## AA gate
|
|
145
161
|
|
|
146
162
|
`scripts/check-contrast.mjs` is the build-time AA gate. In both dark and light
|
|
147
163
|
themes it resolves the semantic aliases and checks:
|
|
148
164
|
|
|
149
|
-
- `foreground`, `muted-foreground`, `
|
|
165
|
+
- `foreground`, `muted-foreground`, `link`, `accent`, `success`, `warning`,
|
|
150
166
|
`danger`, and `info` at **4.5:1** or better against `background`, `surface`,
|
|
151
|
-
and `elevated-surface
|
|
167
|
+
and `elevated-surface`. `link` is gated, not `primary`: since the ink/fill
|
|
168
|
+
split, `primary` is the deep fill and is not text-eligible on dark, while
|
|
169
|
+
the ink that links and indicators actually draw with is `link`;
|
|
152
170
|
- `subtle-foreground` at **3:1** or better against those surfaces, restricting
|
|
153
171
|
it to large text and non-essential metadata;
|
|
154
172
|
- `border-strong` and `corner-mark` at **3:1** or better against those
|
package/kiso/ui.css
CHANGED
|
@@ -362,6 +362,117 @@ a:hover, .link:hover { text-decoration: underline; }
|
|
|
362
362
|
display: flex; align-items: center; gap: var(--spacing-xs);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
/* chip input: several structured values in one field */
|
|
366
|
+
.chip-input { position: relative; display: grid; gap: var(--spacing-xs); }
|
|
367
|
+
.chip-input-box {
|
|
368
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-xs);
|
|
369
|
+
width: 100%;
|
|
370
|
+
min-height: var(--size-control-md);
|
|
371
|
+
padding: var(--spacing-xs);
|
|
372
|
+
border: 1px solid var(--color-input);
|
|
373
|
+
border-radius: var(--radius-md);
|
|
374
|
+
background: var(--color-card);
|
|
375
|
+
box-shadow: var(--shadow-xs);
|
|
376
|
+
cursor: text;
|
|
377
|
+
transition: border-color var(--motion-duration-fast) var(--motion-easing-standard);
|
|
378
|
+
}
|
|
379
|
+
.chip-input-box:hover { border-color: var(--color-border-strong); }
|
|
380
|
+
.chip-input-box:focus-within { outline: 2px solid var(--color-ring); outline-offset: 2px; }
|
|
381
|
+
.chip-input-box[aria-invalid="true"] { border-color: var(--color-danger); }
|
|
382
|
+
.chip-input-box[aria-invalid="true"]:focus-within { outline-color: var(--color-danger); }
|
|
383
|
+
.chip-input-box[data-disabled="true"] {
|
|
384
|
+
background: var(--color-disabled-surface);
|
|
385
|
+
color: var(--color-disabled);
|
|
386
|
+
cursor: not-allowed;
|
|
387
|
+
}
|
|
388
|
+
.chip-input-field {
|
|
389
|
+
flex: 1 1 12ch; min-width: 12ch;
|
|
390
|
+
height: var(--size-control-sm);
|
|
391
|
+
padding-inline: var(--spacing-xs);
|
|
392
|
+
border: 0; background: none; color: inherit;
|
|
393
|
+
font: inherit; font-size: var(--type-size-body);
|
|
394
|
+
outline: none;
|
|
395
|
+
}
|
|
396
|
+
.chip-input-field::placeholder { color: var(--color-subtle-foreground); }
|
|
397
|
+
.chip-input-list {
|
|
398
|
+
position: absolute; inset-inline: 0; top: 100%; z-index: 40;
|
|
399
|
+
margin-block-start: var(--spacing-xs);
|
|
400
|
+
max-height: 240px; overflow-y: auto;
|
|
401
|
+
}
|
|
402
|
+
.chip-input-list .menu-item[aria-selected="true"] { background: var(--color-selected); color: var(--color-selected-foreground); }
|
|
403
|
+
.chip-input-list .menu-item .muted { margin-inline-start: auto; }
|
|
404
|
+
|
|
405
|
+
.chip {
|
|
406
|
+
display: inline-flex; align-items: stretch;
|
|
407
|
+
max-width: 100%;
|
|
408
|
+
min-height: var(--size-control-sm);
|
|
409
|
+
padding-inline-start: var(--spacing-sm);
|
|
410
|
+
padding-inline-end: var(--spacing-xs);
|
|
411
|
+
border: 1px solid transparent;
|
|
412
|
+
border-radius: var(--radius-sm);
|
|
413
|
+
background: var(--color-secondary);
|
|
414
|
+
color: var(--color-secondary-foreground);
|
|
415
|
+
font-size: var(--type-size-label);
|
|
416
|
+
line-height: 1;
|
|
417
|
+
}
|
|
418
|
+
.chip:has(.is-editing) { background: var(--color-card); border-color: var(--color-primary); }
|
|
419
|
+
.chip[data-state="invalid"] { background: var(--color-danger-surface); border-color: var(--color-danger-border); color: var(--color-danger); }
|
|
420
|
+
/* The chip reads left to right like a call: what installs it, what it is,
|
|
421
|
+
which version. Segments are divided the way .btn-group divides buttons. */
|
|
422
|
+
.chip-scope, .chip-name {
|
|
423
|
+
display: inline-flex; align-items: center;
|
|
424
|
+
padding-inline-end: var(--spacing-sm);
|
|
425
|
+
font-family: var(--font-mono); font-size: var(--type-size-metadata);
|
|
426
|
+
}
|
|
427
|
+
.chip-scope { color: var(--color-muted-foreground); }
|
|
428
|
+
.chip-scope + .chip-name {
|
|
429
|
+
padding-inline-start: var(--spacing-sm);
|
|
430
|
+
border-inline-start: 1px solid var(--color-border);
|
|
431
|
+
}
|
|
432
|
+
/* The version and each option are the segments people press, so they carry the
|
|
433
|
+
raised fill of a grouped button rather than waiting for a hover to admit
|
|
434
|
+
they are one. Every option gets its own rule, and a list keeps its brackets,
|
|
435
|
+
so name=value and name=[a, b] read as the same kind of fact. */
|
|
436
|
+
.chip-value, .chip-option {
|
|
437
|
+
display: inline-flex; align-items: center;
|
|
438
|
+
padding-inline: var(--spacing-sm);
|
|
439
|
+
border: 0; border-inline-start: 1px solid var(--color-border);
|
|
440
|
+
border-radius: 0;
|
|
441
|
+
background: var(--color-card);
|
|
442
|
+
font-family: var(--font-mono); font-size: var(--type-size-metadata);
|
|
443
|
+
cursor: pointer;
|
|
444
|
+
}
|
|
445
|
+
.chip-value { color: var(--color-link); }
|
|
446
|
+
.chip-option { color: var(--color-foreground); }
|
|
447
|
+
.chip-value:hover, .chip-option:hover { background: var(--color-accent-surface); }
|
|
448
|
+
.chip-option-name { color: var(--color-muted-foreground); }
|
|
449
|
+
.chip-option-mark { color: var(--color-subtle-foreground); }
|
|
450
|
+
.chip-option[data-empty="true"] { color: var(--color-subtle-foreground); }
|
|
451
|
+
.chip-value:last-child, .chip-option:last-child {
|
|
452
|
+
border-start-end-radius: var(--radius-xs);
|
|
453
|
+
border-end-end-radius: var(--radius-xs);
|
|
454
|
+
}
|
|
455
|
+
.chip-value.is-editing, .chip-option.is-editing {
|
|
456
|
+
min-width: 4ch;
|
|
457
|
+
background: var(--color-accent-surface);
|
|
458
|
+
outline: none;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.chip-action {
|
|
462
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-2xs);
|
|
463
|
+
align-self: center;
|
|
464
|
+
margin-inline-start: var(--spacing-xs);
|
|
465
|
+
min-width: var(--size-icon-sm); height: var(--size-icon-md);
|
|
466
|
+
padding-inline: var(--spacing-2xs);
|
|
467
|
+
border: 0; border-radius: var(--radius-xs);
|
|
468
|
+
background: none; color: var(--color-muted-foreground);
|
|
469
|
+
font: inherit; font-size: var(--type-size-metadata);
|
|
470
|
+
cursor: pointer;
|
|
471
|
+
}
|
|
472
|
+
.chip-action:hover { background: var(--color-accent-surface-hover); color: var(--color-foreground); }
|
|
473
|
+
.chip-action[aria-expanded="true"] { background: var(--color-accent-surface); color: var(--color-foreground); }
|
|
474
|
+
.chip-action .icon { width: var(--size-icon-sm); height: var(--size-icon-sm); }
|
|
475
|
+
|
|
365
476
|
/* checkbox / radio */
|
|
366
477
|
.check {
|
|
367
478
|
display: inline-flex;
|
|
@@ -731,6 +842,7 @@ table.table { width: 100%; border-collapse: collapse; font-size: var(--type-size
|
|
|
731
842
|
.pagination .btn[aria-current="page"] { background: var(--color-secondary); }
|
|
732
843
|
|
|
733
844
|
.app-shell { display: grid; grid-template-columns: var(--size-sidebar) minmax(0, 1fr); min-height: 100vh; }
|
|
845
|
+
.app-shell[data-layout="topbar"] { grid-template-columns: minmax(0, 1fr); }
|
|
734
846
|
.sidebar {
|
|
735
847
|
display: flex; flex-direction: column;
|
|
736
848
|
min-width: 0;
|
|
@@ -778,7 +890,7 @@ table.table { width: 100%; border-collapse: collapse; font-size: var(--type-size
|
|
|
778
890
|
inset-block: 2px;
|
|
779
891
|
width: 2px;
|
|
780
892
|
border-radius: var(--radius-full);
|
|
781
|
-
background: var(--color-
|
|
893
|
+
background: var(--color-link); /* the ink, not the primary fill: on dark the fill misses 3:1 against the sidebar */
|
|
782
894
|
}
|
|
783
895
|
.nav-item[aria-current="page"] .icon, .nav-item.active .icon { color: currentColor; }
|
|
784
896
|
.nav-item .badge { margin-inline-start: auto; }
|
|
@@ -912,6 +1024,10 @@ pre code { display: block; overflow-x: auto; padding: 0; background: none; borde
|
|
|
912
1024
|
min-height: var(--size-touch-min);
|
|
913
1025
|
}
|
|
914
1026
|
.btn-icon { min-width: var(--size-touch-min); }
|
|
1027
|
+
/* The chip grows with its segments, so the floor goes on the chip and the
|
|
1028
|
+
segments stretch to it. */
|
|
1029
|
+
.chip, .chip-input-field { min-height: var(--size-touch-min); }
|
|
1030
|
+
.chip-action { min-width: var(--size-touch-min); height: var(--size-touch-min); }
|
|
915
1031
|
}
|
|
916
1032
|
|
|
917
1033
|
@media (max-width: 1023px) {
|
|
@@ -1015,9 +1131,24 @@ pre::after {
|
|
|
1015
1131
|
.splitter::after { content: ""; position: absolute; inset-block: 0; inset-inline: -3px; }
|
|
1016
1132
|
.splitter:hover, .splitter.dragging { background: var(--color-ring); }
|
|
1017
1133
|
|
|
1018
|
-
/* Chart frame:
|
|
1134
|
+
/* Chart frame: deliberate presentation, one featured series in primary,
|
|
1135
|
+
gridlines at border strength. The area is the line color at low opacity, a
|
|
1136
|
+
flat token fill: no document-level gradient id for a component to forget
|
|
1137
|
+
to define. Supporting, incidental series belong in `.sparkline` instead. */
|
|
1019
1138
|
.chart { width: 100%; height: 200px; display: block; overflow: visible; color: var(--color-primary); }
|
|
1020
1139
|
.chart .grid { stroke: var(--color-border); stroke-width: 1; }
|
|
1021
|
-
.chart .line { fill: none; stroke:
|
|
1022
|
-
.chart .area { fill:
|
|
1140
|
+
.chart .line { fill: none; stroke: currentColor; stroke-width: 1.5; }
|
|
1141
|
+
.chart .area { fill: currentColor; fill-opacity: 0.12; }
|
|
1023
1142
|
.chart-axis { display: flex; justify-content: space-between; font-size: var(--type-size-metadata); color: var(--color-subtle-foreground); margin-block-start: var(--spacing-sm); }
|
|
1143
|
+
|
|
1144
|
+
/* Sparkline — one metric series at cell size (issue #84). No axis, no grid,
|
|
1145
|
+
no legend, no tooltip: the number beside the line carries the precision,
|
|
1146
|
+
the line carries the shape. Recharts draws the path; the frame owns only
|
|
1147
|
+
display, width, and the color tokens. Neutral is the voice of an
|
|
1148
|
+
incidental series in a table cell, `--color-border-strong` like `.bars`;
|
|
1149
|
+
`.primary` marks the series a tile is about, so at most one sibling
|
|
1150
|
+
raises its voice. Fewer than two samples draws nothing — a flat rule
|
|
1151
|
+
across a cell reads as a border, not as a measurement. */
|
|
1152
|
+
.sparkline { display: block; width: 100%; color: var(--color-border-strong); }
|
|
1153
|
+
.sparkline.primary { color: var(--color-primary); }
|
|
1154
|
+
.sparkline svg { display: block; overflow: visible; }
|
package/package.json
CHANGED
package/tokens/build/tokens.css
CHANGED
|
@@ -25,65 +25,77 @@
|
|
|
25
25
|
--color-accent-800: #46309e;
|
|
26
26
|
--color-accent-900: #2e2170;
|
|
27
27
|
--color-accent-950: #211741;
|
|
28
|
-
--color-accent-base: light-dark(#5b3fc4, #
|
|
29
|
-
--color-status-success: light-dark(#0c6b43, #
|
|
30
|
-
--color-status-warning: light-dark(#7a5000, #
|
|
31
|
-
--color-status-danger: light-dark(#ad2f29, #
|
|
32
|
-
--color-status-info: light-dark(#15559e, #
|
|
33
|
-
--color-
|
|
34
|
-
--color-
|
|
35
|
-
--color-
|
|
36
|
-
--color-
|
|
37
|
-
--color-
|
|
38
|
-
--color-
|
|
39
|
-
--color-
|
|
40
|
-
--color-
|
|
28
|
+
--color-accent-base: light-dark(#5b3fc4, #a793f4);
|
|
29
|
+
--color-status-success: light-dark(#0c6b43, #5bd295);
|
|
30
|
+
--color-status-warning: light-dark(#7a5000, #efc05b);
|
|
31
|
+
--color-status-danger: light-dark(#ad2f29, #f5807f);
|
|
32
|
+
--color-status-info: light-dark(#15559e, #75c4fa);
|
|
33
|
+
--color-dark-50: #f8f6f3;
|
|
34
|
+
--color-dark-100: #eeece9;
|
|
35
|
+
--color-dark-200: #d9d7d3;
|
|
36
|
+
--color-dark-300: #c6c4c0;
|
|
37
|
+
--color-dark-400: #aba9a5;
|
|
38
|
+
--color-dark-450: #94928e;
|
|
39
|
+
--color-dark-500: #6b6965;
|
|
40
|
+
--color-dark-600: #494744;
|
|
41
|
+
--color-dark-700: #3a3834;
|
|
42
|
+
--color-dark-800: #2d2b28;
|
|
43
|
+
--color-dark-900: #211f1c;
|
|
44
|
+
--color-dark-950: #151411;
|
|
45
|
+
--color-background: light-dark(var(--color-neutral-200), var(--color-dark-900));
|
|
46
|
+
--color-surface: light-dark(var(--color-neutral-100), var(--color-dark-800));
|
|
47
|
+
--color-elevated-surface: light-dark(var(--color-white), var(--color-dark-700));
|
|
48
|
+
--color-foreground: light-dark(var(--color-neutral-900), var(--color-dark-100));
|
|
49
|
+
--color-muted-foreground: light-dark(var(--color-neutral-600), var(--color-dark-400));
|
|
50
|
+
--color-subtle-foreground: light-dark(var(--color-neutral-500), var(--color-dark-450));
|
|
51
|
+
--color-border: light-dark(var(--color-neutral-300), var(--color-dark-600));
|
|
52
|
+
--color-primary: light-dark(#5b3fc4, #684bb5);
|
|
41
53
|
--color-accent: light-dark(var(--color-accent-800), var(--color-accent-300));
|
|
42
54
|
--color-success: var(--color-status-success);
|
|
43
55
|
--color-warning: var(--color-status-warning);
|
|
44
56
|
--color-danger: var(--color-status-danger);
|
|
45
57
|
--color-info: var(--color-status-info);
|
|
46
|
-
--color-focus:
|
|
47
|
-
--color-disabled: light-dark(var(--color-neutral-400), var(--color-
|
|
48
|
-
--color-card: light-dark(var(--color-neutral-100), var(--color-
|
|
58
|
+
--color-focus: var(--color-accent-base);
|
|
59
|
+
--color-disabled: light-dark(var(--color-neutral-400), var(--color-dark-600));
|
|
60
|
+
--color-card: light-dark(var(--color-neutral-100), var(--color-dark-800));
|
|
49
61
|
--color-card-foreground: var(--color-foreground);
|
|
50
|
-
--color-popover: light-dark(var(--color-white), var(--color-
|
|
62
|
+
--color-popover: light-dark(var(--color-white), var(--color-dark-700));
|
|
51
63
|
--color-popover-foreground: var(--color-foreground);
|
|
52
|
-
--color-muted: light-dark(var(--color-neutral-200), var(--color-
|
|
53
|
-
--color-sidebar: light-dark(var(--color-neutral-100), var(--color-
|
|
64
|
+
--color-muted: light-dark(var(--color-neutral-200), var(--color-dark-700));
|
|
65
|
+
--color-sidebar: light-dark(var(--color-neutral-100), var(--color-dark-950));
|
|
54
66
|
--color-sidebar-border: var(--color-border);
|
|
55
|
-
--color-border-strong: var(--color-neutral-500);
|
|
67
|
+
--color-border-strong: light-dark(var(--color-neutral-500), var(--color-dark-450));
|
|
56
68
|
--color-input: var(--color-border);
|
|
57
|
-
--color-primary-foreground: light-dark(var(--color-white),
|
|
58
|
-
--color-primary-hover: light-dark(var(--color-accent-800),
|
|
59
|
-
--color-link: var(--color-
|
|
60
|
-
--color-accent-surface: light-dark(var(--color-accent-50),
|
|
61
|
-
--color-accent-surface-hover: light-dark(var(--color-accent-200),
|
|
62
|
-
--color-selected: light-dark(var(--color-accent-300),
|
|
69
|
+
--color-primary-foreground: light-dark(var(--color-white), #f7f5fe);
|
|
70
|
+
--color-primary-hover: light-dark(var(--color-accent-800), #795dc9);
|
|
71
|
+
--color-link: var(--color-accent-base);
|
|
72
|
+
--color-accent-surface: light-dark(var(--color-accent-50), #2d2843);
|
|
73
|
+
--color-accent-surface-hover: light-dark(var(--color-accent-200), #3a3356);
|
|
74
|
+
--color-selected: light-dark(var(--color-accent-300), #49406d);
|
|
63
75
|
--color-selected-foreground: var(--color-foreground);
|
|
64
|
-
--color-secondary: light-dark(var(--color-neutral-300), var(--color-
|
|
76
|
+
--color-secondary: light-dark(var(--color-neutral-300), var(--color-dark-700));
|
|
65
77
|
--color-secondary-foreground: var(--color-foreground);
|
|
66
|
-
--color-secondary-hover: light-dark(var(--color-neutral-400), var(--color-
|
|
78
|
+
--color-secondary-hover: light-dark(var(--color-neutral-400), var(--color-dark-600));
|
|
67
79
|
--color-ring: var(--color-focus);
|
|
68
|
-
--color-disabled-surface: light-dark(var(--color-neutral-200), var(--color-
|
|
69
|
-
--color-skeleton: light-dark(var(--color-neutral-300), var(--color-
|
|
80
|
+
--color-disabled-surface: light-dark(var(--color-neutral-200), var(--color-dark-800));
|
|
81
|
+
--color-skeleton: light-dark(var(--color-neutral-300), var(--color-dark-700));
|
|
70
82
|
--color-overlay: light-dark(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6));
|
|
71
|
-
--color-hatch: light-dark(var(--color-neutral-300), var(--color-
|
|
83
|
+
--color-hatch: light-dark(var(--color-neutral-300), var(--color-dark-700));
|
|
72
84
|
--color-corner-mark: var(--color-border-strong);
|
|
73
85
|
--color-shadow-hairline: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.3));
|
|
74
86
|
--color-shadow-contact: light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.4));
|
|
75
|
-
--color-success-surface: light-dark(rgba(12, 107, 67, 0.1), rgba(
|
|
76
|
-
--color-success-border: light-dark(rgba(12, 107, 67, 0.3), rgba(
|
|
77
|
-
--color-warning-surface: light-dark(rgba(122, 80, 0, 0.1), rgba(
|
|
78
|
-
--color-warning-border: light-dark(rgba(122, 80, 0, 0.3), rgba(
|
|
79
|
-
--color-danger-surface: light-dark(rgba(173, 47, 41, 0.1), rgba(
|
|
80
|
-
--color-danger-border: light-dark(rgba(173, 47, 41, 0.3), rgba(
|
|
81
|
-
--color-info-surface: light-dark(rgba(21, 85, 158, 0.1), rgba(
|
|
82
|
-
--color-info-border: light-dark(rgba(21, 85, 158, 0.3), rgba(
|
|
83
|
-
--color-danger-foreground: light-dark(var(--color-white), var(--color-
|
|
84
|
-
--color-warning-on-dark: #
|
|
85
|
-
--color-danger-on-dark: #
|
|
86
|
-
--color-info-on-dark: #
|
|
87
|
+
--color-success-surface: light-dark(rgba(12, 107, 67, 0.1), rgba(91, 210, 149, 0.12));
|
|
88
|
+
--color-success-border: light-dark(rgba(12, 107, 67, 0.3), rgba(91, 210, 149, 0.35));
|
|
89
|
+
--color-warning-surface: light-dark(rgba(122, 80, 0, 0.1), rgba(239, 192, 91, 0.12));
|
|
90
|
+
--color-warning-border: light-dark(rgba(122, 80, 0, 0.3), rgba(239, 192, 91, 0.35));
|
|
91
|
+
--color-danger-surface: light-dark(rgba(173, 47, 41, 0.1), rgba(245, 128, 127, 0.12));
|
|
92
|
+
--color-danger-border: light-dark(rgba(173, 47, 41, 0.3), rgba(245, 128, 127, 0.35));
|
|
93
|
+
--color-info-surface: light-dark(rgba(21, 85, 158, 0.1), rgba(117, 196, 250, 0.12));
|
|
94
|
+
--color-info-border: light-dark(rgba(21, 85, 158, 0.3), rgba(117, 196, 250, 0.35));
|
|
95
|
+
--color-danger-foreground: light-dark(var(--color-white), var(--color-dark-950));
|
|
96
|
+
--color-warning-on-dark: #efc05b;
|
|
97
|
+
--color-danger-on-dark: #f5807f;
|
|
98
|
+
--color-info-on-dark: #75c4fa;
|
|
87
99
|
--font-heading: Inter, system-ui, sans-serif;
|
|
88
100
|
--font-body: Inter, system-ui, sans-serif;
|
|
89
101
|
--font-mono: JetBrains Mono, ui-monospace, monospace;
|
package/tokens/build/tokens.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const colorNeutral600: string;
|
|
|
17
17
|
export const colorNeutral700: string;
|
|
18
18
|
export const colorNeutral800: string;
|
|
19
19
|
export const colorNeutral900: string;
|
|
20
|
-
/** Darkest neutral.
|
|
20
|
+
/** Darkest neutral. Retained for compatibility; the dark theme's sidebar is dark.950. */
|
|
21
21
|
export const colorNeutral950: string;
|
|
22
22
|
/** Lightest lilac. Light-theme tinted surface (row hover, ghost hover). */
|
|
23
23
|
export const colorAccent50: string;
|
|
@@ -30,18 +30,40 @@ export const colorAccent600: string;
|
|
|
30
30
|
export const colorAccent700: string;
|
|
31
31
|
export const colorAccent800: string;
|
|
32
32
|
export const colorAccent900: string;
|
|
33
|
-
/** Darkest lilac.
|
|
33
|
+
/** Darkest lilac. Retained ramp step; the dark accent tints are dedicated literals on the semantic layer. */
|
|
34
34
|
export const colorAccent950: string;
|
|
35
|
-
/** Base accent primitive
|
|
35
|
+
/** Base accent primitive and the theme ink: deeper violet on light (6.36:1 on background), light lilac on dark (6.36:1 on background, 5.5:1 on surface). Flows into link and focus; the deep fill role lives on semantic.primary. */
|
|
36
36
|
export const colorAccentBase: string;
|
|
37
37
|
/** Success status primitive. */
|
|
38
38
|
export const colorStatusSuccess: string;
|
|
39
39
|
/** Warning status primitive. */
|
|
40
40
|
export const colorStatusWarning: string;
|
|
41
|
-
/** Danger status primitive. */
|
|
41
|
+
/** Danger status primitive. The dark step sits at 4.6:1 on elevated-surface; a darker red missed AA there. */
|
|
42
42
|
export const colorStatusDanger: string;
|
|
43
43
|
/** Info status primitive. */
|
|
44
44
|
export const colorStatusInfo: string;
|
|
45
|
+
/** Lightest dark-ramp step. */
|
|
46
|
+
export const colorDark50: string;
|
|
47
|
+
/** Foreground on dark. Text-eligible: passes WCAG AA 4.5:1 on every dark surface. */
|
|
48
|
+
export const colorDark100: string;
|
|
49
|
+
export const colorDark200: string;
|
|
50
|
+
export const colorDark300: string;
|
|
51
|
+
/** Muted foreground on dark. */
|
|
52
|
+
export const colorDark400: string;
|
|
53
|
+
/** Half step. Strong lines and subtle text share it on dark (border-strong, subtle-foreground), as neutral.500 does for both roles on light: dark.500 misses the 3:1 non-text gate on dark.800 and dark.700. */
|
|
54
|
+
export const colorDark450: string;
|
|
55
|
+
/** Free step between the 450 split and the border pair. Not referenced by the semantic layer: 2.6:1 on dark.800 fails the 3:1 non-text gate. */
|
|
56
|
+
export const colorDark500: string;
|
|
57
|
+
/** Borders and inputs on dark. */
|
|
58
|
+
export const colorDark600: string;
|
|
59
|
+
/** Elevated surfaces, muted and secondary fills on dark. */
|
|
60
|
+
export const colorDark700: string;
|
|
61
|
+
/** Surface and card on dark. */
|
|
62
|
+
export const colorDark800: string;
|
|
63
|
+
/** Background on dark. */
|
|
64
|
+
export const colorDark900: string;
|
|
65
|
+
/** Darkest step. Sidebar on dark, and danger-foreground on a danger fill. */
|
|
66
|
+
export const colorDark950: string;
|
|
45
67
|
/** App canvas. Surface role, never text. */
|
|
46
68
|
export const semanticBackground: string;
|
|
47
69
|
/** Cards, panels, table rows. Surface role. */
|
|
@@ -52,11 +74,11 @@ export const semanticElevatedSurface: string;
|
|
|
52
74
|
export const semanticForeground: string;
|
|
53
75
|
/** Secondary text, labels. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
|
|
54
76
|
export const semanticMutedForeground: string;
|
|
55
|
-
/** Placeholders, timestamps, hints. Large text and non-essential metadata only (>=3:1). Never body copy. */
|
|
77
|
+
/** Placeholders, timestamps, hints. Large text and non-essential metadata only (>=3:1). Never body copy. On dark this is dark.450, because dark.500 misses 3:1 on the dark surfaces. */
|
|
56
78
|
export const semanticSubtleForeground: string;
|
|
57
79
|
/** Dividers and input outlines. Non-text role. */
|
|
58
80
|
export const semanticBorder: string;
|
|
59
|
-
/** Primary
|
|
81
|
+
/** Primary fill: primary buttons, solid badges, checked controls, the brand mark. Deep violet in both themes; on dark it is a dedicated fill and is not text-eligible, so ink roles (link, focus) take accent.base instead. */
|
|
60
82
|
export const semanticPrimary: string;
|
|
61
83
|
/** Emphasis and highlight. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
|
|
62
84
|
export const semanticAccent: string;
|
|
@@ -68,7 +90,7 @@ export const semanticWarning: string;
|
|
|
68
90
|
export const semanticDanger: string;
|
|
69
91
|
/** Neutral informational state. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
|
|
70
92
|
export const semanticInfo: string;
|
|
71
|
-
/** Focus ring. Non-text role. >=3:1 against background. */
|
|
93
|
+
/** Focus ring. Non-text role. >=3:1 against background. The light ink: alias of accent.base in both themes. */
|
|
72
94
|
export const semanticFocus: string;
|
|
73
95
|
/** Disabled text and controls. Exempt from contrast minimums per WCAG 1.4.3. */
|
|
74
96
|
export const semanticDisabled: string;
|
|
@@ -86,15 +108,15 @@ export const semanticMuted: string;
|
|
|
86
108
|
export const semanticSidebar: string;
|
|
87
109
|
/** Divider between sidebar and content. */
|
|
88
110
|
export const semanticSidebarBorder: string;
|
|
89
|
-
/** Emphasised line: hover outlines, switch tracks, chart gridlines, corner marks. Non-text role, >=3:1 on background, surface and elevated-surface in both themes. */
|
|
111
|
+
/** Emphasised line: hover outlines, switch tracks, chart gridlines, corner marks. Non-text role, >=3:1 on background, surface and elevated-surface in both themes. On dark this is dark.450, sharing the subtle-text step as neutral.500 does on light. */
|
|
90
112
|
export const semanticBorderStrong: string;
|
|
91
113
|
/** Form control outline at rest. */
|
|
92
114
|
export const semanticInput: string;
|
|
93
|
-
/** Text and icons on a primary fill.
|
|
115
|
+
/** Text and icons on a primary fill. Near-white on the deep violet fill in both themes. Passes AA on primary in both themes. */
|
|
94
116
|
export const semanticPrimaryForeground: string;
|
|
95
|
-
/** Primary fill on hover
|
|
117
|
+
/** Primary fill on hover - one step brighter than primary, keeping primary-foreground legible. */
|
|
96
118
|
export const semanticPrimaryHover: string;
|
|
97
|
-
/** Inline and standalone links. */
|
|
119
|
+
/** Inline and standalone links. The light ink: alias of accent.base in both themes, so links stay text-eligible where the primary fill is not. */
|
|
98
120
|
export const semanticLink: string;
|
|
99
121
|
/** Faintest accent tint: row hover, ghost-button hover. Non-text role. */
|
|
100
122
|
export const semanticAccentSurface: string;
|
|
@@ -142,7 +164,7 @@ export const semanticDangerBorder: string;
|
|
|
142
164
|
export const semanticInfoSurface: string;
|
|
143
165
|
/** Info outline for badges and callouts. Non-text role. */
|
|
144
166
|
export const semanticInfoBorder: string;
|
|
145
|
-
/** Text and icons on a danger fill. Inverts with the fill, like primary-foreground. */
|
|
167
|
+
/** Text and icons on a danger fill. Inverts with the fill, like primary-foreground: white on the light-theme red, near-black on the dark-theme salmon. */
|
|
146
168
|
export const semanticDangerForeground: string;
|
|
147
169
|
/** Warning on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text. */
|
|
148
170
|
export const semanticWarningOnDark: string;
|