@nxgt/material 3.10.2 → 3.12.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 (158) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +10 -11
  3. package/dist/{async-slice-DDjaJY_F.js → async-slice-4qbER86I.js} +1 -1
  4. package/dist/button-BNjw-pnE.js +327 -0
  5. package/dist/button-group-context-CaP0X5p_.js +5 -0
  6. package/dist/{button-tones-BgZJ1tkD.js → button-tones-BwuVvAmd.js} +9 -1
  7. package/dist/{callout-view-DdJPdFrP.js → callout-view-C6voDS00.js} +1 -1
  8. package/dist/charts.js +5 -5
  9. package/dist/{chip-DpySRqCy.js → chip-DLgQTJBZ.js} +4 -4
  10. package/dist/{code-editor-BiA14xua.js → code-editor-BZbaYLvo.js} +8 -8
  11. package/dist/code-editor.js +7 -7
  12. package/dist/{code-language-items-C_dJ6GVs.js → code-language-items-BT5BO5w6.js} +2 -2
  13. package/dist/components/base.css +21 -6
  14. package/dist/components/components.css +6 -2
  15. package/dist/components/type-scale.css +194 -0
  16. package/dist/components/ui/actions/button/button.css +156 -0
  17. package/dist/components/ui/actions/button-group/button-group-standard.css +131 -0
  18. package/dist/components/ui/actions/button-group/button-group.css +4 -1
  19. package/dist/components/ui/actions/icon-button/icon-button.css +115 -0
  20. package/dist/components/ui/actions/morph-fill.css +62 -0
  21. package/dist/components/ui/feedback/toast/toast-expressive.css +17 -0
  22. package/dist/components/ui/layout/app-shell/activity-backdrop-shapes.css +49 -0
  23. package/dist/components/ui/layout/app-shell/activity-backdrop.css +0 -17
  24. package/dist/components/ui/layout/sidebar/sidebar-tokens.css +7 -2
  25. package/dist/{components-BkF9N-Yx.js → components-yx1t_5_P.js} +13740 -13422
  26. package/dist/components.js +24 -24
  27. package/dist/{dialog-CkIYpJXk.js → dialog-BQp5IdFl.js} +147 -147
  28. package/dist/dnd.js +1 -1
  29. package/dist/{dropdown-menu-BC5Tbhb9.js → dropdown-menu-BsH9W_3C.js} +3 -3
  30. package/dist/editor-viewer.js +1 -1
  31. package/dist/editor.js +13 -13
  32. package/dist/{empty-state-SeAg2TOX.js → empty-state-WaU4-T2c.js} +5 -5
  33. package/dist/escape-owner-C4W_sOAU.js +116 -0
  34. package/dist/{field-blur-DPLsMufs.js → field-blur-CJHzVSh9.js} +1 -1
  35. package/dist/{field-blur-context-1ZwJgsHE.js → field-blur-context-Cft9s7Hg.js} +2 -2
  36. package/dist/{hover-card-Cox-tPDz.js → hover-card-C0fw2kLK.js} +72 -71
  37. package/dist/i18n.js +12 -12
  38. package/dist/{icon-badge-q3Ve8gra.js → icon-badge-DUAq9f7H.js} +4 -4
  39. package/dist/icon-button-ifTpXt0A.js +355 -0
  40. package/dist/{illustration-BfHRpVzM.js → illustration-BmQmvW_4.js} +1 -1
  41. package/dist/illustrations.js +2 -2
  42. package/dist/lib/components/ui/actions/button/button-tones.d.ts +16 -0
  43. package/dist/lib/components/ui/actions/button/button.d.ts +13 -0
  44. package/dist/lib/components/ui/actions/button/button.variants.d.ts +4 -3
  45. package/dist/lib/components/ui/actions/button-group/button-group-context.d.ts +16 -0
  46. package/dist/lib/components/ui/actions/button-group/button-group-reset.d.ts +13 -0
  47. package/dist/lib/components/ui/actions/button-group/button-group.d.ts +19 -2
  48. package/dist/lib/components/ui/actions/icon-button/icon-button-shape.d.ts +3 -2
  49. package/dist/lib/components/ui/actions/icon-button/icon-button.d.ts +18 -4
  50. package/dist/lib/components/ui/actions/icon-button/icon-button.variants.d.ts +34 -4
  51. package/dist/lib/components/ui/display/badge/badge.d.ts +7 -2
  52. package/dist/lib/components/ui/display/entity-header/entity-header.d.ts +9 -1
  53. package/dist/lib/components/ui/display/entity-header/entity-stats.d.ts +16 -0
  54. package/dist/lib/components/ui/display/entity-header/partials/entity-header-actions.d.ts +6 -0
  55. package/dist/lib/components/ui/display/entity-header/partials/entity-header-body.d.ts +14 -0
  56. package/dist/lib/components/ui/display/entity-header/partials/entity-header-icon.d.ts +6 -0
  57. package/dist/lib/components/ui/display/entity-header/partials/shown.d.ts +2 -0
  58. package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +8 -1
  59. package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +2 -1
  60. package/dist/lib/components/ui/display/stat-card/index.d.ts +1 -1
  61. package/dist/lib/components/ui/display/stat-card/partials/stat-card-compact.d.ts +12 -0
  62. package/dist/lib/components/ui/display/stat-card/stat-card-classes.d.ts +3 -2
  63. package/dist/lib/components/ui/display/stat-card/stat-card.d.ts +2 -2
  64. package/dist/lib/components/ui/display/stat-card/types.d.ts +9 -0
  65. package/dist/lib/components/ui/feedback/alert/alert-variants.d.ts +18 -0
  66. package/dist/lib/components/ui/feedback/alert/alert.d.ts +2 -6
  67. package/dist/lib/components/ui/feedback/typing-indicator/typing-indicator.d.ts +1 -1
  68. package/dist/lib/components/ui/fields/slider/split-slider-props.d.ts +1 -1
  69. package/dist/lib/components/ui/fields/slider/use-slider-control.d.ts +1 -1
  70. package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +17 -0
  71. package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +3 -106
  72. package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +113 -0
  73. package/dist/lib/components/ui/layout/app-shell/app-breadcrumb.d.ts +2 -1
  74. package/dist/lib/components/ui/layout/app-shell/app-sidebar-icon-item.d.ts +13 -0
  75. package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +11 -0
  76. package/dist/lib/components/ui/layout/app-shell/keep-shapes-inside.d.ts +6 -0
  77. package/dist/lib/components/ui/layout/app-shell/observe-layout.d.ts +11 -0
  78. package/dist/lib/components/ui/layout/app-shell/shape-units.d.ts +16 -0
  79. package/dist/lib/components/ui/layout/app-shell/shapes-clear-content.d.ts +13 -0
  80. package/dist/lib/components/ui/layout/app-shell/types.d.ts +10 -0
  81. package/dist/lib/components/ui/layout/app-shell/use-keep-shapes-inside.d.ts +14 -0
  82. package/dist/lib/components/ui/layout/app-shell/watch-content.d.ts +19 -0
  83. package/dist/lib/components/ui/overlays/dialog/dialog-variants.d.ts +1 -1
  84. package/dist/lib/components/ui/primitives/toggle/toggle.d.ts +1 -1
  85. package/dist/lib/components/ui/primitives/typography/typography.d.ts +17 -7
  86. package/dist/lib/components/ui/primitives/typography/typography.variants.d.ts +55 -0
  87. package/dist/lib/components/ui/selection/choice-group/use-choice-group-field.d.ts +2 -2
  88. package/dist/lib/components/ui/tones.d.ts +1 -1
  89. package/dist/lib/i18n/translator.d.ts +1 -1
  90. package/dist/lib.js +2 -2
  91. package/dist/{line-path-BQeLQZ-P.js → line-path-C5pZGC49.js} +1 -1
  92. package/dist/main.js +29 -29
  93. package/dist/map.js +9 -9
  94. package/dist/material.css +1 -1
  95. package/dist/pdf-reader.js +1 -1
  96. package/dist/print.js +1 -1
  97. package/dist/{public-CtpsxV4I.js → public-B0Nq_yYV.js} +524 -524
  98. package/dist/{render-json-DyFYFIkZ.js → render-json-BOhvPnSv.js} +1 -1
  99. package/dist/{rich-code-block-C2Fp_FDU.js → rich-code-block-v7WmeZp9.js} +5 -5
  100. package/dist/{rich-text-viewer-LO1IZCWs.js → rich-text-viewer-W15XIwah.js} +2 -2
  101. package/dist/{skeleton-Bmp4gWAH.js → skeleton-DSdtr2qc.js} +1 -1
  102. package/dist/{sortable-provider-pVaEZzty.js → sortable-provider-CgDTolQR.js} +3 -3
  103. package/dist/{spinner-CsHcmcpF.js → spinner-BtSIYVrC.js} +3 -3
  104. package/dist/{state-scenes-SGNyZjTz.js → state-scenes-BX03WmSu.js} +1 -1
  105. package/dist/styles.css +2 -0
  106. package/dist/theme-picker.js +3 -3
  107. package/dist/{theme-toggle-oU-fh6WE.js → theme-toggle-B2Pehdhn.js} +2 -2
  108. package/dist/{toggle-group-j8zaxEg1.js → toggle-group-BMJNcFAC.js} +1 -1
  109. package/dist/{toolbar-items-CqHvlTU8.js → toolbar-items-rUHdVqAq.js} +399 -398
  110. package/dist/{tooltip-rhbqNmr3.js → tooltip-CkttDIab.js} +1 -1
  111. package/dist/{translate-material-CGZljjp1.js → translate-material-Bd_KgfB8.js} +1 -1
  112. package/dist/{translator-DCYO4Ij3.js → translator-Dje8HrNz.js} +5 -5
  113. package/dist/{use-chat-composer-nWWfVyrK.js → use-chat-composer-B0_STpQN.js} +4173 -4173
  114. package/dist/{use-material-translation-DkxcLbLM.js → use-material-translation-B3V_bz-5.js} +1 -1
  115. package/dist/{utils-Bcd2Z3ea.js → utils-BLfntITJ.js} +14 -1
  116. package/docs/components/buttons-and-actions.md +64 -15
  117. package/docs/components/data-display.md +40 -9
  118. package/docs/components/feedback-and-status.md +12 -1
  119. package/docs/components/navigation-and-layout.md +4 -3
  120. package/docs/components/primitives.md +52 -10
  121. package/docs/guides/app-shell.md +52 -8
  122. package/docs/guides/backgrounds.md +2 -0
  123. package/docs/guides/theming.md +55 -3
  124. package/docs/previews/buttons-actions-button.webp +0 -0
  125. package/docs/previews/buttons-actions-iconbutton.webp +0 -0
  126. package/docs/previews/data-display-badge.webp +0 -0
  127. package/docs/previews/data-display-entityheader.webp +0 -0
  128. package/docs/previews/data-display-listtile.webp +0 -0
  129. package/docs/previews/data-display-statcard.webp +0 -0
  130. package/docs/previews/feedback-status-banner.webp +0 -0
  131. package/docs/previews/navigation-layout-appshell-footer-icon.webp +0 -0
  132. package/docs/previews/navigation-layout-appshell-menu-badge.webp +0 -0
  133. package/docs/previews/navigation-layout-appshell.webp +0 -0
  134. package/docs/previews/navigation-layout-sidebar.webp +0 -0
  135. package/docs/previews/primitives-typography.webp +0 -0
  136. package/docs/roadmap.md +53 -4
  137. package/docs/troubleshooting.md +75 -4
  138. package/lib/components/base.css +21 -6
  139. package/lib/components/components.css +6 -2
  140. package/lib/components/type-scale.css +194 -0
  141. package/lib/components/ui/actions/button/button.css +156 -0
  142. package/lib/components/ui/actions/button-group/button-group-standard.css +131 -0
  143. package/lib/components/ui/actions/button-group/button-group.css +4 -1
  144. package/lib/components/ui/actions/icon-button/icon-button.css +115 -0
  145. package/lib/components/ui/actions/morph-fill.css +62 -0
  146. package/lib/components/ui/feedback/toast/toast-expressive.css +17 -0
  147. package/lib/components/ui/layout/app-shell/activity-backdrop-shapes.css +49 -0
  148. package/lib/components/ui/layout/app-shell/activity-backdrop.css +0 -17
  149. package/lib/components/ui/layout/sidebar/sidebar-tokens.css +7 -2
  150. package/lib/styles.css +2 -0
  151. package/package.json +2 -1
  152. package/dist/button-DbfdITBB.js +0 -322
  153. package/dist/components/ui/actions/icon-button/icon-button-morph-media.css +0 -65
  154. package/dist/components/ui/actions/icon-button/icon-button-morph.css +0 -152
  155. package/dist/escape-owner-CripBce_.js +0 -57
  156. package/dist/icon-button-DJcWpIhu.js +0 -319
  157. package/lib/components/ui/actions/icon-button/icon-button-morph-media.css +0 -65
  158. package/lib/components/ui/actions/icon-button/icon-button-morph.css +0 -152
