harebear-ui 0.10.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.
@@ -3,6 +3,7 @@ import { Button } from "../ui/button.js";
3
3
  import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerTitle } from "../ui/drawer.js";
4
4
  import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../ui/sheet.js";
5
5
  import { useMediaQuery } from "../../hooks/use-media-query.js";
6
+ import { useToastClearance } from "../../lib/internal/toast-clearance.js";
6
7
  import { cn } from "../../lib/utils.js";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
8
9
  import { XIcon } from "lucide-react";
@@ -13,6 +14,8 @@ import { XIcon } from "lucide-react";
13
14
  */
14
15
  function DetailSheet({ open, onOpenChange, title, description, footer, className, children }) {
15
16
  const desktop = useMediaQuery("(min-width: 768px)");
17
+ useToastClearance("--hb-toast-sheet", "calc(min(420px, 100vw - 32px) + 1rem)", open && desktop);
18
+ useToastClearance("--hb-toast-drawer", footer ? "calc(3.5625rem + max(1rem, env(safe-area-inset-bottom)))" : "env(safe-area-inset-bottom, 0px)", open && !desktop);
16
19
  const noDescription = description ? {} : { "aria-describedby": void 0 };
17
20
  const body = (padBottom) => /* @__PURE__ */ jsx("div", {
18
21
  className: cn("min-h-0 flex-1 overflow-y-auto px-6 pb-6", !footer && padBottom),
@@ -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 };
@@ -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
  });
@@ -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", {
@@ -5,7 +5,7 @@ declare function DialogTrigger({ ...props }: React.ComponentProps<typeof Dialog$
5
5
  declare function DialogPortal({ ...props }: React.ComponentProps<typeof Dialog$1.Portal>): React.JSX.Element;
6
6
  declare function DialogClose({ ...props }: React.ComponentProps<typeof Dialog$1.Close>): React.JSX.Element;
7
7
  declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof Dialog$1.Overlay>): React.JSX.Element;
8
- declare function DialogContent({ className, children, showCloseButton, ...props }: React.ComponentProps<typeof Dialog$1.Content> & {
8
+ declare function DialogContent({ className, children, showCloseButton, onInteractOutside, ...props }: React.ComponentProps<typeof Dialog$1.Content> & {
9
9
  showCloseButton?: boolean;
10
10
  }): React.JSX.Element;
11
11
  declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { keepOpenOnToast } from "../../lib/internal/toast-clearance.js";
2
3
  import { cn } from "../../lib/utils.js";
3
4
  import { Button } from "./button.js";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -36,11 +37,12 @@ function DialogOverlay({ className, ...props }) {
36
37
  ...props
37
38
  });
38
39
  }
