sherick-ui 2.2.0 → 2.4.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 (149) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/Badge.cjs +1 -1
  3. package/dist/cjs/components/Badge.cjs.map +1 -1
  4. package/dist/cjs/components/Button.cjs +11 -2
  5. package/dist/cjs/components/Button.cjs.map +1 -1
  6. package/dist/cjs/components/Calendar.cjs +2 -2
  7. package/dist/cjs/components/Calendar.cjs.map +1 -1
  8. package/dist/cjs/components/Card.cjs +3 -2
  9. package/dist/cjs/components/Card.cjs.map +1 -1
  10. package/dist/cjs/components/Checkbox.cjs +3 -1
  11. package/dist/cjs/components/Checkbox.cjs.map +1 -1
  12. package/dist/cjs/components/Chip.cjs +3 -3
  13. package/dist/cjs/components/Chip.cjs.map +1 -1
  14. package/dist/cjs/components/Combobox.cjs +1 -1
  15. package/dist/cjs/components/Combobox.cjs.map +1 -1
  16. package/dist/cjs/components/Command.cjs +3 -3
  17. package/dist/cjs/components/Command.cjs.map +1 -1
  18. package/dist/cjs/components/CommandPalette.cjs +1 -1
  19. package/dist/cjs/components/CommandPalette.cjs.map +1 -1
  20. package/dist/cjs/components/DateRangePicker.cjs +1 -1
  21. package/dist/cjs/components/DateRangePicker.cjs.map +1 -1
  22. package/dist/cjs/components/DialogHeader.cjs +1 -1
  23. package/dist/cjs/components/DialogHeader.cjs.map +1 -1
  24. package/dist/cjs/components/Drawer.cjs +1 -1
  25. package/dist/cjs/components/Drawer.cjs.map +1 -1
  26. package/dist/cjs/components/Field.cjs +1 -1
  27. package/dist/cjs/components/Field.cjs.map +1 -1
  28. package/dist/cjs/components/FileUpload.cjs +3 -3
  29. package/dist/cjs/components/FileUpload.cjs.map +1 -1
  30. package/dist/cjs/components/IconButton.cjs +1 -1
  31. package/dist/cjs/components/IconButton.cjs.map +1 -1
  32. package/dist/cjs/components/Input.cjs +1 -1
  33. package/dist/cjs/components/Input.cjs.map +1 -1
  34. package/dist/cjs/components/NavItem.cjs +3 -3
  35. package/dist/cjs/components/NavItem.cjs.map +1 -1
  36. package/dist/cjs/components/NumberField.cjs +2 -2
  37. package/dist/cjs/components/NumberField.cjs.map +1 -1
  38. package/dist/cjs/components/Pagination.cjs +1 -1
  39. package/dist/cjs/components/Pagination.cjs.map +1 -1
  40. package/dist/cjs/components/Progress.cjs +7 -4
  41. package/dist/cjs/components/Progress.cjs.map +1 -1
  42. package/dist/cjs/components/RadioGroup.cjs +1 -1
  43. package/dist/cjs/components/RadioGroup.cjs.map +1 -1
  44. package/dist/cjs/components/Search.cjs +4 -1
  45. package/dist/cjs/components/Search.cjs.map +1 -1
  46. package/dist/cjs/components/Select.cjs +1 -1
  47. package/dist/cjs/components/Select.cjs.map +1 -1
  48. package/dist/cjs/components/Slider.cjs +1 -1
  49. package/dist/cjs/components/Slider.cjs.map +1 -1
  50. package/dist/cjs/components/Stepper.cjs +5 -1
  51. package/dist/cjs/components/Stepper.cjs.map +1 -1
  52. package/dist/cjs/components/Table.cjs +1 -1
  53. package/dist/cjs/components/Table.cjs.map +1 -1
  54. package/dist/cjs/components/Tabs.cjs +1 -1
  55. package/dist/cjs/components/Tabs.cjs.map +1 -1
  56. package/dist/cjs/components/Textarea.cjs +1 -1
  57. package/dist/cjs/components/Textarea.cjs.map +1 -1
  58. package/dist/cjs/components/Toast.cjs +5 -2
  59. package/dist/cjs/components/Toast.cjs.map +1 -1
  60. package/dist/cjs/components/Tooltip.cjs +1 -1
  61. package/dist/cjs/components/Tooltip.cjs.map +1 -1
  62. package/dist/cjs/components/TreeView.cjs +43 -10
  63. package/dist/cjs/components/TreeView.cjs.map +1 -1
  64. package/dist/cjs/components/date-field.cjs +2 -2
  65. package/dist/cjs/components/date-field.cjs.map +1 -1
  66. package/dist/cjs/components/ui.common.cjs +197 -88
  67. package/dist/cjs/components/ui.common.cjs.map +1 -1
  68. package/dist/cjs/components/ui.motion.cjs +43 -14
  69. package/dist/cjs/components/ui.motion.cjs.map +1 -1
  70. package/dist/cjs/dev.cjs +10 -0
  71. package/dist/cjs/dev.cjs.map +1 -1
  72. package/dist/esm/components/Badge.js +2 -2
  73. package/dist/esm/components/Badge.js.map +1 -1
  74. package/dist/esm/components/Button.js +12 -3
  75. package/dist/esm/components/Button.js.map +1 -1
  76. package/dist/esm/components/Calendar.js +3 -3
  77. package/dist/esm/components/Calendar.js.map +1 -1
  78. package/dist/esm/components/Card.js +4 -3
  79. package/dist/esm/components/Card.js.map +1 -1
  80. package/dist/esm/components/Checkbox.js +3 -1
  81. package/dist/esm/components/Checkbox.js.map +1 -1
  82. package/dist/esm/components/Chip.js +3 -3
  83. package/dist/esm/components/Chip.js.map +1 -1
  84. package/dist/esm/components/CodeBlock.js +5 -4
  85. package/dist/esm/components/CodeBlock.js.map +1 -1
  86. package/dist/esm/components/Combobox.js +3 -3
  87. package/dist/esm/components/Combobox.js.map +1 -1
  88. package/dist/esm/components/Command.js +4 -4
  89. package/dist/esm/components/Command.js.map +1 -1
  90. package/dist/esm/components/CommandPalette.js +2 -2
  91. package/dist/esm/components/CommandPalette.js.map +1 -1
  92. package/dist/esm/components/DateRangePicker.js +2 -2
  93. package/dist/esm/components/DateRangePicker.js.map +1 -1
  94. package/dist/esm/components/DialogHeader.js +2 -2
  95. package/dist/esm/components/DialogHeader.js.map +1 -1
  96. package/dist/esm/components/Drawer.js +2 -2
  97. package/dist/esm/components/Drawer.js.map +1 -1
  98. package/dist/esm/components/Field.js +2 -2
  99. package/dist/esm/components/Field.js.map +1 -1
  100. package/dist/esm/components/FileUpload.js +4 -4
  101. package/dist/esm/components/FileUpload.js.map +1 -1
  102. package/dist/esm/components/IconButton.js +1 -1
  103. package/dist/esm/components/IconButton.js.map +1 -1
  104. package/dist/esm/components/Input.js +2 -2
  105. package/dist/esm/components/Input.js.map +1 -1
  106. package/dist/esm/components/Markdown.js +3 -2
  107. package/dist/esm/components/Markdown.js.map +1 -1
  108. package/dist/esm/components/NavItem.js +5 -5
  109. package/dist/esm/components/NavItem.js.map +1 -1
  110. package/dist/esm/components/NumberField.js +3 -3
  111. package/dist/esm/components/NumberField.js.map +1 -1
  112. package/dist/esm/components/Pagination.js +2 -2
  113. package/dist/esm/components/Pagination.js.map +1 -1
  114. package/dist/esm/components/Progress.js +8 -5
  115. package/dist/esm/components/Progress.js.map +1 -1
  116. package/dist/esm/components/RadioGroup.js +2 -2
  117. package/dist/esm/components/RadioGroup.js.map +1 -1
  118. package/dist/esm/components/Search.js +5 -2
  119. package/dist/esm/components/Search.js.map +1 -1
  120. package/dist/esm/components/Select.js +3 -3
  121. package/dist/esm/components/Select.js.map +1 -1
  122. package/dist/esm/components/Slider.js +1 -1
  123. package/dist/esm/components/Slider.js.map +1 -1
  124. package/dist/esm/components/Stepper.js +6 -2
  125. package/dist/esm/components/Stepper.js.map +1 -1
  126. package/dist/esm/components/Table.js +2 -2
  127. package/dist/esm/components/Table.js.map +1 -1
  128. package/dist/esm/components/Tabs.js +2 -2
  129. package/dist/esm/components/Tabs.js.map +1 -1
  130. package/dist/esm/components/Textarea.js +2 -2
  131. package/dist/esm/components/Textarea.js.map +1 -1
  132. package/dist/esm/components/Toast.js +6 -3
  133. package/dist/esm/components/Toast.js.map +1 -1
  134. package/dist/esm/components/Tooltip.js +2 -2
  135. package/dist/esm/components/Tooltip.js.map +1 -1
  136. package/dist/esm/components/TreeView.js +45 -12
  137. package/dist/esm/components/TreeView.js.map +1 -1
  138. package/dist/esm/components/date-field.js +3 -3
  139. package/dist/esm/components/date-field.js.map +1 -1
  140. package/dist/esm/components/ui.common.js +194 -90
  141. package/dist/esm/components/ui.common.js.map +1 -1
  142. package/dist/esm/components/ui.motion.js +39 -15
  143. package/dist/esm/components/ui.motion.js.map +1 -1
  144. package/dist/esm/dev.js +2 -2
  145. package/dist/styles.css +339 -110
  146. package/dist/theme.css +40 -7
  147. package/dist/types/dev.d.cts +88 -56
  148. package/dist/types/dev.d.ts +88 -56
  149. package/package.json +1 -1
