@sableui/react 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
- package/dist/{chunk-J23ELQQZ.js → chunk-AOWBYEQ6.js} +4177 -1917
- package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
- package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
- package/dist/foundations/index.js +1 -1
- package/dist/index.d.ts +155 -101
- package/dist/index.js +582 -2778
- package/dist/ios/index.d.ts +6 -1
- package/dist/ios/index.js +4 -4
- package/dist/ios/preview.js +1 -1
- package/dist/ios/styles.css +20 -6
- package/dist/patterns/index.js +1683 -469
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +1 -1
- package/dist/styles.css +238 -40
- package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
- package/dist/themes/atelier/atelier.css +3 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/base/base.css +11 -4
- package/dist/themes/base/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +3 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +10 -5
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +16 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +4 -1
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +7 -2
- package/llms/components/bubble.md +5 -5
- package/llms/components/checkbox-card.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/dialog.md +1 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/entity.md +1 -1
- package/llms/components/fieldset.md +1 -1
- package/llms/components/kbd.md +18 -6
- package/llms/components/pill.md +1 -1
- package/llms/components/side-nav.md +1 -1
- package/llms/components/skeleton.md +1 -1
- package/llms/components/theme-selector.md +1 -1
- package/llms/foundations/shape.md +1 -1
- package/llms/patterns/auth-page.md +132 -42
- package/llms/patterns/brand-landing-page.md +271 -175
- package/llms/patterns/chat-panel.md +120 -39
- package/llms/patterns/dashboard-page.md +277 -65
- package/llms/patterns/feed-page.md +245 -64
- package/llms/patterns/landing-page.md +473 -75
- package/llms/patterns/list-detail-page.md +385 -68
- package/llms/patterns/settings-page.md +279 -67
- package/llms/reference/token-intent.md +4 -2
- package/llms/reference/utilities.md +20 -5
- package/llms-full.txt +2244 -727
- package/llms.txt +14 -15
- package/package.json +1 -1
- package/registry.json +261 -238
- package/targets/ios.json +4 -15
- package/llms/components/description.md +0 -96
|
@@ -117,15 +117,19 @@
|
|
|
117
117
|
---------------------------------------------------------------------- */
|
|
118
118
|
--background: oklch(94.6% 0.0083 91.48);
|
|
119
119
|
--foreground: oklch(26.25% 0.0162 102.18);
|
|
120
|
-
|
|
120
|
+
/* A step above the paper page, as in every preset with a tinted page: a card
|
|
121
|
+
level with it left fields reading as part of the page, not a box to type in. */
|
|
122
|
+
--card: oklch(97.6% 0.006 91.48);
|
|
121
123
|
--card-foreground: oklch(26.25% 0.0162 102.18);
|
|
122
|
-
--popover: oklch(
|
|
124
|
+
--popover: oklch(97.6% 0.006 91.48);
|
|
123
125
|
--popover-foreground: oklch(26.25% 0.0162 102.18);
|
|
124
126
|
--muted: oklch(91.24% 0.0138 92.99);
|
|
125
127
|
--muted-foreground: oklch(49.93% 0.0412 93.54);
|
|
126
128
|
--border: oklch(87.54% 0.0194 93.69);
|
|
127
129
|
--border-strong: oklch(81.94% 0.0252 94.12);
|
|
128
130
|
--input: oklch(87.54% 0.0194 93.69);
|
|
131
|
+
/* The resting edge of a control whose outline IS the control (checkbox, radio, an off switch track). Held to WCAG 1.4.11 3:1 on every surface, unlike the quieter --input field outline. */
|
|
132
|
+
--control-border: oklch(58.9% 0.0346 93.54);
|
|
129
133
|
--ring: oklch(62% 0.2063 32.56);
|
|
130
134
|
--primary: oklch(26.25% 0.0162 102.18);
|
|
131
135
|
--primary-foreground: oklch(94.6% 0.0083 91.48);
|
|
@@ -167,7 +171,7 @@
|
|
|
167
171
|
--sidebar-primary-foreground: oklch(22.46% 0.0094 107.13);
|
|
168
172
|
--sidebar-accent: oklch(27.1% 0.0143 101.41);
|
|
169
173
|
--sidebar-accent-foreground: oklch(94.3% 0.0042 91.45);
|
|
170
|
-
--sidebar-border: oklch(
|
|
174
|
+
--sidebar-border: oklch(34.69% 0.0186 98.75);
|
|
171
175
|
--sidebar-ring: oklch(65.11% 0.2063 32.56);
|
|
172
176
|
|
|
173
177
|
/* ----------------------------------------------------------------------
|
|
@@ -216,8 +220,9 @@
|
|
|
216
220
|
* only the edge separates. Caught by the non-text contrast gate.
|
|
217
221
|
*/
|
|
218
222
|
--border: oklch(34.69% 0.0186 98.75);
|
|
219
|
-
--border-strong: oklch(
|
|
223
|
+
--border-strong: oklch(44% 0.0225 96); /* one visible step above --border, which was lifted to the old strong value */
|
|
220
224
|
--input: oklch(34.69% 0.0186 98.75);
|
|
225
|
+
--control-border: oklch(60% 0.002 0);
|
|
221
226
|
--ring: oklch(65.11% 0.2063 32.56);
|
|
222
227
|
--primary: oklch(94.3% 0.0042 91.45);
|
|
223
228
|
--primary-foreground: oklch(22.46% 0.0094 107.13);
|
|
@@ -250,6 +255,6 @@
|
|
|
250
255
|
--sidebar-primary-foreground: oklch(22.46% 0.0094 107.13);
|
|
251
256
|
--sidebar-accent: oklch(27.1% 0.0143 101.41);
|
|
252
257
|
--sidebar-accent-foreground: oklch(94.3% 0.0042 91.45);
|
|
253
|
-
--sidebar-border: oklch(
|
|
258
|
+
--sidebar-border: oklch(34.69% 0.0186 98.75);
|
|
254
259
|
--sidebar-ring: oklch(65.11% 0.2063 32.56);
|
|
255
260
|
}
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
--border: oklch(69.96908% 0.055090 333.3586);
|
|
70
70
|
--border-strong: oklch(54.87925% 0.056525 333.9456);
|
|
71
71
|
--input: oklch(69.96908% 0.055090 333.3586);
|
|
72
|
+
/* The resting edge of a control whose outline IS the control (checkbox, radio, an off switch track). Held to WCAG 1.4.11 3:1 on every surface, unlike the quieter --input field outline. */
|
|
73
|
+
--control-border: oklch(60% 0.0598 321.39);
|
|
72
74
|
--ring: oklch(40.99289% 0.130336 325.3125);
|
|
73
75
|
--primary: oklch(38.38082% 0.116390 323.6155);
|
|
74
76
|
--primary-foreground: oklch(100.00000% 0.000000 89.8756);
|
|
@@ -119,14 +121,27 @@
|
|
|
119
121
|
--spacing: 0.28rem;
|
|
120
122
|
--theme-button-radius: 12px;
|
|
121
123
|
--button-radius: var(--theme-button-radius);
|
|
124
|
+
/* Hard offsets the whole way up. Only sm and md were declared, so the
|
|
125
|
+
default, lg and xl steps fell through to Base's soft blurs and one ladder
|
|
126
|
+
spoke two shadow languages. Offset doubles roughly per step. */
|
|
122
127
|
--theme-shadow-sm: 3px 3px 0 oklch(32% 0.05 320 / 0.12);
|
|
128
|
+
--theme-shadow: 4px 4px 0 oklch(32% 0.05 320 / 0.13);
|
|
123
129
|
--theme-shadow-md: 5px 5px 0 oklch(32% 0.05 320 / 0.14);
|
|
130
|
+
--theme-shadow-lg: 8px 8px 0 oklch(32% 0.05 320 / 0.16);
|
|
131
|
+
--theme-shadow-xl: 12px 12px 0 oklch(32% 0.05 320 / 0.18);
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
.dark[data-sable-theme="poppy"],
|
|
127
135
|
[data-sable-theme="poppy"].dark,
|
|
128
136
|
[data-sable-theme="poppy"] .dark,
|
|
129
137
|
.dark [data-sable-theme="poppy"]:not(.light) {
|
|
138
|
+
/* The light ladder's ink is 32% lightness — LIGHTER than this 20% page, so
|
|
139
|
+
in dark mode it drew a faint glow, not a shadow. Same offsets, near-black. */
|
|
140
|
+
--theme-shadow-sm: 3px 3px 0 oklch(8% 0.03 320 / 0.5);
|
|
141
|
+
--theme-shadow: 4px 4px 0 oklch(8% 0.03 320 / 0.52);
|
|
142
|
+
--theme-shadow-md: 5px 5px 0 oklch(8% 0.03 320 / 0.55);
|
|
143
|
+
--theme-shadow-lg: 8px 8px 0 oklch(8% 0.03 320 / 0.6);
|
|
144
|
+
--theme-shadow-xl: 12px 12px 0 oklch(8% 0.03 320 / 0.65);
|
|
130
145
|
--background: oklch(20.33265% 0.047438 308.8247);
|
|
131
146
|
--foreground: oklch(96.99788% 0.031195 84.5922);
|
|
132
147
|
--card: oklch(28.82474% 0.059607 315.2029);
|
|
@@ -138,6 +153,7 @@
|
|
|
138
153
|
--border: oklch(62.48980% 0.070560 325.5308);
|
|
139
154
|
--border-strong: oklch(72.17143% 0.060424 328.9840);
|
|
140
155
|
--input: oklch(62.48980% 0.070560 325.5308);
|
|
156
|
+
--control-border: oklch(67.3% 0.0412 332.63);
|
|
141
157
|
--ring: oklch(86.64637% 0.118528 86.6791);
|
|
142
158
|
--primary: oklch(86.64637% 0.118528 86.6791);
|
|
143
159
|
--primary-foreground: oklch(27.91553% 0.070219 323.8141);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
--region-ultrawide: 1800px;
|
|
13
13
|
--theme-radius-none: 0px;
|
|
14
14
|
--theme-radius-xs: 2px;
|
|
15
|
-
--theme-radius-sm: 4px
|
|
15
|
+
--theme-radius-sm: 3px; /* a step of its own — sm and md were both 4px, which erased a rung of the ladder */
|
|
16
16
|
--theme-radius-md: 4px;
|
|
17
17
|
--theme-radius-lg: 6px;
|
|
18
18
|
--theme-radius-xl: 8px;
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
--border: oklch(79.84882% 0.029246 251.5080);
|
|
70
70
|
--border-strong: oklch(64.27577% 0.042824 253.1390);
|
|
71
71
|
--input: oklch(79.84882% 0.029246 251.5080);
|
|
72
|
+
/* The resting edge of a control whose outline IS the control (checkbox, radio, an off switch track). Held to WCAG 1.4.11 3:1 on every surface, unlike the quieter --input field outline. */
|
|
73
|
+
--control-border: oklch(60% 0.0298 252.77);
|
|
72
74
|
--ring: oklch(50.45463% 0.203215 263.5596);
|
|
73
75
|
--primary: oklch(50.45463% 0.203215 263.5596);
|
|
74
76
|
--primary-foreground: oklch(100.00000% 0.000000 89.8756);
|
|
@@ -147,6 +149,7 @@
|
|
|
147
149
|
--border: oklch(58.06940% 0.052170 251.5856);
|
|
148
150
|
--border-strong: oklch(69.19219% 0.042050 253.1082);
|
|
149
151
|
--input: oklch(58.06940% 0.052170 251.5856);
|
|
152
|
+
--control-border: oklch(67.7% 0.0275 255.1);
|
|
150
153
|
--ring: oklch(82.28684% 0.088371 261.1846);
|
|
151
154
|
--primary: oklch(82.28684% 0.088371 261.1846);
|
|
152
155
|
--primary-foreground: oklch(25.20350% 0.047984 259.9119);
|
|
@@ -90,15 +90,19 @@
|
|
|
90
90
|
---------------------------------------------------------------------- */
|
|
91
91
|
--background: oklch(98.6% 0.004 106);
|
|
92
92
|
--foreground: oklch(24% 0.035 175);
|
|
93
|
-
|
|
93
|
+
/* Cards, fields and popovers sit at or above the page, never recessed into it:
|
|
94
|
+
a card darker than the page made every field read as a filled grey box. */
|
|
95
|
+
--card: oklch(99.7% 0.002 106);
|
|
94
96
|
--card-foreground: oklch(24% 0.035 175);
|
|
95
|
-
--popover: oklch(
|
|
97
|
+
--popover: oklch(99.7% 0.002 106);
|
|
96
98
|
--popover-foreground: oklch(24% 0.035 175);
|
|
97
99
|
--muted: oklch(93% 0.007 106);
|
|
98
100
|
--muted-foreground: oklch(49% 0.018 175);
|
|
99
101
|
--border: oklch(88.5% 0.005 106);
|
|
100
102
|
--border-strong: oklch(81% 0.008 130);
|
|
101
103
|
--input: oklch(88.5% 0.005 106);
|
|
104
|
+
/* The resting edge of a control whose outline IS the control (checkbox, radio, an off switch track). Held to WCAG 1.4.11 3:1 on every surface, unlike the quieter --input field outline. */
|
|
105
|
+
--control-border: oklch(59.9% 0.0149 175);
|
|
102
106
|
--ring: oklch(24% 0.035 175);
|
|
103
107
|
--primary: oklch(24% 0.035 175);
|
|
104
108
|
--primary-foreground: oklch(98.6% 0.004 106);
|
|
@@ -174,6 +178,7 @@
|
|
|
174
178
|
--border: oklch(36% 0.03 175);
|
|
175
179
|
--border-strong: oklch(40% 0.028 175);
|
|
176
180
|
--input: oklch(36% 0.03 175);
|
|
181
|
+
--control-border: oklch(58.7% 0.0158 172);
|
|
177
182
|
--ring: oklch(85% 0.015 170);
|
|
178
183
|
--primary: oklch(98.6% 0.004 106);
|
|
179
184
|
--primary-foreground: oklch(24% 0.035 175);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
The surface a message's content sits on: four treatments, an align-aware tail
|
|
5
|
+
The surface a message's content sits on: four treatments, an align-aware tail, a readable measure cap, and an optional row of reaction pills.
|
|
6
6
|
|
|
7
7
|
**Keywords:** chat bubble, message bubble, speech bubble, reactions, conversation, messaging, ai chat
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
|
|
|
14
14
|
|
|
15
15
|
- Do: Use `muted` for the other party and `primary` for the reader's own messages. Two treatments is the whole vocabulary a conversation needs to say who is speaking.
|
|
16
16
|
- Do: Reach for `variant="plain"` for long-form assistant output — rendered markdown, code blocks, tables. A tinted box around three paragraphs reads as a quote block rather than as a turn, and `plain` drops the measure cap with the surface.
|
|
17
|
-
- Do: Pass the same `align` you gave the enclosing Message, so the
|
|
17
|
+
- Do: Pass the same `align` you gave the enclosing Message, so the tail lands on the speaker's side.
|
|
18
18
|
- Do: Set `tail={false}` on the continuation bubbles in a grouped run — the tail marks where a speaker starts, and repeating it on every line makes a block of one person's messages look like four separate turns.
|
|
19
19
|
- Do: Let the register size it. Inside `<Density value="compact">` a Bubble sets itself in the workspace prose tier automatically — a docked agent panel needs no per-call-site sizing, and hardcoding one defeats the mechanism.
|
|
20
20
|
- Don't: Do not remove the measure cap to fill a wide window. ~42rem is a reading measure; a bubble spanning a 1600px viewport is a paragraph at 140 characters per line.
|
|
@@ -26,7 +26,7 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
|
|
|
26
26
|
|
|
27
27
|
| Part | Required | Description |
|
|
28
28
|
| --- | --- | --- |
|
|
29
|
-
| Bubble | yes | The surface itself. Holds the variant, the side, and the
|
|
29
|
+
| Bubble | yes | The surface itself. Holds the variant, the side, and the tail. |
|
|
30
30
|
| BubbleReactions | no | The row of reaction pills, just under the bubble at the message column's small gap, so it reads as belonging to that message rather than the next row. |
|
|
31
31
|
| BubbleReaction | no | One reaction pill — emoji, count (hidden at 1), and pressed state. |
|
|
32
32
|
|
|
@@ -42,8 +42,8 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
|
|
|
42
42
|
| Name | Type | Default | Description |
|
|
43
43
|
| --- | --- | --- | --- |
|
|
44
44
|
| variant | "muted" \| "primary" \| "outline" \| "plain" | "muted" | Surface treatment. `muted` for the other party, `primary` for the reader's own messages, `outline` for a quieter own-side, `plain` for long-form output that should not sit in a box (also drops the measure cap and padding). |
|
|
45
|
-
| align | "start" \| "end" | "start" | Which side of the conversation the bubble is on —
|
|
46
|
-
| tail | boolean | true |
|
|
45
|
+
| align | "start" \| "end" | "start" | Which side of the conversation the bubble is on — puts the tail on the speaker's side. Match it to the enclosing Message. |
|
|
46
|
+
| tail | boolean | true | Draw the tail: a small ear off the speaker-side top corner, which goes square to meet it. The same 5px in every theme's radius language, flat through fully pilled. The bubble keeps the ear's space either way, so turning it off for the continuation bubbles in a grouped run keeps the run aligned. |
|
|
47
47
|
| ...div props | ComponentPropsWithRef<"div"> | — | Spread onto the surface element. |
|
|
48
48
|
|
|
49
49
|
## Variants
|
|
@@ -39,7 +39,7 @@ Use CheckboxCard when an option needs more visual weight, supporting text, or ic
|
|
|
39
39
|
| onCheckedChange | (checked: boolean, eventDetails) => void | — | Fires with the next checked state (Base UI passes eventDetails second). Pair with `checked` for a controlled card; omit both to let it manage its own state. |
|
|
40
40
|
| title | ReactNode | — | The card's primary label (text-title-sm). |
|
|
41
41
|
| description | ReactNode | — | Optional supporting copy beneath the title (text-body-sm, muted). |
|
|
42
|
-
| icon | ReactNode | — | Optional
|
|
42
|
+
| icon | ReactNode | — | Optional glyph (a solid <Icon>) in the tone colour. Its presence switches to the two-dimensional card RadioCard uses — icon top-left, checkbox top-right, copy below — so the two cards match in one form. |
|
|
43
43
|
| tone | "neutral" \| "standard" \| "collectible" \| "edition" \| "info" \| "warning" | "neutral" | Semantic colour the checked state carries across the border, checkbox box, and icon. |
|
|
44
44
|
| value | string | — | Names the card inside a group-owned CheckboxGroup: the card is checked when the group's value array includes it. Also the value submitted with a form. Inside a CheckboxGroup, `name` stands in for `value` when `value` is omitted. |
|
|
45
45
|
| checked / onCheckedChange / defaultChecked / disabled | Base UI Checkbox.Root props | — | Standard checkbox state. Without a group-owned value, each card is independent — drive checked/onCheckedChange per item for multi-select. |
|
|
@@ -67,7 +67,7 @@ import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } f
|
|
|
67
67
|
### With stat row
|
|
68
68
|
|
|
69
69
|
```tsx
|
|
70
|
-
import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger,
|
|
70
|
+
import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } from "@sableui/react"
|
|
71
71
|
|
|
72
|
-
<ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><
|
|
72
|
+
<ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><div className='@container mt-4 border-t border-border pt-3'><dl className='sable-list-dl sable-list-dl-2'><dt>Posts</dt><dd>128</dd><dt>Followers</dt><dd>4.2k</dd></dl></div></ContextCardContent></ContextCard>
|
|
73
73
|
```
|
|
@@ -38,7 +38,7 @@ Use Dialog for focused decisions, short modal forms, confirmations, and content
|
|
|
38
38
|
| Name | Type | Description |
|
|
39
39
|
| --- | --- | --- |
|
|
40
40
|
| DialogContent | Base UI Popup props + size?: "default" \| "fullscreen" + showCloseButton?: boolean + scroll?: boolean + className | Backdrop + popup; portals to <body>. Composes DialogOverlay by default. size="fullscreen" is the media viewer: a near-opaque scrim (bg-overlay/95), an edge-to-edge popup that centres its children with no card chrome, motion-fade instead of motion-pop, and a safe-area-aware floating close button (Button variant="floating"); at that size showCloseButton defaults to true and scroll to false, and the media fits itself with max-h-full max-w-full object-contain. At the default size, showCloseButton (defaults to false) renders a built-in close (X) button in the top-end corner, and the header leaves room for it (Icon name="xmark"). The popup caps at the viewport height less a 1rem margin and wraps its children in a scrolling region, so a tall dialog scrolls instead of overflowing a viewport whose own scroll is locked; pass scroll={false} when the dialog owns its internal layout (a pinned header over a scrolling body) and supplies its own min-h-0 scroll region. |
|
|
41
|
-
| DialogOverlay | Base UI Backdrop props + size?: "default" \| "fullscreen" + className | The styleable scrim/backdrop — bg-overlay/
|
|
41
|
+
| DialogOverlay | Base UI Backdrop props + size?: "default" \| "fullscreen" + className | The styleable scrim/backdrop — bg-overlay/(--scrim-alpha) by default (50% light, 72% dark, so it dims a near-black page as visibly as a white one), near-opaque bg-overlay/95 at size="fullscreen". Composed into DialogContent (at the matching size) by default; exported for manual styling. |
|
|
42
42
|
| DialogTrigger | Base UI Dialog.Trigger props (render) | Element that opens the dialog. Compose via render prop. |
|
|
43
43
|
| DialogClose | Base UI Dialog.Close props (render) | Element that dismisses the dialog. Compose via render prop. |
|
|
44
44
|
| DialogHeader | ComponentProps<"div"> | Top section holding DialogTitle/DialogDescription. |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
The centered empty / error block: optional icon
|
|
5
|
+
The centered empty / error block: optional icon or error code, title, description and an action. tone="destructive" makes it an announced error, size="lg" a page-level state, surface="card" a self-contained card.
|
|
6
6
|
|
|
7
7
|
**Keywords:** empty, zero state, blank slate, no results, placeholder, onboarding, no data
|
|
8
8
|
|
|
@@ -33,12 +33,12 @@ Use EmptyState when a content area has no items, no search results, or no first-
|
|
|
33
33
|
|
|
34
34
|
| Name | Type | Default | Description |
|
|
35
35
|
| --- | --- | --- | --- |
|
|
36
|
-
| icon | ReactNode | — | Optional
|
|
36
|
+
| icon | ReactNode | — | Optional glyph above the title — an `<Icon />` takes the muted ink at 3xl; an image or illustration renders as given. |
|
|
37
37
|
| title | ReactNode | — | The headline. Short and direct. |
|
|
38
38
|
| description | ReactNode | — | Optional supporting line beneath the title. |
|
|
39
39
|
| action | ReactNode | — | Optional action below the copy — typically a `<Button />`. |
|
|
40
40
|
| code | ReactNode | — | A large figure above the title, such as an error code (404). |
|
|
41
|
-
| tone | "neutral" \| "destructive" | "neutral" | destructive is an error state: a destructive icon
|
|
41
|
+
| tone | "neutral" \| "destructive" | "neutral" | destructive is an error state: a destructive icon (a warning by default) and role="alert" so it is announced. |
|
|
42
42
|
| size | "md" \| "lg" | "md" | lg is page-level: the title is an h2 and the block takes more room. md sits in a panel, list or card. |
|
|
43
43
|
| surface | "none" \| "card" | "none" | card draws it as a Card, for an error or empty block standing on its own in a page. |
|
|
44
44
|
|
|
@@ -54,7 +54,7 @@ Use EmptyState when a content area has no items, no search results, or no first-
|
|
|
54
54
|
|
|
55
55
|
## Accessibility
|
|
56
56
|
|
|
57
|
-
- Decorative
|
|
57
|
+
- Decorative icons should not carry meaning the title doesn't already convey.
|
|
58
58
|
- Pair with an actionable next step so the zero-state is a path forward, not a dead end.
|
|
59
59
|
|
|
60
60
|
## Related
|
package/llms/components/kbd.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** stable
|
|
4
4
|
|
|
5
|
-
A keyboard key indicator. Renders a
|
|
5
|
+
A keyboard key indicator. Renders a raised <kbd> key cap sized from the surrounding text, so one Kbd fits body copy, menus and tooltips; KbdGroup sets one cap per key for a combo. `mod` renders the platform modifier (⌘ or Ctrl); `variant="ghost"` is the bare label for menu rows and tooltips.
|
|
6
6
|
|
|
7
7
|
**Keywords:** keyboard, shortcut, key, kbd
|
|
8
8
|
|
|
@@ -13,8 +13,10 @@ Use Kbd to display keyboard keys or shortcuts in help text, menus, and docs.
|
|
|
13
13
|
### Best practices
|
|
14
14
|
|
|
15
15
|
- Do: Use it only for literal keyboard input such as Cmd, Enter, or Esc.
|
|
16
|
-
- Do:
|
|
16
|
+
- Do: Show a combo as one cap per key inside KbdGroup — `<KbdGroup><Kbd mod /><Kbd>K</Kbd></KbdGroup>` — not several keys typed into one cap.
|
|
17
|
+
- Do: Use `variant="ghost"` where the shortcut sits on a surface of its own — a menu row's trailing slot, a tooltip — so it reads as a hint, not a second control.
|
|
17
18
|
- Don't: Do not use Kbd as a decorative badge or button replacement.
|
|
19
|
+
- Don't: Do not set a text size on a Kbd. It sizes itself from the text around it.
|
|
18
20
|
- Don't: Do not hardcode ⌘ or Ctrl. The modifier differs by platform; use `<Kbd mod />`, which renders ⌘ on macOS/iOS and Ctrl elsewhere, or Windows users read Mac keys.
|
|
19
21
|
|
|
20
22
|
## Props
|
|
@@ -22,17 +24,19 @@ Use Kbd to display keyboard keys or shortcuts in help text, menus, and docs.
|
|
|
22
24
|
| Name | Type | Default | Description |
|
|
23
25
|
| --- | --- | --- | --- |
|
|
24
26
|
| mod | boolean | false | Render the platform's shortcut modifier in place of children: ⌘ on macOS and iOS, Ctrl everywhere else. Server-rendered as Ctrl and corrected on hydration. |
|
|
27
|
+
| variant | "key" \| "ghost" | "key" | `key`: a raised cap on the surface it sits on, with a heavier bottom edge as the key's lip. `ghost`: the bare label, for menu rows, tooltips and other surfaces that are already a frame. |
|
|
25
28
|
| className | string | — | Merge additional classes onto the key cap. |
|
|
26
29
|
| ...props | ComponentPropsWithRef<"kbd"> | — | All native <kbd> attributes (children, ref, etc.). |
|
|
27
30
|
|
|
28
31
|
## Tokens used
|
|
29
32
|
|
|
30
|
-
`border`, `
|
|
33
|
+
`border`, `border-strong`, `background`, `muted-foreground`
|
|
31
34
|
|
|
32
35
|
## Accessibility
|
|
33
36
|
|
|
34
37
|
- Uses the semantic <kbd> element so assistive tech announces keyboard input.
|
|
35
|
-
-
|
|
38
|
+
- KbdGroup is itself a <kbd> around the per-key caps — the HTML spelling of one key combination — so the combo is announced as a single piece of input.
|
|
39
|
+
- Sized in em from the surrounding text, so it scales with the user's text size and never shrinks below 12px.
|
|
36
40
|
|
|
37
41
|
## Related
|
|
38
42
|
|
|
@@ -51,7 +55,15 @@ import { Kbd } from "@sableui/react"
|
|
|
51
55
|
### Combo
|
|
52
56
|
|
|
53
57
|
```tsx
|
|
54
|
-
import { Kbd,
|
|
58
|
+
import { Kbd, KbdGroup } from "@sableui/react"
|
|
55
59
|
|
|
56
|
-
<
|
|
60
|
+
<KbdGroup><Kbd mod /><Kbd>K</Kbd></KbdGroup>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### In a menu row
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Kbd, MenuItem } from "@sableui/react"
|
|
67
|
+
|
|
68
|
+
<MenuItem>Search<KbdGroup className="ms-auto"><Kbd variant="ghost" mod /><Kbd variant="ghost">K</Kbd></KbdGroup></MenuItem>
|
|
57
69
|
```
|
package/llms/components/pill.md
CHANGED
|
@@ -24,7 +24,7 @@ Use Pill for a row of filters or choices a person clicks to narrow or toggle wha
|
|
|
24
24
|
| --- | --- | --- | --- |
|
|
25
25
|
| selected | boolean | false | Marks the pill as chosen. Solid lifts onto the primary surface; outline takes a primary tint and soft primary edge. Sets aria-pressed. |
|
|
26
26
|
| appearance | "solid" \| "outline" | "solid" | solid for a filter row; outline for quiet chips that annotate other content (a reaction under a message, a tag on a card), where a filled chip would compete with what it annotates. |
|
|
27
|
-
| size | "md" \| "sm" | "md" |
|
|
27
|
+
| size | "md" \| "sm" | "md" | md sits on the density ladder's xs control height (28px); sm is 24px, for chips under or inside other content. |
|
|
28
28
|
| icon | ReactNode | — | Optional leading glyph (e.g. <Icon name="lock" />), rendered before the children with the base gap. |
|
|
29
29
|
|
|
30
30
|
## Variants
|
|
@@ -15,7 +15,7 @@ Use SideNav for vertical navigation inside a settings page, documentation area,
|
|
|
15
15
|
- Do: Use aria-label on SideNav when a page contains more than one nav landmark.
|
|
16
16
|
- Do: Group related items with SideNavGroup when the rail has more than one conceptual area.
|
|
17
17
|
- Do: Use size='lg' and activeVariant='sidebar' when the nav is part of persistent app chrome.
|
|
18
|
-
- Do:
|
|
18
|
+
- Do: Rows are rounded-sm, which nests in the usual rounded-lg p-2 frame. In any other rounded, padded frame give each item rounded-concentric-<outer>/<inset> — a same-radius row inset into a frame reads as a second system.
|
|
19
19
|
- Don't: Use SideNav for bottom mobile navigation; use BottomNav in AppShell's bottomNav slot.
|
|
20
20
|
- Don't: Mix content and sidebar active variants in the same rail unless individual items intentionally override the group.
|
|
21
21
|
|
|
@@ -32,7 +32,7 @@ Use Skeleton to reserve space and hint at the shape of content while data is loa
|
|
|
32
32
|
- **arrival** · Root · _no motion_ — A skeleton stands in for content that has not arrived; fading the placeholder in adds a wait before the wait.
|
|
33
33
|
- **response** · Root · _no motion_ — Nothing here is interactive. A skeleton that reacted to the pointer would invite a click on a thing that cannot be clicked.
|
|
34
34
|
- **transition** · Root · _no motion_ — The skeleton has one state. Swapping it for real content is the parent's job, and the parent decides whether that swap is animated.
|
|
35
|
-
- **activity** · Root · `motion-shimmer` — The sweep is the entire point of the component: it is what separates 'still loading' from 'loaded, and empty'. A static grey block is indistinguishable from a broken layout, which is why users reload the page.
|
|
35
|
+
- **activity** · Root · `motion-shimmer` — The sweep is the entire point of the component: it is what separates 'still loading' from 'loaded, and empty'. A static grey block is indistinguishable from a broken layout, which is why users reload the page. It is one band of light against the viewport, so every skeleton in a loading layout lights in sequence as a single wave rather than each block flashing at its own speed; each pass eases in, glides and settles, then rests off-screen before the next.
|
|
36
36
|
|
|
37
37
|
## Accessibility
|
|
38
38
|
|
|
@@ -12,7 +12,7 @@ Use ThemeSelector wherever a person picks a theme. The default `cards` layout is
|
|
|
12
12
|
|
|
13
13
|
### Best practices
|
|
14
14
|
|
|
15
|
-
- Do: Use layout="list" in a popover, a menu, or a docked panel. Rows sit on the --control-sm ladder and still show each theme's swatch strip, so the choice stays visible rather than described. Rows take `sable-frame-inner`, so host the list in a sable-frame (
|
|
15
|
+
- Do: Use layout="list" in a popover, a menu, or a docked panel. Rows sit on the --control-sm ladder and still show each theme's swatch strip, so the choice stays visible rather than described. Rows take `sable-frame-inner`, so host the list in a sable-frame (`sable-frame sable-surface sable-surface-popover sable-frame-popover` — PopoverContent is not one; give it that class list, or its p-4 leaves the rows unnested) and their corners nest concentrically.
|
|
16
16
|
- Do: Pass mode={useSableTheme().mode} so every preview renders in the colour mode actually in use, rather than in each theme's manifest default.
|
|
17
17
|
- Do: Load each theme's fonts in the host app. Every card carries a type specimen in that theme's own face so people can compare typography as well as color. A theme whose fonts are not loaded falls back to system-ui, and its specimen stops telling the truth.
|
|
18
18
|
- Don't: Add a viewport breakpoint to force a column count — `className="sm:grid-cols-1"` or similar. `sm:` keys off the VIEWPORT, not this component's container, so on a wide screen it does nothing for a narrow parent. The cards grid already reflows on its own width via auto-fit.
|
|
@@ -30,7 +30,7 @@ Each step is 4px apart, so common insets land on a real step. Themes and the cus
|
|
|
30
30
|
## Rules
|
|
31
31
|
|
|
32
32
|
1. **Nested corners are concentric: inner radius = outer radius − inset. Write it as rounded-concentric-<outer>/<inset>, not a hardcoded step.** A 12px popover with 4px padding takes 8px items. The utility computes the subtraction from the live tokens, so the pair stays concentric when a theme or the customizer rescales the radius ladder; a hardcoded rounded-sm drifts.
|
|
33
|
-
2. **A list of rows inside a rounded popup uses popup-inset on the popup, rounded-popup-item on the rows and popup-bleed on a separator.** All three derive from the popup's radius: the inset grows to a third of it (never below --spacing(1), so density moves it too), the rows take what is left, and a separator bleeds by exactly the inset. A rounder look gets more room instead of rows that round into pills
|
|
33
|
+
2. **A list of rows inside a rounded popup uses popup-inset on the popup, rounded-popup-item on the rows and popup-bleed on a separator.** All three derive from the popup's radius: the inset grows to a third of it (never below --spacing(1), so density moves it too), the rows take what is left after the inset and the 1px border, and a separator bleeds by exactly the inset. A rounder look gets more room instead of rows that round into pills; the default ladder lands on a 4px inset and 7px rows. *Enforced by sable-lint: popup-separator-bleed.*
|
|
34
34
|
3. **A Button, Pill or Badge gets its look from a variant, never from override classes.** border, bg, rounded, ring and shadow classes on a component fight the variant's own hover, selected and focus states, and give one call site a look no other site can ask for. Use the variant that exists (Button variant="floating" over content, "on-color" on a fill; Pill appearance="outline"; Badge appearance and tone) or add one to the component. *Enforced by sable-lint: component-restyle.*
|
|
35
35
|
4. **The outer radius is always at least the inner radius.** If the inset is larger than the outer radius, square the inner element or step the container up.
|
|
36
36
|
5. **Never mix radii within one component.** A component reads as one object when its corners agree.
|