@hattisar/design-tokens 0.3.22 → 0.3.24

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tokens.css +141 -120
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hattisar/design-tokens",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
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
@@ -66,7 +66,10 @@
66
66
  --color-border-raised: #d4d4d8;
67
67
  --shadow-popover: 0 4px 6px -2px rgba(9, 9, 11, 0.08), 0 12px 28px -8px rgba(9, 9, 11, 0.18);
68
68
 
69
- --color-hover: var(--color-surface-tint);
69
+ /* The one hover fill for every surface: a faint veil of the ink over
70
+ whatever it sits on, so it never reads as a second grey competing
71
+ with a header band or a card (those use --color-surface-tint). */
72
+ --color-hover: color-mix(in srgb, var(--color-text-primary) 6%, transparent);
70
73
  --color-focus-ring: rgba(233, 235, 239, 0.2);
71
74
  --color-focus-ring-danger: rgba(255, 138, 156, 0.24);
72
75
  /* The ring drawn around a focused control. Ink at low alpha rather
@@ -128,7 +131,7 @@
128
131
  --color-surface-tint/--color-border-strong/--color-text-primary
129
132
  rather than their own literal hex, so the secondary button always
130
133
  tracks the same neutral scale as the rest of the page. */
131
- --color-secondary-fill: var(--color-surface-tint);
134
+ --color-secondary-fill: var(--color-background-card);
132
135
  --color-secondary-border: var(--color-border-strong);
133
136
  --color-secondary-text: var(--color-text-primary);
134
137
  --color-secondary-hover: color-mix(in srgb, var(--color-text-primary) 6%, var(--color-secondary-fill));
