@sableui/react 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +59 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/blocks/index.d.ts +396 -0
  8. package/dist/blocks/index.js +1997 -0
  9. package/dist/chunk-6LCIBMMG.js +4908 -0
  10. package/dist/{chunk-AOWBYEQ6.js → chunk-CKNM7KSB.js} +1598 -6464
  11. package/dist/chunk-K6LII3OE.js +5093 -0
  12. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  13. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  14. package/dist/icon/index.js +2 -2
  15. package/dist/icon-sets/reicon.js +1 -1
  16. package/dist/index.d.ts +471 -34
  17. package/dist/index.js +588 -2589
  18. package/dist/ios/index.d.ts +8 -1
  19. package/dist/ios/index.js +6 -5
  20. package/dist/ios/preview.d.ts +11 -1
  21. package/dist/ios/preview.js +13 -8
  22. package/dist/ios/styles.css +6 -0
  23. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  24. package/dist/patterns/index.d.ts +1 -1
  25. package/dist/patterns/index.js +29 -30
  26. package/dist/themes/base/base.css +23 -0
  27. package/dist/themes/index.js +2 -2
  28. package/llms/blocks/api-keys-block.md +79 -0
  29. package/llms/blocks/chat-thread-block.md +51 -0
  30. package/llms/blocks/command-palette-block.md +48 -0
  31. package/llms/blocks/cta-band-block.md +62 -0
  32. package/llms/blocks/danger-zone-block.md +69 -0
  33. package/llms/blocks/device-comparison-block.md +91 -0
  34. package/llms/blocks/event-card-block.md +84 -0
  35. package/llms/blocks/faq-block.md +72 -0
  36. package/llms/blocks/feature-grid-block.md +75 -0
  37. package/llms/blocks/feedback-form-block.md +66 -0
  38. package/llms/blocks/goal-progress-block.md +54 -0
  39. package/llms/blocks/hero-block.md +65 -0
  40. package/llms/blocks/invite-team-block.md +61 -0
  41. package/llms/blocks/invoice-history-block.md +84 -0
  42. package/llms/blocks/kpi-sparkline-block.md +57 -0
  43. package/llms/blocks/media-player-block.md +100 -0
  44. package/llms/blocks/newsletter-block.md +64 -0
  45. package/llms/blocks/not-found-block.md +56 -0
  46. package/llms/blocks/notification-list-block.md +63 -0
  47. package/llms/blocks/notification-settings-block.md +54 -0
  48. package/llms/blocks/order-tracking-block.md +62 -0
  49. package/llms/blocks/pricing-card-block.md +82 -0
  50. package/llms/blocks/pricing-table-block.md +129 -0
  51. package/llms/blocks/product-card-block.md +69 -0
  52. package/llms/blocks/profile-card-block.md +103 -0
  53. package/llms/blocks/profile-form-block.md +114 -0
  54. package/llms/blocks/revenue-breakdown-block.md +70 -0
  55. package/llms/blocks/sign-in-block.md +64 -0
  56. package/llms/blocks/sign-up-block.md +78 -0
  57. package/llms/blocks/site-footer-block.md +96 -0
  58. package/llms/blocks/stat-row-block.md +59 -0
  59. package/llms/blocks/steps-block.md +73 -0
  60. package/llms/blocks/task-list-block.md +81 -0
  61. package/llms/blocks/team-members-block.md +91 -0
  62. package/llms/blocks/testimonial-block.md +78 -0
  63. package/llms/blocks/traffic-sources-block.md +63 -0
  64. package/llms/blocks/traffic-trend-block.md +95 -0
  65. package/llms/blocks/upload-list-block.md +66 -0
  66. package/llms/blocks/verify-code-block.md +59 -0
  67. package/llms/blocks/wallet-block.md +84 -0
  68. package/llms/components/area-chart.md +112 -0
  69. package/llms/components/bar-chart.md +131 -0
  70. package/llms/components/button.md +1 -1
  71. package/llms/components/card.md +2 -2
  72. package/llms/components/context-menu.md +1 -1
  73. package/llms/components/date-tile.md +79 -0
  74. package/llms/components/entity.md +4 -2
  75. package/llms/components/line-chart.md +101 -0
  76. package/llms/components/menu.md +1 -1
  77. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  78. package/llms/components/pie-chart.md +103 -0
  79. package/llms/components/radar-chart.md +106 -0
  80. package/llms/components/radial-chart.md +102 -0
  81. package/llms/components/segmented-bar.md +102 -0
  82. package/llms/components/settings-menu.md +2 -2
  83. package/llms/components/sparkline.md +80 -0
  84. package/llms/components/table.md +1 -1
  85. package/llms/patterns/auth-page.md +2 -2
  86. package/llms/patterns/dashboard-page.md +6 -7
  87. package/llms/patterns/feed-page.md +7 -7
  88. package/llms/patterns/list-detail-page.md +7 -15
  89. package/llms/patterns/settings-page.md +10 -21
  90. package/llms/tasks/building-an-ios-prototype.md +4 -1
  91. package/llms/tasks/choosing-a-component.md +2 -2
  92. package/llms-full.txt +4296 -277
  93. package/llms.txt +55 -3
  94. package/package.json +5 -1
  95. package/registry.json +6383 -3741
  96. package/targets/ios.json +90 -9
  97. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Status:** stable
