@godxjp/ui 18.15.4 → 19.0.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 (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +25 -8
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. package/scripts/ui-audit.mjs +51 -2
@@ -39,6 +39,40 @@
39
39
  "sidebar footer";
40
40
  }
41
41
 
42
+ /* NO TOP BAR AT ALL — `topbar`, `topbarLeft`, `topbarRight` and `logo` all undefined. AppShell
43
+ * renders no `<header class="app-topbar">` in that state (the mobile drawer trigger is the one
44
+ * exception, below) and publishes it as ONE attribute here, so the geometry is decided by the
45
+ * grid element itself rather than by `:has()` on whatever the consumer nested inside.
46
+ *
47
+ * The row has to go with the header. An empty --app-shell-bar-height track still costs a fixed
48
+ * 48px, and the header still painted a bottom border over hsl(var(--card)) — so a chat / mail /
49
+ * IDE shell, whose PAGE header is the top row, showed two rows of chrome (~48 + ~60px) over
50
+ * exactly the region that needs the height most.
51
+ *
52
+ * `auto`, not a `0` literal, is what makes one attribute enough for every branch: an absent row
53
+ * measures 0, a `display:none` header measures 0, and at narrow widths — where the header comes
54
+ * back to carry the hamburger — the row sizes itself from that header's own min-height. So the
55
+ * breakpoint block needs no restatement of these rows, which is the trap this file has already
56
+ * paid for once: the deleted 768px block re-declared grid-template-rows with a `3rem` literal
57
+ * and defeated --app-shell-bar-height below 768px only (see the note on the one @media block).
58
+ * `[data-topbar-span="full"]` re-declares grid-template-AREAS only, so it inherits this row
59
+ * template untouched and its named-but-empty "topbar topbar" row is 0 as well. */
60
+ .app-root[data-topbar="none"] {
61
+ grid-template-rows: auto minmax(0, 1fr) auto;
62
+ }
63
+
64
+ /* The one thing that may still live in a bar-less shell: AppShell's OWN hamburger. Below 900px
65
+ * the docked sidebar is hidden, and dropping the bar there too would leave the shell with no
66
+ * reachable navigation at all — a worse failure than the double chrome above (gh#165). So when a
67
+ * drawer exists the header is still rendered, carrying the trigger and nothing else, and it is
68
+ * THIS rule, not the empty row, that keeps it off the wide layout: `display:none` removes its
69
+ * border, its background and its height together. The min-height is inert here and load-bearing
70
+ * at narrow widths, where it is the only thing giving the `auto` row a height. */
71
+ .app-root[data-topbar="none"] > .app-topbar {
72
+ display: none;
73
+ min-height: var(--app-shell-bar-height);
74
+ }
75
+
42
76
  .app-sidebar {
43
77
  grid-area: sidebar;
44
78
  display: flex;
@@ -90,6 +124,15 @@
90
124
  grid-area: main;
91
125
  min-width: 0;
92
126
  overflow: auto;
127
+ /* Column flow, so a page that asks to FILL can actually be bounded by the
128
+ * region instead of merely starting at its top. With the default `block`
129
+ * flow a `--fill` container could only be `min-height: 100%`, i.e. it grew
130
+ * past the region and pushed the scroll back out here — which is why a chat
131
+ * transcript slid UNDER its own sticky composer. Ordinary pages are
132
+ * unaffected: their container still has `min-height: 100%`, still grows,
133
+ * and this element still scrolls it. */
134
+ display: flex;
135
+ flex-direction: column;
93
136
  /* Ambient brand glow hook — invisible by default (--gradient-glow: none); a service paints a
94
137
  * radial/linear brand wash behind the whole content area by configuring the token alone. */
95
138
  background-image: var(--gradient-glow);
@@ -427,20 +470,20 @@
427
470
  --card-space-gap: var(--auth-shell-card-gap-compact);
428
471
  }
429
472
 