@@ -111,21 +111,28 @@ const elevation = {
111
111
  floating: "shadow-sherick-floating",
112
112
  control: "shadow-sherick-control",
113
113
  recessed: "shadow-sherick-recessed",
114
- /* One rung further down: the well of a mark the size of a glyph, whose whole identity is its
115
- depth. A wide groove reads from its fill; a 24px well has no fill step to spare, so one wall
116
- of the well is deepened and that boundary is what says what it is. See `selectable.markSurface`. */
114
+ /* One rung further down: the depth a small selection mark sinks to while it is held. A mark
115
+ rests in the ordinary groove; this is its press, never its identity. See
116
+ `selectable.markSurface`. */
117
117
  well: "shadow-sherick-well",
118
118
  };
119
119
  /* An inset sheet's shaded upper wall, without a lower rim. Kept independent so
120
120
  unrelated components do not bundle this recipe. */
121
121
  const recessedTop = "shadow-sherick-recessed-top";
122
122
  /* Shape — semantic corner roles, never an arbitrary radius. Softness grows with
123
- the size of the object and the emphasis it carries:
123
+ the size of the object and the emphasis it carries, and silhouette says what an object does:
124
+ a **capsule acts** (buttons, chips, badges, toggles are `pill`) and a **rounded rectangle
125
+ holds** (fields, alerts, tables, sheets). Container roles therefore stay clearly short of a
126
+ capsule at their own height — `control` is a third of a 48px field — so a field never reads as
127
+ an almost-button beside a real one. Nested roles stay concentric: an inner corner is the outer
128
+ corner minus the inset between them (a 20px track around a 16px indicator at 4px, a 24px list
129
+ sheet around 16px options at 8px).
124
130
  - control: ordinary controls and dense data regions — fields, rows, options,
125
131
  chips, tables.
126
- - mark: a compact square selection mark — a checkbox box. `control` is a role
132
+ - mark: a compact square selection mark — a 20px checkbox box. `control` is a role
127
133
  for objects the size of a field; its radius exceeds the half-extent of a
128
- 24px square, so a mark needs a step of its own.
134
+ 20px square, so a mark needs a step of its own — a corner under a third of
135
+ its side, so the box stays a rounded square and never reads as a radio.
129
136
  - row: a row at command density — a row in a list that is scanned rather than
130
137
  read holds a corner proportional to its own height, because `control`
131
138
  would reach a 36px row's half-extent and read as a capsule.
@@ -142,13 +149,13 @@ const recessedTop = "shadow-sherick-recessed-top";
142
149
  - pill: fully rounded controls whose width follows their content.
143
150
  - circle: fully rounded square targets. */