4
4
 
5
- A list-row record: leading visual + title/subtitle column + trailing action, with truncating text. Presentational by default (no hover); `interactive` rows gain the hover highlight, focus ring, and keyboard activation. Compact/comfortable density.
5
+ A list-row record: leading visual + title/subtitle column + trailing action, with truncating text. Presentational by default (no hover); `interactive` rows gain the hover highlight, focus ring, and keyboard activation, and `selected` marks the current one. Compact/comfortable density.
6
6
 
7
7
  **Keywords:** entity, identity, object, avatar row
8
8
 
@@ -28,6 +28,7 @@ Use Entity to show a compact identity block for a person, organization, file, pr
28
28
  | action | ReactNode | — | Trailing slot — typically a ghost icon Button or a StatusDot. |
29
29
  | interactive | boolean | false | Make the whole row a target: the leading visual and text become a native <button> whose hit area stretches over the row (focus ring, Enter/Space). The action stays outside it. Requires onSelect. |
30
30
  | onSelect | () => void | — | Activation handler for an interactive row (fired on click and Enter/Space). Required when interactive. |
31
+ | selected | boolean | false | Interactive rows only: the current row of a selectable list (the record open in a list–detail view). Sets aria-current="true" on the row's button and paints the selected fill, a step off the surface the row sits on, which holds on hover. |
31
32
  | density | "compact" \| "comfortable" | "compact" | Row padding: compact (12px × 10px, the default) or comfortable (16px × 12px), on the spacing scale. The insets read --entity-px / --entity-py first, which CardList sets from the card's own padding so rows in a card line up with its header. |
32
33
 
33
34
  ## Variants
@@ -36,13 +37,14 @@ Use Entity to show a compact identity block for a person, organization, file, pr
36
37
 
37
38
  ## Tokens used
38
39
 