473
+ /* AuthDivider is the auth-scoped PRESET over the labelled `Separator` (gh#308). It no longer
474
+ * re-implements the grid, the rule or the accessible-name contract — it only re-points
475
+ * Separator's knobs at the `--auth-shell-divider-*` layer, so the auth micro-scale (11px, not
476
+ * the stream's xs) and the auth rule/label colours stay exactly where a service already tunes
477
+ * them, while a day divider in a message stream is no longer coupled to a login form.
478
+ * `--separator-label-font-weight` is pinned to normal because the auth conjunction has always
479
+ * inherited the body weight, whereas the generic labelled rule defaults to medium. */
430
480
  .ui-auth-divider {
431
- display: grid;
432
- width: 100%;
433
- grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
434
- align-items: center;
435
- gap: var(--auth-shell-divider-gap);
436
- color: hsl(var(--auth-shell-divider-label-color));
437
- font-size: var(--auth-shell-divider-label-font-size);
438
- line-height: var(--auth-shell-divider-label-line-height);
439
- }
440
-
441
- .ui-auth-divider-rule {
442
- height: 1px;
443
- background: hsl(var(--auth-shell-divider-rule-color));
481
+ --separator-label-gap: var(--auth-shell-divider-gap);
482
+ --separator-label-font-size: var(--auth-shell-divider-label-font-size);
483
+ --separator-label-line-height: var(--auth-shell-divider-label-line-height);
484
+ --separator-label-font-weight: var(--font-weight-normal);
485
+ --separator-rule-color: var(--auth-shell-divider-rule-color);
486
+ --separator-label-color: var(--auth-shell-divider-label-color);
444
487
  }
445
488
 
446
489
  @media (max-width: 30rem) {
@@ -658,9 +701,14 @@
658
701
  display: none;
659
702
  }
660
703
 
661
- /* The docked rail is gone, so the bar tightens against the viewport edge (knob, not a literal). */
662
- .app-topbar {
663
- padding-inline: var(--app-shell-bar-inset-compact);
704
+ /* Bar-less shell (data-topbar="none") at narrow widths: the bar comes back for the hamburger
705
+ * ALONE — the only route to navigation once the docked rail is hidden. The `auto` row picks up
706
+ * its height from the header's min-height, so no grid-template-rows is restated here, and none
707
+ * may be: the one-breakpoint guard forbids a rows declaration inside this block precisely
708
+ * because the last one hard-coded 3rem. Docked mode never reaches this rule — it renders no
709
+ * drawer, so a bar-less docked shell has no header to show. */
710
+ .app-root[data-topbar="none"] > .app-topbar {
711
+ display: flex;
664
712
  }
665
713
 
666
714
  .app-topbar-left:has(.app-mobile-sidebar-trigger) {
@@ -699,15 +747,30 @@
699
747
  display: flex;
700
748
  }
701
749
 
702
- .app-root[data-responsive-navigation="docked"] > .app-topbar {
703
- padding-inline: var(--app-shell-bar-inset);
704
- }
705
-
750
+ /* (There is deliberately no `padding-inline` undo for docked mode any more. It existed to
751
+ * cancel a bar inset that stepped on the SHELL's 900px restructure; since gh#330 the inset
752
+ * steps on the PAGE's 720px step instead, and a docked page tightens its own gutters at that
753
+ * same step — so undoing the bar here would re-open the misalignment the step exists to
754
+ * keep closed.) */
706
755
  .app-root[data-responsive-navigation="docked"] .app-mobile-nav-trigger {
707
756
  display: none;
708
757
  }
709
758
  }
710
759
 
760
+ /* The compact step of the HORIZONTAL PAGE-INSET AXIS (gh#330). It is deliberately NOT the shell's
761
+ * 900px restructure breakpoint: the bar's inset is not a fact about whether a rail is docked, it
762
+ * is a fact about where the page's content edge is — and the page steps its gutters to
763
+ * --space-page-compact-x at exactly `(max-width: 720px)` (see styles/layout.css ·
764
+ * `.ui-page-container`). Stated with the identical condition so the two sides can never drift
765
+ * apart again; the shell's own restructure block above still owns everything that IS about the
766
+ * rail. This block mentions `.app-topbar` only, so the one-breakpoint `.app-root` contract
767
+ * (gh#213) is untouched. */
768
+ @media (max-width: 720px) {
769
+ .app-topbar {
770
+ padding-inline: var(--app-shell-bar-inset-compact);
771
+ }
772
+ }
773
+
711
774
  .sb-root {
712
775
  display: contents;
713
776
  }
@@ -909,8 +972,8 @@
909
972
  }
910
973
 
911
974
  .sb-product-caret svg {
912
- width: 0.875rem;
913
- height: 0.875rem;
975
+ width: var(--sidebar-product-caret-icon-size);
976
+ height: var(--sidebar-product-caret-icon-size);
914
977
  }
915
978
 
916
979
  .sb-nav-scroll {
@@ -929,9 +992,19 @@
929
992
  margin-top: var(--sidebar-section-gap);
930
993
  }
931
994
 
