@elizaos/client 1.6.1-alpha.4 → 1.6.1-alpha.5
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/assets/{main-C4q5_rtN.js → main-4tyUgNqd.js} +3 -3
- package/dist/assets/{main-C4q5_rtN.js.map → main-4tyUgNqd.js.map} +1 -1
- package/dist/assets/{main-BNtEiK3o.js → main-Bbs84AcL.js} +77 -63
- package/dist/assets/main-Bbs84AcL.js.map +1 -0
- package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
- package/dist/assets/react-vendor-DxnAFk-d.js +611 -0
- package/dist/assets/react-vendor-DxnAFk-d.js.map +1 -0
- package/dist/index.html +1 -1
- package/package.json +8 -4
- package/src/components/agent-prism/Avatar.tsx +164 -0
- package/src/components/agent-prism/Badge.tsx +109 -0
- package/src/components/agent-prism/Button.tsx +138 -0
- package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
- package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
- package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
- package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
- package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
- package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
- package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
- package/src/components/agent-prism/IconButton.tsx +75 -0
- package/src/components/agent-prism/PriceBadge.tsx +12 -0
- package/src/components/agent-prism/SearchInput.tsx +17 -0
- package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
- package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
- package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
- package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
- package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
- package/src/components/agent-prism/SpanStatus.tsx +79 -0
- package/src/components/agent-prism/Tabs.tsx +141 -0
- package/src/components/agent-prism/TextInput.tsx +142 -0
- package/src/components/agent-prism/TimestampBadge.tsx +28 -0
- package/src/components/agent-prism/TokensBadge.tsx +26 -0
- package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
- package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
- package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
- package/src/components/agent-prism/TraceViewer.tsx +476 -0
- package/src/components/agent-prism/TreeView.tsx +57 -0
- package/src/components/agent-prism/shared.ts +210 -0
- package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
- package/src/components/agent-sidebar.tsx +2 -2
- package/src/components/chat.tsx +8 -8
- package/src/lib/agent-prism-utils.ts +46 -0
- package/src/lib/eliza-span-adapter.ts +487 -0
- package/dist/assets/main-BNtEiK3o.js.map +0 -1
- package/dist/assets/react-vendor-pe76PXQl.js +0 -546
- package/dist/assets/react-vendor-pe76PXQl.js.map +0 -1
package/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
7
7
|
<title>ElizaOS - Client</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/main-
|
|
8
|
+
<script type="module" crossorigin src="/assets/main-4tyUgNqd.js"></script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/client",
|
|
3
|
-
"version": "1.6.1-alpha.
|
|
3
|
+
"version": "1.6.1-alpha.5",
|
|
4
4
|
"description": "Web client interface for ElizaOS agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,8 +61,10 @@
|
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@elizaos/api-client": "1.6.1-alpha.
|
|
65
|
-
"@elizaos/core": "1.6.1-alpha.
|
|
64
|
+
"@elizaos/api-client": "1.6.1-alpha.5",
|
|
65
|
+
"@elizaos/core": "1.6.1-alpha.5",
|
|
66
|
+
"@evilmartians/agent-prism-data": "^0.0.4",
|
|
67
|
+
"@evilmartians/agent-prism-types": "^0.0.4",
|
|
66
68
|
"@hookform/resolvers": "^5.1.1",
|
|
67
69
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
|
68
70
|
"@radix-ui/react-avatar": "^1.1.3",
|
|
@@ -84,6 +86,7 @@
|
|
|
84
86
|
"@uidotdev/usehooks": "^2.4.1",
|
|
85
87
|
"buffer": "^6.0.3",
|
|
86
88
|
"class-variance-authority": "^0.7.1",
|
|
89
|
+
"classnames": "^2.5.1",
|
|
87
90
|
"clsx": "2.1.1",
|
|
88
91
|
"cmdk": "^1.0.4",
|
|
89
92
|
"date-fns": "^4.1.0",
|
|
@@ -96,6 +99,7 @@
|
|
|
96
99
|
"react-force-graph": "^1.47.6",
|
|
97
100
|
"react-force-graph-2d": "^1.27.1",
|
|
98
101
|
"react-joyride": "^2.9.3",
|
|
102
|
+
"react-json-pretty": "^2.2.0",
|
|
99
103
|
"react-markdown": "^10.1.0",
|
|
100
104
|
"react-resizable-panels": "^2.1.7",
|
|
101
105
|
"react-router": "^7.3.0",
|
|
@@ -157,5 +161,5 @@
|
|
|
157
161
|
"vite-tsconfig-paths": "^5.1.4",
|
|
158
162
|
"wait-on": "^8.0.3"
|
|
159
163
|
},
|
|
160
|
-
"gitHead": "
|
|
164
|
+
"gitHead": "90c052c82d42bdbfac79771ccdbcd6a1be939562"
|
|
161
165
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { User } from "lucide-react";
|
|
3
|
+
import { useState, type ComponentPropsWithRef, type ReactElement } from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ROUNDED_CLASSES,
|
|
7
|
+
type ColorVariant,
|
|
8
|
+
type ComponentSize,
|
|
9
|
+
} from "./shared.ts";
|
|
10
|
+
|
|
11
|
+
export type AvatarSize = Extract<
|
|
12
|
+
ComponentSize,
|
|
13
|
+
"4" | "6" | "8" | "9" | "10" | "11" | "12" | "16"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
const sizeClasses: Record<AvatarSize, string> = {
|
|
17
|
+
"4": "size-4 text-xs",
|
|
18
|
+
"6": "size-6 text-xs",
|
|
19
|
+
"8": "size-8 text-xs",
|
|
20
|
+
"9": "size-9 text-sm",
|
|
21
|
+
"10": "size-10 text-base",
|
|
22
|
+
"11": "size-11 text-lg",
|
|
23
|
+
"12": "size-12 text-xl",
|
|
24
|
+
"16": "size-16 text-2xl",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const iconSizeClasses: Record<AvatarSize, string> = {
|
|
28
|
+
"4": "size-3",
|
|
29
|
+
"6": "size-4",
|
|
30
|
+
"8": "size-6",
|
|
31
|
+
"9": "size-7",
|
|
32
|
+
"10": "size-8",
|
|
33
|
+
"11": "size-9",
|
|
34
|
+
"12": "size-10",
|
|
35
|
+
"16": "size-12",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const textSizeClasses: Record<AvatarSize, string> = {
|
|
39
|
+
"4": "text-xs",
|
|
40
|
+
"6": "text-xs",
|
|
41
|
+
"8": "text-xs",
|
|
42
|
+
"9": "text-sm",
|
|
43
|
+
"10": "text-base",
|
|
44
|
+
"11": "text-lg",
|
|
45
|
+
"12": "text-xl",
|
|
46
|
+
"16": "text-2xl",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const bgColorClasses: Record<ColorVariant, string> = {
|
|
50
|
+
gray: "bg-muted-foreground",
|
|
51
|
+
red: "bg-destructive",
|
|
52
|
+
orange: "bg-chart-1",
|
|
53
|
+
yellow: "bg-chart-5",
|
|
54
|
+
teal: "bg-chart-2",
|
|
55
|
+
indigo: "bg-primary",
|
|
56
|
+
purple: "bg-primary",
|
|
57
|
+
sky: "bg-chart-4",
|
|
58
|
+
cyan: "bg-chart-3",
|
|
59
|
+
emerald: "bg-accent",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type AvatarProps = ComponentPropsWithRef<"div"> & {
|
|
63
|
+
/**
|
|
64
|
+
* The image source for the avatar
|
|
65
|
+
*/
|
|
66
|
+
src?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The alt text for the avatar
|
|
69
|
+
*/
|
|
70
|
+
alt?: string;
|
|
71
|
+
/**
|
|
72
|
+
* The size of the avatar
|
|
73
|
+
* @default "md"
|
|
74
|
+
*/
|
|
75
|
+
size?: AvatarSize;
|
|
76
|
+
/**
|
|
77
|
+
* The border radius of the avatar
|
|
78
|
+
* @default "full"
|
|
79
|
+
*/
|
|
80
|
+
rounded?: "none" | "sm" | "md" | "lg" | "full";
|
|
81
|
+
/**
|
|
82
|
+
* Background color theme for the letter avatar
|
|
83
|
+
* Uses the unified color theme system
|
|
84
|
+
* @default "gray"
|
|
85
|
+
*/
|
|
86
|
+
bgColor?: ColorVariant;
|
|
87
|
+
/**
|
|
88
|
+
* Text color for the letter avatar
|
|
89
|
+
* @default "white"
|
|
90
|
+
*/
|
|
91
|
+
textColor?: "white" | "black";
|
|
92
|
+
/**
|
|
93
|
+
* Custom letter to display (will use first letter of alt if not provided)
|
|
94
|
+
*/
|
|
95
|
+
letter?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Optional className for additional styling
|
|
98
|
+
*/
|
|
99
|
+
className?: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const Avatar = ({
|
|
103
|
+
src,
|
|
104
|
+
alt = "Avatar",
|
|
105
|
+
size = "10",
|
|
106
|
+
rounded = "full",
|
|
107
|
+
bgColor = "gray",
|
|
108
|
+
textColor = "white",
|
|
109
|
+
letter,
|
|
110
|
+
className = "",
|
|
111
|
+
...rest
|
|
112
|
+
}: AvatarProps): ReactElement => {
|
|
113
|
+
const [error, setError] = useState(false);
|
|
114
|
+
|
|
115
|
+
const displayLetter = letter ? letter.charAt(0) : alt.charAt(0).toUpperCase();
|
|
116
|
+
|
|
117
|
+
const actualTextColor = textColor === "white" ? "text-white" : "text-black";
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
className={cn(
|
|
122
|
+
"flex items-center justify-center overflow-hidden",
|
|
123
|
+
"bg-muted",
|
|
124
|
+
error && "border border-border",
|
|
125
|
+
sizeClasses[size],
|
|
126
|
+
textSizeClasses[size],
|
|
127
|
+
ROUNDED_CLASSES[rounded],
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
{...rest}
|
|
131
|
+
>
|
|
132
|
+
{error ? (
|
|
133
|
+
<User
|
|
134
|
+
className={cn(
|
|
135
|
+
iconSizeClasses[size],
|
|
136
|
+
"text-muted-foreground",
|
|
137
|
+
)}
|
|
138
|
+
/>
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
{src ? (
|
|
142
|
+
<img
|
|
143
|
+
src={src}
|
|
144
|
+
alt={alt}
|
|
145
|
+
className="h-full w-full object-cover"
|
|
146
|
+
onError={() => setError(true)}
|
|
147
|
+
/>
|
|
148
|
+
) : (
|
|
149
|
+
<div
|
|
150
|
+
className={cn(
|
|
151
|
+
"flex h-full w-full items-center justify-center",
|
|
152
|
+
bgColorClasses[bgColor],
|
|
153
|
+
actualTextColor,
|
|
154
|
+
"font-medium",
|
|
155
|
+
)}
|
|
156
|
+
>
|
|
157
|
+
{displayLetter}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
COLOR_THEME_CLASSES,
|
|
7
|
+
type ColorVariant,
|
|
8
|
+
type ComponentSize,
|
|
9
|
+
} from "./shared.ts";
|
|
10
|
+
|
|
11
|
+
type BadgeSize = Extract<ComponentSize, "4" | "5" | "6" | "7">;
|
|
12
|
+
|
|
13
|
+
const sizeClasses: Record<BadgeSize, string> = {
|
|
14
|
+
"4": "px-1 gap-1 h-4",
|
|
15
|
+
"5": "px-1.5 gap-1 h-5",
|
|
16
|
+
"6": "px-2 gap-1.5 h-6",
|
|
17
|
+
"7": "px-2.5 gap-2 h-7",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const textSizes: Record<BadgeSize, string> = {
|
|
21
|
+
"4": "text-xs font-normal leading-3",
|
|
22
|
+
"5": "text-xs font-medium",
|
|
23
|
+
"6": "text-sm font-medium",
|
|
24
|
+
"7": "text-sm font-medium",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type BadgeProps = ComponentPropsWithRef<"span"> & {
|
|
28
|
+
/**
|
|
29
|
+
* The content of the badge
|
|
30
|
+
*/
|
|
31
|
+
label: ReactNode;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The color theme of the badge
|
|
35
|
+
* Uses the unified color theme system
|
|
36
|
+
* @default "gray"
|
|
37
|
+
*/
|
|
38
|
+
theme: ColorVariant;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The visual variant of the badge
|
|
42
|
+
* @default "solid"
|
|
43
|
+
*/
|
|
44
|
+
variant?: "solid" | "outline";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The size of the badge
|
|
48
|
+
* @default "md"
|
|
49
|
+
*/
|
|
50
|
+
size?: BadgeSize;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional icon to display at the start of the badge
|
|
54
|
+
*/
|
|
55
|
+
iconStart?: ReactElement;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Optional icon to display at the end of the badge
|
|
59
|
+
*/
|
|
60
|
+
iconEnd?: ReactElement;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Optional className for additional styling
|
|
64
|
+
*/
|
|
65
|
+
className?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Badge = ({
|
|
69
|
+
label,
|
|
70
|
+
theme = "gray",
|
|
71
|
+
variant = "solid",
|
|
72
|
+
size = "4",
|
|
73
|
+
iconStart,
|
|
74
|
+
iconEnd,
|
|
75
|
+
className = "",
|
|
76
|
+
...rest
|
|
77
|
+
}: BadgeProps): ReactElement => {
|
|
78
|
+
const { bg, darkBg, text, darkText } = COLOR_THEME_CLASSES[theme];
|
|
79
|
+
|
|
80
|
+
const variantClasses =
|
|
81
|
+
variant === "outline"
|
|
82
|
+
? `border ${text} ${darkText} bg-transparent dark:bg-transparent border-current`
|
|
83
|
+
: `${bg} ${text} ${darkBg} ${darkText}`;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<span
|
|
87
|
+
className={cn(
|
|
88
|
+
"inline-flex min-w-0 items-center overflow-hidden rounded font-medium",
|
|
89
|
+
variantClasses,
|
|
90
|
+
sizeClasses[size],
|
|
91
|
+
className,
|
|
92
|
+
)}
|
|
93
|
+
{...rest}
|
|
94
|
+
>
|
|
95
|
+
{iconStart && <span className="shrink-0">{iconStart}</span>}
|
|
96
|
+
|
|
97
|
+
<span
|
|
98
|
+
className={cn(
|
|
99
|
+
textSizes[size],
|
|
100
|
+
"min-w-0 max-w-full flex-shrink-0 truncate tracking-normal",
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{label}
|
|
104
|
+
</span>
|
|
105
|
+
|
|
106
|
+
{iconEnd && <span className="shrink-0">{iconEnd}</span>}
|
|
107
|
+
</span>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ROUNDED_CLASSES,
|
|
7
|
+
type ColorVariant,
|
|
8
|
+
type ComponentSize,
|
|
9
|
+
} from "./shared.ts";
|
|
10
|
+
|
|
11
|
+
type ButtonSize = Extract<
|
|
12
|
+
ComponentSize,
|
|
13
|
+
"6" | "7" | "8" | "9" | "10" | "11" | "12" | "16"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
const BASE_CLASSES =
|
|
17
|
+
"inline-flex items-center justify-center font-medium transition-all duration-200";
|
|
18
|
+
|
|
19
|
+
const sizeClasses = {
|
|
20
|
+
"6": "h-6 px-2 gap-1 text-xs",
|
|
21
|
+
"7": "h-7 px-2 gap-1 text-xs",
|
|
22
|
+
"8": "h-8 px-2 gap-1 text-xs",
|
|
23
|
+
"9": "h-9 px-2.5 gap-2 text-sm",
|
|
24
|
+
"10": "h-10 px-4 gap-2 text-sm",
|
|
25
|
+
"11": "h-11 px-5 gap-3 text-base",
|
|
26
|
+
"12": "h-12 px-5 gap-2.5 text-base",
|
|
27
|
+
"16": "h-16 px-7 gap-3 text-lg",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const filledThemeClasses: Record<ColorVariant, string> = {
|
|
31
|
+
gray: "bg-muted text-muted-foreground",
|
|
32
|
+
purple: "bg-primary text-primary-foreground",
|
|
33
|
+
indigo: "bg-primary text-primary-foreground",
|
|
34
|
+
orange: "bg-chart-1 text-primary-foreground",
|
|
35
|
+
teal: "bg-chart-2 text-primary-foreground",
|
|
36
|
+
cyan: "bg-chart-3 text-primary-foreground",
|
|
37
|
+
sky: "bg-chart-4 text-primary-foreground",
|
|
38
|
+
yellow: "bg-chart-5 text-primary-foreground",
|
|
39
|
+
emerald: "bg-accent text-accent-foreground",
|
|
40
|
+
red: "bg-destructive text-destructive-foreground",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const variantClasses = {
|
|
44
|
+
filled: "",
|
|
45
|
+
outlined:
|
|
46
|
+
"border border-2 bg-transparent text-foreground border-border",
|
|
47
|
+
ghost: "bg-transparent text-muted-foreground",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ButtonProps = ComponentPropsWithRef<"button"> & {
|
|
51
|
+
/**
|
|
52
|
+
* The size of the button
|
|
53
|
+
* @default "6"
|
|
54
|
+
*/
|
|
55
|
+
size?: ButtonSize;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The color theme of the button
|
|
59
|
+
* @default "gray"
|
|
60
|
+
*/
|
|
61
|
+
theme?: ColorVariant;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The border radius of the button
|
|
65
|
+
* @default "md"
|
|
66
|
+
*/
|
|
67
|
+
rounded?: "none" | "sm" | "md" | "lg" | "full";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The visual variant of the button
|
|
71
|
+
* @default "filled"
|
|
72
|
+
*/
|
|
73
|
+
variant?: "filled" | "outlined" | "ghost";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Makes the button full width
|
|
77
|
+
* @default false
|
|
78
|
+
*/
|
|
79
|
+
fullWidth?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional icon to display at the start of the button
|
|
83
|
+
*/
|
|
84
|
+
iconStart?: ReactElement;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Optional icon to display at the end of the button
|
|
88
|
+
*/
|
|
89
|
+
iconEnd?: ReactElement;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const Button = ({
|
|
93
|
+
children,
|
|
94
|
+
size = "6",
|
|
95
|
+
theme = "gray",
|
|
96
|
+
rounded = "md",
|
|
97
|
+
variant = "filled",
|
|
98
|
+
fullWidth = false,
|
|
99
|
+
disabled = false,
|
|
100
|
+
iconStart,
|
|
101
|
+
iconEnd,
|
|
102
|
+
type = "button",
|
|
103
|
+
onClick,
|
|
104
|
+
className = "",
|
|
105
|
+
...rest
|
|
106
|
+
}: ButtonProps) => {
|
|
107
|
+
const widthClass = fullWidth ? "w-full" : "";
|
|
108
|
+
const stateClasses = disabled
|
|
109
|
+
? "cursor-not-allowed opacity-50"
|
|
110
|
+
: "hover:opacity-70";
|
|
111
|
+
const filledThemeClass =
|
|
112
|
+
variant === "filled"
|
|
113
|
+
? filledThemeClasses[theme] || filledThemeClasses.gray
|
|
114
|
+
: "";
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<button
|
|
118
|
+
type={type}
|
|
119
|
+
onClick={onClick}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
className={cn(
|
|
122
|
+
BASE_CLASSES,
|
|
123
|
+
sizeClasses[size],
|
|
124
|
+
ROUNDED_CLASSES[rounded],
|
|
125
|
+
variantClasses[variant],
|
|
126
|
+
filledThemeClass,
|
|
127
|
+
widthClass,
|
|
128
|
+
stateClasses,
|
|
129
|
+
className,
|
|
130
|
+
)}
|
|
131
|
+
{...rest}
|
|
132
|
+
>
|
|
133
|
+
{iconStart && <span className="mr-1">{iconStart}</span>}
|
|
134
|
+
{children}
|
|
135
|
+
{iconEnd && <span className="ml-1">{iconEnd}</span>}
|
|
136
|
+
</button>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { ChevronsUpDown, ChevronsDownUp } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { IconButton } from "./IconButton.tsx";
|
|
6
|
+
|
|
7
|
+
export type SpanCardExpandAllButtonProps = ComponentPropsWithRef<"button"> & {
|
|
8
|
+
onExpandAll: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SpanCardCollapseAllButtonProps = ComponentPropsWithRef<"button"> & {
|
|
12
|
+
onCollapseAll: () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const ExpandAllButton = ({
|
|
16
|
+
onExpandAll,
|
|
17
|
+
...rest
|
|
18
|
+
}: SpanCardExpandAllButtonProps) => {
|
|
19
|
+
return (
|
|
20
|
+
<IconButton
|
|
21
|
+
size="7"
|
|
22
|
+
onClick={onExpandAll}
|
|
23
|
+
aria-label="Expand all"
|
|
24
|
+
{...rest}
|
|
25
|
+
>
|
|
26
|
+
<ChevronsUpDown className="size-3.5" />
|
|
27
|
+
</IconButton>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const CollapseAllButton = ({
|
|
32
|
+
onCollapseAll,
|
|
33
|
+
...rest
|
|
34
|
+
}: SpanCardCollapseAllButtonProps) => {
|
|
35
|
+
return (
|
|
36
|
+
<IconButton
|
|
37
|
+
size="7"
|
|
38
|
+
onClick={onCollapseAll}
|
|
39
|
+
aria-label="Collapse all"
|
|
40
|
+
{...rest}
|
|
41
|
+
>
|
|
42
|
+
<ChevronsDownUp className="size-3.5" />
|
|
43
|
+
</IconButton>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
export interface CollapsibleSectionProps {
|
|
7
|
+
/**
|
|
8
|
+
* The title text displayed in the trigger button
|
|
9
|
+
*/
|
|
10
|
+
title: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The content to display on the right side of the title
|
|
14
|
+
*/
|
|
15
|
+
rightContent?: React.ReactNode;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The content to display when the section is expanded
|
|
19
|
+
*/
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether the section starts in an open state
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
defaultOpen?: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Optional className for the root container
|
|
30
|
+
*/
|
|
31
|
+
className?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional className for the trigger button
|
|
35
|
+
*/
|
|
36
|
+
triggerClassName?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional className for the content area
|
|
40
|
+
*/
|
|
41
|
+
contentClassName?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional callback fired when the section is expanded or collapsed
|
|
45
|
+
*/
|
|
46
|
+
onOpenChange?: (open: boolean) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
|
50
|
+
title,
|
|
51
|
+
rightContent,
|
|
52
|
+
children,
|
|
53
|
+
defaultOpen = false,
|
|
54
|
+
className = "",
|
|
55
|
+
triggerClassName = "",
|
|
56
|
+
contentClassName = "",
|
|
57
|
+
onOpenChange,
|
|
58
|
+
}) => {
|
|
59
|
+
const [open, setOpen] = React.useState(defaultOpen);
|
|
60
|
+
|
|
61
|
+
const handleOpenChange = React.useCallback(
|
|
62
|
+
(open: boolean): void => {
|
|
63
|
+
setOpen(open);
|
|
64
|
+
onOpenChange?.(open);
|
|
65
|
+
},
|
|
66
|
+
[onOpenChange],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const handleKeyDown = React.useCallback(
|
|
70
|
+
(e: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
71
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
handleOpenChange(!open);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[handleOpenChange, open],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Collapsible.Root
|
|
81
|
+
open={open}
|
|
82
|
+
onOpenChange={handleOpenChange}
|
|
83
|
+
className={cn("rounded-lg", className)}
|
|
84
|
+
>
|
|
85
|
+
<Collapsible.Trigger asChild>
|
|
86
|
+
<div
|
|
87
|
+
tabIndex={0}
|
|
88
|
+
role="button"
|
|
89
|
+
className={cn(
|
|
90
|
+
"mb-1 flex w-full items-center justify-between gap-2 rounded-lg px-1 py-3 text-left text-sm font-medium text-foreground",
|
|
91
|
+
triggerClassName,
|
|
92
|
+
)}
|
|
93
|
+
onKeyDown={handleKeyDown}
|
|
94
|
+
aria-expanded={open}
|
|
95
|
+
aria-label={`${open ? "Collapse" : "Expand"} content of "${title}" section`}
|
|
96
|
+
>
|
|
97
|
+
<div className="flex w-full items-center gap-2">
|
|
98
|
+
<ChevronDown
|
|
99
|
+
className={cn(
|
|
100
|
+
"h-3 w-3 text-muted-foreground transition-transform duration-200",
|
|
101
|
+
open && "rotate-180",
|
|
102
|
+
)}
|
|
103
|
+
/>
|
|
104
|
+
<span className="truncate">{title}</span>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{rightContent}
|
|
108
|
+
</div>
|
|
109
|
+
</Collapsible.Trigger>
|
|
110
|
+
|
|
111
|
+
<Collapsible.Content
|
|
112
|
+
className={cn(
|
|
113
|
+
"data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown",
|
|
114
|
+
contentClassName,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{children}
|
|
118
|
+
</Collapsible.Content>
|
|
119
|
+
</Collapsible.Root>
|
|
120
|
+
);
|
|
121
|
+
};
|