@sableui/react 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +221 -0
- package/DESIGN.md +548 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/THIRD_PARTY_NOTICES.md +44 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +1223 -0
- package/dist/_contract-CYScOdf5.d.ts +150 -0
- package/dist/chunk-3IWZYBGH.js +398 -0
- package/dist/chunk-5Z5KLOAU.js +167 -0
- package/dist/chunk-7MG4SHYX.js +15 -0
- package/dist/chunk-ADLLDWLW.js +15 -0
- package/dist/chunk-EP55KSYA.js +14 -0
- package/dist/chunk-J23ELQQZ.js +9396 -0
- package/dist/chunk-JI7WJP2L.js +286 -0
- package/dist/chunk-K7RUHUGC.js +14 -0
- package/dist/chunk-KO3NAEBF.js +15 -0
- package/dist/chunk-L4IEIQQA.js +14 -0
- package/dist/chunk-L6Y46C62.js +270 -0
- package/dist/chunk-NEQNXI2G.js +25 -0
- package/dist/chunk-PAOZBZME.js +40 -0
- package/dist/chunk-RWP4XBSK.js +139 -0
- package/dist/chunk-SRQWQUTW.js +63 -0
- package/dist/chunk-YXBOYWBN.js +15 -0
- package/dist/foundations/index.d.ts +88 -0
- package/dist/foundations/index.js +792 -0
- package/dist/icon/index.d.ts +60 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon-sets/brands.d.ts +44 -0
- package/dist/icon-sets/brands.js +147 -0
- package/dist/icon-sets/reicon.d.ts +8 -0
- package/dist/icon-sets/reicon.js +1 -0
- package/dist/index.d.ts +7013 -0
- package/dist/index.js +15462 -0
- package/dist/ios/chrome.css +83 -0
- package/dist/ios/frame.css +5 -0
- package/dist/ios/index.d.ts +70 -0
- package/dist/ios/index.js +313 -0
- package/dist/ios/preview.d.ts +64 -0
- package/dist/ios/preview.js +133 -0
- package/dist/ios/styles.css +77 -0
- package/dist/layout/index.d.ts +280 -0
- package/dist/layout/index.js +2 -0
- package/dist/meta-6dUzgC6M.d.ts +205 -0
- package/dist/patterns/index.d.ts +36 -0
- package/dist/patterns/index.js +934 -0
- package/dist/presets/index.d.ts +37 -0
- package/dist/presets/index.js +117 -0
- package/dist/registry-CyL28OzJ.d.ts +28 -0
- package/dist/styles.css +1893 -0
- package/dist/theme-provider-BWcizhAt.d.ts +105 -0
- package/dist/themes/atelier/atelier.css +213 -0
- package/dist/themes/atelier/index.d.ts +9 -0
- package/dist/themes/atelier/index.js +1 -0
- package/dist/themes/base/base.css +443 -0
- package/dist/themes/base/index.d.ts +9 -0
- package/dist/themes/base/index.js +1 -0
- package/dist/themes/ember/ember.css +270 -0
- package/dist/themes/ember/index.d.ts +9 -0
- package/dist/themes/ember/index.js +1 -0
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +8 -0
- package/dist/themes/meridian/index.d.ts +9 -0
- package/dist/themes/meridian/index.js +1 -0
- package/dist/themes/meridian/meridian.css +255 -0
- package/dist/themes/poppy/index.d.ts +6 -0
- package/dist/themes/poppy/index.js +1 -0
- package/dist/themes/poppy/poppy.css +174 -0
- package/dist/themes/prism/index.d.ts +6 -0
- package/dist/themes/prism/index.js +1 -0
- package/dist/themes/prism/prism.css +183 -0
- package/dist/themes/verdant/index.d.ts +9 -0
- package/dist/themes/verdant/index.js +1 -0
- package/dist/themes/verdant/verdant.css +210 -0
- package/dist/themes.css +30 -0
- package/llms/components/accordion.md +93 -0
- package/llms/components/app-header.md +70 -0
- package/llms/components/app-shell.md +117 -0
- package/llms/components/attachment.md +105 -0
- package/llms/components/avatar.md +141 -0
- package/llms/components/badge.md +117 -0
- package/llms/components/banner.md +119 -0
- package/llms/components/bottom-nav.md +80 -0
- package/llms/components/breadcrumbs.md +75 -0
- package/llms/components/browser.md +67 -0
- package/llms/components/bubble.md +119 -0
- package/llms/components/button.md +199 -0
- package/llms/components/calendar.md +75 -0
- package/llms/components/card.md +135 -0
- package/llms/components/carousel.md +87 -0
- package/llms/components/chat-composer.md +126 -0
- package/llms/components/checkbox-card.md +98 -0
- package/llms/components/checkbox.md +90 -0
- package/llms/components/code-block.md +81 -0
- package/llms/components/code.md +62 -0
- package/llms/components/collapsible.md +83 -0
- package/llms/components/color-field.md +100 -0
- package/llms/components/combobox.md +119 -0
- package/llms/components/command-menu.md +158 -0
- package/llms/components/context-card.md +73 -0
- package/llms/components/context-menu.md +79 -0
- package/llms/components/data-table.md +99 -0
- package/llms/components/date-picker.md +97 -0
- package/llms/components/description.md +96 -0
- package/llms/components/destructive-action-modal.md +76 -0
- package/llms/components/dialog.md +127 -0
- package/llms/components/dots-menu.md +64 -0
- package/llms/components/empty-state.md +113 -0
- package/llms/components/entity.md +67 -0
- package/llms/components/feedback.md +63 -0
- package/llms/components/fieldset.md +106 -0
- package/llms/components/file-dropzone.md +103 -0
- package/llms/components/form.md +111 -0
- package/llms/components/gliding-highlight.md +84 -0
- package/llms/components/inline-status.md +62 -0
- package/llms/components/input.md +127 -0
- package/llms/components/kbd.md +57 -0
- package/llms/components/label.md +73 -0
- package/llms/components/marker.md +99 -0
- package/llms/components/marquee.md +66 -0
- package/llms/components/menu.md +122 -0
- package/llms/components/message-scroller.md +110 -0
- package/llms/components/message.md +114 -0
- package/llms/components/meter.md +124 -0
- package/llms/components/middle-truncate.md +62 -0
- package/llms/components/note.md +81 -0
- package/llms/components/notification-badge.md +67 -0
- package/llms/components/number-field.md +70 -0
- package/llms/components/otp-field.md +101 -0
- package/llms/components/page-header.md +76 -0
- package/llms/components/page-loader.md +67 -0
- package/llms/components/pagination.md +92 -0
- package/llms/components/panel-group.md +120 -0
- package/llms/components/panel-section.md +122 -0
- package/llms/components/phone.md +70 -0
- package/llms/components/pill.md +73 -0
- package/llms/components/popover.md +79 -0
- package/llms/components/progress.md +98 -0
- package/llms/components/radio-card.md +90 -0
- package/llms/components/radio.md +86 -0
- package/llms/components/relative-time.md +60 -0
- package/llms/components/scroller.md +88 -0
- package/llms/components/scrub-field.md +97 -0
- package/llms/components/search-input.md +84 -0
- package/llms/components/select.md +106 -0
- package/llms/components/separator.md +70 -0
- package/llms/components/settings-menu.md +139 -0
- package/llms/components/sheet.md +97 -0
- package/llms/components/show-more.md +59 -0
- package/llms/components/side-nav.md +106 -0
- package/llms/components/sidebar.md +87 -0
- package/llms/components/skeleton.md +62 -0
- package/llms/components/slider.md +114 -0
- package/llms/components/spinner.md +87 -0
- package/llms/components/status-bar.md +91 -0
- package/llms/components/status-dot.md +70 -0
- package/llms/components/stepper.md +125 -0
- package/llms/components/streaming-text.md +86 -0
- package/llms/components/switch.md +87 -0
- package/llms/components/table.md +112 -0
- package/llms/components/tabs.md +84 -0
- package/llms/components/textarea.md +88 -0
- package/llms/components/theme-selector.md +95 -0
- package/llms/components/theme-switcher.md +71 -0
- package/llms/components/thinking-orb.md +91 -0
- package/llms/components/toast.md +130 -0
- package/llms/components/toggle.md +95 -0
- package/llms/components/toolbar.md +168 -0
- package/llms/components/tooltip.md +93 -0
- package/llms/components/tree.md +227 -0
- package/llms/components/video.md +66 -0
- package/llms/components/workspace.md +138 -0
- package/llms/foundations/accessibility.md +60 -0
- package/llms/foundations/motion.md +96 -0
- package/llms/foundations/scrolling.md +41 -0
- package/llms/foundations/shape.md +73 -0
- package/llms/foundations/spacing.md +82 -0
- package/llms/foundations/surfaces.md +69 -0
- package/llms/foundations/typography.md +120 -0
- package/llms/layout/aspect-box.md +37 -0
- package/llms/layout/bleed.md +31 -0
- package/llms/layout/center.md +37 -0
- package/llms/layout/columns.md +51 -0
- package/llms/layout/grid-overlay.md +40 -0
- package/llms/layout/grid.md +47 -0
- package/llms/layout/page.md +39 -0
- package/llms/layout/region.md +49 -0
- package/llms/layout/row.md +40 -0
- package/llms/layout/safe-area.md +37 -0
- package/llms/layout/spacer.md +39 -0
- package/llms/layout/split-pane.md +41 -0
- package/llms/layout/stack.md +39 -0
- package/llms/patterns/auth-page.md +68 -0
- package/llms/patterns/brand-landing-page.md +423 -0
- package/llms/patterns/chat-panel.md +210 -0
- package/llms/patterns/dashboard-page.md +170 -0
- package/llms/patterns/feed-page.md +103 -0
- package/llms/patterns/landing-page.md +122 -0
- package/llms/patterns/list-detail-page.md +100 -0
- package/llms/patterns/settings-page.md +121 -0
- package/llms/reference/token-intent.md +324 -0
- package/llms/reference/utilities.md +129 -0
- package/llms/tasks/adding-a-component.md +113 -0
- package/llms/tasks/auditing-an-app.md +216 -0
- package/llms/tasks/building-a-page.md +109 -0
- package/llms/tasks/building-a-tool.md +141 -0
- package/llms/tasks/building-an-ios-prototype.md +105 -0
- package/llms/tasks/building-with-sable.md +73 -0
- package/llms/tasks/choosing-a-component.md +109 -0
- package/llms/tasks/converting-an-app.md +139 -0
- package/llms/tasks/creating-a-preset.md +43 -0
- package/llms/tasks/creating-design-assets.md +96 -0
- package/llms/tasks/designing-a-surface.md +115 -0
- package/llms/tasks/developing-taste.md +157 -0
- package/llms/tasks/getting-started.md +63 -0
- package/llms/tasks/learning-from-builds.md +74 -0
- package/llms/tasks/reviewing-a-design.md +114 -0
- package/llms/tasks/reviewing-a-journey.md +84 -0
- package/llms/tasks/reviewing-motion.md +92 -0
- package/llms/tasks/using-presets.md +87 -0
- package/llms/taste/asset-plan.template.json +53 -0
- package/llms/taste/composition-follows-content.md +20 -0
- package/llms/taste/hierarchy-is-a-relationship.md +22 -0
- package/llms/taste/lessons.json +57 -0
- package/llms/taste/motion-needs-a-reason.md +21 -0
- package/llms/taste/surface-states-travel-together.md +20 -0
- package/llms-full.txt +14906 -0
- package/llms.txt +405 -0
- package/package.json +185 -3
- package/registry.json +18812 -0
- package/scripts/agent-setup.mjs +54 -0
- package/scripts/design-atlas.mjs +221 -0
- package/scripts/design-directions.mjs +123 -0
- package/scripts/design-memory.mjs +166 -0
- package/scripts/design-preferences.mjs +227 -0
- package/scripts/design-presets.mjs +48 -0
- package/scripts/grade.mjs +509 -0
- package/scripts/lint-rules.mjs +1065 -0
- package/scripts/review-evidence.mjs +120 -0
- package/scripts/skeleton.mjs +248 -0
- package/skills/entry/SKILL.md +34 -0
- package/skills/sable/SKILL.md +187 -0
- package/skills/sable/agents/openai.yaml +6 -0
- package/targets/ios.json +982 -0
- package/third-party/reicon-LICENSE.txt +20 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Stacked, collapsible disclosure sections over Base UI Accordion — roving focus, arrow-key navigation, ARIA region/heading/button wiring, and an animated height transition. Each item pairs a full-width trigger (with a rotating chevron) and a panel.
|
|
6
|
+
|
|
7
|
+
**Keywords:** accordion, disclosure, collapse, faq
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Accordion to reveal a vertical set of related sections while keeping the surrounding page scannable.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use concise trigger text that describes the content revealed below it.
|
|
16
|
+
- Do: Keep section content focused so expanding one item does not feel like navigating to a new page.
|
|
17
|
+
- Don't: Do not hide required fields, primary actions, or critical errors inside collapsed sections.
|
|
18
|
+
- Don't: Do not nest an Accordion inside an Accordion. Two layers of roving focus fight each other, and the arrow keys become ambiguous — flatten the hierarchy or use a Tree.
|
|
19
|
+
|
|
20
|
+
### Anatomy
|
|
21
|
+
|
|
22
|
+
| Part | Required | Description |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| AccordionItem | yes | One expandable section in the group. |
|
|
25
|
+
| AccordionTrigger | yes | The button-like control that opens the section. |
|
|
26
|
+
| AccordionPanel | yes | The content revealed by the trigger. |
|
|
27
|
+
|
|
28
|
+
## Subcomponents
|
|
29
|
+
|
|
30
|
+
| Name | Type | Description |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| AccordionItem | Base UI Accordion.Item props + className (value, disabled, onOpenChange) | Pairs a header with its panel. Give each a stable `value` to control or set initial open state. |
|
|
33
|
+
| AccordionTrigger | Base UI Accordion.Trigger props + className + indicator?: ReactNode | The header button (Header + Trigger composed). Renders children plus a chevron that rotates via data-[panel-open] by default; pass `indicator` to replace the icon and its open-state behavior entirely (e.g. a plus that becomes an × for a FAQ-style toggle). |
|
|
34
|
+
| AccordionPanel | Base UI Accordion.Panel props + className (keepMounted, hiddenUntilFound) | The collapsible body. Animates height using the --accordion-panel-height CSS var; fades on data-[starting-style]/data-[ending-style]. |
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Name | Type | Default | Description |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| Accordion (Root) | Base UI Accordion.Root props (value, defaultValue, multiple, disabled, onValueChange, orientation, loopFocus, keepMounted, hiddenUntilFound) | — | Groups all items and owns the open value(s). Uncontrolled via defaultValue, controlled via value. |
|
|
41
|
+
| defaultValue | Value[] | [] | Uncontrolled set of item values that start expanded (e.g. ['faq-1']). |
|
|
42
|
+
| value | Value[] | — | Controlled set of expanded item values; pair with onValueChange. |
|
|
43
|
+
| onValueChange | (value: Value[]) => void | — | Called with the new set of expanded item values when an item opens or closes. |
|
|
44
|
+
| multiple | boolean | false | Whether more than one item can be open at the same time. When false, opening one closes the others. |
|
|
45
|
+
|
|
46
|
+
## Tokens used
|
|
47
|
+
|
|
48
|
+
`border`, `foreground`, `muted-foreground`, `ring`
|
|
49
|
+
|
|
50
|
+
## Motion
|
|
51
|
+
|
|
52
|
+
- **arrival** · Root · _no motion_ — An accordion is page furniture that is present from first paint. Animating it in would stage content the reader is already scanning past.
|
|
53
|
+
- **response** · AccordionTrigger · `motion-interactive` — The whole header row is the hit target, and it looks like a heading rather than a control. The hover shift is what tells the reader it can be clicked before they click it.
|
|
54
|
+
- **transition** · AccordionPanel · `motion-disclosure` — Panel height is the only cue that content came from THIS header rather than appearing at the bottom of the page. Snapping it open moves everything below by a screenful with nothing connecting cause to effect.
|
|
55
|
+
- **activity** · Root · _no motion_ — Nothing here is indeterminate — the panel content is already resident. A panel that loads on expand should put its own loader inside the panel.
|
|
56
|
+
|
|
57
|
+
## Accessibility
|
|
58
|
+
|
|
59
|
+
- Base UI renders the trigger inside an <h3> heading and wires aria-expanded / aria-controls to the panel region.
|
|
60
|
+
- Arrow keys move roving focus between triggers; Home/End jump to the first/last; loopFocus wraps by default.
|
|
61
|
+
- Each trigger toggles its panel with Enter/Space; the panel is announced as a labelled region.
|
|
62
|
+
- A disabled AccordionItem dims and is skipped by keyboard focus while neighbouring items stay interactive.
|
|
63
|
+
- The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible — both honor reduced-motion via the consumer's transitions.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Components: Collapsible, Tabs
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
### Single (default)
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "@sableui/react"
|
|
75
|
+
|
|
76
|
+
<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>How much?</AccordionTrigger><AccordionPanel>A one-time mint fee.</AccordionPanel></AccordionItem></Accordion>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Multiple open
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger } from "@sableui/react"
|
|
83
|
+
|
|
84
|
+
<Accordion multiple defaultValue={["a", "b"]}><AccordionItem value="a"><AccordionTrigger>First</AccordionTrigger><AccordionPanel>One.</AccordionPanel></AccordionItem><AccordionItem value="b"><AccordionTrigger>Second</AccordionTrigger><AccordionPanel>Two.</AccordionPanel></AccordionItem></Accordion>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Custom indicator (plus-becomes-×)
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, Icon } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground transition-transform [[data-panel-open]_&]:rotate-45" />}>Is it permanent?</AccordionTrigger><AccordionPanel>Yes — stored on Arweave.</AccordionPanel></AccordionItem></Accordion>
|
|
93
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# AppHeader
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Mobile top bar (sticky, hidden at lg) with leading / title / actions slots on a 3-column grid that keeps the title optically centered. Grows into the top safe-area.
|
|
6
|
+
|
|
7
|
+
**Keywords:** app header, top bar, mobile header, chrome
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use AppHeader for compact application chrome at the top of a screen, usually with navigation, identity, and a small action set. Pass it to AppShell's `header` slot so screen content clears it on mobile (it is sticky and hides itself at lg).
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Keep the header actions narrow and predictable so the page title remains legible.
|
|
16
|
+
- Do: Use it once per app view as chrome, not as repeated content inside page sections.
|
|
17
|
+
- Don't: Do not overload it with filters, forms, or page-specific controls that belong in the content area.
|
|
18
|
+
- Don't: Do not render more than one per app, or put a second one inside a route. It is sticky chrome, so two of them stack and permanently eat the top of the viewport.
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Name | Type | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| leading | ReactNode | — | Leading slot — back button, create action, or menu trigger. |
|
|
25
|
+
| title | ReactNode | — | Center slot — brand wordmark or contextual page title. |
|
|
26
|
+
| actions | ReactNode | — | Trailing slot — wallet trigger, action buttons. |
|
|
27
|
+
|
|
28
|
+
## Tokens used
|
|
29
|
+
|
|
30
|
+
`background`, `border`, `foreground`
|
|
31
|
+
|
|
32
|
+
## Accessibility
|
|
33
|
+
|
|
34
|
+
- Renders a <header> landmark.
|
|
35
|
+
- Pair with a leading control that has an accessible label (e.g. a BackButton).
|
|
36
|
+
|
|
37
|
+
## Related
|
|
38
|
+
|
|
39
|
+
- Components: AppShell, PageHeader, BottomNav
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
### Contextual header
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { AppHeader, Button, Icon } from "@sableui/react"
|
|
47
|
+
|
|
48
|
+
<AppHeader leading={<Button size="icon" variant="ghost" aria-label="Back"><Icon name="chevron-left" /></Button>} title="Notifications" />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Choosing between AppHeader and PageHeader
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { AppHeader, AppShell, PageHeader, Region } from "@sableui/react"
|
|
55
|
+
|
|
56
|
+
// AppHeader is APP chrome — it persists across routes and holds global
|
|
57
|
+
// affordances (brand, search, account). One per app.
|
|
58
|
+
<AppShell header={<AppHeader title="Studio" />}>
|
|
59
|
+
<Region max="content">Route content</Region>
|
|
60
|
+
</AppShell>
|
|
61
|
+
|
|
62
|
+
// PageHeader is CONTENT — it introduces the current route and scrolls away
|
|
63
|
+
// with it. One per page, INSIDE the region.
|
|
64
|
+
<Region max="content">
|
|
65
|
+
<PageHeader title="Billing" description="Plans, invoices, payment methods." />
|
|
66
|
+
</Region>
|
|
67
|
+
|
|
68
|
+
// ✗ Putting route-specific actions in AppHeader. They then persist onto routes
|
|
69
|
+
// where they mean nothing, and the header becomes a dumping ground.
|
|
70
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# AppShell
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
The application frame: slots for status banners, a mobile header, a desktop sidebar, main content, a mobile bottom nav, and floating overlays — plus a built-in skip link and #main-content target.
|
|
6
|
+
|
|
7
|
+
**Keywords:** app shell, layout, frame, sidebar, header, bottom nav, mobile, banners, main
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use AppShell as the outer product frame for authenticated or app-like experiences. It coordinates responsive chrome slots and guarantees a skip link plus main landmark; put route content inside the children and let AppHeader, Sidebar, BottomNav, and Banner own their own semantics.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use AppShell once around the route tree or major app area so the skip link and main landmark stay stable.
|
|
16
|
+
- Do: Pass app-wide outage or status messages through banners so they remain above the frame.
|
|
17
|
+
- Do: Use Region inside the main content to choose each route's reading width.
|
|
18
|
+
- Do: Wrap EVERY return of a route in its own Page or Region — including early returns for loading, empty and error states. AppShell's <main> deliberately carries no padding (it is a frame, and padding belongs to the region that chose the width), so a route that returns a bare spinner or error renders flush against the chrome. This is the most common conversion bug: the happy path gets a Page, the three early returns do not.
|
|
19
|
+
- Don't: Nest AppShell inside route cards or panels. It is page chrome, not local content.
|
|
20
|
+
- Don't: Put desktop-only navigation in the mobile header slot or mobile-only controls in the sidebar slot.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Banners | no | Full-width app status messages rendered above the shell. |
|
|
27
|
+
| Header | no | Mobile top chrome, usually AppHeader. |
|
|
28
|
+
| Sidebar | no | Desktop navigation chrome, usually Sidebar. |
|
|
29
|
+
| Main | yes | The route content landmark with id main-content. |
|
|
30
|
+
| Bottom nav | no | Mobile fixed navigation with reserved bottom space. |
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
| Name | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| banners | ReactNode | — | Full-width status banners pinned in flow above the frame (offline / chain-down). |
|
|
37
|
+
| header | ReactNode | — | Mobile top header (e.g. AppHeader); expected to hide itself at lg. |
|
|
38
|
+
| sidebar | ReactNode | — | Desktop side navigation (e.g. Sidebar); expected to hide itself below lg. Leading column of the body row. |
|
|
39
|
+
| bottomNav | ReactNode | — | Mobile bottom navigation (e.g. BottomNav); fixed to the bottom edge. main reserves space for it. |
|
|
40
|
+
| overlays | ReactNode | — | Floating overlays — a message launcher, FAB, or modal mount points. |
|
|
41
|
+
|
|
42
|
+
## Tokens used
|
|
43
|
+
|
|
44
|
+
`background`, `foreground`, `primary`, `primary-foreground`
|
|
45
|
+
|
|
46
|
+
## Accessibility
|
|
47
|
+
|
|
48
|
+
- Renders a skip link to #main-content as the first focusable element.
|
|
49
|
+
- Wraps content in a <main id="main-content"> landmark.
|
|
50
|
+
- Slot components own their own ARIA (nav landmarks, labels).
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- Components: AppHeader, Sidebar, SideNav, BottomNav, Banner
|
|
55
|
+
- Patterns: DashboardPage, FeedPage, SettingsPage
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### Full frame
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { AppHeader, AppShell, Banner, BottomNav, BottomNavItem, Icon, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
|
|
63
|
+
// <Link> = your router's link (react-router, next/link, @tanstack/router, …)
|
|
64
|
+
|
|
65
|
+
<AppShell
|
|
66
|
+
banners={<Banner tone="warning">Network congested — mints may be slow.</Banner>}
|
|
67
|
+
header={<AppHeader title="Studio" />}
|
|
68
|
+
sidebar={
|
|
69
|
+
<Sidebar>
|
|
70
|
+
<SidebarNav>
|
|
71
|
+
<SidebarItem icon={<Icon name="home" />} label="Home" active render={<Link to="/" />} />
|
|
72
|
+
<SidebarItem icon={<Icon name="bell" />} label="Notifications" render={<Link to="/notifications" />} />
|
|
73
|
+
<SidebarItem icon={<Icon name="gear" />} label="Settings" render={<Link to="/settings" />} />
|
|
74
|
+
</SidebarNav>
|
|
75
|
+
</Sidebar>
|
|
76
|
+
}
|
|
77
|
+
bottomNav={
|
|
78
|
+
<BottomNav>
|
|
79
|
+
<BottomNavItem icon={<Icon name="home" />} label="Home" active render={<Link to="/" />} />
|
|
80
|
+
<BottomNavItem icon={<Icon name="bell" />} label="Alerts" render={<Link to="/notifications" />} />
|
|
81
|
+
</BottomNav>
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<Region max="feed">
|
|
85
|
+
<PageHeader title="Home" description="Latest posts from artists you follow." />
|
|
86
|
+
</Region>
|
|
87
|
+
</AppShell>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Minimal (sidebar + content only)
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { AppShell, Icon, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
|
|
94
|
+
// <Link> = your router's link (react-router, next/link, @tanstack/router, …)
|
|
95
|
+
|
|
96
|
+
<AppShell
|
|
97
|
+
sidebar={
|
|
98
|
+
<Sidebar>
|
|
99
|
+
<SidebarNav>
|
|
100
|
+
<SidebarItem icon={<Icon name="home" />} label="Dashboard" active render={<Link to="/" />} />
|
|
101
|
+
</SidebarNav>
|
|
102
|
+
</Sidebar>
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
<Region max="wide">
|
|
106
|
+
<PageHeader title="Dashboard" />
|
|
107
|
+
</Region>
|
|
108
|
+
</AppShell>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Route content width
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { AppShell, Card, CardContent, CardHeader, CardTitle, Field, Icon, Input, PageHeader, Region, Sidebar, SidebarItem, SidebarNav } from "@sableui/react"
|
|
115
|
+
|
|
116
|
+
<AppShell sidebar={<Sidebar><SidebarNav><SidebarItem icon={<Icon name="home" />} label="Home" active /></SidebarNav></Sidebar>}><Region max="content"><PageHeader title="Settings" description="Manage account and workspace preferences." /><Card><CardHeader><CardTitle>Profile</CardTitle></CardHeader><CardContent><Field label="Display name"><Input defaultValue="Nova" /></Field></CardContent></Card></Region></AppShell>
|
|
117
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Attachment
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A file or image attached to a message: media or icon lead, name and metadata, upload and error states, an actions slot, and a full-card trigger that keeps those actions separately clickable.
|
|
6
|
+
|
|
7
|
+
**Keywords:** attachment, file, upload, image preview, chat file, document, drag and drop, file card
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
The card a file takes in a conversation — in the composer while it uploads, and in the transcript once it has. It renders all four states an attachment actually moves through (queued, uploading, failed, delivered) rather than only the delivered one, because the other three are the states that are hard to get right and would otherwise fall back on every caller.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use `variant="media"` for images and `variant="card"` for documents. A filename tells you nothing about a photo, and a thumbnail tells you nothing about a contract.
|
|
16
|
+
- Do: Pass `onOpen` rather than wrapping the card in your own button. It renders a stretched trigger underneath the actions, so the card is one tab stop, each action is its own, and clicking "remove" never opens the file.
|
|
17
|
+
- Do: Give `status="uploading"` a numeric `progress` when you have one and omit it when you do not — the bar goes indeterminate rather than lying about 0%.
|
|
18
|
+
- Do: Put the reason in `meta` when `status="error"`. The card re-colors but keeps its shape, so a failed upload stays recognisable as the file it was — the text is what says why.
|
|
19
|
+
- Don't: Do not nest interactive controls inside a card you have wrapped in a link or button yourself. That is invalid HTML and unreachable in the order it looks like; `onOpen` exists to avoid it.
|
|
20
|
+
- Don't: Do not use it as a generic file-list row. `Tree` is the file listing; this one is shaped for a conversation, where an attachment is a message payload.
|
|
21
|
+
- Don't: Do not rely on the error colour alone to signal failure. Colour is not a status for anyone who cannot see it — say what happened in `meta`.
|
|
22
|
+
|
|
23
|
+
### Anatomy
|
|
24
|
+
|
|
25
|
+
| Part | Required | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Media | yes | Thumbnail crop or file glyph. Fills the tile in `media`, a lead square in `card`. |
|
|
28
|
+
| Label | yes | Name and metadata. A row beside the media in `card`; a scrim strip over the picture in `media`, shown only when there is something to say. |
|
|
29
|
+
| Actions | no | Remove / retry / download controls, stacked above the stretched trigger so they stay separately clickable. |
|
|
30
|
+
| Progress | no | A hairline bar along the bottom edge while `status="uploading"`; determinate with `progress`, indeterminate without. |
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
| Name | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| name | string | — | File name. Truncated rather than wrapped — the stem truncates and a short extension stays visible — and used as the default label for the stretched trigger and the media tile's alt text. |
|
|
37
|
+
| meta | ReactNode | — | Size, type, upload progress, or the failure reason — the quiet second line. |
|
|
38
|
+
| thumbnail | string | — | Image preview URL. Present means the card shows a crop of the file instead of a glyph. |
|
|
39
|
+
| icon | string | "file" | Semantic icon name for a non-image file (resolved through IconProvider). Defaults to "image-stack" when a thumbnail is set. |
|
|
40
|
+
| variant | "card" \| "media" | "card" | `card` is a name-led horizontal row for documents; `media` is a square tile that is mostly picture. |
|
|
41
|
+
| status | "idle" \| "uploading" \| "error" | "idle" | Transfer state. `uploading` adds a progress bar; `error` re-colors the card without changing its shape. |
|
|
42
|
+
| progress | number | — | Percentage 0–100 while uploading. Omit for an indeterminate bar. |
|
|
43
|
+
| actions | ReactNode | — | Remove / retry / download controls. Stay clickable when `onOpen` makes the card a trigger. On a `media` tile they float on the top-end corner over the picture, so pass `variant="floating"` buttons there. |
|
|
44
|
+
| onOpen | () => void | — | Makes the whole card activate, via a stretched button rendered beneath the actions rather than by wrapping the card. |
|
|
45
|
+
| openLabel | string | `Open ${name}` | Accessible label for that stretched trigger. |
|
|
46
|
+
| ...div props | Omit<ComponentPropsWithRef<"div">, "onClick"> | — | Spread onto the card. Use `onOpen` rather than `onClick` — a click handler on a div is not a control. |
|
|
47
|
+
|
|
48
|
+
## Variants
|
|
49
|
+
|
|
50
|
+
- **variant**: card, media
|
|
51
|
+
- **status**: idle, uploading, error
|
|
52
|
+
|
|
53
|
+
## Tokens used
|
|
54
|
+
|
|
55
|
+
`card`, `border`, `border-strong`, `muted`, `muted-foreground`, `foreground`, `destructive`, `overlay`, `overlay-foreground`, `ring`
|
|
56
|
+
|
|
57
|
+
## Accessibility
|
|
58
|
+
|
|
59
|
+
- The full-card trigger is a real stretched `<button>` with an accessible label, not a click handler on a div — so it is one tab stop, keyboard-activatable, and focus-ringed.
|
|
60
|
+
- Actions sit above that trigger in the stacking order, which keeps them individually focusable and stops a click on `remove` from also opening the file. Text is only made click-through when a trigger is present, so a filename stays selectable otherwise.
|
|
61
|
+
- A thumbnail is described as the attachment in `media` (where the picture is the only label) and left decorative in `card` (where the filename is right beside it), so AT never reads the same name twice.
|
|
62
|
+
- The upload bar carries an accessible name via Progress; the error state is carried by text in `meta` as well as colour.
|
|
63
|
+
- Label text on a media tile sits on `overlay` / `overlay-foreground` — a fixed pairing that stays legible over arbitrary imagery, where a theme surface token would not.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Components: Message, Bubble, MessageScroller, Progress
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
### Document with a remove action
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Attachment, Button, Icon } from "@sableui/react"
|
|
75
|
+
|
|
76
|
+
<Attachment name="proposal.pdf" meta="2.4 MB · PDF" onOpen={() => open(file)} actions={<Button variant="ghost" size="icon-xs" aria-label="Remove"><Icon name="xmark" /></Button>} />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Image tile, uploading
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Attachment } from "@sableui/react"
|
|
83
|
+
|
|
84
|
+
<Attachment variant="media" name="cover.png" thumbnail={previewUrl} status="uploading" progress={62} meta="Uploading… 62%" />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Failed upload with a retry
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Attachment, Button } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<Attachment name="recording.mov" meta="Upload failed — file is over the 50 MB limit" status="error" icon="warning" actions={<Button variant="ghost" size="xs" onClick={retry}>Retry</Button>} />
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Attachments as a message payload
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Attachment, Message, RelativeTime } from "@sableui/react"
|
|
99
|
+
|
|
100
|
+
<Message messageId="m7" align="end" footer={<RelativeTime date={sentAt} />}>
|
|
101
|
+
<div className="flex flex-wrap gap-2">
|
|
102
|
+
{files.map((f) => (<Attachment key={f.id} variant="media" name={f.name} thumbnail={f.url} onOpen={() => open(f)} />))}
|
|
103
|
+
</div>
|
|
104
|
+
</Message>
|
|
105
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Circular profile image over Base UI Avatar — automatic image-load tracking with an initials/glyph fallback. Ergonomic Avatar plus low-level AvatarRoot/AvatarImage/AvatarFallback parts, and AvatarGroup for an overlapping stack with a "+N" overflow count.
|
|
6
|
+
|
|
7
|
+
**Keywords:** avatar, profile, user, identity, initials, fallback, group, team, stack, overflow, collaborators, facepile
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Avatar to represent a person, team, or account with a compact visual identity. Wrap several in AvatarGroup for collaborators, assignees, or attendees — it overlaps them, rings each in the surface colour, and collapses the rest into a "+N" count; use the low-level parts only when you need custom image/fallback layout.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Provide meaningful alt text for profile images, or an empty alt when the adjacent text already names the person.
|
|
16
|
+
- Do: Use initials or a simple glyph fallback so missing images still communicate identity.
|
|
17
|
+
- Do: Use consistent sizes within a group; reserve lg for profile headers or major identity moments.
|
|
18
|
+
- Do: Use AvatarGroup for a stack of people, and give it an aria-label with the real count ("12 collaborators"). Set `surface` (or `--avatar-group-ring`) to whatever the group sits on, so the separating ring matches a card or popover, not the page. When it shares a row with actions, set `fit` so it gives up avatars to the "+N" count before the row has to wrap.
|
|
19
|
+
- Don't: Hand-build a stack with negative margins and per-avatar ring classes, or fake the overflow as an Avatar with "+4" initials — screen readers hear "plus 4" with no context. Use AvatarGroup's `max`/`total`, or AvatarGroupCount.
|
|
20
|
+
- Don't: Use avatars as decoration without nearby identity context. They should help users recognize a person or account.
|
|
21
|
+
- Don't: Overload a dense table with large avatars; use sm or omit images when scanning data is more important.
|
|
22
|
+
|
|
23
|
+
### Anatomy
|
|
24
|
+
|
|
25
|
+
| Part | Required | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Root | yes | The circular frame that clips the image, owns size, and contains image plus fallback. |
|
|
28
|
+
| Image | no | The loaded profile image, cropped to fill the root. |
|
|
29
|
+
| Fallback | yes | Initials, a glyph, or short text shown while loading, on error, or when no image exists. |
|
|
30
|
+
| Group | no | AvatarGroup: the overlapping stack. Sets one size for every avatar and rings each in the surface colour. |
|
|
31
|
+
| Count | no | AvatarGroupCount: the "+N" avatar ending a group, read as "and N more". Rendered automatically by `max`/`total`. |
|
|
32
|
+
|
|
33
|
+
## Subcomponents
|
|
34
|
+
|
|
35
|
+
| Name | Type | Description |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| AvatarRoot | Base UI Avatar.Root props + size + className | Low-level circular frame for full composition control — clips the image and holds the `size` axis. Wraps AvatarImage and AvatarFallback. |
|
|
38
|
+
| AvatarImage | Base UI Avatar.Image props + className | The profile image. Fills and crops the frame; hidden by Base UI until it loads. |
|
|
39
|
+
| AvatarFallback | Base UI Avatar.Fallback props (delay) + className | Shown when no image is available — typically initials or a glyph. Accepts a delay prop. |
|
|
40
|
+
| AvatarGroup | { size?: "sm" \| "md" \| "lg"; surface?: "background" \| "card" \| "popover" \| "muted"; max?: number; fit?: boolean; total?: number; countLabel?: (count: number) => string; "aria-label"?: string; className?: string; children: ReactNode } | Overlapping stack (role=group) — name it with aria-label ("12 collaborators"). `size` applies to every avatar inside. `max` shows that many and collapses the rest into a "+N" count; `total` is the real head-count when you only pass some people. `fit` shows only as many avatars as fit the width the group is given (it fills the space left in a flex row) and folds the rest into the count, so a tight row reads "MO SR +5" instead of wrapping. The separating ring reads the inherited `--avatar-group-ring` (default: the page background); `surface` sets it to card, popover, or muted. |
|
|
41
|
+
| AvatarGroupCount | { count: number; label?: string; size?: "sm" \| "md" \| "lg"; className?: string } | The "+N" avatar. Shows "+4" and reads "and 4 more" (override with label). AvatarGroup renders it for you from max/total; use it directly only when composing the stack yourself. |
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Name | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| src | string | — | Image source. When omitted, still loading, or failed, the fallback shows. |
|
|
48
|
+
| alt | string | "" | Alt text for the image. Without an image it becomes the avatar's accessible name (role=img), so the initials are never read out as the name. |
|
|
49
|
+
| fallback | ReactNode | — | Initials or a glyph rendered when no image is displayed. |
|
|
50
|
+
| fallbackDelay | number | — | Delay before showing the fallback, in ms — avoids a flash on fast loads. |
|
|
51
|
+
| status | ReactNode | — | A presence indicator pinned to the bottom-end edge, usually <StatusDot tone="success" />. It sits outside the clipped circle and wears a ring in the surface colour (--avatar-group-ring, else the page background). Use this instead of absolutely positioning a dot over an Avatar. |
|
|
52
|
+
| size | "sm" \| "md" \| "lg" | "md" | Square footprint: 32px / 40px / 48px. |
|
|
53
|
+
|
|
54
|
+
## Variants
|
|
55
|
+
|
|
56
|
+
- **size**: sm, md, lg
|
|
57
|
+
|
|
58
|
+
## Tokens used
|
|
59
|
+
|
|
60
|
+
`muted`, `muted-foreground`, `background`, `card`, `popover`
|
|
61
|
+
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
- Base UI tracks the image loading status and swaps to the fallback on missing/loading/error — no manual onError wiring.
|
|
65
|
+
- Provide meaningful alt text via the alt prop; pass an empty alt for decorative avatars.
|
|
66
|
+
- The fallback (initials/glyph) gives a non-image identity cue for screen readers and slow connections.
|
|
67
|
+
- AvatarGroup renders role=group; give it an aria-label naming the whole set ("12 collaborators"), since the visible stack only shows some of them.
|
|
68
|
+
- The "+N" count is hidden from screen readers and replaced by "and N more" (AvatarGroupCount `label`, or AvatarGroup `countLabel`).
|
|
69
|
+
|
|
70
|
+
## Related
|
|
71
|
+
|
|
72
|
+
- Components: Entity, StatusDot, Badge, Row
|
|
73
|
+
- Patterns: DashboardPage, ListDetailPage
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
### With image
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { Avatar } from "@sableui/react"
|
|
81
|
+
|
|
82
|
+
<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Fallback only
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { Avatar } from "@sableui/react"
|
|
89
|
+
|
|
90
|
+
<Avatar fallback="AM" alt="Alex Morgan" />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Sizes
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { Avatar } from "@sableui/react"
|
|
97
|
+
|
|
98
|
+
<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="sm" />
|
|
99
|
+
<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="md" />
|
|
100
|
+
<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="lg" />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Low-level parts
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { AvatarFallback, AvatarImage, AvatarRoot } from "@sableui/react"
|
|
107
|
+
|
|
108
|
+
<AvatarRoot size="lg"><AvatarImage src="/pfp.png" alt="Mara Okafor" /><AvatarFallback>MO</AvatarFallback></AvatarRoot>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Group with overflow
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { Avatar, AvatarGroup } from "@sableui/react"
|
|
115
|
+
|
|
116
|
+
<AvatarGroup aria-label="6 collaborators" max={3} size="sm" surface="card"><Avatar src="/ana.png" alt="Ana Chen" fallback="AC" /><Avatar src="/mika.png" alt="Mika Lee" fallback="ML" /><Avatar fallback="JL" alt="Jo Lund" /><Avatar fallback="AK" alt="Ari Kim" /><Avatar fallback="SR" alt="Sam Reyes" /><Avatar fallback="MO" alt="Mo Ortiz" /></AvatarGroup>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Group of a larger set
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { Avatar, AvatarGroup } from "@sableui/react"
|
|
123
|
+
|
|
124
|
+
<AvatarGroup aria-label="12 attendees" total={12} countLabel={(n) => `and ${n} more attendees`}><Avatar fallback="MO" alt="Mo Ortiz" /><Avatar fallback="SR" alt="Sam Reyes" /><Avatar fallback="JL" alt="Jo Lund" /></AvatarGroup>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Composed count
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { Avatar, AvatarGroup, AvatarGroupCount } from "@sableui/react"
|
|
131
|
+
|
|
132
|
+
<AvatarGroup aria-label="7 reviewers"><Avatar fallback="AC" alt="Ana Chen" /><Avatar fallback="ML" alt="Mika Lee" /><AvatarGroupCount count={5} label="and 5 more reviewers" /></AvatarGroup>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### With status
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { Avatar, Entity, StatusDot } from "@sableui/react"
|
|
139
|
+
|
|
140
|
+
<Entity leading={<Avatar src="/pfp.png" alt="Nova Reed" fallback="NR" status={<StatusDot tone="success" />} />} title="Nova Reed" subtitle="Online" />
|
|
141
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Compact static label, set on two axes: appearance (soft, outline, solid, overlay on media, plain text) and tone (neutral or a semantic tone). An optional leading icon, or a status dot (with pulse) for state.
|
|
6
|
+
|
|
7
|
+
**Keywords:** badge, tag, label, status, metadata, chip, pill, tone, category
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Badge for any short static label: metadata, categories, tone-coded labels, a label on artwork (appearance="overlay"), or a status line (appearance="plain" with dot). Use Pill for a chip someone clicks, NotificationBadge for an unread count on an icon, and Banner or Toast for messages that need attention.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Keep badge text short: one to three words or a compact count.
|
|
16
|
+
- Do: Use tone variants when the badge carries meaning, such as success, warning, destructive, edition, or collectible.
|
|
17
|
+
- Do: Pair icons with text; the icon reinforces meaning but should not be the only cue.
|
|
18
|
+
- Do: Choose appearance and tone separately: appearance is how loud it is (soft by default, solid for emphasis, overlay on images, plain for a status line); tone is what it means.
|
|
19
|
+
- Don't: Use a Badge as a button. If it triggers an action, use Button, Toggle, or a menu item.
|
|
20
|
+
- Don't: Stack many loud tone badges in one row; too many semantic colors compete with the actual content.
|
|
21
|
+
|
|
22
|
+
### Anatomy
|
|
23
|
+
|
|
24
|
+
| Part | Required | Description |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Container | yes | The compact pill surface carrying the tone or neutral treatment. |
|
|
27
|
+
| Label | yes | Short visible text that names the category, state, role, or metadata value. |
|
|
28
|
+
| Icon | no | Optional leading Icon used to reinforce the label. |
|
|
29
|
+
|
|
30
|
+
## Props
|
|
31
|
+
|
|
32
|
+
| Name | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| appearance | "soft" \| "outline" \| "solid" \| "overlay" \| "plain" | "soft" | How it is drawn. soft: a tinted fill (neutral is the secondary surface). outline: a hairline. solid: the tone at full strength (neutral is the inverted chip). overlay: legible on images — a dark scrim, or the solid tone. plain: no fill or padding, a status line. |
|
|
35
|
+
| tone | "neutral" \| "standard" \| "collectible" \| "edition" \| "info" \| "success" \| "warning" \| "destructive" | "neutral" | What it means. Every appearance takes every tone. |
|
|
36
|
+
| size | "sm" \| "md" | "md" | "sm" uses tighter padding + text-label-sm; "md" is the default look. |
|
|
37
|
+
| icon | ReactNode | — | Optional leading glyph (e.g. <Icon name="lock" />), rendered before the children with the base gap. Ignored when dot is set. |
|
|
38
|
+
| dot | boolean | false | A leading status dot in the badge's tone. With appearance="plain" it is a status line (● Ready). |
|
|
39
|
+
| pulse | boolean | false | Pulse the dot, for a state in progress. Needs dot. |
|
|
40
|
+
|
|
41
|
+
## Variants
|
|
42
|
+
|
|
43
|
+
- **appearance**: soft, outline, solid, overlay, plain
|
|
44
|
+
- **tone**: neutral, standard, collectible, edition, info, success, warning, destructive
|
|
45
|
+
- **size**: sm, md
|
|
46
|
+
|
|
47
|
+
## Tokens used
|
|
48
|
+
|
|
49
|
+
`secondary`, `secondary-foreground`, `border`, `foreground`, `background`, `muted`, `muted-foreground`, `tone-standard`, `tone-collectible`, `tone-edition`, `tone-info`, `success`, `tone-warning`, `destructive`, `overlay`, `overlay-foreground`, `tone-standard-foreground`, `tone-collectible-foreground`, `tone-edition-foreground`, `tone-info-foreground`, `success-foreground`, `tone-warning-foreground`, `destructive-foreground`
|
|
50
|
+
|
|
51
|
+
## Accessibility
|
|
52
|
+
|
|
53
|
+
- Badges are static text by default; do not rely on color alone for meaning.
|
|
54
|
+
- Use clear label text for semantic tones so assistive tech receives the same status information sighted users see.
|
|
55
|
+
|
|
56
|
+
## Related
|
|
57
|
+
|
|
58
|
+
- Components: Pill, StatusDot, NotificationBadge, Banner, Toast
|
|
59
|
+
- Patterns: DashboardPage, ListDetailPage
|
|
60
|
+
|
|
61
|
+
## Examples
|
|
62
|
+
|
|
63
|
+
### Tones
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Badge, Row } from "@sableui/react"
|
|
67
|
+
|
|
68
|
+
<Row gap={1}><Badge tone="edition">Edition</Badge><Badge tone="collectible">Collectible</Badge><Badge tone="standard">Standard</Badge></Row>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Solid
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Badge } from "@sableui/react"
|
|
75
|
+
|
|
76
|
+
<Badge appearance="solid" size="sm">Admin</Badge>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Success
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Badge } from "@sableui/react"
|
|
83
|
+
|
|
84
|
+
<Badge tone="success">Resolved</Badge>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### With icon
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { Badge, Icon } from "@sableui/react"
|
|
91
|
+
|
|
92
|
+
<Badge size="sm" icon={<Icon name="lock" />}>Locked</Badge>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Small
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { Badge } from "@sableui/react"
|
|
99
|
+
|
|
100
|
+
<Badge size="sm" tone="info">New</Badge>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Metadata row
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { Badge, Icon, Row } from "@sableui/react"
|
|
107
|
+
|
|
108
|
+
<Row gap={1}><Badge>12 editions</Badge><Badge appearance="outline">Open edition</Badge><Badge tone="info" icon={<Icon name="info" />}>Curated</Badge></Row>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Semantic state
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { Badge, Row } from "@sableui/react"
|
|
115
|
+
|
|
116
|
+
<Row gap={1}><Badge tone="success">Synced</Badge><Badge tone="warning">Review</Badge><Badge tone="destructive">Blocked</Badge></Row>
|
|
117
|
+
```
|