@@ -0,0 +1,156 @@
1
+ /* Button (Material 3 Expressive): the radius is a variable of the height
2
+ * (`--btn-h`, set by the size, 36px without one), a round button is a pill
3
+ * (`--btn-h / 2`, never 999px, so it can morph) and a square one has the
4
+ * size's `--btn-sq`. Pressed, a button squeezes toward the other shape and
5
+ * scales to .97 (.985 at 2xl); selected (`data-selected`) it takes the other
6
+ * shape, and the fill of morph-fill.css. The radius springs on `spatial-fast`
7
+ * (every corner stays >= 4px at the overshoot, scripts/button-radius.spec.ts);
8
+ * colour and outline change on `effects-fast`.
9
+ *
10
+ * Rules on `:where(.nxgt-btn)` have no specificity, so an app's utility
11
+ * (`rounded-md`, `text-lg`) still wins. Toolbar items keep the Toolbar's own
12
+ * look. `.is-press` freezes the pressed state for the stories (as the Toolbar's
13
+ * does). The class is on `buttonVariants` too: Pagination, AlertDialog and the
14
+ * calendar's date picker get the same look. */
15
+ @layer utilities {
16
+ :where(.nxgt-btn) {
17
+ --btn-round: calc(var(--btn-h, 36px) / 2);
18
+ --btn-square: var(--btn-sq, 10px);
19
+ position: relative;
20
+ border-radius: var(--btn-rad, var(--btn-round));
21
+ font-size: var(--btn-fs, 0.875rem);
22
+ line-height: var(--btn-lh, 1.25rem);
23
+ letter-spacing: var(--btn-ls, normal);
24
+ font-weight: var(--btn-fw, 500);
25
+ transition:
26
+ border-radius var(--nxgt-motion-spatial-fast-duration)
27
+ var(--nxgt-motion-spatial-fast),
28
+ padding-inline var(--nxgt-motion-spatial-fast-duration)
29
+ var(--nxgt-motion-spatial-fast),
30
+ width var(--nxgt-motion-spatial-fast-duration)
31
+ var(--nxgt-motion-spatial-fast),
32
+ transform var(--nxgt-motion-spatial-fast-duration)
33
+ var(--nxgt-motion-spatial-fast),
34
+ background-color var(--nxgt-motion-effects-fast-duration)
35
+ var(--nxgt-motion-effects-fast),
36
+ color var(--nxgt-motion-effects-fast-duration)
37
+ var(--nxgt-motion-effects-fast),
38
+ box-shadow var(--nxgt-motion-effects-fast-duration)
39
+ var(--nxgt-motion-effects-fast),
40
+ outline-color var(--nxgt-motion-effects-fast-duration)
41
+ var(--nxgt-motion-effects-fast);
42
+ }
43
+
44
+ /* The label is what is cut with an ellipsis, not the button (so the
45
+ * focus outline and the hit area are never clipped). */
46
+ :where(.nxgt-btn > [data-slot="button-label"]) {
47
+ overflow: hidden;
48
+ text-overflow: ellipsis;
49
+ white-space: nowrap;
50
+ min-width: 0;
51
+ }
52
+
53
+ :where(.nxgt-btn:not(.nxgt-toolbar-item, [data-toolbar-item])):focus-visible {
54
+ outline: 2px solid var(--btn-focus, var(--color-ring));
55
+ outline-offset: 2px;
56
+ }
57
+
58
+ /* Shape: round (default) rests as a pill, pressed it squares; square rests
59
+ * square, pressed it rounds. Selected is the other shape. */
60
+ :where(.nxgt-btn:not(.nxgt-toolbar-item, [data-toolbar-item])) {
61
+ --btn-rest: var(--btn-round);
62
+ --btn-press: var(--btn-square);
63
+ --btn-flip: var(--btn-square);
64
+ border-radius: var(--btn-rad, var(--btn-rest));
65
+
66
+ &[data-shape="square"] {
67
+ --btn-rest: var(--btn-square);
68
+ --btn-press: var(--btn-round);
69
+ --btn-flip: var(--btn-round);
70
+ }
71
+ &[data-selected] {
72
+ --btn-rad: var(--btn-flip);
73
+ }
74
+ &:is(:active, .is-press):not(:disabled, [aria-busy="true"]) {
75
+ --btn-rad: var(--btn-press);
76
+ transform: scale(var(--btn-scale, 0.97));
77
+ }
78
+ /* A pressed selected button goes back to where a click leads. */
79
+ &[data-selected]:is(:active, .is-press):not(:disabled, [aria-busy="true"]) {
80
+ --btn-rad: var(--btn-rest);
81
+ }
82
+ }
83
+
84
+ /* Disabled: 40 % text whatever the variant and colour, and a neutral 12 %
85
+ * fill except on ghost and link (`nxgt-btn-bare`), which stay transparent.
86
+ * Doubled class: it beats the variant's colour utilities. */
87
+ .nxgt-btn.nxgt-btn:disabled:not(.nxgt-toolbar-item, [data-toolbar-item]) {
88
+ color: color-mix(in oklab, var(--color-foreground) 40%, transparent);
89
+ box-shadow: none;
90
+ text-decoration: none;
91
+ }
92
+ .nxgt-btn.nxgt-btn:disabled:not(
93
+ .nxgt-toolbar-item,
94
+ [data-toolbar-item],
95
+ .nxgt-btn-bare
96
+ ) {
97
+ background-color: color-mix(
98
+ in oklab,
99
+ var(--color-foreground) 12%,
100
+ transparent
101
+ );
102
+ }
103
+ .nxgt-btn.nxgt-btn:disabled:not(.nxgt-toolbar-item, [data-toolbar-item]) svg {
104
+ color: color-mix(in oklab, var(--color-foreground) 40%, transparent);
105
+ }
106
+
107
+ /* 44px of hit area on a coarse pointer, whatever the size. */
108
+ @media (pointer: coarse) {
109
+ :where(.nxgt-btn:not(.nxgt-toolbar-item, [data-toolbar-item]))::after {
110
+ content: "";
111
+ position: absolute;
112
+ inset-block: min(0px, calc((100% - 44px) / 2));
113
+ inset-inline: min(0px, calc((100% - 44px) / 2));
114
+ }
115
+ }
116
+
117
+ /* Safari has no `overflow-clip-margin`: an `asChild` host's `overflow: clip`
118
+ * would cut the 44px hit area above, so on a coarse pointer it does not
119
+ * clip (and its text has no ellipsis: put a label that needs one in a span). */
120
+ @supports not (overflow-clip-margin: 0px) {
121
+ @media (pointer: coarse) {
122
+ .nxgt-btn.nxgt-btn-as-child {
123
+ overflow: visible;
124
+ }
125
+ }
126
+ }
127
+
128
+ /* Reduced motion: colour and fill only, no squeeze. */
129
+ @media (prefers-reduced-motion: reduce) {
130
+ :where(.nxgt-btn:not(.nxgt-toolbar-item, [data-toolbar-item])) {
131
+ &:is(:active, .is-press):not(:disabled, [aria-busy="true"]) {
132
+ --btn-rad: var(--btn-rest);
133
+ transform: none;
134
+ }
135
+ &[data-selected]:is(:active, .is-press):not(
136
+ :disabled,
137
+ [aria-busy="true"]
138
+ ) {
139
+ --btn-rad: var(--btn-flip);
140
+ }
141
+ }
142
+ }
143
+
144
+ /* Forced colours: the outlined line is an inset shadow, which they remove. */
145
+ @media (forced-colors: active) {
146
+ .nxgt-btn[data-variant="outlined"]:not(
147
+ .nxgt-toolbar-item,
148
+ [data-toolbar-item],
149
+ :disabled,
150
+ :focus-visible
151
+ ) {
152
+ outline: 1.5px solid ButtonText;
153
+ outline-offset: -1.5px;
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,131 @@
1
+ /* ButtonGroup `look="standard"` (Material 3 Expressive): a row of buttons 8px
2
+ * apart that keep their own look. A pressed one widens by `--bg-d` on each
3
+ * side and its neighbours give the same width back (half each with two, the
4
+ * whole with one), so the group's width never changes: the toggle group's
5
+ * connected squish (toggle-group-squish.css) with the group's own numbers.
6
+ * The widening is `--btn-dx`, which Button reads in its padding (button.variants)
7
+ * and which IconButton gets as width here; both spring on `spatial-fast`.
8
+ *
9
+ * `--bg-d` is the group's, from its `size`, and is the most a button of that
10
+ * size can give back at the end of the group with its padding staying above 0
11
+ * through the spring's ~10 % overshoot (xs 7, sm 10, default 11, 14 from lg),
12
+ * 10 without a `size`. A child with a `size` of its own keeps it,
13
+ * and then trades the group's amount, not its own. `.is-press` freezes a
14
+ * pressed child for the stories, as button.css does.
15
+ *
16
+ * Coarse pointer: the gap is max(8px, 44px - the size's height), so every
17
+ * button's 44px hit area (button.css) fits without touching its neighbour's
18
+ * (each reaches half the gap past its edge), across and between wrapped rows.
19
+ * Reduced motion: no exchange. */
20
+ @layer utilities {
21
+ [data-slot="button-group"][data-look="standard"] {
22
+ --bg-gap: 8px;
23
+ --bg-d: 10px;
24
+ --bg-h: 36px;
25
+ display: inline-flex;
26
+ flex-wrap: wrap;
27
+ align-items: center;
28
+ gap: var(--bg-gap);
29
+ max-width: 100%;
30
+
31
+ &[data-size="xs"] {
32
+ --bg-d: 7px;
33
+ --bg-h: 24px;
34
+ }
35
+ &[data-size="sm"] {
36
+ --bg-d: 10px;
37
+ --bg-h: 32px;
38
+ }
39
+ &[data-size="default"] {
40
+ --bg-d: 11px;
41
+ --bg-h: 36px;
42
+ }
43
+ &[data-size="lg"] {
44
+ --bg-d: 14px;
45
+ --bg-h: 40px;
46
+ }
47
+ &[data-size="xl"] {
48
+ --bg-d: 14px;
49
+ --bg-h: 56px;
50
+ }
51
+ }
52
+
53
+ /* The pressed child widens ... */
54
+ [data-slot="button-group"][data-look="standard"]
55
+ > :is(:active, .is-press):not(:disabled, [aria-busy="true"]) {
56
+ --btn-dx: var(--bg-d);
57
+ }
58
+ /* ... its neighbours give half each ... */
59
+ [data-slot="button-group"][data-look="standard"]
60
+ > :has(+ :is(:active, .is-press):not(:disabled, [aria-busy="true"])),
61
+ [data-slot="button-group"][data-look="standard"]
62
+ > :is(:active, .is-press):not(:disabled, [aria-busy="true"])
63
+ + * {
64
+ --btn-dx: calc(var(--bg-d) / -2);
65
+ }
66
+ /* ... or the whole of it when it is the only one (the pressed child is
67
+ * at an end). */
68
+ [data-slot="button-group"][data-look="standard"]
69
+ > :first-child:is(:active, .is-press):not(:disabled, [aria-busy="true"])
70
+ + *,
71
+ [data-slot="button-group"][data-look="standard"]
72
+ > :has(
73
+ + :last-child:is(:active, .is-press):not(:disabled, [aria-busy="true"])
74
+ ) {
75
+ --btn-dx: calc(var(--bg-d) * -1);
76
+ }
77
+
78
+ /* An IconButton, and a Button of the icon sizes, have a width of their
79
+ * own (a factor of the height): the exchange is added to it. A pill toggle
80
+ * keeps its extra 14px. A shaped one does not trade. */
81
+ [data-slot="button-group"][data-look="standard"]
82
+ > [data-icon-button]:not(
83
+ [data-shape],
84
+ .nxgt-toolbar-item,
85
+ [data-toolbar-item]
86
+ ),
87
+ [data-slot="button-group"][data-look="standard"]
88
+ > [data-slot="button"]:is([data-size="icon"], [data-size^="icon-"]) {
89
+ width: calc(
90
+ var(--btn-h, 2.25rem) *
91
+ var(--ib-k, 1) +
92
+ 2 *
93
+ var(--btn-dx, 0px)
94
+ );
95
+ /* A neighbour never gives up so much that its icon sticks out: its
96
+ * floor is the icon and 4px each side, never above its width at rest
97
+ * (an icon-only one at the end of the group, which gives the whole
98
+ * exchange, may then give back less than asked, and the group widens by
99
+ * the difference). */
100
+ min-width: min(
101
+ calc(var(--btn-h, 2.25rem) * var(--ib-k, 1)),
102
+ calc(var(--btn-ic, 1rem) + 4px)
103
+ );
104
+ }
105
+ [data-slot="button-group"][data-look="standard"]
106
+ > [data-icon-button][data-morph="pill"][data-selected]:not(
107
+ [data-shape],
108
+ .nxgt-toolbar-item,
109
+ [data-toolbar-item]
110
+ ) {
111
+ width: calc(
112
+ var(--btn-h, 2.25rem) *
113
+ var(--ib-k, 1) +
114
+ var(--ib-morph, 0px) +
115
+ 2 *
116
+ var(--btn-dx, 0px)
117
+ );
118
+ }
119
+
120
+ @media (pointer: coarse) {
121
+ [data-slot="button-group"][data-look="standard"] {
122
+ --bg-gap: max(8px, calc(44px - var(--bg-h)));
123
+ }
124
+ }
125
+
126
+ @media (prefers-reduced-motion: reduce) {
127
+ [data-slot="button-group"][data-look="standard"] {
128
+ --bg-d: 0px;
129
+ }
130
+ }
131
+ }
@@ -1,4 +1,7 @@
1
- .nxgt-button-group {
1
+ /* ButtonGroup, the original look (no `look`): children forced to a tinted,
2
+ * rounded button. Scoped to `:not(:where([data-look]))` (the `:where` keeps the specificity at one class, so an app's override still wins), so `look="standard"`
3
+ * (button-group-standard.css) is not touched by it. */
4
+ .nxgt-button-group:not(:where([data-look])) {
2
5
  @apply inline-flex bg-background p-2 gap-1 transition-all **:transition-all items-center w-full;
3
6
  :is([data-slot="button"], [data-slot="icon-button"]) {
4
7
  @apply rounded bg-primary/15 shadow-none hover:bg-primary/10 hover:shadow w-max max-w-max;
@@ -0,0 +1,115 @@
1
+ /* IconButton on the shared Button look (button.css: radius from the height,
2
+ * press morph, 2px focus outline, disabled fill, 44px hit area; morph-fill.css:
3
+ * the selected fill). This file is what is IconButton's own:
4
+ *
5
+ * - a toggle (`aria-pressed`, `data-morph`): `pill` widens by `--ib-morph`
6
+ * (14px from 600px wide, none below, where only the colour changes), `round`
7
+ * stays a circle; both stay round when selected and square when pressed;
8
+ * `none` has no `data-morph` and keeps the look its classes give it. The
9
+ * widening is the host's own width, which springs on `spatial-fast`.
10
+ * - a disabled icon's line (`stroke`) is as dim as its text (the outlined variant's
11
+ * inset line is Button's, button.css and morph-fill.css clear it).
12
+ *
13
+ * A shaped IconButton (`data-shape`, an expressive shape) and a Toolbar item
14
+ * keep their own looks: they have no `nxgt-btn`, or are excluded below. */
15
+ @layer utilities {
16
+ /* A selected toggle stays round (the pill or the circle is its other shape). */
17
+ [data-icon-button][data-morph] {
18
+ --btn-flip: var(--btn-round);
19
+ }
20
+ @media (min-width: 600px) {
21
+ [data-icon-button][data-morph="pill"] {
22
+ --ib-morph: 14px;
23
+ }
24
+ }
25
+ [data-icon-button][data-morph="pill"][data-selected]:not(
26
+ .nxgt-toolbar-item,
27
+ [data-toolbar-item]
28
+ ) {
29
+ width: calc(var(--btn-h) * var(--ib-k, 1) + var(--ib-morph, 0px));
30
+ }
31
+
32
+ /* Pressed, the corners squeeze; a selected toggle too (button.css sends it
33
+ * back to the rest radius, which for a toggle is the shape it already has). */
34
+ [data-icon-button][data-morph][data-selected]:is(:active, .is-press):not(
35
+ :disabled,
36
+ [aria-busy="true"]
37
+ ) {
38
+ --btn-rad: var(--btn-square);
39
+ }
40
+
41
+ /* Selected: the fill replaces the line, the icon's line follows the text. */
42
+ [data-icon-button][data-selected]:not(.nxgt-toolbar-item, [data-toolbar-item])
43
+ svg {
44
+ stroke: var(--morph-on-fg, var(--color-primary-foreground));
45
+ }
46
+
47
+ /* Disabled: the line of the icon as dim as its text. */
48
+ [data-icon-button]:disabled:is(
49
+ [data-variant="filled"],
50
+ [data-variant="tonal"],
51
+ [data-variant="outlined"],
52
+ [data-variant="link"],
53
+ [data-variant="ghost"]:is(
54
+ [data-color="secondary"],
55
+ [data-color="success"],
56
+ [data-color="info"],
57
+ [data-color="warning"],
58
+ [data-color="error"]
59
+ )
60
+ ):not(.nxgt-toolbar-item, [data-toolbar-item]) svg {
61
+ stroke: color-mix(in oklab, var(--color-foreground) 40%, transparent);
62
+ }
63
+
64
+ /* An IconButton slotted into a Toolbar (`data-toolbar-item`, ToolbarItemSlot)
65
+ * has no `.nxgt-toolbar-item` rules of its own: it keeps the Toolbar's
66
+ * focus outline and a dimmed disabled look, which the shared rules leave out. */
67
+ [data-icon-button][data-toolbar-item]:focus-visible {
68
+ outline: 2px solid var(--tb-focus, var(--color-primary-text));
69
+ outline-offset: 2px;
70
+ }
71
+ [data-icon-button][data-toolbar-item]:disabled {
72
+ opacity: 0.4;
73
+ }
74
+
75
+ /* Reduced motion: colour and fill only. The widening is instant and a
76
+ * pressed toggle keeps its shape. */
77
+ @media (prefers-reduced-motion: reduce) {
78
+ [data-icon-button][data-morph]:not(
79
+ .nxgt-toolbar-item,
80
+ [data-toolbar-item]
81
+ ) {
82
+ transition:
83
+ background-color var(--nxgt-motion-effects-fast-duration)
84
+ var(--nxgt-motion-effects-fast),
85
+ color var(--nxgt-motion-effects-fast-duration)
86
+ var(--nxgt-motion-effects-fast);
87
+ }
88
+ [data-icon-button][data-morph][data-selected]:is(:active, .is-press):not(
89
+ :disabled,
90
+ [aria-busy="true"]
91
+ ) {
92
+ --btn-rad: var(--btn-flip);
93
+ }
94
+ }
95
+
96
+ /* Forced colours (the selected toggle is morph-fill.css's). */
97
+ @media (forced-colors: active) {
98
+ /* The outlined line is an inset shadow, which forced colours remove. */
99
+ [data-icon-button][data-variant="outlined"]:not(
100
+ .nxgt-toolbar-item,
101
+ [data-toolbar-item],
102
+ :disabled,
103
+ :focus-visible
104
+ ) {
105
+ outline: 1.5px solid ButtonText;
106
+ outline-offset: -1.5px;
107
+ }
108
+ [data-icon-button]:focus-visible:not(
109
+ .nxgt-toolbar-item,
110
+ [data-toolbar-item]
111
+ ) {
112
+ outline-color: Highlight;
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,62 @@
1
+ /* The fill of a selected morph control, shared (#1106: it was copied into
2
+ * Toolbar, IconButton and RichChatComposer). A host with the class
3
+ * `nxgt-morph-fill` and `data-selected` is filled with `--morph-on` and reads
4
+ * `--morph-on-fg` on it; its colour sets both (`toneVars`, button-tones.ts).
5
+ * Button and IconButton use it; the toggle's shape change is the host's own
6
+ * (button.css, icon-button.css); Toolbar items keep the Toolbar's own fill.
7
+ * A ghost control without a `color` (no `data-color`) is neutral when selected
8
+ * too, for Button and IconButton alike: a 12 % tint of the text colour, label
9
+ * and icon in full text colour (primary only with a `color`).
10
+ * Doubled class: it must beat the colour
11
+ * utilities of the variant (`bg-primary`, `hover:bg-…`). */
12
+ @layer utilities {
13
+ .nxgt-morph-fill[data-variant="ghost"]:not([data-color]) {
14
+ --morph-on: color-mix(in oklab, var(--color-foreground) 12%, transparent);
15
+ --morph-on-fg: var(--color-foreground);
16
+ }
17
+ .nxgt-morph-fill.nxgt-morph-fill[data-selected]:not(
18
+ .nxgt-toolbar-item,
19
+ [data-toolbar-item]
20
+ ) {
21
+ background-color: var(--morph-on, var(--color-primary));
22
+ color: var(--morph-on-fg, var(--color-primary-foreground));
23
+ box-shadow: none;
24
+ }
25
+ .nxgt-morph-fill.nxgt-morph-fill[data-selected]:not(
26
+ .nxgt-toolbar-item,
27
+ [data-toolbar-item]
28
+ )
29
+ svg {
30
+ color: var(--morph-on-fg, var(--color-primary-foreground));
31
+ }
32
+ .nxgt-morph-fill.nxgt-morph-fill[data-selected]:not(
33
+ .nxgt-toolbar-item,
34
+ [data-toolbar-item]
35
+ ):hover:not(:disabled) {
36
+ background-color: color-mix(
37
+ in oklab,
38
+ var(--morph-on-fg, var(--color-primary-foreground)) 10%,
39
+ var(--morph-on, var(--color-primary))
40
+ );
41
+ }
42
+
43
+ /* Forced colours: the selected control is the system's selected item. */
44
+ @media (forced-colors: active) {
45
+ .nxgt-morph-fill.nxgt-morph-fill[data-selected]:not(
46
+ .nxgt-toolbar-item,
47
+ [data-toolbar-item]
48
+ ) {
49
+ background-color: SelectedItem;
50
+ color: SelectedItemText;
51
+ forced-color-adjust: none;
52
+ }
53
+ .nxgt-morph-fill.nxgt-morph-fill[data-selected]:not(
54
+ .nxgt-toolbar-item,
55
+ [data-toolbar-item]
56
+ )
57
+ svg {
58
+ color: SelectedItemText;
59
+ stroke: SelectedItemText;
60
+ }
61
+ }
62
+ }
@@ -107,6 +107,23 @@
107
107
  gap: 8px;
108
108
  margin-inline-start: auto;
109
109
  }
110
+ /* A stacked toast behind the front one (`data-expanded="false"`,
111
+ * `data-front="false"`) sits under it, scaled and offset by a few pixels:
112
+ * its buttons are covered, so a tap lands on the front toast, and a covered,
113
+ * scaled close button fails axe's `target-size` (#1480). Its buttons are
114
+ * hidden until the stack opens (sonner opens it on hover, not on focus) or
115
+ * focus is inside the toaster, so a Tab from the front toast still reaches
116
+ * them. Sonner's own `opacity: 0` for these does not apply to our unstyled
117
+ * (`data-styled="false"`) toasts. */
118
+ [data-sonner-toast][data-expanded="false"][data-front="false"]
119
+ .nxgt-toast-buttons {
120
+ visibility: hidden;
121
+ }
122
+ [data-sonner-toaster]:focus-within
123
+ [data-sonner-toast][data-expanded="false"][data-front="false"]
124
+ .nxgt-toast-buttons {
125
+ visibility: visible;
126
+ }
110
127
  .nxgt-toast-title {
111
128
  color: var(--toast-fg);
112
129
  }
@@ -0,0 +1,49 @@
1
+ /* Which shapes of the backdrop show (observe-layout.ts, watch-content.ts).
2
+ * Plain CSS in `@layer components`, beside activity-backdrop.css. */
3
+ @layer components {
4
+ /* The theme's backdrop keeps its shapes whole (keep-shapes-inside.ts):
5
+ * the scene waits for the first pass, then a shape that would cross the
6
+ * layer's edge or reach the header band is hidden, never cut. */
7
+ [data-keep-inside]:not([data-checked]) .mo,
8
+ [data-slot="activity-layout-backdrop"]
9
+ [data-background]:not([data-checked])
10
+ .mo {
11
+ visibility: hidden;
12
+ }
13
+ [data-keep-inside] .mo > [data-outside],
14
+ [data-slot="activity-layout-backdrop"]
15
+ [data-background]
16
+ .mo
17
+ > [data-outside] {
18
+ visibility: hidden;
19
+ }
20
+
21
+ /* A shape over the page's text or controls fades out and comes back once
22
+ * clear (watch-content.ts): it is hidden, never moved. A compound body (the
23
+ * moon, the planet, the eclipse: `.pt.mg`) is hidden whole, with its halo
24
+ * (`.pt.mh`). The scene stays invisible until the first check, so nothing
25
+ * flashes over the content. */
26
+ [data-keep-inside] .mo > *,
27
+ [data-keep-inside] .pt,
28
+ [data-slot="activity-layout-backdrop"] [data-background] .mo > *,
29
+ [data-slot="activity-layout-backdrop"] [data-background] .pt {
30
+ transition: opacity 150ms ease;
31
+ }
32
+ /* Important: a preset sets its own opacity on a body (`.md`, the dark
33
+ * moon's, a celestial body's); in a layer, `!important` beats every normal
34
+ * declaration of those. The 150 ms transition still runs. */
35
+ [data-keep-inside] .mo > [data-hidden],
36
+ [data-keep-inside] .pt[data-hidden],
37
+ [data-slot="activity-layout-backdrop"] [data-background] .mo > [data-hidden],
38
+ [data-slot="activity-layout-backdrop"] [data-background] .pt[data-hidden] {
39
+ opacity: 0 !important;
40
+ }
41
+ @media (prefers-reduced-motion: reduce) {
42
+ [data-keep-inside] .mo > *,
43
+ [data-keep-inside] .pt,
44
+ [data-slot="activity-layout-backdrop"] [data-background] .mo > *,
45
+ [data-slot="activity-layout-backdrop"] [data-background] .pt {
46
+ transition: none;
47
+ }
48
+ }
49
+ }
@@ -77,23 +77,6 @@
77
77
  display: none;
78
78
  }
79
79
 
80
- /* The theme's backdrop keeps its shapes whole (use-keep-shapes-inside.ts):
81
- * the scene waits for the first pass, then a shape that would cross the
82
- * layer's edge or reach the header band is hidden, never cut. */
83
- [data-keep-inside]:not([data-checked]) .mo,
84
- [data-slot="activity-layout-backdrop"]
85
- [data-background]:not([data-checked])
86
- .mo {
87
- visibility: hidden;
88
- }
89
- [data-keep-inside] .mo > [data-outside],
90
- [data-slot="activity-layout-backdrop"]
91
- [data-background]
92
- .mo
93
- > [data-outside] {
94
- visibility: hidden;
95
- }
96
-
97
80
  /* A preset fills the layer: the layer's size, not `100lvw x 100lvh`, which
98
81
  * counts the scrollbar and a phone's toolbar. */
99
82
  [data-slot="activity-layout-backdrop"] > [data-background] {
@@ -3,13 +3,18 @@
3
3
  :root {
4
4
  /* The active entry of the sidebar and the navigation rail: its tint, the
5
5
  * tint hovered, the start bar (the label's colour), and the icon and label drawn on it. Translucent primary
6
- * (these shares, 15 % and 20 % hovered); the label is primary itself. The
6
+ * (these shares, 15 % and 20 % hovered); the label is primary at 85 % of its lightness in light (a lavender of its hue in dark). The
7
7
  * utilities (`bg-sidebar-active`...) derive them on the element, so they
8
8
  * follow a `--primary` set below :root. To theme the rail set
9
9
  * `--sidebar-active`, `--sidebar-active-hover` and
10
10
  * `--sidebar-active-foreground` (all three, or the first two and the
11
11
  * foreground); the derived colours are not declared here, on purpose. */
12
12
  --nxgt-sidebar-active-share: 15%;
13
+ /* Light: primary itself reads 4.5:1 on the page but not on its own 15 % pill
14
+ * for every preset (corail 3.7:1), so the label is primary darkened to 85 %
15
+ * of its lightness (4.99:1 on the worst preset, hovered). The preset spec
16
+ * (sidebar-active-contrast.test.ts) reads this number back. */
17
+ --nxgt-sidebar-active-l: calc(l * 0.85);
13
18
  /* The height of the app bar and of the rail's header from `md` up, so the
14
19
  * rail's logo and the bar's title share one centre line. Phones keep the
15
20
  * bar alone at this height; the menu sheet's header is unconstrained. */
@@ -24,7 +29,7 @@
24
29
  /* The dark theme's primary is too dark to read on its surfaces (2.5:1), so
25
30
  * the rail's label is a saturated lavender of its hue (5.3:1: lightness
26
31
  * 0.72, chroma 0.15) on a denser pill (30 %, 35 % hovered). Light keeps the
27
- * lightness and chroma of primary (the `l` and `c` fallbacks). */
32
+ * chroma of primary and 85 % of its lightness. */
28
33
  --nxgt-sidebar-active-share: 30%;
29
34
  --nxgt-sidebar-active-hover-share: 35%;
30
35
  --nxgt-sidebar-active-l: 0.72;
package/lib/styles.css CHANGED
@@ -15,6 +15,8 @@
15
15
  /* Motion tokens (--nxgt-motion-*, ease-spatial-*; generated) and corner tokens (--nxgt-shape-corner-*). */
16
16
  @import "./components/motion.css";
17
17
  @import "./components/shape-tokens.css";
18
+ /* The type scale (text-title-md, text-title-md-em…; --text-<role>-<size>*): lib/components/type-scale.css. */
19
+ @import "./components/type-scale.css";
18
20
  /* The animation API (animate-fade-in-up, animate-duration-300…): lib/components/animations/index.css. */
19
21
  @import "./components/animations/index.css";
20
22
  /* The base layer: elements (base.css), then editor content and the form grid. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.10.2",
3
+ "version": "3.12.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -465,6 +465,7 @@
465
465
  "@vitejs/plugin-react": "6.0.4",
466
466
  "@vitest/browser-playwright": "4.1.10",
467
467
  "ajv": "^8.20.0",
468
+ "axe-core": "4.12.1",
468
469
  "globals": "^17.7.0",
469
470
  "hls.js": "^1.7.3",
470
471
  "leaflet": "^1.9.4",