harebear-ui 0.2.2 → 0.3.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-shell.d.ts +4 -1
- package/dist/components/admin/app-shell.js +49 -46
- package/dist/components/admin/confirm-dialog.d.ts +17 -0
- package/dist/components/admin/confirm-dialog.js +20 -0
- package/dist/components/admin/copy-field.d.ts +17 -0
- package/dist/components/admin/copy-field.js +37 -0
- package/dist/components/admin/search-input.js +1 -1
- package/dist/components/admin/status.d.ts +12 -6
- package/dist/components/admin/status.js +8 -5
- package/dist/components/admin/user-chip.d.ts +1 -1
- package/dist/components/admin/user-chip.js +7 -4
- package/dist/components/ai/approval.d.ts +30 -0
- package/dist/components/ai/approval.js +81 -0
- package/dist/components/ai/chat-shell.d.ts +53 -0
- package/dist/components/ai/chat-shell.js +201 -0
- package/dist/components/ai/chat.d.ts +11 -3
- package/dist/components/ai/chat.js +21 -15
- package/dist/components/ai/code-block.js +1 -1
- package/dist/components/ai/conversation-list.d.ts +36 -3
- package/dist/components/ai/conversation-list.js +122 -13
- package/dist/components/ai/prompt-input.d.ts +28 -4
- package/dist/components/ai/prompt-input.js +52 -4
- package/dist/components/ai/tool-steps.d.ts +41 -0
- package/dist/components/ai/tool-steps.js +124 -0
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/alert.js +5 -2
- package/dist/components/ui/badge.d.ts +2 -1
- package/dist/components/ui/badge.js +27 -14
- package/dist/components/ui/item.js +2 -2
- package/dist/components/ui/sidebar.d.ts +3 -1
- package/dist/components/ui/sidebar.js +7 -3
- package/dist/hooks/use-media-query.d.ts +2 -0
- package/dist/hooks/use-media-query.js +12 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.js +10 -4
- package/dist/styles.css +24 -0
- package/docs/COMPONENTS.md +141 -72
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/skills/harebear-ui/SKILL.md +4 -1
|
@@ -21,7 +21,10 @@ export interface AppBrandProps extends React.ComponentProps<"div"> {
|
|
|
21
21
|
/** Optional trailing control, e.g. a workspace switcher button. */
|
|
22
22
|
action?: React.ReactNode;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Logo tile + product name. Used in the sidebar header or a top bar.
|
|
26
|
+
* In a sidebar collapsed to its icon rail, only the tile shows.
|
|
27
|
+
*/
|
|
25
28
|
declare function AppBrand({ name, subtitle, icon, action, className, ...props }: AppBrandProps): React.JSX.Element;
|
|
26
29
|
export interface AppShellProps {
|
|
27
30
|
/** Sidebar header content, usually <AppBrand />. */
|
|
@@ -4,11 +4,14 @@ import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel
|
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
// src/components/admin/app-shell.tsx
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Logo tile + product name. Used in the sidebar header or a top bar.
|
|
9
|
+
* In a sidebar collapsed to its icon rail, only the tile shows.
|
|
10
|
+
*/
|
|
8
11
|
function AppBrand({ name, subtitle, icon, action, className, ...props }) {
|
|
9
12
|
return /* @__PURE__ */ jsxs("div", {
|
|
10
13
|
"data-slot": "app-brand",
|
|
11
|
-
className: cn("flex items-center gap-3", className),
|
|
14
|
+
className: cn("flex items-center gap-3 group-data-[collapsible=icon]:justify-center", className),
|
|
12
15
|
...props,
|
|
13
16
|
children: [
|
|
14
17
|
icon && /* @__PURE__ */ jsx("div", {
|
|
@@ -16,7 +19,7 @@ function AppBrand({ name, subtitle, icon, action, className, ...props }) {
|
|
|
16
19
|
children: icon
|
|
17
20
|
}),
|
|
18
21
|
/* @__PURE__ */ jsxs("div", {
|
|
19
|
-
className: "min-w-0 flex-1",
|
|
22
|
+
className: "min-w-0 flex-1 group-data-[collapsible=icon]:hidden",
|
|
20
23
|
children: [/* @__PURE__ */ jsx("div", {
|
|
21
24
|
className: "truncate text-[15px] font-semibold",
|
|
22
25
|
children: name
|
|
@@ -25,7 +28,10 @@ function AppBrand({ name, subtitle, icon, action, className, ...props }) {
|
|
|
25
28
|
children: subtitle
|
|
26
29
|
})]
|
|
27
30
|
}),
|
|
28
|
-
action
|
|
31
|
+
action && /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: "contents group-data-[collapsible=icon]:hidden",
|
|
33
|
+
children: action
|
|
34
|
+
})
|
|
29
35
|
]
|
|
30
36
|
});
|
|
31
37
|
}
|
|
@@ -37,54 +43,51 @@ function AppShell({ brand, nav = [], sidebarExtra, sidebarFooter, header, linkCo
|
|
|
37
43
|
return /* @__PURE__ */ jsxs(SidebarProvider, {
|
|
38
44
|
defaultOpen,
|
|
39
45
|
style: { "--sidebar-width": "15.75rem" },
|
|
40
|
-
children: [/* @__PURE__ */ jsxs(Sidebar, {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
brand
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/* @__PURE__ */ jsxs(
|
|
48
|
-
className: "
|
|
49
|
-
children:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
children:
|
|
59
|
-
href: item.href,
|
|
60
|
-
children: [
|
|
61
|
-
item.icon,
|
|
62
|
-
/* @__PURE__ */ jsx("span", {
|
|
63
|
-
className: "flex-1",
|
|
64
|
-
children: item.label
|
|
65
|
-
}),
|
|
66
|
-
item.badge !== void 0 && /* @__PURE__ */ jsx(NavBadge, { children: item.badge })
|
|
67
|
-
]
|
|
68
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
46
|
+
children: [/* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
47
|
+
brand && /* @__PURE__ */ jsx(SidebarHeader, {
|
|
48
|
+
className: "px-4 pt-6 pb-2",
|
|
49
|
+
children: brand
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ jsxs(SidebarContent, {
|
|
52
|
+
className: "gap-5 px-2",
|
|
53
|
+
children: [nav.map((section, i) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [section.label && /* @__PURE__ */ jsx(SidebarGroupLabel, {
|
|
54
|
+
className: "text-xs font-medium",
|
|
55
|
+
children: section.label
|
|
56
|
+
}), /* @__PURE__ */ jsx(SidebarMenu, {
|
|
57
|
+
className: "gap-1",
|
|
58
|
+
children: section.items.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, {
|
|
59
|
+
asChild: !!item.href,
|
|
60
|
+
isActive: item.active,
|
|
61
|
+
onClick: item.onSelect,
|
|
62
|
+
children: item.href ? /* @__PURE__ */ jsxs(Link, {
|
|
63
|
+
href: item.href,
|
|
64
|
+
children: [
|
|
69
65
|
item.icon,
|
|
70
66
|
/* @__PURE__ */ jsx("span", {
|
|
71
67
|
className: "flex-1",
|
|
72
68
|
children: item.label
|
|
73
69
|
}),
|
|
74
70
|
item.badge !== void 0 && /* @__PURE__ */ jsx(NavBadge, { children: item.badge })
|
|
75
|
-
]
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
|
|
71
|
+
]
|
|
72
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
73
|
+
item.icon,
|
|
74
|
+
/* @__PURE__ */ jsx("span", {
|
|
75
|
+
className: "flex-1",
|
|
76
|
+
children: item.label
|
|
77
|
+
}),
|
|
78
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx(NavBadge, { children: item.badge })
|
|
79
|
+
] })
|
|
80
|
+
}) }, item.label))
|
|
81
|
+
})] }, section.label ?? i)), sidebarExtra && /* @__PURE__ */ jsx("div", {
|
|
82
|
+
className: "mt-auto px-2",
|
|
83
|
+
children: sidebarExtra
|
|
84
|
+
})]
|
|
85
|
+
}),
|
|
86
|
+
sidebarFooter && /* @__PURE__ */ jsx(SidebarFooter, {
|
|
87
|
+
className: "px-4 pb-6",
|
|
88
|
+
children: sidebarFooter
|
|
89
|
+
})
|
|
90
|
+
] }), /* @__PURE__ */ jsxs(SidebarInset, {
|
|
88
91
|
className: cn("min-w-0", className),
|
|
89
92
|
children: [header !== void 0 && /* @__PURE__ */ jsxs("header", {
|
|
90
93
|
"data-slot": "app-header",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ConfirmDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
title: React.ReactNode;
|
|
6
|
+
description?: React.ReactNode;
|
|
7
|
+
/** Default "Confirm". */
|
|
8
|
+
confirmLabel?: React.ReactNode;
|
|
9
|
+
/** Default "Cancel". */
|
|
10
|
+
cancelLabel?: React.ReactNode;
|
|
11
|
+
/** Red confirm button, for deletes and other destructive actions. */
|
|
12
|
+
destructive?: boolean;
|
|
13
|
+
onConfirm: () => void;
|
|
14
|
+
}
|
|
15
|
+
/** Controlled "Are you sure?" dialog: title, explanation, cancel and confirm. Closes after either choice. */
|
|
16
|
+
declare function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, cancelLabel, destructive, onConfirm }: ConfirmDialogProps): React.JSX.Element;
|
|
17
|
+
export { ConfirmDialog };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "../ui/alert-dialog.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/components/admin/confirm-dialog.tsx
|
|
5
|
+
/** Controlled "Are you sure?" dialog: title, explanation, cancel and confirm. Closes after either choice. */
|
|
6
|
+
function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, onConfirm }) {
|
|
7
|
+
return /* @__PURE__ */ jsx(AlertDialog, {
|
|
8
|
+
open,
|
|
9
|
+
onOpenChange,
|
|
10
|
+
children: /* @__PURE__ */ jsxs(AlertDialogContent, {
|
|
11
|
+
"data-slot": "confirm-dialog",
|
|
12
|
+
children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsx(AlertDialogTitle, { children: title }), description && /* @__PURE__ */ jsx(AlertDialogDescription, { children: description })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: cancelLabel }), /* @__PURE__ */ jsx(AlertDialogAction, {
|
|
13
|
+
variant: destructive ? "destructive" : "default",
|
|
14
|
+
onClick: onConfirm,
|
|
15
|
+
children: confirmLabel
|
|
16
|
+
})] })]
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export { ConfirmDialog };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InputGroup } from "../ui/input-group.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export interface CopyFieldProps extends Omit<React.ComponentProps<typeof InputGroup>, "children"> {
|
|
4
|
+
/** The text copied. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** What the field shows, if not the full value (e.g. a file name for a path). The full value is the tooltip. */
|
|
7
|
+
display?: string;
|
|
8
|
+
/** Leading label inside the field, e.g. "File" or "ID". */
|
|
9
|
+
label?: React.ReactNode;
|
|
10
|
+
/** Accessible name for the field. Default "Value". */
|
|
11
|
+
"aria-label"?: string;
|
|
12
|
+
/** Monospace text. Default true. */
|
|
13
|
+
mono?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Read-only value with a copy button: IDs, paths, tokens' public parts, URLs. */
|
|
16
|
+
declare function CopyField({ value, display, label, mono, "aria-label": name, className, ...props }: CopyFieldProps): React.JSX.Element;
|
|
17
|
+
export { CopyField };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText } from "../ui/input-group.js";
|
|
3
|
+
import { useCopyToClipboard } from "../../hooks/use-copy-to-clipboard.js";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
7
|
+
// src/components/admin/copy-field.tsx
|
|
8
|
+
/** Read-only value with a copy button: IDs, paths, tokens' public parts, URLs. */
|
|
9
|
+
function CopyField({ value, display, label, mono = true, "aria-label": name = "Value", className, ...props }) {
|
|
10
|
+
const { copied, copy } = useCopyToClipboard();
|
|
11
|
+
return /* @__PURE__ */ jsxs(InputGroup, {
|
|
12
|
+
"data-slot": "copy-field",
|
|
13
|
+
className,
|
|
14
|
+
...props,
|
|
15
|
+
children: [
|
|
16
|
+
label && /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(InputGroupText, { children: label }) }),
|
|
17
|
+
/* @__PURE__ */ jsx(InputGroupInput, {
|
|
18
|
+
readOnly: true,
|
|
19
|
+
value: display ?? value,
|
|
20
|
+
title: value,
|
|
21
|
+
"aria-label": name,
|
|
22
|
+
className: cn(mono && "font-mono"),
|
|
23
|
+
onFocus: (e) => e.currentTarget.select()
|
|
24
|
+
}),
|
|
25
|
+
/* @__PURE__ */ jsx(InputGroupAddon, {
|
|
26
|
+
align: "inline-end",
|
|
27
|
+
children: /* @__PURE__ */ jsx(InputGroupButton, {
|
|
28
|
+
size: "icon-xs",
|
|
29
|
+
"aria-label": `Copy ${name.toLowerCase()}`,
|
|
30
|
+
onClick: () => copy(value),
|
|
31
|
+
children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export { CopyField };
|
|
@@ -10,7 +10,7 @@ function SearchInput({ shortcut, size = "default", className, wrapperClassName,
|
|
|
10
10
|
const lg = size === "lg";
|
|
11
11
|
return /* @__PURE__ */ jsxs("label", {
|
|
12
12
|
"data-slot": "search-input",
|
|
13
|
-
className: cn("relative block", wrapperClassName),
|
|
13
|
+
className: cn("relative block group-data-[collapsible=icon]:hidden", wrapperClassName),
|
|
14
14
|
children: [
|
|
15
15
|
/* @__PURE__ */ jsx(SearchIcon, {
|
|
16
16
|
"aria-hidden": "true",
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { Badge } from "../ui/badge.js";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
export type StatusTone = "success" | "warning" | "danger" | "info" | "neutral";
|
|
4
|
-
|
|
5
|
-
declare function StatusDot({ tone, className, ...props }: React.ComponentProps<"span"> & {
|
|
4
|
+
export interface StatusDotProps extends React.ComponentProps<"span"> {
|
|
6
5
|
tone?: StatusTone;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/** Pulse the dot, for live states like "working" or "connecting". */
|
|
7
|
+
pulse?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Small colored dot. Use for status, legends, and project markers. */
|
|
10
|
+
declare function StatusDot({ tone, pulse, className, ...props }: StatusDotProps): React.JSX.Element;
|
|
11
|
+
export interface StatusBadgeProps extends React.ComponentProps<typeof Badge> {
|
|
10
12
|
tone?: StatusTone;
|
|
11
|
-
|
|
13
|
+
/** Pulse the dot. */
|
|
14
|
+
pulse?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Pill with a status dot and label, e.g. Paid / Pending / Refunded. `size="lg"` lines up with sm buttons. */
|
|
17
|
+
declare function StatusBadge({ tone, pulse, variant, children, className, ...props }: StatusBadgeProps): React.JSX.Element;
|
|
12
18
|
export { StatusBadge, StatusDot };
|
|
@@ -11,22 +11,25 @@ const toneColor = {
|
|
|
11
11
|
neutral: "bg-muted-foreground"
|
|
12
12
|
};
|
|
13
13
|
/** Small colored dot. Use for status, legends, and project markers. */
|
|
14
|
-
function StatusDot({ tone = "neutral", className, ...props }) {
|
|
14
|
+
function StatusDot({ tone = "neutral", pulse, className, ...props }) {
|
|
15
15
|
return /* @__PURE__ */ jsx("span", {
|
|
16
16
|
"data-slot": "status-dot",
|
|
17
17
|
"aria-hidden": "true",
|
|
18
|
-
className: cn("inline-block size-2 shrink-0 rounded-full", toneColor[tone], className),
|
|
18
|
+
className: cn("inline-block size-2 shrink-0 rounded-full", toneColor[tone], pulse && "animate-pulse motion-reduce:animate-none", className),
|
|
19
19
|
...props
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
/** Pill with a status dot and label, e.g. Paid / Pending / Refunded. */
|
|
23
|
-
function StatusBadge({ tone = "neutral", variant = "secondary", children, className, ...props }) {
|
|
22
|
+
/** Pill with a status dot and label, e.g. Paid / Pending / Refunded. `size="lg"` lines up with sm buttons. */
|
|
23
|
+
function StatusBadge({ tone = "neutral", pulse, variant = "secondary", children, className, ...props }) {
|
|
24
24
|
return /* @__PURE__ */ jsxs(Badge, {
|
|
25
25
|
"data-slot": "status-badge",
|
|
26
26
|
variant,
|
|
27
27
|
className,
|
|
28
28
|
...props,
|
|
29
|
-
children: [/* @__PURE__ */ jsx(StatusDot, {
|
|
29
|
+
children: [/* @__PURE__ */ jsx(StatusDot, {
|
|
30
|
+
tone,
|
|
31
|
+
pulse
|
|
32
|
+
}), children]
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
export { StatusBadge, StatusDot };
|
|
@@ -9,6 +9,6 @@ export interface UserChipProps extends React.ComponentProps<"div"> {
|
|
|
9
9
|
action?: React.ReactNode;
|
|
10
10
|
size?: "sm" | "default" | "lg";
|
|
11
11
|
}
|
|
12
|
-
/** Avatar + name + secondary line. */
|
|
12
|
+
/** Avatar + name + secondary line. In a sidebar collapsed to its icon rail, only the avatar shows. */
|
|
13
13
|
declare function UserChip({ name, subtitle, avatarUrl, action, size, className, ...props }: UserChipProps): React.JSX.Element;
|
|
14
14
|
export { UserChip };
|
|
@@ -7,11 +7,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
function initials(name) {
|
|
8
8
|
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase()).join("");
|
|
9
9
|
}
|
|
10
|
-
/** Avatar + name + secondary line. */
|
|
10
|
+
/** Avatar + name + secondary line. In a sidebar collapsed to its icon rail, only the avatar shows. */
|
|
11
11
|
function UserChip({ name, subtitle, avatarUrl, action, size = "default", className, ...props }) {
|
|
12
12
|
return /* @__PURE__ */ jsxs("div", {
|
|
13
13
|
"data-slot": "user-chip",
|
|
14
|
-
className: cn("flex min-w-0 items-center gap-3", className),
|
|
14
|
+
className: cn("flex min-w-0 items-center gap-3 group-data-[collapsible=icon]:justify-center", className),
|
|
15
15
|
...props,
|
|
16
16
|
children: [
|
|
17
17
|
/* @__PURE__ */ jsxs(Avatar, {
|
|
@@ -22,7 +22,7 @@ function UserChip({ name, subtitle, avatarUrl, action, size = "default", classNa
|
|
|
22
22
|
}), /* @__PURE__ */ jsx(AvatarFallback, { children: initials(name) })]
|
|
23
23
|
}),
|
|
24
24
|
/* @__PURE__ */ jsxs("div", {
|
|
25
|
-
className: "min-w-0 flex-1 leading-snug",
|
|
25
|
+
className: "min-w-0 flex-1 leading-snug group-data-[collapsible=icon]:hidden",
|
|
26
26
|
children: [/* @__PURE__ */ jsx("div", {
|
|
27
27
|
className: cn("truncate font-medium", size === "lg" && "text-lg font-semibold"),
|
|
28
28
|
children: name
|
|
@@ -31,7 +31,10 @@ function UserChip({ name, subtitle, avatarUrl, action, size = "default", classNa
|
|
|
31
31
|
children: subtitle
|
|
32
32
|
})]
|
|
33
33
|
}),
|
|
34
|
-
action
|
|
34
|
+
action && /* @__PURE__ */ jsx("div", {
|
|
35
|
+
className: "contents group-data-[collapsible=icon]:hidden",
|
|
36
|
+
children: action
|
|
37
|
+
})
|
|
35
38
|
]
|
|
36
39
|
});
|
|
37
40
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ApprovalCardProps extends Omit<React.ComponentProps<"section">, "title"> {
|
|
3
|
+
/** e.g. "Approval needed: run a command on services". */
|
|
4
|
+
title: React.ReactNode;
|
|
5
|
+
/** Small line under the title, e.g. the permission mode and when it was asked. */
|
|
6
|
+
description?: React.ReactNode;
|
|
7
|
+
/** Leading icon in a warning-tinted circle. Default a shield. Pass null for none. */
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
/** The exact command or change, shown in a wrapped code block with a copy button. */
|
|
10
|
+
code?: string;
|
|
11
|
+
/** Label above the code, e.g. "bash". */
|
|
12
|
+
codeLabel?: React.ReactNode;
|
|
13
|
+
/** The choices, usually Buttons: approve, approve for the session, deny. */
|
|
14
|
+
actions?: React.ReactNode;
|
|
15
|
+
/** Small print under the actions. */
|
|
16
|
+
footer?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Highlighted card for anything the agent is waiting on the user for: a tool approval, a question, a choice.
|
|
20
|
+
* Body text goes in children (e.g. the agent's reason).
|
|
21
|
+
*/
|
|
22
|
+
declare function ApprovalCard({ title, description, icon, code, codeLabel, actions, footer, className, children, ...props }: ApprovalCardProps): React.JSX.Element;
|
|
23
|
+
export interface ApprovalResultProps extends React.ComponentProps<"div"> {
|
|
24
|
+
decision: "approved" | "denied";
|
|
25
|
+
/** Trailing time, e.g. "10:42". */
|
|
26
|
+
time?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/** One-line record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
|
|
29
|
+
declare function ApprovalResult({ decision, time, className, children, ...props }: ApprovalResultProps): React.JSX.Element;
|
|
30
|
+
export { ApprovalCard, ApprovalResult };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockHeader, CodeBlockTitle } from "../ui/code-block.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { CheckIcon, ShieldAlertIcon, XIcon } from "lucide-react";
|
|
7
|
+
// src/components/ai/approval.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Highlighted card for anything the agent is waiting on the user for: a tool approval, a question, a choice.
|
|
10
|
+
* Body text goes in children (e.g. the agent's reason).
|
|
11
|
+
*/
|
|
12
|
+
function ApprovalCard({ title, description, icon = /* @__PURE__ */ jsx(ShieldAlertIcon, {}), code, codeLabel, actions, footer, className, children, ...props }) {
|
|
13
|
+
const id = React.useId();
|
|
14
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
15
|
+
"data-slot": "approval-card",
|
|
16
|
+
"aria-labelledby": id,
|
|
17
|
+
className: cn("flex min-w-0 flex-col gap-4 rounded-xl border border-warning/50 bg-card px-5.5 py-5 ring-4 ring-warning/10", className),
|
|
18
|
+
...props,
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsxs("div", {
|
|
21
|
+
className: "flex items-center gap-3",
|
|
22
|
+
children: [icon && /* @__PURE__ */ jsx("span", {
|
|
23
|
+
className: "grid size-8.5 shrink-0 place-items-center rounded-full bg-warning/12 text-warning [&_svg]:size-4.5",
|
|
24
|
+
children: icon
|
|
25
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
26
|
+
className: "flex min-w-0 flex-1 flex-col leading-snug",
|
|
27
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
28
|
+
id,
|
|
29
|
+
className: "text-[15px] font-semibold whitespace-pre-wrap wrap-anywhere",
|
|
30
|
+
children: title
|
|
31
|
+
}), description && /* @__PURE__ */ jsx("span", {
|
|
32
|
+
className: "text-xs text-muted-foreground",
|
|
33
|
+
children: description
|
|
34
|
+
})]
|
|
35
|
+
})]
|
|
36
|
+
}),
|
|
37
|
+
children && /* @__PURE__ */ jsx("div", {
|
|
38
|
+
className: "text-[14.5px] leading-relaxed whitespace-pre-wrap wrap-anywhere [&_strong]:font-semibold",
|
|
39
|
+
children
|
|
40
|
+
}),
|
|
41
|
+
code !== void 0 && /* @__PURE__ */ jsxs(CodeBlock, { children: [/* @__PURE__ */ jsxs(CodeBlockHeader, { children: [/* @__PURE__ */ jsx(CodeBlockTitle, { children: codeLabel ?? "command" }), /* @__PURE__ */ jsx(CodeBlockActions, { children: /* @__PURE__ */ jsx(CodeBlockCopyButton, { value: code }) })] }), /* @__PURE__ */ jsx(CodeBlockContent, {
|
|
42
|
+
wrap: true,
|
|
43
|
+
children: code
|
|
44
|
+
})] }),
|
|
45
|
+
actions && /* @__PURE__ */ jsx("div", {
|
|
46
|
+
className: "flex flex-wrap items-center gap-2.5",
|
|
47
|
+
children: actions
|
|
48
|
+
}),
|
|
49
|
+
footer && /* @__PURE__ */ jsx("div", {
|
|
50
|
+
className: "text-xs text-muted-foreground wrap-anywhere",
|
|
51
|
+
children: footer
|
|
52
|
+
})
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** One-line record of a decision in the thread, e.g. "You approved `docker restart x`, once". */
|
|
57
|
+
function ApprovalResult({ decision, time, className, children, ...props }) {
|
|
58
|
+
const denied = decision === "denied";
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
60
|
+
"data-slot": "approval-result",
|
|
61
|
+
"data-decision": decision,
|
|
62
|
+
className: cn("flex min-w-0 items-center gap-2.5 px-1 text-[13px]", className),
|
|
63
|
+
...props,
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", {
|
|
66
|
+
"aria-hidden": "true",
|
|
67
|
+
className: cn("grid size-6.5 shrink-0 place-items-center rounded-full [&_svg]:size-3.5", denied ? "bg-muted text-muted-foreground" : "bg-success/12 text-success"),
|
|
68
|
+
children: denied ? /* @__PURE__ */ jsx(XIcon, {}) : /* @__PURE__ */ jsx(CheckIcon, {})
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ jsx("span", {
|
|
71
|
+
className: "min-w-0 flex-1 truncate",
|
|
72
|
+
children
|
|
73
|
+
}),
|
|
74
|
+
time && /* @__PURE__ */ jsx("span", {
|
|
75
|
+
className: "shrink-0 text-xs text-muted-foreground tabular-nums",
|
|
76
|
+
children: time
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export { ApprovalCard, ApprovalResult };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ChatShellProps {
|
|
3
|
+
/** Sidebar top, usually <AppBrand />. Collapses to its logo tile in the icon rail. */
|
|
4
|
+
brand?: React.ReactNode;
|
|
5
|
+
/** Under the brand, usually <NewConversationButton />. Stays visible in the icon rail. */
|
|
6
|
+
sidebarActions?: React.ReactNode;
|
|
7
|
+
/** Scrolling sidebar body, usually <ConversationList />. Hidden in the icon rail. */
|
|
8
|
+
sidebar?: React.ReactNode;
|
|
9
|
+
/** Sidebar bottom, usually <UserChip /> with a ThemeToggle as its action. */
|
|
10
|
+
sidebarFooter?: React.ReactNode;
|
|
11
|
+
/** Right-hand panel, e.g. Cards with details about the open conversation. Inline at 1280px and up, a sheet below. */
|
|
12
|
+
details?: React.ReactNode;
|
|
13
|
+
/** Sheet title and toggle label for the details panel. Default "Details". */
|
|
14
|
+
detailsTitle?: string;
|
|
15
|
+
/** Show the details panel at first on wide screens. Default true. */
|
|
16
|
+
defaultDetailsOpen?: boolean;
|
|
17
|
+
/** Remember the sidebar's expanded or collapsed state in localStorage under this key. */
|
|
18
|
+
storageKey?: string;
|
|
19
|
+
/** Sidebar expanded at first (when nothing is stored). Default true. */
|
|
20
|
+
defaultOpen?: boolean;
|
|
21
|
+
className?: string;
|
|
22
|
+
/** The main column, usually <ChatPanel />. */
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Full-height chat app layout: a sidebar that collapses to an icon rail (a sheet on mobile), the main chat
|
|
27
|
+
* column, and an optional details panel. Put a ChatPanel inside.
|
|
28
|
+
*/
|
|
29
|
+
declare function ChatShell({ brand, sidebarActions, sidebar, sidebarFooter, details, detailsTitle, defaultDetailsOpen, storageKey, defaultOpen, className, children }: ChatShellProps): React.JSX.Element;
|
|
30
|
+
export interface ChatPanelProps extends Omit<React.ComponentProps<"section">, "title"> {
|
|
31
|
+
/** Conversation title. */
|
|
32
|
+
title?: React.ReactNode;
|
|
33
|
+
/** Line under the title, e.g. "Started Nov 10, 9:41". */
|
|
34
|
+
description?: React.ReactNode;
|
|
35
|
+
/** Header controls, e.g. <ModelPicker /> and a <StatusBadge size="lg" />. */
|
|
36
|
+
actions?: React.ReactNode;
|
|
37
|
+
/** Warning bar under the header, e.g. "Reconnecting…". */
|
|
38
|
+
notice?: React.ReactNode;
|
|
39
|
+
/** Under the thread, usually <PromptInput />. */
|
|
40
|
+
composer?: React.ReactNode;
|
|
41
|
+
/** Changing it starts a fresh thread view (scroll position resets). Pass the conversation id. */
|
|
42
|
+
conversationId?: React.Key;
|
|
43
|
+
/** Keep the thread scrolled to the bottom as it grows. Default true. */
|
|
44
|
+
autoScroll?: boolean;
|
|
45
|
+
/** The thread: ChatMessage, ToolGroup, ApprovalCard… */
|
|
46
|
+
children?: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The chat column as a card: header, optional notice, scrolling thread, composer. Inside ChatShell it adds the
|
|
50
|
+
* mobile sidebar button and the details toggle to the header by itself.
|
|
51
|
+
*/
|
|
52
|
+
declare function ChatPanel({ title, description, actions, notice, composer, conversationId, autoScroll, className, children, ...props }: ChatPanelProps): React.JSX.Element;
|
|
53
|
+
export { ChatPanel, ChatShell };
|