@hattisar/design-tokens 0.3.5 → 0.3.7
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/package.json +1 -1
- package/tokens.css +372 -122
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hattisar/design-tokens",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Shared design tokens (colors, type, radius, spacing, motion) and base primitives (reset, buttons, inputs, cards, modals, auth shell) for every Hattisar frontend.",
|
|
5
5
|
"main": "tokens.css",
|
|
6
6
|
"files": [
|
package/tokens.css
CHANGED
|
@@ -32,22 +32,49 @@
|
|
|
32
32
|
>=4.5:1) and for a real 3-step surface ladder — surface-tint used
|
|
33
33
|
to equal background-global exactly, which meant hover states had
|
|
34
34
|
no visible target to land on. */
|
|
35
|
-
--color-background-global: #
|
|
35
|
+
--color-background-global: #f4f4f5;
|
|
36
36
|
--color-background-card: #ffffff;
|
|
37
|
-
--color-surface-tint: #
|
|
38
|
-
--color-input: #
|
|
39
|
-
--color-text-primary: #
|
|
37
|
+
--color-surface-tint: #e8e8ea;
|
|
38
|
+
--color-input: #ffffff;
|
|
39
|
+
--color-text-primary: #09090b;
|
|
40
40
|
/* ~6.4:1 on card */
|
|
41
|
-
--color-text-secondary: #
|
|
41
|
+
--color-text-secondary: #52525b;
|
|
42
42
|
/* ~4.6:1 on card — was #8f99a8 at 2.9:1, failing AA body text */
|
|
43
|
-
--color-text-muted: #
|
|
43
|
+
--color-text-muted: #6b6b74;
|
|
44
44
|
/* Derived from text-primary's own RGB (28,33,39) rather than a
|
|
45
45
|
separately maintained literal, so border and text can never drift
|
|
46
46
|
apart the way they used to (border was rgb(17,20,24), a different
|
|
47
47
|
dark from text-primary's rgb(28,33,39)). */
|
|
48
|
-
--color-border:
|
|
49
|
-
--color-border-strong:
|
|
48
|
+
--color-border: #e4e4e7;
|
|
49
|
+
--color-border-strong: #d4d4d8;
|
|
50
|
+
|
|
51
|
+
/* Anything that floats over the page: dropdowns, menus, popovers,
|
|
52
|
+
modals, tooltips.
|
|
53
|
+
Reported: "we might need to add little background shadow for all
|
|
54
|
+
dropdown, popup so it differentiate from background. hard to view
|
|
55
|
+
now."
|
|
56
|
+
They all had a shadow already. The problem was that they were
|
|
57
|
+
painted --color-background-card and opened *over* cards — white on
|
|
58
|
+
white, separated by a 9%-alpha hairline and a shadow tuned so
|
|
59
|
+
softly it read as nothing. A surface only looks raised against
|
|
60
|
+
something.
|
|
61
|
+
So a floating surface gets its own tier: in dark it is a real step
|
|
62
|
+
above the card, and in light — where a popover over a white card
|
|
63
|
+
has nowhere lighter to go — the separation comes from a stronger
|
|
64
|
+
edge and a shadow with actual weight instead. */
|
|
65
|
+
--color-surface-raised: #ffffff;
|
|
66
|
+
--color-border-raised: #d4d4d8;
|
|
67
|
+
--shadow-popover: 0 4px 6px -2px rgba(9, 9, 11, 0.08), 0 12px 28px -8px rgba(9, 9, 11, 0.18);
|
|
68
|
+
|
|
50
69
|
--color-hover: var(--color-surface-tint);
|
|
70
|
+
--color-focus-ring: rgba(233, 235, 239, 0.2);
|
|
71
|
+
--color-focus-ring-danger: rgba(255, 138, 156, 0.24);
|
|
72
|
+
/* The ring drawn around a focused control. Ink at low alpha rather
|
|
73
|
+
than a saturated blue: this palette has no brand hue, and a blue
|
|
74
|
+
ring here would be the one place colour appeared without meaning
|
|
75
|
+
anything — the ten status tiers are what colour is for. */
|
|
76
|
+
--color-focus-ring: rgba(9, 9, 11, 0.14);
|
|
77
|
+
--color-focus-ring-danger: rgba(199, 10, 36, 0.2);
|
|
51
78
|
|
|
52
79
|
/* Named accents — the same 8 hues most of the status tiers below
|
|
53
80
|
already used, promoted into reusable tokens instead of each tier repeating
|
|
@@ -69,7 +96,7 @@
|
|
|
69
96
|
--accent-amber: #8a6600;
|
|
70
97
|
--accent-bronze: #a35200;
|
|
71
98
|
--accent-blue: #005bd3;
|
|
72
|
-
--accent-cyan: #
|
|
99
|
+
--accent-cyan: #0a6d99;
|
|
73
100
|
--accent-mint: #047b5d;
|
|
74
101
|
--accent-fuchsia: #984062;
|
|
75
102
|
|
|
@@ -119,28 +146,28 @@
|
|
|
119
146
|
computed. See StatusBadge.tsx for the status keyword -> tier
|
|
120
147
|
mapping — ten distinct hues, deliberately no collisions. */
|
|
121
148
|
--status-draft-text: var(--accent-slate);
|
|
122
|
-
--status-draft-bg: #
|
|
149
|
+
--status-draft-bg: #eef1f6;
|
|
123
150
|
--status-backordered-text: var(--accent-violet);
|
|
124
|
-
--status-backordered-bg: #
|
|
151
|
+
--status-backordered-bg: #f3ecfd;
|
|
125
152
|
--status-low-stock-text: var(--accent-amber);
|
|
126
|
-
--status-low-stock-bg: #
|
|
153
|
+
--status-low-stock-bg: #fff1d9;
|
|
127
154
|
/* Near-empty stock — more urgent than low-stock's reorder-point
|
|
128
155
|
nudge, not yet fully out. Bg sits between low-stock's punchy
|
|
129
156
|
amber-gold and out-of-stock's pale peach; text is --color-critical. */
|
|
130
157
|
--status-critical-stock-text: var(--color-critical);
|
|
131
|
-
--status-critical-stock-bg: #
|
|
158
|
+
--status-critical-stock-bg: #ffe7d1;
|
|
132
159
|
--status-out-of-stock-text: var(--accent-bronze);
|
|
133
|
-
--status-out-of-stock-bg: #
|
|
160
|
+
--status-out-of-stock-bg: #ffeee4;
|
|
134
161
|
--status-partially-fulfilled-text: var(--accent-blue);
|
|
135
|
-
--status-partially-fulfilled-bg: #
|
|
162
|
+
--status-partially-fulfilled-bg: #e8f1fe;
|
|
136
163
|
--status-shipped-text: var(--accent-cyan);
|
|
137
|
-
--status-shipped-bg: #
|
|
164
|
+
--status-shipped-bg: #ddf0fb;
|
|
138
165
|
--status-delivered-text: var(--accent-mint);
|
|
139
|
-
--status-delivered-bg: #
|
|
166
|
+
--status-delivered-bg: #e1f5e6;
|
|
140
167
|
--status-cancelled-text: var(--accent-fuchsia);
|
|
141
|
-
--status-cancelled-bg: #
|
|
168
|
+
--status-cancelled-bg: #fde7ee;
|
|
142
169
|
--status-overdue-text: var(--color-danger);
|
|
143
|
-
--status-overdue-bg: #
|
|
170
|
+
--status-overdue-bg: #feebec;
|
|
144
171
|
|
|
145
172
|
/* Page-level warning/danger/success banners — distinct from the row
|
|
146
173
|
badges above: a border plus a matching soft fill, no separate text
|
|
@@ -149,11 +176,11 @@
|
|
|
149
176
|
repeating their own literal hex) so they can never drift out of
|
|
150
177
|
sync the way they briefly did here. Success intentionally doesn't:
|
|
151
178
|
it was never meant to mirror --color-success, it's its own shade. */
|
|
152
|
-
--banner-warning-bg: #
|
|
179
|
+
--banner-warning-bg: #fff3e6;
|
|
153
180
|
--banner-warning-border: var(--color-warning);
|
|
154
|
-
--banner-danger-bg: #
|
|
181
|
+
--banner-danger-bg: #fdeaed;
|
|
155
182
|
--banner-danger-border: var(--color-danger);
|
|
156
|
-
--banner-success-bg: #
|
|
183
|
+
--banner-success-bg: #e2f7e6;
|
|
157
184
|
--banner-success-border: var(--color-success);
|
|
158
185
|
|
|
159
186
|
/* A restrained scale, not flat — every step deliberately small, since
|
|
@@ -165,17 +192,28 @@
|
|
|
165
192
|
remove-buttons inside chips, the settings toggle switch) — never
|
|
166
193
|
applied to a badge/tag/chip just for a rounder look; those use
|
|
167
194
|
--radius-sm like everything else (see .status-badge and friends). */
|
|
168
|
-
--radius-xs:
|
|
169
|
-
--radius-sm:
|
|
170
|
-
--radius-md:
|
|
171
|
-
--radius-lg:
|
|
172
|
-
--radius-xl:
|
|
195
|
+
--radius-xs: 5px;
|
|
196
|
+
--radius-sm: 7px;
|
|
197
|
+
--radius-md: 10px;
|
|
198
|
+
--radius-lg: 13px;
|
|
199
|
+
--radius-xl: 16px;
|
|
173
200
|
--radius-full: 9999px;
|
|
174
201
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
202
|
+
/* Depth, deliberately restrained. These were all `none`, which left
|
|
203
|
+
every surface separated by border alone — correct, legible, and
|
|
204
|
+
completely flat: a card, a dropdown and a modal all sat on the page
|
|
205
|
+
at exactly the same height. The 32 places already reaching for
|
|
206
|
+
these tokens were asking for a hierarchy the tokens declined to
|
|
207
|
+
give them.
|
|
208
|
+
Four tiers, each tied to a job: xs for a resting control, sm for a
|
|
209
|
+
card, md for anything that floats over the page (dropdowns,
|
|
210
|
+
popovers), lg for a modal. Tinted with the ink colour rather than
|
|
211
|
+
pure black so a shadow reads as the surface's own shade instead of
|
|
212
|
+
a grey smudge. */
|
|
213
|
+
--shadow-xs: 0 1px 2px rgba(9, 9, 11, 0.04);
|
|
214
|
+
--shadow-sm: 0 1px 2px rgba(9, 9, 11, 0.05), 0 2px 8px -3px rgba(9, 9, 11, 0.08);
|
|
215
|
+
--shadow-md: 0 2px 4px -1px rgba(9, 9, 11, 0.04), 0 8px 20px -4px rgba(9, 9, 11, 0.12);
|
|
216
|
+
--shadow-lg: 0 4px 8px -2px rgba(9, 9, 11, 0.06), 0 20px 48px -12px rgba(9, 9, 11, 0.22);
|
|
179
217
|
|
|
180
218
|
/* One shared tier for every FloatingPanel-based dropdown/menu
|
|
181
219
|
(row actions, column picker, combobox, select, table-actions,
|
|
@@ -204,18 +242,40 @@
|
|
|
204
242
|
--space-8: 32px;
|
|
205
243
|
--space-10: 40px;
|
|
206
244
|
--space-12: 48px;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
245
|
+
/* Two steps past the old ceiling, for the gaps that separate whole
|
|
246
|
+
regions of a page rather than elements inside one. Without them
|
|
247
|
+
every large gap in the app was a hardcoded number, and they were
|
|
248
|
+
all slightly different. */
|
|
249
|
+
/* The narrowest a form field may be before it stops being readable.
|
|
250
|
+
The document forms laid fields out as `flex: 1; minWidth: 140`,
|
|
251
|
+
which on a wide screen fits six controls onto one row at about
|
|
252
|
+
130px each — narrow enough to clip "Customer's own reference"
|
|
253
|
+
mid-word. Every form field grid is built on this instead. */
|
|
254
|
+
--field-min-width: 260px;
|
|
255
|
+
/* For a control whose value is a word, not a sentence — a variant
|
|
256
|
+
name, a unit, a percentage — inside a line-item row. */
|
|
257
|
+
--field-min-width-narrow: 140px;
|
|
258
|
+
|
|
259
|
+
--space-16: 64px;
|
|
260
|
+
--space-20: 80px;
|
|
261
|
+
|
|
262
|
+
/* Type scale. The old one grew out of whatever each screen happened
|
|
263
|
+
to be using — 13.5, 14.5, 15 — three steps inside a point and a
|
|
264
|
+
half of each other, which is not a scale, it is three sizes that
|
|
265
|
+
could not agree. This is a proper ~1.2 ratio off a 14px body, so
|
|
266
|
+
the steps are actually distinguishable and a heading reads as a
|
|
267
|
+
heading rather than as slightly larger body text.
|
|
268
|
+
Body moves 14.5 -> 14: at Inter's x-height 14px is the more
|
|
269
|
+
comfortable long-session size, and it buys back the density the
|
|
270
|
+
roomier spacing below spends. */
|
|
271
|
+
--text-xs: 11.5px;
|
|
272
|
+
--text-sm: 13px;
|
|
273
|
+
--text-base: 14px;
|
|
214
274
|
--text-md: 15px;
|
|
215
275
|
--text-lg: 17px;
|
|
216
|
-
--text-xl:
|
|
217
|
-
--text-2xl:
|
|
218
|
-
--text-3xl:
|
|
276
|
+
--text-xl: 20px;
|
|
277
|
+
--text-2xl: 24px;
|
|
278
|
+
--text-3xl: 30px;
|
|
219
279
|
|
|
220
280
|
/* A real weight scale — headings, card titles, and KPI/stat values
|
|
221
281
|
use these to stand apart from the body text around them; dense
|
|
@@ -244,10 +304,10 @@
|
|
|
244
304
|
--hd-card-bg as every card (bordered, not tonally separate),
|
|
245
305
|
mirrored by dark theme's own literal Hattisar Design values below. */
|
|
246
306
|
--sidebar-bg: var(--color-background-card);
|
|
247
|
-
--sidebar-text:
|
|
248
|
-
--sidebar-text-muted:
|
|
307
|
+
--sidebar-text: var(--color-text-primary);
|
|
308
|
+
--sidebar-text-muted: var(--color-text-muted);
|
|
249
309
|
--sidebar-active-bg: var(--color-surface-tint);
|
|
250
|
-
--sidebar-active-text:
|
|
310
|
+
--sidebar-active-text: var(--color-text-primary);
|
|
251
311
|
--sidebar-hover-bg: var(--color-surface-tint);
|
|
252
312
|
/* Icon tint for the active nav row — not a full fill, so
|
|
253
313
|
--sidebar-active-bg/text above stay the "you're here" pill and
|
|
@@ -289,22 +349,27 @@
|
|
|
289
349
|
sits below the hover/tint tier. Modest steps between each (not a
|
|
290
350
|
dramatic jump) plus real borders is what keeps a dark theme
|
|
291
351
|
reading crisp instead of washed out — see --color-border below. */
|
|
292
|
-
--color-background-global: #
|
|
293
|
-
--color-background-card: #
|
|
294
|
-
--color-surface-tint: #
|
|
352
|
+
--color-background-global: #09090b;
|
|
353
|
+
--color-background-card: #18181b;
|
|
354
|
+
--color-surface-tint: #27272a;
|
|
295
355
|
--color-input: var(--color-background-card);
|
|
296
356
|
/* Not pure white — pure white text on a near-black surface causes
|
|
297
357
|
halation (a glow/vibrate effect on the eye over long sessions).
|
|
298
358
|
~92% white instead. */
|
|
299
|
-
--color-text-primary: #
|
|
359
|
+
--color-text-primary: #fafafa;
|
|
300
360
|
/* ~7:1 on card */
|
|
301
|
-
--color-text-secondary: #
|
|
361
|
+
--color-text-secondary: #d4d4d8;
|
|
302
362
|
/* ~4.7:1 on card — was #5f6b7c at 2.7:1, failing AA body text */
|
|
303
|
-
--color-text-muted: #
|
|
363
|
+
--color-text-muted: #a1a1aa;
|
|
304
364
|
/* Derived from text-primary's own RGB (233,233,236) — same
|
|
305
365
|
single-source-of-truth reasoning as :root's own --color-border. */
|
|
306
|
-
--color-border:
|
|
307
|
-
--color-border-strong:
|
|
366
|
+
--color-border: #27272a;
|
|
367
|
+
--color-border-strong: #3f3f46;
|
|
368
|
+
/* A genuine step above the card, which is what makes a dropdown read
|
|
369
|
+
as floating rather than pasted on. */
|
|
370
|
+
--color-surface-raised: #1f1f23;
|
|
371
|
+
--color-border-raised: #3f3f46;
|
|
372
|
+
--shadow-popover: 0 4px 6px -2px rgba(0, 0, 0, 0.5), 0 16px 40px -10px rgba(0, 0, 0, 0.75);
|
|
308
373
|
--color-hover: var(--color-surface-tint);
|
|
309
374
|
|
|
310
375
|
/* Brighter counterparts of :root's accent set — a dark badge fill
|
|
@@ -317,7 +382,7 @@
|
|
|
317
382
|
--accent-violet: #d4ccff;
|
|
318
383
|
--accent-amber: #ffc879;
|
|
319
384
|
--accent-bronze: #ffab73;
|
|
320
|
-
--accent-blue: #
|
|
385
|
+
--accent-blue: #7ba9ff;
|
|
321
386
|
--accent-cyan: #91d0ff;
|
|
322
387
|
--accent-mint: #2ced83;
|
|
323
388
|
--accent-fuchsia: #ff9dbd;
|
|
@@ -329,10 +394,10 @@
|
|
|
329
394
|
--color-brand: var(--color-text-primary);
|
|
330
395
|
--color-brand-hover: color-mix(in srgb, var(--color-brand) 90%, black);
|
|
331
396
|
--color-brand-active: color-mix(in srgb, var(--color-brand) 75%, black);
|
|
332
|
-
--color-brand-text: #
|
|
397
|
+
--color-brand-text: #09090b;
|
|
333
398
|
|
|
334
|
-
--shadow-btn-primary:
|
|
335
|
-
--shadow-btn-secondary:
|
|
399
|
+
--shadow-btn-primary: 0 1px 2px rgba(9, 9, 11, 0.12);
|
|
400
|
+
--shadow-btn-secondary: 0 1px 2px rgba(9, 9, 11, 0.05);
|
|
336
401
|
|
|
337
402
|
--color-secondary-fill: var(--color-surface-tint);
|
|
338
403
|
--color-secondary-border: var(--color-border-strong);
|
|
@@ -350,44 +415,51 @@
|
|
|
350
415
|
--color-success: var(--accent-mint);
|
|
351
416
|
|
|
352
417
|
--status-draft-text: var(--accent-slate);
|
|
353
|
-
--status-draft-bg: #
|
|
418
|
+
--status-draft-bg: #262a31;
|
|
354
419
|
--status-backordered-text: var(--accent-violet);
|
|
355
|
-
--status-backordered-bg: #
|
|
420
|
+
--status-backordered-bg: #312747;
|
|
356
421
|
--status-low-stock-text: var(--accent-amber);
|
|
357
|
-
--status-low-stock-bg: #
|
|
422
|
+
--status-low-stock-bg: #3f3411;
|
|
358
423
|
--status-critical-stock-text: var(--color-critical);
|
|
359
|
-
--status-critical-stock-bg: #
|
|
424
|
+
--status-critical-stock-bg: #452c12;
|
|
360
425
|
--status-out-of-stock-text: var(--accent-bronze);
|
|
361
|
-
--status-out-of-stock-bg: #
|
|
426
|
+
--status-out-of-stock-bg: #452916;
|
|
362
427
|
--status-partially-fulfilled-text: var(--accent-blue);
|
|
363
|
-
--status-partially-fulfilled-bg: #
|
|
428
|
+
--status-partially-fulfilled-bg: #1b2740;
|
|
364
429
|
--status-shipped-text: var(--accent-cyan);
|
|
365
|
-
--status-shipped-bg: #
|
|
430
|
+
--status-shipped-bg: #0f3341;
|
|
366
431
|
--status-delivered-text: var(--accent-mint);
|
|
367
|
-
--status-delivered-bg: #
|
|
432
|
+
--status-delivered-bg: #133620;
|
|
368
433
|
--status-cancelled-text: var(--accent-fuchsia);
|
|
369
|
-
--status-cancelled-bg: #
|
|
434
|
+
--status-cancelled-bg: #421f2f;
|
|
370
435
|
--status-overdue-text: var(--color-danger);
|
|
371
|
-
--status-overdue-bg: #
|
|
436
|
+
--status-overdue-bg: #401d21;
|
|
372
437
|
|
|
373
|
-
--banner-warning-bg: #
|
|
438
|
+
--banner-warning-bg: #3a2907;
|
|
374
439
|
--banner-warning-border: var(--color-warning);
|
|
375
|
-
--banner-danger-bg: #
|
|
440
|
+
--banner-danger-bg: #4a1119;
|
|
376
441
|
--banner-danger-border: var(--color-danger);
|
|
377
|
-
--banner-success-bg: #
|
|
442
|
+
--banner-success-bg: #0a3129;
|
|
378
443
|
--banner-success-border: var(--color-success);
|
|
379
444
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
445
|
+
/* A dark surface cannot be lifted by a soft grey shadow — there is
|
|
446
|
+
nothing lighter behind it to catch the light. So these go darker
|
|
447
|
+
and deeper than their light-theme counterparts, which is what
|
|
448
|
+
actually reads as elevation on a near-black page. */
|
|
449
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.32);
|
|
450
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px -3px rgba(0, 0, 0, 0.5);
|
|
451
|
+
--shadow-md: 0 2px 4px -1px rgba(0, 0, 0, 0.4), 0 8px 22px -4px rgba(0, 0, 0, 0.6);
|
|
452
|
+
--shadow-lg: 0 4px 8px -2px rgba(0, 0, 0, 0.45), 0 24px 56px -12px rgba(0, 0, 0, 0.75);
|
|
453
|
+
|
|
454
|
+
/* A distinct chrome rail, still — but a step *above* the canvas now
|
|
455
|
+
rather than below it. It used to be true black under a #0d0f13
|
|
456
|
+
page, which read as a rail. Against zinc-950 the same black is
|
|
457
|
+
three values away from the page and the rail disappeared, so the
|
|
458
|
+
separation now comes from the other direction: zinc-900, the same
|
|
459
|
+
surface as a card, which is where shadcn puts a sidebar too.
|
|
460
|
+
The frame still reads as structurally separate from content, which
|
|
461
|
+
was the point of the original choice. */
|
|
462
|
+
--sidebar-bg: #18181b;
|
|
391
463
|
--sidebar-text: var(--color-text-primary);
|
|
392
464
|
--sidebar-text-muted: var(--color-text-muted);
|
|
393
465
|
/* Two distinct white-overlay intensities on top of --sidebar-bg —
|
|
@@ -398,7 +470,7 @@
|
|
|
398
470
|
--sidebar-active-text: var(--color-text-primary);
|
|
399
471
|
--sidebar-hover-bg: rgba(255, 255, 255, 0.06);
|
|
400
472
|
--sidebar-active-icon: var(--sidebar-active-text);
|
|
401
|
-
--sidebar-rail-bg: #
|
|
473
|
+
--sidebar-rail-bg: #18181b;
|
|
402
474
|
|
|
403
475
|
color-scheme: dark;
|
|
404
476
|
}
|
|
@@ -409,15 +481,18 @@
|
|
|
409
481
|
takes the nearest declaration in the tree, so relying on :root
|
|
410
482
|
alone would lose to a dark attribute set higher up. */
|
|
411
483
|
[data-theme="light"] {
|
|
412
|
-
--color-background-global: #
|
|
484
|
+
--color-background-global: #f4f4f5;
|
|
413
485
|
--color-background-card: #ffffff;
|
|
414
|
-
--color-surface-tint: #
|
|
415
|
-
--color-input: #
|
|
416
|
-
--color-text-primary: #
|
|
417
|
-
--color-text-secondary: #
|
|
418
|
-
--color-text-muted: #
|
|
419
|
-
--color-border:
|
|
420
|
-
--color-border-strong:
|
|
486
|
+
--color-surface-tint: #e8e8ea;
|
|
487
|
+
--color-input: #ffffff;
|
|
488
|
+
--color-text-primary: #09090b;
|
|
489
|
+
--color-text-secondary: #52525b;
|
|
490
|
+
--color-text-muted: #6b6b74;
|
|
491
|
+
--color-border: #e4e4e7;
|
|
492
|
+
--color-border-strong: #d4d4d8;
|
|
493
|
+
--color-surface-raised: #ffffff;
|
|
494
|
+
--color-border-raised: #d4d4d8;
|
|
495
|
+
--shadow-popover: 0 4px 6px -2px rgba(9, 9, 11, 0.08), 0 12px 28px -8px rgba(9, 9, 11, 0.18);
|
|
421
496
|
--color-hover: var(--color-surface-tint);
|
|
422
497
|
|
|
423
498
|
--color-brand: var(--color-text-primary);
|
|
@@ -425,8 +500,8 @@
|
|
|
425
500
|
--color-brand-active: color-mix(in srgb, var(--color-brand) 70%, black);
|
|
426
501
|
--color-brand-text: #ffffff;
|
|
427
502
|
|
|
428
|
-
--shadow-btn-primary:
|
|
429
|
-
--shadow-btn-secondary:
|
|
503
|
+
--shadow-btn-primary: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
504
|
+
--shadow-btn-secondary: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
430
505
|
|
|
431
506
|
--color-secondary-fill: var(--color-surface-tint);
|
|
432
507
|
--color-secondary-border: var(--color-border-strong);
|
|
@@ -443,43 +518,49 @@
|
|
|
443
518
|
--color-success: #047b5d;
|
|
444
519
|
|
|
445
520
|
--status-draft-text: var(--accent-slate);
|
|
446
|
-
--status-draft-bg: #
|
|
521
|
+
--status-draft-bg: #eef1f6;
|
|
447
522
|
--status-backordered-text: var(--accent-violet);
|
|
448
|
-
--status-backordered-bg: #
|
|
523
|
+
--status-backordered-bg: #f3ecfd;
|
|
449
524
|
--status-low-stock-text: var(--accent-amber);
|
|
450
|
-
--status-low-stock-bg: #
|
|
525
|
+
--status-low-stock-bg: #fff1d9;
|
|
451
526
|
--status-critical-stock-text: var(--color-critical);
|
|
452
|
-
--status-critical-stock-bg: #
|
|
527
|
+
--status-critical-stock-bg: #ffe7d1;
|
|
453
528
|
--status-out-of-stock-text: var(--accent-bronze);
|
|
454
|
-
--status-out-of-stock-bg: #
|
|
529
|
+
--status-out-of-stock-bg: #ffeee4;
|
|
455
530
|
--status-partially-fulfilled-text: var(--accent-blue);
|
|
456
|
-
--status-partially-fulfilled-bg: #
|
|
531
|
+
--status-partially-fulfilled-bg: #e8f1fe;
|
|
457
532
|
--status-shipped-text: var(--accent-cyan);
|
|
458
|
-
--status-shipped-bg: #
|
|
533
|
+
--status-shipped-bg: #ddf0fb;
|
|
459
534
|
--status-delivered-text: var(--accent-mint);
|
|
460
|
-
--status-delivered-bg: #
|
|
535
|
+
--status-delivered-bg: #e1f5e6;
|
|
461
536
|
--status-cancelled-text: var(--accent-fuchsia);
|
|
462
|
-
--status-cancelled-bg: #
|
|
537
|
+
--status-cancelled-bg: #fde7ee;
|
|
463
538
|
--status-overdue-text: var(--color-danger);
|
|
464
|
-
--status-overdue-bg: #
|
|
539
|
+
--status-overdue-bg: #feebec;
|
|
465
540
|
|
|
466
|
-
--banner-warning-bg: #
|
|
541
|
+
--banner-warning-bg: #fff3e6;
|
|
467
542
|
--banner-warning-border: var(--color-warning);
|
|
468
|
-
--banner-danger-bg: #
|
|
543
|
+
--banner-danger-bg: #fdeaed;
|
|
469
544
|
--banner-danger-border: var(--color-danger);
|
|
470
|
-
--banner-success-bg: #
|
|
545
|
+
--banner-success-bg: #e2f7e6;
|
|
471
546
|
--banner-success-border: #047b5d;
|
|
472
547
|
|
|
548
|
+
/* Deliberately still flat, unlike :root's. The only thing that pins
|
|
549
|
+
itself to light is a print page, and a shadow on paper is a grey
|
|
550
|
+
smear. This block exists to restate the light palette inside a dark
|
|
551
|
+
ancestor — it is a copy of :root's values by necessity (custom
|
|
552
|
+
properties cannot be re-pointed at another block), so anything
|
|
553
|
+
changed above has to be changed here too. */
|
|
473
554
|
--shadow-xs: none;
|
|
474
555
|
--shadow-sm: none;
|
|
475
556
|
--shadow-md: none;
|
|
476
557
|
--shadow-lg: none;
|
|
477
558
|
|
|
478
559
|
--sidebar-bg: var(--color-background-card);
|
|
479
|
-
--sidebar-text: #
|
|
480
|
-
--sidebar-text-muted:
|
|
560
|
+
--sidebar-text: #09090b;
|
|
561
|
+
--sidebar-text-muted: var(--color-text-muted);
|
|
481
562
|
--sidebar-active-bg: var(--color-surface-tint);
|
|
482
|
-
--sidebar-active-text:
|
|
563
|
+
--sidebar-active-text: var(--color-text-primary);
|
|
483
564
|
--sidebar-hover-bg: var(--color-surface-tint);
|
|
484
565
|
--sidebar-active-icon: var(--sidebar-active-text);
|
|
485
566
|
--sidebar-rail-bg: var(--color-background-card);
|
|
@@ -600,9 +681,24 @@ td a:hover {
|
|
|
600
681
|
font-weight: var(--font-weight-semibold);
|
|
601
682
|
}
|
|
602
683
|
|
|
684
|
+
/* A label should recede so the value it names is what gets scanned.
|
|
685
|
+
Medium weight at a step down from body, in secondary ink — quieter
|
|
686
|
+
than the data, still comfortably above AA. */
|
|
603
687
|
label {
|
|
604
688
|
font-size: var(--text-sm);
|
|
605
|
-
font-weight:
|
|
689
|
+
font-weight: var(--font-weight-medium);
|
|
690
|
+
color: var(--color-text-secondary);
|
|
691
|
+
line-height: 1.4;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/* A required field says so once, here, rather than each form inventing
|
|
695
|
+
its own asterisk-or-not. Marked on the label so it reads before the
|
|
696
|
+
field rather than after it. */
|
|
697
|
+
label[data-required]::after {
|
|
698
|
+
content: "*";
|
|
699
|
+
margin-left: 3px;
|
|
700
|
+
color: var(--color-danger);
|
|
701
|
+
font-weight: var(--font-weight-regular);
|
|
606
702
|
}
|
|
607
703
|
|
|
608
704
|
/* Primary action — highest-contrast neutral fill (--color-brand),
|
|
@@ -717,14 +813,24 @@ button:focus-visible {
|
|
|
717
813
|
background: var(--color-destructive-hover);
|
|
718
814
|
}
|
|
719
815
|
|
|
816
|
+
/* font-family is inherited; size, weight and colour are stated.
|
|
817
|
+
`font: inherit` alone meant a field took its type from whatever
|
|
818
|
+
wrapped it — and most fields in this app are wrapped in their own
|
|
819
|
+
<label>, which is a step smaller and now medium weight. So a field
|
|
820
|
+
inside a label rendered at 13px and one outside at 14px, and the
|
|
821
|
+
label change would have made the wrapped ones semibold. The value a
|
|
822
|
+
person typed should look the same everywhere it appears. */
|
|
720
823
|
input,
|
|
721
824
|
select,
|
|
722
825
|
textarea {
|
|
723
|
-
font: inherit;
|
|
826
|
+
font-family: inherit;
|
|
827
|
+
font-size: var(--text-base);
|
|
828
|
+
font-weight: var(--font-weight-regular);
|
|
829
|
+
line-height: 1.4;
|
|
724
830
|
width: 100%;
|
|
725
|
-
padding:
|
|
726
|
-
margin-top:
|
|
727
|
-
border: 1px solid var(--color-border);
|
|
831
|
+
padding: 8px 12px;
|
|
832
|
+
margin-top: 6px;
|
|
833
|
+
border: 1px solid var(--color-border-strong);
|
|
728
834
|
border-radius: var(--radius-sm);
|
|
729
835
|
background: var(--color-input);
|
|
730
836
|
color: var(--color-text-primary);
|
|
@@ -733,6 +839,11 @@ textarea {
|
|
|
733
839
|
box-shadow 0.15s ease;
|
|
734
840
|
}
|
|
735
841
|
|
|
842
|
+
input::placeholder,
|
|
843
|
+
textarea::placeholder {
|
|
844
|
+
color: var(--color-text-muted);
|
|
845
|
+
}
|
|
846
|
+
|
|
736
847
|
/* The browser's native select arrow renders at its own baseline/size
|
|
737
848
|
per-OS (a mismatched-color chevron and off-center text) —
|
|
738
849
|
appearance:none strips it entirely and this draws a fixed, always-
|
|
@@ -740,21 +851,135 @@ textarea {
|
|
|
740
851
|
bottom padding is what actually centers the selected value; the
|
|
741
852
|
native arrow's box was throwing that off by consuming space
|
|
742
853
|
asymmetrically. The open option list itself stays OS-native — no
|
|
743
|
-
browser lets CSS reach inside it.
|
|
744
|
-
|
|
854
|
+
browser lets CSS reach inside it.
|
|
855
|
+
|
|
856
|
+
`input[list]` gets the same chevron. It opens a dropdown of
|
|
857
|
+
suggestions exactly as a select does, but Chrome draws no affordance
|
|
858
|
+
for one at all — so a datalist field read as a plain text box while
|
|
859
|
+
sitting between two selects that both had an arrow. The difference
|
|
860
|
+
that remains, that you may also type a value not on the list, is the
|
|
861
|
+
right one to leave invisible: typing into a text box is
|
|
862
|
+
self-evidently allowed. */
|
|
863
|
+
/* ------------------------------------------------------------------
|
|
864
|
+
Choosing one of something: four controls, one appearance.
|
|
865
|
+
|
|
866
|
+
They are four because they do different jobs, and collapsing them
|
|
867
|
+
would cost something real each time:
|
|
868
|
+
|
|
869
|
+
select a closed set small enough to scroll. Cheap,
|
|
870
|
+
native, keyboard- and mobile-friendly.
|
|
871
|
+
Combobox a closed set too large to scroll — there are
|
|
872
|
+
4,021 customers here, and a <select> of 4,021
|
|
873
|
+
options is not a control, it is a punishment.
|
|
874
|
+
input[type=date] a date. The native one carries the OS picker,
|
|
875
|
+
typed entry, locale order and mobile wheels;
|
|
876
|
+
a hand-built calendar loses all four and is a
|
|
877
|
+
well-known accessibility swamp.
|
|
878
|
+
|
|
879
|
+
Which to reach for:
|
|
880
|
+
under ~15 fixed options, closed -> select
|
|
881
|
+
anything else -> Combobox
|
|
882
|
+
|
|
883
|
+
Not input[list]. A datalist accepts typed values, which is why it
|
|
884
|
+
looked like the right control for an open set — but its suggestion
|
|
885
|
+
popup is drawn by the browser and no CSS reaches inside it, so the
|
|
886
|
+
field opens looking like nothing else on the page. Combobox's
|
|
887
|
+
`onCreate` covers the open set with the app's own dropdown: where
|
|
888
|
+
there is no table to insert into, "creating" is just accepting the
|
|
889
|
+
typed text (see ChannelSelect, VariantSelect).
|
|
890
|
+
|
|
891
|
+
What they share is the mark at the right-hand end that says "this
|
|
892
|
+
opens something", and it is the same mark in the same place on all
|
|
893
|
+
of them. It was not: the Combobox had none at all, so a customer or
|
|
894
|
+
a warehouse read as a plain text box beside fields that plainly
|
|
895
|
+
opened.
|
|
896
|
+
------------------------------------------------------------------ */
|
|
897
|
+
select,
|
|
898
|
+
.combobox-input {
|
|
745
899
|
appearance: none;
|
|
746
900
|
-webkit-appearance: none;
|
|
747
|
-
line-height: normal;
|
|
748
901
|
padding-right: 32px;
|
|
749
902
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23727676' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
750
903
|
background-repeat: no-repeat;
|
|
751
904
|
background-position: right 10px center;
|
|
752
905
|
}
|
|
753
906
|
|
|
754
|
-
/*
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
907
|
+
/* Only a native select needs this, and sharing it was a bug worth
|
|
908
|
+
recording. `line-height: normal` is here to re-centre a select's
|
|
909
|
+
text after appearance:none strips the native box — a select does not
|
|
910
|
+
honour the 1.4 the other fields use. A .combobox-input is a real
|
|
911
|
+
<input> and does, so inheriting `normal` (about 1.2) made it roughly
|
|
912
|
+
three pixels shorter than the text field and the date field beside
|
|
913
|
+
it: same padding, same border, visibly smaller box.
|
|
914
|
+
|
|
915
|
+
The chevron above is what the two share. The line-height is not. */
|
|
916
|
+
select {
|
|
917
|
+
line-height: normal;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/* A date field's calendar button is drawn by the browser, not by us —
|
|
921
|
+
it cannot be swapped for the chevron above, but it can be given the
|
|
922
|
+
same weight and the same position, so a row of dates and selects
|
|
923
|
+
reads as one row of controls rather than two kinds. Sized and dimmed
|
|
924
|
+
to match; `color-scheme` (set on :root and both themes) is what
|
|
925
|
+
makes it and the popup follow the theme. */
|
|
926
|
+
input[type="date"]::-webkit-calendar-picker-indicator {
|
|
927
|
+
opacity: 0.55;
|
|
928
|
+
cursor: pointer;
|
|
929
|
+
padding: 0;
|
|
930
|
+
margin-right: 2px;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
|
934
|
+
opacity: 1;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/* A button that carries no button chrome — a chevron toggle, a
|
|
938
|
+
"Forgot password?" link, an inline remove. Seven of these were made
|
|
939
|
+
bare by hand with `style={{ background: "none", border: "none",
|
|
940
|
+
padding: 0 }}`, and every one of them missed the same thing: the
|
|
941
|
+
bare `button` rule above also sets `box-shadow:
|
|
942
|
+
var(--shadow-btn-primary)`, which an inline background and border
|
|
943
|
+
do not clear.
|
|
944
|
+
That was invisible while every --shadow-* token was `none`. Turning
|
|
945
|
+
them on gave each of these buttons a floating rectangle its author
|
|
946
|
+
had explicitly tried to remove — the "extra layer" behind
|
|
947
|
+
"Additional information". Declared once here so the next one cannot
|
|
948
|
+
miss it. */
|
|
949
|
+
.btn-bare {
|
|
950
|
+
background: none;
|
|
951
|
+
border: none;
|
|
952
|
+
box-shadow: none;
|
|
953
|
+
padding: 0;
|
|
954
|
+
font: inherit;
|
|
955
|
+
color: inherit;
|
|
956
|
+
cursor: pointer;
|
|
957
|
+
text-align: inherit;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.btn-bare:hover:not(:disabled) {
|
|
961
|
+
background: none;
|
|
962
|
+
box-shadow: none;
|
|
963
|
+
transform: none;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* Focus: the border goes to brand ink and a soft ring is drawn *by the
|
|
967
|
+
field itself*, with box-shadow rather than outline.
|
|
968
|
+
The border alone was the previous answer, after an `outline` with an
|
|
969
|
+
offset had read as a second rectangle floating around the input. A
|
|
970
|
+
box-shadow spread follows the border-radius exactly, so it reads as
|
|
971
|
+
one thickened edge — which fixes the original complaint without
|
|
972
|
+
leaving keyboard users hunting for which field they are in. */
|
|
973
|
+
input:focus-visible,
|
|
974
|
+
select:focus-visible,
|
|
975
|
+
textarea:focus-visible {
|
|
976
|
+
outline: none;
|
|
977
|
+
border-color: var(--color-brand);
|
|
978
|
+
box-shadow: 0 0 0 3px var(--color-focus-ring);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/* Kept for pointer focus too — a click into a field should still show
|
|
982
|
+
which one is live, just without the ring shouting. */
|
|
758
983
|
input:focus,
|
|
759
984
|
select:focus,
|
|
760
985
|
textarea:focus {
|
|
@@ -762,6 +987,31 @@ textarea:focus {
|
|
|
762
987
|
border-color: var(--color-brand);
|
|
763
988
|
}
|
|
764
989
|
|
|
990
|
+
/* A field the browser or the form has judged invalid.
|
|
991
|
+
There was no error state at all: a rejected field looked exactly
|
|
992
|
+
like an accepted one, and the only signal was whatever the server
|
|
993
|
+
said afterwards. :user-invalid rather than :invalid deliberately —
|
|
994
|
+
it waits until somebody has actually interacted, so an untouched
|
|
995
|
+
form does not open covered in red. */
|
|
996
|
+
input[aria-invalid="true"],
|
|
997
|
+
select[aria-invalid="true"],
|
|
998
|
+
textarea[aria-invalid="true"],
|
|
999
|
+
input:user-invalid,
|
|
1000
|
+
select:user-invalid,
|
|
1001
|
+
textarea:user-invalid {
|
|
1002
|
+
border-color: var(--color-danger);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
input[aria-invalid="true"]:focus-visible,
|
|
1006
|
+
select[aria-invalid="true"]:focus-visible,
|
|
1007
|
+
textarea[aria-invalid="true"]:focus-visible,
|
|
1008
|
+
input:user-invalid:focus-visible,
|
|
1009
|
+
select:user-invalid:focus-visible,
|
|
1010
|
+
textarea:user-invalid:focus-visible {
|
|
1011
|
+
border-color: var(--color-danger);
|
|
1012
|
+
box-shadow: 0 0 0 3px var(--color-focus-ring-danger);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
765
1015
|
input:disabled,
|
|
766
1016
|
select:disabled,
|
|
767
1017
|
textarea:disabled {
|
|
@@ -1028,10 +1278,10 @@ a.card:hover {
|
|
|
1028
1278
|
}
|
|
1029
1279
|
|
|
1030
1280
|
.modal-panel {
|
|
1031
|
-
background: var(--color-
|
|
1032
|
-
border: 1px solid var(--color-border);
|
|
1281
|
+
background: var(--color-surface-raised);
|
|
1282
|
+
border: 1px solid var(--color-border-raised);
|
|
1033
1283
|
border-radius: var(--radius-lg);
|
|
1034
|
-
box-shadow: var(--shadow-
|
|
1284
|
+
box-shadow: var(--shadow-popover);
|
|
1035
1285
|
width: 100%;
|
|
1036
1286
|
max-width: 640px;
|
|
1037
1287
|
max-height: 80vh;
|