39
- `muted`, `foreground`, `muted-foreground`, `ring`
40
+ `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `ring`
40
41
 
41
42
  ## Accessibility
42
43
 
43
44
  - Default rows are presentational layout; semantics come from the content you place in title/subtitle/action.
44
45
  - interactive rows render a real <button> around the leading visual and text, with its ::after stretched over the whole row — native focus, a focus-visible ring, and Enter/Space. The action slot sits outside that button and above the overlay, so a Button there stays valid HTML and keeps its own clicks.
45
46
  - Give any interactive action an accessible label (e.g. aria-label on a ghost icon Button).
47
+ - A selected row announces aria-current="true" on its button, so the current record is spoken, not only shown by its fill.
46
48
 
47
49
  ## Related
48
50
 
@@ -0,0 +1,101 @@
1
+ # LineChart
2
+
3
+ **Status:** beta
4
+
5
+ Lines over a sequence — for comparing how measures move. Smooth, linear or step curves, optional dots (coloured by series or by category) and value labels. Lines draw on as the chart scrolls into view.
6
+
7
+ **Keywords:** chart, line, trend, time series, graph, compare, analytics, dashboard
8
+
9
+ ## Usage
10
+
11
+ Use LineChart to compare how two or more measures change over the same sequence, or to show one trend precisely (with dots and labels). Unlike AreaChart, its value axis is fitted to the data rather than forced to zero, so small movements stay visible — say so if the axis is shown.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Turn on dots and labels for six or fewer points, when the exact values are the story.
16
+ - Do: Use curve="step" for values that hold until they change — prices, plan tiers, headcount.
17
+ - Don't: Plot unordered categories (channels, products) as a line — the line implies a path between them; use BarChart.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | The rows, in order along the category axis. |
25
+ | index | string | — | The datum field naming each row's category (a month, a day, a channel). |
26
+ | series | { key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[] | — | The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 … --chart-8, validated for colour-blind separation in light and dark; a theme may override the set. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | className | string | "h-60" | Sizes the chart (plot + legend). Set a height: `h-72`, `aspect-video h-auto`. |
29
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
30
+ | indexFormatter | (value: string) => string | — | Shorten category labels on the axis ("January" → "Jan"); the tooltip keeps the full value. |
31
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
32
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
33
+ | legend | boolean | true when there is more than one series | Show the legend under the chart. |
34
+ | grid | boolean | true (false for horizontal bars) | Hairline gridlines along the value axis. |
35
+ | xAxis | boolean | true for columns; false for horizontal bars | The horizontal axis. |
36
+ | yAxis | boolean | false for columns; true for horizontal bars | The vertical axis. |
37
+ | animate | boolean | true | Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. |
38
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
39
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
40
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
41
+ | curve | "smooth" \| "linear" \| "step" | "smooth" | How the line runs between points. `smooth` is monotone: it never overshoots a value the data does not contain. |
42
+ | dots | boolean | false | Mark every data point with a dot. |
43
+ | colorBy | "series" \| "index" | "series" | Colour the dots by series, or each by its category. |
44
+ | labels | boolean | false | Print each point's value above it. |
45
+
46
+ ## Tokens used
47
+
48
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
49
+
50
+ ## Motion
51
+
52
+ - **arrival** · Line · _custom (outside the recipe layer)_ — Once on screen each line draws on from the first point (stroke-dashoffset), series staggered, then the dots and labels fade in. On the motion tokens; reduced motion shows the finished chart.
53
+ - **response** · Cursor + tooltip · _no motion_ — Hover and focus move the cursor and tooltip instantly — a lagging cursor reads as a slow chart.
54
+ - **transition** · Line + dots · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
55
+ - **activity** · LineChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
56
+
57
+ ## Accessibility
58
+
59
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
60
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
61
+ - Colour is never the only key: series are named in the legend, tooltip and table.
62
+
63
+ ## Related
64
+
65
+ - Components: AreaChart, BarChart, Sparkline, Card
66
+
67
+ ## Examples
68
+
69
+ ### Compare two series
70
+
71
+ ```tsx
72
+ import { LineChart } from "@sableui/react"
73
+
74
+ <LineChart
75
+ label="Desktop and mobile visitors"
76
+ data={visitors}
77
+ index="month"
78
+ series={[
79
+ { key: "desktop", label: "Desktop", tone: "primary" },
80
+ { key: "mobile", label: "Mobile", tone: "info" },
81
+ ]}
82
+ indexFormatter={(m) => m.slice(0, 3)}
83
+ />
84
+ ```
85
+
86
+ ### Dots and labels
87
+
88
+ ```tsx
89
+ import { LineChart } from "@sableui/react"
90
+
91
+ <LineChart
92
+ label="Desktop visitors"
93
+ data={visitors}
94
+ index="month"
95
+ series={[{ key: "desktop", label: "Desktop" }]}
96
+ curve="linear"
97
+ dots
98
+ labels
99
+ grid={false}
100
+ />
101
+ ```
@@ -65,7 +65,7 @@ Use Menu for a compact list of actions opened from a visible trigger.
65
65
 
66
66
  ## Related
67
67
 
68
- - Components: ContextMenu, DotsMenu
68
+ - Components: ContextMenu, OverflowMenu
69
69
 
70
70
  ## Examples
71
71
 
@@ -1,20 +1,20 @@
1
- # DotsMenu
1
+ # OverflowMenu
2
2
 
3
3
  **Status:** stable
4
4
 
5
5
  An overflow ellipsis icon button that opens a Menu. Composes our Menu + Button + Icon — pass MenuItems as children and it wires the ghost icon trigger, accessible label, and portal-rendered content. All keyboard, focus, and dismissal behavior comes through Menu.
6
6
 
7
- **Keywords:** dots menu, more actions, overflow, kebab
7
+ **Keywords:** dots menu, more actions, ellipsis, kebab
8
8
 
9
9
  ## Usage
10
10
 
11
- Use DotsMenu for compact overflow actions when the primary action is already visible elsewhere.
11
+ Use OverflowMenu for compact overflow actions when the primary action is already visible elsewhere.
12
12
 
13
13
  ### Best practices
14
14
 
15
15
  - Do: Use it for secondary commands that belong to a row, card, or toolbar item.
16
16
  - Do: Keep labels explicit so the icon-only trigger does not obscure the command meaning.
17
- - Don't: Do not hide the most important or most frequent action inside DotsMenu.
17
+ - Don't: Do not hide the most important or most frequent action inside OverflowMenu.
18
18
  - Don't: Do not use it as the only affordance on a touch surface. It is revealed on hover in most layouts, and hover does not exist on touch — pair it with a visible action or make it always-visible there.
19
19
 
20
20
  ## Props
@@ -24,7 +24,7 @@ Use DotsMenu for compact overflow actions when the primary action is already vis
24
24
  | children | ReactNode | — | The menu rows — MenuItem / MenuSeparator / MenuGroup, etc. Rendered inside MenuContent. |
25
25
  | align | 'start' \| 'center' \| 'end' | end | Alignment of the popup along its side. Overflow menus typically right-align under the trigger; 'start' anchors the popup's left edge to the trigger instead. |
26
26
  | label | string | More actions | Accessible label (aria-label) for the icon-only trigger button. |
27
- | icon | string | ellipsis | Semantic icon name rendered in the trigger. |
27
+ | icon | string | ellipsis | Semantic icon name rendered in the trigger, always at the solid weight. |
28
28
  | size | 'icon' \| 'icon-sm' \| 'icon-xs' | icon | The trigger's rung on the icon-button ladder. 'icon' for a card or page header; 'icon-sm' or 'icon-xs' in a list row or dense toolbar, so the trigger does not set the row height. |
29
29
 
30
30
  ## Variants
@@ -50,15 +50,15 @@ Use DotsMenu for compact overflow actions when the primary action is already vis
50
50
  ### Basic
51
51
 
52
52
  ```tsx
