@sableui/react 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +50 -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,73 @@
1
+ # ContextCard
2
+
3
+ **Status:** stable
4
+
5
+ Hover/focus-triggered rich preview card over Base UI PreviewCard. Reveals contextual content (a profile, an edition summary) from an inline anchor; 20rem-wide surface with delayed open and collision-aware positioning.
6
+
7
+ **Keywords:** context card, rich tooltip, hover card, preview
8
+
9
+ ## Usage
10
+
11
+ Use ContextCard to show richer contextual content near a trigger without taking users out of flow.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it for previews, summaries, or supporting context tied to a specific element.
16
+ - Do: Keep the card read-only and make the trigger the action: it is a real link to the full profile or item. Keyboard users cannot reach a button inside the card, because moving focus off the trigger closes it. If the preview needs an action (Follow, Add), use a Popover instead.
17
+ - Don't: Do not put critical decisions, long forms, or required workflow steps inside ContextCard.
18
+ - Don't: Do not open it on click. It is a hover/focus preview; making it click-triggered gives you a Popover with no dismiss affordance and no way to reach the content it previews.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | ContextCardTrigger | yes | The element that anchors the card. |
25
+ | ContextCardContent | yes | The contextual surface shown near the trigger. |
26
+ | ContextCardArrow | no | An optional arrow connecting card and trigger. |
27
+
28
+ ## Subcomponents
29
+
30
+ | Name | Type | Description |
31
+ | --- | --- | --- |
32
+ | ContextCardTrigger | Base UI Trigger props + render + delay \| closeDelay | The inline anchor (renders an <a>). Use render={<a href='…'/>} to compose with a link. delay defaults to 600ms, closeDelay to 300ms. |
33
+ | ContextCardContent | Base UI Popup props + side \| align \| sideOffset + className | Portals + positions + paints the popup; surface is a fixed 20rem-wide card. side is which side of the trigger to render against (auto-flips on collision), align the alignment along it, sideOffset the gap in px. Defaults to side="bottom", align="center", sideOffset=8. |
34
+ | ContextCardArrow | Base UI Arrow props + className | Optional pointer connecting the popup to its trigger. Place inside ContextCardContent. |
35
+
36
+ ## Props
37
+
38
+ | Name | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | ContextCard (Root) | Base UI PreviewCard.Root props (open, defaultOpen, onOpenChange) | — | Groups the trigger and content. Uncontrolled by default; opens on hover/focus of the trigger. |
41
+
42
+ ## Tokens used
43
+
44
+ `popover`, `popover-foreground`, `border`, `muted-foreground`, `accent`
45
+
46
+ ## Accessibility
47
+
48
+ - Opens on hover and on keyboard focus of the trigger, after a short delay; dismisses on Esc, outside press, and blur.
49
+ - The trigger renders an <a> by default — compose render={<a href='…'/>} so it stays a real, navigable link.
50
+ - Base UI wires the popup's ARIA association with the trigger; the card holds non-interactive context, not focusable controls.
51
+ - Animate via data-[starting-style]/data-[ending-style] opacity attributes on the popup.
52
+
53
+ ## Related
54
+
55
+ - Components: Popover, Tooltip
56
+
57
+ ## Examples
58
+
59
+ ### Profile preview
60
+
61
+ ```tsx
62
+ import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } from "@sableui/react"
63
+
64
+ <ContextCard><ContextCardTrigger render={<a href='/u/ada' className='text-title-sm underline-offset-2 hover:underline'>@ada</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='AL' size='lg' />} title='Ada Lovelace' subtitle='@ada' /><p className='mt-3 text-body-sm text-muted-foreground'>Minting generative editions on Solana. Permanently on Arweave.</p></ContextCardContent></ContextCard>
65
+ ```
66
+
67
+ ### With stat row
68
+
69
+ ```tsx
70
+ import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Description, DescriptionItem, Entity } from "@sableui/react"
71
+
72
+ <ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><Description cols='2' className='mt-4 border-t border-border pt-3'><DescriptionItem term='Posts' detail='128' /><DescriptionItem term='Followers' detail='4.2k' /></Description></ContextCardContent></ContextCard>
73
+ ```
@@ -0,0 +1,79 @@
1
+ # ContextMenu
2
+
3
+ **Status:** stable
4
+
5
+ Right-click menu over Base UI ContextMenu — opens on the native contextmenu event or long press and positions at the pointer. Shares Menu's roving focus, typeahead, ARIA menu/menuitem wiring, collision-aware positioning, and Esc/outside dismissal.
6
+
7
+ **Keywords:** context menu, right click, overflow, menu
8
+
9
+ ## Usage
10
+
11
+ Use ContextMenu for actions that are specific to the object or area the user invoked.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it for secondary object actions such as rename, duplicate, move, or delete.
16
+ - Do: Group destructive or uncommon actions away from frequent safe actions.
17
+ - Don't: Do not make ContextMenu the only way to access a primary or required action.
18
+ - Don't: Do not rely on it for touch. There is no right-click on a touchscreen — the long-press fallback is discoverable by nobody, so anything in here needs a visible route too.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | ContextMenuTrigger | yes | The target area or element that opens the menu. |
25
+ | ContextMenuContent | yes | The menu surface. |
26
+ | ContextMenuItem | yes | One actionable command. |
27
+ | ContextMenuSeparator | no | A divider between command groups. |
28
+ | ContextMenuGroup | no | A semantic group of related commands. |
29
+ | ContextMenuGroupLabel | no | An optional label for a command group. |
30
+
31
+ ## Subcomponents
32
+
33
+ | Name | Type | Description |
34
+ | --- | --- | --- |
35
+ | ContextMenuTrigger | Base UI ContextMenu.Trigger props + className + render | The right-clickable area that opens the menu on contextmenu (right click) or long press. Renders a <div>. |
36
+ | ContextMenuContent | Base UI Popup props + { side, align, sideOffset, className } | Portal + Positioner + Popup. Positions at the pointer. side is which side of the anchor the popup renders against (auto-flips on collision), align its alignment along that side, sideOffset the gap in px. Defaults to side='bottom', align='start', sideOffset=6. |
37
+ | ContextMenuItem | Base UI ContextMenu.Item props + variant?: "default" \| "destructive" + className (disabled, closeOnClick, onClick) | An interactive row. Lays out as a flex row, so a leading Icon sits inline before the label. Highlights via data-[highlighted]; dims via data-[disabled], and disabled items are skipped by keyboard focus. variant="destructive" marks a destructive command (Delete, Remove) and keeps its destructive ink while highlighted; never fake it with a text-destructive class, which the highlight repaints. |
38
+ | ContextMenuSeparator | Base UI Separator props + className | Hairline rule between item runs. |
39
+ | ContextMenuGroup | Base UI ContextMenu.Group props + className | Groups related items under an accessible label auto-associated with the group. |
40
+ | ContextMenuGroupLabel | Base UI ContextMenu.GroupLabel props + className | The label for a ContextMenuGroup. Auto-associated with the group it sits in. |
41
+
42
+ ## Props
43
+
44
+ | Name | Type | Default | Description |
45
+ | --- | --- | --- | --- |
46
+ | ContextMenu (Root) | Base UI ContextMenu.Root props (open, defaultOpen, onOpenChange, orientation, loopFocus) | — | Groups all parts and controls open state. Renders no element. Uncontrolled by default. |
47
+
48
+ ## Tokens used
49
+
50
+ `popover`, `popover-foreground`, `border`, `accent`, `accent-foreground`, `muted-foreground`
51
+
52
+ ## Accessibility
53
+
54
+ - Base UI provides role=menu/menuitem, roving tabindex, arrow-key navigation, and typeahead.
55
+ - Opens on right click (contextmenu) or long press; Esc and outside-press dismiss and restore focus.
56
+ - Animate open/close via data-[starting-style]/data-[ending-style] attributes.
57
+ - ContextMenuGroupLabel is auto-associated with its ContextMenuGroup for screen readers.
58
+
59
+ ## Related
60
+
61
+ - Components: Menu, DotsMenu
62
+
63
+ ## Examples
64
+
65
+ ### Basic
66
+
67
+ ```tsx
68
+ import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@sableui/react"
69
+
70
+ <ContextMenu><ContextMenuTrigger className="rounded-md border border-dashed border-border p-8 text-center text-body-sm text-muted-foreground">Right-click here</ContextMenuTrigger><ContextMenuContent><ContextMenuItem>Cut</ContextMenuItem><ContextMenuItem>Copy</ContextMenuItem><ContextMenuItem>Paste</ContextMenuItem><ContextMenuSeparator /><ContextMenuItem variant="destructive">Delete</ContextMenuItem></ContextMenuContent></ContextMenu>
71
+ ```
72
+
73
+ ### Grouped
74
+
75
+ ```tsx
76
+ import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@sableui/react"
77
+
78
+ <ContextMenu><ContextMenuTrigger className="rounded-md border border-dashed border-border p-8">Right-click here</ContextMenuTrigger><ContextMenuContent><ContextMenuGroup><ContextMenuGroupLabel>Layout</ContextMenuGroupLabel><ContextMenuItem>Move up</ContextMenuItem><ContextMenuItem>Move down</ContextMenuItem></ContextMenuGroup><ContextMenuSeparator /><ContextMenuItem>Duplicate</ContextMenuItem></ContextMenuContent></ContextMenu>
79
+ ```
@@ -0,0 +1,99 @@
1
+ # DataTable
2
+
3
+ **Status:** stable
4
+
5
+ A data-driven table over the Table primitives — a columns def + data array with built-in client-side sorting (click a sortable header to cycle asc → desc → none), optional row click / selection, and an empty state. For server data or paging, sort/page the data and pair with Pagination.
6
+
7
+ **Keywords:** data table, table, sortable, columns, rows, empty state, pagination, list
8
+
9
+ ## Usage
10
+
11
+ Use DataTable when a data array and column definition should render a sortable, semantic table with optional row actions and an empty state. Use the lower-level Table parts for hand-authored markup or highly custom row layouts.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Put a table that fills a card in <CardContent flush>. The table runs edge to edge and its first and last columns inset to the card padding on their own; do not hand-set pl-6 / pr-6 on columns.
16
+ - Do: Provide stable rowKey values for dynamic data so selection and sorting do not jump.
17
+ - Do: Use sortable only on columns where ordering is obvious and useful.
18
+ - Do: Pair server-side datasets with Pagination and pass already-sorted/paged rows.
19
+ - Don't: Make every row clickable and also fill it with competing buttons. Choose a clear row interaction model.
20
+ - Don't: Use DataTable for heterogeneous cards or timeline content. A list or card layout will scan better.
21
+
22
+ ### Anatomy
23
+
24
+ | Part | Required | Description |
25
+ | --- | --- | --- |
26
+ | Columns | yes | Definitions for headers, cell renderers, sorting, alignment, and classes. |
27
+ | Rows | yes | The data array rendered through the column definitions. |
28
+ | Empty state | no | Content spanning all columns when the row array is empty. |
29
+ | Caption | no | Accessible table caption rendered by the underlying Table. |
30
+
31
+ ## Props
32
+
33
+ | Name | Type | Default | Description |
34
+ | --- | --- | --- | --- |
35
+ | columns | DataTableColumn<Row>[] | — | Column defs: { key, header, cell?, sortValue?, sortable?, align?, className? }. cell defaults to row[key]; sortValue defaults to row[key]. When cell renders a formatted string (e.g. a date), pass sortValue returning the underlying value (e.g. date.getTime()) so the column sorts by the real value, not the rendered text. |
36
+ | data | Row[] | — | The rows to render. |
37
+ | rowKey | (row, index) => string \| number | index | Stable React key per row. |
38
+ | onRowClick | (row) => void | — | Row click handler — makes rows pointer-interactive. |
39
+ | isRowSelected | (row) => boolean | — | Mark a row visually selected (data-selected). Derive it from a stable id (row.id === selectedId), not the row index, so selection survives re-sorts. |
40
+ | defaultSort | { key: string; direction: "asc" \| "desc" } | — | Initial sort column + direction. |
41
+ | empty | ReactNode | "No results." | Rendered spanning all columns when data is empty. Pass an EmptyState for the full treatment (icon, title, next step). |
42
+ | caption | ReactNode | — | Bottom table caption. |
43
+
44
+ ## Tokens used
45
+
46
+ `muted-foreground`, `foreground`, `ring`
47
+
48
+ ## Accessibility
49
+
50
+ - Sorted columns set aria-sort=ascending/descending on the header cell.
51
+ - Sortable headers are real <button>s with a focus-visible ring, keyboard-operable.
52
+ - Inherits the Table's scrollable frame and semantic <table>/<th>/<td> structure.
53
+
54
+ ## Related
55
+
56
+ - Components: Table, Pagination, EmptyState, Badge
57
+ - Patterns: DashboardPage, ListDetailPage
58
+
59
+ ## Examples
60
+
61
+ ### Sortable columns
62
+
63
+ ```tsx
64
+ import { DataTable } from "@sableui/react"
65
+
66
+ <DataTable data={users} columns={[{ key: "name", header: "Name", sortable: true }, { key: "role", header: "Role" }, { key: "joined", header: "Joined", sortable: true, sortValue: (u) => u.joinedAt, cell: (u) => formatDate(u.joinedAt) }]} />
67
+ ```
68
+
69
+ ### Clickable rows
70
+
71
+ ```tsx
72
+ import { DataTable } from "@sableui/react"
73
+
74
+ <DataTable data={posts} columns={cols} onRowClick={(p) => navigate(`/post/${p.id}`)} rowKey={(p) => p.id} />
75
+ ```
76
+
77
+ ### Rich status column
78
+
79
+ ```tsx
80
+ import { Avatar, Badge, DataTable, Entity } from "@sableui/react"
81
+
82
+ <DataTable data={deployments} rowKey={(row) => row.id} columns={[{ key: "name", header: "Name", sortable: true }, { key: "status", header: "Status", cell: (row) => <Badge appearance="plain" tone={row.status === "failed" ? "destructive" : row.status === "building" ? "warning" : "success"} dot>{row.statusLabel}</Badge> }, { key: "owner", header: "Owner", cell: (row) => <Entity className="[--entity-px:0] [--entity-py:0]" leading={<Avatar src={row.owner.avatar} alt={row.owner.name} fallback={row.owner.initials} size="sm" />} title={row.owner.name} /> }]} />
83
+ ```
84
+
85
+ ### In a card, edge to edge
86
+
87
+ ```tsx
88
+ import { Card, CardContent, CardHeader, CardTitle, DataTable } from "@sableui/react"
89
+
90
+ <Card><CardHeader><CardTitle>Deployments</CardTitle></CardHeader><CardContent flush><DataTable data={deployments} rowKey={(row) => row.id} columns={[{ key: "project", header: "Project", sortable: true }, { key: "duration", header: "Duration", align: "end" }]} /></CardContent></Card>
91
+ ```
92
+
93
+ ### Empty state
94
+
95
+ ```tsx
96
+ import { Button, DataTable, EmptyState, Icon } from "@sableui/react"
97
+
98
+ <DataTable data={[]} columns={[{ key: "name", header: "Name" }, { key: "status", header: "Status" }]} empty={<EmptyState icon={<Icon name="search" />} title="No deployments" description="Try a different filter or create a deployment." action={<Button>Create deployment</Button>} />} />
99
+ ```
@@ -0,0 +1,97 @@
1
+ # DatePicker
2
+
3
+ **Status:** stable
4
+
5
+ Date (and optionally time) picker: a field-shaped trigger (the Input / SelectTrigger shell) opens a Popover holding Sable's Calendar. With granularity="day" selecting a day fires onValueChange and closes; granularity="minute" adds an hour + minute row of Selects and keeps the time-of-day when the day changes.
6
+
7
+ **Keywords:** date picker, calendar, date input, date time picker, time, schedule, form
8
+
9
+ ## Usage
10
+
11
+ Use DatePicker when users need to choose one date — or, with granularity="minute", one date and time — from a compact control.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it in forms where a typed date alone would be error-prone.
16
+ - Do: Show clear selected and disabled states when date rules apply.
17
+ - Do: Set granularity="minute" when a time matters, with a minuteStep that matches the real precision (15 for meetings, 5 for scheduled posts).
18
+ - Don't: Do not show a time without saying which zone it is in. A meeting time with no zone is wrong for everyone not in yours — wrap the picker in a Field whose description names the zone.
19
+ - Don't: Do not leave the format ambiguous. `03/04` is two different dates depending on the reader's locale — the trigger shows a formatted date precisely so the interpretation is not guessed.
20
+
21
+ ## Props
22
+
23
+ | Name | Type | Default | Description |
24
+ | --- | --- | --- | --- |
25
+ | value | Date | — | The selected date (controlled). When absent, the trigger shows a muted placeholder. |
26
+ | defaultValue | Date | — | Initial value when uncontrolled (no `value` prop). |
27
+ | onValueChange | (date: Date \| undefined) => void | — | Fired when the day is chosen or cleared, and under granularity="minute" when the hour or minute changes. With granularity="day" it also closes the popover. |
28
+ | onChange | (date: Date \| undefined) => void | — | Deprecated alias for onValueChange, kept for backwards compatibility. Both fire when set; prefer onValueChange. |
29
+ | granularity | "day" \| "minute" | "day" | What the picker chooses. "minute" adds an hour + minute row and keeps the popover open so day and time can be set in any order. |
30
+ | minuteStep | number | 5 | Minute increment for the time row (granularity="minute" only). |
31
+ | defaultTime | string | "00:00" | Time-of-day shown and applied while nothing is selected yet, as 24h "HH:mm" — the first day pick lands on this time instead of midnight (granularity="minute" only). |
32
+ | placeholder | string | "Pick a date" ("Pick date & time" with granularity="minute") | Trigger label shown when no date is selected. |
33
+ | disabled | boolean | — | Disable the trigger and block selection. |
34
+ | minDate | Date | — | Earliest selectable day (inclusive). Earlier days are disabled and navigation is bounded. |
35
+ | maxDate | Date | — | Latest selectable day (inclusive). Later days are disabled and navigation is bounded. |
36
+ | className | string | — | Extra classes merged onto the trigger. |
37
+ | id / aria-label / aria-labelledby / aria-describedby / aria-invalid / aria-required | button attributes | — | Forwarded to the trigger, so a wrapping Field ties its label to the picker and announces its description and error. Pass them yourself only when labelling the picker without a Field. |
38
+
39
+ ## Tokens used
40
+
41
+ `primary`, `primary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `popover`, `popover-foreground`, `border`, `input`, `card`, `ring`
42
+
43
+ ## Accessibility
44
+
45
+ - Trigger opens on click/Enter/Space; the popover traps focus and restores it to the trigger on close (Base UI Popover).
46
+ - The calendar grid (react-day-picker) provides a roving tabindex, arrow-key day navigation, and aria-selected on the chosen day.
47
+ - Navigation chevrons render through the Icon adapter and inherit the calendar's button labels.
48
+ - Esc and outside press dismiss the popover.
49
+ - With granularity="minute" the hour and minute Selects are full Base UI listboxes (typeahead, ARIA) labelled "Hour" and "Minute"; selecting a day keeps the chosen time-of-day, so day and time can be set in any order.
50
+
51
+ ## Related
52
+
53
+ - Components: Calendar, Field
54
+
55
+ ## Examples
56
+
57
+ ### Controlled
58
+
59
+ ```tsx
60
+ import { DatePicker } from "@sableui/react"
61
+
62
+ const [date, setDate] = useState<Date>()
63
+ <DatePicker value={date} onValueChange={setDate} />
64
+ ```
65
+
66
+ ### Custom placeholder
67
+
68
+ ```tsx
69
+ import { DatePicker } from "@sableui/react"
70
+
71
+ <DatePicker placeholder="Schedule for…" value={date} onValueChange={setDate} />
72
+ ```
73
+
74
+ ### Date and time
75
+
76
+ ```tsx
77
+ import { DatePicker } from "@sableui/react"
78
+
79
+ const [start, setStart] = useState<Date>()
80
+ <DatePicker granularity="minute" value={start} onValueChange={setStart} minDate={new Date()} defaultTime="09:00" minuteStep={15} />
81
+ ```
82
+
83
+ ### Disabled
84
+
85
+ ```tsx
86
+ import { DatePicker } from "@sableui/react"
87
+
88
+ <DatePicker disabled />
89
+ ```
90
+
91
+ ### Bounded (no past dates)
92
+
93
+ ```tsx
94
+ import { DatePicker } from "@sableui/react"
95
+
96
+ <DatePicker value={date} onValueChange={setDate} minDate={new Date()} />
97
+ ```
@@ -0,0 +1,96 @@
1
+ # Description
2
+
3
+ **Status:** stable
4
+
5
+ A labeled key/value list (definition list). Compose with DescriptionTerm + DescriptionDetail, or the ergonomic DescriptionItem shorthand. Aligns terms and details into a grid; cols="2" packs pairs two-wide.
6
+
7
+ **Keywords:** description list, metadata, details, key value
8
+
9
+ ## Usage
10
+
11
+ Use Description to present label-value details where each term needs a clear corresponding value.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Use it for object metadata, settings summaries, and read-only record details.
16
+ - Do: Keep terms brief and values scannable so the list works as reference material.
17
+ - Don't: Do not use Description for editable forms; pair Label and form controls instead.
18
+ - Don't: Do not use it for a single key/value. A definition list of one is a labelled value — use a Label and the value, and keep the list semantics for actual lists.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | DescriptionItem | yes | One term and detail pair. |
25
+ | DescriptionTerm | yes | The label for the value. |
26
+ | DescriptionDetail | yes | The value or explanatory detail. |
27
+
28
+ ## Subcomponents
29
+
30
+ | Name | Type | Description |
31
+ | --- | --- | --- |
32
+ | DescriptionTerm | <dt> props + className | The key. Muted label tier; sits in the first grid column. |
33
+ | DescriptionDetail | <dd> props + tone?: "default" \| "muted" \| "success" \| "warning" \| "destructive" + className | The value. Body tier on the foreground; sits in the second grid column. tone colors the value to encode a status (e.g. Ended → muted, Active → success), replacing inline conditional color classes; defaults to "default". |
34
+ | DescriptionItem | { term: ReactNode; detail?: ReactNode; tone?: "default" \| "muted" \| "success" \| "warning" \| "destructive"; children? } | Ergonomic shorthand for one term/detail pair — renders a DescriptionTerm followed by a DescriptionDetail. Pass the value via detail or children; tone (defaults to "default") colors the detail value the same way it does on DescriptionDetail. |
35
+
36
+ ## Props
37
+
38
+ | Name | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | cols | "1" \| "2" | "1" | Column density. One term/detail column, or two pairs per row once the list's container is at least @md (28rem) wide; narrower, it stays one column. A container query, not a viewport breakpoint, so a narrow card on a wide screen stays one column. With cols="2" the <dl> is wrapped in an @container div. |
41
+
42
+ ## Variants
43
+
44
+ - **cols**: 1, 2
45
+ - **tone**: default, muted, success, warning, destructive
46
+
47
+ ## Tokens used
48
+
49
+ `muted-foreground`, `foreground`, `success`, `tone-warning`, `destructive`
50
+
51
+ ## Accessibility
52
+
53
+ - Renders semantic <dl>/<dt>/<dd> elements so assistive tech announces the term/detail relationship.
54
+ - tone is a visual emphasis only — keep the value text itself meaningful (e.g. "Ended"), never color alone.
55
+
56
+ ## Related
57
+
58
+ - Components: Fieldset, Entity
59
+
60
+ ## Examples
61
+
62
+ ### Metadata list
63
+
64
+ ```tsx
65
+ import { Description, DescriptionItem } from "@sableui/react"
66
+
67
+ <Description>
68
+ <DescriptionItem term="Network" detail="Solana" />
69
+ <DescriptionItem term="Supply" detail="100" />
70
+ <DescriptionItem term="Royalty" detail="5%" />
71
+ </Description>
72
+ ```
73
+
74
+ ### Composed term/detail
75
+
76
+ ```tsx
77
+ import { Description, DescriptionDetail, DescriptionTerm } from "@sableui/react"
78
+
79
+ <Description>
80
+ <DescriptionTerm>Network</DescriptionTerm>
81
+ <DescriptionDetail>Solana</DescriptionDetail>
82
+ <DescriptionTerm>Contract</DescriptionTerm>
83
+ <DescriptionDetail className="text-mono-sm">7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU</DescriptionDetail>
84
+ </Description>
85
+ ```
86
+
87
+ ### Toned status value
88
+
89
+ ```tsx
90
+ import { Description, DescriptionItem } from "@sableui/react"
91
+
92
+ <Description>
93
+ <DescriptionItem term="Edition" detail="Midnight Archive" />
94
+ <DescriptionItem term="Status" detail={isEnded ? "Ended" : "Active"} tone={isEnded ? "muted" : "success"} />
95
+ </Description>
96
+ ```
@@ -0,0 +1,76 @@
1
+ # DestructiveActionModal
2
+
3
+ **Status:** stable
4
+
5
+ Confirmation modal for irreversible actions, built on AlertDialog (role=alertdialog; a backdrop click does not dismiss). Pairs a secondary Cancel with a destructive confirm; awaits an async onConfirm (spinner + stays open on failure) or takes an external pending flag; optional body slot and type-to-confirm guard for high-stakes deletions.
6
+
7
+ **Keywords:** destructive, modal, confirmation, danger
8
+
9
+ ## Usage
10
+
11
+ Use DestructiveActionModal to confirm irreversible or high-risk actions before they run.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Name the affected object and the consequence of the action in plain language.
16
+ - Do: Make the destructive action visually distinct from the cancel path.
17
+ - Don't: Do not use it for routine low-risk actions where undo or inline confirmation would be faster.
18
+ - Don't: Do not put the destructive action where Cancel usually sits, or make it the default focus. A confirmation dialog exists to add friction; autofocusing Delete removes the friction it was added for.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | open | boolean | — | Controlled open state. |
25
+ | onOpenChange | (open: boolean) => void | — | Called when the open state should change (Esc, Cancel, or confirm). A backdrop click does not dismiss. |
26
+ | title | ReactNode | — | Modal heading — names the irreversible action. |
27
+ | description | ReactNode | — | Optional supporting copy explaining the consequence. |
28
+ | confirmLabel | string | "Delete" | Label for the destructive confirm button. |
29
+ | cancelLabel | string | "Cancel" | Label for the dismissing secondary button. |
30
+ | onConfirm | () => void \| Promise<void> | — | Called when the user confirms. If it returns a Promise the modal shows an in-flight spinner, closes on resolve, and stays open on reject; if void it closes immediately (unless pending is controlled). |
31
+ | pending | boolean | — | Externally-driven in-flight state. Overrides the internal await tracking; the parent owns closing (the modal stays open on a synchronous confirm). |
32
+ | confirmingLabel | string | confirmLabel | Confirm-button label shown while the action is in flight. |
33
+ | children | ReactNode | — | Body content rendered between the description and the type-to-confirm Input / footer — e.g. a warning <Note>. |
34
+ | confirmText | string | — | When set, renders an Input the user must type matching to enable the destructive confirm button. |
35
+
36
+ ## Tokens used
37
+
38
+ `card`, `card-foreground`, `border`, `muted-foreground`, `foreground`, `input`, `destructive`, `destructive-foreground`, `secondary`, `secondary-foreground`
39
+
40
+ ## Accessibility
41
+
42
+ - Built on AlertDialog (Base UI): role="alertdialog", focus trapping, scroll lock, and aria-modal. Esc dismisses; a click on the backdrop does not, so a stray click can't throw away the decision.
43
+ - Focus opens on Cancel — the safe action — even when a type-to-confirm Input comes first, so a reflexive Enter never fires the destructive action.
44
+ - The title labels the modal; the description describes it.
45
+ - The type-to-confirm Input is wired to its label via htmlFor; the destructive Button is disabled (not just hidden) until the text matches.
46
+ - While a confirm is in flight (awaited onConfirm or pending), both buttons disable, the confirm Button sets aria-busy, and Esc dismissal is blocked so the action can't be interrupted or double-fired.
47
+
48
+ ## Related
49
+
50
+ - Components: Dialog, Button, Banner
51
+
52
+ ## Examples
53
+
54
+ ### Basic
55
+
56
+ ```tsx
57
+ import { DestructiveActionModal } from "@sableui/react"
58
+
59
+ <DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete post?' description='This permanently removes the post. This cannot be undone.' onConfirm={handleDelete} />
60
+ ```
61
+
62
+ ### Async confirm (awaited spinner)
63
+
64
+ ```tsx
65
+ import { DestructiveActionModal } from "@sableui/react"
66
+
67
+ <DestructiveActionModal open={open} onOpenChange={setOpen} title='Block user?' description='They can no longer interact with you.' confirmLabel='Block' confirmingLabel='Blocking…' onConfirm={() => blockUser(id)} />
68
+ ```
69
+
70
+ ### Body slot + type-to-confirm
71
+
72
+ ```tsx
73
+ import { DestructiveActionModal, Note } from "@sableui/react"
74
+
75
+ <DestructiveActionModal open={open} onOpenChange={setOpen} title='Delete this post?' confirmText='gold rush' confirmLabel='Delete forever' onConfirm={handleDelete}><Note tone='warning'>Collectors who minted this edition keep their copies.</Note></DestructiveActionModal>
76
+ ```