@sableui/react 0.14.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 (163) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DESIGN.md +3 -1
  3. package/bin/sable-lint.mjs +5 -1
  4. package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
  5. package/dist/blocks/index.d.ts +12 -1
  6. package/dist/blocks/index.js +4 -0
  7. package/dist/blocks/message-reactions-block.js +87 -0
  8. package/dist/blocks/message-reactions-block.meta.js +23 -0
  9. package/dist/blocks/wallet-block.js +2 -2
  10. package/dist/components/app-shell/app-shell-frame.js +16 -0
  11. package/dist/components/app-shell/app-shell-main.js +12 -0
  12. package/dist/components/app-shell/app-shell.js +28 -4
  13. package/dist/components/app-shell/app-shell.meta.js +88 -4
  14. package/dist/components/area-chart/area-chart.meta.js +6 -1
  15. package/dist/components/attachment/attachment.meta.js +2 -1
  16. package/dist/components/attachment/attachment.variants.js +1 -1
  17. package/dist/components/badge/badge.meta.js +8 -1
  18. package/dist/components/badge/badge.variants.js +7 -7
  19. package/dist/components/banner/banner.meta.js +6 -1
  20. package/dist/components/banner/banner.variants.js +5 -5
  21. package/dist/components/bar-chart/bar-chart.meta.js +6 -1
  22. package/dist/components/bubble/bubble.js +105 -5
  23. package/dist/components/bubble/bubble.meta.js +42 -6
  24. package/dist/components/bubble/bubble.variants.js +1 -1
  25. package/dist/components/bubble/index.js +2 -0
  26. package/dist/components/caret-menu/caret-menu.js +5 -6
  27. package/dist/components/chat-composer/chat-composer.meta.js +2 -2
  28. package/dist/components/chat-composer/chat-composer.variants.js +1 -1
  29. package/dist/components/color-field/color-field.js +6 -8
  30. package/dist/components/combobox/combobox.js +27 -5
  31. package/dist/components/combobox/combobox.meta.js +10 -2
  32. package/dist/components/command-menu/command-menu.js +14 -3
  33. package/dist/components/command-menu/command-menu.meta.js +9 -1
  34. package/dist/components/date-picker/date-picker.meta.js +2 -2
  35. package/dist/components/feedback/feedback.js +9 -2
  36. package/dist/components/feedback/feedback.meta.js +8 -1
  37. package/dist/components/gradient-art/gradient-art.js +8 -5
  38. package/dist/components/gradient-art/gradient-art.meta.js +35 -2
  39. package/dist/components/input/input.meta.js +11 -3
  40. package/dist/components/input-group/input-group.js +1 -1
  41. package/dist/components/input-group/input-group.meta.js +1 -1
  42. package/dist/components/like-button/index.js +7 -0
  43. package/dist/components/like-button/like-button.js +89 -0
  44. package/dist/components/like-button/like-button.meta.js +132 -0
  45. package/dist/components/line-chart/line-chart.meta.js +6 -1
  46. package/dist/components/menu/menu.js +1 -1
  47. package/dist/components/meter/meter.js +53 -7
  48. package/dist/components/meter/meter.meta.js +51 -4
  49. package/dist/components/meter/meter.variants.js +43 -7
  50. package/dist/components/note/note.meta.js +5 -1
  51. package/dist/components/note/note.variants.js +4 -4
  52. package/dist/components/number-field/number-field.meta.js +2 -2
  53. package/dist/components/otp-field/otp-field.meta.js +2 -2
  54. package/dist/components/panel-group/panel-group.js +5 -8
  55. package/dist/components/pie-chart/pie-chart.meta.js +6 -1
  56. package/dist/components/radar-chart/radar-chart.meta.js +6 -1
  57. package/dist/components/radial-chart/radial-chart.meta.js +6 -1
  58. package/dist/components/reaction-picker/index.js +13 -0
  59. package/dist/components/reaction-picker/reaction-picker.js +104 -0
  60. package/dist/components/reaction-picker/reaction-picker.meta.js +156 -0
  61. package/dist/components/search-input/search-input.meta.js +11 -3
  62. package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
  63. package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
  64. package/dist/components/select/select.js +1 -1
  65. package/dist/components/select/select.meta.js +2 -2
  66. package/dist/components/sheet/sheet.js +5 -4
  67. package/dist/components/sidebar/sidebar.js +16 -1
  68. package/dist/components/sidebar/sidebar.meta.js +7 -2
  69. package/dist/components/sparkline/sparkline.meta.js +8 -1
  70. package/dist/components/status-dot/status-dot.meta.js +8 -1
  71. package/dist/components/status-dot/status-dot.variants.js +7 -7
  72. package/dist/components/stepper/stepper.meta.js +2 -1
  73. package/dist/components/stepper/stepper.variants.js +1 -1
  74. package/dist/components/table/table.meta.js +4 -4
  75. package/dist/components/textarea/textarea.js +5 -0
  76. package/dist/components/textarea/textarea.meta.js +3 -3
  77. package/dist/components/toast/toast.js +25 -13
  78. package/dist/components/toast/toast.meta.js +7 -1
  79. package/dist/file-hashes.json +112 -100
  80. package/dist/foundations/accessibility.js +5 -0
  81. package/dist/foundations/motion.js +21 -1
  82. package/dist/icon-sets/reicon.js +11 -1
  83. package/dist/index.d.ts +425 -84
  84. package/dist/index.js +11 -1
  85. package/dist/lib/chart/types.js +8 -8
  86. package/dist/lib/chart/use-arrival.js +5 -4
  87. package/dist/lib/field-shell.js +1 -1
  88. package/dist/lib/highlight-glide.js +6 -6
  89. package/dist/lib/scrub-gesture.js +6 -6
  90. package/dist/lib/taxonomy.js +2 -0
  91. package/dist/lib/use-caret-trigger.js +11 -18
  92. package/dist/lib/use-match-highlight.js +69 -0
  93. package/dist/lib/use-smooth-wheel.js +10 -8
  94. package/dist/motion/Burst.js +79 -0
  95. package/dist/motion/index.js +2 -0
  96. package/dist/presets/index.d.ts +13 -2
  97. package/dist/presets/index.js +17 -1
  98. package/dist/styles.css +241 -0
  99. package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
  100. package/dist/themes/_contract.js +14 -0
  101. package/dist/themes/atelier/atelier.css +18 -0
  102. package/dist/themes/atelier/index.d.ts +1 -1
  103. package/dist/themes/contrast.js +48 -1
  104. package/dist/themes/ember/ember.css +18 -0
  105. package/dist/themes/ember/index.d.ts +1 -1
  106. package/dist/themes/index.d.ts +2 -2
  107. package/dist/themes/meridian/index.d.ts +1 -1
  108. package/dist/themes/meridian/meridian.css +18 -0
  109. package/dist/themes/poppy/index.d.ts +1 -1
  110. package/dist/themes/poppy/poppy.css +18 -0
  111. package/dist/themes/prism/index.d.ts +1 -1
  112. package/dist/themes/prism/prism.css +18 -0
  113. package/dist/themes/sable/index.d.ts +1 -1
  114. package/dist/themes/sable/sable.css +18 -0
  115. package/dist/themes/verdant/index.d.ts +1 -1
  116. package/dist/themes/verdant/verdant.css +18 -0
  117. package/llms/blocks/message-reactions-block.md +116 -0
  118. package/llms/blocks/wallet-block.md +5 -4
  119. package/llms/components/app-shell.md +72 -3
  120. package/llms/components/area-chart.md +1 -1
  121. package/llms/components/attachment.md +1 -1
  122. package/llms/components/badge.md +1 -1
  123. package/llms/components/banner.md +1 -1
  124. package/llms/components/bar-chart.md +1 -1
  125. package/llms/components/bubble.md +17 -7
  126. package/llms/components/chat-composer.md +1 -1
  127. package/llms/components/combobox.md +2 -1
  128. package/llms/components/command-menu.md +3 -2
  129. package/llms/components/date-picker.md +1 -1
  130. package/llms/components/feedback.md +2 -1
  131. package/llms/components/gradient-art.md +28 -1
  132. package/llms/components/input-group.md +1 -1
  133. package/llms/components/input.md +4 -2
  134. package/llms/components/like-button.md +91 -0
  135. package/llms/components/line-chart.md +1 -1
  136. package/llms/components/meter.md +33 -4
  137. package/llms/components/note.md +1 -1
  138. package/llms/components/number-field.md +1 -1
  139. package/llms/components/otp-field.md +1 -1
  140. package/llms/components/pie-chart.md +1 -1
  141. package/llms/components/radar-chart.md +1 -1
  142. package/llms/components/radial-chart.md +1 -1
  143. package/llms/components/reaction-picker.md +115 -0
  144. package/llms/components/search-input.md +20 -2
  145. package/llms/components/segmented-bar.md +1 -1
  146. package/llms/components/select.md +1 -1
  147. package/llms/components/sidebar.md +3 -2
  148. package/llms/components/sparkline.md +1 -1
  149. package/llms/components/status-dot.md +1 -1
  150. package/llms/components/stepper.md +1 -1
  151. package/llms/components/table.md +2 -2
  152. package/llms/components/textarea.md +2 -2
  153. package/llms/components/toast.md +2 -1
  154. package/llms/foundations/accessibility.md +1 -0
  155. package/llms/foundations/motion.md +18 -2
  156. package/llms/reference/token-intent.md +22 -2
  157. package/llms/reference/utilities.md +9 -3
  158. package/llms-full.txt +596 -61
  159. package/llms.txt +10 -7
  160. package/package.json +1 -1
  161. package/registry.json +968 -68
  162. package/scripts/lint-rules.mjs +6 -3
  163. package/targets/ios.json +18 -0
