@tanishqkancharla/maui 0.0.21 → 0.0.22
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/package.json +1 -1
- package/skills/maui/SKILL.md +64 -57
- package/skills/maui/references/apps/ai-chat.md +32 -0
- package/skills/maui/references/apps/calendar.md +46 -0
- package/skills/maui/references/apps/email-client.md +27 -0
- package/skills/maui/references/components/avatar.md +9 -0
- package/skills/maui/references/components/badge.md +9 -0
- package/skills/maui/references/components/buttons.md +57 -0
- package/skills/maui/references/components/code.md +11 -0
- package/skills/maui/references/components/crossfade.md +16 -0
- package/skills/maui/references/components/editor.md +22 -0
- package/skills/maui/references/components/form-controls.md +49 -0
- package/skills/maui/references/components/fuzzy-string.md +13 -0
- package/skills/maui/references/components/icons.md +16 -0
- package/skills/maui/references/components/layout-utilities.md +50 -0
- package/skills/maui/references/components/list-box.md +22 -0
- package/skills/maui/references/components/loading-screen.md +17 -0
- package/skills/maui/references/components/menu.md +17 -0
- package/skills/maui/references/components/prose.md +39 -0
- package/skills/maui/references/components/select.md +23 -0
- package/skills/maui/references/components/table.md +41 -0
- package/skills/maui/references/components/text.md +14 -0
- package/skills/maui/references/components/thinking.md +12 -0
- package/skills/maui/references/components/tooltip.md +15 -0
- package/skills/maui/references/patterns/assistant-message.md +23 -0
- package/skills/maui/references/patterns/inbox.md +36 -0
- package/skills/maui/references/patterns/message-list.md +23 -0
- package/skills/maui/references/patterns/sidebar.md +38 -0
- package/skills/maui/references/tokens.md +229 -0
package/package.json
CHANGED
package/skills/maui/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maui
|
|
3
|
-
description: Conventions and design constraints for consuming the Maui design system. Use when building UI with Maui tokens, components, or purse-styles
|
|
3
|
+
description: Conventions and design constraints for consuming the Maui design system. Use when building UI with Maui tokens, components, patterns, or purse-styles. Prefer this skill and its references for composition knowledge.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Maui
|
|
@@ -34,14 +34,32 @@ The published package exposes:
|
|
|
34
34
|
- `"maui/src"` — TypeScript source barrel
|
|
35
35
|
- `"maui/src/*"` — TypeScript source for deep imports
|
|
36
36
|
- `"maui/skills/maui"` — this skill file
|
|
37
|
+
- `"maui/skills/maui/*"` — reference files next to this skill
|
|
37
38
|
|
|
38
39
|
`MauiProvider` sets up theme (`data-theme` / `color-scheme`), `PurseProvider`, design-system globals, and the focus UI database used by Button/Dialog.
|
|
39
40
|
|
|
41
|
+
## How to learn Maui
|
|
42
|
+
|
|
43
|
+
Prefer this skill for composition knowledge — how to assemble tokens, components, patterns, and apps. Reach for package source (`"maui/src/patterns"`, `"maui/src/apps"`, or an install under `node_modules`) when you need a detail the skill does not cover.
|
|
44
|
+
|
|
45
|
+
Before designing or implementing new UI:
|
|
46
|
+
|
|
47
|
+
1. Read the constraints in this file.
|
|
48
|
+
2. Open the matching reference (same grouping as the [gallery nav](https://maui.tanishqkancharla.dev)) and reuse its structure, tokens, and interactions.
|
|
49
|
+
|
|
50
|
+
| Need | Read |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| Tokens, `purse-styles`, motion, layout, theme | [references/tokens.md](references/tokens.md) |
|
|
53
|
+
| A component | [Components](#components) |
|
|
54
|
+
| A pattern | [Patterns](#patterns) |
|
|
55
|
+
| An app | [Apps](#apps) |
|
|
56
|
+
|
|
57
|
+
Patterns, demo apps, and the gallery `Panel` preview surface are **not** part of the `"maui"` package barrel. Prefer the recipes in those references and rebuild with barrel exports (`Button`, `Flex`, `text(...)`, …).
|
|
58
|
+
|
|
40
59
|
## Design constraints
|
|
41
60
|
|
|
42
|
-
- Before designing or implementing new UI with Maui components, read the closest example under `src/apps/` or `src/patterns/`. Reuse its structure, components, tokens, and interactions.
|
|
43
61
|
- Hover backgrounds have no transitions. Hover fills (`backgroundColor.elementHover`, quiet-button washes, list/row highlights) snap instantly. Do not animate `background` / `background-color` on hover with `motion.standard(...)` or a CSS `transition`. Other motion (tooltips, transforms) is fine.
|
|
44
|
-
- Simple apps default to a `proseMaxWidth` column (`72ch`) centered in their container: `width: "100%"`, `maxWidth: proseMaxWidth`, `marginInline: "auto"`. `
|
|
62
|
+
- Simple apps default to a `proseMaxWidth` column (`72ch`) centered in their container: `width: "100%"`, `maxWidth: proseMaxWidth`, `marginInline: "auto"`. `sizing.contentWidth` is the same measure. Use this for single-column tools, settings, forms, and reading layouts. Multi-pane or full-bleed apps (inbox, calendar, IDE) are the exception.
|
|
45
63
|
- Always design empty states. Every list, inbox, search result, or collection needs an intentional empty composition (copy and an optional action), never a blank panel.
|
|
46
64
|
|
|
47
65
|
## Theme FOUC
|
|
@@ -84,65 +102,54 @@ import { Text as TextIcon } from "maui/icons"
|
|
|
84
102
|
<TextIcon size="md" />
|
|
85
103
|
```
|
|
86
104
|
|
|
87
|
-
`size` uses the same t-shirt scale as `text(...)` (`2xs`–`xl`, default `sm`). Stroke and fill use `currentColor`. Icons that share a root export name (`Text`, `Badge`, `Switch`,
|
|
105
|
+
`size` uses the same t-shirt scale as `text(...)` (`2xs`–`xl`, default `sm`). Stroke and fill use `currentColor`. Icons that share a root export name (`Text`, `Badge`, `Switch`, `H1`, `H2`, `H3`, `Link`, `Menu`, `Code`, `Blockquote`, `Padding`, `SearchField`) are `TextIcon` / `BadgeIcon` / … from `"maui"`, or the original name from `"maui/icons"` / `Icons.Text`.
|
|
88
106
|
|
|
89
107
|
## Components
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- `Text` — size / weight / color / `monospace` span
|
|
94
|
-
- `H1`–`H4`, `P`, `Label`, `Blockquote`, `Ul`, `Ol`, `Li`, `Link`
|
|
95
|
-
- `Prose` — long-form rhythm; headings switch to the prose scale inside it
|
|
96
|
-
- `Editor` — TipTap markdown surface (CommonMark shortcuts, `proseHtml` type) with no chrome; wrap it for padding, elevation, and actions
|
|
97
|
-
|
|
98
|
-
### Form controls
|
|
99
|
-
|
|
100
|
-
- `Button` — `variant` is `"default"` | `"quiet"` | `"primary"`; `variantColor` is a palette name, or an opaque hex / `rgb()` string (`#${string}` or `rgb(`). Primary palettes fill step 9 (hover 10) with light text (`onAccent`, or step 12 on amber/lime/mint/sky/yellow). A CSS color is used as the fill (alpha is dropped; hover is `l - 0.04`; text is white or near-black from lightness). The edge is `tintedSubtle`. Quiet + color uses a 3.5% wash of the fill (hover 7%). Disable with React Aria’s `isDisabled` (maps to the native `disabled` attribute; no parallel `disabled` React prop).
|
|
101
|
-
- `TextField`, `SearchField`, `NumberField`, `QuietTextField`
|
|
102
|
-
- `Checkbox`, `Switch`, `Slider`
|
|
103
|
-
- `RadioOptionGroup` / `RadioOption`
|
|
104
|
-
- `Select` / `SelectItem`
|
|
105
|
-
|
|
106
|
-
### Collections and overlays
|
|
107
|
-
|
|
108
|
-
- `ListBox` / `ListBoxItem`
|
|
109
|
-
- `MenuTrigger` / `Menu` / `MenuItem`
|
|
110
|
-
- `Tooltip`
|
|
111
|
-
- `CollectionPopover` — shared popover used by Select and Menu
|
|
112
|
-
- `Overlay`, `Dialog`
|
|
113
|
-
|
|
114
|
-
### Display
|
|
115
|
-
|
|
116
|
-
- `Avatar`
|
|
117
|
-
- `Badge`
|
|
118
|
-
- `Code`, `Kbd`, `CodeBlock`
|
|
119
|
-
- `Table` — React Aria table. `TableHeader` contains `TableHead` columns directly (no `TableRow`). Mark the identifying column with `isRowHeader` (required; usually the name/id column, not a leading checkbox or drag handle). When `selectionMode` is `"multiple"`, `TableHeader` and `TableRow` insert a leading checkbox column (`Checkbox slot="selection"`). `align` on `TableHead` / `TableCell` is `"start"` | `"center"` | `"end"`. `TableFooter` fills with `colors.gray[2]`. Place `TableCaption` after `Table`. `TableBody` renders “No results.” when empty; pass `renderEmptyState` to replace it.
|
|
120
|
-
- `FuzzyString` — highlight segments; takes a match result, not a plain string
|
|
121
|
-
- `Thinking` — 3×3 Game of Life indicator; reseeds when the board dies or loops
|
|
122
|
-
- `Crossfade` — when `contentKey` changes, the previous view exits in `direction` (`up` | `down` | `left` | `right`) and the next view enters from the opposite side. `contentKey` is required. Do not put `key` on `Crossfade` itself or the exit is skipped.
|
|
123
|
-
- `LoadingScreen` — fills available width and height. Optional `progressLabel`. Label at start fades Thinking (accent, small / `0.8em`) and the label (accent, weight 500, trailing `...`) in together; later label changes Crossfade up. With no label at start, Thinking waits 2s before fading in; a label before 2s fades both in immediately; a label after 2s animates in and shifts Thinking so the pair stays centered.
|
|
124
|
-
|
|
125
|
-
## Reference: patterns and apps
|
|
126
|
-
|
|
127
|
-
Patterns, demo apps, and the gallery `Panel` preview surface are not part of the `"maui"` package barrel. Read the closest example before designing or implementing new UI, and reuse its structure, components, tokens, and interactions (also available via `"maui/src/..."`):
|
|
128
|
-
|
|
129
|
-
### Patterns — `src/patterns/`
|
|
130
|
-
|
|
131
|
-
| Path | Role |
|
|
132
|
-
| --- | --- |
|
|
133
|
-
| `src/patterns/AssistantMessage.tsx` | Streaming markdown reply (Streamdown + Maui prose) |
|
|
134
|
-
| `src/patterns/Sidebar.tsx` | App sidebar chrome |
|
|
135
|
-
| `src/patterns/Inbox.tsx` | Mail inbox layout |
|
|
136
|
-
| `src/patterns/MessageList.tsx` | Message list rows |
|
|
137
|
-
|
|
138
|
-
### Apps — `src/apps/`
|
|
109
|
+
Gallery order. Props and composition live in the linked file.
|
|
139
110
|
|
|
140
|
-
|
|
|
111
|
+
| Page | Reference |
|
|
141
112
|
| --- | --- |
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
113
|
+
| Avatar | [avatar.md](references/components/avatar.md) |
|
|
114
|
+
| Badge | [badge.md](references/components/badge.md) |
|
|
115
|
+
| Buttons | [buttons.md](references/components/buttons.md) (`Button`, `Overlay`, `Dialog`) |
|
|
116
|
+
| Prose | [prose.md](references/components/prose.md) (`Prose`, `H1`–`H4`, `P`, lists, `Label`, `Link`) |
|
|
117
|
+
| Editor | [editor.md](references/components/editor.md) |
|
|
118
|
+
| Thinking | [thinking.md](references/components/thinking.md) |
|
|
119
|
+
| Crossfade | [crossfade.md](references/components/crossfade.md) |
|
|
120
|
+
| Loading screen | [loading-screen.md](references/components/loading-screen.md) |
|
|
121
|
+
| Text | [text.md](references/components/text.md) |
|
|
122
|
+
| Form controls | [form-controls.md](references/components/form-controls.md) |
|
|
123
|
+
| Select | [select.md](references/components/select.md) |
|
|
124
|
+
| List box | [list-box.md](references/components/list-box.md) |
|
|
125
|
+
| Table | [table.md](references/components/table.md) |
|
|
126
|
+
| Menu | [menu.md](references/components/menu.md) |
|
|
127
|
+
| Tooltip | [tooltip.md](references/components/tooltip.md) |
|
|
128
|
+
| Layout utilities | [layout-utilities.md](references/components/layout-utilities.md) |
|
|
129
|
+
| FuzzyString | [fuzzy-string.md](references/components/fuzzy-string.md) |
|
|
130
|
+
| Icons | [icons.md](references/components/icons.md) |
|
|
131
|
+
| Code | [code.md](references/components/code.md) |
|
|
132
|
+
|
|
133
|
+
## Patterns
|
|
134
|
+
|
|
135
|
+
Not on the `"maui"` barrel. Prefer the recipes; rebuild with barrel exports in consuming apps. Hover fills snap; empty collections get copy.
|
|
136
|
+
|
|
137
|
+
| Page | Role | Reference |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| Inbox | Mail thread list with unread dot, hover actions, selection | [inbox.md](references/patterns/inbox.md) |
|
|
140
|
+
| Message list | Thread of raised message cards (avatar + Prose body) | [message-list.md](references/patterns/message-list.md) |
|
|
141
|
+
| Assistant message | Streaming markdown reply (Streamdown + Maui prose + CodeBlock) | [assistant-message.md](references/patterns/assistant-message.md) |
|
|
142
|
+
| Sidebar | 240px nav: sections, active item, optional icon and trailing badge | [sidebar.md](references/patterns/sidebar.md) |
|
|
143
|
+
|
|
144
|
+
## Apps
|
|
145
|
+
|
|
146
|
+
Not on the `"maui"` barrel. Prefer these layouts; rebuild with barrel exports.
|
|
147
|
+
|
|
148
|
+
| Page | Role | Reference |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| Email client | Two-pane inbox + reading pane | [email-client.md](references/apps/email-client.md) |
|
|
151
|
+
| AI chat | Mock streaming chat (Editor + AssistantMessage + Thinking) | [ai-chat.md](references/apps/ai-chat.md) |
|
|
152
|
+
| Calendar | Three-pane schedule (mini month, week grid, event details) | [calendar.md](references/apps/calendar.md) |
|
|
146
153
|
|
|
147
154
|
## License
|
|
148
155
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# AI chat
|
|
2
|
+
|
|
3
|
+
Gallery: `/apps/ai-chat`. Not on the `"maui"` barrel. Prefer this layout; rebuild with barrel components. Package source: `"maui/src/apps/AiChat/"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
Uses [Editor](../components/editor.md), [Assistant message](../patterns/assistant-message.md), [Thinking](../components/thinking.md).
|
|
6
|
+
|
|
7
|
+
Column shell: outline border, `radius.lg`, `minHeight` ~560px / `maxHeight` ~720px, `overflow: hidden`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[ scrollable feed: user bubbles + assistant rows ]
|
|
11
|
+
[ composer: Editor in a raised shell + send ]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Feed** (`role="log"` `aria-label="Conversation"` `aria-relevant="additions"`)
|
|
15
|
+
|
|
16
|
+
- Padding `x: 8, y: 6`, column `gap` 6, `flex: 1`, `overflowY: auto`. Scroll to bottom when messages change.
|
|
17
|
+
- **User**: `justify: end`, bubble `maxWidth: 80%`, `prose("sm")` paragraph, `radius.md`, `shadow.subtle`, `background.element`, `pre-wrap`.
|
|
18
|
+
- **Assistant**: full-width column.
|
|
19
|
+
1. Optional tool-call lines (lowContrast, ellipsis): `Read path`, `Wrote path`, `$ command` (command in `monospace`).
|
|
20
|
+
2. AssistantMessage `size="sm"` `isAnimating={streaming}` — override `maxWidth: none` so it fills the pane.
|
|
21
|
+
3. While streaming, a muted `Thinking` + “Thinking” (`xs` / lowContrast, row `gap` 4).
|
|
22
|
+
|
|
23
|
+
**Composer**
|
|
24
|
+
|
|
25
|
+
- Outer padding `x: 6, top: 4, bottom: 6`.
|
|
26
|
+
- Inner shell: `radius.lg`, `shadow.subtle`, `background.element`, padding `x: 4, y: 3`, column.
|
|
27
|
+
- `<Editor size="sm" onSubmit={send} editable={!streaming} placeholder="Message the assistant…" />`
|
|
28
|
+
- Send: circular quiet-ish `Button` (`radius.circle`, no box-shadow, `gray[3]` fill so it reads on `element`), icon `ArrowUp`, `aria-label="Send"`, disabled while streaming or empty. ⌘/Ctrl+Enter also sends (`Editor onSubmit`).
|
|
29
|
+
|
|
30
|
+
Mock streaming (no model required for a demo): wait ~3s (Thinking), optionally emit tool-call rows, then append markdown in small chunks. Keep `animated` on Streamdown the whole time; only `isAnimating` flips off when the last chunk lands.
|
|
31
|
+
|
|
32
|
+
Empty conversation: still show a welcome **assistant** markdown message, not a blank feed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
Gallery: `/apps/calendar`. Not on the `"maui"` barrel. Prefer this layout; rebuild with barrel components. Package source: `"maui/src/apps/Calendar/"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
**Three panes** (sidebar can collapse):
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
[ 196px sidebar ] [ flexible week grid ] [ ~200–220px details ]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Shell: `radius.lg`, `shadow.subtle`, `minHeight` ~640px, `backgroundColor.app`, `overflow: hidden`. Without sidebar: `minmax(0, 1fr) minmax(200px, 220px)`.
|
|
12
|
+
|
|
13
|
+
This is a **full-bleed schedule**, not a prose column.
|
|
14
|
+
|
|
15
|
+
## Sidebar
|
|
16
|
+
|
|
17
|
+
Raised `background.element` + `shadow.subtle`, column `gap` 6, padding 4.
|
|
18
|
+
|
|
19
|
+
- Toolbar: quiet icon buttons — hide sidebar (`Sidebar` icon), new event (`Plus`). Wrap in `Tooltip`.
|
|
20
|
+
- **Mini month**: 7-column weekday initials (`2xs`), 24px circular day buttons. Today = `accent[9]` + `onAccent` text. Selected (not today) = `grayAlpha[4]`. Outside-month = `gray[8]`. Hover = `elementHover` (no background transition on the chrome; day buttons may use `motion.standard` on color only). Prev/next month chevrons.
|
|
21
|
+
- `TextField` placeholder “Meet with...”.
|
|
22
|
+
- Account groups: xs/500/lowContrast email, then source rows (10px color swatch + name + hover-revealed quiet `Eye` to hide the calendar). Hidden sources drop to 45% opacity.
|
|
23
|
+
- Footer: quiet “Add calendar account” / “Add Notion database”.
|
|
24
|
+
|
|
25
|
+
Event colors: `accent` | `green` | `orange` | `pink` — fill step 3, text 11, selected fill 9 + `onAccent`.
|
|
26
|
+
|
|
27
|
+
## Week grid
|
|
28
|
+
|
|
29
|
+
- Header: month title (`xl`/700) + `Avatar` + `Select` for 1/3/5/7-day view + `Today` + prev/next quiet chevrons (`Tooltip`).
|
|
30
|
+
- Sticky **all-day** row, then a vertically scrolling 24h grid (`HOUR_HEIGHT` 52px).
|
|
31
|
+
- Timed events: absolutely positioned blocks (`radius.sm`, padding 3/1, title + time range `2xs` lowContrast). Click a block to select; click empty grid to **create** a 30-minute event (snap 15 minutes).
|
|
32
|
+
- Today column: `accentAlpha[2]` wash. Now line: 2px `accent[9]` + circle on the today column.
|
|
33
|
+
- Time gutter ~36px, `2xs` mono labels.
|
|
34
|
+
|
|
35
|
+
## Details pane
|
|
36
|
+
|
|
37
|
+
Raised column, padding 8.
|
|
38
|
+
|
|
39
|
+
- `SearchField` “Search events”. Matches render as a list of title (`FuzzyString`) + meta; click jumps to that event and date.
|
|
40
|
+
- Selected event: title `lg`/600, time range or “All day”, duration, calendar swatch + name, `Button` “Add meeting note”.
|
|
41
|
+
- None selected: “Select an event to see details, or click the grid to create one.”
|
|
42
|
+
- Footer: “Useful shortcuts” with `Kbd` (`T` today, `←`/`→` range, `` ` `` sidebar). Ignore shortcuts when focus is in an input.
|
|
43
|
+
|
|
44
|
+
Hotkeys (no modifiers): `t` today, arrows shift the visible range by `viewDays`, backtick toggles sidebar.
|
|
45
|
+
|
|
46
|
+
Empty search: show nothing extra (the selected-event block and shortcuts remain). Empty calendar: still show the grid + the details empty copy.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Email client
|
|
2
|
+
|
|
3
|
+
Gallery: `/apps/email-client`. Not on the `"maui"` barrel. Prefer this layout; rebuild with barrel components. Package source: `"maui/src/apps/EmailClient/"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
Patterns: [Inbox](../patterns/inbox.md), [Message list](../patterns/message-list.md).
|
|
6
|
+
|
|
7
|
+
**Two panes**, full-bleed (not a 72ch column).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[ 240px inbox ] [ flexible reading pane ]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Shell: `radius.lg`, `shadow.subtle`, `minHeight` ~640px, `overflow: hidden`, `backgroundColor.app`, grid `240px minmax(0, 1fr)`.
|
|
14
|
+
|
|
15
|
+
**Inbox pane**
|
|
16
|
+
|
|
17
|
+
- Column, padding step 2, `minHeight: 0`, list `overflowY: auto`.
|
|
18
|
+
- Header: `H3` “Inbox” (or mailbox name), padded to match multi-line row text (`pt` 6, `px` 4).
|
|
19
|
+
- Body: InboxMultiLine with `threads`, `selectedId`, `onSelectThread`. Reset the pattern’s default `marginTop` so it sits flush under the header.
|
|
20
|
+
|
|
21
|
+
**Reading pane**
|
|
22
|
+
|
|
23
|
+
- Padding `x: 16, y: 8`, column `gap` 6, `overflowY: auto`.
|
|
24
|
+
- Selected: `H2` subject, then MessageList of that thread’s messages (`sender`, `timestamp`, optional `edited`, `P` children).
|
|
25
|
+
- None selected: lowContrast “Select a thread to read messages.”
|
|
26
|
+
|
|
27
|
+
State: one `selectedThreadId`. Threads carry `id / senders / subject / snippet / time / unread?` plus a `messages[]` array. Unread dots and hover toolbars come from the inbox pattern.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/avatar`. Import from `"maui"`. Tokens: [tokens.md](../tokens.md).
|
|
4
|
+
|
|
5
|
+
Initials from `name` (first letters of up to two words). `size` matches `TextSize` (default `sm` = 18px). Color is hashed from the name (accent / green / orange / pink). `aria-hidden`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Avatar name="Maya Chen" size="lg" />
|
|
9
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Buttons
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/buttons`. Import from `"maui"`. Overlay and Dialog live on this page too.
|
|
4
|
+
|
|
5
|
+
## `Button`
|
|
6
|
+
|
|
7
|
+
Native `<button>` (default `type="button"`). Height 28px, `shadow.subtle`, `radius` 4px.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<Button>Save</Button>
|
|
11
|
+
<Button variant="quiet">Cancel</Button>
|
|
12
|
+
<Button variant="primary" variantColor="blue">Create</Button>
|
|
13
|
+
<Button variant="primary" variantColor="#6366f1">Indigo</Button>
|
|
14
|
+
<Button isDisabled>Wait</Button>
|
|
15
|
+
<Button aria-label="Search"><Search size="sm" /></Button>
|
|
16
|
+
<Button>
|
|
17
|
+
<Plus size="sm" />
|
|
18
|
+
Create
|
|
19
|
+
</Button>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Prop | Notes |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `variant` | `"default"` (raised element) \| `"quiet"` (no shadow, transparent) \| `"primary"` (solid fill) |
|
|
25
|
+
| `variantColor` | Palette name or opaque `#hex` / `rgb(...)`. Primary default is `"accent"`. Quiet ignores color unless set |
|
|
26
|
+
| `isDisabled` | React Aria name. Maps to native `disabled`. **No `disabled` React prop** |
|
|
27
|
+
| `children` | Text is wrapped for cap-height trim; SVG icons sit beside text. Icon-only needs `aria-label` |
|
|
28
|
+
|
|
29
|
+
Primary: fill step 9, hover 10, light text (`onAccent`, or step 12 on amber/lime/mint/sky/yellow). Custom CSS fill drops alpha; hover is `l - 0.04`; text is white or near-black from lightness. Edge is `tintedSubtle`. Quiet + color: 3.5% wash (hover 7%).
|
|
30
|
+
|
|
31
|
+
`useButton(props)` is exported for custom focus-tracked buttons; prefer `Button`.
|
|
32
|
+
|
|
33
|
+
## `Overlay`
|
|
34
|
+
|
|
35
|
+
Full-viewport portal. `onClickOutside` fires when the backdrop itself is the mousedown target.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
{open && (
|
|
39
|
+
<Overlay onClickOutside={() => setOpen(false)}>
|
|
40
|
+
{/* centered panel */}
|
|
41
|
+
</Overlay>
|
|
42
|
+
)}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## `Dialog`
|
|
46
|
+
|
|
47
|
+
`Overlay` + focus lock + scale/fade in. Children are the dialog body (padding 32px, `background.element`, 4px radius). Not React Aria Dialog — you own title and close.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
{open && (
|
|
51
|
+
<Dialog onClickOutside={() => setOpen(false)}>
|
|
52
|
+
<H3>Confirm</H3>
|
|
53
|
+
<P>This cannot be undone.</P>
|
|
54
|
+
<Button onClick={() => setOpen(false)}>Close</Button>
|
|
55
|
+
</Dialog>
|
|
56
|
+
)}
|
|
57
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Code
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/code`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Code>proseMaxWidth</Code>
|
|
7
|
+
<Kbd>⌘</Kbd><Kbd>K</Kbd>
|
|
8
|
+
<CodeBlock lang="ts">{`const n = 1`}</CodeBlock>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`CodeBlock` highlights with Shiki using the resolved theme. `lang` is required. Unsupported langs / pending highlight render a plain `<pre>`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Crossfade
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/crossfade`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
When `contentKey` changes, the previous view exits in `direction` while the next view enters from the opposite side. Views overlap (`AnimatePresence` `mode="sync"`). Offset is spacing step 6. The root clips overflow.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Crossfade direction="left" contentKey={slide.id}>
|
|
9
|
+
<Text size="lg">{slide.title}</Text>
|
|
10
|
+
</Crossfade>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `direction` is required: `"up" | "down" | "left" | "right"`. Gallery playground starts on `"left"`.
|
|
14
|
+
- `contentKey` is required. **Do not** put `key` on `Crossfade` itself or the exit is skipped.
|
|
15
|
+
- Timing is internal: enter is a spring (`visualDuration` 0.3, `bounce` 0.2); exit uses `motionDurationMs` (80ms) and ease-in-out. There are no public motion props.
|
|
16
|
+
- Reduced motion: opacity only.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Editor
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/editor`. Import from `"maui"`. Composer chrome: [AI chat](../apps/ai-chat.md).
|
|
4
|
+
|
|
5
|
+
Unchromed TipTap markdown surface. Wrap it for padding, elevation, and actions.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Editor
|
|
9
|
+
content={draft}
|
|
10
|
+
onChange={setDraft}
|
|
11
|
+
onSubmit={send} // ⌘/Ctrl+Enter
|
|
12
|
+
placeholder="Write…"
|
|
13
|
+
size="sm" // ProseSize: sm | md | lg
|
|
14
|
+
editable={!streaming}
|
|
15
|
+
aria-label="Compose message"
|
|
16
|
+
/>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `content` is markdown; updates apply when the string changes.
|
|
20
|
+
- `onChange` receives markdown.
|
|
21
|
+
- CommonMark shortcuts (`#`, `**`, `-`, `>`).
|
|
22
|
+
- No chrome, no submit button — parent owns those.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Form controls
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/form-controls`. Import from `"maui"`. [Select](select.md) has its own page.
|
|
4
|
+
|
|
5
|
+
All fields are 28px tall, full width of the parent, `shadow.subtle` except `QuietTextField`. They take React Aria field props (`value`, `onChange`, `placeholder`, `isDisabled`, `isInvalid`, `aria-label`, `id`, …).
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<TextField aria-label="Title" placeholder="Title" value={v} onChange={setV} />
|
|
9
|
+
<SearchField aria-label="Search" value={q} onChange={setQ} /> // clear button when non-empty
|
|
10
|
+
<NumberField aria-label="Count" value={n} onChange={setN} minValue={0} maxValue={10} />
|
|
11
|
+
<QuietTextField aria-label="Filter" placeholder="Filter" value={f} onChange={setF} />
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Invalid (unfocused) adds a red 1px ring on top of `shadow.subtle`. Placeholders are italic `gray[8]`. Cap width in the parent (`maxWidth: 240px` is the gallery default).
|
|
15
|
+
|
|
16
|
+
## `Checkbox`
|
|
17
|
+
|
|
18
|
+
Controlled only: `checked` + `setChecked`. Optional `label`.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<Checkbox label="Subscribe" checked={on} setChecked={setOn} />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `Switch`
|
|
25
|
+
|
|
26
|
+
Controlled: `selected` + `onChange`. **`label` is required.**
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<Switch label="Enable notifications" selected={on} onChange={setOn} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## `Slider`
|
|
33
|
+
|
|
34
|
+
`label` required. React Aria slider props: `value`, `onChange`, `minValue`, `maxValue`, `step`. Default width 240px. Shows the formatted value as `<output>`.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<Slider label="Volume" value={v} onChange={setV} minValue={0} maxValue={100} />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## `RadioOptionGroup` / `RadioOption`
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<RadioOptionGroup label="Plan" value={plan} onChange={setPlan}>
|
|
44
|
+
<RadioOption value="free">Free</RadioOption>
|
|
45
|
+
<RadioOption value="pro">Pro</RadioOption>
|
|
46
|
+
</RadioOptionGroup>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`RadioOption` must be nested. Group also accepts React Aria radio-group props (`isDisabled`, …).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# FuzzyString
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/fuzzy-string`. Import from `"maui"`. Search UI example: [Calendar](../apps/calendar.md).
|
|
4
|
+
|
|
5
|
+
Renders highlight segments. **Not a plain string.**
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
type FuzzyMatch = Array<{ match: string } | { skip: string }>
|
|
9
|
+
|
|
10
|
+
<FuzzyString match={[{ skip: "Em" }, { match: "ail" }, { skip: " client" }]} />
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Matched spans use `accent[11]`. The matcher is **not** on the package barrel — build `{ match | skip }[]` in the app (first matching subsequence is enough). Drop items that do not match; sort by a score if you have one.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Icons
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/icons`. Import named icons from `"maui"` or `"maui/icons"`.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Search, Plus, ChevronLeft } from "maui"
|
|
7
|
+
import { Text as TextIcon, Badge as BadgeIcon } from "maui/icons"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Root-export collisions (use the `*Icon` alias from `"maui"`, or the original name from `"maui/icons"`):
|
|
11
|
+
|
|
12
|
+
`BadgeIcon`, `BlockquoteIcon`, `CodeIcon`, `H1Icon`, `H2Icon`, `H3Icon`, `LinkIcon`, `MenuIcon`, `PaddingIcon`, `SearchFieldIcon`, `SwitchIcon`, `TextIcon`.
|
|
13
|
+
|
|
14
|
+
Avoid `import { Icons } from "maui"` in app code — it pulls the full set. The JSX editor catalog uses `Icons.*` because every icon is in scope.
|
|
15
|
+
|
|
16
|
+
`IconProps`: SVG props + `size?: TextSize` (default `sm`). Size matches the text t-shirt scale (`2xs`–`xl`). Stroke and fill use `currentColor`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Layout utilities
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/layout-utilities`. Import from `"maui"`. Layout tokens: [tokens.md](../tokens.md).
|
|
4
|
+
|
|
5
|
+
Gallery-only: `Panel` is a preview frame in the docs site. It is **not** exported. Use `Flex` + `shadow` + `radius` instead.
|
|
6
|
+
|
|
7
|
+
## `Flex`
|
|
8
|
+
|
|
9
|
+
Row **or** column (exactly one of `row` / `column` is required).
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Flex row gap={4} alignItems="center">
|
|
13
|
+
<Avatar name="Ada Lovelace" size="md" />
|
|
14
|
+
<Text size="lg" fontWeight={600}>Ada Lovelace</Text>
|
|
15
|
+
<Spacer />
|
|
16
|
+
<Badge>Active</Badge>
|
|
17
|
+
</Flex>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Prop | Notes |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `row` / `column` | Required, mutually exclusive |
|
|
23
|
+
| `gap` | Spacing step |
|
|
24
|
+
| `p` / `padding` | All-side padding step |
|
|
25
|
+
| `px` `py` `pt` `pb` | Axis / side padding |
|
|
26
|
+
| `alignItems` | CSS `align-items` |
|
|
27
|
+
| `border` | `true` (outline) or `"border" \| "outline" \| "accent"`. **Skipped when `shadow` is set** |
|
|
28
|
+
| `shadow` | `"subtle" \| "medium" \| "strong"` — includes a ring; do not also set `border` |
|
|
29
|
+
| `radius` | Token key (`sm`, `lg`, `pill`, …) |
|
|
30
|
+
| `style` | React style object |
|
|
31
|
+
|
|
32
|
+
## `Padding`
|
|
33
|
+
|
|
34
|
+
`xy` (all), `x` / `y`, or `top` `right` `bottom` `left`. Spacing steps.
|
|
35
|
+
|
|
36
|
+
## `Gap`
|
|
37
|
+
|
|
38
|
+
Fixed spacer: `{ width: Space }` **or** `{ height: Space }`. Does not grow.
|
|
39
|
+
|
|
40
|
+
## `Spacer`
|
|
41
|
+
|
|
42
|
+
`flex: 1 1 auto` — fills leftover space in a `Flex`.
|
|
43
|
+
|
|
44
|
+
## `Divider`
|
|
45
|
+
|
|
46
|
+
Full-width `<hr>` (`gray[5]`, 1.5px). Has vertical margin (~1.5rem). For tight lists, prefer `gap: 1px` between rows instead.
|
|
47
|
+
|
|
48
|
+
## `navigationItem`
|
|
49
|
+
|
|
50
|
+
Style object (not a component) for current-page nav rows: sm text, `radius.sm`, hover `elementHover`, `[aria-current="page"]` accent + weight 500. Used by the [Sidebar](../patterns/sidebar.md) recipe.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# List box
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/list-box`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
Persistent selectable list (not a popup). Wrap in a raised, padded panel.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<ListBox
|
|
9
|
+
aria-label="Inboxes"
|
|
10
|
+
selectionMode="single"
|
|
11
|
+
selectedKeys={keys}
|
|
12
|
+
onSelectionChange={setKeys}
|
|
13
|
+
disallowEmptySelection
|
|
14
|
+
>
|
|
15
|
+
<ListBoxItem id="inbox">Inbox</ListBoxItem>
|
|
16
|
+
<ListBoxItem id="sent">Sent</ListBoxItem>
|
|
17
|
+
</ListBox>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Hover/focus uses `elementHover` with **no** background transition. Selected: accent text + trailing ✓.
|
|
21
|
+
|
|
22
|
+
`textValue` is inferred from string children; pass it when children are not a string.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Loading screen
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/loading-screen`. Import from `"maui"`. Uses [Thinking](thinking.md) and [Crossfade](crossfade.md).
|
|
4
|
+
|
|
5
|
+
Fills available width and height (`flex: 1`, `width/height: 100%`). Centered `Thinking` (accent, `0.8em`).
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<LoadingScreen />
|
|
9
|
+
<LoadingScreen progressLabel="Indexing files" />
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| First paint | Later `progressLabel` |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| Label present | Thinking + label fade in together. Later labels Crossfade `up`. Missing `...` / `…` is appended |
|
|
15
|
+
| No label | Thinking waits **2s**, then fades in. A label before 2s fades both in immediately. A label after 2s animates in and shifts Thinking so the pair stays centered |
|
|
16
|
+
|
|
17
|
+
`role="status"` `aria-busy` `aria-live="polite"`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/menu`. Import from `"maui"`. Related: [select.md](select.md).
|
|
4
|
+
|
|
5
|
+
Exactly two children on `MenuTrigger`: **trigger**, then **menu**. The trigger should be focusable.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<MenuTrigger placement="bottom start">
|
|
9
|
+
<Button>Actions</Button>
|
|
10
|
+
<Menu onAction={(key) => doAction(String(key))}>
|
|
11
|
+
<MenuItem id="rename">Rename</MenuItem>
|
|
12
|
+
<MenuItem id="delete">Delete</MenuItem>
|
|
13
|
+
</Menu>
|
|
14
|
+
</MenuTrigger>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`placement` defaults to `"bottom start"`. Popover uses `shadow.strong` via `CollectionPopover`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Prose
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/prose`. Import from `"maui"`. Type tokens: [tokens.md](../tokens.md).
|
|
4
|
+
|
|
5
|
+
Typography components have **no margin and no max-width**. Spacing belongs to the parent (`Flex` gap or `Prose`).
|
|
6
|
+
|
|
7
|
+
## `Prose`
|
|
8
|
+
|
|
9
|
+
Long-form column: `maxWidth: 72ch` (`proseMaxWidth`) plus vertical rhythm. `size?: "sm" | "md" | "lg"` (default `md`) is inherited via `useProseSize()`.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Prose size="md">
|
|
13
|
+
<H2>Shipping notes</H2>
|
|
14
|
+
<P>Body copy with <Link href="/docs">a link</Link>.</P>
|
|
15
|
+
<Ul>
|
|
16
|
+
<Li>First</Li>
|
|
17
|
+
<Li>Second</Li>
|
|
18
|
+
</Ul>
|
|
19
|
+
</Prose>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do not wrap app chrome (sidebars, toolbars, forms) in `Prose`.
|
|
23
|
+
|
|
24
|
+
## `H1` `H2` `H3` `H4` `Blockquote` `Link`
|
|
25
|
+
|
|
26
|
+
**Children must be a `string`.** `Link` also needs `href`.
|
|
27
|
+
|
|
28
|
+
Outside `Prose` they use the app `text` scale (`H1` xl/700, `H2` lg/600, `H3`/`H4` md/600). Inside `Prose` they switch to the prose scale.
|
|
29
|
+
|
|
30
|
+
## `P` `Ul` `Ol` `Li` `Label`
|
|
31
|
+
|
|
32
|
+
`P` / lists / `Li` take `ReactNode`. `Label` forwards `<label>` attributes (`htmlFor`, …) and uses xs/500/lowContrast, non-selectable (`labelText`).
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Flex column gap={3}>
|
|
36
|
+
<Label htmlFor="name">Name</Label>
|
|
37
|
+
<TextField id="name" aria-label="Name" />
|
|
38
|
+
</Flex>
|
|
39
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Select
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/select`. Import from `"maui"`. Related: [list-box.md](list-box.md), [menu.md](menu.md).
|
|
4
|
+
|
|
5
|
+
Trigger + `CollectionPopover` + `ListBox`. Width 100% of parent.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Select
|
|
9
|
+
label="Favorite fruit"
|
|
10
|
+
placeholder="Choose a fruit"
|
|
11
|
+
selectedKey={key}
|
|
12
|
+
onSelectionChange={(k) => setKey(String(k))}
|
|
13
|
+
>
|
|
14
|
+
<SelectItem id="apple">Apple</SelectItem>
|
|
15
|
+
<SelectItem id="banana">Banana</SelectItem>
|
|
16
|
+
</Select>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
React Aria also accepts `value` / `onChange` on some versions; `selectedKey` / `onSelectionChange` is the usual collection API. Optional `description`, `errorMessage`, `items` + child render function.
|
|
20
|
+
|
|
21
|
+
Selected items show an accent checkmark on the right (`ListBoxItem`).
|
|
22
|
+
|
|
23
|
+
`CollectionPopover` is the shared RAC `Popover` for Select and Menu. Default `placement="bottom start"`, `offset={6}`, min-width = trigger width, max-height 280px, `shadow.strong`. Use it for a custom collection overlay.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/table`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
React Aria table. Columns live **directly** in `TableHeader` (no header `TableRow`).
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Table aria-label="Invoices">
|
|
9
|
+
<TableHeader>
|
|
10
|
+
<TableHead isRowHeader>Invoice</TableHead>
|
|
11
|
+
<TableHead>Status</TableHead>
|
|
12
|
+
<TableHead align="end">Amount</TableHead>
|
|
13
|
+
</TableHeader>
|
|
14
|
+
<TableBody>
|
|
15
|
+
{rows.map((row) => (
|
|
16
|
+
<TableRow key={row.id} id={row.id}>
|
|
17
|
+
<TableCell>{row.id}</TableCell>
|
|
18
|
+
<TableCell>{row.status}</TableCell>
|
|
19
|
+
<TableCell align="end">{row.amount}</TableCell>
|
|
20
|
+
</TableRow>
|
|
21
|
+
))}
|
|
22
|
+
</TableBody>
|
|
23
|
+
<TableFooter>
|
|
24
|
+
<TableRow id="total">
|
|
25
|
+
<TableCell colSpan={2}>Total</TableCell>
|
|
26
|
+
<TableCell align="end">$2,500.00</TableCell>
|
|
27
|
+
</TableRow>
|
|
28
|
+
</TableFooter>
|
|
29
|
+
</Table>
|
|
30
|
+
<TableCaption>Recent invoices.</TableCaption>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
|
|
35
|
+
- Mark the identifying column with `isRowHeader` (name/id, not a checkbox).
|
|
36
|
+
- `align` on `TableHead` / `TableCell`: `"start"` | `"center"` | `"end"`.
|
|
37
|
+
- Selection is **opt-in**. Without `selectionMode`, rows do not highlight on hover.
|
|
38
|
+
- `selectionMode="multiple"` inserts a leading checkbox column (select-all in the header). Hover/selected washes follow `data-selection-mode`.
|
|
39
|
+
- `TableBody` default empty state is “No results.” Pass `renderEmptyState`.
|
|
40
|
+
- Put `TableCaption` **after** `Table` (often inside a `<figure>`).
|
|
41
|
+
- `TableFooter` uses `gray[2]`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Text
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/text`. Import from `"maui"`. Type tokens: [tokens.md](../tokens.md). Headings and `Prose`: [prose.md](prose.md).
|
|
4
|
+
|
|
5
|
+
Inline `<span>`. Defaults: `size="md"`, `fontWeight={400}`, `color="highContrast"`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Text size="sm" color="lowContrast">Secondary</Text>
|
|
9
|
+
<Text size="lg" fontWeight={600} monospace>src/maui.ts</Text>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Also accepts native span props except `color` (that axis is the token).
|
|
13
|
+
|
|
14
|
+
`size`: `2xs` | `xs` | `sm` | `md` | `lg` | `xl`. `fontWeight`: `400` | `500` | `600` | `700`. `color`: `lowContrast` | `highContrast` | `accent` | `onAccent`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Thinking
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/thinking`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
3×3 Game of Life, text-sized. `size` is a CSS length (default `1em`). `variant`: `"primary"` (currentColor) | `"accent"` | `"muted"`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Flex row alignItems="center" gap={4}>
|
|
9
|
+
<Thinking size="0.75em" variant="muted" />
|
|
10
|
+
<Text size="xs" color="lowContrast">Thinking</Text>
|
|
11
|
+
</Flex>
|
|
12
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
Gallery: `/components/tooltip`. Import from `"maui"`.
|
|
4
|
+
|
|
5
|
+
Wraps the trigger in an inline-block `<span>`. Child should contain something focusable.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Tooltip content="Previous" placement="top" delay={500}>
|
|
9
|
+
<Button variant="quiet" aria-label="Previous">
|
|
10
|
+
<ChevronLeft size="sm" />
|
|
11
|
+
</Button>
|
|
12
|
+
</Tooltip>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`placement`: `top` | `bottom` | `left` | `right`. Adjacent tooltips skip enter animation (warm group); only the first/last animate.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Assistant message
|
|
2
|
+
|
|
3
|
+
Gallery: `/patterns/assistant-message`. Not on the `"maui"` barrel. Prefer this recipe; rebuild with barrel components. Package source: `"maui/src/patterns/AssistantMessage.tsx"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
`AssistantMessage`: streaming **markdown reply**. Same reading scale as `Prose` (`proseHtml(size)`), max width `proseMaxWidth`.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<AssistantMessage size="sm" isAnimating={streaming}>
|
|
9
|
+
{markdown}
|
|
10
|
+
</AssistantMessage>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Rebuild with Streamdown + Maui tokens (Streamdown is a Maui dependency):
|
|
14
|
+
|
|
15
|
+
1. Root: `maxWidth: proseMaxWidth`, `minWidth: 0`. `aria-live="polite"` and `aria-busy` while `isAnimating`.
|
|
16
|
+
2. `<Streamdown>` with `proseHtml(size)` on the root (flex column, Maui type, **drop Streamdown’s Tailwind classNames** on `h1`/`p`/`ul`/… so markers and gaps stay under Maui).
|
|
17
|
+
3. Fenced code: while the fence is incomplete, render a plain `<pre>` (`radius.md`, `shadow.subtle`, `backgroundColor.app`, padding 12px) so the highlighter does not remount every chunk. When complete, `<CodeBlock lang={lang}>{text}</CodeBlock>`.
|
|
18
|
+
4. Animation: keep Streamdown `animated` **stably on**; toggle `isAnimating` only. Flipping `animated` resets stagger and makes new blocks pop. Word fade: `duration: motionStreamDurationMs`, `easing: motionEasing`, `stagger: 16`. While animating, also apply `proseStreamingMarkers` so list bullets fade with words.
|
|
19
|
+
5. `controls={false}`, `lineNumbers={false}`, `mode="streaming"`.
|
|
20
|
+
|
|
21
|
+
Do not wrap AssistantMessage in `Prose` — `proseHtml` already owns rhythm.
|
|
22
|
+
|
|
23
|
+
User turns in a chat are **not** this pattern: they are a right-aligned bubble (`prose("sm").paragraph`, `radius.md`, `shadow.subtle`, `background.element`, max-width ~80%). See [AI chat](../apps/ai-chat.md).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Inbox
|
|
2
|
+
|
|
3
|
+
Gallery: `/patterns/inbox`. Not on the `"maui"` barrel. Prefer this recipe; rebuild with barrel components. Package source: `"maui/src/patterns/Inbox.tsx"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
`Inbox` / `InboxMultiLine`: mail **thread list**, not a table. Two densities:
|
|
6
|
+
|
|
7
|
+
| | `Inbox` (single line) | `InboxMultiLine` |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Grid | `minmax(180px, 24%) minmax(0, 1fr) 92px` — sender · subject+snippet · time | Column: sender+time row, subject, 2-line snippet |
|
|
10
|
+
| Height | ~40px | Padded `y: 6, x: 4` |
|
|
11
|
+
| Unread | 6px accent dot before sender | Same, aligned to the first line |
|
|
12
|
+
|
|
13
|
+
Row model:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
type EmailThread = {
|
|
17
|
+
id: string
|
|
18
|
+
senders: string
|
|
19
|
+
subject: string
|
|
20
|
+
snippet: string
|
|
21
|
+
time: string
|
|
22
|
+
unread?: boolean
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Behavior:
|
|
27
|
+
|
|
28
|
+
- `selectedId` + `onSelectThread(id)`. Selected row uses `backgroundColor.elementActive`. Hover uses `elementHover` (instant).
|
|
29
|
+
- `radius.md`, `userSelect: none`, list `gap: 1px`.
|
|
30
|
+
- Subject: highContrast, ellipsis. Snippet: lowContrast, ellipsis (multi-line clamps to 2).
|
|
31
|
+
- Time sits on the right. **On hover**, time fades out and a quiet icon toolbar (`Star`, `Archive`, `Trash`, `Envelope`, `Clock`) fades in at the right — `shadow.subtle` chip, `Button variant="quiet"` with `aria-label`, `stopPropagation` on click.
|
|
32
|
+
- Always include an empty state when `threads.length === 0` (“No messages” + optional compose action).
|
|
33
|
+
|
|
34
|
+
Single-line sender column is highContrast with the unread dot. Multi-line puts the dot in a leading column (`align: start`).
|
|
35
|
+
|
|
36
|
+
[Email client](../apps/email-client.md) uses **InboxMultiLine** in a 240px pane.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Message list
|
|
2
|
+
|
|
3
|
+
Gallery: `/patterns/message-list`. Not on the `"maui"` barrel. Prefer this recipe; rebuild with barrel components. Package source: `"maui/src/patterns/MessageList.tsx"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
`MessageList` + `Message`: raised **cards** in a column (`gap` 6, max width ~760px), `role="feed"`.
|
|
6
|
+
|
|
7
|
+
Each `Message`:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<article> {/* radius.lg, shadow.subtle, padding 8, column gap 4, background.element */}
|
|
11
|
+
<header> {/* row, wrap, gap 2 */}
|
|
12
|
+
<Avatar name={sender} size="sm" />
|
|
13
|
+
<Text size="sm" fontWeight={500}>{sender}</Text>
|
|
14
|
+
<time><Text size="xs" color="lowContrast">{timestamp}</Text></time>
|
|
15
|
+
{edited ? <Text size="xs" color="lowContrast">(edited)</Text> : null}
|
|
16
|
+
</header>
|
|
17
|
+
<Prose>{/* body: P / lists, not a raw string */}</Prose>
|
|
18
|
+
</article>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `sender` + `timestamp` required; `edited?` appends “(edited)”.
|
|
22
|
+
- Body is React children inside `Prose` (so headings/paragraphs get reading rhythm). For email, keep the inner `Prose` unconstrained (`minWidth: 0`) so it fills the card.
|
|
23
|
+
- Empty thread: “Select a thread to read messages.” See [Email client](../apps/email-client.md).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Sidebar
|
|
2
|
+
|
|
3
|
+
Gallery: `/patterns/sidebar`. Not on the `"maui"` barrel. Prefer this recipe; rebuild with barrel components. Package source: `"maui/src/patterns/Sidebar.tsx"` if you need a detail this file does not cover.
|
|
4
|
+
|
|
5
|
+
`Sidebar` / `SidebarSection` / `SidebarItem`: fixed **240px** nav: raised `background.element` + `shadow.subtle` + `radius.lg`, column `gap={8}`, padding step 2.
|
|
6
|
+
|
|
7
|
+
Uses `navigationItem` from [layout utilities](../components/layout-utilities.md).
|
|
8
|
+
|
|
9
|
+
Composition:
|
|
10
|
+
|
|
11
|
+
1. Optional brand block (not a special component).
|
|
12
|
+
2. One or more sections: xs/500/lowContrast label, then a list of items.
|
|
13
|
+
3. Each item is a full-width quiet row: 16px icon column, label, optional trailing (`Badge`).
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Flex, Text } from "maui"
|
|
17
|
+
|
|
18
|
+
<nav aria-label="Workspace">
|
|
19
|
+
{/* 240px column, padding step 2, radius.lg, shadow.subtle, background.element, gap 8 */}
|
|
20
|
+
<Flex column gap={2} px={4} pt={4}>
|
|
21
|
+
<Text size="sm" fontWeight={600}>Maui Cloud</Text>
|
|
22
|
+
<Text size="xs" color="lowContrast">Production</Text>
|
|
23
|
+
</Flex>
|
|
24
|
+
{/* sections + items — see layout rules below */}
|
|
25
|
+
</nav>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Item layout (rebuild, do not invent a different nav row):
|
|
29
|
+
|
|
30
|
+
- Grid: `16px minmax(0, 1fr) auto`, column gap spacing 3.
|
|
31
|
+
- Style from `navigationItem` + `focusRing("&:focus-visible")`.
|
|
32
|
+
- `aria-current="page"` when `active` — accent color + weight 500, **no** filled background.
|
|
33
|
+
- Icon wrap is 16×16, `gray[11]`; active icon uses `accent[9]`.
|
|
34
|
+
- Label: 13/20, ellipsis. Section label is padded to align with the text column (padding-start = item padding 4 + 16px icon + gap 3).
|
|
35
|
+
- Native `<button type="button">` inside `<li>`, not `Button` — so the row can be a grid without the 28px control chrome.
|
|
36
|
+
- List: no bullets, `gap: 1px`.
|
|
37
|
+
|
|
38
|
+
Hover: `backgroundColor.elementHover`, instant.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Tokens, theme, and styling
|
|
2
|
+
|
|
3
|
+
Use this file when choosing color, type, space, motion, or elevation, or when writing custom `purse-styles` around Maui components.
|
|
4
|
+
|
|
5
|
+
Import tokens from `"maui"`. Import `style` / `useStyles` from `"purse-styles"`. Prefer semantic tokens over one-off CSS variables.
|
|
6
|
+
|
|
7
|
+
## Theme
|
|
8
|
+
|
|
9
|
+
`MauiProvider` wraps `ThemeProvider` + `PurseProvider` + global CSS + the focus UI database.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { MauiProvider, themeFoucScript, useTheme } from "maui"
|
|
13
|
+
|
|
14
|
+
// In <head>, before React:
|
|
15
|
+
// <script>{themeFoucScript}</script>
|
|
16
|
+
|
|
17
|
+
const { preference, resolvedTheme, setPreference } = useTheme()
|
|
18
|
+
// preference: "system" | "light" | "dark"
|
|
19
|
+
// resolvedTheme: "light" | "dark"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Storage key: `themeStorageKey` (`"maui-theme"`).
|
|
23
|
+
- Dark CSS: `DARK_THEME` is `:root[data-theme="dark"]`. `defineVars` dark values use that selector.
|
|
24
|
+
- FOUC: inline `themeFoucScript` in a classic `<script>` in `<head>` so `data-theme` and `color-scheme` exist before first paint.
|
|
25
|
+
|
|
26
|
+
`MauiProvider` also sets `box-sizing: border-box`, zeros `html`/`body` margin, paints `backgroundColor.app`, applies `baseTextStyle` (md / 400 / highContrast) on `html, body`, and resets heading/paragraph margins.
|
|
27
|
+
|
|
28
|
+
## purse-styles
|
|
29
|
+
|
|
30
|
+
Maui styles are `purse-styles` objects. `useStyles(...)` turns one or more of them into a class name.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { style, useStyles } from "purse-styles"
|
|
34
|
+
import { flex, spacing, text, backgroundColor, radius, shadow } from "maui"
|
|
35
|
+
|
|
36
|
+
function Row({ children }: { children: React.ReactNode }) {
|
|
37
|
+
const className = useStyles(
|
|
38
|
+
flex({ direction: "row", align: "center", gap: 4 }),
|
|
39
|
+
radius.sm,
|
|
40
|
+
shadow.subtle,
|
|
41
|
+
style({
|
|
42
|
+
minWidth: 0,
|
|
43
|
+
backgroundColor: backgroundColor.element,
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
return <div className={className}>{children}</div>
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `text({...})`, `shadow.subtle`, `background.element`, `radius.md`, `flex({...})`, `spacing.padding({...})` are already style objects — pass them to `useStyles`.
|
|
51
|
+
- `style({ ... })` is for leftover CSS that tokens do not cover (`minWidth`, grid templates, absolute positioning).
|
|
52
|
+
- Compose several objects in one `useStyles` call. Falsy entries are skipped.
|
|
53
|
+
- For one-off layout, prefer the `Flex` / `Padding` components (see [layout-utilities.md](components/layout-utilities.md)). For repeated custom chrome, prefer tokens + `useStyles`.
|
|
54
|
+
|
|
55
|
+
## Color
|
|
56
|
+
|
|
57
|
+
`colors` is Radix Scales with a brand `accent` (teal in light, violet in dark). Each scale has steps **1–12**. Alpha scales are `colors.grayAlpha`, `colors.accentAlpha`, `colors.blueAlpha`, …
|
|
58
|
+
|
|
59
|
+
| Steps | Use |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| 1–2 | App / subtle surfaces |
|
|
62
|
+
| 3–5 | Interactive washes |
|
|
63
|
+
| 6–8 | Borders / strong lines |
|
|
64
|
+
| 9–10 | Solid fills (9 rest, 10 hover) |
|
|
65
|
+
| 11–12 | Text (11 secondary, 12 primary) |
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { colors, colorNames, paletteNames } from "maui"
|
|
69
|
+
|
|
70
|
+
colors.accent[9] // solid brand fill
|
|
71
|
+
colors.gray[12] // high-contrast text
|
|
72
|
+
colors.blueAlpha[8]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`colorNames` is `"accent"` plus every Radix palette name. `paletteNames` omits `"accent"`. `"blue"` as a `variantColor` is the Radix blue scale, not CSS `blue`.
|
|
76
|
+
|
|
77
|
+
Prefer semantic surfaces over raw scale steps for chrome:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { background, backgroundColor } from "maui"
|
|
81
|
+
|
|
82
|
+
backgroundColor.app // page
|
|
83
|
+
backgroundColor.element // raised control / card
|
|
84
|
+
backgroundColor.elementHover // 3.5% gray-12 wash over element
|
|
85
|
+
backgroundColor.elementActive // 7% wash
|
|
86
|
+
background.app // style objects of the same values
|
|
87
|
+
background.element
|
|
88
|
+
background.accent // accent[9]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Hover/active washes use `color-mix` in oklch. **Do not transition `background` / `background-color` on hover.**
|
|
92
|
+
|
|
93
|
+
## Text
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { text, monospace, fontFamily, monoFontFamily } from "maui"
|
|
97
|
+
|
|
98
|
+
text({
|
|
99
|
+
size: "sm", // 2xs | xs | sm | md | lg | xl (default md)
|
|
100
|
+
fontWeight: 500, // 400 | 500 | 600 | 700 (default 400)
|
|
101
|
+
color: "lowContrast", // lowContrast | highContrast | accent | onAccent
|
|
102
|
+
monospace: true,
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Size | Font | Line |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `2xs` | 10px | 14px |
|
|
109
|
+
| `xs` | 12px | 18px |
|
|
110
|
+
| `sm` | 13px | 20px |
|
|
111
|
+
| `md` | 14px | 22px |
|
|
112
|
+
| `lg` | 16px | 24px |
|
|
113
|
+
| `xl` | 22px | 30px |
|
|
114
|
+
|
|
115
|
+
- `lowContrast` → `gray[11]`, `highContrast` → `gray[12]`, `accent` → `accent[11]`, `onAccent` → white.
|
|
116
|
+
- UI sans: `fontFamily` (system ui-sans-serif stack). Mono: Commit Mono with `ss05` smart kerning (`monoFontStyle` / `monospace`).
|
|
117
|
+
- `baseTextStyle` is md / 400 / highContrast — already on `html, body`.
|
|
118
|
+
- Inside `Prose`, `H1`–`H4` / `P` / lists switch to the **prose** scale (`sm` 14px, `md` 16px, `lg` 18px), which is larger and has reading rhythm. Do not put app chrome inside `Prose`.
|
|
119
|
+
|
|
120
|
+
`proseMaxWidth` is `"72ch"`. `sizing.contentWidth` is `maxWidth: 72ch`. `sizing.fullWidth` is `width: 100%`.
|
|
121
|
+
|
|
122
|
+
## Spacing
|
|
123
|
+
|
|
124
|
+
Scale steps are **not pixels**:
|
|
125
|
+
|
|
126
|
+
| Step | px |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| 1 | 2 |
|
|
129
|
+
| 2 | 4 |
|
|
130
|
+
| 3 | 6 |
|
|
131
|
+
| 4 | 9 |
|
|
132
|
+
| 6 | 12 |
|
|
133
|
+
| 8 | 16 |
|
|
134
|
+
| 12 | 24 |
|
|
135
|
+
| 16 | 32 |
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
spacing.padding({ all: 4, x: 6, y: 2, top: 8 })
|
|
139
|
+
spacing.gap[4] // style { gap: 9px }
|
|
140
|
+
spacing.value(4) // "9px" — only when gap/padding tokens cannot apply
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`Flex` / `Padding` / `Gap` / `flex({ gap })` all take these steps.
|
|
144
|
+
|
|
145
|
+
## Layout tokens
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
flex({ direction: "row" | "column", align, justify, gap, wrap })
|
|
149
|
+
flexItem({ size: "hug" | "fill" | "auto", align, order })
|
|
150
|
+
grid({ columns: "one" | "two" | "three" | "autoFit" | "sidebarContent", align, justify, gap })
|
|
151
|
+
gridItem({ area: "sidebar" | "content", span: "full" | 1 | 2 | 3, align, justify })
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `align`: `start` | `center` | `end` | `stretch` | `baseline`
|
|
155
|
+
- `justify`: `start` | `center` | `end` | `between` | `around` | `evenly`
|
|
156
|
+
- `gap` on these token helpers also accepts `0`.
|
|
157
|
+
- `sidebarContent` is `180px minmax(0, 1fr)` with areas `"sidebar content"`.
|
|
158
|
+
|
|
159
|
+
Prefer `<Flex row gap={4}>` for one-off JSX. Use `flex()` / `grid()` when composing with other tokens.
|
|
160
|
+
|
|
161
|
+
## Radius
|
|
162
|
+
|
|
163
|
+
`radius.none` | `2xs` (2px) | `xs` (3) | `sm` (4) | `md` (6) | `lg` (8) | `xl` (12) | `pill` | `circle`.
|
|
164
|
+
|
|
165
|
+
Controls typically use `sm`. Cards / sidebars use `lg`. Avatars use `circle`.
|
|
166
|
+
|
|
167
|
+
## Shadows and rings
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
shadow.subtle | shadow.medium | shadow.strong // style objects
|
|
171
|
+
shadowVars.subtle | .medium | .strong // raw box-shadow strings
|
|
172
|
+
tintedSubtle(color) // subtle ring+blur tinted from a fill
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Every elevation already includes a 1px ring. **Do not also `border()` the same element.**
|
|
176
|
+
|
|
177
|
+
`border(sides, color)`:
|
|
178
|
+
|
|
179
|
+
- `sides`: `[]` for all sides, or `"top" | "right" | "bottom" | "left"`
|
|
180
|
+
- `color`: `"border"` (gray-12 @ 5%) | `"outline"` (10%) | `"accent"` (`accent[8]`)
|
|
181
|
+
|
|
182
|
+
`borderColor.border` / `borderColor.outline` are the raw color strings (hairlines, `borderBottom`).
|
|
183
|
+
|
|
184
|
+
`focusRing(selector = "&:focus-visible", existingShadow?)` — Radix blue ring. Pass `shadowVars.subtle` as the second argument so a raised control keeps its elevation while focused.
|
|
185
|
+
|
|
186
|
+
`visuallyHidden` — clip an input and keep the styled sibling visible (Checkbox / Switch / Radio).
|
|
187
|
+
|
|
188
|
+
## Motion
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
motionDurationMs // 80
|
|
192
|
+
motionStreamDurationMs // 80 — Streamdown word fade
|
|
193
|
+
motionEasing // "ease-in-out"
|
|
194
|
+
motion.standard("opacity", "transform") // 80ms ease-in-out on those properties
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use `motion.standard` for opacity, transform, box-shadow, color. **Never** for hover `background` / `background-color`.
|
|
198
|
+
|
|
199
|
+
Tooltips use a snappy spring (not `motion.standard`). Crossfade enter is a 0.3s spring (`bounce` 0.2); exit uses `motionDurationMs` / ease-in-out. Streaming markdown uses `motionStreamDurationMs`.
|
|
200
|
+
|
|
201
|
+
## Sizing and icons
|
|
202
|
+
|
|
203
|
+
`iconSizeValues` pairs t-shirt sizes to icon boxes (slightly above the matching font-size):
|
|
204
|
+
|
|
205
|
+
| Size | Box |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `2xs` | 12px |
|
|
208
|
+
| `xs` | 14px |
|
|
209
|
+
| `sm` | 16px (default) |
|
|
210
|
+
| `md` | 18px |
|
|
211
|
+
| `lg` | 20px |
|
|
212
|
+
| `xl` | 24px (intrinsic SVG) |
|
|
213
|
+
|
|
214
|
+
Stroke/fill is `currentColor`. Pass `size` on the icon component; optional `width` / `height` override the box.
|
|
215
|
+
|
|
216
|
+
## Avatar tokens
|
|
217
|
+
|
|
218
|
+
`avatar.green` / `avatar.orange` / `avatar.pink` each have `background` and `foreground`. The `Avatar` component also uses `accent[4]`/`[11]`. You rarely need these tokens directly.
|
|
219
|
+
|
|
220
|
+
## Prose tokens
|
|
221
|
+
|
|
222
|
+
For HTML that is **not** React typography (`Editor` ProseMirror tree, Streamdown output):
|
|
223
|
+
|
|
224
|
+
- `prose(size)` — style objects for paragraph, h1–h4, link, blockquote, list (`size`: `sm` | `md` | `lg`)
|
|
225
|
+
- `proseRhythm(size)` — margin-top-only rhythm for `Prose` children
|
|
226
|
+
- `proseHtml(size)` — flex-column + gap styles for a markdown HTML tree
|
|
227
|
+
- `proseStreamingMarkers` — fade list markers with Streamdown’s word animation
|
|
228
|
+
|
|
229
|
+
Prefer `<Prose>` + `H1`/`P`/… for React trees. Use `proseHtml` only when rendering markdown HTML.
|