harebear-ui 0.9.0 → 0.11.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/dist/components/admin/app-nav.d.ts +11 -1
- package/dist/components/admin/app-nav.js +26 -9
- package/dist/components/admin/checklist-editor.d.ts +20 -0
- package/dist/components/admin/checklist-editor.js +69 -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.js +1 -1
- package/dist/components/admin/detail-sheet.js +3 -0
- package/dist/components/admin/markdown-editor.d.ts +2 -5
- package/dist/components/admin/markdown-editor.js +13 -9
- 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-menu.d.ts +3 -1
- package/dist/components/admin/user-menu.js +21 -10
- package/dist/components/ai/action-receipt.d.ts +1 -1
- package/dist/components/ai/action-receipt.js +1 -1
- package/dist/components/ai/approval.d.ts +1 -1
- package/dist/components/ai/approval.js +2 -2
- package/dist/components/ai/ask-box.js +1 -1
- package/dist/components/ai/assistant.d.ts +3 -1
- package/dist/components/ai/assistant.js +3 -2
- package/dist/components/ai/chat.js +1 -1
- package/dist/components/ai/code-block.js +3 -3
- package/dist/components/ai/markdown.d.ts +13 -0
- package/dist/components/ai/markdown.js +41 -0
- package/dist/components/ai/tool-steps.d.ts +2 -2
- package/dist/components/ai/tool-steps.js +5 -4
- package/dist/components/auth/auth.d.ts +41 -5
- package/dist/components/auth/auth.js +165 -155
- package/dist/components/data/day-strip.d.ts +6 -1
- package/dist/components/data/day-strip.js +15 -5
- 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/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/dialog.d.ts +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/drawer.d.ts +1 -1
- package/dist/components/ui/drawer.js +3 -1
- package/dist/components/ui/input-group.js +1 -1
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/sheet.d.ts +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sonner.d.ts +2 -2
- package/dist/components/ui/sonner.js +3 -2
- package/dist/components/ui/toggle.js +1 -1
- package/dist/components/ui/trace.js +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +9 -3
- package/dist/lib/{date.js → internal/date.js} +1 -1
- package/dist/lib/{toast-clearance.js → internal/toast-clearance.js} +12 -2
- package/dist/styles.css +25 -5
- package/docs/COMPONENTS.md +157 -33
- package/docs/ROADMAP.md +2 -1
- package/llms.txt +7 -6
- package/package.json +3 -1
- package/skills/harebear-ui/SKILL.md +5 -4
|
@@ -15,6 +15,8 @@ export interface SettingsRowProps extends Omit<React.ComponentProps<"div">, "con
|
|
|
15
15
|
htmlFor?: string;
|
|
16
16
|
/** The control at the right: Switch, Select, ToggleGroup, Button. */
|
|
17
17
|
control?: React.ReactNode;
|
|
18
|
+
/** Always shown under the label, full width, with no toggle (a list of connections, a hint, a short form). */
|
|
19
|
+
body?: React.ReactNode;
|
|
18
20
|
/**
|
|
19
21
|
* Inline content that expands under the row (change password, add a connection). Without a `control`, the row
|
|
20
22
|
* gets a toggle button (`expandLabel` / "Cancel").
|
|
@@ -26,6 +28,6 @@ export interface SettingsRowProps extends Omit<React.ComponentProps<"div">, "con
|
|
|
26
28
|
/** Label of the default toggle button. Default "Change". */
|
|
27
29
|
expandLabel?: React.ReactNode;
|
|
28
30
|
}
|
|
29
|
-
/** One setting: label and description at the left, a control at the right, optional expandable content. */
|
|
30
|
-
declare function SettingsRow({ label, description, htmlFor, control, content, open: openProp, defaultOpen, onOpenChange, expandLabel, className, ...props }: SettingsRowProps): React.JSX.Element;
|
|
31
|
+
/** One setting: label and description at the left, a control at the right, optional body and expandable content. */
|
|
32
|
+
declare function SettingsRow({ label, description, htmlFor, control, body, content, open: openProp, defaultOpen, onOpenChange, expandLabel, className, ...props }: SettingsRowProps): React.JSX.Element;
|
|
31
33
|
export { SettingsRow, SettingsSection };
|
|
@@ -29,8 +29,8 @@ function SettingsSection({ title, description, className, children, ...props })
|
|
|
29
29
|
})]
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
/** One setting: label and description at the left, a control at the right, optional expandable content. */
|
|
33
|
-
function SettingsRow({ label, description, htmlFor, control, content, open: openProp, defaultOpen = false, onOpenChange, expandLabel = "Change", className, ...props }) {
|
|
32
|
+
/** One setting: label and description at the left, a control at the right, optional body and expandable content. */
|
|
33
|
+
function SettingsRow({ label, description, htmlFor, control, body, content, open: openProp, defaultOpen = false, onOpenChange, expandLabel = "Change", className, ...props }) {
|
|
34
34
|
const [openState, setOpenState] = React.useState(defaultOpen);
|
|
35
35
|
const open = openProp ?? openState;
|
|
36
36
|
const setOpen = (next) => {
|
|
@@ -44,35 +44,42 @@ function SettingsRow({ label, description, htmlFor, control, content, open: open
|
|
|
44
44
|
"data-state": content ? open ? "open" : "closed" : void 0,
|
|
45
45
|
className: cn("py-3.5", className),
|
|
46
46
|
...props,
|
|
47
|
-
children: [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: "flex flex-wrap items-center gap-x-4 gap-y-2",
|
|
50
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
51
|
+
className: "flex min-w-0 flex-1 basis-48 flex-col",
|
|
52
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
53
|
+
htmlFor,
|
|
54
|
+
className: "text-sm font-medium",
|
|
55
|
+
children: label
|
|
56
|
+
}), description && /* @__PURE__ */ jsx("span", {
|
|
57
|
+
className: "text-[13px] text-muted-foreground",
|
|
58
|
+
children: description
|
|
59
|
+
})]
|
|
60
|
+
}), control ?? (content ? /* @__PURE__ */ jsx(Button, {
|
|
61
|
+
type: "button",
|
|
62
|
+
variant: "outline",
|
|
63
|
+
size: "sm",
|
|
64
|
+
"aria-expanded": open,
|
|
65
|
+
"aria-controls": contentId,
|
|
66
|
+
onClick: () => setOpen(!open),
|
|
67
|
+
children: open ? "Cancel" : expandLabel
|
|
68
|
+
}) : null)]
|
|
69
|
+
}),
|
|
70
|
+
body && /* @__PURE__ */ jsx("div", {
|
|
71
|
+
className: "pt-3",
|
|
72
|
+
children: body
|
|
73
|
+
}),
|
|
74
|
+
content && /* @__PURE__ */ jsx(Collapse, {
|
|
75
|
+
open,
|
|
76
|
+
id: contentId,
|
|
77
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
78
|
+
className: "pt-3.5",
|
|
79
|
+
children: content
|
|
80
|
+
})
|
|
74
81
|
})
|
|
75
|
-
|
|
82
|
+
]
|
|
76
83
|
});
|
|
77
84
|
}
|
|
78
85
|
export { SettingsRow, SettingsSection };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface TimeZonePickerProps {
|
|
3
|
+
/** IANA name, e.g. "America/Vancouver". */
|
|
4
|
+
value: string;
|
|
5
|
+
onValueChange: (value: string) => void;
|
|
6
|
+
/** id of the search input, for a FieldLabel or SettingsRow `htmlFor`. */
|
|
7
|
+
id?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Searchable IANA time zone list (Intl.supportedValuesOf("timeZone")), built on Combobox. */
|
|
11
|
+
declare function TimeZonePicker({ value, onValueChange, id, className }: TimeZonePickerProps): React.JSX.Element;
|
|
12
|
+
export { TimeZonePicker };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "../ui/combobox.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
// src/components/admin/time-zone-picker.tsx
|
|
7
|
+
const label = (zone) => zone.replaceAll("_", " ");
|
|
8
|
+
/** Searchable IANA time zone list (Intl.supportedValuesOf("timeZone")), built on Combobox. */
|
|
9
|
+
function TimeZonePicker({ value, onValueChange, id, className }) {
|
|
10
|
+
const zones = React.useMemo(() => {
|
|
11
|
+
const all = Intl.supportedValuesOf("timeZone");
|
|
12
|
+
return value && !all.includes(value) ? [value, ...all] : all;
|
|
13
|
+
}, [value]);
|
|
14
|
+
return /* @__PURE__ */ jsxs(Combobox, {
|
|
15
|
+
items: zones,
|
|
16
|
+
value,
|
|
17
|
+
onValueChange: (next) => next && onValueChange(next),
|
|
18
|
+
itemToStringLabel: label,
|
|
19
|
+
children: [/* @__PURE__ */ jsx(ComboboxInput, {
|
|
20
|
+
id,
|
|
21
|
+
placeholder: "Search time zones",
|
|
22
|
+
showTrigger: false,
|
|
23
|
+
className: cn("w-full", className)
|
|
24
|
+
}), /* @__PURE__ */ jsxs(ComboboxContent, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: "No matching time zone." }), /* @__PURE__ */ jsx(ComboboxList, { children: (zone) => /* @__PURE__ */ jsx(ComboboxItem, {
|
|
25
|
+
value: zone,
|
|
26
|
+
className: "pointer-coarse:min-h-11",
|
|
27
|
+
children: label(zone)
|
|
28
|
+
}, zone) })] })]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export { TimeZonePicker };
|
|
@@ -15,11 +15,13 @@ export interface UserMenuProps {
|
|
|
15
15
|
onSignOut?: () => void;
|
|
16
16
|
/** Default "Sign out". */
|
|
17
17
|
signOutLabel?: React.ReactNode;
|
|
18
|
+
/** Trigger is just the avatar (44px on touch screens) and the menu opens below it, e.g. in a phone page header. */
|
|
19
|
+
compact?: boolean;
|
|
18
20
|
className?: string;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* The signed-in user as a menu button, for a sidebar footer or top bar: theme choice, app items such as
|
|
22
24
|
* Settings, and sign out. In a sidebar icon rail the trigger is just the avatar and the menu opens to the side.
|
|
23
25
|
*/
|
|
24
|
-
declare function UserMenu({ name, subtitle, avatarUrl, color, children, themeSwitch, onSignOut, signOutLabel, className }: UserMenuProps): React.JSX.Element;
|
|
26
|
+
declare function UserMenu({ name, subtitle, avatarUrl, color, children, themeSwitch, onSignOut, signOutLabel, compact, className }: UserMenuProps): React.JSX.Element;
|
|
25
27
|
export { UserMenu };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { categoryBg } from "./category.js";
|
|
3
|
+
import { UserChip, initials } from "./user-chip.js";
|
|
3
4
|
import { useTheme } from "../theme/theme-provider.js";
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar.js";
|
|
4
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
|
|
5
7
|
import { useSidebarOptional } from "../ui/sidebar.js";
|
|
6
8
|
import { cn } from "../../lib/utils.js";
|
|
@@ -28,26 +30,35 @@ const themes = [
|
|
|
28
30
|
* The signed-in user as a menu button, for a sidebar footer or top bar: theme choice, app items such as
|
|
29
31
|
* Settings, and sign out. In a sidebar icon rail the trigger is just the avatar and the menu opens to the side.
|
|
30
32
|
*/
|
|
31
|
-
function UserMenu({ name, subtitle, avatarUrl, color, children, themeSwitch = true, onSignOut, signOutLabel = "Sign out", className }) {
|
|
33
|
+
function UserMenu({ name, subtitle, avatarUrl, color, children, themeSwitch = true, onSignOut, signOutLabel = "Sign out", compact = false, className }) {
|
|
32
34
|
const { mode, setMode } = useTheme();
|
|
33
35
|
const sidebar = useSidebarOptional();
|
|
34
36
|
const rail = sidebar?.state === "collapsed" && !sidebar.isMobile;
|
|
35
|
-
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */
|
|
37
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
36
38
|
"data-slot": "user-menu-trigger",
|
|
37
|
-
"aria-label": `Account menu for ${name}
|
|
38
|
-
className: cn("flex w-full min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring
|
|
39
|
-
children:
|
|
39
|
+
"aria-label": compact ? `Account menu for ${name}` : rail ? name : void 0,
|
|
40
|
+
className: cn(compact ? "grid size-10 shrink-0 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background pointer-coarse:size-11" : "flex w-full min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-muted group-data-[collapsible=icon]:mx-auto group-data-[collapsible=icon]:size-10 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:rounded-full group-data-[collapsible=icon]:p-0", className),
|
|
41
|
+
children: compact || rail ? /* @__PURE__ */ jsxs(Avatar, {
|
|
42
|
+
"aria-hidden": "true",
|
|
43
|
+
children: [avatarUrl && /* @__PURE__ */ jsx(AvatarImage, {
|
|
44
|
+
src: avatarUrl,
|
|
45
|
+
alt: ""
|
|
46
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
47
|
+
"data-initials": initials(name),
|
|
48
|
+
className: cn("after:content-[attr(data-initials)]", color && [categoryBg[color], "text-category-foreground"])
|
|
49
|
+
})]
|
|
50
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(UserChip, {
|
|
40
51
|
name,
|
|
41
52
|
subtitle,
|
|
42
53
|
avatarUrl,
|
|
43
54
|
color,
|
|
44
55
|
className: "flex-1"
|
|
45
|
-
}), /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "size-4 shrink-0 text-muted-foreground group-data-[collapsible=icon]:hidden" })]
|
|
56
|
+
}), /* @__PURE__ */ jsx(ChevronsUpDownIcon, { className: "size-4 shrink-0 text-muted-foreground group-data-[collapsible=icon]:hidden" })] })
|
|
46
57
|
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
47
58
|
"data-slot": "user-menu",
|
|
48
|
-
side: rail ? "right" : "top",
|
|
49
|
-
align: rail ? "end" : "start",
|
|
50
|
-
className: "w-(--radix-dropdown-menu-trigger-width) min-w-60",
|
|
59
|
+
side: compact ? "bottom" : rail ? "right" : "top",
|
|
60
|
+
align: compact || rail ? "end" : "start",
|
|
61
|
+
className: compact ? "min-w-60" : "w-(--radix-dropdown-menu-trigger-width) min-w-60",
|
|
51
62
|
children: [
|
|
52
63
|
/* @__PURE__ */ jsxs(DropdownMenuLabel, {
|
|
53
64
|
className: "flex min-w-0 flex-col font-normal",
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
export interface ActionReceiptProps extends Omit<React.ComponentProps<"div">, "title"> {
|
|
3
3
|
/** What was done, past tense: "Added milk to Groceries". */
|
|
4
4
|
title: React.ReactNode;
|
|
5
|
-
/**
|
|
5
|
+
/** Short line under the title, e.g. "Groceries · just now". Wraps when long. */
|
|
6
6
|
meta?: React.ReactNode;
|
|
7
7
|
/** Shows an Undo button. */
|
|
8
8
|
onUndo?: () => void;
|
|
@@ -26,7 +26,7 @@ function ActionReceipt({ title, meta, onUndo, undoLabel = "Undo", undone, classN
|
|
|
26
26
|
className: cn("text-sm font-medium wrap-anywhere", undone && "text-muted-foreground line-through"),
|
|
27
27
|
children: title
|
|
28
28
|
}), meta && /* @__PURE__ */ jsx("span", {
|
|
29
|
-
className: "
|
|
29
|
+
className: "text-xs text-muted-foreground wrap-anywhere",
|
|
30
30
|
children: meta
|
|
31
31
|
})]
|
|
32
32
|
}),
|
|
@@ -27,6 +27,6 @@ export interface ApprovalResultProps extends React.ComponentProps<"div"> {
|
|
|
27
27
|
/** Trailing time, e.g. "10:42". */
|
|
28
28
|
time?: React.ReactNode;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
30
|
+
/** Short record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
|
|
31
31
|
declare function ApprovalResult({ decision, time, className, children, ...props }: ApprovalResultProps): React.JSX.Element;
|
|
32
32
|
export { ApprovalCard, ApprovalResult };
|
|
@@ -56,7 +56,7 @@ function ApprovalCard({ title, description, icon = /* @__PURE__ */ jsx(ShieldAle
|
|
|
56
56
|
]
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
/**
|
|
59
|
+
/** Short record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
|
|
60
60
|
function ApprovalResult({ decision, time, className, children, ...props }) {
|
|
61
61
|
const denied = decision === "denied";
|
|
62
62
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -71,7 +71,7 @@ function ApprovalResult({ decision, time, className, children, ...props }) {
|
|
|
71
71
|
children: denied ? /* @__PURE__ */ jsx(XIcon, {}) : /* @__PURE__ */ jsx(CheckIcon, {})
|
|
72
72
|
}),
|
|
73
73
|
/* @__PURE__ */ jsx("span", {
|
|
74
|
-
className: "min-w-0 flex-1
|
|
74
|
+
className: "min-w-0 flex-1 wrap-anywhere",
|
|
75
75
|
children
|
|
76
76
|
}),
|
|
77
77
|
time && /* @__PURE__ */ jsx("span", {
|
|
@@ -91,7 +91,7 @@ function AskBox({ value, onValueChange, onSubmit, placeholder = "Ask anything",
|
|
|
91
91
|
"data-slot": "ask-box-reply",
|
|
92
92
|
"aria-live": "polite",
|
|
93
93
|
"aria-busy": pending || void 0,
|
|
94
|
-
className: "flex min-w-0 flex-col gap-3 px-2 text-[15px] leading-relaxed",
|
|
94
|
+
className: "flex min-w-0 flex-col gap-3 px-2 text-[15px] leading-relaxed wrap-anywhere",
|
|
95
95
|
children: [hasReply ? reply : /* @__PURE__ */ jsx(TypingIndicator, { label: "Working" }), chatHref && /* @__PURE__ */ jsx(Button, {
|
|
96
96
|
asChild: true,
|
|
97
97
|
variant: "link",
|
|
@@ -80,6 +80,8 @@ export interface AssistantDockProps extends Omit<React.ComponentProps<"section">
|
|
|
80
80
|
icon?: React.ReactNode;
|
|
81
81
|
/** Link to the full-screen chat. Shows an expand button in the header. */
|
|
82
82
|
fullScreenHref?: string;
|
|
83
|
+
/** Header controls before the full-screen link and close button, e.g. a "New chat" icon button. */
|
|
84
|
+
actions?: React.ReactNode;
|
|
83
85
|
/** Empty state heading. Default "How can I help?". */
|
|
84
86
|
emptyTitle?: React.ReactNode;
|
|
85
87
|
/** Empty state line under the heading. */
|
|
@@ -100,5 +102,5 @@ export interface AssistantDockProps extends Omit<React.ComponentProps<"section">
|
|
|
100
102
|
* suggestions, message list, AskBox. A soft launcher button opens it. Escape closes it. Needs AssistantProvider.
|
|
101
103
|
* Sits above AppNav's phone tab bar.
|
|
102
104
|
*/
|
|
103
|
-
declare function AssistantDock({ title, icon, fullScreenHref, emptyTitle, emptyDescription, suggestions, placeholder, dictation, launcher, launcherLabel, className, ...props }: AssistantDockProps): React.JSX.Element | null;
|
|
105
|
+
declare function AssistantDock({ title, icon, fullScreenHref, actions, emptyTitle, emptyDescription, suggestions, placeholder, dictation, launcher, launcherLabel, className, ...props }: AssistantDockProps): React.JSX.Element | null;
|
|
104
106
|
export { AssistantDock, AssistantProvider };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AskBox } from "./ask-box.js";
|
|
3
3
|
import { AssistantAvatar, ChatMessage, ChatThread, TypingIndicator } from "./chat.js";
|
|
4
4
|
import { Button } from "../ui/button.js";
|
|
5
|
-
import { useToastClearance } from "../../lib/toast-clearance.js";
|
|
5
|
+
import { useToastClearance } from "../../lib/internal/toast-clearance.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -144,7 +144,7 @@ function useAssistantPage(page) {
|
|
|
144
144
|
* suggestions, message list, AskBox. A soft launcher button opens it. Escape closes it. Needs AssistantProvider.
|
|
145
145
|
* Sits above AppNav's phone tab bar.
|
|
146
146
|
*/
|
|
147
|
-
function AssistantDock({ title = "Assistant", icon = /* @__PURE__ */ jsx(SparklesIcon, {}), fullScreenHref, emptyTitle = "How can I help?", emptyDescription, suggestions, placeholder, dictation, launcher = true, launcherLabel, className, ...props }) {
|
|
147
|
+
function AssistantDock({ title = "Assistant", icon = /* @__PURE__ */ jsx(SparklesIcon, {}), fullScreenHref, actions, emptyTitle = "How can I help?", emptyDescription, suggestions, placeholder, dictation, launcher = true, launcherLabel, className, ...props }) {
|
|
148
148
|
const { open, setOpen, messages, pending, page, send, stop } = useAssistant();
|
|
149
149
|
const [draft, setDraft] = React.useState("");
|
|
150
150
|
const titleId = React.useId();
|
|
@@ -200,6 +200,7 @@ function AssistantDock({ title = "Assistant", icon = /* @__PURE__ */ jsx(Sparkle
|
|
|
200
200
|
className: "min-w-0 flex-1 truncate text-[15px] font-semibold",
|
|
201
201
|
children: title
|
|
202
202
|
}),
|
|
203
|
+
actions,
|
|
203
204
|
fullScreenHref && /* @__PURE__ */ jsx(Button, {
|
|
204
205
|
asChild: true,
|
|
205
206
|
variant: "ghost",
|
|
@@ -59,7 +59,7 @@ function ChatMessage({ role, avatar, actions, variant = "muted", className, chil
|
|
|
59
59
|
className: cn("flex items-start gap-4", className),
|
|
60
60
|
...props,
|
|
61
61
|
children: [avatar === false ? null : avatar ?? /* @__PURE__ */ jsx(AssistantAvatar, {}), /* @__PURE__ */ jsxs("div", {
|
|
62
|
-
className: "flex min-w-0 flex-1 flex-col gap-3.5",
|
|
62
|
+
className: "flex min-w-0 flex-1 flex-col gap-3.5 wrap-anywhere",
|
|
63
63
|
children: [children, actions]
|
|
64
64
|
})]
|
|
65
65
|
});
|
|
@@ -30,9 +30,9 @@ function CodeSnippet({ code, language, highlight = true, lineNumbers, hideCopy,
|
|
|
30
30
|
type: "button",
|
|
31
31
|
variant: "ghost",
|
|
32
32
|
size: "sm",
|
|
33
|
-
className: "ms-auto",
|
|
33
|
+
className: "ms-auto pointer-coarse:h-11",
|
|
34
34
|
onClick: () => copy(code),
|
|
35
|
-
"aria-label": "Copy code",
|
|
35
|
+
"aria-label": copied ? void 0 : "Copy code",
|
|
36
36
|
children: [copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {}), copied ? "Copied" : "Copy"]
|
|
37
37
|
})]
|
|
38
38
|
}), /* @__PURE__ */ jsx(CodeBlockContent, {
|
|
@@ -106,7 +106,7 @@ function withTips(children) {
|
|
|
106
106
|
function Prose({ className, children, ...props }) {
|
|
107
107
|
return /* @__PURE__ */ jsx("div", {
|
|
108
108
|
"data-slot": "prose",
|
|
109
|
-
className: cn("flex min-w-0 flex-col gap-3.5 text-[15px] leading-[1.65] wrap-anywhere text-foreground", "[&_ol]:flex [&_ol]:list-decimal [&_ol]:flex-col [&_ol]:gap-2 [&_ol]:pl-5.5", "[&_ul]:flex [&_ul]:list-disc [&_ul]:flex-col [&_ul]:gap-2 [&_ul]:pl-5.5", "[&_strong]:font-semibold [&_a]:text-primary [&_a]:underline-offset-4 hover:[&_a]:
|
|
109
|
+
className: cn("flex min-w-0 flex-col gap-3.5 text-[15px] leading-[1.65] wrap-anywhere text-foreground", "[&_ol]:flex [&_ol]:list-decimal [&_ol]:flex-col [&_ol]:gap-2 [&_ol]:pl-5.5", "[&_ul]:flex [&_ul]:list-disc [&_ul]:flex-col [&_ul]:gap-2 [&_ul]:pl-5.5", "[&_strong]:font-semibold [&_a]:text-primary [&_a]:underline [&_a]:decoration-primary/40 [&_a]:underline-offset-4 hover:[&_a]:decoration-primary [&_a]:rounded-sm [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-offset-2 [&_a:focus-visible]:outline-ring", "[&_h1]:text-xl [&_h1]:font-semibold [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:font-semibold [&_h4]:font-semibold", "[&_blockquote]:border-l-2 [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground", "[&_:not(pre)>code]:rounded-[6px] [&_:not(pre)>code]:bg-muted [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[12.5px]", "[&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-muted [&_pre]:p-4.5 [&_pre]:font-mono [&_pre]:text-[13px]", "[&_table]:w-full [&_table]:text-sm [&_td]:border-b [&_td]:py-2 [&_th]:border-b [&_th]:py-2 [&_th]:text-left [&_th]:font-medium", className),
|
|
110
110
|
...props,
|
|
111
111
|
children: withTips(children)
|
|
112
112
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface MarkdownProps extends Omit<React.ComponentProps<"div">, "children"> {
|
|
3
|
+
/** The Markdown source. GitHub flavoured (tables, task lists, strikethrough, autolinks). Raw HTML is dropped. */
|
|
4
|
+
children: string;
|
|
5
|
+
/** Component for in-app links (href without a scheme, e.g. "/lists/2"), e.g. Next.js or React Router Link. Gets `href`. */
|
|
6
|
+
linkComponent?: React.ElementType;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Renders Markdown safely inside Prose: no raw HTML, unsafe URLs (javascript:) removed, `> Tip:` blockquotes as
|
|
10
|
+
* a Callout. Other props go to the Prose wrapper.
|
|
11
|
+
*/
|
|
12
|
+
declare function Markdown({ children, linkComponent: Link, ...props }: MarkdownProps): React.JSX.Element;
|
|
13
|
+
export { Markdown };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Prose, ProseBlockquote } from "./code-block.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import ReactMarkdown from "react-markdown";
|
|
6
|
+
import remarkGfm from "remark-gfm";
|
|
7
|
+
// src/components/ai/markdown.tsx
|
|
8
|
+
const plugins = [remarkGfm];
|
|
9
|
+
const SCHEME = /^([a-z][a-z\d+.-]*:|\/\/)/i;
|
|
10
|
+
/**
|
|
11
|
+
* Renders Markdown safely inside Prose: no raw HTML, unsafe URLs (javascript:) removed, `> Tip:` blockquotes as
|
|
12
|
+
* a Callout. Other props go to the Prose wrapper.
|
|
13
|
+
*/
|
|
14
|
+
function Markdown({ children, linkComponent: Link, ...props }) {
|
|
15
|
+
const components = React.useMemo(() => ({
|
|
16
|
+
blockquote: ProseBlockquote,
|
|
17
|
+
a: ({ node: _node, href = "", ...rest }) => Link && !SCHEME.test(href) ? /* @__PURE__ */ jsx(Link, {
|
|
18
|
+
href,
|
|
19
|
+
...rest
|
|
20
|
+
}) : /* @__PURE__ */ jsx("a", {
|
|
21
|
+
href,
|
|
22
|
+
...rest
|
|
23
|
+
}),
|
|
24
|
+
input: ({ node: _node, checked, ...rest }) => /* @__PURE__ */ jsx("input", {
|
|
25
|
+
...rest,
|
|
26
|
+
checked,
|
|
27
|
+
"aria-label": checked ? "Done" : "Not done"
|
|
28
|
+
})
|
|
29
|
+
}), [Link]);
|
|
30
|
+
return /* @__PURE__ */ jsx(Prose, {
|
|
31
|
+
"data-slot": "markdown",
|
|
32
|
+
...props,
|
|
33
|
+
children: /* @__PURE__ */ jsx(ReactMarkdown, {
|
|
34
|
+
remarkPlugins: plugins,
|
|
35
|
+
components,
|
|
36
|
+
skipHtml: true,
|
|
37
|
+
children
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export { Markdown };
|
|
@@ -24,9 +24,9 @@ declare function ToolGroup({ title, status, meta, autoOpen, open: openProp, onOp
|
|
|
24
24
|
export interface ToolStepProps extends Omit<React.ComponentProps<"div">, "title"> {
|
|
25
25
|
/** Tool icon, e.g. <TerminalIcon />. */
|
|
26
26
|
icon?: React.ReactNode;
|
|
27
|
-
/** Short verb column, e.g. "Run", "Read", "Edit". */
|
|
27
|
+
/** Short verb column, e.g. "Run", "Read", "Edit". With an empty `detail` it is the whole label, e.g. "Added milk". */
|
|
28
28
|
kind: React.ReactNode;
|
|
29
|
-
/** What it acted on, shown monospace: the command or path. */
|
|
29
|
+
/** What it acted on, shown monospace: the command or path. Empty for a plain-language step. */
|
|
30
30
|
detail: string;
|
|
31
31
|
status: ToolStatus;
|
|
32
32
|
/** Trailing text. Default derived from status ("running", "failed"…). */
|
|
@@ -52,7 +52,7 @@ function ToolGroup({ title, status, meta, autoOpen, open: openProp, onOpenChange
|
|
|
52
52
|
children: [
|
|
53
53
|
/* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-4 shrink-0 text-muted-foreground transition-transform", open && "rotate-90") }),
|
|
54
54
|
/* @__PURE__ */ jsx("span", {
|
|
55
|
-
className: "min-w-0 flex-1
|
|
55
|
+
className: "min-w-0 flex-1 font-medium wrap-anywhere",
|
|
56
56
|
children: title
|
|
57
57
|
}),
|
|
58
58
|
meta !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
@@ -85,11 +85,12 @@ function ToolStep({ icon, kind, detail, status, meta, output, autoOpen, classNam
|
|
|
85
85
|
children: icon
|
|
86
86
|
}),
|
|
87
87
|
/* @__PURE__ */ jsx("span", {
|
|
88
|
-
|
|
88
|
+
"data-slot": "tool-step-kind",
|
|
89
|
+
className: cn("text-[13px] text-muted-foreground wrap-anywhere", detail ? "w-18 shrink-0" : "min-w-0 flex-1 text-foreground"),
|
|
89
90
|
children: kind
|
|
90
91
|
}),
|
|
91
|
-
/* @__PURE__ */ jsx("span", {
|
|
92
|
-
className: "min-w-0 flex-1
|
|
92
|
+
detail && /* @__PURE__ */ jsx("span", {
|
|
93
|
+
className: "min-w-0 flex-1 font-mono text-[12.5px] wrap-anywhere",
|
|
93
94
|
children: detail
|
|
94
95
|
}),
|
|
95
96
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -33,6 +33,23 @@ declare function GitHubIcon(props: React.ComponentProps<"svg">): React.JSX.Eleme
|
|
|
33
33
|
declare function GoogleIcon(props: React.ComponentProps<"svg">): React.JSX.Element;
|
|
34
34
|
/** Apple mark, for AuthProvider icons. */
|
|
35
35
|
declare function AppleIcon(props: React.ComponentProps<"svg">): React.JSX.Element;
|
|
36
|
+
export interface AuthFormProps extends Omit<React.ComponentProps<"form">, "onSubmit" | "title"> {
|
|
37
|
+
title: React.ReactNode;
|
|
38
|
+
description?: React.ReactNode;
|
|
39
|
+
/** The fields. Give each a `name`; they arrive in onSubmit's FormData. */
|
|
40
|
+
children?: React.ReactNode;
|
|
41
|
+
/** Form-level error, e.g. "Invalid email or password". */
|
|
42
|
+
error?: React.ReactNode;
|
|
43
|
+
submitLabel: React.ReactNode;
|
|
44
|
+
/** Shows a spinner on the submit button and disables every control. */
|
|
45
|
+
loading?: boolean;
|
|
46
|
+
/** Called with the form's data once the browser's validation passes. Omit to submit natively (e.g. to an `action`). */
|
|
47
|
+
onSubmit?: (data: FormData) => void | Promise<void>;
|
|
48
|
+
/** Under the submit button: provider buttons, a "Sign in" link. */
|
|
49
|
+
footer?: React.ReactNode;
|
|
50
|
+
}
|
|
51
|
+
/** The shell LoginForm, SignupForm and NewPasswordForm share: heading, your fields, error, submit button. */
|
|
52
|
+
declare function AuthForm({ title, description, children, error, submitLabel, loading, onSubmit, footer, className, ...props }: AuthFormProps): React.JSX.Element;
|
|
36
53
|
interface AuthFormBase extends Omit<React.ComponentProps<"form">, "onSubmit" | "title"> {
|
|
37
54
|
title?: React.ReactNode;
|
|
38
55
|
description?: React.ReactNode;
|
|
@@ -51,7 +68,7 @@ export interface LoginValues {
|
|
|
51
68
|
/** Absent in "email-only" mode. */
|
|
52
69
|
password?: string;
|
|
53
70
|
}
|
|
54
|
-
export interface LoginFormProps extends AuthFormBase {
|
|
71
|
+
export interface LoginFormProps extends Omit<AuthFormBase, "children"> {
|
|
55
72
|
/** Called with the entered values. Omit to let the form submit natively (e.g. to an `action`). */
|
|
56
73
|
onSubmit?: (values: LoginValues) => void | Promise<void>;
|
|
57
74
|
/** "email-only" drops the password field for magic-link or OTP flows (shadcn login-05). */
|
|
@@ -61,14 +78,17 @@ export interface LoginFormProps extends AuthFormBase {
|
|
|
61
78
|
submitLabel?: React.ReactNode;
|
|
62
79
|
}
|
|
63
80
|
/** Email + password sign-in form with optional SSO buttons. Pair with AuthLayout. */
|
|
64
|
-
declare function LoginForm({ title, description, onSubmit, mode, providers, onProviderClick, providersFirst, forgotPasswordHref, signupHref, submitLabel,
|
|
81
|
+
declare function LoginForm({ title, description, onSubmit, mode, providers, onProviderClick, providersFirst, forgotPasswordHref, signupHref, submitLabel, ...props }: LoginFormProps): React.JSX.Element;
|
|
65
82
|
export interface SignupValues {
|
|
66
83
|
name?: string;
|
|
67
84
|
email: string;
|
|
68
85
|
password: string;
|
|
69
86
|
}
|
|
70
87
|
export interface SignupFormProps extends AuthFormBase {
|
|
71
|
-
|
|
88
|
+
/** `data` holds every field, including your own `children` fields. */
|
|
89
|
+
onSubmit?: (values: SignupValues, data: FormData) => void | Promise<void>;
|
|
90
|
+
/** Extra fields shown before the name field (e.g. a household name). Give each a `name`; read it from `data`. */
|
|
91
|
+
children?: React.ReactNode;
|
|
72
92
|
/** Ask for a full name. Default true. */
|
|
73
93
|
nameField?: boolean;
|
|
74
94
|
/** Ask for the password twice; mismatches are reported before onSubmit. Default true. */
|
|
@@ -81,5 +101,21 @@ export interface SignupFormProps extends AuthFormBase {
|
|
|
81
101
|
submitLabel?: React.ReactNode;
|
|
82
102
|
}
|
|
83
103
|
/** Account creation form: name, email, password (+ confirm), optional SSO. Pair with AuthLayout. */
|
|
84
|
-
declare function SignupForm({ title, description, onSubmit, nameField, confirmPassword, passwordHint, minPasswordLength, providers, onProviderClick, providersFirst, loginHref, submitLabel,
|
|
85
|
-
export
|
|
104
|
+
declare function SignupForm({ title, description, onSubmit, children, nameField, confirmPassword, passwordHint, minPasswordLength, providers, onProviderClick, providersFirst, loginHref, submitLabel, ...props }: SignupFormProps): React.JSX.Element;
|
|
105
|
+
export interface NewPasswordFormProps extends Omit<React.ComponentProps<"form">, "onSubmit" | "title" | "children">, Pick<AuthFormProps, "description" | "error" | "loading"> {
|
|
106
|
+
/** Default "Choose a new password". */
|
|
107
|
+
title?: React.ReactNode;
|
|
108
|
+
/** Called once both fields match. */
|
|
109
|
+
onSubmit?: (values: {
|
|
110
|
+
password: string;
|
|
111
|
+
}) => void | Promise<void>;
|
|
112
|
+
/** Minimum length enforced by the browser. Default 8. */
|
|
113
|
+
minPasswordLength?: number;
|
|
114
|
+
/** Hint under the password field. Default "At least N characters." */
|
|
115
|
+
passwordHint?: React.ReactNode;
|
|
116
|
+
/** Default "Save password". */
|
|
117
|
+
submitLabel?: React.ReactNode;
|
|
118
|
+
}
|
|
119
|
+
/** Password reset or first password: new password + confirm. Pair with AuthLayout. */
|
|
120
|
+
declare function NewPasswordForm({ title, onSubmit, minPasswordLength, passwordHint, submitLabel, ...props }: NewPasswordFormProps): React.JSX.Element;
|
|
121
|
+
export { AppleIcon, AuthForm, AuthLayout, GitHubIcon, GoogleIcon, LoginForm, NewPasswordForm, SignupForm };
|