@@ -0,0 +1,132 @@
1
+ // src/components/like-button/like-button.meta.ts
2
+ var likeButtonMeta = {
3
+ name: "LikeButton",
4
+ summary: "A pressed-or-not toggle for a like, a star or a save, with its count. Pressing it on fills the glyph, which blooms while a ring and a spray of dots burst from it and the count rolls up; pressing it off is quiet.",
5
+ status: "stable",
6
+ keywords: [
7
+ "like",
8
+ "heart",
9
+ "favorite",
10
+ "favourite",
11
+ "love",
12
+ "star",
13
+ "bookmark",
14
+ "save",
15
+ "upvote",
16
+ "reaction",
17
+ "count",
18
+ "toggle"
19
+ ],
20
+ usage: {
21
+ description: "Use LikeButton for a personal, reversible endorsement that other people can see the tally of: liking a post, starring a repository, saving a recipe. It is the one place in Sable that celebrates, because the person chose it and it costs nothing to undo.",
22
+ bestPractices: [
23
+ {
24
+ guidance: "do",
25
+ description: "Pass the server's total as `count` and update it when the save lands. The button shows the viewer's own like at once, before the save, and goes back to `count` exactly whenever it changes, so a failed save that leaves `count` alone can also turn `pressed` back off."
26
+ },
27
+ {
28
+ guidance: "do",
29
+ description: 'Match the glyph, family and label to the meaning: `icon="star" tone="yellow" label="Star"` for a star. The label is the accessible name, so it must say what pressing does.'
30
+ },
31
+ {
32
+ guidance: "do",
33
+ description: 'Use `format={{ notation: "compact" }}` once counts pass a few thousand, so a feed of buttons keeps one width.'
34
+ },
35
+ {
36
+ guidance: "dont",
37
+ description: "Do not use it for a setting or a view switch (bold, mute, grid or list). Those are tools, not endorsements, and a burst on every press of a formatting control is noise; use Toggle.",
38
+ instead: ["Toggle"]
39
+ },
40
+ {
41
+ guidance: "dont",
42
+ description: "Do not use it for a rating or a verdict on an answer (helpful, not helpful). That is a question with two sides; use Feedback.",
43
+ instead: ["Feedback"]
44
+ },
45
+ {
46
+ guidance: "dont",
47
+ description: "Do not press it for the user. A burst is a reply to a person's choice \u2014 liking something on their behalf, or on load, turns delight into a notification."
48
+ }
49
+ ]
50
+ },
51
+ related: {
52
+ components: ["Toggle", "Feedback", "NumberTicker", "Button"]
53
+ },
54
+ props: [
55
+ {
56
+ name: "count",
57
+ type: "number",
58
+ description: "The total, rolled when it changes. Pressing adds or removes the viewer's like at once; the button shows `count` exactly again whenever `count` changes. Omit it for a bare glyph (an icon-size button)."
59
+ },
60
+ { name: "pressed", type: "boolean", description: "Controlled pressed state." },
61
+ { name: "defaultPressed", type: "boolean", default: "false", description: "Initial pressed state when uncontrolled." },
62
+ {
63
+ name: "onPressedChange",
64
+ type: "(pressed: boolean, event) => void",
65
+ description: "Called when the user presses it, with the new state."
66
+ },
67
+ { name: "icon", type: "string", default: '"heart"', description: "The glyph, by icon name. Its `solid` variant is shown while pressed." },
68
+ {
69
+ name: "tone",
70
+ type: "PaletteColor",
71
+ default: '"red"',
72
+ description: "The palette family the glyph fills with while pressed, and the burst blooms from. A family, so it follows the theme."
73
+ },
74
+ { name: "label", type: "string", default: '"Like"', description: "The accessible name, read before the count." },
75
+ { name: "format", type: "Intl.NumberFormatOptions", description: 'How to write the count, e.g. `{ notation: "compact" }`.' },
76
+ { name: "variant", type: '"ghost" | "outline" | "secondary"', default: '"ghost"', description: "The Button surface." },
77
+ { name: "size", type: '"xs" | "sm" | "md"', default: '"sm"', description: "A Button size; without a count, the matching icon size." },
78
+ { name: "disabled", type: "boolean", default: "false", description: "Disables it." },
79
+ {
80
+ name: "motion",
81
+ type: '"full" | "subtle" | "off"',
82
+ default: '"full"',
83
+ description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The bloom and the burst are the flourish; the fill and the count changing stay."
84
+ }
85
+ ],
86
+ variants: {
87
+ variant: ["ghost", "outline", "secondary"],
88
+ size: ["xs", "sm", "md"],
89
+ tone: ["red", "orange", "yellow", "green", "teal", "blue", "purple", "pink"]
90
+ },
91
+ tokensUsed: ["palette-dot", "accent", "ring"],
92
+ motion: [
93
+ {
94
+ slot: "arrival",
95
+ recipe: "none",
96
+ part: "Root",
97
+ why: "It arrives with the post or row it belongs to. A page that loads with likes already given must be still: nothing blooms on mount."
98
+ },
99
+ {
100
+ slot: "response",
101
+ recipe: "motion-bloom",
102
+ part: "Glyph",
103
+ why: "Pressing it on is the moment. The glyph is 16px, so it needs real amplitude to read as an event: it grows from nothing past its size and settles, after a 90ms beat in which the ring leads. Inherits Button's motion-press for the press itself. Pressing off has no bloom \u2014 taking a like back is not a celebration."
104
+ },
105
+ {
106
+ slot: "transition",
107
+ recipe: "custom",
108
+ part: "Burst",
109
+ why: "The follow-through: <Burst> draws a disc that hollows into a ring and shifts colour, then dots thrown outward in the theme's palette families. Expressive only \u2014 under `subtle` or reduced motion it renders nothing and the fill alone answers the press. The count rolls with NumberTicker."
110
+ },
111
+ {
112
+ slot: "activity",
113
+ recipe: "none",
114
+ part: "Root",
115
+ why: "There is no in-between state to show: the like is shown at once, before it is saved."
116
+ }
117
+ ],
118
+ a11y: [
119
+ "A native <button> through Button, with `aria-pressed`. Enter and Space press it.",
120
+ `The accessible name is the label then the count ("Like 1,301"); the burst is aria-hidden and the rolling digits are hidden behind the count's real text.`,
121
+ "The pressed state is carried by the filled glyph and `aria-pressed`, never by colour alone.",
122
+ "Under reduced motion the glyph fills and the count changes at once; the bloom and the burst are dropped."
123
+ ],
124
+ examples: [
125
+ { label: "A post's likes", code: "<LikeButton count={post.likes} defaultPressed={post.liked} onPressedChange={(on) => api.like(post.id, on)} />" },
126
+ { label: "Star a repository", code: '<LikeButton icon="star" tone="yellow" label="Star" variant="outline" count={repo.stars} format={{ notation: "compact" }} />' },
127
+ { label: "Bare glyph", code: '<LikeButton label="Save recipe" />' }
128
+ ]
129
+ };
130
+ export {
131
+ likeButtonMeta
132
+ };
@@ -180,7 +180,12 @@ var lineChartMeta = {
180
180
  "popover",
181
181
  "popover-foreground",
182
182
  "foreground",
183
- "background"
183
+ "background",
184
+ "tone-warning-mark",
185
+ "tone-info-mark",
186
+ "tone-1-mark",
187
+ "tone-2-mark",
188
+ "tone-3-mark"
184
189
  ],