995
+ /* The section label is the ONE piece of sidebar text with no colour knob.
996
+ * Every other one has a variable with this same fallback — item, hover,
997
+ * active, icon — and the sidebar is paintable by design (`--sidebar-gradient`
998
+ * is a published knob). So a consumer that paints it can recolour every label
999
+ * except this heading, which stays muted-foreground and lands on whatever
1000
+ * they painted: measured at 1.50 against a mid-tone blue, where 11px text
1001
+ * needs 4.5.
1002
+ *
1003
+ * The fallback is the old value, so nothing changes for a sidebar that does
1004
+ * not paint itself. */
932
1005
  .sb-section-label {
933
1006
  padding: 0 var(--sidebar-section-label-padding-x) var(--sidebar-section-label-padding-bottom);
934
- color: hsl(var(--muted-foreground));
1007
+ color: var(--sidebar-section-label-foreground, hsl(var(--muted-foreground)));
935
1008
  font-size: var(--sidebar-section-label-font-size);
936
1009
  font-weight: 700;
937
1010
  letter-spacing: 0.08em;
@@ -968,6 +1041,23 @@
968
1041
  text-align: start;
969
1042
  }
970
1043
 
1044
+ /* Every nav row is a CONTROL, so it owes a visible keyboard indicator (WCAG 2.4.7) — and it
1045
+ * has to be the design system's, not the UA's. Measured in Chromium before this rule: the
1046
+ * first Tab into the shell landed on a rail item drawing Chrome's own
1047
+ * `outline: rgb(0,95,204) auto 1px`, a blue that belongs to no theme and matches nothing else
1048
+ * on the page, while every Button beside it drew the token ring. On a Slack-style shell that
1049
+ * is the FIRST thing a keyboard user sees.
1050
+ *
1051
+ * Same shape as `.app-main:focus-visible` above — suppress the UA outline, draw a token ring —
1052
+ * but NOT gated behind an opt-in width the way the region ring is. A whole-content-area frame
1053
+ * reads as a glitch and defaults off; a 2px ring hugging one 32px row is exactly the
1054
+ * affordance, so it reads the global `--focus-ring-width` and is on by default. `box-shadow`
1055
+ * rather than `outline` so the ring follows `--sidebar-nav-item-radius`. */
1056
+ .sb-nav-item:focus-visible {
1057
+ outline: none;
1058
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
1059
+ }
1060
+
971
1061
  .sb-nav-item:hover {
972
1062
  background: hsl(var(--accent));
973
1063
  color: var(--sidebar-nav-item-hover-foreground, hsl(var(--foreground)));
@@ -1016,20 +1106,36 @@
1016
1106
  white-space: nowrap;
1017
1107
  }
1018
1108
 
1109
+ /* The nav row's count pill. Its COLOUR is a knob pair, and the resting values are exactly the
1110
+ * literals it carried before (rule #44/#45) — `initial` at the token tier so the role defaults
1111
+ * re-resolve under a scoped theme, resolved here at the call site. Before the knobs the only
1112
+ * way to emphasise a row was to nest a `<Badge>` INSIDE `badge`, which produced two stacked
1113
+ * pills: a 37.11×19.14 `.sb-badge` wrapping a 25.11×19.14 `<Badge>` with its own border. */
1019
1114
  .sb-badge {
1020
1115
  display: inline-flex;
1021
1116
  min-width: 1.125rem;
1022
1117
  align-items: center;
1023
1118
  justify-content: center;
1024
1119
  border-radius: var(--radius-pill);
1025
- background: hsl(var(--secondary));
1026
- color: hsl(var(--muted-foreground));
1120
+ background: var(--sidebar-badge-background, hsl(var(--secondary)));
1121
+ color: var(--sidebar-badge-foreground, hsl(var(--muted-foreground)));
1027
1122
  font-size: var(--sidebar-badge-font-size);
1028
1123
  font-weight: var(--font-weight-medium);
1029
1124
  line-height: 1.2;
1030
1125
  padding-inline: 0.375rem;
1031
1126
  }
1032
1127
 
1128
+ /* `badgeTone="destructive"` — the count is ADDRESSED TO YOU (an @mention, a direct message),
1129
+ * not merely unread. Present-when-on (rule #44): a neutral row emits no `data-tone` at all, so
1130
+ * this rule cannot reach it and the default pill is byte-identical. GEOMETRY IS UNTOUCHED — the
1131
+ * tone moves two colours and nothing else, which is the whole point of the axis: the row's
1132
+ * height, the pill's radius and its 0.375rem inline pad stay on the base rule, so a mention row
1133
+ * and an unread row still line up in the same column. */
1134
+ .sb-badge[data-tone="destructive"] {
1135
+ background: var(--sidebar-badge-destructive-background, hsl(var(--destructive)));
1136
+ color: var(--sidebar-badge-destructive-foreground, hsl(var(--destructive-foreground)));
1137
+ }
1138
+
1033
1139
  .sb-footer {
1034
1140
  margin-top: auto;
1035
1141
  border-top: 1px solid hsl(var(--border));
@@ -1174,10 +1280,6 @@
1174
1280
  outline: none;
1175
1281
  }
