harebear-ui 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/dist/components/admin/app-nav.d.ts +15 -1
- package/dist/components/admin/app-nav.js +28 -11
- package/dist/components/admin/avatar-upload.d.ts +32 -0
- package/dist/components/admin/avatar-upload.js +121 -0
- package/dist/components/admin/category.d.ts +17 -0
- package/dist/components/admin/category.js +35 -0
- package/dist/components/admin/celebration.d.ts +7 -0
- package/dist/components/admin/celebration.js +61 -0
- package/dist/components/admin/checklist-editor.d.ts +20 -0
- package/dist/components/admin/checklist-editor.js +69 -0
- package/dist/components/admin/color-swatch-picker.d.ts +15 -0
- package/dist/components/admin/color-swatch-picker.js +68 -0
- package/dist/components/admin/comment-thread.d.ts +26 -0
- package/dist/components/admin/comment-thread.js +68 -0
- package/dist/components/admin/copy-field.d.ts +3 -1
- package/dist/components/admin/copy-field.js +10 -5
- package/dist/components/admin/date-time-picker.d.ts +20 -0
- package/dist/components/admin/date-time-picker.js +97 -0
- package/dist/components/admin/detail-sheet.d.ts +18 -0
- package/dist/components/admin/detail-sheet.js +80 -0
- package/dist/components/admin/markdown-editor.d.ts +12 -0
- package/dist/components/admin/markdown-editor.js +46 -0
- package/dist/components/admin/page-header.d.ts +3 -1
- package/dist/components/admin/page-header.js +17 -9
- package/dist/components/admin/page.d.ts +16 -0
- package/dist/components/admin/page.js +42 -0
- package/dist/components/admin/prompt-dialog.d.ts +19 -0
- package/dist/components/admin/prompt-dialog.js +83 -0
- package/dist/components/admin/settings.d.ts +4 -2
- package/dist/components/admin/settings.js +37 -30
- package/dist/components/admin/time-zone-picker.d.ts +12 -0
- package/dist/components/admin/time-zone-picker.js +31 -0
- package/dist/components/admin/user-chip.d.ts +34 -3
- package/dist/components/admin/user-chip.js +66 -7
- package/dist/components/admin/user-menu.d.ts +6 -1
- package/dist/components/admin/user-menu.js +22 -10
- package/dist/components/ai/ask-box.d.ts +14 -2
- package/dist/components/ai/ask-box.js +90 -54
- package/dist/components/ai/assistant.d.ts +13 -3
- package/dist/components/ai/assistant.js +36 -8
- package/dist/components/ai/code-block.d.ts +10 -3
- package/dist/components/ai/code-block.js +57 -8
- package/dist/components/ai/conversation-list.d.ts +13 -3
- package/dist/components/ai/conversation-list.js +15 -26
- package/dist/components/ai/markdown.d.ts +13 -0
- package/dist/components/ai/markdown.js +41 -0
- package/dist/components/auth/auth.d.ts +41 -5
- package/dist/components/auth/auth.js +167 -159
- package/dist/components/data/agenda-row.d.ts +26 -0
- package/dist/components/data/agenda-row.js +62 -0
- package/dist/components/data/day-strip.d.ts +27 -0
- package/dist/components/data/day-strip.js +78 -0
- package/dist/components/data/sectioned-list.d.ts +21 -6
- package/dist/components/data/sectioned-list.js +44 -21
- package/dist/components/data/task-row.d.ts +3 -0
- package/dist/components/data/task-row.js +6 -14
- package/dist/components/layout/collapse.js +1 -1
- package/dist/components/layout/text.d.ts +3 -3
- package/dist/components/layout/text.js +6 -1
- package/dist/components/ui/avatar.js +1 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/check-circle.js +1 -1
- package/dist/components/ui/code-block.js +2 -2
- package/dist/components/ui/combobox.js +1 -0
- package/dist/components/ui/input-group.js +1 -1
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/kbd.js +1 -1
- package/dist/components/ui/password-input.d.ts +8 -0
- package/dist/components/ui/password-input.js +32 -0
- package/dist/components/ui/sonner.d.ts +2 -2
- package/dist/components/ui/sonner.js +1 -1
- package/dist/components/ui/toggle.js +1 -1
- package/dist/index.d.ts +23 -7
- package/dist/index.js +21 -5
- package/dist/lib/internal/date.js +8 -0
- package/dist/lib/{toast-clearance.js → internal/toast-clearance.js} +1 -1
- package/dist/styles.css +31 -1
- package/docs/COMPONENTS.md +302 -28
- package/docs/ROADMAP.md +13 -11
- package/llms.txt +10 -8
- package/package.json +3 -1
- package/skills/harebear-ui/SKILL.md +5 -3
package/docs/COMPONENTS.md
CHANGED
|
@@ -15,6 +15,7 @@ Full working screens that combine these live in the repo under `playground/scree
|
|
|
15
15
|
- **Compose, don't fork.** Every component accepts `className` (merged with `cn`, later classes win). Reach for a primitive before writing a new component.
|
|
16
16
|
- **Icons are lucide-react elements.** Pass `<SearchIcon />`, not the component. Buttons size icons automatically.
|
|
17
17
|
- **Status uses tones**, not colors: `"success" | "warning" | "danger" | "info" | "neutral"`.
|
|
18
|
+
- **Touch targets are built in.** Under `pointer: coarse`, `Button` (every size) and `Toggle`/`ToggleGroupItem` get a 44px hit area (an invisible `::after`, which an `overflow-hidden` ancestor or a later sibling clips: in such a spot, size the button itself, e.g. `pointer-coarse:size-11`, as the `CodeBlock` and `CodeSnippet` copy buttons do) and `Input`/`InputGroup` grow to 44px.
|
|
18
19
|
|
|
19
20
|
## Agent-first apps
|
|
20
21
|
|
|
@@ -27,7 +28,7 @@ A household or personal app where an assistant sits on every page and safe actio
|
|
|
27
28
|
<AppNav
|
|
28
29
|
brand={<AppBrand name="Homeclub" icon={<HouseIcon />} />}
|
|
29
30
|
places={[{ label: "Today", icon: <SunIcon />, href: "/", active: true, count: 3 }, ...]}
|
|
30
|
-
group={{ label: "Lists", items: lists, onAdd: newList, addLabel: "New list" }}
|
|
31
|
+
group={{ label: "Lists", href: "/lists", items: lists, onAdd: newList, addLabel: "New list" }}
|
|
31
32
|
action={{ label: "New to-do", icon: <PlusIcon />, onSelect: openQuickAdd }}
|
|
32
33
|
footer={<UserMenu name={user.name} onSignOut={signOut} />}
|
|
33
34
|
>
|
|
@@ -40,10 +41,14 @@ A household or personal app where an assistant sits on every page and safe actio
|
|
|
40
41
|
</ThemeProvider>
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
-
|
|
44
|
+
- Pages: `Page` (the column) with a `PageHeader` (h1; `UserMenu compact className="md:hidden"` in `actions`, hidden from md up, since the sidebar footer is hidden on phones) and `PageSection`s (h2 regions). A chat page sets `fill` on `AppNav`.
|
|
45
|
+
- Lists: `SectionedList` (`TaskRow` rows); when the agent or a bulk action changes data, confirm with `toastUndo` and set `lingerMs` to its window so a ticked row stays struck through for about as long as Undo shows.
|
|
46
|
+
- A week agenda: `DayStrip sticky` above one `SectionedList` with a section per day (`empty` keeps empty days) and `AgendaRow` rows from `renderItem`.
|
|
47
|
+
- A record's details (`DetailSheet`): `ChecklistEditor` for steps, `CommentThread` for comments, `MarkdownEditor` for notes.
|
|
44
48
|
- In the assistant thread, each action it took is an `ActionReceipt` with Undo (append it from `onSend`).
|
|
49
|
+
- A page with its own ask box (Today) answers under it: `AskBox` with `reply` and `pending` from `useAssistant()`, `onStop={stop}`, and `send(text, { open: false })`.
|
|
45
50
|
- Settings and setup flows leave the frame: `FocusLayout` around `SettingsSection`s of `SettingsRow`s.
|
|
46
|
-
- Members, lists and calendars take a `category-1..6` color; status colors never double as categories.
|
|
51
|
+
- Members, lists and calendars take a `category-1..6` color (`CategoryColor`); status colors never double as categories. People are `MemberAvatar` (`AvatarStack`, `VisibilityBadge`), list dots are `CategoryDot`, profile settings use `AvatarUpload` and `ColorSwatchPicker`.
|
|
47
52
|
- Live example: `playground/screens/household.tsx` (`#household`) and `settings.tsx` (`#settings`).
|
|
48
53
|
|
|
49
54
|
## Theme
|
|
@@ -75,7 +80,7 @@ Every component in the shadcn registry is included. (shadcn's `form` item is an
|
|
|
75
80
|
| Alert dialog | `AlertDialog`, `AlertDialogTrigger`, `AlertDialogContent`, `AlertDialogHeader`, `AlertDialogFooter`, `AlertDialogTitle`, `AlertDialogDescription`, `AlertDialogAction`, `AlertDialogCancel`, `AlertDialogMedia`, `AlertDialogOverlay`, `AlertDialogPortal` | Use for destructive confirmations. |
|
|
76
81
|
| Aspect ratio | `AspectRatio` | |
|
|
77
82
|
| Attachment | `Attachment`, `AttachmentGroup`, `AttachmentMedia`, `AttachmentContent`, `AttachmentTitle`, `AttachmentDescription`, `AttachmentActions`, `AttachmentAction`, `AttachmentTrigger` | File/image chips on messages and composers. `size`: `default`, `sm`, `xs`; `orientation`: `horizontal`, `vertical`. |
|
|
78
|
-
| Avatar | `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarGroup`, `AvatarGroupCount`, `AvatarBadge` | `size`: `sm` 28px, `default` 36px, `lg` 56px. Fallback is tinted initials
|
|
83
|
+
| Avatar | `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarGroup`, `AvatarGroupCount`, `AvatarBadge` | `size`: `sm` 28px, `default` 36px, `lg` 56px. Fallback is tinted initials, 12px at `sm`. For people use `MemberAvatar`. |
|
|
79
84
|
| Badge | `Badge`, `badgeVariants` | 24px pill. `variant`: `default` (primary), `secondary` (neutral, the common one), `soft` (tinted primary), `outline`, `success`, `warning`, `destructive`, `ghost`, `link`. harebear adds `size="lg"` (32px, lines up with sm buttons) and `tone` (`success \| warning \| danger \| info \| neutral`, the StatusBadge words; wins over `variant`). |
|
|
80
85
|
| Bubble | `Bubble`, `BubbleGroup`, `BubbleContent`, `BubbleReactions` | Chat bubble. `variant`: `default` (primary), `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`; `align`: `start`, `end`. |
|
|
81
86
|
| Breadcrumb | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis` | |
|
|
@@ -87,9 +92,9 @@ Every component in the shadcn registry is included. (shadcn's `form` item is an
|
|
|
87
92
|
| Carousel | `Carousel`, `CarouselContent`, `CarouselItem`, `CarouselPrevious`, `CarouselNext`, `useCarousel`, type `CarouselApi` | embla. |
|
|
88
93
|
| Chart | `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent`, `ChartStyle` | Recharts wrappers. Prefer `TrendChart` for line/area; use these for custom charts. |
|
|
89
94
|
| Checkbox | `Checkbox` | |
|
|
90
|
-
| Check circle | `CheckCircle` | harebear. 24px round checkbox for to-do rows (44px touch target), pops when ticked. `tone="important"` tints it; pair with a word. Name it with `aria-labelledby` the row title.
|
|
95
|
+
| Check circle | `CheckCircle` | harebear. 24px round checkbox for to-do rows (44px touch target), pops when ticked (no pop or fade under reduced motion). Focus shows a solid primary outline whatever the `tone`. `tone="important"` tints it; pair with a word. Name it with `aria-labelledby` the row title. |
|
|
91
96
|
| Collapsible | `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | |
|
|
92
|
-
| Combobox | `Combobox`, `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup`, `ComboboxLabel`, `ComboboxCollection`, `ComboboxEmpty`, `ComboboxSeparator`, `ComboboxChips`, `ComboboxChip`, `ComboboxChipsInput`, `ComboboxTrigger`, `ComboboxValue`, `useComboboxAnchor` | Searchable select (Base UI). `<Combobox items={list}>` + `<ComboboxList>{(item) => <ComboboxItem value={item}>…}</ComboboxList>`. Chips for multi-select.
|
|
97
|
+
| Combobox | `Combobox`, `ComboboxInput`, `ComboboxContent`, `ComboboxList`, `ComboboxItem`, `ComboboxGroup`, `ComboboxLabel`, `ComboboxCollection`, `ComboboxEmpty`, `ComboboxSeparator`, `ComboboxChips`, `ComboboxChip`, `ComboboxChipsInput`, `ComboboxTrigger`, `ComboboxValue`, `useComboboxAnchor` | Searchable select (Base UI). `<Combobox items={list}>` + `<ComboboxList>{(item) => <ComboboxItem value={item}>…}</ComboboxList>`. Chips for multi-select. `ComboboxInput`'s built-in trigger is named "Show options". |
|
|
93
98
|
| Command | `Command`, `CommandDialog`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandShortcut`, `CommandSeparator` | Use for ⌘K palettes. |
|
|
94
99
|
| Direction | `DirectionProvider`, `useDirection` | RTL/LTR context for Radix components. |
|
|
95
100
|
| Context menu | `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioItem`, `ContextMenuRadioGroup`, `ContextMenuLabel`, `ContextMenuSeparator`, `ContextMenuShortcut`, `ContextMenuGroup`, `ContextMenuPortal`, `ContextMenuSub`, `ContextMenuSubContent`, `ContextMenuSubTrigger` | |
|
|
@@ -99,11 +104,11 @@ Every component in the shadcn registry is included. (shadcn's `form` item is an
|
|
|
99
104
|
| Empty state | `Empty`, `EmptyHeader`, `EmptyMedia`, `EmptyTitle`, `EmptyDescription`, `EmptyContent` | Use for zero-data screens. harebear adds `size="sm"`: compact, no flex-grow, for empties inside cards and panels. |
|
|
100
105
|
| Field | `Field`, `FieldLabel`, `FieldDescription`, `FieldError`, `FieldGroup`, `FieldLegend`, `FieldSeparator`, `FieldSet`, `FieldContent`, `FieldTitle` | Form layout. Works with react-hook-form or plain state. |
|
|
101
106
|
| Hover card | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` | |
|
|
102
|
-
| Input | `Input` | 40px, filled `bg-muted`, turns `bg-card` with a primary ring on focus.
|
|
107
|
+
| Input | `Input` | 40px, filled `bg-muted`, turns `bg-card` with a primary ring on focus. 44px under `pointer: coarse` (also `InputGroup`). |
|
|
103
108
|
| Input group | `InputGroup`, `InputGroupAddon`, `InputGroupButton`, `InputGroupText`, `InputGroupInput`, `InputGroupTextarea` | Inputs with icons, prefixes, buttons. |
|
|
104
109
|
| Input OTP | `InputOTP`, `InputOTPGroup`, `InputOTPSlot`, `InputOTPSeparator` | |
|
|
105
110
|
| Item | `Item`, `ItemMedia`, `ItemContent`, `ItemTitle`, `ItemDescription`, `ItemActions`, `ItemGroup`, `ItemSeparator`, `ItemHeader`, `ItemFooter` | Generic list rows. harebear fix: `ItemTitle` clamps to one line (long words break) and shows the full text as a tooltip when clamped, `ItemContent` has `min-w-0`. harebear adds `ItemTitle mono` for commands, paths and IDs. |
|
|
106
|
-
| Kbd | `Kbd`, `KbdGroup` | Mono keycap.
|
|
111
|
+
| Kbd | `Kbd`, `KbdGroup` | Mono keycap, 12px text. |
|
|
107
112
|
| Marker | `Marker`, `MarkerIcon`, `MarkerContent`, `markerVariants` | Inline status line in a thread ("Conversation saved"). `variant`: `default`, `separator` (date dividers), `border`. |
|
|
108
113
|
| Label | `Label` | |
|
|
109
114
|
| Menubar | `Menubar`, `MenubarMenu`, `MenubarTrigger`, `MenubarContent`, `MenubarItem`, `MenubarCheckboxItem`, `MenubarRadioGroup`, `MenubarRadioItem`, `MenubarLabel`, `MenubarSeparator`, `MenubarShortcut`, `MenubarGroup`, `MenubarPortal`, `MenubarSub`, `MenubarSubTrigger`, `MenubarSubContent` | |
|
|
@@ -129,8 +134,8 @@ Every component in the shadcn registry is included. (shadcn's `form` item is an
|
|
|
129
134
|
| Table | `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` | Borderless rows with rounded hover; quiet 12px headers. `data-state="selected"` tints a row. |
|
|
130
135
|
| Tabs | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `tabsListVariants` | `TabsList variant`: `default` (segmented control), `pill` (rounded top-level nav), `line`. Use `Tabs` as a segmented filter too (date ranges, status filters). |
|
|
131
136
|
| Textarea | `Textarea` | Same filled style as `Input`, auto-grows. |
|
|
132
|
-
| Toast | `Toaster`
|
|
133
|
-
| Toggle | `Toggle`, `toggleVariants`, `ToggleGroup`, `ToggleGroupItem` |
|
|
137
|
+
| Toast | `Toaster`, `toast`, `toastUndo` | sonner. Mount once; call `toast("Saved")`, `toast.success(...)`, `toast.error(...)` (sonner's `toast`, re-exported by harebear-ui). Follows the theme mode. `toastUndo(message, { onUndo, undoLabel?, duration = 9000 })` shows an Undo toast for something already done. |
|
|
138
|
+
| Toggle | `Toggle`, `toggleVariants`, `ToggleGroup`, `ToggleGroupItem` | 44px hit area under `pointer: coarse`. |
|
|
134
139
|
| Tooltip | `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent` | Mount `TooltipProvider` once near the root. |
|
|
135
140
|
| Trace | `Trace`, `TraceLine`, `TraceIcon`, `TraceTitle`, `TraceDescription`, `TraceCollapsible`, `TraceCollapsibleTrigger`, `TraceCollapsibleContent` | Agent step log (searched, read, ran). `TraceLine status`: `running` (shimmer), `error`, `pending`. |
|
|
136
141
|
|
|
@@ -196,7 +201,31 @@ Logo tile + name. Props: `name`, `subtitle?`, `icon?`, `action?` (trailing contr
|
|
|
196
201
|
|
|
197
202
|
### `PageHeader`
|
|
198
203
|
|
|
199
|
-
Title row
|
|
204
|
+
Title row with the page's `h1`. Props: `title`, `description?`, `count?` (badge after title), `eyebrow?` (small text above), `icon?` (leading mark before the title: `CategoryDot`, `MemberAvatar`, a lucide icon at 24px), `actions?` (right side, wraps on mobile).
|
|
205
|
+
|
|
206
|
+
### `Page`, `PageSection`
|
|
207
|
+
|
|
208
|
+
`Page`: the page column inside `AppNav` or `SidebarLayout`: centred, 16px gutters on phones (32px from 768px), children spaced `gap-7`. Props: `width?` (`sm | md | lg`, default `md` = 42rem, like `FocusLayout`), div props. `PageSection`: a region named by its `h2` (so headings go h1, h2 without skipping), with `title`, `action?` (right of the heading, 44px tall on touch screens) and `children`. Inside a card, use `Heading level={3}`.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
;<Page>
|
|
212
|
+
<PageHeader
|
|
213
|
+
title="Groceries"
|
|
214
|
+
icon={<CategoryDot color={1} />}
|
|
215
|
+
actions={<UserMenu compact name={user.name} className="md:hidden" />}
|
|
216
|
+
/>
|
|
217
|
+
<PageSection
|
|
218
|
+
title="This week"
|
|
219
|
+
action={
|
|
220
|
+
<Button variant="ghost" size="sm">
|
|
221
|
+
See all
|
|
222
|
+
</Button>
|
|
223
|
+
}
|
|
224
|
+
>
|
|
225
|
+
<SectionedList sections={sections} onToggle={setDone} />
|
|
226
|
+
</PageSection>
|
|
227
|
+
</Page>
|
|
228
|
+
```
|
|
200
229
|
|
|
201
230
|
### `StatCard`
|
|
202
231
|
|
|
@@ -242,11 +271,11 @@ Label-over-value pairs as a `<dl>`. Props: `items: { label; value }[]`, `columns
|
|
|
242
271
|
|
|
243
272
|
### `UserChip`
|
|
244
273
|
|
|
245
|
-
Avatar + name + subtitle. Props: `name`, `subtitle?`, `avatarUrl?`, `action?`, `size?`. In a sidebar icon rail only the avatar shows. Also exports `initials(name)`.
|
|
274
|
+
Avatar + name + subtitle. Props: `name`, `subtitle?`, `avatarUrl?`, `color?` (`CategoryColor` behind the initials), `action?`, `size?`. In a sidebar icon rail only the avatar shows. Also exports `initials(name)`.
|
|
246
275
|
|
|
247
276
|
### `UserMenu`
|
|
248
277
|
|
|
249
|
-
The signed-in user as a menu button (sidebar footer or top bar). The menu has a header, your items (`children`, e.g. a Settings `DropdownMenuItem`), a Light / Dark / System theme choice, and Sign out.
|
|
278
|
+
The signed-in user as a menu button (sidebar footer or top bar). The menu has a header, your items (`children`, e.g. a Settings `DropdownMenuItem`), a Light / Dark / System theme choice, and Sign out. The trigger is named by its visible name and subtitle (in a sidebar icon rail, where it is just the avatar, by `name`) and the menu opens to the side there. `compact` makes the trigger just the avatar anywhere (40px, 44px on touch screens, named "Account menu for {name}") with the menu below it, for a phone page header. Upgrading from 0.9: the non-compact trigger is no longer named "Account menu for {name}"; find it by `name` instead. Props: `name`, `subtitle?`, `avatarUrl?`, `color?` (`CategoryColor` behind the initials), `children?`, `themeSwitch?` (default true, needs `ThemeProvider`), `onSignOut?`, `signOutLabel?`, `compact?`.
|
|
250
279
|
|
|
251
280
|
```tsx
|
|
252
281
|
;<UserMenu name={email} subtitle="Signed in" onSignOut={signOut}>
|
|
@@ -257,28 +286,170 @@ The signed-in user as a menu button (sidebar footer or top bar). The menu has a
|
|
|
257
286
|
</UserMenu>
|
|
258
287
|
```
|
|
259
288
|
|
|
289
|
+
### `MemberAvatar`
|
|
290
|
+
|
|
291
|
+
A person's photo, or their initials on their category colour (`bg-category-N`, `text-category-foreground`). Props: `name`, `avatarUrl?`, `color?: CategoryColor` (1 to 6; without it the soft primary tint), `size?` (`sm` 28px, `default` 36px, `lg` 56px). It is `role="img"` named after the person; pass `aria-hidden` when the name is shown next to it. Used by `UserChip`, `UserMenu` and `TaskRow`.
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
;<MemberAvatar name="Sam Rivera" avatarUrl={sam.photoUrl ?? undefined} color={sam.color} size="sm" />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### `AvatarStack`, `VisibilityBadge`
|
|
298
|
+
|
|
299
|
+
`AvatarStack`: overlapping small `MemberAvatar`s. Props: `members: { name; avatarUrl?; color? }[]`, `max?` (default 4): total slots; past it, `max - 1` faces and a "+N" chip (read as "N more"). `VisibilityBadge`: who can see something. Props: `visibility: "shared" | "private"`, `members?` (faces when shared), `label?` (default "Shared with everyone" or "Only you"; the lock shows when private).
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
;<VisibilityBadge visibility={list.private ? "private" : "shared"} members={household} />
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### `CategoryDot`
|
|
306
|
+
|
|
307
|
+
A 10px dot in a category colour, e.g. as the `icon` of a list in `AppNav`'s group. Props: `color: CategoryColor`, `label?` (accessible name when the colour means something on its own, e.g. whose event; decorative without it), `locked?` (adds a lock named `lockLabel?`, default "Only you").
|
|
308
|
+
|
|
309
|
+
### `ColorSwatchPicker`
|
|
310
|
+
|
|
311
|
+
The six category colours as a radio group (arrow keys skip taken ones). 32px swatches with 44px touch targets; the chosen one has a check and a ring. Props: `value?: CategoryColor`, `onValueChange?(color)`, `taken?: Partial<Record<CategoryColor, string>>` (colour to who has it: shown with their initial, disabled, named "Violet, taken by Riley"). Name it with `aria-label` or `aria-labelledby`.
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
;<ColorSwatchPicker aria-label="Your colour" value={me.color} onValueChange={setColor} taken={{ 3: "Riley" }} />
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### `AvatarUpload`, `cropImageToSquare`
|
|
318
|
+
|
|
319
|
+
Profile photo: a 72px avatar, "Upload a photo" / "Change photo" and "Remove photo". The chosen image is cropped to its centred square at 256x256 in the browser (canvas, JPEG) and previewed at once. Props: `name`, `color?`, `avatarUrl?` (current photo), `onChange(photo: Blob | null)` (an event, not a controlled value) (`null` after Remove), `maxBytes?` (JPEG quality steps down to fit), `onError?(error)` ("Could not read that image." or "That photo is too big."), `description?`. `cropImageToSquare(file, { size?, maxBytes? })` is the crop on its own: a `Promise<Blob>`.
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
;<AvatarUpload
|
|
323
|
+
name={me.name}
|
|
324
|
+
color={me.color}
|
|
325
|
+
avatarUrl={me.photoUrl ?? undefined}
|
|
326
|
+
maxBytes={512 * 1024}
|
|
327
|
+
onChange={(photo) => (photo ? uploadPhoto(photo) : removePhoto())}
|
|
328
|
+
onError={(e) => toast.error(e.message)}
|
|
329
|
+
description="Without a photo, your initial and colour are used."
|
|
330
|
+
/>
|
|
331
|
+
```
|
|
332
|
+
|
|
260
333
|
### `ConfirmDialog`
|
|
261
334
|
|
|
262
335
|
Controlled "are you sure" dialog. Props: `open`, `onOpenChange`, `title`, `description?`, `confirmLabel?`, `cancelLabel?`, `destructive?` (red confirm), `onConfirm`.
|
|
263
336
|
|
|
337
|
+
### `PromptDialog`
|
|
338
|
+
|
|
339
|
+
Controlled dialog that asks for one line of text (New list, Rename). Props: `open`, `onOpenChange`, `title`, `description?`, `label` (field label), `defaultValue?`, `submitLabel?` (default "Save"), `cancelLabel?` (default "Cancel"), `maxLength?`, `onSubmit(value): void | Promise<void>` (trimmed; empty is blocked). It closes when `onSubmit` returns or resolves and starts fresh on every open; a thrown error's message shows under the field and the dialog stays open.
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
;<PromptDialog
|
|
343
|
+
open={renaming}
|
|
344
|
+
onOpenChange={setRenaming}
|
|
345
|
+
title="Rename list"
|
|
346
|
+
label="Name"
|
|
347
|
+
defaultValue={list.name}
|
|
348
|
+
submitLabel="Rename"
|
|
349
|
+
maxLength={60}
|
|
350
|
+
onSubmit={(name) => api.renameList(list.id, name)}
|
|
351
|
+
/>
|
|
352
|
+
```
|
|
353
|
+
|
|
264
354
|
### `CopyField`
|
|
265
355
|
|
|
266
|
-
Read-only value with a copy button (IDs, paths, URLs). Props: `value` (copied), `display?` (shown instead, full value as tooltip), `label?` (leading addon), `aria-label?`, `mono?` (default true).
|
|
356
|
+
Read-only value with a copy button (IDs, paths, URLs). Props: `value` (copied), `display?` (shown instead, full value as tooltip), `label?` (leading addon), `aria-label?`, `mono?` (default true), `copiedLabel?` (default "Copied"; after copying, the button's name and a label beside it read this for a moment).
|
|
357
|
+
|
|
358
|
+
### `DateTimePicker`
|
|
359
|
+
|
|
360
|
+
Date field: a button that opens a `Calendar` in a `Popover`, with an optional time field and a Clear button. Value is a floating local date, no time zone: `DateTimeValue = { date: "YYYY-MM-DD", time?: "HH:MM" }`. Props: `value: DateTimeValue | null`, `onValueChange(value | null)`, `withTime?` (time field under the calendar; enabled once a date is set), `placeholder?` (default "Pick a date"). Other props (`id`, `aria-label`, `disabled`) go to the trigger button. Day cells are 44px on phones.
|
|
361
|
+
|
|
362
|
+
### `TimeZonePicker`
|
|
363
|
+
|
|
364
|
+
Searchable IANA time zone field built on `Combobox`, listing `Intl.supportedValuesOf("timeZone")` (underscores shown as spaces, so "new york" matches). A `value` not in that list (an alias such as "US/Pacific") is kept at the top so it still shows. Props: `value` (e.g. `"America/Vancouver"`), `onValueChange(zone)`, `id?` (for a `FieldLabel` or `SettingsRow` `htmlFor`), `className?`.
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
;<SettingsRow
|
|
368
|
+
label="Time zone"
|
|
369
|
+
htmlFor="tz"
|
|
370
|
+
control={<TimeZonePicker id="tz" value={me.timeZone} onValueChange={(timeZone) => save({ timeZone })} />}
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `DetailSheet`
|
|
375
|
+
|
|
376
|
+
A record's details, one API for both sizes: a floating side sheet from 768px up, a full-screen drawer with a close button on phones. Props: `open`, `onOpenChange`, `title` (heading and accessible name), `description?`, `footer?` (pinned under the scrolling body, e.g. Delete), `children` (the fields). `data-layout` is `"sheet"` or `"drawer"`.
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
;<DetailSheet
|
|
380
|
+
open={!!task}
|
|
381
|
+
onOpenChange={(o) => !o && close()}
|
|
382
|
+
title="To-do"
|
|
383
|
+
footer={
|
|
384
|
+
<Button variant="ghost" onClick={askDelete}>
|
|
385
|
+
Delete
|
|
386
|
+
</Button>
|
|
387
|
+
}
|
|
388
|
+
>
|
|
389
|
+
<Field>
|
|
390
|
+
<FieldLabel htmlFor="when">When</FieldLabel>
|
|
391
|
+
<DateTimePicker id="when" withTime value={due} onValueChange={setDue} />
|
|
392
|
+
</Field>
|
|
393
|
+
</DetailSheet>
|
|
394
|
+
```
|
|
267
395
|
|
|
268
396
|
### `Callout`
|
|
269
397
|
|
|
270
398
|
Quiet note with an icon (sparkle by default). Props: `icon?`, `tone?` (`"muted" | "soft"`).
|
|
271
399
|
|
|
400
|
+
### `Celebration`, `celebrate`
|
|
401
|
+
|
|
402
|
+
An occasional confetti burst, e.g. when the last to-do of the day is ticked. Mount `<Celebration />` once near the root (like `Toaster`), then call `celebrate()` from anywhere. CSS only, about 1.5 s, renders nothing between bursts (`data-slot="celebration"` while it plays), and does nothing under `prefers-reduced-motion`. Keep it rare.
|
|
403
|
+
|
|
272
404
|
### `LinkTile`
|
|
273
405
|
|
|
274
406
|
Muted tile that lifts on hover, for service launchers. Props: `title`, `subtitle?` (mono), `icon?`, `status?: { tone; label }`, `href`, or `asChild` with your router `<Link>` as the only child.
|
|
275
407
|
|
|
408
|
+
### `MarkdownEditor`
|
|
409
|
+
|
|
410
|
+
Markdown field with Write and Preview tabs; Preview renders with `Markdown`. Props: `value`, `onValueChange(value)`, `renderPreview?(markdown)` (custom renderer, wrapped in `Prose`), `wrapperClassName?`. Other props (`id`, `placeholder`, `aria-*`, `className`) go to the `Textarea`.
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
;<MarkdownEditor id="body" value={body} onValueChange={setBody} />
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### `ChecklistEditor`
|
|
417
|
+
|
|
418
|
+
Steps inside a record, e.g. a to-do's checklist: `CheckCircle` per item (the text is its label), a Remove button, an add field (Enter adds the trimmed text and clears), and an "x of y done" line when there are items. Props: `items: { id; text; done }[]`, `onToggle(id, done)`, `onRemove(id)`, `onAdd(text)`, `addPlaceholder?` (also the field's name, default "Add an item"), `progressLabel?(done, total)`. The root is a `role="group"`: name it with `aria-label` or `aria-labelledby`.
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
;<ChecklistEditor
|
|
422
|
+
aria-label="Checklist"
|
|
423
|
+
items={task.checklist}
|
|
424
|
+
onToggle={toggleStep}
|
|
425
|
+
onRemove={removeStep}
|
|
426
|
+
onAdd={addStep}
|
|
427
|
+
addPlaceholder="Add a step"
|
|
428
|
+
/>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### `CommentThread`
|
|
432
|
+
|
|
433
|
+
Comments on a record, oldest first, then a box and a submit button (disabled while empty; submits the trimmed text and clears). Bodies render with `Markdown`. Props: `comments: { id; author: { name; color?; avatarUrl? }; meta?; body }[]` (`meta` is muted text after the name, e.g. "via HomeBot · 2 h ago"), `onSubmit(text)`, `placeholder?` (also the box's name, default "Add a comment"), `submitLabel?` (default "Comment"). Show your own empty state if you want one.
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
;<CommentThread comments={task.comments} onSubmit={(text) => api.comment(task.id, text)} />
|
|
437
|
+
```
|
|
438
|
+
|
|
276
439
|
### `AppNav`
|
|
277
440
|
|
|
278
441
|
One nav config, two renderings: on desktop a `SidebarLayout` sidebar (brand, action button, places, one collapsible group, footer; active place is a raised card; collapses to the icon rail), under 768px a bottom tab bar (first 5 places, 56px targets, `aria-current="page"`) plus a round floating action button. Use it instead of `AppShell` for consumer apps that need phone navigation.
|
|
279
442
|
|
|
280
|
-
Props: `places: AppNavPlace[]` (`label`, `icon?`, `href?` or `onSelect?`, `active?`, `count?`, `tab?: false` to keep out of the tab bar), `group?: AppNavGroup` (`label`, `items`, `defaultOpen?`, `onAdd?`, `addLabel?`), `action?: { label; icon; onSelect }
|
|
281
|
-
On phones the group and footer live in the sidebar sheet: put a `SidebarTrigger` in the page header if users need them there. Render `AssistantDock` inside `children` so it clears the tab bar. Toasts stack above the tab bar, the action button and the dock launcher on their own; no `Toaster` offset needed.
|
|
443
|
+
Props: `places: AppNavPlace[]` (`label`, `icon?`, `href?` or `onSelect?`, `active?`, `count?`, `tab?: false` to keep out of the tab bar, `sidebar?: false` for a tab-bar-only place such as Lists when the sidebar has a Lists group), `group?: AppNavGroup` (`label`, `href?` (heading becomes a link, e.g. to all lists; the chevron is a separate "Collapse Lists" button), `active?` (the heading link is the current page), `items`, `defaultOpen?`, `onAdd?`, `addLabel?`), `action?: { label; icon; onSelect; sidebar?: false }` (`sidebar: false` keeps only the round phone button), `brand?`, `footer?` (usually `UserMenu`), `label?` (nav name, default "Main"), `linkComponent?`, `collapsible?`, `storageKey?`, `defaultOpen?`, `width?`, `fill?` (main column is exactly the screen height minus the tab bar (and the round action button's space when `action` is set) and does not scroll, for a chat: the child takes it with `flex-1 min-h-0`, which `ChatPanel` already has), `children`.
|
|
444
|
+
On phones the group and footer live in the sidebar sheet: put a `SidebarTrigger` in the page header if users need them there. With `action`, the main column's bottom padding also clears the round button, so a trailing control on the last row is not under it. Render `AssistantDock` inside `children` so it clears the tab bar. Toasts stack above the tab bar, the action button and the dock launcher on their own; no `Toaster` offset needed.
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
;<AppNav fill places={places} footer={<UserMenu name={user.name} />}>
|
|
448
|
+
<ChatPanel title="Assistant" composer={<PromptInput … />}>
|
|
449
|
+
{thread}
|
|
450
|
+
</ChatPanel>
|
|
451
|
+
</AppNav>
|
|
452
|
+
```
|
|
282
453
|
|
|
283
454
|
### `FocusLayout`
|
|
284
455
|
|
|
@@ -286,7 +457,7 @@ A page outside the app frame (settings, onboarding, setup): slim sticky top bar,
|
|
|
286
457
|
|
|
287
458
|
### `SettingsSection`, `SettingsRow`
|
|
288
459
|
|
|
289
|
-
`SettingsSection`: card with `title`, `description?` and rows divided by hairlines (a labelled `region`). `SettingsRow`: `label`, `description?`, `control?` (Switch, Select, ToggleGroup, Button), `htmlFor?` (makes the label a `<label>` for the control). Add `content` to expand inline (change password, add a connection): without a `control` the row gets a "Change"/"Cancel" button (`expandLabel?`); control it with `open`/`onOpenChange` or `defaultOpen`.
|
|
460
|
+
`SettingsSection`: card with `title`, `description?` and rows divided by hairlines (a labelled `region`). `SettingsRow`: `label`, `description?`, `control?` (Switch, Select, ToggleGroup, Button), `htmlFor?` (makes the label a `<label>` for the control), `body?` (always shown under the label, full width, no toggle: a list, a hint, a short form). Add `content` to expand inline (change password, add a connection): without a `control` the row gets a "Change"/"Cancel" button (`expandLabel?`); control it with `open`/`onOpenChange` or `defaultOpen`.
|
|
290
461
|
|
|
291
462
|
```tsx
|
|
292
463
|
;<FocusLayout
|
|
@@ -307,6 +478,7 @@ A page outside the app frame (settings, onboarding, setup): slim sticky top bar,
|
|
|
307
478
|
control={<Switch id="daily" />}
|
|
308
479
|
/>
|
|
309
480
|
<SettingsRow label="Password" content={<ChangePasswordForm />} />
|
|
481
|
+
<SettingsRow label="Connections" body={<ConnectionList />} />
|
|
310
482
|
</SettingsSection>
|
|
311
483
|
</FocusLayout>
|
|
312
484
|
```
|
|
@@ -321,7 +493,7 @@ Props instead of class names, for blocks that should look right without styling
|
|
|
321
493
|
|
|
322
494
|
### `Text`, `Heading`
|
|
323
495
|
|
|
324
|
-
`Text` takes `size` (`xs | sm | base | lg | xl | 2xl`), `tone` (`default | muted | success | warning | danger | info`), `weight`, `mono`, `truncate`, `lines` (1 to 3) and `tabular`, and renders `as` a span, p or div. `Heading` is a card section title (`level={3}
|
|
496
|
+
`Text` takes `size` (`xs | sm | base | lg | xl | 2xl`), `tone` (`default | muted | success | warning | danger | info`), `weight`, `mono`, `truncate`, `lines` (1 to 3) and `tabular`, and renders `as` a span, p or div. `Heading` is a page section title (`level={2}`, under the page `h1`; `PageSection` uses it), a card section title (`level={3}`, default) or a small uppercase group label (`level={4}`). Do not skip levels: a page with an h1 needs h2s before h3s.
|
|
325
497
|
|
|
326
498
|
### `Metric`, `MetricGroup`
|
|
327
499
|
|
|
@@ -337,7 +509,7 @@ Shows the first `initial` children (default 5) and a "Show N more" button for th
|
|
|
337
509
|
|
|
338
510
|
### `Collapse`
|
|
339
511
|
|
|
340
|
-
Animates children between full height and 0 (CSS `grid-template-rows`), so neighbours slide instead of jumping. Closed content is `inert` and `aria-hidden`. Props: `open`, `contentClassName?`. No motion under `prefers-reduced-motion`.
|
|
512
|
+
Animates children between full height and 0 (CSS `grid-template-rows`), so neighbours slide instead of jumping. Closed content is `inert` and `aria-hidden`. Clips vertically only, so focus rings and hit areas at the sides are not cut off. Props: `open`, `contentClassName?`. No motion under `prefers-reduced-motion`.
|
|
341
513
|
|
|
342
514
|
## Data display
|
|
343
515
|
|
|
@@ -385,11 +557,16 @@ Mini bar history (pings, uptime). Props: `data: (number | { value; tone?; label?
|
|
|
385
557
|
|
|
386
558
|
### `TaskRow`
|
|
387
559
|
|
|
388
|
-
One to-do: `CheckCircle` aligned with the first line, title, one meta line, optional assignee avatar. Props: `title`, `checked`, `onCheckedChange(checked)`, `meta?`, `assignee?: { name; avatarUrl? }
|
|
560
|
+
One to-do: `CheckCircle` aligned with the first line, title, one meta line, optional assignee avatar. Props: `title`, `checked`, `onCheckedChange(checked)`, `meta?`, `assignee?: { name; avatarUrl?; color? }` (a `MemberAvatar`), `important?` (say it in `meta` too), `onOpen?` (title becomes a button, e.g. to open a sheet), `disabled?`.
|
|
389
561
|
|
|
390
562
|
### `SectionedList`
|
|
391
563
|
|
|
392
|
-
A to-do list: one add box at the top, sections with headings, done items folded into a "Show N done" toggle, an empty state. A ticked row stays
|
|
564
|
+
A to-do list: one add box at the top, sections with headings, done items folded into a "Show N done" toggle, an empty state. A ticked row stays struck through for `lingerMs`, then folds away with `Collapse`. Props: `sections: { id; title?; action?; empty?; items: SectionedListItem[] }[]` (item: `id`, `title`, `meta?`, `done?`, `important?`, `assignee?`), `onToggle(id, done)`, `lingerMs?` (default 600; 9000 matches `toastUndo`), `renderItem?(item, toggle)`, `onAdd?(text)`, `addPlaceholder?` (default "Add a to-do"), `addChips?(text)`, `onOpen?(id)`, `empty?`, `defaultShowDone?`.
|
|
565
|
+
|
|
566
|
+
- Section `id` is also the `<section>`'s DOM id, so `DayStrip` or an `#id` link can jump to it. Keep ids unique on the page.
|
|
567
|
+
- `action` sits at the right of the heading (a small ghost `Button`, e.g. "Move to today"; stretched to 44px on phones). `empty` keeps the section shown with that text when nothing in it is open ("Nothing planned").
|
|
568
|
+
- `renderItem` draws other rows, e.g. `AgendaRow`. Its `toggle(done)` lingers and folds the row like a `TaskRow`. Items without `done` (events) always stay. Type the items with `SectionedList<MyItem>`.
|
|
569
|
+
- `addChips(text)` returns chips shown at the right of the add box while it has text, like `AskBox` `chips` (the date or person parsed from the text).
|
|
393
570
|
|
|
394
571
|
```tsx
|
|
395
572
|
;<SectionedList
|
|
@@ -400,6 +577,39 @@ A to-do list: one add box at the top, sections with headings, done items folded
|
|
|
400
577
|
/>
|
|
401
578
|
```
|
|
402
579
|
|
|
580
|
+
### `AgendaRow`
|
|
581
|
+
|
|
582
|
+
One line of a day's agenda: time column, event icon (or a `CheckCircle` for a to-do), title, one meta line, whose dot and name. Props: `title`, `time?` ("3:15", "All day"), `meta?`, `who?: { name; color: CategoryColor }` (`CategoryDot` before the name), `checked?` + `onCheckedChange?(checked)` (draws the check instead of the icon), `important?`, `onOpen?` (title becomes a button, e.g. to open an event dialog).
|
|
583
|
+
|
|
584
|
+
### `DayStrip`
|
|
585
|
+
|
|
586
|
+
A row of day links (short weekday, date, a dot when `hasPlans`) that smooth-scroll to the element whose id is the day's `id` (instant under reduced motion) and move focus to it; scrolls sideways when the days do not fit. Props: `days: { id; date: "YYYY-MM-DD"; hasPlans? }[]` (floating local date, like `DateTimePicker`), `value?` (selected id, `aria-current`), `onValueChange?(id)`, `aria-label?` (default "Jump to a day"), `sticky?` (sticks at `top-0` with the page background while the page or any container scrolls, and gives the jumped-to element a `scroll-margin-top` as tall as the strip so its heading lands below it; add `className="top-14"` under a sticky header). Each link's name is the full date ("Thursday, November 12, has plans") as visually hidden text, so it contains what is shown.
|
|
587
|
+
|
|
588
|
+
```tsx
|
|
589
|
+
;<>
|
|
590
|
+
<DayStrip
|
|
591
|
+
sticky
|
|
592
|
+
days={week.map((d) => ({ id: d.key, date: d.date, hasPlans: d.items.length > 0 }))}
|
|
593
|
+
value={day}
|
|
594
|
+
onValueChange={setDay}
|
|
595
|
+
/>
|
|
596
|
+
<SectionedList<WeekItem>
|
|
597
|
+
lingerMs={9000}
|
|
598
|
+
sections={week.map((d) => ({ id: d.key, title: d.label, empty: "Nothing planned", items: d.items }))}
|
|
599
|
+
onToggle={setDone}
|
|
600
|
+
renderItem={(i, toggle) => (
|
|
601
|
+
<AgendaRow
|
|
602
|
+
time={i.time}
|
|
603
|
+
title={i.title}
|
|
604
|
+
who={i.who}
|
|
605
|
+
{...(i.kind === "todo" && { checked: !!i.done, onCheckedChange: toggle })}
|
|
606
|
+
onOpen={() => open(i)}
|
|
607
|
+
/>
|
|
608
|
+
)}
|
|
609
|
+
/>
|
|
610
|
+
</>
|
|
611
|
+
```
|
|
612
|
+
|
|
403
613
|
## AI and chat
|
|
404
614
|
|
|
405
615
|
A chat screen is `ChatShell` (sidebar with `AppBrand`, `NewConversationButton`, `ConversationList`, `UserMenu`; optional details panel) around a `ChatPanel` (header, thread, `PromptInput`). Agent apps add `ToolGroup`/`ToolStep` for tool calls and `ApprovalCard`/`ApprovalResult` for permission prompts. See `playground/screens/chat.tsx` and `playground/screens/agent.tsx`.
|
|
@@ -495,7 +705,7 @@ Pill showing the active model with a status dot, opening a menu. Props: `models:
|
|
|
495
705
|
Grouped thread list. Props: `groups: { label; items: Conversation[] }[]`, `activeId?`, `onSelect?(id)`, `onDelete?(conversation)` (adds a delete button per row that confirms first; `deleteDescription?` is the confirmation body), `renderAction?(conversation)` (any other trailing control; wins over `onDelete`; on desktop it shows on hover/focus), `empty?`.
|
|
496
706
|
`Conversation = { id; title; description?; tone?; pulse?; deleteDisabled? }`: `description` is the second line (time, or a live state with a `tone` dot); `deleteDisabled` blocks delete for that row (a string is the reason, shown as the tooltip).
|
|
497
707
|
Inside a `Sidebar` it closes the mobile sheet on select and hides in the icon rail.
|
|
498
|
-
`groupByDate(items, getDate, toConversation,
|
|
708
|
+
`groupByDate(items, getDate, toConversation, options?)` buckets items into Today / Yesterday / Previous 7 days / Earlier, newest first. `options` is `{ now?, buckets? }` (or just `now`); `buckets: "week"` gives Today / This week (the 6 days before today) / Earlier.
|
|
499
709
|
`formatWhen(date, now?, locale?)`: short timestamp for rows and headers: the time today ("9:41 AM"), the date this year ("Nov 3"), else with the year.
|
|
500
710
|
|
|
501
711
|
### `NewConversationButton`
|
|
@@ -530,15 +740,32 @@ Agent tool calls as a bordered, collapsible card. `ToolStatus = "queued" | "wait
|
|
|
530
740
|
|
|
531
741
|
### `AssistantProvider`, `useAssistant`, `useAssistantPage`
|
|
532
742
|
|
|
533
|
-
One assistant conversation across pages. `AssistantProvider` props: `onSend(text, { page, messages, append, update })` (return the reply, or `append`/`update` messages yourself for streaming and receipts; a thrown error becomes the reply), `initialMessages?`, `defaultOpen?`. `useAssistant()` returns `{ open, setOpen, toggle, messages, pending, page, setPage, send, append, update, clear }`. A page calls `useAssistantPage({ id, title?, data?, suggestions? })` to become the context while mounted.
|
|
743
|
+
One assistant conversation across pages. `AssistantProvider` props: `onSend(text, { page, messages, append, update, signal })` (return the reply, or `append`/`update` messages yourself for streaming and receipts; a thrown error becomes the reply; `signal` aborts when the user presses Stop, so pass it to `fetch` or listen for `abort` to tell your server), `initialMessages?`, `defaultOpen?`. `useAssistant()` returns `{ open, setOpen, toggle, messages, pending, reply, page, setPage, send, stop, append, update, clear }`: `send(text, { open: false }?)` skips opening the dock, `stop()` aborts the running send, ends `pending` and drops its late reply, `reply` is the assistant messages after the latest user message (null until there is one), for `AskBox`. A page calls `useAssistantPage({ id, title?, data?, suggestions? })` to become the context while mounted.
|
|
534
744
|
|
|
535
745
|
### `AssistantDock`
|
|
536
746
|
|
|
537
|
-
The assistant as a floating panel over the page (never reflows it): header (icon, title, full-screen link, close), empty state with suggestions, thread, `AskBox
|
|
747
|
+
The assistant as a floating panel over the page (never reflows it): header (icon, title, full-screen link, close), empty state with suggestions, thread, `AskBox` (Stop while the assistant works, wired to `useAssistant().stop`). Closed, it is a soft launcher button. Escape closes and returns focus to the launcher. Props: `title?` (default "Assistant"), `icon?`, `fullScreenHref?`, `emptyTitle?`, `emptyDescription?`, `suggestions?` (default: the page's), `placeholder?`, `dictation?`, `launcher?` (false when another control opens it), `launcherLabel?`.
|
|
538
748
|
|
|
539
749
|
### `AskBox`
|
|
540
750
|
|
|
541
|
-
Large rounded composer: auto-growing text, `chips` shown while there is text, suggestion pills, `DictationButton`. The focus ring is on the box (`focus-box`). Props: `value`, `onValueChange`, `onSubmit(text)`, `placeholder?` (also the label, default "Ask anything"), `chips?`, `suggestions?`, `onSuggestion?` (default submits), `dictation?` (`SpeechSource`, or `false` to hide the mic), `lang?`, `disabled
|
|
751
|
+
Large rounded composer: auto-growing text, `chips` shown while there is text, suggestion pills, `DictationButton`. The focus ring is on the box (`focus-box`). Props: `value`, `onValueChange`, `onSubmit(text)`, `placeholder?` (also the label, default "Ask anything"), `chips?`, `suggestions?`, `onSuggestion?` (default submits), `dictation?` (`SpeechSource`, or `false` to hide the mic), `lang?`, `disabled?`, `pending?` (blocks sending; with `onStop?` the send button becomes Stop), `reply?` (the answer under the box; passing it, even `null`, shows "Working" while `pending`), `chatHref?` (an "Open chat" link under the reply).
|
|
752
|
+
|
|
753
|
+
```tsx
|
|
754
|
+
// Inline answer under the box (no dock), e.g. a Today page
|
|
755
|
+
const { send, pending, stop, reply } = useAssistant()
|
|
756
|
+
;<AskBox
|
|
757
|
+
value={text}
|
|
758
|
+
onValueChange={setText}
|
|
759
|
+
onSubmit={(t) => {
|
|
760
|
+
setText("")
|
|
761
|
+
send(t, { open: false })
|
|
762
|
+
}}
|
|
763
|
+
pending={pending}
|
|
764
|
+
onStop={stop}
|
|
765
|
+
reply={reply}
|
|
766
|
+
chatHref={`/chat/${conversationId}`}
|
|
767
|
+
/>
|
|
768
|
+
```
|
|
542
769
|
|
|
543
770
|
### `DictationButton`, `useDictation`, `browserSpeechSource`
|
|
544
771
|
|
|
@@ -549,11 +776,20 @@ Large rounded composer: auto-growing text, `chips` shown while there is text, su
|
|
|
549
776
|
|
|
550
777
|
What the assistant did, as a compact card: round check, `title` (past tense), `meta?`, Undo (`onUndo?`, `undoLabel?`). `undone` strikes it through and shows "Undone".
|
|
551
778
|
|
|
552
|
-
### `CodeSnippet`, `InlineCode`, `Prose`
|
|
779
|
+
### `CodeSnippet`, `InlineCode`, `Prose`, `ProseBlockquote`
|
|
553
780
|
|
|
554
781
|
`CodeSnippet`: one-prop code panel with language label, copy button and shiki syntax highlighting (each language loads on first use). Props: `code`, `language?` (`bash`, `tsx`, `ts`, `js`, `json`, `python`, `sql`, `yaml`, `css`, `html`, `markdown`, `diff`; others render plain), `highlight?` (default true), `lineNumbers?`, `hideCopy?`. Built on the `CodeBlock` primitives; drop to those for diffs or highlighted lines.
|
|
555
782
|
`InlineCode`: monospace span. `truncate?: number` cuts longer string children with an ellipsis and puts the full text in the tooltip.
|
|
556
|
-
`Prose`: typography for model output and markdown. Styles plain `p, ul, ol, li, strong, a, code, pre, h1-h4, blockquote, table` children
|
|
783
|
+
`Prose`: typography for model output and markdown. Styles plain `p, ul, ol, li, strong, a, code, pre, h1-h4, blockquote, table` children.
|
|
784
|
+
Tip boxes: a blockquote whose first word is `Tip:` (`> Tip: Turn the power off first.`) renders as a `Callout` without the word. Prose does this for plain `<blockquote>` children; with your own react-markdown pass `components={{ blockquote: ProseBlockquote }}`.
|
|
785
|
+
|
|
786
|
+
### `Markdown`
|
|
787
|
+
|
|
788
|
+
Renders a Markdown string inside `Prose`: GitHub flavoured (tables, task lists, strikethrough, autolinks), `> Tip:` blockquotes as a `Callout`. Safe for user and model text: raw HTML is dropped and `javascript:` links are removed. Props: `children: string`, `linkComponent?` (your router `Link`, gets `href`; used for links without a scheme such as `/lists/2`). Other props go to the `Prose` wrapper.
|
|
789
|
+
|
|
790
|
+
```tsx
|
|
791
|
+
;<Markdown linkComponent={Link}>{message.text}</Markdown>
|
|
792
|
+
```
|
|
557
793
|
|
|
558
794
|
### Chat primitives vs. composites
|
|
559
795
|
|
|
@@ -567,13 +803,49 @@ Parameterized versions of shadcn's login-01..05 and signup-01..05 blocks.
|
|
|
567
803
|
|
|
568
804
|
Full-page wrapper. Props: `variant` (`"centered"` card on the page, `"muted"` same on a muted background, `"split"` form left and cover image right, `"card-image"` one wide card with form and image), `brand?` (usually `AppBrand`), `image?: { src; alt }` (split and card-image), `footer?` (terms text), `children` (the form).
|
|
569
805
|
|
|
806
|
+
### `AuthForm`
|
|
807
|
+
|
|
808
|
+
The shell the auth forms share; use it for other signed-out steps (join with a code, verify an email). Props: `title`, `description?`, `children` (fields, each with a `name`), `error?`, `submitLabel`, `loading?` (spinner, disables every control), `onSubmit?(data: FormData)` (after browser validation, which `noValidate` skips; omit to submit natively), `footer?` (under the button: links, provider buttons).
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
;<AuthForm title="Join your household" submitLabel="Join" onSubmit={(d) => join(String(d.get("code")))}>
|
|
812
|
+
<Field>
|
|
813
|
+
<FieldLabel htmlFor="code">Invite code</FieldLabel>
|
|
814
|
+
<Input id="code" name="code" required />
|
|
815
|
+
</Field>
|
|
816
|
+
</AuthForm>
|
|
817
|
+
```
|
|
818
|
+
|
|
570
819
|
### `LoginForm`
|
|
571
820
|
|
|
572
821
|
Props: `onSubmit?({ email, password? })` (omit to submit natively to an `action`), `mode?` (`"password"` or `"email-only"` for magic links), `providers?: { id; label; icon? }[]` + `onProviderClick?(id)` (3+ providers with icons collapse to an icon row), `providersFirst?`, `forgotPasswordHref?`, `signupHref?`, `title?`, `description?`, `submitLabel?`, `loading?`, `error?`.
|
|
573
822
|
|
|
823
|
+
### `PasswordInput`
|
|
824
|
+
|
|
825
|
+
Password `Input` with a Show/Hide button (`aria-label` "Show password" / "Hide password") that switches `type` between `password` and `text`. Input props go to the input; `wrapperClassName?` styles the wrapper. `LoginForm`, `SignupForm` and `NewPasswordForm` use it for every password field.
|
|
826
|
+
|
|
574
827
|
### `SignupForm`
|
|
575
828
|
|
|
576
|
-
Props: `onSubmit?({ name?, email, password })`, `nameField?` (default true), `confirmPassword?` (default true; mismatches are blocked with a browser validation message), `minPasswordLength?` (default 8), `passwordHint?`, `providers?`, `onProviderClick?`, `providersFirst?`, `loginHref?`, `title?`, `description?`, `submitLabel?`, `loading?`, `error?`.
|
|
829
|
+
Props: `onSubmit?({ name?, email, password }, data: FormData)`, `children?` (extra fields before the name, e.g. a household name; read them from `data`), `nameField?` (default true), `confirmPassword?` (default true; mismatches are blocked with a browser validation message), `minPasswordLength?` (default 8), `passwordHint?`, `providers?`, `onProviderClick?`, `providersFirst?`, `loginHref?`, `title?`, `description?`, `submitLabel?`, `loading?`, `error?`.
|
|
830
|
+
|
|
831
|
+
```tsx
|
|
832
|
+
;<SignupForm onSubmit={(v, data) => create({ ...v, household: String(data.get("household")) })}>
|
|
833
|
+
<Field>
|
|
834
|
+
<FieldLabel htmlFor="household">Household name</FieldLabel>
|
|
835
|
+
<Input id="household" name="household" required />
|
|
836
|
+
</Field>
|
|
837
|
+
</SignupForm>
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### `NewPasswordForm`
|
|
841
|
+
|
|
842
|
+
Reset or first password: "New password" + "Confirm password" (a mismatch is blocked with a browser validation message). Props: `onSubmit?({ password })`, `minPasswordLength?` (default 8), `passwordHint?`, `title?` (default "Choose a new password"), `description?`, `submitLabel?` (default "Save password"), `loading?`, `error?`.
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
;<AuthLayout variant="centered">
|
|
846
|
+
<NewPasswordForm onSubmit={({ password }) => reset(token, password)} loading={pending} error={error} />
|
|
847
|
+
</AuthLayout>
|
|
848
|
+
```
|
|
577
849
|
|
|
578
850
|
`GitHubIcon`, `GoogleIcon`, `AppleIcon`: brand marks for provider buttons.
|
|
579
851
|
|
|
@@ -605,6 +877,7 @@ Props: `onSubmit?({ name?, email, password })`, `nameField?` (default true), `co
|
|
|
605
877
|
| `useIsMobile()` | `true` under 768px. |
|
|
606
878
|
| `useAssistant()`, `useAssistantPage(page)`, `useDictation(options)` | See [`AssistantProvider`](#assistantprovider-useassistant-useassistantpage) and [`DictationButton`](#dictationbutton-usedictation-browserspeechsource). |
|
|
607
879
|
| `useMediaQuery(query)` | `true` while the media query matches; `false` during SSR. |
|
|
880
|
+
| `categoryBg[color]`, type `CategoryColor` | `bg-category-N` class for a colour `1..6`, as a complete class name. |
|
|
608
881
|
| `cn(...classes)` | clsx + tailwind-merge. Use for every `className` you compose. |
|
|
609
882
|
|
|
610
883
|
## Tokens
|
|
@@ -623,6 +896,7 @@ Use these as Tailwind utilities (`bg-*`, `text-*`, `border-*`, `ring-*`, `fill-*
|
|
|
623
896
|
| `border`, `input`, `ring` | Hairlines, input borders, focus rings. |
|
|
624
897
|
| `chart-1` … `chart-5`, `chart-grid` | Data series and gridlines. |
|
|
625
898
|
| `category-1` … `category-6` | Members, lists, calendars: blue, teal, violet, rose, slate, cyan, each with a dark value. Never status. |
|
|
899
|
+
| `category-foreground` | Text and icons on a category fill (initials, a swatch's check). |
|
|
626
900
|
| `sidebar-*` | Sidebar surfaces (default to the page tokens). |
|
|
627
901
|
|
|
628
902
|
Other theme values: `rounded-sm/md/lg/xl/2xl` scale from `--radius` (default 14px; cards use `xl`, buttons and inputs `md`), `shadow-card` (the one elevation), `font-sans` (Geist), `font-mono` (Geist Mono), `animate-typing`.
|