185
190
  motion: [
186
191
  {
@@ -68,7 +68,7 @@ var MENU_ITEM_DESTRUCTIVE = (
68
68
  // enough for a white label, and ink mixed from them measured 3.8:1 on
69
69
  // Meridian's popover. From the text token every theme clears 4.5:1 (worst
70
70
  // 5.4:1, Prism dark, on the 10% red wash).
71
- "text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))] data-[highlighted]:bg-destructive/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
71
+ "text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))] data-[highlighted]:bg-destructive-mark/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
72
72
  );
73
73
  function MenuItem({ className, variant = "default", icon, description, children, ...props }) {
74
74
  const tier = useTypeTier();
@@ -1,22 +1,30 @@
1
1
  // src/components/meter/meter.tsx
2
2
  import { Meter as Base } from "@base-ui/react/meter";
3
- import { createContext, useContext } from "react";
3
+ import { createContext, useContext, useState } from "react";
4
4
  import { cn } from "../../lib/cn.js";
5
5
  import {
6
6
  meterIndicatorVariants,
7
7
  meterRingValueVariants,
8
8
  meterRingVariants,
9
+ meterSegmentedTrackVariants,
10
+ meterTickVariants,
9
11
  meterTrackVariants
10
12
  } from "./meter.variants.js";
11
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
14
  var ToneContext = createContext("primary");
13
15
  var SizeContext = createContext(void 0);
16
+ var SegmentsContext = createContext(null);
17
+ function filledTicks({ count, value, min, max }) {
18
+ if (value == null || value <= min) return 0;
19
+ const ratio = max === min ? 1 : Math.min(1, (value - min) / (max - min));
20
+ return Math.min(count, Math.max(1, Math.round(ratio * count)));
21
+ }
14
22
  var RING_TONE_STROKE = {
15
23
  primary: "var(--primary)",
16
- "3": "var(--tone-3)",
17
- success: "var(--success)",
18
- warning: "var(--tone-warning)",
19
- destructive: "var(--destructive)"
24
+ "3": "var(--tone-3-mark)",
25
+ success: "var(--success-mark)",
26
+ warning: "var(--tone-warning-mark)",
27
+ destructive: "var(--destructive-mark)"
20
28
  };
21
29
  function MeterRing({
22
30
  value,
@@ -53,15 +61,30 @@ function MeterRing({
53
61
  showValue ? /* @__PURE__ */ jsx(MeterValue, { className: meterRingValueVariants({ size }) }) : null
54
62
  ] });
55
63
  }
56
- function Meter({ className, children, tone = "primary", shape = "bar", size, strokeWidth = 6, showValue, ...props }) {
64
+ function Meter({ className, children, tone = "primary", shape = "bar", size, strokeWidth = 6, showValue, segments, ...props }) {
57
65
  if (shape === "ring") {
58
66
  return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(Base.Root, { className: cn(meterRingVariants({ size }), className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterRing, { value: props.value ?? null, min: props.min, max: props.max, strokeWidth, showValue, tone, size }) }) });
59
67
  }
60
- return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(SizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(Base.Root, { className: cn("w-full", className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterTrack, {}) }) }) });
68
+ const count = segments != null && Number.isFinite(segments) && segments >= 1 ? Math.floor(segments) : 0;
69
+ const segmented = count ? { count, value: props.value ?? null, min: props.min ?? 0, max: props.max ?? 100 } : null;
70
+ return /* @__PURE__ */ jsx(ToneContext.Provider, { value: tone, children: /* @__PURE__ */ jsx(SizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx(SegmentsContext.Provider, { value: segmented, children: /* @__PURE__ */ jsx(Base.Root, { className: cn("w-full", className), ...props, children: children ?? /* @__PURE__ */ jsx(MeterTrack, {}) }) }) }) });
61
71
  }