1176
1282
 
1177
- .sb-user:focus-visible {
1178
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)) / 0.45);
1179
- }
1180
-
1181
1283
  .sb-user-avatar {
1182
1284
  position: relative;
1183
1285
  display: inline-flex;
@@ -1424,8 +1526,8 @@
1424
1526
 
1425
1527
  .tb-chip-icon {
1426
1528
  display: grid;
1427
- width: 1.125rem;
1428
- height: 1.125rem;
1529
+ width: var(--topbar-chip-icon-size);
1530
+ height: var(--topbar-chip-icon-size);
1429
1531
  flex: 0 0 auto;
1430
1532
  place-items: center;
1431
1533
  border-radius: calc(var(--radius) - 2px);
@@ -1452,8 +1554,8 @@
1452
1554
  }
1453
1555
 
1454
1556
  .tb-chip-caret svg {
1455
- width: 0.75rem;
1456
- height: 0.75rem;
1557
+ width: var(--topbar-caret-icon-size);
1558
+ height: var(--topbar-caret-icon-size);
1457
1559
  }
1458
1560
 
1459
1561
  .tb-search {
@@ -1480,8 +1582,8 @@
1480
1582
 
1481
1583
  .tb-search svg,
1482
1584
  .tb-icon-btn svg {
1483
- width: 1rem;
1484
- height: 1rem;
1585
+ width: var(--topbar-icon-size);
1586
+ height: var(--topbar-icon-size);
1485
1587
  }
1486
1588
 
1487
1589
  .kbd {
@@ -1521,10 +1623,6 @@
1521
1623
  outline: none;
1522
1624
  }
1523
1625
 
1524
- .tb-icon-btn:focus-visible {
1525
- box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)) / 0.45);
1526
- }
1527
-
1528
1626
  .tb-bell {
1529
1627
  position: relative;
1530
1628
  }
@@ -3,6 +3,27 @@
3
3
  */
4
4
 
