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.
Files changed (70) hide show
  1. package/dist/components/admin/app-nav.d.ts +11 -1
  2. package/dist/components/admin/app-nav.js +26 -9
  3. package/dist/components/admin/checklist-editor.d.ts +20 -0
  4. package/dist/components/admin/checklist-editor.js +69 -0
  5. package/dist/components/admin/comment-thread.d.ts +26 -0
  6. package/dist/components/admin/comment-thread.js +68 -0
  7. package/dist/components/admin/copy-field.d.ts +3 -1
  8. package/dist/components/admin/copy-field.js +10 -5
  9. package/dist/components/admin/date-time-picker.js +1 -1
  10. package/dist/components/admin/detail-sheet.js +3 -0
  11. package/dist/components/admin/markdown-editor.d.ts +2 -5
  12. package/dist/components/admin/markdown-editor.js +13 -9
  13. package/dist/components/admin/page-header.d.ts +3 -1
  14. package/dist/components/admin/page-header.js +17 -9
  15. package/dist/components/admin/page.d.ts +16 -0
  16. package/dist/components/admin/page.js +42 -0
  17. package/dist/components/admin/prompt-dialog.d.ts +19 -0
  18. package/dist/components/admin/prompt-dialog.js +83 -0
  19. package/dist/components/admin/settings.d.ts +4 -2
  20. package/dist/components/admin/settings.js +37 -30
  21. package/dist/components/admin/time-zone-picker.d.ts +12 -0
  22. package/dist/components/admin/time-zone-picker.js +31 -0
  23. package/dist/components/admin/user-menu.d.ts +3 -1
  24. package/dist/components/admin/user-menu.js +21 -10
  25. package/dist/components/ai/action-receipt.d.ts +1 -1
  26. package/dist/components/ai/action-receipt.js +1 -1
  27. package/dist/components/ai/approval.d.ts +1 -1
  28. package/dist/components/ai/approval.js +2 -2
  29. package/dist/components/ai/ask-box.js +1 -1
  30. package/dist/components/ai/assistant.d.ts +3 -1
  31. package/dist/components/ai/assistant.js +3 -2
  32. package/dist/components/ai/chat.js +1 -1
  33. package/dist/components/ai/code-block.js +3 -3
  34. package/dist/components/ai/markdown.d.ts +13 -0
  35. package/dist/components/ai/markdown.js +41 -0
  36. package/dist/components/ai/tool-steps.d.ts +2 -2
  37. package/dist/components/ai/tool-steps.js +5 -4
  38. package/dist/components/auth/auth.d.ts +41 -5
  39. package/dist/components/auth/auth.js +165 -155
  40. package/dist/components/data/day-strip.d.ts +6 -1
  41. package/dist/components/data/day-strip.js +15 -5
  42. package/dist/components/layout/collapse.js +1 -1
  43. package/dist/components/layout/text.d.ts +3 -3
  44. package/dist/components/layout/text.js +6 -1
  45. package/dist/components/ui/button.js +1 -1
  46. package/dist/components/ui/check-circle.js +1 -1
  47. package/dist/components/ui/code-block.js +2 -2
  48. package/dist/components/ui/combobox.js +1 -0
  49. package/dist/components/ui/dialog.d.ts +1 -1
  50. package/dist/components/ui/dialog.js +3 -1
  51. package/dist/components/ui/drawer.d.ts +1 -1
  52. package/dist/components/ui/drawer.js +3 -1
  53. package/dist/components/ui/input-group.js +1 -1
  54. package/dist/components/ui/input.js +1 -1
  55. package/dist/components/ui/sheet.d.ts +1 -1
  56. package/dist/components/ui/sheet.js +3 -1
  57. package/dist/components/ui/sonner.d.ts +2 -2
  58. package/dist/components/ui/sonner.js +3 -2
  59. package/dist/components/ui/toggle.js +1 -1
  60. package/dist/components/ui/trace.js +1 -1
  61. package/dist/index.d.ts +9 -3
  62. package/dist/index.js +9 -3
  63. package/dist/lib/{date.js → internal/date.js} +1 -1
  64. package/dist/lib/{toast-clearance.js → internal/toast-clearance.js} +12 -2
  65. package/dist/styles.css +25 -5
  66. package/docs/COMPONENTS.md +157 -33
  67. package/docs/ROADMAP.md +2 -1
  68. package/llms.txt +7 -6
  69. package/package.json +3 -1
  70. 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: [/* @__PURE__ */ jsxs("div", {
48
- className: "flex flex-wrap items-center gap-x-4 gap-y-2",
49
- children: [/* @__PURE__ */ jsxs("div", {
50
- className: "flex min-w-0 flex-1 basis-48 flex-col",
51
- children: [/* @__PURE__ */ jsx(Label, {
52
- htmlFor,
53
- className: "text-sm font-medium",
54
- children: label
55
- }), description && /* @__PURE__ */ jsx("span", {
56
- className: "text-[13px] text-muted-foreground",
57
- children: description
58
- })]
59
- }), control ?? (content ? /* @__PURE__ */ jsx(Button, {
60
- type: "button",
61
- variant: "outline",
62
- size: "sm",
63
- "aria-expanded": open,
64
- "aria-controls": contentId,
65
- onClick: () => setOpen(!open),
66
- children: open ? "Cancel" : expandLabel
67
- }) : null)]
68
- }), content && /* @__PURE__ */ jsx(Collapse, {
69
- open,
70
- id: contentId,
71
- children: /* @__PURE__ */ jsx("div", {
72
- className: "pt-3.5",
73
- children: content
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 { UserChip } from "./user-chip.js";
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__ */ jsxs(DropdownMenuTrigger, {
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/40 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),
39
- children: [/* @__PURE__ */ jsx(UserChip, {
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
- /** One line, e.g. "Groceries · just now". */
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: "truncate text-xs text-muted-foreground",
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
- /** One-line record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
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
- /** One-line record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
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 truncate",
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]:underline", "[&_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),
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 truncate font-medium",
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
- className: "w-18 shrink-0 truncate text-[13px] text-muted-foreground",
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 truncate font-mono text-[12.5px]",
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, loading, error, className, ...props }: LoginFormProps): React.JSX.Element;
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
- onSubmit?: (values: SignupValues) => void | Promise<void>;
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, loading, error, className, ...props }: SignupFormProps): React.JSX.Element;
85
- export { AppleIcon, AuthLayout, GitHubIcon, GoogleIcon, LoginForm, SignupForm };
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 };