@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,81 @@
|
|
|
1
|
+
# Note
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Callout / admonition box. Its tone follows the shared status-tone vocabulary (neutral/info/success/warning/destructive), pairing a soft tone fill with a matching leading icon. Optional title.
|
|
6
|
+
|
|
7
|
+
**Keywords:** note, callout, admonition, warning, caveat, hint, context
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Note for helpful supporting information that should sit near the relevant content — including a caveat or warning attached to a form, a dialog, or a section. It is the in-context callout; Banner is the page-level strip.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for tips, caveats, or secondary context that improves understanding.
|
|
16
|
+
- Do: Reach for Note rather than Banner when the message qualifies something on the page — a warning inside a dialog, a caveat above a submit button. Banner is a full-width announcement strip for page- or app-level state; inside a form it reads as a slab, and its soft fill carries no border or tone-colored icon, so the severity stops being visible.
|
|
17
|
+
- Do: Keep tone calm and concise so the note does not compete with primary content.
|
|
18
|
+
- Do: When the reader can resolve the state, pass `action` rather than putting a Button in children. The prop is data — label plus handler — so the callout owns the placement and weight; a Button dropped into children inherits neither, floats in the box's padding, and (in `secondary`) punches a near-background hole in the tone fill.
|
|
19
|
+
- Don't: Do not use Note for state the whole page or app is in (an outage, an offline session, a blocked account) — that is Banner — or for a message tied to one field, which is the field's own error text.
|
|
20
|
+
- Don't: Do not give a Note two actions. One remedy reads as the sentence's next move; two make it a decision point, which is a Dialog's job.
|
|
21
|
+
- Don't: Do not stack several in a row. Three notes together are a wall of asides; if everything is called out, nothing is.
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Name | Type | Default | Description |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| action | { label: string; onClick?: () => void; href?: string } | — | A single remedy, rendered below the body and aligned to the text column. Deliberately data, not a slot: Note owns the placement, weight, ink and motion, so the action cannot be styled into a button that outweighs the sentence explaining it. Use onClick for an in-place action (renders a button, no glyph) or href for a link (renders an anchor with a trailing chevron) — the chevron means navigate, so it appears only when the action genuinely goes somewhere. |
|
|
28
|
+
| tone | "neutral" \| "info" \| "success" \| "warning" \| "destructive" | "info" | Severity tone — drives the border, fill, and leading icon color/name. The shared status-tone vocabulary (STATUS_TONES). |
|
|
29
|
+
| title | string | — | Optional bold lead-in rendered above the body (text-title-sm). |
|
|
30
|
+
| icon | string \| ReactNode \| null | — | Override the variant glyph: a string swaps the icon name (keeping the tone color), a ReactNode replaces it, null removes it; omit for the variant default. |
|
|
31
|
+
|
|
32
|
+
## Variants
|
|
33
|
+
|
|
34
|
+
- **tone**: neutral, info, success, warning, destructive
|
|
35
|
+
|
|
36
|
+
## Tokens used
|
|
37
|
+
|
|
38
|
+
`foreground`, `border`, `muted`, `muted-foreground`, `tone-info`, `tone-warning`, `success`, `destructive`
|
|
39
|
+
|
|
40
|
+
## Accessibility
|
|
41
|
+
|
|
42
|
+
- Rendered with role="note" so assistive tech can announce it as an aside.
|
|
43
|
+
- Color is never the sole signal — the leading icon and optional title reinforce severity.
|
|
44
|
+
|
|
45
|
+
## Related
|
|
46
|
+
|
|
47
|
+
- Components: Banner, InlineStatus, EmptyState
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### Info
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { Note } from "@sableui/react"
|
|
55
|
+
|
|
56
|
+
<Note tone="info">Editions mint on Solana mainnet.</Note>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### With title
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { Note } from "@sableui/react"
|
|
63
|
+
|
|
64
|
+
<Note tone="warning" title="Low balance">Top up SOL before minting.</Note>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Warning with a remedy, inside a dialog
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { Note } from "@sableui/react"
|
|
71
|
+
|
|
72
|
+
<Note tone="warning" action={{ label: "Unload chat model", onClick: unloadChatModel }}>gemma-4 is holding VRAM — generating now may be slow or fail.</Note>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Custom icon
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { Note } from "@sableui/react"
|
|
79
|
+
|
|
80
|
+
<Note tone="neutral" icon="lock">Pricing and supply cannot be changed after an edition is purchased.</Note>
|
|
81
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# NotificationBadge
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
An unread indicator. Renders a bare dot or a count pill (capped as "{max}+"), or nothing when there's nothing to show. overlay pins it to the corner of a relative host; it is aria-hidden, so the host's name carries the count.
|
|
6
|
+
|
|
7
|
+
**Keywords:** notification, badge, count, unread
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use NotificationBadge to show a compact unread count, alert count, or attention marker.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Place it on or near the item that owns the notifications.
|
|
16
|
+
- Do: Use compact counts and cap large numbers when exact precision is not useful.
|
|
17
|
+
- Don't: Do not use NotificationBadge as a standalone status label or category marker.
|
|
18
|
+
- Don't: Do not show a bare dot when the count is meaningful. A dot says "something"; if the user can act on "how many", show the number.
|
|
19
|
+
- Don't: Do not put a neutral badge on a secondary host. Both are the secondary surface, so the badge disappears; use primary or destructive there.
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Name | Type | Default | Description |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| dot | boolean | false | Render a bare dot indicator with no number. Takes precedence over count. |
|
|
26
|
+
| count | number | — | Unread count. Renders nothing when <= 0 and dot is unset. |
|
|
27
|
+
| max | number | 99 | Cap before showing "{max}+". |
|
|
28
|
+
| tone | "destructive" \| "primary" \| "neutral" | "destructive" | Fill color. destructive for alarming counts; primary/neutral for non-alarming ones. |
|
|
29
|
+
| size | "sm" \| "md" \| "lg" | "md" | Scales the dot/pill on the spacing scale (count pill: sm 16px, md 18px, lg 20px at the default density). |
|
|
30
|
+
| overlay | boolean | false | Pin to the top-end corner of the nearest relative host (an icon button) — one placement for every caller instead of hand-tuned absolute offsets. |
|
|
31
|
+
|
|
32
|
+
## Variants
|
|
33
|
+
|
|
34
|
+
- **tone**: destructive, primary, neutral
|
|
35
|
+
- **size**: sm, md, lg
|
|
36
|
+
- **overlay**: true, false
|
|
37
|
+
|
|
38
|
+
## Tokens used
|
|
39
|
+
|
|
40
|
+
`destructive`, `destructive-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- The badge is aria-hidden: a count rendered on a control is not associated with it, so the host needs an accessible name that includes the count — "Notifications, 3 unread" — or a screen-reader user hears "Notifications, button" and never learns there is anything new. Pass aria-hidden={false} only for a standalone badge whose number is the information.
|
|
45
|
+
- A dot carries no number and no words: on its own it tells a sighted user only that something changed, and tells everyone else nothing. Pair it with a label that says what is new, and never make its colour the only difference between two states.
|
|
46
|
+
|
|
47
|
+
## Related
|
|
48
|
+
|
|
49
|
+
- Components: Badge, StatusDot
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
### Dot + counts
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { NotificationBadge, Row } from "@sableui/react"
|
|
57
|
+
|
|
58
|
+
<Row gap={3}><NotificationBadge dot /><NotificationBadge count={3} /><NotificationBadge count={128} /></Row>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Overlay on an icon button
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { Button, Icon, NotificationBadge } from "@sableui/react"
|
|
65
|
+
|
|
66
|
+
<Button variant="secondary" size="icon" aria-label="Notifications, 5 unread" className="relative"><Icon name="bell" /><NotificationBadge count={5} overlay /></Button>
|
|
67
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# NumberField
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Numeric input with decrement/increment steppers over Base UI NumberField — keyboard arrows, scroll, scrub-to-change, min/max clamping, locale formatting, and form integration. Styled like Input; drops straight into a Field.
|
|
6
|
+
|
|
7
|
+
**Keywords:** number field, numeric input, stepper, form
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use NumberField for numeric values that benefit from validation, min/max bounds, or step controls.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for quantities, thresholds, percentages, and other real numeric values.
|
|
16
|
+
- Do: Set sensible min, max, and step behavior when the domain has bounds.
|
|
17
|
+
- Don't: Do not use NumberField for identifiers such as phone numbers, ZIP codes, or credit card numbers.
|
|
18
|
+
- Don't: Do not use it in a property grid with steppers left on. Its two 32px stepper buttons (36px each with their inset) put a ~110px floor on a control that needs ~44px there — pass steppers={false}, or use ScrubField.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| min | number | — | Lower bound. Clamps typing, arrows, scroll and scrub alike, so the value cannot leave the range by any route. |
|
|
25
|
+
| max | number | — | Upper bound, clamped on every input route the same way as `min`. |
|
|
26
|
+
| value / defaultValue | number | — | Controlled / uncontrolled numeric value. |
|
|
27
|
+
| onValueChange | (value: number \| null, event) => void | — | Fired when the value changes (null when cleared). |
|
|
28
|
+
| step / smallStep / largeStep | number | — | Increment amount; smallStep applies with the meta key, largeStep with shift. |
|
|
29
|
+
| format | Intl.NumberFormatOptions | — | Locale formatting (currency, percent, units, …). |
|
|
30
|
+
| steppers | boolean | true | Show the decrement / increment buttons. Turn off in a dense property grid — the two 32px stepper buttons (36px with their inset) put a ~110px floor on the control; without them the value left-aligns. |
|
|
31
|
+
| disabled / readOnly / required / name / id | — | — | Standard form-control props. Base UI applies id + required to the input, so it labels and validates correctly. |
|
|
32
|
+
|
|
33
|
+
## Tokens used
|
|
34
|
+
|
|
35
|
+
`input`, `card`, `foreground`, `muted-foreground`, `accent`, `accent-foreground`, `ring`
|
|
36
|
+
|
|
37
|
+
## Accessibility
|
|
38
|
+
|
|
39
|
+
- Decrement / Increment are labelled buttons (Decrease / Increase); the input is a spinbutton with aria-valuenow/min/max from Base UI.
|
|
40
|
+
- Routes aria-describedby / aria-invalid onto the input, so a Field's helper + error are announced for it.
|
|
41
|
+
|
|
42
|
+
## Related
|
|
43
|
+
|
|
44
|
+
- Components: Input, Slider
|
|
45
|
+
|
|
46
|
+
## Examples
|
|
47
|
+
|
|
48
|
+
### Bounded
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { NumberField } from "@sableui/react"
|
|
52
|
+
|
|
53
|
+
<NumberField defaultValue={1} min={0} max={99} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### In a field
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Field, NumberField } from "@sableui/react"
|
|
60
|
+
|
|
61
|
+
<Field label="Quantity"><NumberField min={1} defaultValue={1} /></Field>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Currency
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { NumberField } from "@sableui/react"
|
|
68
|
+
|
|
69
|
+
<NumberField defaultValue={9.99} format={{ style: "currency", currency: "USD" }} />
|
|
70
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# OtpField
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
One-time-code entry over Base UI's OTP Field — one slot per character, SMS autofill on the first slot, paste fills every slot, and only one tab stop for the whole code.
|
|
6
|
+
|
|
7
|
+
**Keywords:** otp, one-time code, one-time password, verification code, 2fa, two-factor, mfa, pin, passcode, input otp
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use OtpField for a short, fixed-length code the user copies from somewhere else — an SMS or email verification code, an authenticator app, a device pairing PIN. The slots show how long the code is and where the user is in it, which a plain Input cannot.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Set `length` to the exact length the server sends, and verify in `onValueComplete` (or with `autoSubmit` inside a form). Most users paste or autofill the whole code at once, so the moment of completion is the natural submit.
|
|
16
|
+
- Do: Label it through a `Field` ("Verification code") and say where the code was sent in the description. The first slot takes the field's name; the rest are announced by position.
|
|
17
|
+
- Do: Use `groupSize` for codes of 6 or more so the user can check the code in chunks against the message they are reading from.
|
|
18
|
+
- Don't: Do not use OtpField for a password, an arbitrary-length PIN, or anything the user types from memory. Slots promise a fixed length; use Input with `type="password"` and `autocomplete="current-password"` instead.
|
|
19
|
+
- Don't: Do not intercept paste or clear the field on a wrong code before the user has seen the error. Blocking paste breaks SMS autofill and password managers, and wiping the slots erases the evidence of which digit was wrong.
|
|
20
|
+
- Don't: Do not swap it for NumberField because the code is digits. A code is an identifier, not a quantity: NumberField strips leading zeros and offers steppers that make no sense here.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Root | yes | A role=group that holds the value, carries the field label and description, and reports completion. |
|
|
27
|
+
| Slot | yes | One input per character. The first carries autocomplete="one-time-code" and accepts the whole code. |
|
|
28
|
+
| Group gap | no | A decorative dash inserted every `groupSize` slots; hidden from assistive tech. |
|
|
29
|
+
|
|
30
|
+
## Props
|
|
31
|
+
|
|
32
|
+
| Name | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| length | number | 6 | Number of character slots. Completion fires when every slot is filled. |
|
|
35
|
+
| groupSize | number | — | Insert a visual gap every N slots (3 renders 123 – 456). The value stays one contiguous string. |
|
|
36
|
+
| value / defaultValue | string | — | Controlled / uncontrolled code. |
|
|
37
|
+
| onValueChange | (value: string, details) => void | — | Fires on every edit. `details.reason` is input-change, input-clear, input-paste or keyboard. |
|
|
38
|
+
| onValueComplete | (value: string, details) => void | — | Fires once every slot is filled — the place to verify the code. Also fires when a complete code is pasted over a complete value. |
|
|
39
|
+
| onValueInvalid | (attempted: string, details) => void | — | Fires when typed or pasted text contains characters `validationType` rejects, so you can explain the rejection rather than letting it vanish. |
|
|
40
|
+
| validationType | "numeric" \| "alpha" \| "alphanumeric" \| "none" | "numeric" | Which characters a slot accepts. Also picks the virtual keyboard (numeric → number pad). |
|
|
41
|
+
| autoSubmit | boolean | false | Submit the owning form when the code becomes complete. |
|
|
42
|
+
| mask | boolean | false | Render slots as password inputs. Only for codes that are secret on screen, such as a card PIN. |
|
|
43
|
+
| getSlotLabel | (index: number, length: number) => string | Character N of L | Accessible name for slots after the first. Override to localize. |
|
|
44
|
+
| aria-invalid | boolean | — | Marks every slot invalid (destructive border). `<Field error>` sets it for you. |
|
|
45
|
+
| disabled / readOnly / required / name / id / form | — | — | Standard form-control props. `id` lands on the first slot, so `<Field>` and `<label htmlFor>` name the field. |
|
|
46
|
+
|
|
47
|
+
## Tokens used
|
|
48
|
+
|
|
49
|
+
`input`, `card`, `foreground`, `muted-foreground`, `border-strong`, `primary`, `ring`, `destructive`
|
|
50
|
+
|
|
51
|
+
## Motion
|
|
52
|
+
|
|
53
|
+
- **arrival** · Root · _no motion_ — A code field is part of the form it sits in and appears with it. Animating it in would delay the one thing the user arrived to do — paste the code they just copied.
|
|
54
|
+
- **response** · Slot · `motion-interactive` — Focus moves between slots on every keystroke. The eased ring and border are what let the eye follow the cursor across six identical boxes instead of hunting for it after each digit.
|
|
55
|
+
- **transition** · Slot · `motion-interactive` — A filled slot strengthens its border, so a pasted code visibly lands in every slot at once. Easing it reads as the code arriving rather than the field flickering.
|
|
56
|
+
- **activity** · Root · _no motion_ — Verifying the code is the form's work, not the field's. Show it on the submit Button's `loading` state, where the user expects the answer to appear.
|
|
57
|
+
|
|
58
|
+
## Accessibility
|
|
59
|
+
|
|
60
|
+
- The root is a role=group named by the field label; the first slot shares the label through `id`/`htmlFor`, and later slots are announced by position ("Character 2 of 6").
|
|
61
|
+
- Only one slot is in the tab order. Arrow keys, Home/End and Backspace move between slots; Tab leaves the whole field.
|
|
62
|
+
- The first slot carries autocomplete="one-time-code" and the numeric virtual keyboard, so iOS and Android offer the SMS code above the keyboard.
|
|
63
|
+
- Slots render at 20px (`text-mono-lg`), above the 16px floor that stops iOS Safari zooming on focus.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Components: Input, Form, NumberField
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
### Six digits
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Field, OtpField } from "@sableui/react"
|
|
75
|
+
|
|
76
|
+
<Field label="Verification code"><OtpField length={6} onValueComplete={verify} /></Field>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### In a field, grouped
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Field, OtpField } from "@sableui/react"
|
|
83
|
+
|
|
84
|
+
<Field label="Verification code" description="Enter the 6-digit code we sent to your phone."><OtpField length={6} groupSize={3} /></Field>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### With an error
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Field, OtpField } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<Field label="Verification code" error="That code has expired. Request a new code and try again."><OtpField length={6} groupSize={3} defaultValue="482913" /></Field>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Alphanumeric
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Field, OtpField } from "@sableui/react"
|
|
99
|
+
|
|
100
|
+
<Field label="Pairing code"><OtpField length={8} groupSize={4} validationType="alphanumeric" /></Field>
|
|
101
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# PageHeader
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
In-flow page heading block: a title row (optional back control + actions) over an optional description, with room for children (filters, tabs) below.
|
|
6
|
+
|
|
7
|
+
**Keywords:** page header, title, lede, page actions
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use PageHeader to establish a page's title, supporting description, and primary page actions.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use one PageHeader per major page or route.
|
|
16
|
+
- Do: Keep the title literal and put explanation in the lede or nearby content.
|
|
17
|
+
- Don't: Do not use PageHeader inside repeated cards, dialogs, or compact panels.
|
|
18
|
+
- Don't: Do not put it outside the Region that holds the page content. It then sits at a different measure from what it introduces, and the page reads as two documents.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| title | ReactNode | — | The page title (renders an <h1>). |
|
|
25
|
+
| description | ReactNode | — | Supporting copy beneath the title. |
|
|
26
|
+
| back | ReactNode | — | Leading element before the title — typically a back control on drill-in routes. Keep it a ghost icon Button (size="icon", variant="ghost", aria-label="Back") so it stays quieter than the title. |
|
|
27
|
+
| actions | ReactNode | — | Trailing actions aligned to the title row's end. They drop under the title when the row runs out of room, and the title wraps rather than truncating. |
|
|
28
|
+
| titleAs | "h1" \| "h2" | "h1" | The title's heading element. Keep h1 — the route's one h1. Use h2 only when something above already owns the h1: a shell that renders the route title, or a page showing a PageHeader as an example. |
|
|
29
|
+
|
|
30
|
+
## Tokens used
|
|
31
|
+
|
|
32
|
+
`foreground`, `muted-foreground`
|
|
33
|
+
|
|
34
|
+
## Accessibility
|
|
35
|
+
|
|
36
|
+
- Renders the title as an <h1>; use one PageHeader per page.
|
|
37
|
+
- Its title renders the route's `h1`. Do not render another `h1` elsewhere on the page: heading navigation would report two documents, and the outline a screen-reader user builds from it stops matching the page.
|
|
38
|
+
|
|
39
|
+
## Related
|
|
40
|
+
|
|
41
|
+
- Components: AppHeader, Breadcrumbs, Button
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### With actions
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { Button, PageHeader } from "@sableui/react"
|
|
49
|
+
|
|
50
|
+
<PageHeader title="Storage credits" description="Fund permanent storage." actions={<Button>Top up</Button>} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### One per page, inside the region
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { Button, PageHeader, Region } from "@sableui/react"
|
|
57
|
+
|
|
58
|
+
// PageHeader introduces the current route: title, optional description, and the
|
|
59
|
+
// actions that act on THIS page.
|
|
60
|
+
|
|
61
|
+
<Region max="content">
|
|
62
|
+
<PageHeader
|
|
63
|
+
title="Invoices"
|
|
64
|
+
description="Every charge on this account."
|
|
65
|
+
actions={<Button>Export</Button>}
|
|
66
|
+
/>
|
|
67
|
+
<p className="text-body-md text-muted-foreground">Page content.</p>
|
|
68
|
+
</Region>
|
|
69
|
+
|
|
70
|
+
// ✗ Outside the Region. The header then sits at a different measure from the
|
|
71
|
+
// content it introduces, and the page reads as two documents.
|
|
72
|
+
// ✗ Two on one page. A second one is a section heading — an h2 in a
|
|
73
|
+
// smaller step (text-heading-3 or below), not another PageHeader.
|
|
74
|
+
// ✗ Global actions in it (account, search). Those persist across routes and
|
|
75
|
+
// belong in AppHeader.
|
|
76
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# PageLoader
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Full-screen branded loading splash: the screen fades in, the mark breathes, and an indeterminate Progress bar pulses beneath it. All motion is token-driven, so it freezes to a calm static screen when motion is off/reduced.
|
|
6
|
+
|
|
7
|
+
**Keywords:** page loader, loading, route, pending
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use PageLoader for full-route or full-page pending states where the entire view is not ready yet.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it during route transitions, auth checks, or initial data loading.
|
|
16
|
+
- Do: Prefer a clear page-level message when the wait may take noticeable time.
|
|
17
|
+
- Don't: Do not use PageLoader for small inline waits; use Spinner, an indeterminate Progress, or Skeleton.
|
|
18
|
+
- Don't: Do not leave it mounted if the route's data can fail. It covers the whole viewport, so a PageLoader that never resolves is indistinguishable from a hung application — always give it a failure path.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| children | ReactNode | — | Brand mark / logo shown at center. Falls back to a neutral pulsing mark. |
|
|
25
|
+
| label | string | "Loading" | Caption beneath the mark; also the accessible label on the role=status region. |
|
|
26
|
+
| fullscreen | boolean | true | Cover the viewport (fixed) vs fill the nearest positioned parent (absolute). |
|
|
27
|
+
| hideBar | boolean | false | Hide the indeterminate Progress bar under the mark. |
|
|
28
|
+
|
|
29
|
+
## Tokens used
|
|
30
|
+
|
|
31
|
+
`background`, `foreground`, `secondary`, `primary`, `muted-foreground`
|
|
32
|
+
|
|
33
|
+
## Accessibility
|
|
34
|
+
|
|
35
|
+
- role=status with an aria-label (the label prop) announces the loading state politely.
|
|
36
|
+
- Decorative mark and bar are aria-hidden.
|
|
37
|
+
- Motion respects the policy: under off/reduced the pulses hold their first frame (static).
|
|
38
|
+
|
|
39
|
+
## Related
|
|
40
|
+
|
|
41
|
+
- Components: Spinner, Skeleton, Progress
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### Default
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { PageLoader } from "@sableui/react"
|
|
49
|
+
|
|
50
|
+
<PageLoader label="Loading Studio" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With a logo
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { PageLoader } from "@sableui/react"
|
|
57
|
+
|
|
58
|
+
<PageLoader label="Loading"><img src="/logo.svg" alt="" className="h-10 w-auto" /></PageLoader>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Contained (fills parent)
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { PageLoader } from "@sableui/react"
|
|
65
|
+
|
|
66
|
+
<div className="relative h-80"><PageLoader fullscreen={false} hideBar /></div>
|
|
67
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Page navigation built from our own Button + Icon (no Base UI). A <nav aria-label='Pagination'> with prev/next icon buttons that disable at the bounds and numbered page buttons; the current page is a secondary button marked aria-current. Collapses to first/last/current plus a sibling window, bridging skipped runs with an ellipsis.
|
|
6
|
+
|
|
7
|
+
**Keywords:** pagination, pager, pages, table, results, navigation, next, previous
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Pagination when a result set is split into discrete pages and the user needs predictable movement through them. Pair it with DataTable, list results, or server-paged views; use a load-more Button or infinite scroll when page numbers are not meaningful.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Keep page state controlled by the parent route or data layer so URL, filters, and result fetches stay in sync.
|
|
16
|
+
- Do: Use a specific label when multiple pagers appear on a page, such as "Collector pages".
|
|
17
|
+
- Do: Reset to page 1 when filters or search terms materially change the result set.
|
|
18
|
+
- Don't: Use Pagination for tiny lists where all items fit comfortably on one page.
|
|
19
|
+
- Don't: Hide disabled previous/next buttons at the bounds; disabled controls make the current position clearer.
|
|
20
|
+
|
|
21
|
+
### Anatomy
|
|
22
|
+
|
|
23
|
+
| Part | Required | Description |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Nav | yes | The labelled pagination landmark. |
|
|
26
|
+
| Previous/next | yes | Icon buttons for adjacent page movement, disabled at the bounds. |
|
|
27
|
+
| Page buttons | yes | Numbered buttons with aria-current on the active page. |
|
|
28
|
+
| Ellipsis | no | Non-interactive marker for skipped page ranges. |
|
|
29
|
+
|
|
30
|
+
## Props
|
|
31
|
+
|
|
32
|
+
| Name | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| page | number | — | The active page (1-based). Clamped into the valid range. |
|
|
35
|
+
| total | number | — | Total number of pages. |
|
|
36
|
+
| onPageChange | (page: number) => void | — | Called with the next page when a control is activated. No-op for out-of-range or unchanged values. |
|
|
37
|
+
| siblingCount | number | 1 | Page buttons shown on each side of the current page before the list collapses into ellipses. First and last pages are always shown. |
|
|
38
|
+
| label | string | Pagination | Accessible label applied to the surrounding nav element. |
|
|
39
|
+
| className | string | — | Extra classes merged onto the nav. |
|
|
40
|
+
|
|
41
|
+
## Tokens used
|
|
42
|
+
|
|
43
|
+
`secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `ring`
|
|
44
|
+
|
|
45
|
+
## Accessibility
|
|
46
|
+
|
|
47
|
+
- Renders a landmark <nav> with aria-label so assistive tech can jump to the pager.
|
|
48
|
+
- The current page button carries aria-current='page'.
|
|
49
|
+
- Prev/next buttons are real <button>s with aria-label and become disabled at the bounds.
|
|
50
|
+
- Ellipsis slots are aria-hidden — they convey no navigable target.
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- Components: DataTable, Table, Button
|
|
55
|
+
- Patterns: ListDetailPage, DashboardPage
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Controlled
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { Pagination } from "@sableui/react"
|
|
63
|
+
|
|
64
|
+
const [page, setPage] = useState(1)
|
|
65
|
+
<Pagination page={page} total={10} onPageChange={setPage} />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Many pages (ellipsis)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Pagination } from "@sableui/react"
|
|
72
|
+
|
|
73
|
+
const [page, setPage] = useState(8)
|
|
74
|
+
<Pagination page={page} total={20} onPageChange={setPage} />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Table results footer
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { DataTable, Pagination, Stack } from "@sableui/react"
|
|
81
|
+
|
|
82
|
+
<Stack gap={2}><DataTable data={rows} columns={columns} rowKey={(row) => row.id} /><Pagination label="Collector pages" page={page} total={12} onPageChange={setPage} /></Stack>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Server query state
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { Pagination } from "@sableui/react"
|
|
89
|
+
|
|
90
|
+
const [page, setPage] = useState(searchParams.page ?? 1)
|
|
91
|
+
<Pagination page={page} total={pageCount} onPageChange={(next) => { setPage(next); loadPage(next) }} />
|
|
92
|
+
```
|