@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.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +50 -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,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
|
+
```
|