39
- function DialogContent({ className, children, showCloseButton = true, ...props }) {
40
+ function DialogContent({ className, children, showCloseButton = true, onInteractOutside, ...props }) {
40
41
  return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Content, {
41
42
  "data-slot": "dialog-content",
42
43
  className: cn("fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
43
44
  ...props,
45
+ onInteractOutside: keepOpenOnToast(onInteractOutside),
44
46
  children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog$1.Close, {
45
47
  "data-slot": "dialog-close",
46
48
  asChild: true,
@@ -5,7 +5,7 @@ declare function DrawerTrigger({ ...props }: React.ComponentProps<typeof Drawer$
5
5
  declare function DrawerPortal({ ...props }: React.ComponentProps<typeof Drawer$1.Portal>): React.JSX.Element;
6
6
  declare function DrawerClose({ ...props }: React.ComponentProps<typeof Drawer$1.Close>): React.JSX.Element;
7
7
  declare function DrawerOverlay({ className, ...props }: React.ComponentProps<typeof Drawer$1.Overlay>): React.JSX.Element;
8
- declare function DrawerContent({ className, children, ...props }: React.ComponentProps<typeof Drawer$1.Content>): React.JSX.Element;
8
+ declare function DrawerContent({ className, children, onInteractOutside, ...props }: React.ComponentProps<typeof Drawer$1.Content>): React.JSX.Element;
9
9
  declare function DrawerHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
10
10
  declare function DrawerFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
11
11
  declare function DrawerTitle({ className, ...props }: React.ComponentProps<typeof Drawer$1.Title>): React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { keepOpenOnToast } from "../../lib/internal/toast-clearance.js";
2
3
  import { cn } from "../../lib/utils.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { Drawer as Drawer$1 } from "vaul";
@@ -34,13 +35,14 @@ function DrawerOverlay({ className, ...props }) {
34
35
  ...props
35
36
  });
36
37
  }
37
- function DrawerContent({ className, children, ...props }) {
38
+ function DrawerContent({ className, children, onInteractOutside, ...props }) {
38
39
  return /* @__PURE__ */ jsxs(DrawerPortal, {
39
40
  "data-slot": "drawer-portal",
40
41
  children: [/* @__PURE__ */ jsx(DrawerOverlay, {}), /* @__PURE__ */ jsxs(Drawer$1.Content, {
41
42
  "data-slot": "drawer-content",
42
43
  className: cn("group/drawer-content fixed z-50 flex h-auto flex-col bg-popover text-sm text-popover-foreground data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm", className),
43
44
  ...props,
45
+ onInteractOutside: keepOpenOnToast(onInteractOutside),
44
46
  children: [/* @__PURE__ */ jsx("div", { className: "mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }), children]
45
47
  })]
46
48
  });
@@ -3,7 +3,7 @@ import { Dialog } from "radix-ui";
3
3
  declare function Sheet({ ...props }: React.ComponentProps<typeof Dialog.Root>): React.JSX.Element;
4
4
  declare function SheetTrigger({ ...props }: React.ComponentProps<typeof Dialog.Trigger>): React.JSX.Element;
5
5
  declare function SheetClose({ ...props }: React.ComponentProps<typeof Dialog.Close>): React.JSX.Element;
6
- declare function SheetContent({ className, children, side, showCloseButton, ...props }: React.ComponentProps<typeof Dialog.Content> & {
6
+ declare function SheetContent({ className, children, side, showCloseButton, onInteractOutside, ...props }: React.ComponentProps<typeof Dialog.Content> & {
7
7
  side?: "top" | "right" | "bottom" | "left";
8
8
  showCloseButton?: boolean;
9
9
  }): React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { keepOpenOnToast } from "../../lib/internal/toast-clearance.js";
2
3
  import { cn } from "../../lib/utils.js";
3
4
  import { Button } from "./button.js";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -36,12 +37,13 @@ function SheetOverlay({ className, ...props }) {
36
37
  ...props
37
38
  });
38
39
  }
39
- function SheetContent({ className, children, side = "right", showCloseButton = true, ...props }) {
40
+ function SheetContent({ className, children, side = "right", showCloseButton = true, onInteractOutside, ...props }) {
40
41
  return /* @__PURE__ */ jsxs(SheetPortal, { children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(Dialog.Content, {
41
42
  "data-slot": "sheet-content",
42
43
  "data-side": side,
43
44
  className: cn("fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10", className),
44
45
  ...props,
46
+ onInteractOutside: keepOpenOnToast(onInteractOutside),
45
47
  children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog.Close, {
46
48
  "data-slot": "sheet-close",
47
49
  asChild: true,
@@ -20,7 +20,8 @@ const Toaster = ({ ...props }) => {
20
20
  "--normal-bg": "var(--popover)",
21
21
  "--normal-text": "var(--popover-foreground)",
22
22
  "--normal-border": "var(--border)",
23
- "--border-radius": "var(--radius)"
23
+ "--border-radius": "var(--radius)",
24
+ pointerEvents: "auto"
24
25
  },
25
26
  toastOptions: { classNames: {
26
27
  toast: "cn-toast",
@@ -30,7 +30,7 @@ function TraceIcon({ className, ...props }) {
30
30
  function TraceTitle({ className, ...props }) {
31
31
  return /* @__PURE__ */ jsx("span", {
32
32
  "data-slot": "trace-title",
33
- className: cn("min-w-0 truncate motion-reduce:shimmer-none", className),
33
+ className: cn("min-w-0 wrap-anywhere motion-reduce:shimmer-none", className),
34
34
  ...props
35
35
  });
36
36
  }
@@ -2,6 +2,16 @@
2
2
  import * as React from "react";
3
3
  // src/lib/internal/toast-clearance.ts
4
4
  const counts = /* @__PURE__ */ new Map();
5
+ /**
6
+ * For a modal's `onInteractOutside`: a press or focus inside the Toaster (e.g. a toast's Undo) does not dismiss the
7
+ * modal. The Toaster opts out of the modal's `pointer-events: none` on <body> (src/components/ui/sonner.tsx).
8
+ */
9
+ function keepOpenOnToast(handler) {
10
+ return (event) => {
11
+ handler?.(event);
12
+ if (event.target instanceof Element && event.target.closest("[data-sonner-toaster]")) event.preventDefault();
13
+ };
14
+ }
5
15
  function useToastClearance(name, value, enabled = true) {
6
16
  React.useEffect(() => {
7
17
  if (!enabled || typeof document === "undefined") return;
@@ -19,4 +29,4 @@ function useToastClearance(name, value, enabled = true) {
19
29
  enabled
20
30
  ]);
21
31
  }
22
- export { useToastClearance };
32
+ export { keepOpenOnToast, useToastClearance };
package/dist/styles.css CHANGED
@@ -319,22 +319,37 @@
319
319
  /* Toasts clear floating controls. AppNav (tab bar, action button) and AssistantDock (launcher) set --hb-toast-* on
320
320
  <html> while mounted (src/lib/internal/toast-clearance.ts); this adds them to Sonner's bottom offset. Unlayered and
321
321
  !important because Sonner injects its own unlayered rule at runtime. Breakpoints: AppNav's tab bar shows under
322
- 768px; Sonner switches to full-width mobile toasts under 600px. */
322
+ 768px; Sonner switches to full-width mobile toasts under 600px.
323
+ An open DetailSheet sets --hb-toast-sheet (desktop side sheet: its width plus gap) or --hb-toast-drawer (phone
324
+ full-screen drawer: its footer height). The sheet moves toasts left of it and narrows them to fit; the drawer
325
+ replaces the other bottom offsets, since it covers the tab bar, action button and launcher. */
323
326
  [data-sonner-toaster][data-y-position="bottom"] {
324
327
  bottom: calc(var(--offset-bottom) + var(--hb-toast-launcher, 0px)) !important;
325
328
  }
329
+ @media (min-width: 768px) {
330
+ [data-sonner-toaster] {
331
+ --width: min(356px, 100vw - var(--hb-toast-sheet, 0px) - 2 * var(--offset-right)) !important;
332
+ }
333
+ [data-sonner-toaster][data-x-position="right"] {
334
+ right: calc(var(--offset-right) + var(--hb-toast-sheet, 0px)) !important;
335
+ }
336
+ [data-sonner-toaster][data-x-position="center"] {
337
+ transform: translateX(calc(-50% - var(--hb-toast-sheet, 0px) / 2)) !important;
338
+ }
339
+ }
326
340
  @media (max-width: 767px) {
327
341
  [data-sonner-toaster][data-y-position="bottom"] {
328
342
  bottom: calc(
329
- var(--offset-bottom) + var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) + var(--hb-toast-launcher, 0px)
343
+ var(--offset-bottom) +
344
+ var(--hb-toast-drawer, var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) + var(--hb-toast-launcher, 0px))
330
345
  ) !important;
331
346
  }
332
347
  }
333
348
  @media (max-width: 600px) {
334
349
  [data-sonner-toaster][data-y-position="bottom"] {
335
350
  bottom: calc(
336
- var(--mobile-offset-bottom) + var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) +
337
- var(--hb-toast-launcher, 0px)
351
+ var(--mobile-offset-bottom) +
352
+ var(--hb-toast-drawer, var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) + var(--hb-toast-launcher, 0px))
338
353
  ) !important;
339
354
  }
340
355
  }
@@ -373,7 +373,7 @@ Searchable IANA time zone field built on `Combobox`, listing `Intl.supportedValu
373
373
 
374
374
  ### `DetailSheet`
375
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"`.
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"`. While open, toasts (e.g. `toastUndo`) stay off it: on desktop they move to the left of the sheet and narrow to fit; on phones they sit above the footer (one row of buttons) or the bottom safe area, in place of the hidden tab bar's space.
377
377
 
378
378
  ```tsx
379
379
  ;<DetailSheet
@@ -651,7 +651,7 @@ Scrollable log that sticks to the bottom as content streams in (unless the user
651
651
 
652
652
  ### `ChatMessage`
653
653
 
654
- One turn. Props: `role: "user" | "assistant"` (matches AI SDK message roles), `avatar?` (assistant only, defaults to `AssistantAvatar`; `false` for none and full-width content, for agent threads where one turn is many blocks), `actions?` (assistant only, under the content), `variant?` (user bubble: `"muted"` default or `"soft"` accent tint), `children`. Long words wrap.
654
+ One turn. Props: `role: "user" | "assistant"` (matches AI SDK message roles), `avatar?` (assistant only, defaults to `AssistantAvatar`; `false` for none and full-width content, for agent threads where one turn is many blocks), `actions?` (assistant only, under the content), `variant?` (user bubble: `"muted"` default or `"soft"` accent tint), `children`. Long words wrap, in assistant content too.
655
655
  User turns render as a right-aligned bubble with preserved newlines. Assistant turns render free-flowing: put `Prose`, `CodeBlock`, `ToolCall`, `TypingIndicator` inside.
656
656
 
657
657
  ```tsx
@@ -722,9 +722,9 @@ Three pulsing dots. Props: `label?` (text after the dots, e.g. "Working"), `vari
722
722
 
723
723
  ### `ToolGroup`, `ToolStep`, `ToolStatusIcon`
724
724
 
725
- Agent tool calls as a bordered, collapsible card. `ToolStatus = "queued" | "waiting" | "running" | "done" | "error" | "stopped"`.
725
+ Agent tool calls as a bordered, collapsible card. Titles, labels and details wrap instead of cutting off. `ToolStatus = "queued" | "waiting" | "running" | "done" | "error" | "stopped"`.
726
726
  `ToolGroup` props: `title`, `status`, `meta?` (e.g. time), `autoOpen?` (default open while active, until the user toggles), `open?`/`onOpenChange?`.
727
- `ToolStep` props: `icon?`, `kind` (verb column), `detail` (monospace command or path), `status`, `meta?` (default from status), `output?` (monospace, shown when expanded; omit for a fixed row), `autoOpen?` (default open while running).
727
+ `ToolStep` props: `icon?`, `kind` (verb column; with an empty `detail` it is the whole label, for plain-language steps such as "Added milk"), `detail` (monospace command or path, or `""`), `status`, `meta?` (default from status), `output?` (monospace, shown when expanded; omit for a fixed row), `autoOpen?` (default open while running).
728
728
  `ToolStatusIcon`: spinner while running, else a toned dot. Lighter alternative: `Trace`.
729
729
 
730
730
  ```tsx
@@ -736,7 +736,7 @@ Agent tool calls as a bordered, collapsible card. `ToolStatus = "queued" | "wait
736
736
  ### `ApprovalCard`, `ApprovalResult`
737
737
 
738
738
  `ApprovalCard`: warning-ringed card for anything the agent waits on the user for. Props: `title`, `description?`, `icon?` (default shield, `null` for none), `reason?` (shown first after a bold "Why:"), `code?` + `codeLabel?` (wrapped code block with copy), `actions?` (buttons), `footer?`, `children` (more body).
739
- `ApprovalResult`: one-line record in the thread. Props: `decision: "approved" | "denied"`, `time?`, `children`.
739
+ `ApprovalResult`: short record of a decision in the thread; wraps when long. Props: `decision: "approved" | "denied"`, `time?`, `children`.
740
740
 
741
741
  ### `AssistantProvider`, `useAssistant`, `useAssistantPage`
742
742
 
@@ -744,7 +744,7 @@ One assistant conversation across pages. `AssistantProvider` props: `onSend(text
744
744
 
745
745
  ### `AssistantDock`
746
746
 
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?`.
747
+ The assistant as a floating panel over the page (never reflows it): header (icon, title, your `actions`, 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?`, `actions?` (header controls before the full-screen link, e.g. a "New chat" icon button with an `aria-label`), `emptyTitle?`, `emptyDescription?`, `suggestions?` (default: the page's), `placeholder?`, `dictation?`, `launcher?` (false when another control opens it), `launcherLabel?`.
748
748
 
749
749
  ### `AskBox`
750
750
 
@@ -774,7 +774,7 @@ const { send, pending, stop, reply } = useAssistant()
774
774
 
775
775
  ### `ActionReceipt`
776
776
 
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".
777
+ What the assistant did, as a compact card: round check, `title` (past tense), `meta?` (wraps when long), Undo (`onUndo?`, `undoLabel?`). `undone` strikes it through and shows "Undone".
778
778
 
779
779
  ### `CodeSnippet`, `InlineCode`, `Prose`, `ProseBlockquote`
780
780
 
package/docs/ROADMAP.md CHANGED
@@ -48,7 +48,7 @@ Calendar views and SmartInput follow when a second app needs them.
48
48
  | AssistantDock (built) | Floating panel over the page (never reflows it) with header, empty state, thread, receipts and composer; a soft launcher button when closed; a link to full screen. Same on every page; pages pass suggestions only. | Extends ChatShell. |
49
49
  | AskBox (built) | Large rounded input with chips that appear as you type, suggestion pills under it, and the dictation button. Focus ring on the box, not the field. | One placeholder per app ("Ask HomeBot anything"). |
50
50
  | DictationButton, `useDictation` (built) | One button that is a microphone when empty, a stop button while listening (with listening bars and a live region), and send once there is text. The speech source plugs in (browser speech recognition, a server model). | Off-animation under reduced motion. |
51
- | ActionReceipt (built) | What the assistant did, as a compact card: round check, title, one meta line, Undo. One per action. | The core of "safe actions run on their own". |
51
+ | ActionReceipt (built) | What the assistant did, as a compact card: round check, title, meta, Undo. One per action. | The core of "safe actions run on their own". |
52
52
  | WhatIDid | Collapsed list of plain past-tense steps under an answer. | A plain-language variant of ToolSteps. |
53
53
  | ProgressCard | A long-running job: title, status word, progress bar, time left, an action when finished. | Downloads, imports, syncs. |
54
54
  | ChatsHome | Recent chats grouped Today, This week, Earlier, with search and a row menu (Rename, Delete with inline confirm). | Extends ConversationList. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harebear-ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Opinionated, pre-themed shadcn/ui component library for React 19 + Tailwind CSS v4. Admin, data, and AI chat building blocks, ready for agents.",
5
5
  "license": "MIT",
6
6
  "author": "Harrison Milbradt",