@wtfalch/design 0.6.0 → 0.8.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.
Files changed (59) hide show
  1. package/dist/components/Button.d.ts +19 -27
  2. package/dist/components/Button.js +61 -1
  3. package/dist/components/Callout.d.ts +0 -21
  4. package/dist/components/Callout.js +7 -0
  5. package/dist/components/Card.js +7 -0
  6. package/dist/components/Checkbox.d.ts +0 -31
  7. package/dist/components/Checkbox.js +7 -0
  8. package/dist/components/Command.js +10 -2
  9. package/dist/components/DangerZone.js +7 -0
  10. package/dist/components/Dialog.d.ts +0 -21
  11. package/dist/components/Dialog.js +7 -0
  12. package/dist/components/Field.d.ts +22 -50
  13. package/dist/components/Field.js +18 -9
  14. package/dist/components/Identity.d.ts +0 -25
  15. package/dist/components/Identity.js +7 -0
  16. package/dist/components/Input.d.ts +10 -0
  17. package/dist/components/Input.js +31 -3
  18. package/dist/components/Kbd.d.ts +7 -0
  19. package/dist/components/Kbd.js +34 -0
  20. package/dist/components/Markdown.js +7 -0
  21. package/dist/components/Menu.js +10 -2
  22. package/dist/components/Modal.d.ts +0 -33
  23. package/dist/components/Modal.js +7 -0
  24. package/dist/components/Pagination.d.ts +0 -33
  25. package/dist/components/Pagination.js +7 -0
  26. package/dist/components/Popover.js +7 -0
  27. package/dist/components/Rows.js +7 -0
  28. package/dist/components/ScrollArea.js +7 -0
  29. package/dist/components/Select.d.ts +8 -1
  30. package/dist/components/Select.js +34 -5
  31. package/dist/components/Shell.d.ts +35 -0
  32. package/dist/components/Shell.js +26 -0
  33. package/dist/components/SizeGrid.js +7 -0
  34. package/dist/components/Slider.js +7 -0
  35. package/dist/components/SplitPane.js +7 -0
  36. package/dist/components/Stat.d.ts +45 -0
  37. package/dist/components/Stat.js +31 -0
  38. package/dist/components/Tabs.d.ts +21 -0
  39. package/dist/components/Tabs.js +8 -1
  40. package/dist/components/Textarea.js +14 -4
  41. package/dist/components/ThemeSwitch.d.ts +19 -0
  42. package/dist/components/ThemeSwitch.js +65 -0
  43. package/dist/components/Toast.d.ts +24 -18
  44. package/dist/components/Toast.js +44 -3
  45. package/dist/components/Toggle.d.ts +0 -30
  46. package/dist/components/Toggle.js +7 -0
  47. package/dist/components/Tooltip.js +7 -0
  48. package/dist/components/Tour.d.ts +0 -23
  49. package/dist/components/Tour.js +7 -0
  50. package/dist/components/fieldWiring.d.ts +44 -0
  51. package/dist/components/fieldWiring.js +30 -0
  52. package/dist/index.d.ts +23 -3
  53. package/dist/index.js +17 -1
  54. package/dist/styles/index.css +259 -13
  55. package/dist/tf.css +259 -13
  56. package/dist/themes/choice.d.ts +71 -0
  57. package/dist/themes/choice.js +81 -0
  58. package/dist/valet.css +259 -13
  59. package/package.json +2 -1