62
72
  Meter.displayName = "Meter";
63
73
  function MeterTrack({ className, children, size, ...props }) {
64
74
  const inherited = useContext(SizeContext);
75
+ const tone = useContext(ToneContext);
76
+ const segments = useContext(SegmentsContext);
77
+ if (segments) {
78
+ return /* @__PURE__ */ jsx(
79
+ Base.Track,
80
+ {
81
+ "data-segmented": "",
82
+ className: cn(meterSegmentedTrackVariants({ size: size ?? inherited }), className),
83
+ ...props,
84
+ children: /* @__PURE__ */ jsx(MeterTicks, { count: segments.count, filled: filledTicks(segments), tone })
85
+ }
86
+ );
87
+ }
65
88
  return /* @__PURE__ */ jsx(
66
89
  Base.Track,
67
90
  {
@@ -72,6 +95,29 @@ function MeterTrack({ className, children, size, ...props }) {
72
95
  );
73
96
  }
74
97
  MeterTrack.displayName = "MeterTrack";
98
+ function MeterTicks({ count, filled, tone }) {
99
+ const [shown, setShown] = useState(filled);
100
+ const [from, setFrom] = useState(filled);
101
+ if (filled !== shown) {
102
+ setFrom(shown);
103
+ setShown(filled);
104
+ }
105
+ return Array.from({ length: count }, (_, i) => {
106
+ const isFilled = i < filled;
107
+ const step = filled > from && i >= from && i < filled ? i - from : filled < from && i >= filled && i < from ? from - 1 - i : 0;
108
+ return /* @__PURE__ */ jsx(
109
+ "span",
110
+ {
111
+ "aria-hidden": "true",
112
+ "data-slot": "meter-tick",
113
+ "data-filled": isFilled ? "" : void 0,
114
+ className: meterTickVariants({ tone, filled: isFilled }),
115
+ style: step ? { transitionDelay: `calc(var(--motion-duration-slow) * ${Number((step / count).toFixed(4))} * var(--motion-expressive, 1))` } : void 0
116
+ },
117
+ i
118
+ );
119
+ });
120
+ }
75
121
  function MeterIndicator({ className, tone, ...props }) {
76
122
  const inherited = useContext(ToneContext);
77
123
  return /* @__PURE__ */ jsx(
@@ -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
  {
@@ -0,0 +1,13 @@
1
+ // src/components/reaction-picker/index.ts
2
+ import {
3
+ DEFAULT_REACTIONS,
4
+ MenuReactions,
5
+ ReactionPicker
6
+ } from "./reaction-picker.js";
7
+ import { reactionPickerMeta } from "./reaction-picker.meta.js";
8
+ export {
9
+ DEFAULT_REACTIONS,
10
+ MenuReactions,
11
+ ReactionPicker,
12
+ reactionPickerMeta
13
+ };