@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.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +221 -0
- package/DESIGN.md +548 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/THIRD_PARTY_NOTICES.md +44 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +1223 -0
- package/dist/_contract-CYScOdf5.d.ts +150 -0
- package/dist/chunk-3IWZYBGH.js +398 -0
- package/dist/chunk-5Z5KLOAU.js +167 -0
- package/dist/chunk-7MG4SHYX.js +15 -0
- package/dist/chunk-ADLLDWLW.js +15 -0
- package/dist/chunk-EP55KSYA.js +14 -0
- package/dist/chunk-J23ELQQZ.js +9396 -0
- package/dist/chunk-JI7WJP2L.js +286 -0
- package/dist/chunk-K7RUHUGC.js +14 -0
- package/dist/chunk-KO3NAEBF.js +15 -0
- package/dist/chunk-L4IEIQQA.js +14 -0
- package/dist/chunk-L6Y46C62.js +270 -0
- package/dist/chunk-NEQNXI2G.js +25 -0
- package/dist/chunk-PAOZBZME.js +40 -0
- package/dist/chunk-RWP4XBSK.js +139 -0
- package/dist/chunk-SRQWQUTW.js +63 -0
- package/dist/chunk-YXBOYWBN.js +15 -0
- package/dist/foundations/index.d.ts +88 -0
- package/dist/foundations/index.js +792 -0
- package/dist/icon/index.d.ts +60 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon-sets/brands.d.ts +44 -0
- package/dist/icon-sets/brands.js +147 -0
- package/dist/icon-sets/reicon.d.ts +8 -0
- package/dist/icon-sets/reicon.js +1 -0
- package/dist/index.d.ts +7013 -0
- package/dist/index.js +15462 -0
- package/dist/ios/chrome.css +83 -0
- package/dist/ios/frame.css +5 -0
- package/dist/ios/index.d.ts +70 -0
- package/dist/ios/index.js +313 -0
- package/dist/ios/preview.d.ts +64 -0
- package/dist/ios/preview.js +133 -0
- package/dist/ios/styles.css +77 -0
- package/dist/layout/index.d.ts +280 -0
- package/dist/layout/index.js +2 -0
- package/dist/meta-6dUzgC6M.d.ts +205 -0
- package/dist/patterns/index.d.ts +36 -0
- package/dist/patterns/index.js +934 -0
- package/dist/presets/index.d.ts +37 -0
- package/dist/presets/index.js +117 -0
- package/dist/registry-CyL28OzJ.d.ts +28 -0
- package/dist/styles.css +1893 -0
- package/dist/theme-provider-BWcizhAt.d.ts +105 -0
- package/dist/themes/atelier/atelier.css +213 -0
- package/dist/themes/atelier/index.d.ts +9 -0
- package/dist/themes/atelier/index.js +1 -0
- package/dist/themes/base/base.css +443 -0
- package/dist/themes/base/index.d.ts +9 -0
- package/dist/themes/base/index.js +1 -0
- package/dist/themes/ember/ember.css +270 -0
- package/dist/themes/ember/index.d.ts +9 -0
- package/dist/themes/ember/index.js +1 -0
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +8 -0
- package/dist/themes/meridian/index.d.ts +9 -0
- package/dist/themes/meridian/index.js +1 -0
- package/dist/themes/meridian/meridian.css +255 -0
- package/dist/themes/poppy/index.d.ts +6 -0
- package/dist/themes/poppy/index.js +1 -0
- package/dist/themes/poppy/poppy.css +174 -0
- package/dist/themes/prism/index.d.ts +6 -0
- package/dist/themes/prism/index.js +1 -0
- package/dist/themes/prism/prism.css +183 -0
- package/dist/themes/verdant/index.d.ts +9 -0
- package/dist/themes/verdant/index.js +1 -0
- package/dist/themes/verdant/verdant.css +210 -0
- package/dist/themes.css +30 -0
- package/llms/components/accordion.md +93 -0
- package/llms/components/app-header.md +70 -0
- package/llms/components/app-shell.md +117 -0
- package/llms/components/attachment.md +105 -0
- package/llms/components/avatar.md +141 -0
- package/llms/components/badge.md +117 -0
- package/llms/components/banner.md +119 -0
- package/llms/components/bottom-nav.md +80 -0
- package/llms/components/breadcrumbs.md +75 -0
- package/llms/components/browser.md +67 -0
- package/llms/components/bubble.md +119 -0
- package/llms/components/button.md +199 -0
- package/llms/components/calendar.md +75 -0
- package/llms/components/card.md +135 -0
- package/llms/components/carousel.md +87 -0
- package/llms/components/chat-composer.md +126 -0
- package/llms/components/checkbox-card.md +98 -0
- package/llms/components/checkbox.md +90 -0
- package/llms/components/code-block.md +81 -0
- package/llms/components/code.md +62 -0
- package/llms/components/collapsible.md +83 -0
- package/llms/components/color-field.md +100 -0
- package/llms/components/combobox.md +119 -0
- package/llms/components/command-menu.md +158 -0
- package/llms/components/context-card.md +73 -0
- package/llms/components/context-menu.md +79 -0
- package/llms/components/data-table.md +99 -0
- package/llms/components/date-picker.md +97 -0
- package/llms/components/description.md +96 -0
- package/llms/components/destructive-action-modal.md +76 -0
- package/llms/components/dialog.md +127 -0
- package/llms/components/dots-menu.md +64 -0
- package/llms/components/empty-state.md +113 -0
- package/llms/components/entity.md +67 -0
- package/llms/components/feedback.md +63 -0
- package/llms/components/fieldset.md +106 -0
- package/llms/components/file-dropzone.md +103 -0
- package/llms/components/form.md +111 -0
- package/llms/components/gliding-highlight.md +84 -0
- package/llms/components/inline-status.md +62 -0
- package/llms/components/input.md +127 -0
- package/llms/components/kbd.md +57 -0
- package/llms/components/label.md +73 -0
- package/llms/components/marker.md +99 -0
- package/llms/components/marquee.md +66 -0
- package/llms/components/menu.md +122 -0
- package/llms/components/message-scroller.md +110 -0
- package/llms/components/message.md +114 -0
- package/llms/components/meter.md +124 -0
- package/llms/components/middle-truncate.md +62 -0
- package/llms/components/note.md +81 -0
- package/llms/components/notification-badge.md +67 -0
- package/llms/components/number-field.md +70 -0
- package/llms/components/otp-field.md +101 -0
- package/llms/components/page-header.md +76 -0
- package/llms/components/page-loader.md +67 -0
- package/llms/components/pagination.md +92 -0
- package/llms/components/panel-group.md +120 -0
- package/llms/components/panel-section.md +122 -0
- package/llms/components/phone.md +70 -0
- package/llms/components/pill.md +73 -0
- package/llms/components/popover.md +79 -0
- package/llms/components/progress.md +98 -0
- package/llms/components/radio-card.md +90 -0
- package/llms/components/radio.md +86 -0
- package/llms/components/relative-time.md +60 -0
- package/llms/components/scroller.md +88 -0
- package/llms/components/scrub-field.md +97 -0
- package/llms/components/search-input.md +84 -0
- package/llms/components/select.md +106 -0
- package/llms/components/separator.md +70 -0
- package/llms/components/settings-menu.md +139 -0
- package/llms/components/sheet.md +97 -0
- package/llms/components/show-more.md +59 -0
- package/llms/components/side-nav.md +106 -0
- package/llms/components/sidebar.md +87 -0
- package/llms/components/skeleton.md +62 -0
- package/llms/components/slider.md +114 -0
- package/llms/components/spinner.md +87 -0
- package/llms/components/status-bar.md +91 -0
- package/llms/components/status-dot.md +70 -0
- package/llms/components/stepper.md +125 -0
- package/llms/components/streaming-text.md +86 -0
- package/llms/components/switch.md +87 -0
- package/llms/components/table.md +112 -0
- package/llms/components/tabs.md +84 -0
- package/llms/components/textarea.md +88 -0
- package/llms/components/theme-selector.md +95 -0
- package/llms/components/theme-switcher.md +71 -0
- package/llms/components/thinking-orb.md +91 -0
- package/llms/components/toast.md +130 -0
- package/llms/components/toggle.md +95 -0
- package/llms/components/toolbar.md +168 -0
- package/llms/components/tooltip.md +93 -0
- package/llms/components/tree.md +227 -0
- package/llms/components/video.md +66 -0
- package/llms/components/workspace.md +138 -0
- package/llms/foundations/accessibility.md +60 -0
- package/llms/foundations/motion.md +96 -0
- package/llms/foundations/scrolling.md +41 -0
- package/llms/foundations/shape.md +73 -0
- package/llms/foundations/spacing.md +82 -0
- package/llms/foundations/surfaces.md +69 -0
- package/llms/foundations/typography.md +120 -0
- package/llms/layout/aspect-box.md +37 -0
- package/llms/layout/bleed.md +31 -0
- package/llms/layout/center.md +37 -0
- package/llms/layout/columns.md +51 -0
- package/llms/layout/grid-overlay.md +40 -0
- package/llms/layout/grid.md +47 -0
- package/llms/layout/page.md +39 -0
- package/llms/layout/region.md +49 -0
- package/llms/layout/row.md +40 -0
- package/llms/layout/safe-area.md +37 -0
- package/llms/layout/spacer.md +39 -0
- package/llms/layout/split-pane.md +41 -0
- package/llms/layout/stack.md +39 -0
- package/llms/patterns/auth-page.md +68 -0
- package/llms/patterns/brand-landing-page.md +423 -0
- package/llms/patterns/chat-panel.md +210 -0
- package/llms/patterns/dashboard-page.md +170 -0
- package/llms/patterns/feed-page.md +103 -0
- package/llms/patterns/landing-page.md +122 -0
- package/llms/patterns/list-detail-page.md +100 -0
- package/llms/patterns/settings-page.md +121 -0
- package/llms/reference/token-intent.md +324 -0
- package/llms/reference/utilities.md +129 -0
- package/llms/tasks/adding-a-component.md +113 -0
- package/llms/tasks/auditing-an-app.md +216 -0
- package/llms/tasks/building-a-page.md +109 -0
- package/llms/tasks/building-a-tool.md +141 -0
- package/llms/tasks/building-an-ios-prototype.md +105 -0
- package/llms/tasks/building-with-sable.md +73 -0
- package/llms/tasks/choosing-a-component.md +109 -0
- package/llms/tasks/converting-an-app.md +139 -0
- package/llms/tasks/creating-a-preset.md +43 -0
- package/llms/tasks/creating-design-assets.md +96 -0
- package/llms/tasks/designing-a-surface.md +115 -0
- package/llms/tasks/developing-taste.md +157 -0
- package/llms/tasks/getting-started.md +63 -0
- package/llms/tasks/learning-from-builds.md +74 -0
- package/llms/tasks/reviewing-a-design.md +114 -0
- package/llms/tasks/reviewing-a-journey.md +84 -0
- package/llms/tasks/reviewing-motion.md +92 -0
- package/llms/tasks/using-presets.md +87 -0
- package/llms/taste/asset-plan.template.json +53 -0
- package/llms/taste/composition-follows-content.md +20 -0
- package/llms/taste/hierarchy-is-a-relationship.md +22 -0
- package/llms/taste/lessons.json +57 -0
- package/llms/taste/motion-needs-a-reason.md +21 -0
- package/llms/taste/surface-states-travel-together.md +20 -0
- package/llms-full.txt +14906 -0
- package/llms.txt +405 -0
- package/package.json +185 -3
- package/registry.json +18812 -0
- package/scripts/agent-setup.mjs +54 -0
- package/scripts/design-atlas.mjs +221 -0
- package/scripts/design-directions.mjs +123 -0
- package/scripts/design-memory.mjs +166 -0
- package/scripts/design-preferences.mjs +227 -0
- package/scripts/design-presets.mjs +48 -0
- package/scripts/grade.mjs +509 -0
- package/scripts/lint-rules.mjs +1065 -0
- package/scripts/review-evidence.mjs +120 -0
- package/scripts/skeleton.mjs +248 -0
- package/skills/entry/SKILL.md +34 -0
- package/skills/sable/SKILL.md +187 -0
- package/skills/sable/agents/openai.yaml +6 -0
- package/targets/ios.json +982 -0
- 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
|
+
```
|