@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,112 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Styled native table subcomponents (Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption). Horizontally scrollable frame, hairline row borders, hover-lift, and selectable rows via data-selected.
|
|
6
|
+
|
|
7
|
+
**Keywords:** table, data table, rows, columns, grid, caption, header, cell, tabular
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Table for structured data with consistent columns and rows. Use DataTable when the table should be data-driven with sorting or empty state behavior, and use lists or cards when each item has a different shape.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use TableCaption and TableHead so the table has a clear purpose and column semantics.
|
|
16
|
+
- Do: Keep cells concise and scannable; compose Badge, StatusDot, Avatar, or Button only when they clarify the row.
|
|
17
|
+
- Do: Use the built-in horizontal frame for overflow instead of squeezing columns until content becomes unreadable.
|
|
18
|
+
- Do: Put a table in a card with `<CardContent flush>`. The first and last columns then take the card's own padding (`--table-edge-px`), so they line up with the CardHeader at any density. Never hand-set `pl-6`/`pr-6` on edge cells.
|
|
19
|
+
- Don't: Use Table for heterogeneous items with no stable columns. Use Card, Entity, or a list composition instead.
|
|
20
|
+
- Don't: Put every possible action in every row. Use one primary row action, a DotsMenu, or row detail navigation.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Caption | no | Accessible summary of what the table contains. |
|
|
27
|
+
| Header | yes | Column labels rendered with TableHead cells. |
|
|
28
|
+
| Body | yes | Rows and cells containing the tabular data. |
|
|
29
|
+
| Footer | no | Optional totals or summary row group. |
|
|
30
|
+
|
|
31
|
+
## Subcomponents
|
|
32
|
+
|
|
33
|
+
| Name | Type | Description |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| TableHeader | ComponentPropsWithRef<"thead"> | The header row group (thead). |
|
|
36
|
+
| TableBody | ComponentPropsWithRef<"tbody"> | The data row group (tbody). |
|
|
37
|
+
| TableFooter | ComponentPropsWithRef<"tfoot"> | Summary row group (tfoot) with a muted fill. |
|
|
38
|
+
| TableRow | ComponentPropsWithRef<"tr"> | A row (tr) — hairline bottom border, hover lift, and a selected state via the data-selected attribute. |
|
|
39
|
+
| TableHead | ComponentPropsWithRef<"th"> | A column header cell (th) in muted label styling. |
|
|
40
|
+
| TableCell | ComponentPropsWithRef<"td"> | A data cell (td). |
|
|
41
|
+
| TableCaption | ComponentPropsWithRef<"caption"> | Accessible description of the table (caption), muted. |
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Name | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| className | string | — | The root renders a horizontally scrollable frame around a native <table>; forwards all native table attributes plus className. Each subcomponent likewise forwards its element's native attributes + className. |
|
|
48
|
+
|
|
49
|
+
## Tokens used
|
|
50
|
+
|
|
51
|
+
`border`, `muted`, `muted-foreground`
|
|
52
|
+
|
|
53
|
+
## Accessibility
|
|
54
|
+
|
|
55
|
+
- Renders semantic native table elements (thead/tbody/tfoot/th/td/caption) preserving built-in table semantics.
|
|
56
|
+
- Use TableHead (th) for column headers and TableCaption for an accessible description.
|
|
57
|
+
- Mark the active row with the data-selected attribute for a visible selected state.
|
|
58
|
+
|
|
59
|
+
## Related
|
|
60
|
+
|
|
61
|
+
- Components: DataTable, Pagination, Badge, StatusDot, Avatar
|
|
62
|
+
- Patterns: DashboardPage, ListDetailPage
|
|
63
|
+
|
|
64
|
+
## Examples
|
|
65
|
+
|
|
66
|
+
### Collectors
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
70
|
+
|
|
71
|
+
<Table>
|
|
72
|
+
<TableCaption>Recent collectors</TableCaption>
|
|
73
|
+
<TableHeader>
|
|
74
|
+
<TableRow>
|
|
75
|
+
<TableHead>Collector</TableHead>
|
|
76
|
+
<TableHead>Edition</TableHead>
|
|
77
|
+
<TableHead>Paid</TableHead>
|
|
78
|
+
</TableRow>
|
|
79
|
+
</TableHeader>
|
|
80
|
+
<TableBody>
|
|
81
|
+
<TableRow>
|
|
82
|
+
<TableCell>@nova</TableCell>
|
|
83
|
+
<TableCell>#012</TableCell>
|
|
84
|
+
<TableCell>0.5 SOL</TableCell>
|
|
85
|
+
</TableRow>
|
|
86
|
+
</TableBody>
|
|
87
|
+
</Table>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Rich status cells
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { Avatar, Badge, Button, Row, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
94
|
+
|
|
95
|
+
<Table><TableCaption>Deployments</TableCaption><TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Status</TableHead><TableHead>Owner</TableHead><TableHead className="text-end">Actions</TableHead></TableRow></TableHeader><TableBody><TableRow data-selected><TableCell>Catalog API</TableCell><TableCell><Badge appearance="plain" tone="warning" dot pulse>Building</Badge></TableCell><TableCell><Row gap={1}><Avatar fallback="NR" alt="Nova Reed" size="sm" /><span>Nova</span></Row></TableCell><TableCell className="text-end"><Button size="sm" variant="ghost">View</Button></TableCell></TableRow></TableBody></Table>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Flush in a card
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
102
|
+
|
|
103
|
+
<Card><CardHeader><CardTitle>Collectors</CardTitle></CardHeader><CardContent flush><Table><TableHeader><TableRow><TableHead>Collector</TableHead><TableHead>Paid</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>@nova</TableCell><TableCell>0.5 SOL</TableCell></TableRow></TableBody></Table></CardContent></Card>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Summary footer
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@sableui/react"
|
|
110
|
+
|
|
111
|
+
<Table><TableHeader><TableRow><TableHead>Item</TableHead><TableHead className="text-end">Amount</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Primary sale</TableCell><TableCell className="text-end">42 SOL</TableCell></TableRow></TableBody><TableFooter><TableRow><TableCell>Total</TableCell><TableCell className="text-end">42 SOL</TableCell></TableRow></TableFooter></Table>
|
|
112
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Tabs over Base UI Tabs (roving focus, arrow-key nav, ARIA tablist wiring) in two styles: underline (page sections) or pill (compact switchers). Tabs (Root) + TabsList + TabsTrigger + TabsPanel.
|
|
6
|
+
|
|
7
|
+
**Keywords:** tabs, tablist, panels, segmented content
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Tabs to switch between related panels within the same page context.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use tabs when each panel is peer content and users may compare or switch often.
|
|
16
|
+
- Do: Keep tab labels short and make the active tab unmistakable.
|
|
17
|
+
- Do: Use variant="pill" for a short switcher inside a card or toolbar (Preview / Code, List / Grid); keep the default underline for sections of a page.
|
|
18
|
+
- Don't: Do not use Tabs for unrelated pages or primary navigation that needs distinct URLs.
|
|
19
|
+
- Don't: Do not use tabs for sequential steps. Tabs are peer views the user returns to in any order; a process with an order needs a stepper, or the user will skip ahead.
|
|
20
|
+
|
|
21
|
+
### Anatomy
|
|
22
|
+
|
|
23
|
+
| Part | Required | Description |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| TabsList | yes | The list of tab triggers. |
|
|
26
|
+
| TabsTrigger | yes | One selectable tab. |
|
|
27
|
+
| TabsPanel | yes | The content associated with a trigger. |
|
|
28
|
+
| TabsContent | no | Compatibility alias for panel content. |
|
|
29
|
+
|
|
30
|
+
## Subcomponents
|
|
31
|
+
|
|
32
|
+
| Name | Type | Description |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| TabsList | Base UI Tabs.List props (activateOnFocus, loopFocus) + variant?: "underline" \| "pill" + className | The tablist row. variant="underline" (default) draws a divider rule beneath the triggers; variant="pill" drops the rule and fills the active trigger. Triggers inherit the variant. |
|
|
35
|
+
| TabsTrigger | Base UI Tabs.Tab props (value, disabled) + className | A single tab button. value pairs it with the panel of the same value. |
|
|
36
|
+
| TabsPanel | Base UI Tabs.Panel props (value, keepMounted) + className | The content shown when the matching trigger is active. |
|
|
37
|
+
| TabsContent | Alias of TabsPanel | Re-export of TabsPanel for consumers that prefer this name. |
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
| Name | Type | Default | Description |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| Tabs (Root) | Base UI Tabs.Root props (value, defaultValue, onValueChange, orientation) | defaultValue: 0 | Controls the active tab. Uncontrolled via defaultValue; controlled via value + onValueChange. |
|
|
44
|
+
|
|
45
|
+
## Tokens used
|
|
46
|
+
|
|
47
|
+
`border`, `muted`, `muted-foreground`, `foreground`, `ring`
|
|
48
|
+
|
|
49
|
+
## Accessibility
|
|
50
|
+
|
|
51
|
+
- Base UI provides the tablist/tab/tabpanel roles, aria-selected, and roving tabindex.
|
|
52
|
+
- Arrow keys move between tabs; Home/End jump to first/last; panels are linked via aria-controls.
|
|
53
|
+
- Active tab styling reads from the data-active attribute; disabled tabs from data-disabled. Disabled tabs are skipped by arrow-key navigation.
|
|
54
|
+
- Keyboard focus shows a ring on the focused trigger in both variants; the panel remains focusable.
|
|
55
|
+
|
|
56
|
+
## Related
|
|
57
|
+
|
|
58
|
+
- Components: Accordion, BottomNav, SideNav
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### Basic
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
66
|
+
|
|
67
|
+
<Tabs defaultValue="overview"><TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="activity">Activity</TabsTrigger></TabsList><TabsPanel value="overview">Overview content</TabsPanel><TabsPanel value="activity">Activity content</TabsPanel></Tabs>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Pill switcher
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
74
|
+
|
|
75
|
+
<Tabs defaultValue="preview"><TabsList variant="pill"><TabsTrigger value="preview">Preview</TabsTrigger><TabsTrigger value="code">Code</TabsTrigger></TabsList><TabsPanel value="preview">Rendered example</TabsPanel><TabsPanel value="code">Source</TabsPanel></Tabs>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### With a disabled tab
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
|
|
82
|
+
|
|
83
|
+
<Tabs defaultValue="overview"><TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="holders" disabled>Holders</TabsTrigger></TabsList><TabsPanel value="overview">Overview content</TabsPanel></Tabs>
|
|
84
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Textarea
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Styled native multi-line text control. Soft-square, card surface, --ring focus, and an aria-invalid destructive error state.
|
|
6
|
+
|
|
7
|
+
**Keywords:** textarea, multi line input, form, text
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Textarea for multi-line freeform text entry such as notes, descriptions, or messages.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use labels, helper text, and character limits when expectations are specific.
|
|
16
|
+
- Do: Size it to the expected amount of input without making the page feel blocked.
|
|
17
|
+
- Do: Keep a focusable field at 16px or larger on mobile. iOS Safari zooms the whole page when a focused textarea computes under 16px and never zooms back out. Textarea handles this itself — below the sm breakpoint it renders text-body-lg (17px) and returns to text-body-md at sm and up. Build a custom field the same way: a second paired tier under a max-sm: variant, never a hand-set font-size.
|
|
18
|
+
- Don't: Do not override the type with a page tier below 16px (text-body-md, text-body-sm) without a >=16px mobile arm — that reintroduces the iOS focus zoom.
|
|
19
|
+
- Don't: Do not use Textarea for short single-line values; use Input.
|
|
20
|
+
- Don't: Do not leave it at the default height for long-form input. A two-row textarea for a paragraph forces the user to write through a letterbox.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Name | Type | Default | Description |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| aria-invalid | boolean | — | When true, swaps the input border to the destructive token to signal a validation error. |
|
|
27
|
+
| disabled | boolean | false | Dims the control to 50% and blocks pointer interaction. |
|
|
28
|
+
| rows | number | — | Native textarea row count. The floor is min-h-20 on the spacing scale, so it moves with density. |
|
|
29
|
+
| autoResize | boolean | false | Grows to fit content via CSS field-sizing: content (no JS). The min-height stays the floor; browsers without field-sizing fall back to the fixed min-height. |
|
|
30
|
+
| ...props | ComponentPropsWithRef<"textarea"> | — | All native textarea attributes (value, placeholder, onChange, name, ref, …). |
|
|
31
|
+
|
|
32
|
+
## Tokens used
|
|
33
|
+
|
|
34
|
+
`input`, `card`, `foreground`, `muted-foreground`, `ring`, `destructive`
|
|
35
|
+
|
|
36
|
+
## Accessibility
|
|
37
|
+
|
|
38
|
+
- Renders a native <textarea> — label association, keyboard, and screen-reader semantics come for free.
|
|
39
|
+
- Renders through Base UI's Field.Control: inside a Field it picks up the label, description, error and validity wiring from context.
|
|
40
|
+
- Pair with a <label htmlFor> (or aria-label) for an accessible name.
|
|
41
|
+
- Set aria-invalid and reference an error message via aria-describedby to announce validation failures.
|
|
42
|
+
- Focus-visible draws a 2px ring in the --ring token.
|
|
43
|
+
|
|
44
|
+
## Related
|
|
45
|
+
|
|
46
|
+
- Components: Input, Form, Label
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
### Default
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { Textarea } from "@sableui/react"
|
|
54
|
+
|
|
55
|
+
<Textarea placeholder="Write a caption…" />
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### With rows
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { Textarea } from "@sableui/react"
|
|
62
|
+
|
|
63
|
+
<Textarea rows={6} placeholder="Tell the story behind this edition…" />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Auto-resize
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { Textarea } from "@sableui/react"
|
|
70
|
+
|
|
71
|
+
<Textarea autoResize placeholder="Grows as you type…" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Invalid
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Textarea } from "@sableui/react"
|
|
78
|
+
|
|
79
|
+
<Textarea aria-invalid defaultValue="Too long" />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Disabled
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Textarea } from "@sableui/react"
|
|
86
|
+
|
|
87
|
+
<Textarea disabled defaultValue="Captions lock once the edition is minted." />
|
|
88
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# ThemeSelector
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A presentational radiogroup for choosing a Sable design theme from theme manifests — swatch-strip cards for a settings page, or compact rows for tool chrome. Pair it with useSableTheme().themeId and setThemeId when the app uses SableThemeProvider.
|
|
6
|
+
|
|
7
|
+
**Keywords:** theme selector, theme, appearance, skin, design theme, preset
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use ThemeSelector wherever a person picks a theme. The default `cards` layout is page-register furniture: a swatch strip, a name, and a caption only where one distinguishes the card (the default theme, or a single-mode theme), at ~144px tall. Use `layout="list"` in tool chrome, a popover, or any docked panel — five cards is the wrong object for a 256px surface, however it is sized. For colour mode (light/dark/system) use ThemeSwitcher, which is a segmented control and belongs in chrome as-is.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use layout="list" in a popover, a menu, or a docked panel. Rows sit on the --control-sm ladder and still show each theme's swatch strip, so the choice stays visible rather than described. Rows take `sable-frame-inner`, so host the list in a sable-frame (a Popover, or `sable-frame sable-surface sable-surface-popover sable-frame-popover`) and their corners nest concentrically.
|
|
16
|
+
- Do: Pass mode={useSableTheme().mode} so every preview renders in the colour mode actually in use, rather than in each theme's manifest default.
|
|
17
|
+
- Do: Load each theme's fonts in the host app. Every card carries a type specimen in that theme's own face so people can compare typography as well as color. A theme whose fonts are not loaded falls back to system-ui, and its specimen stops telling the truth.
|
|
18
|
+
- Don't: Add a viewport breakpoint to force a column count — `className="sm:grid-cols-1"` or similar. `sm:` keys off the VIEWPORT, not this component's container, so on a wide screen it does nothing for a narrow parent. The cards grid already reflows on its own width via auto-fit.
|
|
19
|
+
- Don't: Put the cards layout in workspace chrome. It was built for a settings page; in a 256px popover the cards clip their labels to a single character and grow to roughly twice their intended height.
|
|
20
|
+
- Don't: Use ThemeSelector for light / dark / system colour mode. That is ThemeSwitcher — a segmented control. This picks the design theme, which is a different axis entirely, and the two are frequently confused.
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Name | Type | Default | Description |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| value | ThemeId | — | The currently selected Sable design theme id. |
|
|
27
|
+
| onValueChange | (themeId: ThemeId) => void | — | Called with the next design theme id when an option is chosen. |
|
|
28
|
+
| themes | readonly ThemeManifest[] | themeManifests | Theme manifests to render. Defaults to Sable's built-in themes. |
|
|
29
|
+
| mode | "light" \| "dark" | — | Preview mode for each card's swatch strip. Pass useSableTheme().mode to match the current color mode; omitted cards use their manifest defaultMode. |
|
|
30
|
+
| label | string | "Design theme" | Accessible label for the radiogroup. |
|
|
31
|
+
| layout | "cards" \| "list" | "cards" | Presentation. "cards" is the page-register picker (~144px per card); "list" is one compact --control-sm row per theme, for chrome and popovers. A presentation variant, not a size — "list" is not "cards, smaller" — which is why it is a prop rather than a density scope. |
|
|
32
|
+
| className | string | — | Merged onto the outer radiogroup grid. |
|
|
33
|
+
|
|
34
|
+
## Tokens used
|
|
35
|
+
|
|
36
|
+
`input`, `card`, `border`, `background`, `primary`, `primary-foreground`, `accent`, `muted`, `muted-foreground`, `ring`
|
|
37
|
+
|
|
38
|
+
## Accessibility
|
|
39
|
+
|
|
40
|
+
- The container is role="radiogroup" with an aria-label; each card is a role="radio" button with aria-checked reflecting the selected theme.
|
|
41
|
+
- Focus-visible draws a 2px ring on the focused card.
|
|
42
|
+
- The selected card wears the shared selection-card checked ring (the same as RadioCard and CheckboxCard), a muted fill and a check icon, so selection does not rely on colour alone.
|
|
43
|
+
- Presentational only: it does not change the document theme; pass useSableTheme().setThemeId or apply the change in onValueChange.
|
|
44
|
+
|
|
45
|
+
## Related
|
|
46
|
+
|
|
47
|
+
- Components: ThemeSwitcher, Popover, RadioCard
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### With SableThemeProvider
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { ThemeSelector, useSableTheme } from "@sableui/react"
|
|
55
|
+
|
|
56
|
+
const { themeId, setThemeId, themes, mode } = useSableTheme()
|
|
57
|
+
<ThemeSelector value={themeId} onValueChange={setThemeId} themes={themes} mode={mode} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Controlled
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { ThemeSelector, type ThemeId } from "@sableui/react"
|
|
64
|
+
|
|
65
|
+
const [themeId, setThemeId] = useState<ThemeId>("base")
|
|
66
|
+
<ThemeSelector value={themeId} onValueChange={setThemeId} />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### In chrome — a popover or a docked panel
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { PopoverContent, ThemeSelector, ThemeSwitcher } from "@sableui/react"
|
|
73
|
+
|
|
74
|
+
// Five 144px cards is the wrong object for a 256px surface. Rows are.
|
|
75
|
+
<PopoverContent align="end" className="w-64 p-3">
|
|
76
|
+
<ThemeSwitcher value={preference} onValueChange={setPreference} />
|
|
77
|
+
<ThemeSelector layout="list" value={themeId} onValueChange={setThemeId} mode={mode} />
|
|
78
|
+
</PopoverContent>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Do not reach for a viewport breakpoint
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { ThemeSelector } from "@sableui/react"
|
|
85
|
+
|
|
86
|
+
// ✗ `sm:` keys off the VIEWPORT, not this component's container. On a wide
|
|
87
|
+
// screen it does nothing at all for a narrow parent — and this exact
|
|
88
|
+
// workaround, once documented here, is how the cards layout ended up in a
|
|
89
|
+
// 256px popover clipping "Default" to "D".
|
|
90
|
+
<ThemeSelector className="sm:grid-cols-1" value={themeId} onValueChange={setThemeId} />
|
|
91
|
+
|
|
92
|
+
// ✓ The cards grid already reflows on its own width (auto-fit, 9rem tracks).
|
|
93
|
+
// For a genuinely narrow surface, change the presentation instead.
|
|
94
|
+
<ThemeSelector layout="list" value={themeId} onValueChange={setThemeId} />
|
|
95
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ThemeSwitcher
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A segmented light / dark / system switch. Presentational: it renders the selected color preference and reports changes via onValueChange; pair it with useSableTheme() when the app uses SableThemeProvider. A radiogroup of three icon buttons (sun / moon / desktop).
|
|
6
|
+
|
|
7
|
+
**Keywords:** theme switcher, color mode, dark mode, appearance
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use ThemeSwitcher for light, dark, and system color mode preference.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it where users expect appearance controls, such as app chrome or settings.
|
|
16
|
+
- Do: Respect persisted system or user preference when initializing the control.
|
|
17
|
+
- Don't: Do not use ThemeSwitcher to choose brand themes or component style presets.
|
|
18
|
+
- Don't: Do not hand-build another segmented capsule for a different choice. ThemeSwitcher wears the shared segmented look (lib/segmented); for any other single-select switch use ToggleGroup appearance="segmented".
|
|
19
|
+
- Don't: Do not omit the `system` option or default to a hardcoded mode. Overriding the OS preference is the single most common accessibility complaint about theme controls.
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Name | Type | Default | Description |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| value | "light" \| "dark" \| "system" | — | The currently selected color preference. |
|
|
26
|
+
| onValueChange | (value: ThemeMode) => void | — | Called with the next color preference when an option is chosen. |
|
|
27
|
+
| label | string | "Theme" | Accessible label for the radiogroup. |
|
|
28
|
+
| className | string | — | Merged onto the outer radiogroup container. |
|
|
29
|
+
|
|
30
|
+
## Tokens used
|
|
31
|
+
|
|
32
|
+
`border`, `card`, `muted-foreground`, `foreground`, `accent`, `accent-foreground`, `ring`
|
|
33
|
+
|
|
34
|
+
## Accessibility
|
|
35
|
+
|
|
36
|
+
- The container is role="radiogroup" with an aria-label; each option is role="radio" with aria-checked reflecting the selected value.
|
|
37
|
+
- Each icon button carries an aria-label (Light / Dark / System) plus a title tooltip, since the visual is icon-only.
|
|
38
|
+
- Focus-visible draws a 2px ring on the focused option; selection styling reads from aria-checked.
|
|
39
|
+
- Presentational only: it does not change the document theme; pass useSableTheme().setPreference or apply the change in onValueChange.
|
|
40
|
+
|
|
41
|
+
## Related
|
|
42
|
+
|
|
43
|
+
- Components: ThemeSelector, Switch, Toggle
|
|
44
|
+
|
|
45
|
+
## Examples
|
|
46
|
+
|
|
47
|
+
### Controlled
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { ThemeSwitcher, type ThemeMode } from "@sableui/react"
|
|
51
|
+
|
|
52
|
+
const [theme, setTheme] = useState<ThemeMode>("system")
|
|
53
|
+
<ThemeSwitcher value={theme} onValueChange={setTheme} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### With SableThemeProvider
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { ThemeSwitcher, useSableTheme } from "@sableui/react"
|
|
60
|
+
|
|
61
|
+
const { preference, setPreference } = useSableTheme()
|
|
62
|
+
<ThemeSwitcher value={preference} onValueChange={setPreference} />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Light selected
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { ThemeSwitcher } from "@sableui/react"
|
|
69
|
+
|
|
70
|
+
<ThemeSwitcher value="light" onValueChange={setTheme} />
|
|
71
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# ThinkingOrb
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Canvas dot-cloud 'agent is thinking' indicator. Three generated modes (orbit / globe / mesh) drawn from pure math — no icon dependency, no animation library. Ink follows currentColor; size steps are separately tuned.
|
|
6
|
+
|
|
7
|
+
**Keywords:** thinking, orb, agent, ai, chat, loading, streaming, indeterminate, canvas
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use ThinkingOrb while an AI agent is working and there is nothing specific to report yet — before the first token streams, or between tool calls. It is a richer, more characterful sibling of Spinner for the one place in a product where the wait is the experience.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Reserve it for the single active agent state — the composer, the streaming message header, a standalone thinking screen. One orb on screen reads as intent; several read as decoration.
|
|
16
|
+
- Do: Switch mode to say what kind of work is happening: orbit for generic thinking, globe while searching or retrieving, mesh while relating sources or running tools. Pair it with a text status so the phase is legible without decoding the animation.
|
|
17
|
+
- Do: Use size="sm" inline in a message row or composer and size="lg" only when the orb is the main thing on screen. The size steps are separately tuned, not scaled, so the small one stays legible.
|
|
18
|
+
- Don't: Do not put one on every row of a list or in a repeated skeleton. Each orb runs its own animation frame loop painting hundreds of arcs; use Skeleton for content-shaped waits and Spinner inside buttons.
|
|
19
|
+
- Don't: Do not use it for a determinate wait. It carries no sense of how far along anything is — reach for Progress when a percentage exists.
|
|
20
|
+
- Don't: Do not rely on the animation alone to announce the wait. Give the label a short constant phrase (it is the role=status announcement) or place a status message next to it.
|
|
21
|
+
- Don't: Do not pass a label that changes while the orb is up. It is the accessible name of a live region, so every change re-announces the whole thing — feed it a per-token phase and a screen reader interrupts on each delta: "Chromatic drift" → "Chromatic drift · rgb-shift" → "Chromatic drift · rgb-shift → grain", which is unusable. The label names the wait ("Thinking"); the phase belongs in adjacent text, as in the "Inline with a status line" example. If the phase must be announced, announce it once when the turn settles from a separate live region — not per update.
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Name | Type | Default | Description |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| mode | "orbit" \| "globe" \| "mesh" | "orbit" | Which cloud to draw. orbit = generic working, globe = searching/retrieving (a scan band sweeps pole to pole), mesh = relating things (nodes wire to near neighbours and signals run the edges). |
|
|
28
|
+
| size | "sm" \| "md" \| "lg" | "md" | Rendered box on the spacing scale: sm 5 units (20px at the default spacing; inline in a composer or message row), md 8 (32px; beside a status line), lg 16 (64px; standalone). The box moves with density like the text beside it, and the canvas re-measures it. Each step has its own dot density, dot size and speed. |
|
|
29
|
+
| speed | number | 1 | Multiplier on top of the per-size tuned speed. Below ~0.5 the orb reads as stalled. |
|
|
30
|
+
| paused | boolean | false | Freeze on a representative still frame without unmounting. |
|
|
31
|
+
| label | string | "Thinking" | Accessible label announced by screen readers while visible (sets aria-label on the role=status element). Must stay constant for as long as the orb is mounted — it names the wait, it does not report progress. |
|
|
32
|
+
| className | string | — | Set the ink with a semantic text-* token (the dots are drawn from the element's computed color). The box is sized by the size prop, not by className. |
|
|
33
|
+
|
|
34
|
+
## Tokens used
|
|
35
|
+
|
|
36
|
+
`foreground`, `muted-foreground`, `primary`
|
|
37
|
+
|
|
38
|
+
## Motion
|
|
39
|
+
|
|
40
|
+
- **arrival** · Root · _no motion_ — The orb appears when work starts, and the point cloud is already in motion on its first painted frame. There is nothing to stage.
|
|
41
|
+
- **response** · Root · _no motion_ — It reports state; it is not a control. A hover reaction would suggest the wait is something you can click your way out of.
|
|
42
|
+
- **transition** · Root · _no motion_ — Changing `mode` swaps the renderer on the next frame: the cloud is redrawn as the new shape, not morphed into it. The phase change is carried by the status text beside the orb, which is what a reader actually reads; a per-particle morph would be decoration on top of it.
|
|
43
|
+
- **activity** · Root · _custom (outside the recipe layer)_ — The cloud is drawn per frame on a canvas by a single requestAnimationFrame loop — no CSS animation exists to name here. The loop reads the motion policy itself, which is the part worth checking when auditing: `custom` means the kill-switch is honoured in JS rather than by --motion-scale.
|
|
44
|
+
|
|
45
|
+
## Accessibility
|
|
46
|
+
|
|
47
|
+
- Wrapped in role=status, a polite live region, with an aria-label (default 'Thinking'); the canvas itself is aria-hidden.
|
|
48
|
+
- Because that label is the live region's accessible name, it must not change while the orb is mounted — a name that churns re-announces on every change, so a phase recomputed per streamed delta becomes an interruption per delta. Keep the label constant and let the phase live in adjacent text; announce an outcome once when the work settles, from a separate region.
|
|
49
|
+
- Ink is the element's computed color, so contrast is the consumer's token choice — pair with foreground or muted-foreground rather than a decorative tint.
|
|
50
|
+
- Honours the motion policy: under prefers-reduced-motion or <MotionProvider> off, it paints one still frame and never starts an animation loop.
|
|
51
|
+
- The loop also stops when the orb scrolls out of view or the tab is backgrounded, so a forgotten orb cannot burn a CPU core.
|
|
52
|
+
|
|
53
|
+
## Related
|
|
54
|
+
|
|
55
|
+
- Components: Spinner, Skeleton, Progress, InlineStatus, ChatComposer, Message
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Default
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { ThinkingOrb } from "@sableui/react"
|
|
63
|
+
|
|
64
|
+
<ThinkingOrb />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Modes
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { ThinkingOrb } from "@sableui/react"
|
|
71
|
+
|
|
72
|
+
<ThinkingOrb mode="orbit" size="lg" />
|
|
73
|
+
<ThinkingOrb mode="globe" size="lg" />
|
|
74
|
+
<ThinkingOrb mode="mesh" size="lg" />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Inline with a status line (the label stays constant, the phase moves)
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { InlineStatus, ThinkingOrb } from "@sableui/react"
|
|
81
|
+
|
|
82
|
+
<InlineStatus icon={<ThinkingOrb mode="globe" size="sm" label="Searching" />}>Searching your workspace…</InlineStatus>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Tinted and paused
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { ThinkingOrb } from "@sableui/react"
|
|
89
|
+
|
|
90
|
+
<ThinkingOrb size="lg" className="text-primary" paused />
|
|
91
|
+
```
|