@@ -260,6 +263,40 @@
260
263
  and the gray column-header band right below ("search area look very
261
264
  tight"). One token so the tiles and the toolbar can't drift apart. */
262
265
  --toolbar-row-height: 48px;
266
+ /* The pinned page header on a list page (title + actions), and what the
267
+ table's column headers stick beneath. */
268
+ --page-heading-height: 52px;
269
+
270
+ /* Buttons, after Shopify's admin: a fully rounded pill, 28px tall,
271
+ medium-weight small type. One set of numbers for every button the
272
+ app draws (primary, secondary, destructive, the save bar). */
273
+ --button-height: 28px;
274
+ --button-radius: var(--radius-full);
275
+ --button-padding-x: var(--space-3);
276
+ --button-font-size: var(--text-sm);
277
+ --button-font-weight: var(--font-weight-medium);
278
+
279
+ /* The big rounded containers on a list page, after Shopify's Orders
280
+ page: the KPI strip, the search bar and the table card all share it. */
281
+ --list-card-radius: var(--radius-xl);
282
+ /* A table row under the cursor: a faint veil of the ink, deliberately
283
+ lighter than the header band (--color-surface-tint) so the two never
284
+ read as the same grey. */
285
+ --table-row-hover-bg: color-mix(in srgb, var(--color-text-primary) 4%, transparent);
286
+ /* The secondary button's surface, named once so every control that is a
287
+ secondary button in all but markup (More Actions, the dashboard
288
+ filters, the close x) reads the same two tokens as .btn-secondary
289
+ instead of keeping a private copy of the look. A white pill needs a
290
+ hairline of its own to read against the page. Primary is simply the
291
+ bare `button` rule (--color-cta). */
292
+ --button-secondary-bg: var(--color-secondary-fill);
293
+ --button-secondary-shadow: var(--shadow-btn-secondary), 0 0 0 1px var(--color-border);
294
+
295
+ /* The picture picker (ImagePicker.tsx) behind an item image, the org
296
+ logo and both user photos. Named so those four can't drift apart. */
297
+ --image-picker-size: 96px;
298
+ --image-picker-border: 1px dashed var(--color-border);
299
+ --image-picker-overlay: color-mix(in srgb, var(--color-background-card) 55%, transparent);
263
300
 
264
301
  /* Depth, deliberately restrained. These were all `none`, which left
265
302
  every surface separated by border alone — correct, legible, and
@@ -405,33 +442,46 @@
405
442
  /* 58px, not 52 — asked for directly, matched against a reference
406
443
  layout's own header height so this app's chrome proportions read
407
444
  the same. */
408
- --header-height: 58px;
445
+ --header-height: 8px;
446
+ /* Height of a title bar inside a panel or drawer (notifications,
447
+ support). Was --header-height before the top bar left the desktop
448
+ shell and that token shrank to the frame's own 4px + 4px margins. */
449
+ --panel-header-height: 58px;
409
450
 
410
451
  /* Exact Hattisar Design match — aside/header sit at the same white
411
452
  --hd-card-bg as every card (bordered, not tonally separate),
412
453
  mirrored by dark theme's own literal Hattisar Design values below. */
413
- --sidebar-bg: var(--color-background-card);
414
- --sidebar-text: var(--color-text-primary);
415
- --sidebar-text-muted: var(--color-text-muted);
416
- --sidebar-active-text: var(--color-text-primary);
417
- --sidebar-hover-bg: var(--color-surface-tint);
418
- /* Accent ink for the row you are on: icon, label and a 3px left bar
419
- all take this, and nothing takes it as a fill. --color-cta-on-
420
- surface rather than --color-cta itself because this sits on a
421
- white rail: measured, 3973d4 lands at 4.59:1 against it and
422
- 4285f4 at 3.56:1, so the unmodified CTA blue would miss AA for
423
- normal text on the one rail it appears on most.
424
-
425
- Was var(--sidebar-active-text), i.e. --color-text-primary, which
426
- made it the same near-black as every other row's hover state and
427
- so no accent at all. The active row had a pill fill to lean on
428
- instead, except that in light theme the fill token and
429
- --sidebar-hover-bg were both --color-surface-tint — so the page
430
- you were on and whatever row the cursor happened to be over were
431
- pixel-identical. That fill token is gone; both secondary rails
432
- already read this one, so all three navs now share one accent. */
433
- --sidebar-active-ink: var(--color-cta-on-surface);
434
- --sidebar-rail-bg: var(--color-background-card);
454
+ /* The sidebar is dark chrome in every theme, built only from the
455
+ palette above: its fill is the theme's own near-black and its ink the
456
+ theme's own near-white, and every other shade is a mix of those two
457
+ (so a theme change re-tints all of them from one pair of tokens). */
458
+ --sidebar-bg: var(--color-text-primary);
459
+ --sidebar-active-text: var(--color-background-card);
460
+ --sidebar-text: color-mix(in srgb, var(--sidebar-active-text) 88%, var(--sidebar-bg));
461
+ --sidebar-text-muted: color-mix(in srgb, var(--sidebar-active-text) 70%, var(--sidebar-bg));
462
+ /* Hover (and the current row's fill), the dividers, and the search /
463
+ input fill: a veil of the ink over the chrome. */
464
+ --sidebar-hover-bg: color-mix(in srgb, var(--sidebar-active-text) 10%, transparent);
465
+ --sidebar-border: color-mix(in srgb, var(--sidebar-active-text) 14%, transparent);
466
+ --sidebar-input-bg: var(--sidebar-hover-bg);
467
+ /* The row you are on: the full ink on the same fill hover uses, the
468
+ weight doing the rest. */
469
+ --sidebar-active-ink: var(--sidebar-active-text);
470
+ --sidebar-rail-bg: var(--sidebar-bg);
471
+ /* The one background every popout shares (account menu, notifications,
472
+ support, the collapsed rail's hover menu, the search popup): the
473
+ sidebar colour one step lighter, so a popout reads as lifted off
474
+ the dark chrome rather than merging into it. */
475
+ --popout-bg: color-mix(in srgb, var(--sidebar-active-text) 12%, var(--sidebar-bg));
476
+
477
+ /* Sidebar and popout dimensions, named once. */
478
+ --sidebar-icon-size: 20px;
479
+ --sidebar-control-height: 36px;
480
+ --sidebar-badge-size: 22px;
481
+ --sidebar-avatar-size: 28px;
482
+ --popout-min-width: 180px;
483
+ --search-popup-width: 640px;
484
+ --mobile-nav-width: 350px;
435
485
 
436
486
  /* Every left-hand nav rail in an app sharing this one width instead
437
487
  of each picking its own. 260px, not 240 — asked for directly,
@@ -439,7 +489,7 @@
439
489
  layout's own sidebar width, the same reason --header-height above
440
490
  moved too. */
441
491
  --sidebar-width: 260px;
442
- --sidebar-width-collapsed: 64px;
492
+ --sidebar-width-collapsed: 56px;
443
493
  --sidebar-height: calc(100vh - var(--header-height));
444
494
 
445
495
  /* Two widths, because a page is one of two things.
@@ -520,7 +570,7 @@
520
570
  --color-surface-raised: #1f1f23;
521
571
  --color-border-raised: #3f3f46;
522
572
  --shadow-popover: 0 4px 6px -2px rgba(0, 0, 0, 0.5), 0 16px 40px -10px rgba(0, 0, 0, 0.75);
523
- --color-hover: var(--color-surface-tint);
573
+ --color-hover: color-mix(in srgb, var(--color-text-primary) 6%, transparent);
524
574
 
525
575
  /* Brighter counterparts of :root's accent set — a dark badge fill
526
576
  needs a light/saturated text color for contrast, the mirror image
@@ -618,16 +668,11 @@
618
668
  surface as a card, which is where shadcn puts a sidebar too.
619
669
  The frame still reads as structurally separate from content, which
620
670
  was the point of the original choice. */
621
- --sidebar-bg: #18181b;
622
- --sidebar-text: var(--color-text-primary);
623
- --sidebar-text-muted: var(--color-text-muted);
671
+ /* Dark theme: a fill one step below the page canvas (so the page frame
672
+ still reads against it) and the theme's own ink; every other sidebar
673
+ shade is the :root mix of the two. */
674
+ --sidebar-bg: color-mix(in srgb, var(--color-background-global) 40%, black);
624
675
  --sidebar-active-text: var(--color-text-primary);
625
- --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
626
- /* --color-cta is brand ink now (see :root's own --color-cta) — this
627
- "current row" marker used to lean on the accent blue clearing AA
628
- on #18181b without the on-surface variant's extra darkening. */
629
- --sidebar-active-ink: var(--color-cta);
630
- --sidebar-rail-bg: #18181b;
631
676
 
632
677
  color-scheme: dark;
633
678
  }
