@sableui/react 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +55 -0
  3. package/CONTRIBUTING.md +221 -0
  4. package/DESIGN.md +548 -0
  5. package/LICENSE +21 -0
  6. package/README.md +194 -2
  7. package/THIRD_PARTY_NOTICES.md +44 -0
  8. package/bin/sable-lint.mjs +0 -0
  9. package/bin/sable.mjs +1223 -0
  10. package/dist/_contract-CYScOdf5.d.ts +150 -0
  11. package/dist/chunk-3IWZYBGH.js +398 -0
  12. package/dist/chunk-5Z5KLOAU.js +167 -0
  13. package/dist/chunk-7MG4SHYX.js +15 -0
  14. package/dist/chunk-ADLLDWLW.js +15 -0
  15. package/dist/chunk-EP55KSYA.js +14 -0
  16. package/dist/chunk-J23ELQQZ.js +9396 -0
  17. package/dist/chunk-JI7WJP2L.js +286 -0
  18. package/dist/chunk-K7RUHUGC.js +14 -0
  19. package/dist/chunk-KO3NAEBF.js +15 -0
  20. package/dist/chunk-L4IEIQQA.js +14 -0
  21. package/dist/chunk-L6Y46C62.js +270 -0
  22. package/dist/chunk-NEQNXI2G.js +25 -0
  23. package/dist/chunk-PAOZBZME.js +40 -0
  24. package/dist/chunk-RWP4XBSK.js +139 -0
  25. package/dist/chunk-SRQWQUTW.js +63 -0
  26. package/dist/chunk-YXBOYWBN.js +15 -0
  27. package/dist/foundations/index.d.ts +88 -0
  28. package/dist/foundations/index.js +792 -0
  29. package/dist/icon/index.d.ts +60 -0
  30. package/dist/icon/index.js +2 -0
  31. package/dist/icon-sets/brands.d.ts +44 -0
  32. package/dist/icon-sets/brands.js +147 -0
  33. package/dist/icon-sets/reicon.d.ts +8 -0
  34. package/dist/icon-sets/reicon.js +1 -0
  35. package/dist/index.d.ts +7013 -0
  36. package/dist/index.js +15462 -0
  37. package/dist/ios/chrome.css +83 -0
  38. package/dist/ios/frame.css +5 -0
  39. package/dist/ios/index.d.ts +70 -0
  40. package/dist/ios/index.js +313 -0
  41. package/dist/ios/preview.d.ts +64 -0
  42. package/dist/ios/preview.js +133 -0
  43. package/dist/ios/styles.css +77 -0
  44. package/dist/layout/index.d.ts +280 -0
  45. package/dist/layout/index.js +2 -0
  46. package/dist/meta-6dUzgC6M.d.ts +205 -0
  47. package/dist/patterns/index.d.ts +36 -0
  48. package/dist/patterns/index.js +934 -0
  49. package/dist/presets/index.d.ts +37 -0
  50. package/dist/presets/index.js +117 -0
  51. package/dist/registry-CyL28OzJ.d.ts +28 -0
  52. package/dist/styles.css +1893 -0
  53. package/dist/theme-provider-BWcizhAt.d.ts +105 -0
  54. package/dist/themes/atelier/atelier.css +213 -0
  55. package/dist/themes/atelier/index.d.ts +9 -0
  56. package/dist/themes/atelier/index.js +1 -0
  57. package/dist/themes/base/base.css +443 -0
  58. package/dist/themes/base/index.d.ts +9 -0
  59. package/dist/themes/base/index.js +1 -0
  60. package/dist/themes/ember/ember.css +270 -0
  61. package/dist/themes/ember/index.d.ts +9 -0
  62. package/dist/themes/ember/index.js +1 -0
  63. package/dist/themes/index.d.ts +10 -0
  64. package/dist/themes/index.js +8 -0
  65. package/dist/themes/meridian/index.d.ts +9 -0
  66. package/dist/themes/meridian/index.js +1 -0
  67. package/dist/themes/meridian/meridian.css +255 -0
  68. package/dist/themes/poppy/index.d.ts +6 -0
  69. package/dist/themes/poppy/index.js +1 -0
  70. package/dist/themes/poppy/poppy.css +174 -0
  71. package/dist/themes/prism/index.d.ts +6 -0
  72. package/dist/themes/prism/index.js +1 -0
  73. package/dist/themes/prism/prism.css +183 -0
  74. package/dist/themes/verdant/index.d.ts +9 -0
  75. package/dist/themes/verdant/index.js +1 -0
  76. package/dist/themes/verdant/verdant.css +210 -0
  77. package/dist/themes.css +30 -0
  78. package/llms/components/accordion.md +93 -0
  79. package/llms/components/app-header.md +70 -0
  80. package/llms/components/app-shell.md +117 -0
  81. package/llms/components/attachment.md +105 -0
  82. package/llms/components/avatar.md +141 -0
  83. package/llms/components/badge.md +117 -0
  84. package/llms/components/banner.md +119 -0
  85. package/llms/components/bottom-nav.md +80 -0
  86. package/llms/components/breadcrumbs.md +75 -0
  87. package/llms/components/browser.md +67 -0
  88. package/llms/components/bubble.md +119 -0
  89. package/llms/components/button.md +199 -0
  90. package/llms/components/calendar.md +75 -0
  91. package/llms/components/card.md +135 -0
  92. package/llms/components/carousel.md +87 -0
  93. package/llms/components/chat-composer.md +126 -0
  94. package/llms/components/checkbox-card.md +98 -0
  95. package/llms/components/checkbox.md +90 -0
  96. package/llms/components/code-block.md +81 -0
  97. package/llms/components/code.md +62 -0
  98. package/llms/components/collapsible.md +83 -0
  99. package/llms/components/color-field.md +100 -0
  100. package/llms/components/combobox.md +119 -0
  101. package/llms/components/command-menu.md +158 -0
  102. package/llms/components/context-card.md +73 -0
  103. package/llms/components/context-menu.md +79 -0
  104. package/llms/components/data-table.md +99 -0
  105. package/llms/components/date-picker.md +97 -0
  106. package/llms/components/description.md +96 -0
  107. package/llms/components/destructive-action-modal.md +76 -0
  108. package/llms/components/dialog.md +127 -0
  109. package/llms/components/dots-menu.md +64 -0
  110. package/llms/components/empty-state.md +113 -0
  111. package/llms/components/entity.md +67 -0
  112. package/llms/components/feedback.md +63 -0
  113. package/llms/components/fieldset.md +106 -0
  114. package/llms/components/file-dropzone.md +103 -0
  115. package/llms/components/form.md +111 -0
  116. package/llms/components/gliding-highlight.md +84 -0
  117. package/llms/components/inline-status.md +62 -0
  118. package/llms/components/input.md +127 -0
  119. package/llms/components/kbd.md +57 -0
  120. package/llms/components/label.md +73 -0
  121. package/llms/components/marker.md +99 -0
  122. package/llms/components/marquee.md +66 -0
  123. package/llms/components/menu.md +122 -0
  124. package/llms/components/message-scroller.md +110 -0
  125. package/llms/components/message.md +114 -0
  126. package/llms/components/meter.md +124 -0
  127. package/llms/components/middle-truncate.md +62 -0
  128. package/llms/components/note.md +81 -0
  129. package/llms/components/notification-badge.md +67 -0
  130. package/llms/components/number-field.md +70 -0
  131. package/llms/components/otp-field.md +101 -0
  132. package/llms/components/page-header.md +76 -0
  133. package/llms/components/page-loader.md +67 -0
  134. package/llms/components/pagination.md +92 -0
  135. package/llms/components/panel-group.md +120 -0
  136. package/llms/components/panel-section.md +122 -0
  137. package/llms/components/phone.md +70 -0
  138. package/llms/components/pill.md +73 -0
  139. package/llms/components/popover.md +79 -0
  140. package/llms/components/progress.md +98 -0
  141. package/llms/components/radio-card.md +90 -0
  142. package/llms/components/radio.md +86 -0
  143. package/llms/components/relative-time.md +60 -0
  144. package/llms/components/scroller.md +88 -0
  145. package/llms/components/scrub-field.md +97 -0
  146. package/llms/components/search-input.md +84 -0
  147. package/llms/components/select.md +106 -0
  148. package/llms/components/separator.md +70 -0
  149. package/llms/components/settings-menu.md +139 -0
  150. package/llms/components/sheet.md +97 -0
  151. package/llms/components/show-more.md +59 -0
  152. package/llms/components/side-nav.md +106 -0
  153. package/llms/components/sidebar.md +87 -0
  154. package/llms/components/skeleton.md +62 -0
  155. package/llms/components/slider.md +114 -0
  156. package/llms/components/spinner.md +87 -0
  157. package/llms/components/status-bar.md +91 -0
  158. package/llms/components/status-dot.md +70 -0
  159. package/llms/components/stepper.md +125 -0
  160. package/llms/components/streaming-text.md +86 -0
  161. package/llms/components/switch.md +87 -0
  162. package/llms/components/table.md +112 -0
  163. package/llms/components/tabs.md +84 -0
  164. package/llms/components/textarea.md +88 -0
  165. package/llms/components/theme-selector.md +95 -0
  166. package/llms/components/theme-switcher.md +71 -0
  167. package/llms/components/thinking-orb.md +91 -0
  168. package/llms/components/toast.md +130 -0
  169. package/llms/components/toggle.md +95 -0
  170. package/llms/components/toolbar.md +168 -0
  171. package/llms/components/tooltip.md +93 -0
  172. package/llms/components/tree.md +227 -0
  173. package/llms/components/video.md +66 -0
  174. package/llms/components/workspace.md +138 -0
  175. package/llms/foundations/accessibility.md +60 -0
  176. package/llms/foundations/motion.md +96 -0
  177. package/llms/foundations/scrolling.md +41 -0
  178. package/llms/foundations/shape.md +73 -0
  179. package/llms/foundations/spacing.md +82 -0
  180. package/llms/foundations/surfaces.md +69 -0
  181. package/llms/foundations/typography.md +120 -0
  182. package/llms/layout/aspect-box.md +37 -0
  183. package/llms/layout/bleed.md +31 -0
  184. package/llms/layout/center.md +37 -0
  185. package/llms/layout/columns.md +51 -0
  186. package/llms/layout/grid-overlay.md +40 -0
  187. package/llms/layout/grid.md +47 -0
  188. package/llms/layout/page.md +39 -0
  189. package/llms/layout/region.md +49 -0
  190. package/llms/layout/row.md +40 -0
  191. package/llms/layout/safe-area.md +37 -0
  192. package/llms/layout/spacer.md +39 -0
  193. package/llms/layout/split-pane.md +41 -0
  194. package/llms/layout/stack.md +39 -0
  195. package/llms/patterns/auth-page.md +68 -0
  196. package/llms/patterns/brand-landing-page.md +423 -0
  197. package/llms/patterns/chat-panel.md +210 -0
  198. package/llms/patterns/dashboard-page.md +170 -0
  199. package/llms/patterns/feed-page.md +103 -0
  200. package/llms/patterns/landing-page.md +122 -0
  201. package/llms/patterns/list-detail-page.md +100 -0
  202. package/llms/patterns/settings-page.md +121 -0
  203. package/llms/reference/token-intent.md +324 -0
  204. package/llms/reference/utilities.md +129 -0
  205. package/llms/tasks/adding-a-component.md +113 -0
  206. package/llms/tasks/auditing-an-app.md +216 -0
  207. package/llms/tasks/building-a-page.md +109 -0
  208. package/llms/tasks/building-a-tool.md +141 -0
  209. package/llms/tasks/building-an-ios-prototype.md +105 -0
  210. package/llms/tasks/building-with-sable.md +73 -0
  211. package/llms/tasks/choosing-a-component.md +109 -0
  212. package/llms/tasks/converting-an-app.md +139 -0
  213. package/llms/tasks/creating-a-preset.md +43 -0
  214. package/llms/tasks/creating-design-assets.md +96 -0
  215. package/llms/tasks/designing-a-surface.md +115 -0
  216. package/llms/tasks/developing-taste.md +157 -0
  217. package/llms/tasks/getting-started.md +63 -0
  218. package/llms/tasks/learning-from-builds.md +74 -0
  219. package/llms/tasks/reviewing-a-design.md +114 -0
  220. package/llms/tasks/reviewing-a-journey.md +84 -0
  221. package/llms/tasks/reviewing-motion.md +92 -0
  222. package/llms/tasks/using-presets.md +87 -0
  223. package/llms/taste/asset-plan.template.json +53 -0
  224. package/llms/taste/composition-follows-content.md +20 -0
  225. package/llms/taste/hierarchy-is-a-relationship.md +22 -0
  226. package/llms/taste/lessons.json +57 -0
  227. package/llms/taste/motion-needs-a-reason.md +21 -0
  228. package/llms/taste/surface-states-travel-together.md +20 -0
  229. package/llms-full.txt +14906 -0
  230. package/llms.txt +405 -0
  231. package/package.json +185 -3
  232. package/registry.json +18812 -0
  233. package/scripts/agent-setup.mjs +54 -0
  234. package/scripts/design-atlas.mjs +221 -0
  235. package/scripts/design-directions.mjs +123 -0
  236. package/scripts/design-memory.mjs +166 -0
  237. package/scripts/design-preferences.mjs +227 -0
  238. package/scripts/design-presets.mjs +48 -0
  239. package/scripts/grade.mjs +509 -0
  240. package/scripts/lint-rules.mjs +1065 -0
  241. package/scripts/review-evidence.mjs +120 -0
  242. package/scripts/skeleton.mjs +248 -0
  243. package/skills/entry/SKILL.md +34 -0
  244. package/skills/sable/SKILL.md +187 -0
  245. package/skills/sable/agents/openai.yaml +6 -0
  246. package/targets/ios.json +982 -0
  247. package/third-party/reicon-LICENSE.txt +20 -0
@@ -0,0 +1,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
+ ```