5
5
  @layer components {
6
+ /* The table's base type. It used to ride on a `text-sm` utility emitted by the component; the
7
+ * font-size AND its line-height companion now live here (gh#319 — see
8
+ * tokens/components/table.css for why both are required). */
9
+ [data-slot="table"] {
10
+ font-size: var(--table-font-size);
11
+ line-height: var(--table-line-height);
12
+ }
13
+
14
+ /* Horizontal rules between rows, and the "last row draws no rule" idiom. Both must be in the
15
+ * SAME layer: while the row's rule was a `border-b` utility, no `components` selector could
16
+ * zero it on the last row however specific (the gh#412 layer contract), so the tbody had to
17
+ * carry an arbitrary-variant utility of its own. `[data-slot="table-body"] .ui-table-row`
18
+ * (0,2,0) beats `.ui-table-row` (0,1,0), so the last-row reset lands cleanly. */
19
+ .ui-table-row {
20
+ border-block-end-width: var(--table-row-border-width);
21
+ }
22
+
23
+ [data-slot="table-body"] .ui-table-row:last-child {
24
+ border-block-end-width: 0;
25
+ }
26
+
6
27
  [data-slot="table-head"] {
7
28
  height: var(--table-row-height);
8
29
  padding: var(--table-cell-padding-y) var(--table-cell-space-x);
@@ -268,10 +289,9 @@
268
289
  inset-inline-end: 0;
269
290
  }
270
291
 
271
- [data-slot="card-content"][data-flush] .ui-data-table-surface {
272
- border-width: 0;
273
- border-radius: 0;
274
- }
292
+ /* The flush frame contract lives in ONE place, next to `.ui-table-bordered` below — it has to
293
+ * cover BOTH full-bleed surfaces (`.ui-data-table-surface` and `<Table bordered>`) or the fix
294
+ * for one silently regresses the other (gh#305 → gh#306). */
275
295
 
276
296
  @media (min-width: 640px) {
277
297
  .ui-data-table-scroll {
@@ -347,17 +367,60 @@
347
367
  border-inline-end: 1px solid var(--table-border-color, hsl(var(--border)));
348
368
  }
349
369
 
350
- /* A bordered table sitting FLUSH inside a Card already has the card's frame around it —
351
- * its own outer frame doubles up as two nested rounded borders (measured on
352
- * customer-inquiries detail: TABLE 1px inside .ui-card 1px, gh#305). Drop the three edges
353
- * that coincide with the card frame, but KEEP border-block-start: the top edge meets the
354
- * CardHeader area, not the card border, and without it the header band floats with no
355
- * separating line (measured on a rental detail's 出荷伝票一覧 section, gh#306). The
356
- * vertical column rules above and TableRow's horizontal rules stay, so the grid inside
357
- * the card is unchanged. A bordered table NOT in a flush card keeps its full frame. */
358
- [data-slot="card-content"][data-flush] .ui-table-bordered {
359
- border: 0;
360
- border-block-start: 1px solid var(--table-border-color, hsl(var(--border)));
370
+ /* ── The flush full-bleed frame contract (gh#305 · gh#306) ────────────────────────────────
371
+ * A full-bleed surface inside `<CardContent flush>` already has the CARD's 1px frame around
372
+ * it, so its own outer frame doubles up as two nested rounded borders (measured on
373
+ * customer-inquiries detail: TABLE 1px inside the card's 1px, gh#305). Both full-bleed
374
+ * surfaces are covered by ONE rule: `.ui-data-table-surface` (the DataTable's own frame) and
375
+ * `.ui-table-bordered` (`<Table bordered>`). They used to be two rules a hundred lines apart
376
+ * and the gh#306 repair reached only the second the canonical
377
+ * `<Card><CardContent flush><DataTable/></CardContent></Card>` kept a frameless surface, so
378
+ * the header still floated with no line under it (measured at HEAD: surface
379
+ * border-top-width 0px).
380
+ *
381
+ * ERASE, never repaint (rule #44). The earlier repair spelled this `border: 0` followed by a
382
+ * hard-coded `border-block-start: 1px solid …` — chrome painted unconditionally, which is the
383
+ * bug pattern the rule exists to forbid: it re-drew the line even where the card already had
384
+ * one (a banded header's own bottom border → a doubled 2px rule) and even where the card frame
385
+ * itself sits (a headerless card → a doubled top edge). Here only the WIDTH is zeroed, so the
386
+ * style and colour stay whatever the surface's own border declared, and the block-start edge is
387
+ * restored only where something actually needs separating.
388
+ *
389
+ * `.ui-skeleton-table` rides along in BOTH lists on purpose. It is the placeholder the real
390
+ * DataTable swaps for, and alert-layout.css says so in as many words ("mirrors
391
+ * `.ui-data-table-surface` … so the skeleton placeholder and the real DataTable it swaps for
392
+ * sit identically"). Give the surface a divider without giving the skeleton one and that
393
+ * promise breaks: a 1px line appears the instant the data lands. (alert-layout.css keeps its
394
+ * own copy of the erase half so Skeleton is still framed correctly for a consumer who imports
395
+ * it WITHOUT this layer.) */
396
+ [data-slot="card-content"][data-flush]
397
+ :is(.ui-data-table-surface, .ui-table-bordered, .ui-skeleton-table) {
398
+ border-width: 0;
399
+ border-radius: 0;
400
+ }
401
+
402
+ /* …restored ONLY when a plain CardHeader sits DIRECTLY above the flush body. That edge is the
403
+ * one place a flush surface's border does NOT coincide with something the card already draws:
404
+ * above it is the header's whitespace, not the card frame (gh#306, measured on a rental
405
+ * detail's 出荷伝票一覧 section). Adjacent-sibling, not `:has()` on the card, so the three
406
+ * counter-cases fall out by construction and need no extra rule: no header above → the card
407
+ * frame is the top edge; a BANDED header → its own `border-bottom` is the divider; a CardBar
408
+ * above → its own `border-block` is. Width comes from a token (rules #44/#45) whose default is
409
+ * the same hairline the surface itself draws, so a service quiets the divider once instead of
410
+ * forking this file. */
411
+ [data-slot="card-header"]:not([data-banded])
412
+ + [data-slot="card-content"][data-flush]
413
+ :is(.ui-data-table-surface, .ui-table-bordered, .ui-skeleton-table) {
414
+ border-block-start-width: var(--table-flush-divider-width);
415
+ }
416
+
417
+ /* …and quieted again when a DataTable TOOLBAR sits between the header and the surface: the
418
+ * flush toolbar already draws that dividing line (see `.ui-data-table-toolbar` above), so the
419
+ * surface below it would stack a second hairline on top of it. Done by setting the token on the
420
+ * root rather than by another `border-*` rule, so it INHERITS down to whichever surface ends up
421
+ * under the toolbar and cannot get into a specificity race with the restore rule. */
422
+ [data-slot="card-content"][data-flush] .ui-data-table-root:has(.ui-data-table-toolbar) {
423
+ --table-flush-divider-width: 0px;
361
424
  }
362
425
  }
363
426
 
@@ -452,14 +515,82 @@
452
515
  }
453
516
 
454
517
  /* The compact tier lives in `godxjp-ui-responsive` — the LAST layer, declared after Tailwind in
455
- * styles/base.css — not in `@layer components`. `<table>` carries `text-sm`, a Tailwind utility,
456
- * and `utilities` outranks `components` by LAYER ORDER, so a `font-size:` re-point written here as
457
- * a component rule can never apply however specific it is: that is exactly why
518
+ * styles/base.css — not in `@layer components`. The <table> USED to carry `text-sm`, a Tailwind
519
+ * utility, and `utilities` outranks `components` by LAYER ORDER, so a `font-size:` re-point
520
+ * written as a component rule could never apply however specific it was: that is exactly why
458
521
  * `--table-action-collection-font-size-compact` was dead at every width (gh#412), leaving the
459
- * narrow frame at 14px where a 5–6 character Japanese label overflows its column measure. The
460
- * column measures move with it so the whole compact tier is decided in ONE place, and so a
461
- * consumer utility on the table can't half-collapse it either. See styles/base.css · THE LAYER
462
- * CONTRACT. */
522
+ * narrow frame at 14px where a 5–6 character Japanese label overflows its column measure.
523
+ * gh#319 moved the base type off that utility and onto `[data-slot="table"]` above, but the
524
+ * compact tier STAYS here: a CONSUMER's own `text-*` utility on the table would otherwise
525
+ * half-collapse it, and this keeps the whole tier — type AND column measures — decided in ONE
526
+ * place. See styles/base.css · THE LAYER CONTRACT. */
527
+ @layer components {
528
+ /* DATA TABLE — the pieces around the already-tokenized cell rhythm (#319).
529
+ *
530
+ * These belong in `components`, not in the responsive layer above: an append placed them there
531
+ * by mistake (it targeted the file's last `}`, which closes the layer, not the layer's own end)
532
+ * and `table-action-collection.test.tsx` catches exactly that — the responsive layer is
533
+ * reserved for media-queried rules.
534
+ *
535
+ * Four of them are scoped through `.ui-data-table-root` rather than left bare, because the last
536
+ * layer was doing work for them: the icon rules sit on svgs inside real <Button>s where
537
+ * `.ui-button svg` (0,1,1) outranks a bare class, and the skeleton chip's radius competes with
538
+ * `.ui-skeleton-block` from the later-imported alert-layout.css. Demoting them without raising
539
+ * specificity would have shrunk the toolbar/pagination glyphs and re-rounded the chips. */
540
+
541
+ /* DATA TABLE — the pieces around the already-tokenized cell rhythm (#319). */
542
+
543
+ /* The select column holds only a checkbox, so it takes a fixed narrow width rather than a
544
+ * share of the flexible content columns. */
545
+ .ui-data-table-select-column {
546
+ inline-size: var(--table-select-column-width);
547
+ }
548
+
549
+ /* A sticky header floats over the scrolled rows, so it joins the one system overlay layer
550
+ * rather than carrying a private z-index that a portaled surface could out-rank. */
551
+ .ui-data-table-sticky-header {
552
+ position: sticky;
553
+ inset-block-start: 0;
554
+ z-index: 10;
555
+ }
556
+
557
+ .ui-data-table-sort-icon {
558
+ inline-size: var(--table-sort-icon-size);
559
+ block-size: var(--table-sort-icon-size);
560
+ }
561
+
562
+ .ui-data-table-root .ui-data-table-toolbar-icon {
563
+ inline-size: var(--table-toolbar-icon-size);
564
+ block-size: var(--table-toolbar-icon-size);
565
+ flex-shrink: 0;
566
+ }
567
+
568
+ .ui-data-table-root .ui-data-table-pagination-icon {
569
+ inline-size: var(--table-pagination-icon-size);
570
+ block-size: var(--table-pagination-icon-size);
571
+ }
572
+
573
+ .ui-data-table-pagination-text {
574
+ font-size: var(--table-pagination-font-size);
575
+ color: hsl(var(--muted-foreground));
576
+ }
577
+
578
+ /* Skeletons mimic the real content: a checkbox square and a text line at cap height. */
579
+ .ui-data-table-root .ui-data-table-skeleton-check {
580
+ inline-size: var(--table-skeleton-check-size);
581
+ block-size: var(--table-skeleton-check-size);
582
+ border-radius: var(--table-skeleton-radius);
583
+ }
584
+
585
+ .ui-data-table-root .ui-data-table-skeleton-line {
586
+ block-size: var(--table-skeleton-line-height);
587
+ }
588
+
589
+ .ui-data-table-sort-button {
590
+ border-radius: var(--table-skeleton-radius);
591
+ }
592
+ }
593
+
463
594
  @layer godxjp-ui-responsive {
464
595
  /* Below the step the SOURCE measures are re-pointed to their compact tier (and the type / row
465
596
  * density with them); nothing else moves. One block per canonical step — a media/container
@@ -0,0 +1,59 @@
1
+ /* TOGGLE · counter pill + pressed-state chrome (gh#312).
2
+ *
3
+ * The Toggle BOX itself (heights, padding, variants, the pressed fill) lives in
4
+ * `styles/control.css` alongside every other control — import `@godxjp/ui/styles/control` for it.
5
+ * This layer is only the counter pill Toggle shares with Button, plus the non-colour encodings of
6
+ * the pressed state; `@godxjp/ui/styles` imports it right after control.css so its pressed-state
7
+ * rules land later in the cascade at equal specificity.
8
+ *
9
+ * Every constant here is a token declared in `tokens/components/toggle.css`, where the share-vs-
10
+ * split reasoning and the measured contrast numbers live. The geometry knobs carry Button's exact
11
+ * counter values off the same primitive scale (asserted by the token-parity test), so the two
12
+ * pills are the same size; only the COLOUR knobs are deliberately Toggle's own, because Toggle's
13
+ * pill sits on a surface that inverts.
14
+ */
15
+
16
+ @layer components {
17
+ .ui-toggle-count {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ min-inline-size: var(--toggle-count-min-width);
22
+ margin-inline-start: var(--toggle-count-gap);
23
+ border-radius: var(--toggle-count-radius);
24
+ padding-inline: var(--toggle-count-space-inline);
25
+ background: var(--toggle-count-background, hsl(var(--muted)));
26
+ color: var(--toggle-count-color, hsl(var(--foreground)));
27
+ font-size: var(--toggle-count-font-size);
28
+ line-height: 1;
29
+ font-variant-numeric: tabular-nums;
30
+ }
31
+
32
+ /* PRESSED — the state is never carried by hue alone (WCAG 1.4.1). Three independent encodings:
33
+ * the chip's own fill/label invert (control.css), the pill inverts from a quiet near-invisible
34
+ * chip to a solid one, and the forced-colors rule below. `aria-pressed` carries it
35
+ * programmatically; nothing appends "(selected)" to the accessible name. */
36
+ .ui-toggle[data-state="on"] {
37
+ border-color: var(--toggle-pressed-border-color, hsl(var(--primary)));
38
+ }
39
+
40
+ .ui-toggle[data-state="on"] .ui-toggle-count {
41
+ background: var(--toggle-pressed-count-background, hsl(var(--primary-foreground)));
42
+ color: var(--toggle-pressed-count-color, hsl(var(--primary)));
43
+ }
44
+
45
+ /* Forced colors flattens every background, so neither inversion distinguishes the states any
46
+ * more. An outline on the pressed pill restores a structural difference at zero layout cost,
47
+ * and the chip's border follows the system Highlight — the `default` variant's border is
48
+ * `transparent` when unpressed, and forced-colors preserves transparency, so the two states
49
+ * stay structurally different there too. */
50
+ @media (forced-colors: active) {
51
+ .ui-toggle[data-state="on"] {
52
+ border-color: Highlight;
53
+ }
54
+
55
+ .ui-toggle[data-state="on"] .ui-toggle-count {
56
+ outline: var(--toggle-count-forced-outline-width) solid currentColor;
57
+ }
58
+ }
59
+ }
@@ -7,6 +7,7 @@
7
7
  @import "./semantic/layout.css";
8
8
  @import "./axes.css";
9
9
  @import "./components/control.css";
10
+ @import "./components/toggle.css";
10
11
  @import "./components/form.css";
11
12
  @import "./components/card.css";
12
13
  @import "./components/chart.css";
@@ -18,11 +19,15 @@
18
19
  @import "./components/badge.css";
19
20
  @import "./components/navigation.css";
20
21
  @import "./components/data-display.css";
22
+ @import "./components/scroll-area.css";
21
23
  @import "./components/list-row.css";
22
24
  @import "./components/logo.css";
23
25
  @import "./components/email.css";
24
26
  @import "./components/data-entry.css";
27
+ @import "./components/upload.css";
25
28
  @import "./components/shell.css";
29
+ @import "./components/separator.css";
26
30
  @import "./components/sidebar.css";
27
31
  @import "./components/legal-document.css";
28
32
  @import "./components/error-surface.css";
33
+ @import "./components/activity.css";
@@ -0,0 +1,64 @@
1
+ /*
2
+ * ACTIVITY — the ambient/looping motion primitive (gh#313).
3
+ *
4
+ * The loop counterpart to `Reveal`: `Reveal` runs ONCE on mount, `Activity` runs FOREVER while
5
+ * something is happening elsewhere (someone typing, a sync in flight, a response streaming, a
6
+ * recording running). Every value the loop reads is a knob (cardinal rule #45) so a service theme
7
+ * retunes the whole ambient feel in one place instead of forking a `@keyframes`.
8
+ *
9
+ * TIMING LIVES IN THE MOTION TIER, NOT HERE. `--duration-loop`, `--activity-interval` and
10
+ * `--activity-stagger-step` are declared in `src/tokens/foundation.css` beside `--duration-slow`
11
+ * and `--reveal-stagger-step`: they are the cycle members of the motion tier, they must sit next
12
+ * to their one-shot siblings to stay comparable, and `check-token-tiers`' component-token name
13
+ * shape (`--{component}-{part}-{property}`) has no property word for a duration. Geometry, colour
14
+ * and alpha — everything the shape rule DOES cover — live here.
15
+ *
16
+ * Defaults are the QUIETEST ambient state (cardinal rule #44): muted ink, a small mark, low travel.
17
+ */
18
+
19
+ :root {
20
+ /* Mark geometry. `em`-based so the whole mark tracks `--activity-font-size-*` (and therefore
21
+ * `--font-size-base` and the density axis) instead of pinning a px diameter per size step. */
22
+ --activity-mark-size: 0.25em;
23
+ /* Travel of one `dots` step — the loop counterpart to `--reveal-distance`, named to match.
24
+ * It is ALSO the height the mark row reserves, so the indicator appearing under a composer
25
+ * never shifts the row. */
26
+ --activity-mark-offset: 0.25em;
27
+ /* Resting opacity of a dot at the bottom of its cycle. Never 0: a dot that vanishes reads as a
28
+ * rendering bug at the trough, and the reduced-motion resting state must stay fully legible. */
29
+ --activity-mark-rest-alpha: 0.35;
30
+ /* The single `pulse` mark — a breathing dot (live / recording), bigger than one `dots` dot
31
+ * because it carries the whole affordance alone. */
32
+ --activity-pulse-mark-size: 0.5em;
33
+
34
+ /* Mark-to-mark and mark-to-label gap. */
35
+ --activity-gap: var(--space-1);
36
+
37
+ /* Root size ladder — the mark is `em`-based, so this ONE value scales the whole indicator.
38
+ * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically
39
+ * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias). */
40
+ --activity-font-size-xs: var(--font-size-2xs);
41
+ --activity-font-size-sm: var(--font-size-xs);
42
+ --activity-font-size-md: var(--font-size-sm);
43
+ --activity-font-size-lg: var(--font-size-lg);
44
+
45
+ /* Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a
46
+ * label in a row, so `100%` would eat the label's space. */
47
+ --activity-bar-width: 4rem;
48
+ --activity-bar-radius: var(--radius-pill);
49
+ --activity-bar-segment-width: 40%;
50
+ --activity-bar-track-alpha: 0.2;
51
+ /* Bar THICKNESS, declared last in the group because it carries the axis exemption below.
52
+ * Drawn at TEXT scale, not band scale: the bar sits inline beside its label, so it tracks the
53
+ * type it accompanies instead of a fixed step (gh#324). */
54
+ /* scale-exempt: an em thickness that must track the label beside it, not a fixed band step */
55
+ --activity-bar-height: 0.25em;
56
+
57
+ /* Mark colour. ROLE-MIRROR KNOB ⇒ declared `initial` here with the role default at the CALL SITE
58
+ * (`hsl(var(--activity-color, var(--muted-foreground)))` in styles/motion.css). Written as
59
+ * `--activity-color: var(--muted-foreground)` it would freeze at the `:root` role value and a
60
+ * scoped `[data-tenant]` / `.dark` override of `--muted-foreground` would never reach it.
61
+ * @see docs/TOKENS.md · "Role-mirror knobs MUST be `initial`".
62
+ * Documented default: --activity-color = var(--muted-foreground) (tone="muted"). */
63
+ --activity-color: initial;
64
+ }
@@ -12,4 +12,11 @@
12
12
  * remaps --text-xs but never that companion). Same unitless ratio here keeps the default
13
13
  * badge pixel-identical now that badge-layout.css owns the type metrics. */
14
14
  --badge-line-height: calc(1 / 0.75);
15
+ /* Leading/trailing glyph inside the pill. It was a bare `0.75rem` in badge-layout.css with no
16
+ * token at all, so a service could only resize it with `!important` or a forked stylesheet —
17
+ * the two routes the icon axis's tier 2 exists to abolish (gh#326). Same step, so nothing
18
+ * moves. Matches --badge-font-size's step by design: the glyph reads as a character in the
19
+ * label's run, so the two retune together. Deliberately NOT --scaling-multiplied — the literal
20
+ * it replaces did not track density. */
21
+ --badge-icon-size: var(--icon-size-xs);
15
22
  }