@sableui/react 0.15.0 → 0.16.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 (134) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/bin/sable-lint.mjs +5 -1
  3. package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
  4. package/dist/blocks/wallet-block.js +2 -2
  5. package/dist/components/app-shell/app-shell-frame.js +16 -0
  6. package/dist/components/app-shell/app-shell-main.js +12 -0
  7. package/dist/components/app-shell/app-shell.js +28 -4
  8. package/dist/components/app-shell/app-shell.meta.js +88 -4
  9. package/dist/components/area-chart/area-chart.meta.js +6 -1
  10. package/dist/components/attachment/attachment.meta.js +2 -1
  11. package/dist/components/attachment/attachment.variants.js +1 -1
  12. package/dist/components/badge/badge.meta.js +8 -1
  13. package/dist/components/badge/badge.variants.js +7 -7
  14. package/dist/components/banner/banner.meta.js +6 -1
  15. package/dist/components/banner/banner.variants.js +5 -5
  16. package/dist/components/bar-chart/bar-chart.meta.js +6 -1
  17. package/dist/components/caret-menu/caret-menu.js +5 -6
  18. package/dist/components/chat-composer/chat-composer.meta.js +2 -2
  19. package/dist/components/chat-composer/chat-composer.variants.js +1 -1
  20. package/dist/components/color-field/color-field.js +6 -8
  21. package/dist/components/combobox/combobox.js +27 -5
  22. package/dist/components/combobox/combobox.meta.js +10 -2
  23. package/dist/components/command-menu/command-menu.js +14 -3
  24. package/dist/components/command-menu/command-menu.meta.js +9 -1
  25. package/dist/components/date-picker/date-picker.meta.js +2 -2
  26. package/dist/components/feedback/feedback.js +1 -1
  27. package/dist/components/feedback/feedback.meta.js +2 -1
  28. package/dist/components/gradient-art/gradient-art.js +8 -5
  29. package/dist/components/gradient-art/gradient-art.meta.js +35 -2
  30. package/dist/components/input/input.meta.js +11 -3
  31. package/dist/components/input-group/input-group.js +1 -1
  32. package/dist/components/input-group/input-group.meta.js +1 -1
  33. package/dist/components/line-chart/line-chart.meta.js +6 -1
  34. package/dist/components/menu/menu.js +1 -1
  35. package/dist/components/meter/meter.js +53 -7
  36. package/dist/components/meter/meter.meta.js +51 -4
  37. package/dist/components/meter/meter.variants.js +43 -7
  38. package/dist/components/note/note.meta.js +5 -1
  39. package/dist/components/note/note.variants.js +4 -4
  40. package/dist/components/number-field/number-field.meta.js +2 -2
  41. package/dist/components/otp-field/otp-field.meta.js +2 -2
  42. package/dist/components/panel-group/panel-group.js +5 -8
  43. package/dist/components/pie-chart/pie-chart.meta.js +6 -1
  44. package/dist/components/radar-chart/radar-chart.meta.js +6 -1
  45. package/dist/components/radial-chart/radial-chart.meta.js +6 -1
  46. package/dist/components/search-input/search-input.meta.js +11 -3
  47. package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
  48. package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
  49. package/dist/components/select/select.js +1 -1
  50. package/dist/components/select/select.meta.js +2 -2
  51. package/dist/components/sheet/sheet.js +5 -4
  52. package/dist/components/sidebar/sidebar.js +16 -1
  53. package/dist/components/sidebar/sidebar.meta.js +7 -2
  54. package/dist/components/sparkline/sparkline.meta.js +8 -1
  55. package/dist/components/status-dot/status-dot.meta.js +8 -1
  56. package/dist/components/status-dot/status-dot.variants.js +7 -7
  57. package/dist/components/stepper/stepper.meta.js +2 -1
  58. package/dist/components/stepper/stepper.variants.js +1 -1
  59. package/dist/components/table/table.meta.js +4 -4
  60. package/dist/components/textarea/textarea.js +5 -0
  61. package/dist/components/textarea/textarea.meta.js +3 -3
  62. package/dist/file-hashes.json +91 -88
  63. package/dist/foundations/accessibility.js +5 -0
  64. package/dist/index.d.ts +132 -24
  65. package/dist/index.js +9 -1
  66. package/dist/lib/chart/types.js +8 -8
  67. package/dist/lib/chart/use-arrival.js +5 -4
  68. package/dist/lib/field-shell.js +1 -1
  69. package/dist/lib/highlight-glide.js +6 -6
  70. package/dist/lib/scrub-gesture.js +6 -6
  71. package/dist/lib/use-caret-trigger.js +11 -18
  72. package/dist/lib/use-match-highlight.js +69 -0
  73. package/dist/lib/use-smooth-wheel.js +10 -8
  74. package/dist/presets/index.d.ts +13 -2
  75. package/dist/presets/index.js +17 -1
  76. package/dist/styles.css +100 -0
  77. package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
  78. package/dist/themes/_contract.js +14 -0
  79. package/dist/themes/atelier/atelier.css +18 -0
  80. package/dist/themes/atelier/index.d.ts +1 -1
  81. package/dist/themes/contrast.js +48 -1
  82. package/dist/themes/ember/ember.css +18 -0
  83. package/dist/themes/ember/index.d.ts +1 -1
  84. package/dist/themes/index.d.ts +2 -2
  85. package/dist/themes/meridian/index.d.ts +1 -1
  86. package/dist/themes/meridian/meridian.css +18 -0
  87. package/dist/themes/poppy/index.d.ts +1 -1
  88. package/dist/themes/poppy/poppy.css +18 -0
  89. package/dist/themes/prism/index.d.ts +1 -1
  90. package/dist/themes/prism/prism.css +18 -0
  91. package/dist/themes/sable/index.d.ts +1 -1
  92. package/dist/themes/sable/sable.css +18 -0
  93. package/dist/themes/verdant/index.d.ts +1 -1
  94. package/dist/themes/verdant/verdant.css +18 -0
  95. package/llms/blocks/wallet-block.md +5 -4
  96. package/llms/components/app-shell.md +72 -3
  97. package/llms/components/area-chart.md +1 -1
  98. package/llms/components/attachment.md +1 -1
  99. package/llms/components/badge.md +1 -1
  100. package/llms/components/banner.md +1 -1
  101. package/llms/components/bar-chart.md +1 -1
  102. package/llms/components/chat-composer.md +1 -1
  103. package/llms/components/combobox.md +2 -1
  104. package/llms/components/command-menu.md +3 -2
  105. package/llms/components/date-picker.md +1 -1
  106. package/llms/components/feedback.md +1 -1
  107. package/llms/components/gradient-art.md +28 -1
  108. package/llms/components/input-group.md +1 -1
  109. package/llms/components/input.md +4 -2
  110. package/llms/components/line-chart.md +1 -1
  111. package/llms/components/meter.md +33 -4
  112. package/llms/components/note.md +1 -1
  113. package/llms/components/number-field.md +1 -1
  114. package/llms/components/otp-field.md +1 -1
  115. package/llms/components/pie-chart.md +1 -1
  116. package/llms/components/radar-chart.md +1 -1
  117. package/llms/components/radial-chart.md +1 -1
  118. package/llms/components/search-input.md +20 -2
  119. package/llms/components/segmented-bar.md +1 -1
  120. package/llms/components/select.md +1 -1
  121. package/llms/components/sidebar.md +3 -2
  122. package/llms/components/sparkline.md +1 -1
  123. package/llms/components/status-dot.md +1 -1
  124. package/llms/components/stepper.md +1 -1
  125. package/llms/components/table.md +2 -2
  126. package/llms/components/textarea.md +2 -2
  127. package/llms/foundations/accessibility.md +1 -0
  128. package/llms/reference/token-intent.md +22 -2
  129. package/llms/reference/utilities.md +5 -2
  130. package/llms-full.txt +222 -49
  131. package/llms.txt +2 -2
  132. package/package.json +1 -1
  133. package/registry.json +396 -57
  134. package/scripts/lint-rules.mjs +6 -3