@@ -650,7 +695,7 @@
650
695
  --color-surface-raised: #ffffff;
651
696
  --color-border-raised: #d4d4d8;
652
697
  --shadow-popover: 0 4px 6px -2px rgba(9, 9, 11, 0.08), 0 12px 28px -8px rgba(9, 9, 11, 0.18);
653
- --color-hover: var(--color-surface-tint);
698
+ --color-hover: color-mix(in srgb, var(--color-text-primary) 6%, transparent);
654
699
 
655
700
  --color-brand: var(--color-text-primary);
656
701
  --color-brand-hover: color-mix(in srgb, var(--color-brand) 85%, black);
@@ -667,7 +712,7 @@
667
712
  --shadow-btn-primary: 0 1px 2px rgba(0, 0, 0, 0.4);
668
713
  --shadow-btn-secondary: 0 1px 2px rgba(0, 0, 0, 0.3);
669
714
 
670
- --color-secondary-fill: var(--color-surface-tint);
715
+ --color-secondary-fill: var(--color-background-card);
671
716
  --color-secondary-border: var(--color-border-strong);
672
717
  --color-secondary-text: var(--color-text-primary);
673
718
  --color-secondary-hover: color-mix(in srgb, var(--color-text-primary) 6%, var(--color-secondary-fill));
@@ -722,13 +767,8 @@
722
767
  --shadow-md: none;
723
768
  --shadow-lg: none;
724
769
 
725
- --sidebar-bg: var(--color-background-card);
726
- --sidebar-text: #09090b;
727
- --sidebar-text-muted: var(--color-text-muted);
728
- --sidebar-active-text: var(--color-text-primary);
729
- --sidebar-hover-bg: var(--color-surface-tint);
730
- --sidebar-active-ink: var(--color-cta-on-surface);
731
- --sidebar-rail-bg: var(--color-background-card);
770
+ --sidebar-bg: var(--color-text-primary);
771
+ --sidebar-active-text: var(--color-background-card);
732
772
 