package/dist/valet.css CHANGED
@@ -367,7 +367,28 @@ body::before {
367
367
  where the flat grey reads as recessed rather than as something to use. It is
368
368
  derived rather than declared -- a new token would have to be added to every
369
369
  theme in `themes.ts`, and a theme that missed it would lose its controls. */
370
+ /* An anchor drawn as a button. Three things the element brings that a
371
+ `<button>` does not, and all three have to go: the underline, the link
372
+ colour, and the text alignment a block anchor inherits. `cursor: pointer`
373
+ it already has. Disabled is `aria-disabled` here -- an anchor ignores the
374
+ native attribute -- so every `:disabled` rule above has an
375
+ `[aria-disabled='true']` twin rather than a rewrite, which leaves each
376
+ `button` selector at the specificity it had and every committed baseline
377
+ untouched. */
378
+ a.btn {
379
+ text-decoration: none;
380
+ color: var(--text);
381
+ text-align: center;
382
+ }
383
+
384
+ a.btn[aria-disabled='true'] {
385
+ /* A disabled anchor still takes the pointer, so say so. The click itself is
386
+ blocked in the capture phase by the component. */
387
+ cursor: not-allowed;
388
+ }
389
+
370
390
  button,
391
+ a.btn,
371
392
  input,
372
393
  textarea,
373
394
  select {
@@ -391,7 +412,8 @@ select {
391
412
  the icon; the button simply had no opinion about how to arrange two things.
392
413
  `inline-flex` and a gap gives it one, and the gap replaces the space
393
414
  character people were putting between an icon and its label. */
394
- button {
415
+ button,
416
+ a.btn {
395
417
  display: inline-flex;
396
418
  align-items: center;
397
419
  justify-content: center;
@@ -417,7 +439,8 @@ button[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent);
417
439
 
418
440
  /* The glyph never shrinks: in a narrow row flexbox would take it from the icon
419
441
  before the text, and half an icon is not a smaller icon. */
420
- button > svg { flex: none; }
442
+ button > svg,
443
+ a.btn > svg { flex: none; }
421
444
 
422
445
  .size-sm { font-size: var(--text-xs); padding: var(--space-1) var(--space-2); }
423
446
 
@@ -443,6 +466,7 @@ input, textarea, select { width: 100%; }
443
466
  textarea { resize: none; }
444
467
 
445
468
  button:hover:not(:disabled),
469
+ a.btn:hover:not([aria-disabled='true']),
446
470
  input:hover:not(:disabled),
447
471
  textarea:hover:not(:disabled),
448
472
  select:hover:not(:disabled) { border-color: var(--accent); }
@@ -451,6 +475,7 @@ select:hover:not(:disabled) { border-color: var(--accent); }
451
475
  `:focus` on the fields because a focused field is a place you are typing and
452
476
  should say so however you got there. */
453
477
  button:focus-visible,
478
+ a.btn:focus-visible,
454
479
  input:focus,
455
480
  textarea:focus,
456
481
  select:focus { outline: none; border-color: var(--accent); }
@@ -544,11 +569,13 @@ input[type='checkbox']:disabled,
544
569
  input[type='radio']:disabled { cursor: not-allowed; background: var(--panel-2); }
545
570
 
546
571
  button:disabled,
572
+ a.btn[aria-disabled='true'],
547
573
  input:disabled,
548
574
  textarea:disabled,
549
575
  select:disabled { opacity: 0.45; cursor: not-allowed; }
550
576
 
551
- button.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
577
+ button.primary,
578
+ a.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
552
579
 
553
580
  /* A disabled primary is not a faded primary.
554
581
  `opacity: .45` was the whole of it, and a coloured fill at 45% is still a
@@ -556,7 +583,8 @@ button.primary { background: var(--accent); border-color: var(--accent); color:
556
583
  thing on the panel. That got worse when onboarding started holding people at
557
584
  a disabled Next. So it gives up the fill entirely and becomes the shape of a
558
585
  disabled control -- the opacity then has nothing left to do. */
559
- button.primary:disabled {
586
+ button.primary:disabled,
587
+ a.btn.primary[aria-disabled='true'] {
560
588
  background: var(--panel-2);
561
589
  border-color: var(--border);
562
590
  color: var(--muted);
@@ -626,13 +654,15 @@ button[aria-busy='true']::after {
626
654
 
627
655
  /* Quiet: no border, no fill. For the action beside the action -- Cancel next to
628
656
  Save -- where a bordered box competes with the thing it is next to. */
629
- button.ghost {
657
+ button.ghost,
658
+ a.btn.ghost {
630
659
  background: transparent;
631
660
  border-color: transparent;
632
661
  color: var(--muted);
633
662
  }
634
663
 
635
- button.ghost:hover:not(:disabled) {
664
+ button.ghost:hover:not(:disabled),
665
+ a.btn.ghost:hover:not([aria-disabled='true']) {
636
666
  background: var(--panel-2);
637
667
  border-color: transparent;
638
668
  color: var(--text);
@@ -839,19 +869,22 @@ button.ghost:hover:not(:disabled) {
839
869
  /* `:where(button)` so this carries no specificity of its own: a component's
840
870
  press rule below -- a card, a tab, a rail item, all of them buttons --
841
871
  wins by coming later, instead of every one having to restate `button`. */
842
- :where(button):active:not(:disabled) {
872
+ :where(button):active:not(:disabled),
873
+ :where(a.btn):active:not([aria-disabled='true']) {
843
874
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--control));
844
875
  border-color: var(--accent);
845
876
  transform: scale(var(--press-scale));
846
877
  transition: none;
847
878
  }
848
879
 
849
- button.primary:active:not(:disabled) {
880
+ button.primary:active:not(:disabled),
881
+ a.btn.primary:active:not([aria-disabled='true']) {
850
882
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
851
883
  border-color: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
852
884
  }
853
885
 
854
- button.ghost:active:not(:disabled) {
886
+ button.ghost:active:not(:disabled),
887
+ a.btn.ghost:active:not([aria-disabled='true']) {
855
888
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--panel-2));
856
889
  border-color: transparent;
857
890
  }
@@ -1074,7 +1107,8 @@ input:active, textarea:active, select:active { transform: none; }
1074
1107
  /* Full width, for the one action a card or a step is about. Not a size: a
1075
1108
  `block` button is the same height as any other, it just stops being a thing
1076
1109
  you have to aim at. */
1077
- button.block { display: flex; width: 100%; }
1110
+ button.block,
1111
+ a.btn.block { display: flex; width: 100%; }
1078
1112
 
1079
1113
  /* The default is `size-md`, spelled once for every control.
1080
1114
  The paragraph above promised one scale for buttons, inputs and selects, and
@@ -1083,6 +1117,7 @@ button.block { display: flex; width: 100%; }
1083
1117
  could line up. The download modal's search box against its Search button was
1084
1118
  the version you could see from across the room. */
1085
1119
  button,
1120
+ a.btn,
1086
1121
  input,
1087
1122
  textarea,
1088
1123
  select,
@@ -1233,18 +1268,21 @@ select,
1233
1268
 
1234
1269
  /* ---- ./dangerzone.css ---- */
1235
1270
  /* Destructive, and legible as such standing still. */
1236
- button.danger {
1271
+ button.danger,
1272
+ a.btn.danger {
1237
1273
  color: var(--bad);
1238
1274
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border-strong));
1239
1275
  }
1240
1276
 
1241
- button.danger:hover:not(:disabled) {
1277
+ button.danger:hover:not(:disabled),
1278
+ a.btn.danger:hover:not([aria-disabled='true']) {
1242
1279
  background: color-mix(in srgb, var(--bad) 12%, var(--panel));
1243
1280
  border-color: var(--bad);
1244
1281
  color: var(--bad);
1245
1282
  }
1246
1283
 
1247
- button.danger:active:not(:disabled) {
1284
+ button.danger:active:not(:disabled),
1285
+ a.btn.danger:active:not([aria-disabled='true']) {
1248
1286
  background: color-mix(in srgb, var(--bad) 20%, var(--panel));
1249
1287
  border-color: var(--bad);
1250
1288
  }
@@ -3354,6 +3392,70 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3354
3392
 
3355
3393
  .secret-lg > .secret-eye { padding: var(--space-2); }
3356
3394
 
3395
+ /* An input with something inside its edges: a glyph on the left, a hint or a
3396
+ clear button on the right. Same arrangement as `.secret` above, and for the
3397
+ same reason -- one grid cell with everything stacked in it, and the room
3398
+ reserved in the input's own padding, so nothing is positioned against a
3399
+ height that changes with `size`. */
3400
+ .adorned { display: grid; align-items: center; }
3401
+
3402
+ .adorned > input,
3403
+ .adorned > .adorned-mark,
3404
+ .adorned > .adorned-end { grid-area: 1 / 1; }
3405
+
3406
+ .adorned.block { width: 100%; }
3407
+
3408
+ .adorned > .adorned-mark {
3409
+ justify-self: start;
3410
+ margin-left: var(--space-2);
3411
+ color: var(--muted);
3412
+ /* The glyph is decoration over the box; clicks belong to the input under
3413
+ it, which is otherwise unreachable in its own left edge. */
3414
+ pointer-events: none;
3415
+ /* Over the input, not under it. Both share one grid cell, the input has an
3416
+ opaque `--control` background, and it comes later in the DOM -- so
3417
+ without this the glyph is painted and then covered, which looks exactly
3418
+ like an icon that failed to render. `.secret` never hit this because its
3419
+ button is written after the input. */
3420
+ z-index: 1;
3421
+ }
3422
+
3423
+ /* Room for the glyph, on the scale, so a density change moves the text with
3424
+ the mark rather than leaving one behind. `--space-8` is what `.secret`
3425
+ reserves on the right for the same reason; the scale has no 7, and an
3426
+ undefined custom property does not fall back -- the declaration computes
3427
+ to 0 and the text sits under the glyph. */
3428
+ .adorned-icon > input { padding-left: var(--space-8); }
3429
+
3430
+ /* No `--space-12` on the scale; two steps that are, added. Same arithmetic
3431
+ as `.secret-lg`. */
3432
+ .adorned-icon.adorned-lg > input { padding-left: calc(var(--space-10) + var(--space-1)); }
3433
+
3434
+ .adorned > .adorned-end {
3435
+ justify-self: end;
3436
+ display: flex;
3437
+ align-items: center;
3438
+ gap: var(--space-1);
3439
+ margin-right: var(--space-1);
3440
+ }
3441
+
3442
+ /* `:has`, not a sibling combinator: the input comes *before* the end cap in
3443
+ the DOM, so `~` reads the wrong way and matches nothing. */
3444
+ .adorned:has(> .adorned-end) > input { padding-right: var(--space-8); }
3445
+ .adorned-lg:has(> .adorned-end) > input {
3446
+ padding-right: calc(var(--space-10) + var(--space-1));
3447
+ }
3448
+
3449
+ /* Sized from its glyph, not `.icon-btn`'s 34px tile, which would be taller
3450
+ than a small input -- the note above `.secret-eye` in full. */
3451
+ .adorned-clear {
3452
+ width: auto; height: auto;
3453
+ padding: var(--space-1);
3454
+ opacity: 1;
3455
+ }
3456
+
3457
+ .adorned-lg .adorned-clear { padding: var(--space-2); }
3458
+
3357
3459
  /* ---- ./tour.css ---- */
3358
3460
  /* tour ----------------------------------------------------------------- */
3359
3461
 
@@ -3421,6 +3523,150 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3421
3523
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
3422
3524
  rule of equal specificity that came earlier would lose to whatever the
3423
3525
  sheets they cover happen to set. */
3526
+ /* ---- ./kbd.css ---- */
3527
+ /* One rule where there were two identical ones, in `menu.css` and
3528
+ `command.css`. Both keep their class as well, so `Menu` and `Command` are
3529
+ `.kbd .menu-key` and `.kbd .cmd-key` now: the declarations are the same, so
3530
+ nothing moves, and the context classes stay available for a rule that ever
3531
+ needs to tell the two apart. */
3532
+ .kbd {
3533
+ flex: none;
3534
+ font-family: var(--font-mono);
3535
+ font-size: var(--text-2xs);
3536
+ color: var(--muted);
3537
+ letter-spacing: var(--tracking);
3538
+ }
3539
+
3540
+ /* ---- ./stat.css ---- */
3541
+ /* A figure with furniture. `--space-1` between the three lines because they
3542
+ are one object read top to bottom, not three paragraphs. */
3543
+ .stat {
3544
+ display: grid;
3545
+ gap: var(--space-1);
3546
+ padding: var(--space-4);
3547
+ border: var(--border-width) solid var(--border);
3548
+ border-radius: var(--radius-md);
3549
+ background: var(--panel);
3550
+ }
3551
+
3552
+ /* Inside something already bordered. A bordered tile in a bordered row is two
3553
+ boxes saying one thing. */
3554
+ .stat-bare {
3555
+ padding: 0;
3556
+ border: 0;
3557
+ border-radius: 0;
3558
+ background: none;
3559
+ gap: 0;
3560
+ line-height: 1.25;
3561
+ }
3562
+
3563
+ .stat > p { margin: 0; }
3564
+
3565
+ .stat-label {
3566
+ color: var(--muted);
3567
+ font-size: var(--text-xs);
3568
+ }
3569
+
3570
+ /* Tabular figures are the point: a column of numbers is compared by its
3571
+ digits' positions, and proportional ones move them per row. */
3572
+ .stat-value {
3573
+ font-size: var(--text-lg);
3574
+ font-weight: var(--weight-strong);
3575
+ font-variant-numeric: tabular-nums;
3576
+ }
3577
+
3578
+ .stat-bare .stat-value { font-size: var(--text-sm); }
3579
+
3580
+ .stat-note {
3581
+ color: var(--muted);
3582
+ font-size: var(--text-xs);
3583
+ }
3584
+
3585
+ /* Only the figure takes the colour. Colouring the label too makes the tile a
3586
+ status box, and a tone here is a remark about the number. */
3587
+ .stat[data-tone='info'] .stat-value { color: var(--info); }
3588
+ .stat[data-tone='good'] .stat-value { color: var(--good); }
3589
+ .stat[data-tone='warn'] .stat-value { color: var(--warn); }
3590
+ .stat[data-tone='bad'] .stat-value { color: var(--bad); }
3591
+
3592
+ /* ---- ./shell.css ---- */
3593
+ /* The frame. Layout only: every colour and every step is a token, and the
3594
+ page inside it owns its own spacing. */
3595
+ .shell {
3596
+ display: flex;
3597
+ flex-direction: column;
3598
+ min-height: 100vh;
3599
+ background: var(--bg);
3600
+ color: var(--text);
3601
+ }
3602
+
3603
+ .shell-head {
3604
+ border-bottom: var(--border-width) solid var(--border);
3605
+ background: var(--panel);
3606
+ /* `flex: none` so a long page does not squeeze the header. */
3607
+ flex: none;
3608
+ }
3609
+
3610
+ /* The measure, said once. `--shell-measure` rather than a literal so an app
3611
+ with a different one sets a variable instead of overriding three rules and
3612
+ missing the fourth -- which is how manage ended up with `head-inner-wide`
3613
+ and `main-wide` as separate classes. */
3614
+ .shell {
3615
+ --shell-measure: 64rem;
3616
+ --shell-measure-wide: 90rem;
3617
+ }
3618
+
3619
+ .shell-head-inner,
3620
+ .shell-nav,
3621
+ .shell-main {
3622
+ width: 100%;
3623
+ max-width: var(--shell-measure);
3624
+ margin-inline: auto;
3625
+ padding-inline: var(--space-4);
3626
+ }
3627
+
3628
+ .shell-wide { max-width: var(--shell-measure-wide); }
3629
+
3630
+ .shell-head-inner {
3631
+ display: flex;
3632
+ align-items: center;
3633
+ justify-content: space-between;
3634
+ gap: var(--space-4);
3635
+ padding-block: var(--space-3);
3636
+ min-width: 0;
3637
+ }
3638
+
3639
+ .shell-who {
3640
+ display: flex;
3641
+ align-items: center;
3642
+ gap: var(--space-3);
3643
+ min-width: 0;
3644
+ }
3645
+
3646
+ .shell-nav { padding-bottom: var(--space-2); }
3647
+
3648
+ .shell-main {
3649
+ flex: 1;
3650
+ padding-block: var(--space-6);
3651
+ /* `min-width: 0` because a flex child will not shrink below its content,
3652
+ and one wide table inside then pushes the whole page sideways. */
3653
+ min-width: 0;
3654
+ }
3655
+
3656
+ /* The label beside the theme control, when it is drawn. */
3657
+ .theme-switch {
3658
+ display: flex;
3659
+ align-items: center;
3660
+ gap: var(--space-2);
3661
+ min-width: 0;
3662
+ }
3663
+
3664
+ .theme-switch-label {
3665
+ color: var(--muted);
3666
+ font-size: var(--text-sm);
3667
+ white-space: nowrap;
3668
+ }
3669
+
3424
3670
  /* ---- ./scrollarea.css ---- */
3425
3671
  /* A box that scrolls, with its edges drawn.
3426
3672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/design",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "wtfalch's design system: themeable components on a fixed token vocabulary.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -65,6 +65,7 @@
65
65
  "react-dom": "^19.0.0"
66
66
  },
67
67
  "dependencies": {
68
+ "@radix-ui/react-slot": "^1.3.3",
68
69
  "dompurify": "^3.4.13",
69
70
  "marked": "^18.0.9",
70
71
  "react-aria-components": "^1.21.0"