@sableui/react 0.0.0-stage → 0.1.1

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 (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +55 -0
  3. package/CONTRIBUTING.md +221 -0
  4. package/DESIGN.md +548 -0
  5. package/LICENSE +21 -0
  6. package/README.md +194 -2
  7. package/THIRD_PARTY_NOTICES.md +44 -0
  8. package/bin/sable-lint.mjs +0 -0
  9. package/bin/sable.mjs +1223 -0
  10. package/dist/_contract-CYScOdf5.d.ts +150 -0
  11. package/dist/chunk-3IWZYBGH.js +398 -0
  12. package/dist/chunk-5Z5KLOAU.js +167 -0
  13. package/dist/chunk-7MG4SHYX.js +15 -0
  14. package/dist/chunk-ADLLDWLW.js +15 -0
  15. package/dist/chunk-EP55KSYA.js +14 -0
  16. package/dist/chunk-J23ELQQZ.js +9396 -0
  17. package/dist/chunk-JI7WJP2L.js +286 -0
  18. package/dist/chunk-K7RUHUGC.js +14 -0
  19. package/dist/chunk-KO3NAEBF.js +15 -0
  20. package/dist/chunk-L4IEIQQA.js +14 -0
  21. package/dist/chunk-L6Y46C62.js +270 -0
  22. package/dist/chunk-NEQNXI2G.js +25 -0
  23. package/dist/chunk-PAOZBZME.js +40 -0
  24. package/dist/chunk-RWP4XBSK.js +139 -0
  25. package/dist/chunk-SRQWQUTW.js +63 -0
  26. package/dist/chunk-YXBOYWBN.js +15 -0
  27. package/dist/foundations/index.d.ts +88 -0
  28. package/dist/foundations/index.js +792 -0
  29. package/dist/icon/index.d.ts +60 -0
  30. package/dist/icon/index.js +2 -0
  31. package/dist/icon-sets/brands.d.ts +44 -0
  32. package/dist/icon-sets/brands.js +147 -0
  33. package/dist/icon-sets/reicon.d.ts +8 -0
  34. package/dist/icon-sets/reicon.js +1 -0
  35. package/dist/index.d.ts +7013 -0
  36. package/dist/index.js +15462 -0
  37. package/dist/ios/chrome.css +83 -0
  38. package/dist/ios/frame.css +5 -0
  39. package/dist/ios/index.d.ts +70 -0
  40. package/dist/ios/index.js +313 -0
  41. package/dist/ios/preview.d.ts +64 -0
  42. package/dist/ios/preview.js +133 -0
  43. package/dist/ios/styles.css +77 -0
  44. package/dist/layout/index.d.ts +280 -0
  45. package/dist/layout/index.js +2 -0
  46. package/dist/meta-6dUzgC6M.d.ts +205 -0
  47. package/dist/patterns/index.d.ts +36 -0
  48. package/dist/patterns/index.js +934 -0
  49. package/dist/presets/index.d.ts +37 -0
  50. package/dist/presets/index.js +117 -0
  51. package/dist/registry-CyL28OzJ.d.ts +28 -0
  52. package/dist/styles.css +1893 -0
  53. package/dist/theme-provider-BWcizhAt.d.ts +105 -0
  54. package/dist/themes/atelier/atelier.css +213 -0
  55. package/dist/themes/atelier/index.d.ts +9 -0
  56. package/dist/themes/atelier/index.js +1 -0
  57. package/dist/themes/base/base.css +443 -0
  58. package/dist/themes/base/index.d.ts +9 -0
  59. package/dist/themes/base/index.js +1 -0
  60. package/dist/themes/ember/ember.css +270 -0
  61. package/dist/themes/ember/index.d.ts +9 -0
  62. package/dist/themes/ember/index.js +1 -0
  63. package/dist/themes/index.d.ts +10 -0
  64. package/dist/themes/index.js +8 -0
  65. package/dist/themes/meridian/index.d.ts +9 -0
  66. package/dist/themes/meridian/index.js +1 -0
  67. package/dist/themes/meridian/meridian.css +255 -0
  68. package/dist/themes/poppy/index.d.ts +6 -0
  69. package/dist/themes/poppy/index.js +1 -0
  70. package/dist/themes/poppy/poppy.css +174 -0
  71. package/dist/themes/prism/index.d.ts +6 -0
  72. package/dist/themes/prism/index.js +1 -0
  73. package/dist/themes/prism/prism.css +183 -0
  74. package/dist/themes/verdant/index.d.ts +9 -0
  75. package/dist/themes/verdant/index.js +1 -0
  76. package/dist/themes/verdant/verdant.css +210 -0
  77. package/dist/themes.css +30 -0
  78. package/llms/components/accordion.md +93 -0
  79. package/llms/components/app-header.md +70 -0
  80. package/llms/components/app-shell.md +117 -0
  81. package/llms/components/attachment.md +105 -0
  82. package/llms/components/avatar.md +141 -0
  83. package/llms/components/badge.md +117 -0
  84. package/llms/components/banner.md +119 -0
  85. package/llms/components/bottom-nav.md +80 -0
  86. package/llms/components/breadcrumbs.md +75 -0
  87. package/llms/components/browser.md +67 -0
  88. package/llms/components/bubble.md +119 -0
  89. package/llms/components/button.md +199 -0
  90. package/llms/components/calendar.md +75 -0
  91. package/llms/components/card.md +135 -0
  92. package/llms/components/carousel.md +87 -0
  93. package/llms/components/chat-composer.md +126 -0
  94. package/llms/components/checkbox-card.md +98 -0
  95. package/llms/components/checkbox.md +90 -0
  96. package/llms/components/code-block.md +81 -0
  97. package/llms/components/code.md +62 -0
  98. package/llms/components/collapsible.md +83 -0
  99. package/llms/components/color-field.md +100 -0
  100. package/llms/components/combobox.md +119 -0
  101. package/llms/components/command-menu.md +158 -0
  102. package/llms/components/context-card.md +73 -0
  103. package/llms/components/context-menu.md +79 -0
  104. package/llms/components/data-table.md +99 -0
  105. package/llms/components/date-picker.md +97 -0
  106. package/llms/components/description.md +96 -0
  107. package/llms/components/destructive-action-modal.md +76 -0
  108. package/llms/components/dialog.md +127 -0
  109. package/llms/components/dots-menu.md +64 -0
  110. package/llms/components/empty-state.md +113 -0
  111. package/llms/components/entity.md +67 -0
  112. package/llms/components/feedback.md +63 -0
  113. package/llms/components/fieldset.md +106 -0
  114. package/llms/components/file-dropzone.md +103 -0
  115. package/llms/components/form.md +111 -0
  116. package/llms/components/gliding-highlight.md +84 -0
  117. package/llms/components/inline-status.md +62 -0
  118. package/llms/components/input.md +127 -0
  119. package/llms/components/kbd.md +57 -0
  120. package/llms/components/label.md +73 -0
  121. package/llms/components/marker.md +99 -0
  122. package/llms/components/marquee.md +66 -0
  123. package/llms/components/menu.md +122 -0
  124. package/llms/components/message-scroller.md +110 -0
  125. package/llms/components/message.md +114 -0
  126. package/llms/components/meter.md +124 -0
  127. package/llms/components/middle-truncate.md +62 -0
  128. package/llms/components/note.md +81 -0
  129. package/llms/components/notification-badge.md +67 -0
  130. package/llms/components/number-field.md +70 -0
  131. package/llms/components/otp-field.md +101 -0
  132. package/llms/components/page-header.md +76 -0
  133. package/llms/components/page-loader.md +67 -0
  134. package/llms/components/pagination.md +92 -0
  135. package/llms/components/panel-group.md +120 -0
  136. package/llms/components/panel-section.md +122 -0
  137. package/llms/components/phone.md +70 -0
  138. package/llms/components/pill.md +73 -0
  139. package/llms/components/popover.md +79 -0
  140. package/llms/components/progress.md +98 -0
  141. package/llms/components/radio-card.md +90 -0
  142. package/llms/components/radio.md +86 -0
  143. package/llms/components/relative-time.md +60 -0
  144. package/llms/components/scroller.md +88 -0
  145. package/llms/components/scrub-field.md +97 -0
  146. package/llms/components/search-input.md +84 -0
  147. package/llms/components/select.md +106 -0
  148. package/llms/components/separator.md +70 -0
  149. package/llms/components/settings-menu.md +139 -0
  150. package/llms/components/sheet.md +97 -0
  151. package/llms/components/show-more.md +59 -0
  152. package/llms/components/side-nav.md +106 -0
  153. package/llms/components/sidebar.md +87 -0
  154. package/llms/components/skeleton.md +62 -0
  155. package/llms/components/slider.md +114 -0
  156. package/llms/components/spinner.md +87 -0
  157. package/llms/components/status-bar.md +91 -0
  158. package/llms/components/status-dot.md +70 -0
  159. package/llms/components/stepper.md +125 -0
  160. package/llms/components/streaming-text.md +86 -0
  161. package/llms/components/switch.md +87 -0
  162. package/llms/components/table.md +112 -0
  163. package/llms/components/tabs.md +84 -0
  164. package/llms/components/textarea.md +88 -0
  165. package/llms/components/theme-selector.md +95 -0
  166. package/llms/components/theme-switcher.md +71 -0
  167. package/llms/components/thinking-orb.md +91 -0
  168. package/llms/components/toast.md +130 -0
  169. package/llms/components/toggle.md +95 -0
  170. package/llms/components/toolbar.md +168 -0
  171. package/llms/components/tooltip.md +93 -0
  172. package/llms/components/tree.md +227 -0
  173. package/llms/components/video.md +66 -0
  174. package/llms/components/workspace.md +138 -0
  175. package/llms/foundations/accessibility.md +60 -0
  176. package/llms/foundations/motion.md +96 -0
  177. package/llms/foundations/scrolling.md +41 -0
  178. package/llms/foundations/shape.md +73 -0
  179. package/llms/foundations/spacing.md +82 -0
  180. package/llms/foundations/surfaces.md +69 -0
  181. package/llms/foundations/typography.md +120 -0
  182. package/llms/layout/aspect-box.md +37 -0
  183. package/llms/layout/bleed.md +31 -0
  184. package/llms/layout/center.md +37 -0
  185. package/llms/layout/columns.md +51 -0
  186. package/llms/layout/grid-overlay.md +40 -0
  187. package/llms/layout/grid.md +47 -0
  188. package/llms/layout/page.md +39 -0
  189. package/llms/layout/region.md +49 -0
  190. package/llms/layout/row.md +40 -0
  191. package/llms/layout/safe-area.md +37 -0
  192. package/llms/layout/spacer.md +39 -0
  193. package/llms/layout/split-pane.md +41 -0
  194. package/llms/layout/stack.md +39 -0
  195. package/llms/patterns/auth-page.md +68 -0
  196. package/llms/patterns/brand-landing-page.md +423 -0
  197. package/llms/patterns/chat-panel.md +210 -0
  198. package/llms/patterns/dashboard-page.md +170 -0
  199. package/llms/patterns/feed-page.md +103 -0
  200. package/llms/patterns/landing-page.md +122 -0
  201. package/llms/patterns/list-detail-page.md +100 -0
  202. package/llms/patterns/settings-page.md +121 -0
  203. package/llms/reference/token-intent.md +324 -0
  204. package/llms/reference/utilities.md +129 -0
  205. package/llms/tasks/adding-a-component.md +113 -0
  206. package/llms/tasks/auditing-an-app.md +216 -0
  207. package/llms/tasks/building-a-page.md +109 -0
  208. package/llms/tasks/building-a-tool.md +141 -0
  209. package/llms/tasks/building-an-ios-prototype.md +105 -0
  210. package/llms/tasks/building-with-sable.md +73 -0
  211. package/llms/tasks/choosing-a-component.md +109 -0
  212. package/llms/tasks/converting-an-app.md +139 -0
  213. package/llms/tasks/creating-a-preset.md +43 -0
  214. package/llms/tasks/creating-design-assets.md +96 -0
  215. package/llms/tasks/designing-a-surface.md +115 -0
  216. package/llms/tasks/developing-taste.md +157 -0
  217. package/llms/tasks/getting-started.md +63 -0
  218. package/llms/tasks/learning-from-builds.md +74 -0
  219. package/llms/tasks/reviewing-a-design.md +114 -0
  220. package/llms/tasks/reviewing-a-journey.md +84 -0
  221. package/llms/tasks/reviewing-motion.md +92 -0
  222. package/llms/tasks/using-presets.md +87 -0
  223. package/llms/taste/asset-plan.template.json +53 -0
  224. package/llms/taste/composition-follows-content.md +20 -0
  225. package/llms/taste/hierarchy-is-a-relationship.md +22 -0
  226. package/llms/taste/lessons.json +57 -0
  227. package/llms/taste/motion-needs-a-reason.md +21 -0
  228. package/llms/taste/surface-states-travel-together.md +20 -0
  229. package/llms-full.txt +14906 -0
  230. package/llms.txt +405 -0
  231. package/package.json +185 -3
  232. package/registry.json +18812 -0
  233. package/scripts/agent-setup.mjs +54 -0
  234. package/scripts/design-atlas.mjs +221 -0
  235. package/scripts/design-directions.mjs +123 -0
  236. package/scripts/design-memory.mjs +166 -0
  237. package/scripts/design-preferences.mjs +227 -0
  238. package/scripts/design-presets.mjs +48 -0
  239. package/scripts/grade.mjs +509 -0
  240. package/scripts/lint-rules.mjs +1065 -0
  241. package/scripts/review-evidence.mjs +120 -0
  242. package/scripts/skeleton.mjs +248 -0
  243. package/skills/entry/SKILL.md +34 -0
  244. package/skills/sable/SKILL.md +187 -0
  245. package/skills/sable/agents/openai.yaml +6 -0
  246. package/targets/ios.json +982 -0
  247. package/third-party/reicon-LICENSE.txt +20 -0
@@ -0,0 +1,114 @@
1
+ # Slider
2
+
3
+ **Status:** stable
4
+
5
+ Slider over Base UI Slider — pointer + keyboard control, ARIA slider wiring, and range support via multiple thumbs. The default export composes Root + Control + Track + Indicator + Thumb; pass an array value for a range.
6
+
7
+ **Keywords:** slider, range, value, input
8
+
9
+ ## Usage
10
+
11
+ Use Slider for bounded numeric choices where approximate adjustment is faster than typing.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use visible labels, min/max context, and value output when precision matters.
16
+ - Do: Choose steps that match the real granularity of the setting.
17
+ - Don't: Do not use Slider when users must enter an exact arbitrary number; use NumberField.
18
+ - Don't: Do not use it without showing the value. A slider with no numeric readout makes any precise choice guesswork — pair it with a ScrubField or a NumberField.
19
+ - Don't: Do not resize the thumb's box on hover, focus, or drag. The thumb is a fixed-size hit target wrapping a decorative dot; only the dot scales. A target that grows under the cursor is a target nobody can learn, and WCAG 2.5.8 measures target size at rest. Restyle the dot, not the box.
20
+
21
+ ### Anatomy
22
+
23
+ | Part | Required | Description |
24
+ | --- | --- | --- |
25
+ | SliderRoot | yes | The controlled slider wrapper. |
26
+ | SliderControl | yes | The interactive slider control region. |
27
+ | SliderTrack | yes | The visible value range. |
28
+ | SliderIndicator | no | The filled portion of the track. |
29
+ | SliderThumb | yes | The draggable handle. |
30
+ | SliderValue | no | A readable value display. |
31
+
32
+ ## Subcomponents
33
+
34
+ | Name | Type | Description |
35
+ | --- | --- | --- |
36
+ | SliderRoot | Base UI Slider.Root props | The unstyled Base UI Root for custom composition — owns the value state and forwards value / defaultValue / onValueChange / min / max / step. Drag state surfaces via data-[dragging]. The default Slider export wires all the parts for you. |
37
+ | SliderControl | Base UI Control props + className | The interactive track lane — the positioning context the indicator and thumbs sit in; dims when disabled. |
38
+ | SliderTrack | Base UI Track props + className | The full-range rail beneath the indicator. |
39
+ | SliderIndicator | Base UI Indicator props + className | The filled portion from min (or the lower thumb) to the current value. |
40
+ | SliderThumb | Base UI Thumb props + className | A draggable handle: a fixed-size hit target wrapping a decorative dot. The dot rests small and scales to full size on hover, keyboard focus, and drag; the box never changes, so row metrics and the tap target hold. Range sliders render one per value; pass index for SSR. Interaction state surfaces via data-[dragging]. |
41
+ | SliderValue | Base UI Value props | Formatted value readout for custom compositions (e.g. a live number beside the control). |
42
+
43
+ ## Props
44
+
45
+ | Name | Type | Default | Description |
46
+ | --- | --- | --- | --- |
47
+ | value | number \| number[] | — | Controlled value. An array renders a range slider with one thumb per entry. |
48
+ | defaultValue | number \| number[] | — | Uncontrolled initial value. An array renders a range slider. |
49
+ | onValueChange | (value, eventDetails) => void | — | Fires on every value change. value is a number (or number[] for a range). |
50
+ | min | number | 0 | Minimum allowed value. |
51
+ | max | number | 100 | Maximum allowed value. |
52
+ | step | number | 1 | Granularity the slider steps through. Snaps to multiples. |
53
+ | disabled | boolean | false | Ignore user interaction and dim the control. |
54
+ | orientation | 'horizontal' \| 'vertical' | horizontal | Axis the slider tracks along. A vertical slider has no intrinsic length — give it a height (h-40, or flex-1 in a column). |
55
+
56
+ ## Tokens used
57
+
58
+ `secondary`, `primary`, `background`, `ring`
59
+
60
+ ## Accessibility
61
+
62
+ - Base UI provides role=slider, aria-valuemin/max/now, and arrow-key / Page Up-Down / Home-End control.
63
+ - Each thumb is a focusable input[type=range] nested in the thumb element; keyboard focus raises a 2px ring on the thumb via has-[:focus-visible] (a bare focus-visible: on the thumb never fires, since the div itself is not focusable).
64
+ - The thumb's hit area is a fixed 16px box at every state. Only the decorative inner dot resizes — rest 12px, full 16px on hover, keyboard focus, and drag — so the target a pointer or finger must land on never moves or changes size. Keyboard users, who never hover, get the enlarged dot from focus-visible.
65
+ - The rest-to-full transition uses the motion duration/easing tokens, so it collapses to an instant state change under prefers-reduced-motion or MotionProvider off. The size change itself still happens: it reports interaction state, it is not decoration.
66
+ - Range sliders get one thumb per value with minStepsBetweenValues honored.
67
+ - Pass an aria-label (or getAriaLabel on a thumb) so each handle has an accessible name.
68
+
69
+ ## Related
70
+
71
+ - Components: NumberField, Progress
72
+
73
+ ## Examples
74
+
75
+ ### Single value
76
+
77
+ ```tsx
78
+ import { Slider } from "@sableui/react"
79
+
80
+ <Slider defaultValue={50} aria-label="Volume" />
81
+ ```
82
+
83
+ ### Range
84
+
85
+ ```tsx
86
+ import { Slider } from "@sableui/react"
87
+
88
+ const [range, setRange] = useState([20, 60])
89
+ <Slider value={range} onValueChange={setRange} aria-label="Price range" />
90
+ ```
91
+
92
+ ### Stepped
93
+
94
+ ```tsx
95
+ import { Slider } from "@sableui/react"
96
+
97
+ <Slider defaultValue={40} min={0} max={100} step={10} />
98
+ ```
99
+
100
+ ### Vertical
101
+
102
+ ```tsx
103
+ import { Slider } from "@sableui/react"
104
+
105
+ <div className="h-40"><Slider orientation="vertical" defaultValue={40} aria-label="Level" /></div>
106
+ ```
107
+
108
+ ### Composed with a value readout
109
+
110
+ ```tsx
111
+ import { Field, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue } from "@sableui/react"
112
+
113
+ <Field label="Volume"><SliderRoot defaultValue={40} className="flex items-center gap-3"><SliderControl><SliderTrack><SliderIndicator /><SliderThumb /></SliderTrack></SliderControl><SliderValue className="text-mono-sm" /></SliderRoot></Field>
114
+ ```
@@ -0,0 +1,87 @@
1
+ # Spinner
2
+
3
+ **Status:** stable
4
+
5
+ Pure SVG activity indicator. No Base UI, no icon dependency — a role=status ring (faint track + rounded-cap arc) that inherits the current text color. Size with the size prop (sm / md / lg), color with text-*.
6
+
7
+ **Keywords:** spinner, loading, pending, indeterminate
8
+
9
+ ## Usage
10
+
11
+ Use Spinner for short indeterminate waits where the user cannot know exact progress.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it in compact panels or places where layout is already stable. In a button, use Button's loading prop rather than placing a Spinner by hand — it disables the button, sets aria-busy and sizes the spinner to the button's icon.
16
+ - Do: Pair it with accessible loading text when the surrounding label is not enough.
17
+ - Don't: Do not use Spinner as the only content for long waits or full page loading.
18
+ - Don't: Do not use it without an accessible name or a nearby status message. A spinning icon is invisible to a screen reader, so the wait is announced as nothing at all.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | label | string | "Loading" | Accessible label announced by screen readers while visible (sets aria-label on the role=status element). |
25
+ | appearance | "ring" \| "dots" | "ring" | ring is the default indicator; dots are three bouncing dots, softer, for chat and inline text. |
26
+ | size | "sm" \| "md" \| "lg" | "md" | One scale for both appearances: ring 14 / 16 / 24px, dots 4 / 6 / 8px. |
27
+ | className | string | — | Color (text-*); the ring's strokes use currentColor, so it follows the text color. Size with the size prop — a size-* class still overrides the ring for an off-scale context (Button sizes its own spinner this way), but is not the way to pick a size. |
28
+
29
+ ## Variants
30
+
31
+ - **appearance**: ring, dots
32
+ - **size**: sm, md, lg
33
+
34
+ ## Tokens used
35
+
36
+ `foreground`, `muted-foreground`, `primary-foreground`
37
+
38
+ ## Motion
39
+
40
+ - **arrival** · Root · _no motion_ — A spinner already signals a wait. Fading it in delays the signal for exactly as long as the user most needs it.
41
+ - **response** · Root · _no motion_ — It is a status indicator, not a control.
42
+ - **transition** · Root · _no motion_ — One state. Whatever replaces it on completion owns that change.
43
+ - **activity** · Root · `motion-spin` — The rotation is the whole component — it is the only thing distinguishing 'working' from 'frozen'. motion-spin rather than Tailwind's animate-spin because the built-in ignores --motion-scale, so the reduced-motion policy cannot freeze it.
44
+
45
+ ## Accessibility
46
+
47
+ - Wrapped in role=status, a polite live region, with an aria-label (default 'Loading').
48
+ - Color comes from currentColor; pair with a text-* token for adequate contrast.
49
+ - Spins with the motion-spin utility, which follows --motion-scale, so it slows and stops with the motion setting and reduced-motion.
50
+
51
+ ## Related
52
+
53
+ - Components: Skeleton, Progress
54
+
55
+ ## Examples
56
+
57
+ ### Dots
58
+
59
+ ```tsx
60
+ import { Spinner } from "@sableui/react"
61
+
62
+ <Spinner appearance="dots" label="Assistant is typing" className="text-muted-foreground" />
63
+ ```
64
+
65
+ ### Default
66
+
67
+ ```tsx
68
+ import { Spinner } from "@sableui/react"
69
+
70
+ <Spinner />
71
+ ```
72
+
73
+ ### Sized + tinted
74
+
75
+ ```tsx
76
+ import { Spinner } from "@sableui/react"
77
+
78
+ <Spinner size="lg" className="text-muted-foreground" />
79
+ ```
80
+
81
+ ### Inside a button (Button owns it — use loading)
82
+
83
+ ```tsx
84
+ import { Button } from "@sableui/react"
85
+
86
+ <Button loading>Collecting</Button>
87
+ ```
@@ -0,0 +1,91 @@
1
+ # StatusBar
2
+
3
+ **Status:** stable
4
+
5
+ The bottom chrome strip of a Workspace: ambient document facts — dimensions, counts, sync state — in the micro type tier.
6
+
7
+ **Keywords:** status bar, status, footer, chrome, workspace, tool, ambient, counts, document
8
+
9
+ ## Usage
10
+
11
+ Use StatusBar for the persistent facts about a document that otherwise have nowhere to live: size, counts, selection state, sync or connection status. Everything in it is ambient — it reports, it does not command. Pair it with Workspace's statusBar slot.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Put document-scoped facts here — dimensions, layer or record counts, saved/sync state — so panels stop competing for that space.
16
+ - Do: Use StatusBarGroup with align="end" for the trailing cluster, and StatusBarSeparator between related facts.
17
+ - Do: Render numeric facts in the UI num tier so a changing value doesn't shift the strip's layout.
18
+ - Don't: Make it the only route to an action. It's ambient chrome at the micro tier — anything essential needs a real control elsewhere too.
19
+ - Don't: Put zoom here. Anchor zoom to the canvas it acts on, which is where every reference tool puts it.
20
+ - Don't: Use it for transient feedback like a save confirmation — that's Toast. A status bar states what is true, not what just happened.
21
+ - Don't: Derive a fact here separately from the surface that shows it, or from something expensive to recompute. The strip re-renders with the document, so read the same value the canvas or panel uses: a status bar that reports max(asset sizes) while the canvas reports something else is a bug types cannot catch, because both are numbers.
22
+
23
+ ### Anatomy
24
+
25
+ | Part | Required | Description |
26
+ | --- | --- | --- |
27
+ | StatusBarGroup | no | A cluster of items; align="end" pins it to the trailing edge. |
28
+ | StatusBarSeparator | no | A decorative dot between related facts; aria-hidden. |
29
+
30
+ ## Subcomponents
31
+
32
+ | Name | Type | Description |
33
+ | --- | --- | --- |
34
+ | StatusBarGroup | { align?: 'start' \| 'end' } | A cluster of status items; `end` pushes it to the trailing edge. |
35
+ | StatusBarSeparator | ComponentProps<'span'> | A decorative middle dot between facts. aria-hidden, since order conveys grouping. |
36
+
37
+ ## Tokens used
38
+
39
+ `muted-foreground`, `panel`, `hairline`, `hairline-strong`
40
+
41
+ ## Accessibility
42
+
43
+ - Renders a <footer>, so the strip is a discoverable region rather than an anonymous row of text.
44
+ - Separators are aria-hidden — a screen reader announcing 'middle dot' between every fact is noise.
45
+ - Micro-tier text is small by design; never put information here that exists nowhere else.
46
+
47
+ ## Related
48
+
49
+ - Components: Workspace, PanelGroup, InlineStatus, Toast, Banner
50
+
51
+ ## Examples
52
+
53
+ ### Document facts with a trailing cluster
54
+
55
+ ```tsx
56
+ import { StatusBar, StatusBarGroup, StatusBarSeparator } from "@sableui/react"
57
+
58
+ <StatusBar>
59
+ <span className="text-ui-num">1024 × 768</span>
60
+ <StatusBarSeparator />
61
+ <span>3 layers</span>
62
+ <StatusBarGroup align="end">
63
+ <span>Saved</span>
64
+ </StatusBarGroup>
65
+ </StatusBar>
66
+ ```
67
+
68
+ ### With a warning tone
69
+
70
+ ```tsx
71
+ import { StatusBar, StatusBarGroup } from "@sableui/react"
72
+
73
+ <StatusBar>
74
+ <span>Offline — edits are queued</span>
75
+ <StatusBarGroup align="end">
76
+ <span className="text-tone-warning">2 unsynced</span>
77
+ </StatusBarGroup>
78
+ </StatusBar>
79
+ ```
80
+
81
+ ### In a Workspace
82
+
83
+ ```tsx
84
+ import { PanelGroup, StatusBar, Workspace, WorkspaceCanvas } from "@sableui/react"
85
+
86
+ <Workspace statusBar={<StatusBar><span>Connected</span></StatusBar>}>
87
+ <PanelGroup>
88
+ <WorkspaceCanvas />
89
+ </PanelGroup>
90
+ </Workspace>
91
+ ```
@@ -0,0 +1,70 @@
1
+ # StatusDot
2
+
3
+ **Status:** stable
4
+
5
+ A small colored status dot. Tone variants encode state via semantic tone tokens; sizes scale the diameter. Decorative (aria-hidden) — pair with text. Optional pulse adds a live ping ring.
6
+
7
+ **Keywords:** status, dot, presence, health, state, online, live, indicator
8
+
9
+ ## Usage
10
+
11
+ Use StatusDot as a compact visual state cue next to text, avatars, rows, or labels. It is decorative by design, so pair it with visible text or use `<Badge appearance="plain" dot>` when the label and dot should travel together — that is the labelled status line, not a hand-built row.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Place StatusDot beside a readable state label, person name, or row title.
16
+ - Do: Use pulse only for live or in-progress states that are actively changing.
17
+ - Don't: Use the dot as the only status indicator; it is aria-hidden and color alone is not enough.
18
+ - Don't: Mix many dot sizes in the same list. Keep row indicators visually consistent.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | Dot | yes | The small tone-colored circle. It is decorative and hidden from assistive tech. |
25
+ | Pulse ring | no | Optional motion ring for in-progress or live states. |
26
+
27
+ ## Props
28
+
29
+ | Name | Type | Default | Description |
30
+ | --- | --- | --- | --- |
31
+ | tone | "neutral" \| "standard" \| "success" \| "warning" \| "info" \| "edition" \| "collectible" \| "destructive" | "neutral" | Semantic color of the dot. |
32
+ | size | "sm" \| "md" \| "lg" | "md" | Dot diameter. |
33
+ | pulse | boolean | false | Wrap the dot in an animated ping ring to signal a live state. |
34
+
35
+ ## Variants
36
+
37
+ - **tone**: neutral, standard, success, warning, info, edition, collectible, destructive
38
+ - **size**: sm, md, lg
39
+
40
+ ## Tokens used
41
+
42
+ `muted-foreground`, `tone-standard`, `success`, `tone-warning`, `tone-info`, `tone-edition`, `tone-collectible`, `destructive`
43
+
44
+ ## Accessibility
45
+
46
+ - Rendered aria-hidden — purely decorative. Convey status in adjacent text, not color alone.
47
+ - A dot conveys state through colour alone, which fails for colour-blind users and announces as nothing. It must always sit beside text that states the same thing, or carry its own accessible name.
48
+
49
+ ## Related
50
+
51
+ - Components: Badge, Avatar, InlineStatus
52
+ - Patterns: DashboardPage, ListDetailPage
53
+
54
+ ## Examples
55
+
56
+ ### Tones
57
+
58
+ ```tsx
59
+ import { Row, StatusDot } from "@sableui/react"
60
+
61
+ <Row gap={2}><StatusDot tone="standard" /><StatusDot tone="info" /><StatusDot tone="warning" /><StatusDot tone="destructive" /></Row>
62
+ ```
63
+
64
+ ### With label — use Badge
65
+
66
+ ```tsx
67
+ import { Badge } from "@sableui/react"
68
+
69
+ <Badge appearance="plain" tone="success" dot pulse>Live</Badge>
70
+ ```
@@ -0,0 +1,125 @@
1
+ # Stepper
2
+
3
+ **Status:** stable
4
+
5
+ Where the user is in a multi-step flow — an ordered list of numbered steps, horizontal or vertical, each complete, current, upcoming or in error, with aria-current="step" on the current one. `indicator="dot"` turns it into a timeline of events (an order's delivery) with dots instead of numbers.
6
+
7
+ **Keywords:** stepper, steps, wizard, multi-step form, checkout steps, onboarding progress, progress steps, timeline
8
+
9
+ ## Usage
10
+
11
+ Use Stepper above or beside a flow that is split into a known, small number of named steps — checkout, onboarding, an import wizard — so the user can see how far they have come and what is left. It reports position; the flow's own Back and Continue buttons move it.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Drive it from one number (`current`) and override a single step's `status` only for what a number cannot say — a completed step that now needs attention is `status="error"`.
16
+ - Do: Name steps with a word or two ("Shipping", "Payment"). Go vertical when steps need a description or there are more than five; horizontal titles truncate first.
17
+ - Do: Give completed steps `onSelect` only when going back to edit them actually works, and keep the user's later answers when they do.
18
+ - Don't: Do not use Stepper for a task with unknown length or for a percentage. That is Progress; a stepper promises a fixed set of named stops.
19
+ - Don't: Do not use it as navigation between independent sections. Sections the user can visit in any order are Tabs; a stepper implies each step depends on the one before.
20
+ - Don't: Do not make upcoming steps pressable to skip ahead. A step that looks reachable but is not is worse than one that is plainly locked.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Stepper | yes | The ordered list. Takes `current` and `orientation`, and an aria-label naming the flow. |
27
+ | StepperItem | yes | One step: a numbered disc (a check when complete, a warning mark on error) — or, with `indicator="dot"`, a dot centred on the title's first line — a title, and an optional description. |
28
+ | Connector | no | The line to the next step, filled once this step is complete. Drawn automatically between items. |
29
+
30
+ ## Subcomponents
31
+
32
+ | Name | Type | Description |
33
+ | --- | --- | --- |
34
+ | StepperItem | { title: ReactNode; description?: ReactNode; status?: StepStatus; onSelect?: () => void } + li props | One step. `status` overrides the derived state; `onSelect` makes the disc and title a button for returning to the step. |
35
+
36
+ ## Props
37
+
38
+ | Name | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | current | number | 0 | Zero-based index of the current step. Earlier steps derive complete, later ones upcoming. |
41
+ | orientation | "horizontal" \| "vertical" | "horizontal" | Horizontal for a short flow across the top of a form; vertical for long flows, narrow columns or steps with descriptions. |
42
+ | indicator | "number" \| "dot" | "number" | `number` for a flow the user moves through; `dot` for a timeline of events (delivery tracking, a document's history) where the position number adds nothing. The dot centres on the title's first line and scales with its type at every density. |
43
+ | statusLabels | Partial<Record<StepStatus, string>> | — | Screen-reader status words appended to each title (Completed, Current step, Not started, Needs attention). Override to localize. |
44
+ | aria-label | string | — | Names the flow ("Checkout progress"). Every Stepper needs one. |
45
+
46
+ ## Variants
47
+
48
+ - **orientation**: horizontal, vertical
49
+ - **indicator**: number, dot
50
+ - **status**: complete, current, upcoming, error
51
+
52
+ ## Tokens used
53
+
54
+ `primary`, `primary-foreground`, `background`, `foreground`, `muted-foreground`, `border-strong`, `destructive`, `destructive-foreground`, `ring`
55
+
56
+ ## Motion
57
+
58
+ - **arrival** · Stepper · _no motion_ — The stepper frames the flow and is on screen before any step is. Animating it in would compete with the step content, which is what changed.
59
+ - **response** · StepperItem · `motion-interactive` — Only steps with onSelect respond, and the eased underline is what tells a returning user that this completed step can be reopened while the upcoming ones cannot.
60
+ - **transition** · Indicator · `motion-interactive` — When the user continues, the disc they leave fills and its connector darkens. Easing the colour makes that read as one step completing rather than the whole bar re-rendering.
61
+ - **activity** · Stepper · _no motion_ — A step is a place, not a process. Saving or validating between steps belongs on the Continue button's loading state; a pulsing disc would suggest the step itself is busy.
62
+
63
+ ## Accessibility
64
+
65
+ - Renders an ordered list, so assistive tech announces the step count and each step's position.
66
+ - The current step carries aria-current="step"; every title also gets a visually hidden status word (Completed, Not started, Needs attention), so state never depends on colour or the disc glyph.
67
+ - Discs and connectors are aria-hidden — the number is already the list position.
68
+ - Steps with onSelect render as native buttons with a visible focus ring; steps without it are not focusable.
69
+
70
+ ## Related
71
+
72
+ - Components: Progress, Tabs, Form, Button
73
+
74
+ ## Examples
75
+
76
+ ### Checkout
77
+
78
+ ```tsx
79
+ import { Stepper, StepperItem } from "@sableui/react"
80
+
81
+ <Stepper current={1} aria-label="Checkout progress">
82
+ <StepperItem title="Cart" />
83
+ <StepperItem title="Shipping" />
84
+ <StepperItem title="Payment" />
85
+ <StepperItem title="Review" />
86
+ </Stepper>
87
+ ```
88
+
89
+ ### Vertical with descriptions
90
+
91
+ ```tsx
92
+ import { Stepper, StepperItem } from "@sableui/react"
93
+
94
+ <Stepper orientation="vertical" current={2} aria-label="Workspace setup">
95
+ <StepperItem title="Create account" description="Email verified" />
96
+ <StepperItem title="Invite team" description="3 people invited" />
97
+ <StepperItem title="Connect data" description="Choose a source to import from" />
98
+ <StepperItem title="Launch" />
99
+ </Stepper>
100
+ ```
101
+
102
+ ### Delivery timeline (dots)
103
+
104
+ ```tsx
105
+ import { Stepper, StepperItem } from "@sableui/react"
106
+
107
+ <Stepper orientation="vertical" indicator="dot" current={2} aria-label="Delivery status">
108
+ <StepperItem title="Order placed" description="Oct 9, 10:14 AM" />
109
+ <StepperItem title="Printed and packed" description="Oct 10, 4:02 PM" />
110
+ <StepperItem title="Out for delivery" description="Arriving today by 6 PM" />
111
+ <StepperItem title="Delivered" />
112
+ </Stepper>
113
+ ```
114
+
115
+ ### A completed step with an error
116
+
117
+ ```tsx
118
+ import { Stepper, StepperItem } from "@sableui/react"
119
+
120
+ <Stepper current={2} aria-label="Checkout progress">
121
+ <StepperItem title="Cart" onSelect={() => console.log("cart")} />
122
+ <StepperItem title="Shipping" status="error" description="Address needs a postcode" onSelect={() => console.log("shipping")} />
123
+ <StepperItem title="Payment" />
124
+ </Stepper>
125
+ ```
@@ -0,0 +1,86 @@
1
+ # StreamingText
2
+
3
+ **Status:** stable
4
+
5
+ Prose arriving from a live source, a token at a time. Each token resolves out of blur, the trailing edge stays soft while more is coming, and a steady caret marks the write head.
6
+
7
+ **Keywords:** streaming, stream, tokens, typewriter, generation, assistant, llm, caret, incremental
8
+
9
+ ## Usage
10
+
11
+ Use StreamingText for text whose final length is unknown while it renders — a model's answer, a live transcript, a log line being written. It is the counterpart to ThinkingOrb and Spinner: those cover the wait BEFORE the first token, this covers the arrival of the tokens themselves.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Grow `text` and let the component do the rest. It assumes an append-only string and keys units by index, so React mounts only the new spans — re-rendering a longer string animates what was added and leaves what the reader has already read alone.
16
+ - Do: Drop `streaming` to false the moment the source closes. That is what removes the caret and the softened tail, and their disappearance is the only signal the reader gets that the answer is complete.
17
+ - Do: Turn `caret` off when something adjacent already reports the wait — a spinner in the message header, a status row beneath. Two indicators for one wait read as two things happening.
18
+ - Don't: Do not use it for text that is already complete. Staging known text on scroll is TextReveal (a `motion` export, not a component folder), which computes a stagger from the full string; StreamingText cannot, because nothing here knows the length. Using it for static prose animates the whole paragraph on every mount and says 'still generating' about something that finished.
19
+ - Don't: Do not wrap it in an aria-live region. Every token would be announced, which is unusable; the component sets `aria-busy` instead. Announce once when the turn settles, from whatever owns the turn.
20
+ - Don't: Do not pass `split="char"` for paragraph-length text. It is one element per character — thousands of nodes for an answer, each with its own animation — and word-splitting is indistinguishable at prose reading speed. Reserve it for a headline-length string.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Container | yes | The paragraph (or `as` element). Carries `aria-busy` while output is incomplete. |
27
+ | Unit | yes | One word or character. Animates once on mount and never again; the newest one carries the softened tail while streaming. |
28
+ | Caret | no | The write head, shown while streaming. Steady rather than blinking — see `motion-caret`. |
29
+
30
+ ## Props
31
+
32
+ | Name | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | text | string | — | The text received so far. Assumed append-only: the component keys units by index precisely so a grown string mounts only its new spans. |
35
+ | streaming | boolean | false | Whether more is still coming. Drives the caret, the softened trailing edge, and `aria-busy`. |
36
+ | as | ElementType | "p" | Render container element. |
37
+ | split | "word" \| "char" | "word" | Split granularity. `char` is one element per character — short strings only. |
38
+ | caret | boolean | true | Show the caret while streaming. Turn it off when something adjacent already reports the wait. |
39
+
40
+ ## Tokens used
41
+
42
+ `foreground`
43
+
44
+ ## Motion
45
+
46
+ - **arrival** · Unit · `motion-stream-in` — Each token resolves out of blur rather than fading. A fade makes every word a separate event; blur reads as the word coming into focus, so a paragraph arrives as one sentence being written instead of a queue of arrivals.
47
+ - **response** · Container · _no motion_ — It is prose, not a control. Selecting and copying it is the only interaction, and both are native.
48
+ - **transition** · Container · _no motion_ — There is no state to move between — streaming to complete is marked by the caret and tail leaving, not by the text changing. Animating the whole paragraph on completion would re-stage text the reader already read.
49
+ - **activity** · Caret · `motion-caret` — The caret is the write head, and it is deliberately STEADY (`data-streaming="true"` suppresses the blink). A blink during arrival puts two rhythms on screen — text advancing at one rate, a caret flashing at another — and the flashing one carries no information the moving text has not already given.
50
+
51
+ ## Accessibility
52
+
53
+ - The text is real text in the accessibility tree: units are ordinary spans, not aria-hidden fragments, so a screen reader concatenates them into the sentence.
54
+ - `aria-busy` is set on the container while `streaming` is true, marking the output incomplete without announcing each token.
55
+ - The caret is `aria-hidden` — it is a rendering of the write head, and a screen reader has no use for it.
56
+ - Under reduced motion or with MotionProvider off, no unit animates and no tail is softened; text simply appears as it arrives.
57
+
58
+ ## Related
59
+
60
+ - Components: ThinkingOrb, Message, Bubble
61
+
62
+ ## Examples
63
+
64
+ ### Streaming an answer
65
+
66
+ ```tsx
67
+ import { StreamingText } from "@sableui/react"
68
+
69
+ <StreamingText text={answer} streaming={pending} />
70
+ ```
71
+
72
+ ### Inside a message, with the wait reported elsewhere
73
+
74
+ ```tsx
75
+ import { Message, StreamingText } from "@sableui/react"
76
+
77
+ <Message role="assistant"><StreamingText text={answer} streaming={pending} caret={false} /></Message>
78
+ ```
79
+
80
+ ### A headline resolving character by character
81
+
82
+ ```tsx
83
+ import { StreamingText } from "@sableui/react"
84
+
85
+ <StreamingText as="h2" split="char" className="text-heading-2" text={headline} streaming={pending} />
86
+ ```
@@ -0,0 +1,87 @@
1
+ # Switch
2
+
3
+ **Status:** stable
4
+
5
+ Binary on/off toggle for immediate-effect settings. Track fills with the primary tone when checked; the thumb slides on a spring-eased transform (mirrored in RTL).
6
+
7
+ **Keywords:** switch, toggle setting, on off, form control
8
+
9
+ ## Usage
10
+
11
+ Use Switch for immediate on/off settings that take effect as a persistent preference or mode.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it for binary settings where the label describes the enabled state.
16
+ - Do: Make the current state obvious through both position and accessible text.
17
+ - Don't: Do not phrase the label as a negative, such as "Don't send read receipts". Turning off a negative is a double negative, and the user has to solve it before they can act.
18
+ - Don't: Do not use Switch for one-time agreement, multi-select choices, or submit-only booleans.
19
+ - Don't: Do not use a Switch whose effect is not immediately visible somewhere. A toggle flipped with no observable consequence gives no confirmation it worked, so the user flips it again to check.
20
+
21
+ ## Props
22
+
23
+ | Name | Type | Default | Description |
24
+ | --- | --- | --- | --- |
25
+ | checked | boolean | — | Controlled on/off state. Pair with onCheckedChange. Omit for an uncontrolled switch. |
26
+ | defaultChecked | boolean | false | Initial state for an uncontrolled switch. |
27
+ | onCheckedChange | (checked: boolean, eventDetails) => void | — | Fired when the switch is toggled. Note: Base UI passes a second eventDetails arg — not the common (checked) => void, so a single-arg handler must widen its signature. |
28
+ | disabled | boolean | false | Dims the control and ignores user interaction. |
29
+ | readOnly | boolean | false | Prevents toggling while keeping the control fully visible. |
30
+ | required | boolean | false | Marks the switch as required when inside a form. |
31
+ | name | string | — | Identifies the field when the enclosing form is submitted. |
32
+
33
+ ## Tokens used
34
+
35
+ `input`, `primary`, `primary-foreground`, `ring`
36
+
37
+ ## Motion
38
+
39
+ - **arrival** · Root · _no motion_ — Switches arrive as part of a settings list. Animating each one in turns a form into a title sequence.
40
+ - **response** · Root · `motion-interactive` — The track is small and its hover change is subtle; without the ease the colour shift reads as a rendering glitch rather than a reaction.
41
+ - **transition** · Thumb · `motion-interactive` — The thumb sliding IS the affordance — it shows the control has two ends and which one you are now at. Teleporting the thumb makes on and off two unrelated pictures, and users re-read the label to work out which they are looking at. motion-interactive is the right recipe because it declares `translate`, so the thumb transform and the track colour ease on one timeline instead of two.
42
+ - **activity** · Root · _no motion_ — A switch commits instantly. If the write is slow enough to need a pending state, the surrounding form owns that, not the control.
43
+
44
+ ## Accessibility
45
+
46
+ - Renders Base UI's Switch with a hidden native <input> — role, ARIA, and keyboard (Space/Enter) toggling come for free.
47
+ - Focus-visible draws a 2px ring in the --ring token.
48
+ - Disabled state sets cursor-not-allowed and 50% opacity; readOnly keeps full contrast but blocks changes.
49
+ - Give it an accessible name: wrap it in <Field label=… orientation="horizontal">, which wires the label for you.
50
+
51
+ ## Related
52
+
53
+ - Components: Checkbox, Toggle, ThemeSwitcher
54
+
55
+ ## Examples
56
+
57
+ ### Default (uncontrolled)
58
+
59
+ ```tsx
60
+ import { Switch } from "@sableui/react"
61
+
62
+ <Switch defaultChecked />
63
+ ```
64
+
65
+ ### Controlled
66
+
67
+ ```tsx
68
+ import { Switch } from "@sableui/react"
69
+
70
+ <Switch checked={on} onCheckedChange={setOn} />
71
+ ```
72
+
73
+ ### Disabled
74
+
75
+ ```tsx
76
+ import { Switch } from "@sableui/react"
77
+
78
+ <Switch disabled defaultChecked />
79
+ ```
80
+
81
+ ### With a label
82
+
83
+ ```tsx
84
+ import { Field, Switch } from "@sableui/react"
85
+
86
+ <Field label="Reduce motion" orientation="horizontal"><Switch defaultChecked /></Field>
87
+ ```