733
773
  color-scheme: light;
734
774
  }
@@ -860,15 +900,11 @@ label {
860
900
  line-height: 1.4;
861
901
  }
862
902
 
863
- /* A required field says so once, here, rather than each form inventing
864
- its own asterisk-or-not. Marked on the label so it reads before the
865
- field rather than after it. */
866
- label[data-required]::after {
867
- content: "*";
868
- margin-left: 3px;
869
- color: var(--color-danger);
870
- font-weight: var(--font-weight-regular);
871
- }
903
+ /* No star. Asked for directly, 2026-09-25: "remove star and always
904
+ show red border for require field unless its typed out or
905
+ selected". The field's own border is the one and only required
906
+ signal now — see input:required:invalid below — and it needs no
907
+ asterisk to explain what it already shows. */
872
908
 
873
909
  /* Primary action — the call-to-action fill. Charcoal, the same ink as
874
910
  --color-brand (see --color-cta above): had a hue of its own for a
@@ -878,10 +914,11 @@ button {
878
914
  font: inherit;
879
915
  cursor: pointer;
880
916
  border: none;
881
- border-radius: var(--radius-xs);
882
- padding: 4px 10px;
883
- font-size: var(--text-base);
884
- font-weight: 400;
917
+ border-radius: var(--button-radius);
918
+ min-height: var(--button-height);
919
+ padding: var(--space-1) var(--button-padding-x);
920
+ font-size: var(--button-font-size);
921
+ font-weight: var(--button-font-weight);
885
922
  background: var(--color-cta);
886
923
  color: var(--color-cta-text);
887
924
  box-shadow: var(--shadow-btn-primary);
@@ -940,13 +977,14 @@ button:focus-visible {
940
977
  font: inherit;
941
978
  cursor: pointer;
942
979
  border: none;
943
- border-radius: var(--radius-xs);
944
- padding: 4px 10px;
945
- font-size: var(--text-base);
946
- font-weight: 400;
947
- background: var(--color-secondary-fill);
980
+ border-radius: var(--button-radius);
981
+ min-height: var(--button-height);
982
+ padding: var(--space-1) var(--button-padding-x);
983
+ font-size: var(--button-font-size);
984
+ font-weight: var(--button-font-weight);
985
+ background: var(--button-secondary-bg);
948
986
  color: var(--color-secondary-text);
949
- box-shadow: var(--shadow-btn-secondary);
987
+ box-shadow: var(--button-secondary-shadow);
950
988
  text-decoration: none;
951
989
  }
952
990
 
@@ -976,10 +1014,11 @@ button:focus-visible {
976
1014
  font: inherit;
977
1015
  cursor: pointer;
978
1016
  border: none;
979
- border-radius: var(--radius-xs);
980
- padding: 4px 10px;
981
- font-size: var(--text-base);
982
- font-weight: 400;
1017
+ border-radius: var(--button-radius);
1018
+ min-height: var(--button-height);
1019
+ padding: var(--space-1) var(--button-padding-x);
1020
+ font-size: var(--button-font-size);
1021
+ font-weight: var(--button-font-weight);
983
1022
  background: var(--color-destructive-fill);
984
1023
  /* Bare button{} above defaults every <button> to box-shadow:
985
1024
  var(--shadow-btn-primary), a layered bevel built around
@@ -1083,6 +1122,16 @@ textarea::placeholder {
1083
1122
  a warehouse read as a plain text box beside fields that plainly
1084
1123
  opened.
1085
1124
  ------------------------------------------------------------------ */
1125
+ /* Combobox's real `required` validation lives on an invisible sibling
1126
+ input (see Combobox.tsx's own comment) — it has no border of its
1127
+ own to turn red, so the visible box reads its sibling's validity
1128
+ instead. Placed after :focus in the file so it wins while the field
1129
+ is focused too: empty-and-required should read as an error the
1130
+ whole time it stays empty, not just at rest. */
1131
+ .combobox-input:has(~ input:required:invalid) {
1132
+ border-color: var(--color-danger);
1133
+ }
1134
+
1086
1135
  select,
