@tanishqkancharla/maui 0.0.21 → 0.0.23
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/dist/components/Button.d.ts +1 -0
- package/dist/components/Button.js +1 -1
- 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/src/apps/JsxEditor/catalog.ts +3 -0
- package/src/apps/JsxEditor/lint.test.ts +53 -0
- package/src/apps/JsxEditor/lint.ts +22 -10
- package/src/components/Button.tsx +1 -1
|
@@ -4,6 +4,7 @@ export type ButtonVariant = "default" | "quiet" | "primary";
|
|
|
4
4
|
/** Opaque hex or `rgb()`. Palette names stay ColorName (`"blue"` is Radix, not CSS `blue`). */
|
|
5
5
|
export type ButtonCssColor = `#${string}` | `rgb(${string}`;
|
|
6
6
|
export type ButtonVariantColor = ColorName | ButtonCssColor;
|
|
7
|
+
export declare function isCssColor(value: string): value is ButtonCssColor;
|
|
7
8
|
type ButtonAttributes = React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
|
|
8
9
|
type ButtonData = {
|
|
9
10
|
id: string;
|
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`.
|