53
- import { DotsMenu, MenuItem, MenuSeparator } from "@sableui/react"
53
+ import { MenuItem, MenuSeparator, OverflowMenu } from "@sableui/react"
54
54
 
55
- <DotsMenu label="Post actions"><MenuItem>Edit</MenuItem><MenuItem>Share</MenuItem><MenuSeparator /><MenuItem variant="destructive">Delete</MenuItem></DotsMenu>
55
+ <OverflowMenu label="Post actions"><MenuItem>Edit</MenuItem><MenuItem>Share</MenuItem><MenuSeparator /><MenuItem variant="destructive">Delete</MenuItem></OverflowMenu>
56
56
  ```
57
57
 
58
58
  ### With leading icons
59
59
 
60
60
  ```tsx
61
- import { DotsMenu, Icon, MenuItem, MenuSeparator } from "@sableui/react"
61
+ import { Icon, MenuItem, MenuSeparator, OverflowMenu } from "@sableui/react"
62
62
 
63
- <DotsMenu><MenuItem><Icon name="check" className="text-sm" />Mark as read</MenuItem><MenuSeparator /><MenuItem variant="destructive"><Icon name="xmark" className="text-sm" />Delete</MenuItem></DotsMenu>
63
+ <OverflowMenu><MenuItem><Icon name="check" className="text-sm" />Mark as read</MenuItem><MenuSeparator /><MenuItem variant="destructive"><Icon name="xmark" className="text-sm" />Delete</MenuItem></OverflowMenu>
64
64
  ```
@@ -0,0 +1,103 @@
1
+ # PieChart
2
+
3
+ **Status:** beta
4
+
5
+ Parts of a whole as slices: a pie or donut, concentric rings for several measures, a total in the hole, slice labels inside or out, and an active slice that pulls outward. Slices sweep in clockwise as the chart scrolls into view.
6
+
7
+ **Keywords:** chart, pie, donut, doughnut, share, proportion, breakdown, parts, whole
8
+
9
+ ## Usage
10
+
11
+ Use PieChart for a share of one total across two to six categories — traffic sources, a budget split, survey answers. A donut leaves room for the total in the middle (`centerLabel`). With more categories, or to compare values precisely, use BarChart; for a breakdown inside a stat card, SegmentedBar is lighter.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Put the total, or the headline slice, in a donut's centerLabel.
16
+ - Do: Configure slice names, tones and icons with categories, keyed by each slice's value in the data.
17
+ - Don't: Use more than six slices — merge the tail into "Other" or use BarChart.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | One row per slice. |
25
+ | index | string | — | The datum field naming each slice. |
26
+ | valueKey | string \| string[] | — | The datum field holding each slice's amount — or several fields, drawn as concentric rings, outer first. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | categories | Record<string, { label?: string; tone?: ChartTone; color?: string; icon?: string }> | — | Labels, tones and icons per slice, keyed by the slice's name. Unlisted slices take the tones in order. |
29
+ | className | string | "h-64" | Sizes the chart. Set a height. |
30
+ | donut | boolean \| number | false | Cut out the middle: `true` for a ring at 60% of the radius, or a ratio (0–0.9). |
31
+ | centerLabel | { value: string; label?: string } | — | A value and caption in the hole of a donut. |
32
+ | separator | boolean | true | A hairline in the surface colour between slices. |
33
+ | labels | "none" \| "outside" \| "inside" | "none" | Label each slice outside the pie or inside it. |
34
+ | labelContent | "value" \| "percent" \| "name" | "value" | What a slice label says. |
35
+ | legend | boolean | true unless slices are labelled | Show the legend. |
36
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
37
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
38
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
39
+ | animate | boolean | true | Sweep the slices in clockwise when the chart scrolls into view. |
40
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
41
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
42
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
43
+
44
+ ## Tokens used
45
+
46
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
47
+
48
+ ## Motion
49
+
50
+ - **arrival** · Slices · _custom (outside the recipe layer)_ — Once on screen the slices are revealed by a mask that sweeps clockwise from 12 o'clock — the whole is read as it fills. On the motion tokens; reduced motion shows the finished chart.
51
+ - **response** · Active slice · _no motion_ — The hovered slice pulls outward and the rest dim at once; the geometry change itself eases (see transition).
52
+ - **transition** · Slices · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
53
+ - **activity** · PieChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
54
+
55
+ ## Accessibility
56
+
57
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
58
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
59
+ - Colour is never the only key: series are named in the legend, tooltip and table.
60
+
61
+ ## Related
62
+
63
+ - Components: SegmentedBar, RadialChart, BarChart, Card
64
+
65
+ ## Examples
66
+
67
+ ### Donut with a total
68
+
69
+ ```tsx
70
+ import { PieChart } from "@sableui/react"
71
+
72
+ const sources = [
73
+ { source: "Search", visitors: 275 },
74
+ { source: "Direct", visitors: 200 },
75
+ { source: "Social", visitors: 187 },
76
+ { source: "Email", visitors: 90 },
77
+ ]
78
+
79
+ <PieChart
80
+ label="Visitors by source"
81
+ data={sources}
82
+ index="source"
83
+ valueKey="visitors"
84
+ donut
85
+ centerLabel={{ value: "752", label: "Visitors" }}
86
+ />
87
+ ```
88
+
89
+ ### Labelled slices
90
+
91
+ ```tsx
92
+ import { PieChart } from "@sableui/react"
93
+
94
+ <PieChart
95
+ label="Share of visitors by source"
96
+ data={sources}
97
+ index="source"
98
+ valueKey="visitors"
99
+ labels="outside"
100
+ labelContent="percent"
101
+ categories={{ Search: { tone: "primary" }, Email: { tone: "neutral" } }}
102
+ />
103
+ ```
@@ -0,0 +1,106 @@
1
+ # RadarChart
2
+
3
+ **Status:** beta
4
+
5
+ Several measures compared around a centre: one polygon per series over a polygon or circular web, filled or outline only, with optional dots, a radius scale and a tinted web. Polygons grow from the centre as the chart scrolls into view.
6
+
7
+ **Keywords:** chart, radar, spider, web, profile, skills, comparison, polar
8
+
9
+ ## Usage
10
+
11
+ Use RadarChart to compare the shape of a profile across five to eight dimensions — skills, product scores, a cycle of months — for one to three series. The shape is the message; for exact values or more than three series, BarChart is easier to read.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Keep every spoke on the same scale and direction (more is better on all of them).
16
+ - Do: Use filled={false} when two polygons overlap heavily, so both outlines stay visible.
17
+ - Don't: Use fewer than five spokes — three or four make a triangle or square that says little; use BarChart.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | One row per spoke. |
25
+ | index | string | — | The datum field naming each spoke. |
26
+ | series | { key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[] | — | The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 … --chart-8, validated for colour-blind separation in light and dark; a theme may override the set. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | className | string | "h-72" | Sizes the chart. Set a height. |
29
+ | grid | "polygon" \| "circle" \| "none" | "polygon" | The web behind the polygons. |
30
+ | gridFill | boolean | false | Tint the web's outer area. |
31
+ | filled | boolean | true | Fill each polygon; `false` draws outlines only. |
32
+ | dots | boolean | false | Mark every vertex with a dot. |
33
+ | radiusAxis | boolean | false | Print the ring values along the top spoke. |
34
+ | legend | boolean | true when there is more than one series | Show the legend. |
35
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
36
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
37
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
38
+ | animate | boolean | true | Grow the polygons from the centre when the chart scrolls into view. |
39
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
40
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
41
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
42
+
43
+ ## Tokens used
44
+
45
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
46
+
47
+ ## Motion
48
+
49
+ - **arrival** · Polygon · _custom (outside the recipe layer)_ — Once on screen each polygon grows from the centre and fades in, series staggered, on the emphasized easing. On the motion tokens; reduced motion shows the finished chart.
50
+ - **response** · Active spoke · _no motion_ — The hovered spoke's vertices and label highlight at once.
51
+ - **transition** · Polygon · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
52
+ - **activity** · RadarChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
53
+
54
+ ## Accessibility
55
+
56
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
57
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
58
+ - Colour is never the only key: series are named in the legend, tooltip and table.
59
+
60
+ ## Related
61
+
62
+ - Components: BarChart, LineChart, Card
63
+
64
+ ## Examples
65
+
66
+ ### Two profiles
67
+
68
+ ```tsx
69
+ import { RadarChart } from "@sableui/react"
70
+
71
+ const scores = [
72
+ { skill: "Speed", a: 86, b: 65 },
73
+ { skill: "Quality", a: 72, b: 90 },
74
+ { skill: "Support", a: 64, b: 80 },
75
+ { skill: "Price", a: 90, b: 55 },
76
+ { skill: "Docs", a: 70, b: 85 },
77
+ { skill: "Ecosystem", a: 60, b: 75 },
78
+ ]
79
+
80
+ <RadarChart
81
+ label="Product scores compared"
82
+ data={scores}
83
+ index="skill"
84
+ series={[
85
+ { key: "a", label: "Atlas" },
86
+ { key: "b", label: "Beacon" },
87
+ ]}
88
+ />
89
+ ```
90
+
91
+ ### Circular web, outlines only
92
+
93
+ ```tsx
94
+ import { RadarChart } from "@sableui/react"
95
+
96
+ <RadarChart
97
+ label="Atlas scores"
98
+ data={scores}
99
+ index="skill"
100
+ series={[{ key: "a", label: "Atlas" }]}
101
+ grid="circle"
102
+ gridFill
103
+ filled={false}
104
+ dots
105
+ />
106
+ ```
@@ -0,0 +1,102 @@
1
+ # RadialChart
2
+
3
+ **Status:** beta
4
+
5
+ Progress or share as rings: one ring per row against a muted track, a full circle or a gauge (`sweep`), one ring split between rows (`stacked`), ring labels and a value in the middle. Rings draw round as the chart scrolls into view.
6
+
7
+ **Keywords:** chart, radial, ring, gauge, progress, goal, score, activity rings, donut
8
+
9
+ ## Usage
10
+
11
+ Use RadialChart for progress toward goals (several rings), a single score or usage gauge (`sweep={180}` with `centerLabel`), or a split of one total along a gauge (`stacked`). For one reading in a form or settings row, Meter's ring shape is lighter.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Set max to the goal, so a full ring means "done".
16
+ - Do: Pair a gauge with centerLabel stating the value in words or units.
17
+ - Don't: Compare more than four rings — inner rings are shorter for the same value, which misleads; use BarChart.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | One row per ring, outermost first. |
25
+ | index | string | — | The datum field naming each ring. |
26
+ | valueKey | string | — | The datum field holding each ring's amount. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | categories | Record<string, { label?: string; tone?: ChartTone; color?: string; icon?: string }> | — | Labels, tones and icons per ring, keyed by the ring's name. |
29
+ | className | string | "h-64" | Sizes the chart. Set a height. |
30
+ | max | number | the largest value (stacked: the total; labelled: the largest value ÷ 0.75, leaving room for the labels) | The value a full ring stands for. |
31
+ | sweep | number | 360 | How far round a full ring runs, in degrees: 360 for a circle, 180 for a gauge. |
32
+ | stacked | boolean | false | Split ONE ring between the rows instead of a ring each. |
33
+ | track | boolean | true | Draw each ring's full track behind it. |
34
+ | labels | boolean | false | Name each ring at its start. |
35
+ | centerLabel | { value: string; label?: string } | — | A value and caption in the middle. |
36
+ | legend | boolean | true unless rings are labelled | Show the legend. |
37
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
38
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
39
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
40
+ | animate | boolean | true | Draw the rings round from their start when the chart scrolls into view. |
41
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
42
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
43
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
44
+
45
+ ## Tokens used
46
+
47
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
48
+
49
+ ## Motion
50
+
51
+ - **arrival** · Ring · _custom (outside the recipe layer)_ — Once on screen each ring draws round from its start (stroke-dashoffset on a round-capped arc), staggered outside in. On the motion tokens; reduced motion shows the finished chart.
52
+ - **response** · Active ring · _no motion_ — The hovered ring stays full strength and the others dim at once.
53
+ - **transition** · Ring · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
54
+ - **activity** · RadialChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
55
+
56
+ ## Accessibility
57
+
58
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
59
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
60
+ - Colour is never the only key: series are named in the legend, tooltip and table.
61
+
62
+ ## Related
63
+
64
+ - Components: Meter, PieChart, Progress, Card
65
+
66
+ ## Examples
67
+
68
+ ### Goal rings
69
+
70
+ ```tsx
71
+ import { RadialChart } from "@sableui/react"
72
+
73
+ <RadialChart
74
+ label="Weekly goals"
75
+ data={[
76
+ { goal: "Move", progress: 82 },
77
+ { goal: "Exercise", progress: 64 },
78
+ { goal: "Stand", progress: 91 },
79
+ ]}
80
+ index="goal"
81
+ valueKey="progress"
82
+ max={100}
83
+ labels
84
+ />
85
+ ```
86
+
87
+ ### Gauge with a value
88
+
89
+ ```tsx
90
+ import { RadialChart } from "@sableui/react"
91
+
92
+ <RadialChart
93
+ label="Plan usage"
94
+ data={[{ plan: "Used", gb: 7.2 }]}
95
+ index="plan"
96
+ valueKey="gb"
97
+ max={10}
98
+ sweep={180}
99
+ centerLabel={{ value: "7.2 GB", label: "of 10 GB" }}
100
+ legend={false}
101
+ />
102
+ ```
@@ -0,0 +1,102 @@
1
+ # SegmentedBar
2
+
3
+ **Status:** stable
4
+
5
+ One bar split into the parts of a whole — revenue by type, storage by kind, results by outcome. Segments size by their share of the total; the bar is one labelled image to assistive tech.
6
+
7
+ **Keywords:** breakdown, stacked, segmented, proportion, split, distribution, category, bar, share
8
+
9
+ ## Usage
10
+
11
+ Use SegmentedBar when a stat card or row needs to show how a total divides into a few categories. Pair it with a legend you compose (StatusDot + label + share), placed where the card has room. For one reading against a range (storage used, quota), use Meter; for task completion, use Progress.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Keep it to two to six segments and give the same category the same tone everywhere it appears.
16
+ - Do: Show the legend's shares in text ("42%") — colour identifies a segment, but the number is what a reader compares.
17
+ - Don't: Use it for a single value against a limit; that is Meter, which carries the meter role and its value.
18
+ - Don't: Rely on tone order alone to tell segments apart; a legend row per segment, with its label, is required.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | Track | yes | The rounded bar; segments fill it edge to edge with a hairline gap. |
25
+ | Segment | yes | One category, sized by its share of the total and filled with its tone. |
26
+
27
+ ## Props
28
+
29
+ | Name | Type | Default | Description |
30
+ | --- | --- | --- | --- |
31
+ | segments | { label: string; value: number; tone?: SegmentedBarTone }[] | — | The categories, left to right. Values in any unit; zero or negative values are skipped. |
32
+ | label | string | — | What the bar breaks down. Prefixes the accessible summary of every segment's share. |
33
+ | size | "sm" \| "md" \| "lg" | "md" | Bar thickness — the same rails as Meter and Progress. |
34
+ | locale | string | runtime locale | Locale for the spoken percentages. |
35
+
36
+ ## Variants
37
+
38
+ - **size**: sm, md, lg
39
+ - **tone**: edition, collectible, standard, info, primary, neutral, success, warning, destructive
40
+
41
+ ## Tokens used
42
+
43
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `primary`, `muted-foreground`, `success`, `tone-warning`, `destructive`
44
+
45
+ ## Motion
46
+
47
+ - **arrival** · SegmentedBar · _no motion_ — A breakdown is a reading, present with the card that reports it; growing the segments in on load would act out a change that did not happen.
48
+ - **response** · SegmentedBar · _no motion_ — It reports and accepts no input.
49
+ - **transition** · Segment · _custom (outside the recipe layer)_ — Each segment's share and colour ease on the motion tokens when the data changes (no recipe animates flex-grow), so the reader sees which part grew.
50
+ - **activity** · SegmentedBar · _no motion_ — No indeterminate state — an unknown breakdown is not drawn.
51
+
52
+ ## Accessibility
53
+
54
+ - Rendered role=img with an aria-label built from `label` and each segment's share ("Revenue by type: Editions 42%, Collectibles 27%"), so the whole breakdown is read in one stop.
55
+ - Colour alone does not identify a segment: always show a legend with each segment's label beside the bar.
56
+
57
+ ## Related
58
+
59
+ - Components: Meter, Progress, StatusDot, Card
60
+ - Patterns: DashboardPage
61
+
62
+ ## Examples
63
+
64
+ ### With a legend
65
+
66
+ ```tsx
67
+ import { SegmentedBar, Stack, StatusDot } from "@sableui/react"
68
+
69
+ const sales = [
70
+ { label: "Editions", value: 42, tone: "edition" },
71
+ { label: "Collectibles", value: 27, tone: "collectible" },
72
+ { label: "Standard", value: 19, tone: "standard" },
73
+ ] as const
74
+
75
+ <Stack gap={3}>
76
+ <SegmentedBar label="Revenue by type" segments={[...sales]} size="lg" />
77
+ <ul className="flex flex-col gap-1.5">
78
+ {sales.map((s) => (
79
+ <li key={s.label} className="flex items-center gap-2 text-body-sm">
80
+ <StatusDot tone={s.tone} />
81
+ <span className="flex-1 text-muted-foreground">{s.label}</span>
82
+ <span className="text-mono-caption">{s.value}%</span>
83
+ </li>
84
+ ))}
85
+ </ul>
86
+ </Stack>
87
+ ```
88
+
89
+ ### Test results — status tones
90
+
91
+ ```tsx
92
+ import { SegmentedBar } from "@sableui/react"
93
+
94
+ <SegmentedBar
95
+ label="Test results"
96
+ segments={[
97
+ { label: "Passed", value: 412, tone: "success" },
98
+ { label: "Flaky", value: 9, tone: "warning" },
99
+ { label: "Failed", value: 3, tone: "destructive" },
100
+ ]}
101
+ />
102
+ ```
@@ -17,7 +17,7 @@ For a cluster of related settings that a reader checks often and changes occasio
17
17
  - Do: Use `MenuRadioGroup` + `MenuRadioItem` inside a row for single-choice settings, so the current option is checked rather than merely remembered.
18
18
  - Do: Reach for `variant="subtle"` when the trigger sits on a surface where a ghost control would float unanchored — inside a ChatComposer, for instance.
19
19
  - Don't: Do not use it for a single setting. One choice is a `Select`; the nesting only earns its cost when there are several related settings to summarise.
20
- - Don't: Do not put actions in it. Rows change state; a menu that mixes "Effort ›" with "Delete conversation" makes destructive commands look like preferences. That is `Menu` or `DotsMenu`.
20
+ - Don't: Do not put actions in it. Rows change state; a menu that mixes "Effort ›" with "Delete conversation" makes destructive commands look like preferences. That is `Menu` or `OverflowMenu`.
21
21
  - Don't: Do not nest more than one level deep. Past that the reader loses the path back, and the thing you have is a settings dialog wearing a menu.
22
22
  - Don't: Do not override the upward, end-aligned default without reason. Its home is the bottom of a composer or toolbar, where a downward menu opens off-screen.
23
23
 
@@ -69,7 +69,7 @@ For a cluster of related settings that a reader checks often and changes occasio
69
69
 
70
70
  ## Related
71
71
 
72
- - Components: Menu, DotsMenu, Select, ChatComposer, Popover
72
+ - Components: Menu, OverflowMenu, Select, ChatComposer, Popover
73
73
  - Patterns: ChatPanel
74
74
 
75
75
  ## Examples