144
151
  const shape = {
145
- control: "rounded-[1.25rem]",
146
- mark: "rounded-[0.625rem]",
147
- row: "rounded-[0.875rem]",
148
- prominent: "rounded-[1.5rem]",
149
- surface: "rounded-[1.75rem]",
150
- sheet: "rounded-[1.25rem]",
151
- expressive: "rounded-[2rem]",
152
+ control: "rounded-[1rem]",
153
+ mark: "rounded-[0.375rem]",
154
+ row: "rounded-[0.75rem]",
155
+ prominent: "rounded-[1.25rem]",
156
+ surface: "rounded-[1.5rem]",
157
+ sheet: "rounded-[1rem]",
158
+ expressive: "rounded-[1.75rem]",
152
159
  pill: "rounded-full",
153
160
  circle: "rounded-full",
154
161
  };
@@ -170,6 +177,28 @@ const edgeTone = {
170
177
  header: "border-sherick-edge/[0.10]",
171
178
  rule: "border-sherick-edge/[0.075]",
172
179
  };
180
+ /* Rim — the boundary of a *hollow* control: a text field, or an empty checkbox or radio. Nothing
181
+ inside such a control says what it is — an empty field holds at most a placeholder, an empty mark
182
+ holds nothing — and it still has to be identifiable at 3:1, which no neutral fill step and no
183
+ depth the light model can draw achieves on its own. So a hollow control carries one rim in the
184
+ `rim` tone, tuned to clear 3:1 on every surface by a small margin and no more, and that rim is
185
+ the only border a control has. A filled control — a button, a chip, a selected mark — is identified by its fill and
186
+ never carries one.
187
+ The field rim is a real border: forced colors repaints a border in a system colour, where it
188
+ would remove an inset shadow, and the scoped baseline sizes boxes by their border edge, so the
189
+ rim never changes a field's footprint. Its states ride on the field ladder in `state.field`.
190
+ A drop target's rim is dashed, and the dashes are a mask over an overlay rather than the native
191
+ `dashed` style, whose dash length and corner joins the platform decides: the mask fixes the dash
192
+ rhythm and follows the `control` corner exactly. The mask is authored in `tokens.ts`. */
193
+ const dashedRimMask = "after:[mask-image:var(--sui-rim-dashed)] after:[mask-size:100%_100%] after:[mask-repeat:no-repeat]";
194
+ const rim = {
195
+ field: "border-[1.5px] border-sherick-rim",
196
+ fieldError: "border-[1.5px] border-sherick-danger",
197
+ dashed: /* @__PURE__ */ utils.cx("after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:content-['']", "after:bg-sherick-rim", dashedRimMask, ui_motion.motionRim),
198
+ /* The dashes step up under the pointer and take the primary tone while a file is held over them. */
199
+ dashedHover: "hover:after:bg-sherick-ink-muted",
200
+ dashedDragging: "data-[dragging]:after:bg-sherick-primary",
201
+ };
173
202
  const edge = {
174
203
  /* Rows of a stacked list or table. */
175
204
  row: /* @__PURE__ */ utils.cx("border-b", edgeTone.row, "last:border-b-0"),
@@ -224,22 +253,26 @@ const state = {
224
253
  composite field focuses its outer surface while the input inside stays
225
254
  borderless; an expanded control holds the engaged step while its popup is
226
255
  open. Tonality carries the state, so no border appears and disappears. */
256
+ /* A field is a well: its fill sits below the surface around it, and engaging it sinks it further
257
+ while its rim steps up. An invalid field is the same well with a danger rim: the rim, the
258
+ danger placeholder and the field's message carry the error, so the fill never trades its
259
+ depth for a tinted wash. The error steps keep the well's depth ladder and leave the rim alone. */
227
260
  field: {
228
- hover: "hover:bg-sherick-surface-high/[0.82]",
229
- focus: "focus:bg-sherick-surface-high/[0.9]",
261
+ hover: "hover:bg-sherick-canvas/[0.85] hover:border-sherick-ink-muted",
262
+ focus: "focus:bg-sherick-canvas focus:border-sherick-ink-muted",
230
263
  /* A composite's engaged tone outranks hover, independent of variant emission order. */
231
- focusWithin: "[&&]:focus-within:bg-sherick-surface-high/[0.9]",
232
- engaged: "bg-sherick-surface-high/[0.9]",
233
- errorHover: "hover:bg-sherick-danger/[0.10]",
234
- errorFocus: "focus:bg-sherick-danger/[0.13]",
235
- errorEngaged: "bg-sherick-danger/[0.13]",
264
+ focusWithin: "[&&]:focus-within:bg-sherick-canvas [&&]:focus-within:border-sherick-ink-muted",
265
+ engaged: "bg-sherick-canvas border-sherick-ink-muted",
266
+ errorHover: "hover:bg-sherick-canvas/[0.85]",
267
+ errorFocus: "focus:bg-sherick-canvas",
268
+ errorEngaged: "bg-sherick-canvas",
236
269
  /* The error ladder again, keyed on Base UI's validity attribute for a part that learns its
237
270
  validity from the field it sits in. Two variants outrank the single-variant ladder above,
238
271
  so neither ladder has to be conditional. */
239
- invalid: "data-[invalid]:bg-sherick-danger/[0.075]",
240
- invalidHover: "data-[invalid]:hover:bg-sherick-danger/[0.10]",
241
- invalidEngaged: "data-[invalid]:bg-sherick-danger/[0.13]",
242
- invalidFocusWithin: "[&&]:data-[invalid]:focus-within:bg-sherick-danger/[0.13]",
272
+ invalid: "data-[invalid]:border-sherick-danger",
273
+ invalidHover: "data-[invalid]:hover:border-sherick-danger",
274
+ invalidEngaged: "data-[invalid]:border-sherick-danger",
275
+ invalidFocusWithin: "[&&]:data-[invalid]:focus-within:border-sherick-danger",
243
276
  },
244
277
  };
245
278
  /* State layers.
@@ -278,8 +311,19 @@ const hoverFilled = "[&:not([data-disabled]):not(:disabled)]:hover:before:opacit
278
311
  const hoverTrack = "group-[:not([data-disabled]):not(:disabled)]:hover:before:opacity-[0.18]";
279
312
  const pressQuiet = "[&:not([data-disabled]):not(:disabled)]:active:before:opacity-[0.09]";
280
313
  const pressTonal = "[&:not([data-disabled]):not(:disabled)]:active:before:opacity-[0.15]";
314
+ /* A neutral fill's label is ink, so a 15% ink veil turns the whole control a flat grey while it is
315
+ held — and the recess and the compression already say "pressed". The neutral press is lighter;
316
+ coloured fills keep the stronger step their labels were measured through. */
317
+ const pressTonalNeutral = "[&:not([data-disabled]):not(:disabled)]:active:before:opacity-[0.12]";
281
318
  const pressFilled = "[&:not([data-disabled]):not(:disabled)]:active:before:opacity-[0.26]";
282
319
  const pressTrack = "group-[:not([data-disabled]):not(:disabled)]:active:before:opacity-[0.26]";
320
+ /* A selection mark answers hover with the light tonal step: it is a 20px glyph beside its own
321
+ label, and the track's step would turn an empty well into a grey disc. Its press is depth, not
322
+ a heavier veil — `selectable.markSurface` sinks the well — so the layer only holds its hover
323
+ step while the pointer is down. A read-only mark accepts no change and advertises none.
324
+ The pseudo-class sits *inside* the group selector: written after it, as `…]:hover:`, it would
325
+ match the mark's own 20px box rather than the control and the row around it. */
326
+ const hoverMark = "group-[:not([data-disabled]):not([data-readonly]):not(:disabled):hover]:before:opacity-[0.09]";
283
327
  /* A labelled control surface paints its state on a row that owns the pointer area while the
284
328
  actual disabled marker belongs to a descendant control. Gate the same quiet response on that
285
329
  descendant so a disabled or read-only choice does not advertise a press it cannot accept. */
@@ -291,12 +335,17 @@ const stateLayer = {
291
335
  on-color (`current`) carries the state at a light step. Shared by every matte
292
336
  control with a fill — tonal buttons, icon buttons, acrylic buttons. */
293
337
  tonal: /* @__PURE__ */ utils.cx("relative", stateLayerBase, "before:rounded-[inherit] before:bg-current", hoverTonal, pressTonal),
338
+ /* The same layer for a neutral (`secondary`) tonal fill, with the lighter press. */
339
+ tonalNeutral: /* @__PURE__ */ utils.cx("relative", stateLayerBase, "before:rounded-[inherit] before:bg-current", hoverTonal, pressTonalNeutral),
294
340
  /* Opaque fills: `current` is the fill's own on-color — the color furthest from it in
295
341
  either theme — so one step reads on a saturated blue and a neutral gray alike. */
296
342
  filled: /* @__PURE__ */ utils.cx("relative", stateLayerBase, "before:rounded-[inherit] before:bg-current", hoverFilled, pressFilled),
297
343
  /* A switch track or a selection well: the same layer, but hover and press arrive from the
298
344
  wrapping control rather than from the surface itself. */
299
345
  track: /* @__PURE__ */ utils.cx(stateLayerBase, "before:rounded-[inherit] before:bg-current", hoverTrack, pressTrack),
346
+ /* A checkbox box or a radio socket: the hover arrives from the wrapping control, at the light
347
+ tonal step, and the press is carried by the well's own depth rather than by this layer. */
348
+ mark: /* @__PURE__ */ utils.cx(stateLayerBase, "before:rounded-[inherit] before:bg-current", hoverMark),
300
349
  /* A row highlighted by keyboard or pointer navigation. Base UI collection primitives expose
301
350
  `data-highlighted`; this is the one canonical visual treatment for it, and it pairs with a
302
351
  row's own `quiet` layer — the row carries both, so a highlighted row and a hovered row are the
@@ -324,7 +373,10 @@ const hitArea = "relative after:pointer-events-auto after:absolute after:inset-[
324
373
  - matteHigh: the second matte step, for nesting inside another matte surface.
325
374
  - matteInset: a passive sheet tucked under a stronger control; subdued without
326
375
  blending into the canvas or needing a drawn rim.
327
- - control: the fill every text control shares, plus its placeholder tone.
376
+ - control: the fill every text control shares, plus its placeholder tone. It is a well:
377
+ the canvas tone, which sits below a card in both themes, so a field reads as
378
+ sunk into the surface rather than as a pad raised from it. Its rim, not its
379
+ fill, is what separates it (see `rim`).
328
380
  - handle: the fill of a small part the user has to find — a value control's handle.
329
381
  Surface steps sit within a few percent of their neighbours, so this role
330
382
  takes the mid-emphasis tone, which inverts with the theme and separates in
@@ -357,7 +409,7 @@ const material = {
357
409
  matteQuiet: "bg-sherick-surface/[0.42] text-sherick-ink",
358
410
  matte: "bg-sherick-surface/[0.78] text-sherick-ink",
359
411
  matteHigh: "bg-sherick-surface-high/[0.72] text-sherick-ink",
360
- control: "bg-sherick-surface-high/[0.66] text-sherick-ink placeholder:text-sherick-ink-muted",
412
+ control: "bg-sherick-canvas/[0.7] text-sherick-ink placeholder:text-sherick-ink-muted",
361
413
  /* The invalid fill carries its placeholder at the *full* danger tone, not at a fraction of it. A
362
414
  placeholder is text, so it answers to the same 4.5:1 the copy beside it does, and a partial
363
415
  opacity of an accent is a different colour from the accent: the 72%-opacity placeholder
@@ -365,7 +417,7 @@ const material = {
365
417
  measures 4.1–4.7:1 light and 5.2–6.2:1 dark on it. There is no dimmer step of a semantic tone
366
418
  that is still readable, and a normal field's placeholder is `ink-muted` at full strength
367
419
  already — so the error form is the same rule, in the tone the field is in. */
368
- controlError: "bg-sherick-danger/[0.075] text-sherick-ink placeholder:text-sherick-danger",
420
+ controlError: "bg-sherick-canvas/[0.7] text-sherick-ink placeholder:text-sherick-danger",
369
421
  handle: "bg-sherick-ink-muted text-sherick-canvas",
370
422
  acrylic: "bg-sherick-surface-float/[var(--sui-glass-fill)] bg-[image:var(--sui-glass-gradient)] text-sherick-ink backdrop-blur-[var(--sui-glass-blur)] backdrop-saturate-[var(--sui-glass-saturation)] backdrop-brightness-[var(--sui-glass-brightness)]",
371
423
  acrylicDense: "bg-sherick-surface-float/[var(--sui-glass-dense-fill)] bg-[image:var(--sui-glass-gradient-dense)] text-sherick-ink backdrop-blur-[var(--sui-glass-dense-blur)] backdrop-saturate-[var(--sui-glass-dense-saturation)] backdrop-brightness-[var(--sui-glass-dense-brightness)]",
@@ -375,17 +427,40 @@ const material = {
375
427
  const mediaCanvas = "bg-sherick-media";
376
428
  /* A passive sheet tucked below a stronger control; independently tree-shakeable. */
377
429
  const matteInset = "bg-sherick-surface-high/[0.35] text-sherick-ink";
430
+ /* Type roles — the recurring kinds of text the library sets, each with one size, line height and,
431
+ where the role owns it, one weight. A component still owns copy that only it renders; these are
432
+ the roles that recur across families and have to agree wherever they appear:
433
+ - control: the type step of a control, owned through `density` below.
434
+ - supporting: a field's description, hint and error, and the secondary line under a row's label.
435
+ - caption: compact labels — a badge, a tooltip, a group label, a calendar weekday.
436
+ - title: the heading of a compact surface — a popover, a toast, a command palette.
437
+ - heading: the title of a surface that owns the viewport — a dialog, an alert dialog, a drawer.
438
+ - numeric: figures that are compared down a column or updated in place.
439
+ Every size lands on a whole-pixel line box, so stacked rows keep one rhythm. */
440
+ const type = {
441
+ control: {
442
+ compact: "text-sm",
443
+ normal: "text-[0.9375rem] leading-[1.375rem]",
444
+ prominent: "text-lg",
445
+ },
446
+ supporting: "text-[0.8125rem] leading-5",
447
+ caption: "text-xs leading-4",
448
+ title: "text-[0.9375rem] leading-[1.375rem] font-semibold",
449
+ heading: "text-xl leading-7 font-semibold tracking-[-0.02em]",
450
+ numeric: "tabular-nums",
451
+ };
378
452
  /* Density — three control steps plus the accessible hit target.
379
453
  Density owns height and the type step, so controls of one density share a
380
454
  rhythm. A component's anatomy owns its padding: a button's inline padding is
381
455
  set by how it is gripped, a field's by how much text it holds, and neither is
382
- derived from the other. The library targets dense desktop and product UI, so
383
- even the prominent step stays compact. Body copy, headings and labels are
384
- content, not controls, and set their own type. */
456
+ derived from the other. The default step is comfortable rather than dense: a
457
+ 48px control is Sherick's resting proportion, and `compact` is the step for
458
+ dense product regions. Body copy, headings and labels are content, not
459
+ controls, and take a type role above. */
385
460
  const density = {
386
- compact: "min-h-10 text-sm",
387
- normal: "min-h-12 text-[0.95rem]",
388
- prominent: "min-h-14 text-lg",
461
+ compact: /* @__PURE__ */ utils.cx("min-h-10", type.control.compact),
462
+ normal: /* @__PURE__ */ utils.cx("min-h-12", type.control.normal),
463
+ prominent: /* @__PURE__ */ utils.cx("min-h-14", type.control.prominent),
389
464
  /* Minimum interactive target for an icon-only control that stands on its own. */
390
465
  target: "min-h-11 min-w-11",
391
466
  /* A control that is one part of a composite field — the trailing controls a Combobox owns. It
@@ -397,6 +472,17 @@ const density = {
397
472
  target of its own action, and this one is a part of a field the pointer is already in. */
398
473
  part: "min-h-11 min-w-9",
399
474
  };
475
+ /* Icon slot — the fixed box a leading or trailing mark occupies, sized by the type step it sits
476
+ beside so the icon-to-label ratio is the same at every size: a 14px label takes a 16px mark, a
477
+ 15px label a 20px mark, an 18px label a 24px mark, and a 12px caption a 14px mark. The box, not
478
+ the artwork's own whitespace, is what aligns with the label, and a spinner that replaces the icon
479
+ lands in the same box. */
480
+ const iconSlot = {
481
+ caption: "inline-flex size-3.5 shrink-0 items-center justify-center [&>svg]:size-3.5",
482
+ compact: "inline-flex size-4 shrink-0 items-center justify-center [&>svg]:size-4",
483
+ normal: "inline-flex size-5 shrink-0 items-center justify-center [&>svg]:size-5",
484
+ prominent: "inline-flex size-6 shrink-0 items-center justify-center [&>svg]:size-6",
485
+ };
400
486
  /* Stacking level — where anything that floats sits relative to the application.
401
487
 
402
488
  One interaction level, deliberately. Base UI nests a popup's portal *inside* the portal of the surface it
@@ -512,12 +598,12 @@ const list = {
512
598
  both, a selected row keeps its selection tint under the ring, and a disabled-but-navigable
513
599
  row still shows where the navigation is while saying it cannot be used. The ring is inset
514
600
  because the row lives inside the sheet it belongs to, as a segment does in its track. */
515
- option: /* @__PURE__ */ utils.cx("flex w-full items-center justify-between gap-4 px-4 py-3 text-start text-sm outline-none", shape.control, ui_motion.motionFeedback, focusRingInset, text.high, stateLayer.quiet, stateLayer.activeRow, state.effectiveDisabled),
601
+ option: /* @__PURE__ */ utils.cx("flex w-full items-center justify-between gap-4 px-4 py-3 text-start outline-none", type.control.normal, shape.control, ui_motion.motionFeedback, focusRingInset, text.high, stateLayer.quiet, stateLayer.activeRow, ui_motion.motionRowLayer, state.effectiveDisabled),
516
602
  /* The same object at the density a list of short actions wants, where the row is scanned
517
603
  rather than read and the list is a control rather than a page. `shape.row` keeps the
518
604
  command's corner proportional to its own height, so its highlight nests in the tighter
519
605
  sheet the menu is. */
520
- command: /* @__PURE__ */ utils.cx("flex w-full items-center gap-3 px-3 py-2 text-start text-sm outline-none", shape.row, ui_motion.motionFeedback, focusRingInset, text.high, stateLayer.quiet, stateLayer.activeRow, state.effectiveDisabled),
606
+ command: /* @__PURE__ */ utils.cx("flex w-full items-center gap-3 px-3 py-2 text-start outline-none", type.control.compact, shape.row, ui_motion.motionFeedback, focusRingInset, text.high, stateLayer.quiet, stateLayer.activeRow, ui_motion.motionRowLayer, state.effectiveDisabled),
521
607
  };
522
608
  /* A disclosure — one row that opens the region beneath it. An item in an `Accordion` and a
523
609
  standalone `Collapsible` are the same object: a stacked row, and the in-flow panel its
@@ -538,11 +624,14 @@ const disclosure = {
538
624
  text foreground only, for quiet rows and links.
539
625
  soft a de-emphasised tinted surface: alerts, badges, quiet cards.
540
626
  tonal a matte control fill at rest: tonal buttons, icon buttons.
541
- selected the fill a selected control holds: menu options, navigation, the
542
- selected segment of a segmented control.
627
+ selected the fill a selected control holds: options, segments, calendar days, tree rows.
543
628
  strong the opaque accent fill that marks priority.
544
- A fill step is composited over whatever sits beneath it, so one tone reads
545
- correctly on the canvas, inside a card and on an acrylic sheet. */
629
+ The semantic soft and selected steps are the role's own authored surfaces (`--sui-<role>-soft`,
630
+ `--sui-<role>-selected`), not a fraction of its foreground: the foreground is tuned for text
631
+ contrast, and a fraction of it is a greyed version of the hue. They are opaque, so one tone reads
632
+ the same on the canvas, inside a card and on an acrylic sheet. The neutral soft step is a step
633
+ *within* whatever holds it rather than the card fill itself, so a neutral badge or tag stays
634
+ visible inside a card. */
546
635
  const tone = {
547
636
  text: {
548
637
  primary: "text-sherick-primary",
@@ -552,25 +641,25 @@ const tone = {
552
641
  success: "text-sherick-success",
553
642
  },
554
643
  soft: {
555
- primary: "bg-sherick-primary/[0.12] text-sherick-primary",
556
- secondary: "bg-sherick-surface/[0.78] text-sherick-ink",
557
- danger: "bg-sherick-danger/[0.09] text-sherick-danger",
558
- warning: "bg-sherick-warning/[0.09] text-sherick-warning",
559
- success: "bg-sherick-success/[0.09] text-sherick-success",
644
+ primary: "bg-sherick-primary-soft text-sherick-primary",
645
+ secondary: "bg-sherick-surface-high/[0.72] text-sherick-ink",
646
+ danger: "bg-sherick-danger-soft text-sherick-danger",
647
+ warning: "bg-sherick-warning-soft text-sherick-warning",
648
+ success: "bg-sherick-success-soft text-sherick-success",
560
649
  },
561
650
  tonal: {
562
- primary: "bg-sherick-primary/[0.12]",
651
+ primary: "bg-sherick-primary-soft",
563
652
  secondary: "bg-sherick-surface-high/[0.56]",
564
- danger: "bg-sherick-danger/[0.09]",
565
- warning: "bg-sherick-warning/[0.09]",
566
- success: "bg-sherick-success/[0.09]",
653
+ danger: "bg-sherick-danger-soft",
654
+ warning: "bg-sherick-warning-soft",
655
+ success: "bg-sherick-success-soft",
567
656
  },
568
657
  selected: {
569
- primary: "bg-sherick-primary/[0.22] text-sherick-ink",
658
+ primary: "bg-sherick-primary-selected text-sherick-ink",
570
659
  secondary: "bg-sherick-surface-high/[0.82] text-sherick-ink",
571
- danger: "bg-sherick-danger/[0.16] text-sherick-ink",
572
- warning: "bg-sherick-warning/[0.16] text-sherick-ink",
573
- success: "bg-sherick-success/[0.16] text-sherick-ink",
660
+ danger: "bg-sherick-danger-selected text-sherick-ink",
661
+ warning: "bg-sherick-warning-selected text-sherick-ink",
662
+ success: "bg-sherick-success-selected text-sherick-ink",
574
663
  },
575
664
  strong: {
576
665
  primary: "bg-sherick-primary-strong text-sherick-on-primary",
@@ -592,6 +681,11 @@ const tone = {
592
681
  success: "group-data-[checked]:bg-sherick-success group-data-[checked]:text-sherick-on-success",
593
682
  },
594
683
  };
684
+ /* Current location — where the reader is, as opposed to a value they chose. A navigation row, the
685
+ current page of a pagination and any other `aria-current` destination hold the role's soft
686
+ container, full ink and a touch of weight, and stay flat: depth and the stronger selected step
687
+ belong to held values (`tone.selected` with `elevation.control`). */
688
+ const currentDestination = "bg-sherick-primary-soft text-sherick-ink font-medium";
595
689
  /* Selectable surface — the recessed surface of a control that fills once it is selected: a
596
690
  switch track, a checkbox box, a radio circle, a slider groove. Selection is keyed on the
597
691
  primitive's own `data-checked` / `data-indeterminate`, so an uncontrolled control is styled
@@ -600,30 +694,38 @@ const tone = {
600
694
  /* A radio's opt-in full row follows the Base control it labels. `:has` keeps uncontrolled
601
695
  selection authoritative at the primitive rather than mirroring it in component state.
602
696
  Keep this independent so unrelated consumers of the mark recipes can tree-shake it. */
603
- const selectableRowSurface = "has-[[data-checked]]:bg-sherick-primary/[0.22] has-[[data-checked]]:text-sherick-ink has-[[data-checked]]:shadow-sherick-control";
697
+ const selectableRowSurface = "has-[[data-checked]]:bg-sherick-primary-selected has-[[data-checked]]:text-sherick-ink has-[[data-checked]]:shadow-sherick-control";
604
698
  const selectable = {
605
699
  surface: /* @__PURE__ */ utils.cx("relative", elevation.recessed, ui_motion.motionFeedback),
606
- /* The same object one rung deeper, for a mark whose identity is its depth. */
607
- markSurface: /* @__PURE__ */ utils.cx("relative", elevation.well, ui_motion.motionFeedback),
700
+ /* A selection mark — a checkbox box, a radio socket — rests in the same groove a Switch track
701
+ does. While the wrapping control is held it sinks to the `well`: the floor darkens and the
702
+ boundary stays exactly where it was, which is how a mark that must not move under the pointer
703
+ answers a press. */
704
+ markSurface: /* @__PURE__ */ utils.cx("relative", elevation.recessed, "group-[:not([data-disabled]):not([data-readonly]):not(:disabled):active]:shadow-sherick-well", ui_motion.motionFeedback),
608
705
  /* The resting fill a *groove* holds — a segmented control's track, a tab list — and nothing more.
609
706
  A groove that holds labelled segments is identified by those segments, exactly as a tab list is
610
707
  identified by its labels, so it takes no boundary of its own. */
611
708
  rest: /* @__PURE__ */ utils.cx(material.matteHigh, text.medium),
612
- /* An *empty mark* — an unchecked box, an unselected radio — is the one thing in the family with
613
- no content at all: nothing inside it says what it is. It takes the fill a `Switch`'s track takes,
614
- so the interior is a surface rather than a hole, but that *cannot* be what identifies it: the
615
- tightest neutral step this palette has measures about 1.3:1 against the surface around it, where
616
- WCAG asks 3:1.
617
- So the mark is **sunk deeper** instead — `markSurface` — and its depth is what identifies it:
618
- one wall deepened a rung further than `recessed` draws it (the shaded wall above in light mode,
619
- the lit wall below in dark mode), which is the same light the whole system is built from. See
620
- the elevation recipes in `tokens.ts`, and §5 of the design language. No rim: a stroke tracing
621
- all four sides of a matte control is not part of this language, and the mark now reads as the
622
- switch's own well does, only deeper. */
623
- mark: /* @__PURE__ */ utils.cx("bg-sherick-surface-high", text.medium),
624
- /* A filled mark is identified by its own fill. */
625
- selected: "group-data-[checked]:bg-sherick-primary-strong group-data-[checked]:text-sherick-on-primary",
626
- indeterminate: "group-data-[indeterminate]:bg-sherick-primary-strong group-data-[indeterminate]:text-sherick-on-primary",
709
+ /* An *empty mark* — an unchecked box, an unselected radio — is a hollow control: nothing inside it
710
+ says what it is, and it still has to be identifiable at 3:1. Neither the neutral fill (about
711
+ 1.3:1 against the surface around it) nor any depth the light model can draw gets it there
712
+ without turning the mark into a shaded orb. So an empty mark is the one control that carries
713
+ a **rim**: the `rim` tone, tuned to just clear 3:1 on every surface, drawn as the lip of the
714
+ recess it sits in rather than as an outline over a filled control. The rim belongs to the empty
715
+ state only — a selected mark is identified by its fill, and the rim dissolves into it.
716
+ The rim is an overlay rather than the mark's own border, so the fill, the hover layer and the
717
+ recess all reach the mark's real edge: a border would leave a 1.5px frame of untinted fill
718
+ around a hovered selected box. Hover steps the rim to the mid-emphasis tone — darker in light
719
+ mode, lighter in dark — which is a visible answer on a 20px mark where a tint alone is not. */
720
+ mark: /* @__PURE__ */ utils.cx("bg-sherick-canvas/[0.7]", text.medium, "after:pointer-events-none after:absolute after:inset-0 after:box-border after:rounded-[inherit] after:border-[1.5px] after:content-['']", "after:border-sherick-rim", "group-[:not([data-disabled]):not([data-readonly]):not(:disabled):hover]:after:border-sherick-ink-muted", ui_motion.motionRim),
721
+ /* A selected mark is the strong accent, edge to edge, and it settles flat: it is identified by
722
+ its fill and its glyph, and the rim fades out as the fill arrives. The rim fades by opacity
723
+ rather than by colour, so forced colors cannot repaint a transparent rim as a visible one. */
724
+ selected: "group-data-[checked]:bg-sherick-primary-strong group-data-[checked]:text-sherick-on-primary group-data-[checked]:shadow-sherick-flat group-data-[checked]:after:opacity-0",
725
+ indeterminate: "group-data-[indeterminate]:bg-sherick-primary-strong group-data-[indeterminate]:text-sherick-on-primary group-data-[indeterminate]:shadow-sherick-flat group-data-[indeterminate]:after:opacity-0",
726
+ /* A selected radio's dot: the socket's on-colour, flat. A Switch thumb is lifted because it is a
727
+ 24px part the user moves; an 8px dot under the same shadow reads as a bead with a halo. */
728
+ thumb: "bg-current",
627
729
  };
628
730
  /* The authored alphas the contrast contract measures. They are derived from the class names above
629
731
  rather than restated, so a change to a state step, a tint strength or the field ladder moves the
@@ -638,42 +740,45 @@ const recipeAlpha = (recipe, pattern, label) => {
638
740
  };
639
741
  const stateAlpha = (recipe, label) => recipeAlpha(recipe, /opacity-\[(0?\.\d+)\]/, label);
640
742
  const fillAlpha = (recipe, label) => recipeAlpha(recipe, /\/\[(0?\.\d+)\]/, label);
743
+ /* A field well's canvas step: written without an alpha, the step is the opaque canvas. */
744
+ const wellAlpha = (recipe, label) => {
745
+ const match = recipe.match(/bg-sherick-canvas(?:\/\[(0?\.\d+)\])?(?:\s|$)/);
746
+ if (!match)
747
+ throw new Error(`recipe alpha missing for ${label}`);
748
+ return match[1] === undefined ? 1 : Number(match[1]);
749
+ };
641
750
  const recipeAlphas = {
642
751
  state: {
643
752
  quiet: { hover: stateAlpha(hoverQuiet, "state.quiet.hover"), press: stateAlpha(pressQuiet, "state.quiet.press") },
644
753
  tonal: { hover: stateAlpha(hoverTonal, "state.tonal.hover"), press: stateAlpha(pressTonal, "state.tonal.press") },
754
+ tonalNeutral: { hover: stateAlpha(hoverTonal, "state.tonalNeutral.hover"), press: stateAlpha(pressTonalNeutral, "state.tonalNeutral.press") },
645
755
  filled: { hover: stateAlpha(hoverFilled, "state.filled.hover"), press: stateAlpha(pressFilled, "state.filled.press") },
756
+ mark: { hover: stateAlpha(hoverMark, "state.mark.hover") },
646
757
  },
647
758
  tint: {
648
- soft: {
649
- primary: fillAlpha(tone.soft.primary, "tint.soft.primary"),
650
- danger: fillAlpha(tone.soft.danger, "tint.soft.danger"),
651
- warning: fillAlpha(tone.soft.warning, "tint.soft.warning"),
652
- success: fillAlpha(tone.soft.success, "tint.soft.success"),
653
- },
654
- selected: {
655
- primary: fillAlpha(tone.selected.primary, "tint.selected.primary"),
656
- danger: fillAlpha(tone.selected.danger, "tint.selected.danger"),
657
- warning: fillAlpha(tone.selected.warning, "tint.selected.warning"),
658
- success: fillAlpha(tone.selected.success, "tint.selected.success"),
659
- },
759
+ neutralSoft: fillAlpha(tone.soft.secondary, "tint.neutralSoft"),
660
760
  neutralRest: fillAlpha(tone.tonal.secondary, "tint.neutralRest"),
661
761
  },
662
762
  field: {
663
763
  quiet: fillAlpha(material.matteQuiet, "field.quiet"),
664
764
  card: fillAlpha(material.matte, "field.card"),
665
- control: fillAlpha(material.control, "field.control"),
666
- hover: fillAlpha(state.field.hover, "field.hover"),
667
- engaged: fillAlpha(state.field.engaged, "field.engaged"),
668
- invalid: fillAlpha(state.field.invalid, "field.invalid"),
669
- invalidHover: fillAlpha(state.field.invalidHover, "field.invalidHover"),
670
- invalidFocus: fillAlpha(state.field.invalidFocusWithin, "field.invalidFocus"),
765
+ groove: fillAlpha(material.matteHigh, "field.groove"),
766
+ control: wellAlpha(material.control, "field.control"),
767
+ hover: wellAlpha(state.field.hover, "field.hover"),
768
+ engaged: wellAlpha(state.field.engaged, "field.engaged"),
671
769
  },
672
770
  };
673
771
  /* A labelled field is a shrinkable column. Its explanatory copy wraps, including identifiers;
674
772
  the input itself keeps its native single-line scrolling behavior. */
675
773
  const fieldLayout = "flex min-w-0 w-full flex-col [overflow-wrap:anywhere]";
774
+ /* The messages under a field: its description or hint, and its error. One role for every field in
775
+ the family, so a hint reads the same under a text field, a date range and a file chooser. */
776
+ const fieldMessage = {
777
+ description: /* @__PURE__ */ utils.cx("mt-2", type.supporting, text.medium),
778
+ error: /* @__PURE__ */ utils.cx("mt-2", type.supporting, tone.text.danger),
779
+ };
676
780
 
781
+ exports.currentDestination = currentDestination;
677
782
  exports.density = density;
678
783
  exports.detail = detail;
679
784
  exports.disclosure = disclosure;
@@ -681,6 +786,7 @@ exports.edge = edge;
681
786
  exports.edgeTone = edgeTone;
682
787
  exports.elevation = elevation;
683
788
  exports.fieldLayout = fieldLayout;
789
+ exports.fieldMessage = fieldMessage;
684
790
  exports.focusRing = focusRing;
685
791
  exports.focusRingDrag = focusRingDrag;
686
792
  exports.focusRingHeld = focusRingHeld;
@@ -688,6 +794,7 @@ exports.focusRingInset = focusRingInset;
688
794
  exports.focusRingWithin = focusRingWithin;
689
795
  exports.groupFocusRing = groupFocusRing;
690
796
  exports.hitArea = hitArea;
797
+ exports.iconSlot = iconSlot;
691
798
  exports.list = list;
692
799
  exports.material = material;
693
800
  exports.matteInset = matteInset;
@@ -696,6 +803,7 @@ exports.overlay = overlay;
696
803
  exports.parentFocusRingInset = parentFocusRingInset;
697
804
  exports.recessedTop = recessedTop;
698
805
  exports.recipeAlphas = recipeAlphas;
806
+ exports.rim = rim;
699
807
  exports.selectable = selectable;
700
808
  exports.selectableRowSurface = selectableRowSurface;
701
809
  exports.shape = shape;
@@ -705,4 +813,5 @@ exports.stateLayer = stateLayer;
705
813
  exports.stateLayerQuietComposite = stateLayerQuietComposite;
706
814
  exports.text = text;
707
815
  exports.tone = tone;
816
+ exports.type = type;
708
817
  //# sourceMappingURL=ui.common.cjs.map