1087
1136
  .combobox-input {
1088
1137
  appearance: none;
@@ -1184,57 +1233,14 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover {
1184
1233
  * difference between one red label on a form and all of them.
1185
1234
  */
1186
1235
  /*
1187
- * And a star after the label text, the usual way to mark a required
1188
- * field.
1189
- *
1190
- * Harder than it looks, because the label's text is a bare text node:
1191
- * `<label>First Name<input required></label>`. A plain `label::after`
1192
- * renders after *everything* in the label — under the input, not
1193
- * beside the words — and the only way to attach it to the text
1194
- * directly would be to wrap that text in a span on every field of
1195
- * every form.
1196
- *
1197
- * So the label becomes a wrapping flex row for required fields only,
1198
- * and the three items are ordered explicitly: the text (order 0, an
1199
- * anonymous flex item), the star (order 1), then the control, forced
1200
- * onto the next line by a full-width basis. The result is the text and
1201
- * its star on one line and the field under them — which is what the
1202
- * label already looked like, with a star added and no markup touched.
1203
- */
1204
- label:has(:required) {
1205
- /*
1206
- * !important because 212 labels in this app carry an inline
1207
- * `style={{ display: "block" }}`, and an inline declaration beats
1208
- * any stylesheet rule regardless of specificity. This is the case
1209
- * the keyword exists for: overriding markup a stylesheet cannot
1210
- * reach. Without it the star renders after the control on every one
1211
- * of those labels instead of beside the words.
1212
- */
1213
- display: flex !important;
1214
- flex-wrap: wrap;
1215
- align-items: baseline;
1216
- }
1217
-
1218
- label:has(:required) > * {
1219
- order: 2;
1220
- flex-basis: 100%;
1221
- }
1222
-
1223
- label:has(:required)::after {
1224
- content: "*";
1225
- order: 1;
1226
- margin-left: 2px;
1227
- color: var(--color-danger);
1228
- }
1229
-
1230
- label:has(:required) {
1231
- color: var(--color-danger);
1232
- }
1233
-
1234
- /*
1235
- * The controls inside go back to the normal ink. Colour inherits, so
1236
- * without this a red label means red typing in the box under it — and
1237
- * on a Combobox, red text in the dropdown as well.
1236
+ * No star, and no flex-row layout to hold one — the star this used to
1237
+ * draw beside the text is gone (2026-09-25), so the label goes back
1238
+ * to its own ordinary flow, inline `display: block` and all. The
1239
+ * label's own colour was never touched by this rule either way (see
1240
+ * label[data-required]'s own comment above), so the override just
1241
+ * below — forcing the control's typed text back to normal ink — is
1242
+ * dead weight now and kept only because removing it is out of scope
1243
+ * for this pass.
1238
1244
  */
1239
1245
  label:has(:required) input,
1240
1246
  label:has(:required) select,
@@ -1274,12 +1280,27 @@ textarea:user-invalid {
1274
1280
  border-color: var(--color-danger);
1275
1281
  }
1276
1282
 
1283
+ /* Required and currently empty — on from the moment the form opens,
1284
+ not waiting for a touch. Off the instant a value exists: typed text
1285
+ satisfies :invalid on a plain input/textarea directly; a picked
1286
+ option satisfies it on a select or Combobox's own hidden required
1287
+ input (see .combobox-input's own rule, and .tags-input's, both
1288
+ further down) the same way. */
1289
+ input:required:invalid,
1290
+ select:required:invalid,
1291
+ textarea:required:invalid {
1292
+ border-color: var(--color-danger);
1293
+ }
1294
+
1277
1295
  input[aria-invalid="true"]:focus-visible,
1278
1296
  select[aria-invalid="true"]:focus-visible,
1279
1297
  textarea[aria-invalid="true"]:focus-visible,
1280
1298
  input:user-invalid:focus-visible,
1281
1299
  select:user-invalid:focus-visible,
1282
- textarea:user-invalid:focus-visible {
1300
+ textarea:user-invalid:focus-visible,
1301
+ input:required:invalid:focus-visible,
1302
+ select:required:invalid:focus-visible,
1303
+ textarea:required:invalid:focus-visible {
1283
1304
  border-color: var(--color-danger);
1284
1305
  box-shadow: 0 0 0 3px var(--color-focus-ring-danger);
1285
1306
  }