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