@@ -11,7 +11,12 @@ var meterMeta = {
11
11
  "capacity",
12
12
  "limit",
13
13
  "password strength",
14
- "level"
14
+ "level",
15
+ "segmented",
16
+ "tick bar",
17
+ "score",
18
+ "credits",
19
+ "spend limit"
15
20
  ],
16
21
  usage: {
17
22
  description: "Use Meter to show a static measurement against a known maximum \u2014 storage used, seats taken, API quota, password strength. It is announced as a meter, not a progress bar, so a screen-reader user hears a level rather than a task underway.",
@@ -36,6 +41,15 @@ var meterMeta = {
36
41
  {
37
42
  guidance: "do",
38
43
  description: 'Use shape="ring" when the reading is a score that belongs in a small, glanceable spot on a card. It is the same measurement drawn as a circular gauge, not a different component.'
44
+ },
45
+ {
46
+ guidance: "do",
47
+ description: "Use `segments` (a tick bar, \u25AE\u25AE\u25AE\u25AE\u25AE\u25AE\u25AF\u25AF\u25AF\u25AF) for a discrete budget, score or quota read at a glance \u2014 a spend limit, credits left, an ICP lead score, months of data coverage. Pick a count that matches the unit when there is one (10 for a score out of 10, 12 for months) and otherwise size it to the track: a tick reads as a tick when it is taller than it is wide, about one per 8px of track (40 across a card row, 20 in a narrow table cell). Any reading above min shows at least one tick."
48
+ },
49
+ {
50
+ guidance: "dont",
51
+ description: "Do not segment a fine-grained continuous value where the exact proportion matters (disk at 73.4%) \u2014 ticks round it to the nearest mark; use the continuous bar. And do not use a tick bar for a task in flight or steps completed: that is Progress, or a Stepper for named steps.",
52
+ instead: ["Progress"]
39
53
  }
40
54
  ],
41
55
  anatomy: [
@@ -47,7 +61,7 @@ var meterMeta = {
47
61
  {
48
62
  name: "MeterIndicator",
49
63
  required: true,
50
- description: "The fill, sized from value and coloured by tone."
64
+ description: "The fill, sized from value and coloured by tone. Not used with `segments`: the track draws its ticks instead."
51
65
  },
52
66
  {
53
67
  name: "MeterLabel",
@@ -105,6 +119,11 @@ var meterMeta = {
105
119
  type: "boolean",
106
120
  description: "Print the value in the ring's centre (formatted like MeterValue)."
107
121
  },
122
+ {
123
+ name: "segments",
124
+ type: "number",
125
+ description: 'Draw the bar as this many separate ticks instead of a continuous fill (a quota, score or spend limit). Filled ticks = round(share \xD7 N), at least one for any reading above min, none at min. Ticks take the tone; empty ones sit on the rail colour. The segmented track has its own, taller sm / md / lg ladder. Bar only: ignored for shape="ring".'
126
+ },
108
127
  {
109
128
  name: "strokeWidth",
110
129
  type: "number",
@@ -131,7 +150,7 @@ var meterMeta = {
131
150
  {
132
151
  name: "MeterTrack",
133
152
  type: "Base UI Track props + className + size",
134
- description: "The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default."
153
+ description: "The rail. Rendered automatically when Meter has no children; renders a MeterIndicator by default \u2014 or, when the Meter has `segments`, its row of ticks (children are not rendered then)."
135
154
  },
136
155
  {
137
156
  name: "MeterIndicator",
@@ -157,11 +176,16 @@ var meterMeta = {
157
176
  tokensUsed: [
158
177
  "secondary",
159
178
  "primary",
179
+ "tone-3",
160
180
  "success",
161
181
  "tone-warning",
162
182
  "destructive",
163
183
  "foreground",
164
- "muted-foreground"
184
+ "muted-foreground",
185
+ "destructive-mark",
186
+ "success-mark",
187
+ "tone-warning-mark",
188
+ "tone-3-mark"
165
189
  ],
166
190
  motion: [
167
191
  {
@@ -182,6 +206,12 @@ var meterMeta = {
182
206
  part: "MeterIndicator",
183
207
  why: "Width and colour ease on the motion tokens (no recipe animates width) when the reading changes \u2014 freeing space after a delete visibly shrinks the bar, so the user sees the cause and effect rather than a different bar."
184
208
  },
209
+ {
210
+ slot: "transition",
211
+ recipe: "custom",
212
+ part: "MeterTrack (segments)",
213
+ why: "With segments, the fill runs through the ticks from the edge it is leaving \u2014 left to right as it grows, right to left as it shrinks \u2014 each tick easing its colour on the fast token after a stagger that is a share of the slow token (a full sweep is one slow duration) \xD7 --motion-expressive. Every tick switching at once would read as a different bar, not the same budget moving; `subtle` lands them together and off is instant."
214
+ },
185
215
  {
186
216
  slot: "activity",
187
217
  recipe: "none",
@@ -212,6 +242,23 @@ var meterMeta = {
212
242
  </div>
213
243
  <MeterTrack />
214
244
  </Meter>`
245
+ },
246
+ {
247
+ label: "Segmented spend limit",
248
+ code: `<Meter value={1240} max={2000} segments={40} getAriaValueText={() => "\u20AC1,240 of \u20AC2,000 spent this month"}>
249
+ <div className="mb-2 flex flex-wrap items-center justify-between gap-x-3 gap-y-1">
250
+ <MeterLabel>Monthly spend limit</MeterLabel>
251
+ <MeterValue>{() => "\u20AC1,240 / \u20AC2,000"}</MeterValue>
252
+ </div>
253
+ <MeterTrack />
254
+ </Meter>`
255
+ },
256
+ {
257
+ label: "Segmented score cell",
258
+ code: `<div className="flex items-center gap-3">
259
+ <Meter value={8} max={10} segments={10} size="sm" tone="success" className="w-24" aria-label="ICP score" getAriaValueText={(_, v) => \`\${v} out of 10\`} />
260
+ <span className="text-mono-sm text-foreground">8/10</span>
261
+ </div>`
215
262
  },
216
263
  {
217
264
  label: "Tones",
@@ -1,16 +1,17 @@
1
1
  // src/components/meter/meter.variants.ts
2
2
  import { cva } from "class-variance-authority";
3
+ var meterToneFill = {
4
+ primary: "bg-primary",
5
+ "3": "bg-tone-3-mark",
6
+ success: "bg-success-mark",
7
+ warning: "bg-tone-warning-mark",
8
+ destructive: "bg-destructive-mark"
9
+ };
3
10
  var meterIndicatorVariants = cva(
4
11
  "h-full rounded-full transition-[width,background-color] duration-(--motion-duration-slow) ease-decelerate",
5
12
  {
6
13
  variants: {
7
- tone: {
8
- primary: "bg-primary",
9
- "3": "bg-tone-3",
10
- success: "bg-success",
11
- warning: "bg-tone-warning",
12
- destructive: "bg-destructive"
13
- }
14
+ tone: meterToneFill
14
15
  },
15
16
  defaultVariants: {
16
17
  tone: "primary"
@@ -29,6 +30,39 @@ var meterTrackVariants = cva("relative w-full overflow-hidden rounded-full bg-se
29
30
  size: "md"
30
31
  }
31
32
  });
33
+ var meterSegmentedTrackVariants = cva("flex w-full gap-0.5", {
34
+ variants: {
35
+ size: {
36
+ sm: "h-3",
37
+ md: "h-4",
38
+ lg: "h-5"
39
+ }
40
+ },
41
+ defaultVariants: {
42
+ size: "md"
43
+ }
44
+ });
45
+ var meterTickVariants = cva(
46
+ // The corner follows the theme's smallest radius but stops at half a spacing
47
+ // step: a tick is a few px wide, and at the full radius-xs (4px in most
48
+ // themes) it rounded into a pill and the row read as a string of beads.
49
+ "h-full min-w-0 flex-1 rounded-[min(var(--radius-xs),--spacing(0.5))] transition-[background-color] duration-(--motion-duration-fast) ease-standard",
50
+ {
51
+ variants: {
52
+ tone: { primary: "", "3": "", success: "", warning: "", destructive: "" },
53
+ filled: { true: "", false: "bg-secondary" }
54
+ },
55
+ compoundVariants: Object.keys(meterToneFill).map((tone) => ({
56
+ tone,
57
+ filled: true,
58
+ class: meterToneFill[tone]
59
+ })),
60
+ defaultVariants: {
61
+ tone: "primary",
62
+ filled: false
63
+ }
64
+ }
65
+ );
32
66
  var meterRingValueVariants = cva("absolute inset-0 flex items-center justify-center text-foreground", {
33
67
  variants: {
34
68
  size: {
@@ -57,5 +91,7 @@ export {
57
91
  meterIndicatorVariants,
58
92
  meterRingValueVariants,
59
93
  meterRingVariants,
94
+ meterSegmentedTrackVariants,
95
+ meterTickVariants,
60
96
  meterTrackVariants
61
97
  };
@@ -83,7 +83,11 @@ var noteMeta = {
83
83
  "tone-info",
84
84
  "tone-warning",
85
85
  "success",
86
- "destructive"
86
+ "destructive",
87
+ "destructive-mark",
88
+ "success-mark",
89
+ "tone-warning-mark",
90
+ "tone-info-mark"
87
91
  ],
88
92
  motion: [
89
93
  {
@@ -5,10 +5,10 @@ var noteVariants = cva(
5
5
  {
6
6
  variants: {
7
7
  tone: {
8
- info: "border-tone-info/30 bg-tone-info/10",
9
- warning: "border-tone-warning/30 bg-tone-warning/10",
10
- success: "border-success/30 bg-success/10",
11
- destructive: "border-destructive/30 bg-destructive/10",
8
+ info: "border-tone-info-mark/30 bg-tone-info-mark/10",
9
+ warning: "border-tone-warning-mark/30 bg-tone-warning-mark/10",
10
+ success: "border-success-mark/30 bg-success-mark/10",
11
+ destructive: "border-destructive-mark/30 bg-destructive-mark/10",
12
12
  neutral: "border-border bg-muted/40"
13
13
  }
14
14
  },
@@ -80,8 +80,8 @@ var numberFieldMeta = {
80
80
  }
81
81
  ],
82
82
  tokensUsed: [
83
- "input",
84
- "card",
83
+ "field-border",
84
+ "field",
85
85
  "foreground",
86
86
  "muted-foreground",
87
87
  "accent",
@@ -142,8 +142,8 @@ var otpFieldMeta = {
142
142
  }
143
143
  ],
144
144
  tokensUsed: [
145
- "input",
146
- "card",
145
+ "field-border",
146
+ "field",
147
147
  "foreground",
148
148
  "muted-foreground",
149
149
  "border-strong",
@@ -107,14 +107,11 @@ function PanelResizer({
107
107
  const delta = side === "left" ? ev.clientX - startX : startX - ev.clientX;
108
108
  onWidthChange(startWidth + delta);
109
109
  }
110
- function onUp() {
111
- window.removeEventListener("pointermove", onMove);
112
- window.removeEventListener("pointerup", onUp);
113
- window.removeEventListener("pointercancel", onUp);
114
- }
115
- window.addEventListener("pointermove", onMove);
116
- window.addEventListener("pointerup", onUp);
117
- window.addEventListener("pointercancel", onUp);
110
+ const drag = new AbortController();
111
+ const { signal } = drag;
112
+ window.addEventListener("pointermove", onMove, { signal });
113
+ window.addEventListener("pointerup", () => drag.abort(), { signal });
114
+ window.addEventListener("pointercancel", () => drag.abort(), { signal });
118
115
  }
119
116
  function onKeyDown(e) {
120
117
  const grow = side === "left" ? "ArrowRight" : "ArrowLeft";
@@ -168,7 +168,12 @@ var pieChartMeta = {
168
168
  "popover",
169
169
  "popover-foreground",
170
170
  "foreground",
171
- "background"
171
+ "background",
172
+ "tone-warning-mark",
173
+ "tone-info-mark",
174
+ "tone-1-mark",
175
+ "tone-2-mark",
176
+ "tone-3-mark"
172
177
  ],
173
178
  motion: [
174
179
  {
@@ -162,7 +162,12 @@ var radarChartMeta = {
162
162
  "popover",
163
163
  "popover-foreground",
164
164
  "foreground",
165
- "background"
165
+ "background",
166
+ "tone-warning-mark",
167
+ "tone-info-mark",
168
+ "tone-1-mark",
169
+ "tone-2-mark",
170
+ "tone-3-mark"
166
171
  ],
167
172
  motion: [
168
173
  {
@@ -174,7 +174,12 @@ var radialChartMeta = {
174
174
  "popover",
175
175
  "popover-foreground",
176
176
  "foreground",
177
- "background"
177
+ "background",
178
+ "tone-warning-mark",
179
+ "tone-info-mark",
180
+ "tone-1-mark",
181
+ "tone-2-mark",
182
+ "tone-3-mark"
178
183
  ],
179
184
  motion: [
180
185
  {
@@ -1,7 +1,7 @@
1
1
  // src/components/search-input/search-input.meta.ts
2
2
  var searchInputMeta = {
3
3
  name: "SearchInput",
4
- summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), card surface, 2px focus ring; the clear button appears only when there is a value.",
4
+ summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value.",
5
5
  status: "stable",
6
6
  keywords: [
7
7
  "search",
@@ -24,6 +24,10 @@ var searchInputMeta = {
24
24
  guidance: "do",
25
25
  description: "When a button stands in for search (a header trigger that opens CommandMenu with \u2318K), use CommandMenuTrigger. It wears this field's shell from the same source, so the two cannot drift; a hand-styled button already did once, into a borderless chip that matched neither the field nor a button."
26
26
  },
27
+ {
28
+ guidance: "do",
29
+ description: "Light the query in the results it filters with `useMatchHighlight(query)` \u2014 a callback ref for the results container, with `selector` to keep it to titles. It paints on the `highlight` pair without wrapping matches in `<mark>`, the same paint Combobox and CommandMenu use for their rows."
30
+ },
27
31
  {
28
32
  guidance: "dont",
29
33
  description: "Do not use SearchInput as a command palette; use CommandMenu for global commands, opened from the header by CommandMenuTrigger. A SearchInput that opens a dialog on focus is a text box you cannot type in."
@@ -91,8 +95,8 @@ var searchInputMeta = {
91
95
  }
92
96
  ],
93
97
  tokensUsed: [
94
- "input",
95
- "card",
98
+ "field-border",
99
+ "field",
96
100
  "foreground",
97
101
  "muted-foreground",
98
102
  "ring",
@@ -144,6 +148,10 @@ var searchInputMeta = {
144
148
  label: "Uncontrolled, sized as a whole",
145
149
  code: '<Field label="Search posts"><SearchInput defaultValue="roadmap" className="max-w-80" /></Field>'
146
150
  },
151
+ {
152
+ label: "Highlighting matches in the results",
153
+ code: 'const [q, setQ] = useState("")\nconst results = useMatchHighlight<HTMLUListElement>(q, { selector: "[data-title]" })\nconst shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))\n\n<SearchInput value={q} onValueChange={setQ} placeholder="Search posts" />\n<ul ref={results}>\n {shown.map((post) => (\n <li key={post.id}><span data-title>{post.title}</span></li>\n ))}\n</ul>'
154
+ },
147
155
  {
148
156
  label: "Disabled",
149
157
  code: '<SearchInput disabled placeholder="Search" />'
@@ -71,7 +71,14 @@ var segmentedBarMeta = {
71
71
  "muted-foreground",
72
72
  "success",
73
73
  "tone-warning",
74
- "destructive"
74
+ "destructive",
75
+ "destructive-mark",
76
+ "success-mark",
77
+ "tone-warning-mark",
78
+ "tone-info-mark",
79
+ "tone-1-mark",
80
+ "tone-2-mark",
81
+ "tone-3-mark"
75
82
  ],
76
83
  motion: [
77
84
  {
@@ -5,15 +5,15 @@ var segmentedBarSegmentVariants = cva(
5
5
  {
6
6
  variants: {
7
7
  tone: {
8
- "1": "bg-tone-1",
9
- "2": "bg-tone-2",
10
- "3": "bg-tone-3",
11
- info: "bg-tone-info",
8
+ "1": "bg-tone-1-mark",
9
+ "2": "bg-tone-2-mark",
10
+ "3": "bg-tone-3-mark",
11
+ info: "bg-tone-info-mark",
12
12
  primary: "bg-primary",
13
13
  neutral: "bg-muted-foreground",
14
- success: "bg-success",
15
- warning: "bg-tone-warning",
16
- destructive: "bg-destructive"
14
+ success: "bg-success-mark",
15
+ warning: "bg-tone-warning-mark",
16
+ destructive: "bg-destructive-mark"
17
17
  }
18
18
  },
19
19
  defaultVariants: {
@@ -72,7 +72,7 @@ function SelectTrigger({
72
72
  className: cn(
73
73
  `flex w-full items-center justify-between ${tier.body} text-foreground motion-interactive`,
74
74
  mobileTier,
75
- size === "xs" ? cn("h-(--control-xs) gap-1 px-1.5", FIELD_SHELL_XS) : "h-(--control-md) gap-2 rounded-sm border border-input bg-card px-3",
75
+ size === "xs" ? cn("h-(--control-xs) gap-1 px-1.5", FIELD_SHELL_XS) : "h-(--control-md) gap-2 rounded-sm border border-field-border bg-field px-3",
76
76
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
77
77
  "data-[popup-open]:ring-2 data-[popup-open]:ring-ring",
78
78
  // Invalid like every other field: Base UI marks the trigger
@@ -124,8 +124,8 @@ var selectMeta = {
124
124
  }
125
125
  ],
126
126
  tokensUsed: [
127
- "input",
128
- "card",
127
+ "field-border",
128
+ "field",
129
129
  "foreground",
130
130
  "ring",
131
131
  "border",
@@ -61,11 +61,12 @@ function useKeyboardInset(enabled) {
61
61
  Math.abs(viewport.scale - 1) > 0.01 ? 0 : Math.max(0, Math.round(window.innerHeight - viewport.height - viewport.offsetTop))
62
62
  );
63
63
  measure();
64
- viewport.addEventListener("resize", measure);
65
- viewport.addEventListener("scroll", measure);
64
+ const listeners = new AbortController();
65
+ const { signal } = listeners;
66
+ viewport.addEventListener("resize", measure, { signal });
67
+ viewport.addEventListener("scroll", measure, { signal });
66
68
  return () => {
67
- viewport.removeEventListener("resize", measure);
68
- viewport.removeEventListener("scroll", measure);
69
+ listeners.abort();
69
70
  setInset(0);
70
71
  };
71
72
  }, [enabled]);
@@ -10,6 +10,7 @@ import {
10
10
  import { cn } from "../../lib/cn.js";
11
11
  import { HighlightGlide } from "../../lib/highlight-glide.js";
12
12
  import { motionAttribute } from "../../motion/MotionProvider.js";
13
+ import { useAppShellFrame } from "../app-shell/app-shell-frame.js";
13
14
  import { RAIL_ROW_INLINE, sideNavItemIconVariants, sideNavItemVariants } from "../side-nav/side-nav.variants.js";
14
15
  import { SimpleTooltip } from "../tooltip/index.js";
15
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -27,6 +28,8 @@ var SIDEBAR_VARIANT = {
27
28
  app: "hidden lg:flex lg:sticky lg:top-0 lg:h-[calc(100vh-var(--dock-height,0px))] lg:self-start",
28
29
  inline: "flex h-full"
29
30
  };
31
+ var INSET_RAIL = "lg:h-auto lg:self-stretch";
32
+ var INSET_RAIL_SURFACE = "lg:border-r-0 lg:[--sidebar:var(--shell-ground)]";
30
33
  var SIDEBAR_INK = [
31
34
  "[--background:var(--sidebar)] [--sable-surface-bg:var(--sidebar)]",
32
35
  "[--foreground:var(--sidebar-foreground)]",
@@ -54,6 +57,7 @@ function Sidebar({
54
57
  }) {
55
58
  const [hasCollapsed, setHasCollapsed] = useState(collapsed);
56
59
  if (collapsed && !hasCollapsed) setHasCollapsed(true);
60
+ const inset = useAppShellFrame() === "inset";
57
61
  return /* @__PURE__ */ jsx(SidebarContext.Provider, { value: { collapsed, icons, reopened: hasCollapsed && !collapsed }, children: /* @__PURE__ */ jsx(
58
62
  "aside",
59
63
  {
@@ -61,6 +65,7 @@ function Sidebar({
61
65
  "data-collapsed": collapsed || void 0,
62
66
  "data-variant": variant,
63
67
  "data-surface": surface,
68
+ "data-frame": inset ? "inset" : void 0,
64
69
  className: cn(
65
70
  "shrink-0 flex-col bg-sidebar text-sidebar-foreground",
66
71
  // The rail is its own surface. Anything placed in it that inks with
@@ -71,8 +76,11 @@ function Sidebar({
71
76
  // a theme names --sidebar-muted-foreground.
72
77
  // On the page surface the rail is separated by space and its
73
78
  // content, not a rule and a tonal step.
74
- surface === "rail" ? cn("border-r border-sidebar-border", SIDEBAR_INK) : PAGE_INK,
79
+ // In an inset AppShell the rail is the ground itself, so from lg its border
80
+ // goes too: the gap around the main panel is the divider.
81
+ surface === "rail" ? cn("border-r border-sidebar-border", inset && INSET_RAIL_SURFACE, SIDEBAR_INK) : PAGE_INK,
75
82
  SIDEBAR_VARIANT[variant],
83
+ inset && INSET_RAIL,
76
84
  "w-[var(--sidebar-width,16rem)] data-[collapsed]:w-[var(--sidebar-width-collapsed,4rem)]",
77
85
  // Collapsing eases the width (`motion-size`, expressive) rather than
78
86
  // jumping 192px in one frame. It reflows the page beside the rail for
@@ -164,11 +172,18 @@ function SidebarFooter({
164
172
  className,
165
173
  ...props
166
174
  }) {
175
+ const inset = useAppShellFrame() === "inset";
167
176
  return /* @__PURE__ */ jsx(
168
177
  "div",
169
178
  {
170
179
  className: cn(
171
180
  "flex flex-col gap-1 border-t border-sidebar-border px-3 py-4",
181
+ // In an inset shell the rail has no edge of its own (the gap is the
182
+ // divider), so a full-width rule ran out into that gap and stopped on
183
+ // nothing. Inset to the content column it spans what the rows span:
184
+ // the padding becomes margin, so the rule shortens and nothing inside
185
+ // moves.
186
+ inset && "lg:mx-3 lg:px-0",
172
187
  className
173
188
  ),
174
189
  ...props
@@ -24,6 +24,10 @@ var sidebarMeta = {
24
24
  guidance: "do",
25
25
  description: `Set surface="page" when a full-width top bar sits above the rail. A theme's tinted or dark rail is for a full-height rail that frames the app.`
26
26
  },
27
+ {
28
+ guidance: "do",
29
+ description: `Keep surface="rail" inside AppShell frame="inset": the rail's tint becomes the ground the main panel floats on. Do not add a border-r class back \u2014 the gap is the divider.`
30
+ },
27
31
  {
28
32
  guidance: "dont",
29
33
  description: "Do not use Sidebar for page-local settings, filters, or temporary action groups."
@@ -76,7 +80,7 @@ var sidebarMeta = {
76
80
  name: "surface",
77
81
  type: '"rail" | "page"',
78
82
  default: '"rail"',
79
- description: `Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border \u2014 right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border \u2014 use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat.`
83
+ description: `Which surface the rail is painted on. "rail" is its own --sidebar-* surface with a border \u2014 right for a full-height rail that frames the app, which a theme may tint or paint dark. "page" uses the page's surface and ink with no border \u2014 use it when a full-width header sits above the rail, where a tinted column starting under a page-coloured bar reads as pasted on. Use it instead of overriding bg-/text- classes, which the rail's re-pointed tokens defeat. In AppShell's sidebar slot with frame="inset", the rail's surface is also the shell's ground: from lg a "rail" rail drops its border (the gap around the main panel divides them) and either surface stretches to the shell's body row instead of sizing to the viewport. AppShell signals this to its sidebar slot only, so a Sidebar elsewhere in the page is unaffected.`
80
84
  },
81
85
  {
82
86
  name: "motion",
@@ -146,7 +150,8 @@ var sidebarMeta = {
146
150
  "sidebar-border",
147
151
  "sidebar-accent",
148
152
  "sidebar-accent-foreground",
149
- "sidebar-ring"
153
+ "sidebar-ring",
154
+ "shell-ground"
150
155
  ],
151
156
  variants: {
152
157
  variant: ["app", "inline"]
@@ -106,7 +106,14 @@ var sparklineMeta = {
106
106
  "tone-warning",
107
107
  "muted-foreground",
108
108
  "success",
109
- "destructive"
109
+ "destructive",
110
+ "destructive-mark",
111
+ "success-mark",
112
+ "tone-warning-mark",
113
+ "tone-info-mark",
114
+ "tone-1-mark",
115
+ "tone-2-mark",
116
+ "tone-3-mark"
110
117
  ],
111
118
  motion: [
112
119
  {
@@ -83,7 +83,14 @@ var statusDotMeta = {
83
83
  "tone-info",
84
84
  "tone-1",
85
85
  "tone-2",
86
- "destructive"
86
+ "destructive",
87
+ "destructive-mark",
88
+ "success-mark",
89
+ "tone-warning-mark",
90
+ "tone-info-mark",
91
+ "tone-1-mark",
92
+ "tone-2-mark",
93
+ "tone-3-mark"
87
94
  ],
88
95
  a11y: [
89
96
  "Rendered aria-hidden \u2014 purely decorative. Convey status in adjacent text, not color alone.",
@@ -4,13 +4,13 @@ var statusDotVariants = cva("inline-block shrink-0 rounded-full", {
4
4
  variants: {
5
5
  tone: {
6
6
  neutral: "bg-muted-foreground",
7
- "3": "bg-tone-3",
8
- success: "bg-success",
9
- warning: "bg-tone-warning",
10
- info: "bg-tone-info",
11
- "1": "bg-tone-1",
12
- "2": "bg-tone-2",
13
- destructive: "bg-destructive",
7
+ "3": "bg-tone-3-mark",
8
+ success: "bg-success-mark",
9
+ warning: "bg-tone-warning-mark",
10
+ info: "bg-tone-info-mark",
11
+ "1": "bg-tone-1-mark",
12
+ "2": "bg-tone-2-mark",
13
+ destructive: "bg-destructive-mark",
14
14
  // A palette family paints from the slot its data-palette scope fills.
15
15
  red: "bg-palette-dot",
16
16
  orange: "bg-palette-dot",
@@ -115,7 +115,8 @@ var stepperMeta = {
115
115
  "border-strong",
116
116
  "destructive",
117
117
  "destructive-foreground",
118
- "ring"
118
+ "ring",
119
+ "destructive-mark"
119
120
  ],
120
121
  motion: [
121
122
  {
@@ -24,7 +24,7 @@ var stepperDotVariants = cva(
24
24
  complete: "bg-primary",
25
25
  current: "bg-[var(--sable-surface-bg,var(--background))] ring-[0.1875em] ring-primary ring-inset",
26
26
  upcoming: "bg-border-strong",
27
- error: "bg-destructive"
27
+ error: "bg-destructive-mark"
28
28
  }
29
29
  },
30
30
  defaultVariants: {
@@ -100,13 +100,13 @@ var tableMeta = {
100
100
  },
101
101
  {
102
102
  name: "TableHead",
103
- type: 'ComponentPropsWithRef<"th">',
104
- description: "A column header cell (th) in muted label styling."
103
+ type: 'ComponentPropsWithRef<"th"> & { numeric?: boolean }',
104
+ description: "A column header cell (th) in muted label styling. `numeric` marks a number column: end-aligned, tabular figures \u2014 set it on the column's cells too."
105
105
  },
106
106
  {
107
107
  name: "TableCell",
108
- type: 'ComponentPropsWithRef<"td">',
109
- description: "A data cell (td)."
108
+ type: 'ComponentPropsWithRef<"td"> & { numeric?: boolean }',
109
+ description: "A data cell (td). `numeric` end-aligns it with tabular figures, matching its column's header."
110
110
  },
111
111
  {
112
112
  name: "TableCaption",
@@ -23,6 +23,11 @@ function Textarea({
23
23
  // already lost the invalid ring Input shows on focus.
24
24
  FIELD_SHELL,
25
25
  FIELD_INVALID,
26
+ // Text being edited must not rewrap above the caret. `text-wrap-style`
27
+ // inherits, so a field inside a `text-pretty` or `text-balance` block
28
+ // would re-break every earlier line on each keystroke; `stable` is
29
+ // what an editing surface asks for. Longhand only: the wrap mode stays.
30
+ "[text-wrap-style:stable]",
26
31
  autoResize && "field-sizing-content resize-none",
27
32
  tier.body,
28
33
  density === "comfortable" && MOBILE_FIELD_TIER,
@@ -1,7 +1,7 @@
1
1
  // src/components/textarea/textarea.meta.ts
2
2
  var textareaMeta = {
3
3
  name: "Textarea",
4
- summary: "Styled native multi-line text control. Soft-square, card surface, --ring focus, and an aria-invalid destructive error state.",
4
+ summary: "Styled native multi-line text control. Soft-square, the theme's field surface, --ring focus, and an aria-invalid destructive error state.",
5
5
  status: "stable",
6
6
  keywords: [
7
7
  "textarea",
@@ -75,8 +75,8 @@ var textareaMeta = {
75
75
  }
76
76
  ],
77
77
  tokensUsed: [
78
- "input",
79
- "card",
78
+ "field-border",
79
+ "field",
80
80
  "foreground",
81
81
  "muted-foreground",
82
82
  "ring",