@cloudbase/agent-react-ui 0.0.23
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/README.md +135 -0
- package/components.json +21 -0
- package/dist/index.css +4241 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +2169 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2182 -0
- package/dist/index.mjs.map +1 -0
- package/example/.env.sample +2 -0
- package/example/App.tsx +368 -0
- package/example/app.css +1 -0
- package/example/index.html +12 -0
- package/example/main.tsx +9 -0
- package/example/vite.config.ts +34 -0
- package/package.json +75 -0
- package/postcss.config.cjs +3 -0
- package/src/components/ai-elements/agent.tsx +140 -0
- package/src/components/ai-elements/artifact.tsx +147 -0
- package/src/components/ai-elements/attachments.tsx +421 -0
- package/src/components/ai-elements/audio-player.tsx +228 -0
- package/src/components/ai-elements/canvas.tsx +22 -0
- package/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/src/components/ai-elements/checkpoint.tsx +71 -0
- package/src/components/ai-elements/code-block.tsx +532 -0
- package/src/components/ai-elements/commit.tsx +448 -0
- package/src/components/ai-elements/confirmation.tsx +176 -0
- package/src/components/ai-elements/connection.tsx +28 -0
- package/src/components/ai-elements/context.tsx +408 -0
- package/src/components/ai-elements/controls.tsx +18 -0
- package/src/components/ai-elements/conversation.tsx +100 -0
- package/src/components/ai-elements/edge.tsx +140 -0
- package/src/components/ai-elements/environment-variables.tsx +295 -0
- package/src/components/ai-elements/file-tree.tsx +258 -0
- package/src/components/ai-elements/image.tsx +24 -0
- package/src/components/ai-elements/inline-citation.tsx +287 -0
- package/src/components/ai-elements/message.tsx +336 -0
- package/src/components/ai-elements/mic-selector.tsx +370 -0
- package/src/components/ai-elements/model-selector.tsx +211 -0
- package/src/components/ai-elements/node.tsx +71 -0
- package/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/src/components/ai-elements/package-info.tsx +233 -0
- package/src/components/ai-elements/panel.tsx +15 -0
- package/src/components/ai-elements/persona.tsx +270 -0
- package/src/components/ai-elements/plan.tsx +142 -0
- package/src/components/ai-elements/prompt-input.tsx +1263 -0
- package/src/components/ai-elements/queue.tsx +274 -0
- package/src/components/ai-elements/reasoning.tsx +193 -0
- package/src/components/ai-elements/sandbox.tsx +126 -0
- package/src/components/ai-elements/schema-display.tsx +458 -0
- package/src/components/ai-elements/shimmer.tsx +64 -0
- package/src/components/ai-elements/snippet.tsx +139 -0
- package/src/components/ai-elements/sources.tsx +77 -0
- package/src/components/ai-elements/speech-input.tsx +301 -0
- package/src/components/ai-elements/stack-trace.tsx +482 -0
- package/src/components/ai-elements/suggestion.tsx +53 -0
- package/src/components/ai-elements/task.tsx +87 -0
- package/src/components/ai-elements/terminal.tsx +261 -0
- package/src/components/ai-elements/test-results.tsx +485 -0
- package/src/components/ai-elements/tool.tsx +174 -0
- package/src/components/ai-elements/toolbar.tsx +16 -0
- package/src/components/ai-elements/transcription.tsx +124 -0
- package/src/components/ai-elements/voice-selector.tsx +479 -0
- package/src/components/ai-elements/web-preview.tsx +263 -0
- package/src/components/chat/Chat.tsx +178 -0
- package/src/components/chat/Input.tsx +98 -0
- package/src/components/chat/Message.tsx +276 -0
- package/src/components/chat/index.ts +2 -0
- package/src/components/index.ts +1 -0
- package/src/components/ui/accordion.tsx +64 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +239 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/hover-card.tsx +42 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/css/global.css +123 -0
- package/src/css/index.css +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/index.ts +4 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/context.ts +8 -0
- package/src/locales/hooks.ts +20 -0
- package/src/locales/index.ts +3 -0
- package/src/locales/langs/en.ts +17 -0
- package/src/locales/langs/index.ts +12 -0
- package/src/locales/langs/zh-cn.ts +18 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +21 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import {
|
|
5
|
+
type RiveParameters,
|
|
6
|
+
useRive,
|
|
7
|
+
useStateMachineInput,
|
|
8
|
+
useViewModel,
|
|
9
|
+
useViewModelInstance,
|
|
10
|
+
useViewModelInstanceColor,
|
|
11
|
+
} from "@rive-app/react-webgl2";
|
|
12
|
+
import type { FC, ReactNode } from "react";
|
|
13
|
+
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
|
|
15
|
+
export type PersonaState =
|
|
16
|
+
| "idle"
|
|
17
|
+
| "listening"
|
|
18
|
+
| "thinking"
|
|
19
|
+
| "speaking"
|
|
20
|
+
| "asleep";
|
|
21
|
+
|
|
22
|
+
interface PersonaProps {
|
|
23
|
+
state: PersonaState;
|
|
24
|
+
onLoad?: RiveParameters["onLoad"];
|
|
25
|
+
onLoadError?: RiveParameters["onLoadError"];
|
|
26
|
+
onReady?: () => void;
|
|
27
|
+
onPause?: RiveParameters["onPause"];
|
|
28
|
+
onPlay?: RiveParameters["onPlay"];
|
|
29
|
+
onStop?: RiveParameters["onStop"];
|
|
30
|
+
className?: string;
|
|
31
|
+
variant?: keyof typeof sources;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The state machine name is always 'default' for Elements AI visuals
|
|
35
|
+
const stateMachine = "default";
|
|
36
|
+
|
|
37
|
+
const sources = {
|
|
38
|
+
obsidian: {
|
|
39
|
+
source:
|
|
40
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/obsidian-2.0.riv",
|
|
41
|
+
dynamicColor: true,
|
|
42
|
+
hasModel: true,
|
|
43
|
+
},
|
|
44
|
+
mana: {
|
|
45
|
+
source:
|
|
46
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/mana-2.0.riv",
|
|
47
|
+
dynamicColor: false,
|
|
48
|
+
hasModel: true,
|
|
49
|
+
},
|
|
50
|
+
opal: {
|
|
51
|
+
source:
|
|
52
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/orb-1.2.riv",
|
|
53
|
+
dynamicColor: false,
|
|
54
|
+
hasModel: false,
|
|
55
|
+
},
|
|
56
|
+
halo: {
|
|
57
|
+
source:
|
|
58
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/halo-2.0.riv",
|
|
59
|
+
dynamicColor: true,
|
|
60
|
+
hasModel: true,
|
|
61
|
+
},
|
|
62
|
+
glint: {
|
|
63
|
+
source:
|
|
64
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/glint-2.0.riv",
|
|
65
|
+
dynamicColor: true,
|
|
66
|
+
hasModel: true,
|
|
67
|
+
},
|
|
68
|
+
command: {
|
|
69
|
+
source:
|
|
70
|
+
"https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/command-2.0.riv",
|
|
71
|
+
dynamicColor: true,
|
|
72
|
+
hasModel: true,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getCurrentTheme = (): "light" | "dark" => {
|
|
77
|
+
if (typeof window !== "undefined") {
|
|
78
|
+
if (document.documentElement.classList.contains("dark")) {
|
|
79
|
+
return "dark";
|
|
80
|
+
}
|
|
81
|
+
if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
|
82
|
+
return "dark";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return "light";
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const useTheme = (enabled: boolean) => {
|
|
89
|
+
const [theme, setTheme] = useState<"light" | "dark">(getCurrentTheme);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
// Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)
|
|
93
|
+
if (!enabled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Watch for classList changes
|
|
98
|
+
const observer = new MutationObserver(() => {
|
|
99
|
+
setTheme(getCurrentTheme());
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
observer.observe(document.documentElement, {
|
|
103
|
+
attributes: true,
|
|
104
|
+
attributeFilter: ["class"],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Watch for OS-level theme changes
|
|
108
|
+
let mql: MediaQueryList | null = null;
|
|
109
|
+
const handleMediaChange = () => {
|
|
110
|
+
setTheme(getCurrentTheme());
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (window.matchMedia) {
|
|
114
|
+
mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
115
|
+
mql.addEventListener("change", handleMediaChange);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return () => {
|
|
119
|
+
observer.disconnect();
|
|
120
|
+
if (mql) {
|
|
121
|
+
mql.removeEventListener("change", handleMediaChange);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}, [enabled]);
|
|
125
|
+
|
|
126
|
+
return theme;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
interface PersonaWithModelProps {
|
|
130
|
+
rive: ReturnType<typeof useRive>["rive"];
|
|
131
|
+
source: (typeof sources)[keyof typeof sources];
|
|
132
|
+
children: React.ReactNode;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const PersonaWithModel = memo(
|
|
136
|
+
({ rive, source, children }: PersonaWithModelProps) => {
|
|
137
|
+
const theme = useTheme(source.dynamicColor);
|
|
138
|
+
const viewModel = useViewModel(rive, { useDefault: true });
|
|
139
|
+
const viewModelInstance = useViewModelInstance(viewModel, {
|
|
140
|
+
rive,
|
|
141
|
+
useDefault: true,
|
|
142
|
+
});
|
|
143
|
+
const viewModelInstanceColor = useViewModelInstanceColor(
|
|
144
|
+
"color",
|
|
145
|
+
viewModelInstance
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!(viewModelInstanceColor && source.dynamicColor)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const [r, g, b] = theme === "dark" ? [255, 255, 255] : [0, 0, 0];
|
|
154
|
+
viewModelInstanceColor.setRgb(r, g, b);
|
|
155
|
+
}, [viewModelInstanceColor, theme, source.dynamicColor]);
|
|
156
|
+
|
|
157
|
+
return children;
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
interface PersonaWithoutModelProps {
|
|
162
|
+
children: ReactNode;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const PersonaWithoutModel = memo(
|
|
166
|
+
({ children }: PersonaWithoutModelProps) => children
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
export const Persona: FC<PersonaProps> = memo(
|
|
170
|
+
({
|
|
171
|
+
variant = "obsidian",
|
|
172
|
+
state = "idle",
|
|
173
|
+
onLoad,
|
|
174
|
+
onLoadError,
|
|
175
|
+
onReady,
|
|
176
|
+
onPause,
|
|
177
|
+
onPlay,
|
|
178
|
+
onStop,
|
|
179
|
+
className,
|
|
180
|
+
}) => {
|
|
181
|
+
const source = sources[variant];
|
|
182
|
+
|
|
183
|
+
if (!source) {
|
|
184
|
+
throw new Error(`Invalid variant: ${variant}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Stabilize callbacks to prevent useRive from reinitializing
|
|
188
|
+
const callbacksRef = useRef({
|
|
189
|
+
onLoad,
|
|
190
|
+
onLoadError,
|
|
191
|
+
onReady,
|
|
192
|
+
onPause,
|
|
193
|
+
onPlay,
|
|
194
|
+
onStop,
|
|
195
|
+
});
|
|
196
|
+
callbacksRef.current = {
|
|
197
|
+
onLoad,
|
|
198
|
+
onLoadError,
|
|
199
|
+
onReady,
|
|
200
|
+
onPause,
|
|
201
|
+
onPlay,
|
|
202
|
+
onStop,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const stableCallbacks = useMemo(
|
|
206
|
+
() => ({
|
|
207
|
+
onLoad: ((loadedRive) =>
|
|
208
|
+
callbacksRef.current.onLoad?.(
|
|
209
|
+
loadedRive
|
|
210
|
+
)) as RiveParameters["onLoad"],
|
|
211
|
+
onLoadError: ((err) =>
|
|
212
|
+
callbacksRef.current.onLoadError?.(
|
|
213
|
+
err
|
|
214
|
+
)) as RiveParameters["onLoadError"],
|
|
215
|
+
onReady: () => callbacksRef.current.onReady?.(),
|
|
216
|
+
onPause: ((event) =>
|
|
217
|
+
callbacksRef.current.onPause?.(event)) as RiveParameters["onPause"],
|
|
218
|
+
onPlay: ((event) =>
|
|
219
|
+
callbacksRef.current.onPlay?.(event)) as RiveParameters["onPlay"],
|
|
220
|
+
onStop: ((event) =>
|
|
221
|
+
callbacksRef.current.onStop?.(event)) as RiveParameters["onStop"],
|
|
222
|
+
}),
|
|
223
|
+
[]
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const { rive, RiveComponent } = useRive({
|
|
227
|
+
src: source.source,
|
|
228
|
+
stateMachines: stateMachine,
|
|
229
|
+
autoplay: true,
|
|
230
|
+
onLoad: stableCallbacks.onLoad,
|
|
231
|
+
onLoadError: stableCallbacks.onLoadError,
|
|
232
|
+
onRiveReady: stableCallbacks.onReady,
|
|
233
|
+
onPause: stableCallbacks.onPause,
|
|
234
|
+
onPlay: stableCallbacks.onPlay,
|
|
235
|
+
onStop: stableCallbacks.onStop,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const listeningInput = useStateMachineInput(
|
|
239
|
+
rive,
|
|
240
|
+
stateMachine,
|
|
241
|
+
"listening"
|
|
242
|
+
);
|
|
243
|
+
const thinkingInput = useStateMachineInput(rive, stateMachine, "thinking");
|
|
244
|
+
const speakingInput = useStateMachineInput(rive, stateMachine, "speaking");
|
|
245
|
+
const asleepInput = useStateMachineInput(rive, stateMachine, "asleep");
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (listeningInput) {
|
|
249
|
+
listeningInput.value = state === "listening";
|
|
250
|
+
}
|
|
251
|
+
if (thinkingInput) {
|
|
252
|
+
thinkingInput.value = state === "thinking";
|
|
253
|
+
}
|
|
254
|
+
if (speakingInput) {
|
|
255
|
+
speakingInput.value = state === "speaking";
|
|
256
|
+
}
|
|
257
|
+
if (asleepInput) {
|
|
258
|
+
asleepInput.value = state === "asleep";
|
|
259
|
+
}
|
|
260
|
+
}, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);
|
|
261
|
+
|
|
262
|
+
const Component = source.hasModel ? PersonaWithModel : PersonaWithoutModel;
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<Component rive={rive} source={source}>
|
|
266
|
+
<RiveComponent className={cn("size-16 shrink-0", className)} />
|
|
267
|
+
</Component>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardAction,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardFooter,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import {
|
|
14
|
+
Collapsible,
|
|
15
|
+
CollapsibleContent,
|
|
16
|
+
CollapsibleTrigger,
|
|
17
|
+
} from "@/components/ui/collapsible";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
import { ChevronsUpDownIcon } from "lucide-react";
|
|
20
|
+
import type { ComponentProps } from "react";
|
|
21
|
+
import { createContext, useContext } from "react";
|
|
22
|
+
import { Shimmer } from "./shimmer";
|
|
23
|
+
|
|
24
|
+
interface PlanContextValue {
|
|
25
|
+
isStreaming: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PlanContext = createContext<PlanContextValue | null>(null);
|
|
29
|
+
|
|
30
|
+
const usePlan = () => {
|
|
31
|
+
const context = useContext(PlanContext);
|
|
32
|
+
if (!context) {
|
|
33
|
+
throw new Error("Plan components must be used within Plan");
|
|
34
|
+
}
|
|
35
|
+
return context;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type PlanProps = ComponentProps<typeof Collapsible> & {
|
|
39
|
+
isStreaming?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Plan = ({
|
|
43
|
+
className,
|
|
44
|
+
isStreaming = false,
|
|
45
|
+
children,
|
|
46
|
+
...props
|
|
47
|
+
}: PlanProps) => (
|
|
48
|
+
<PlanContext.Provider value={{ isStreaming }}>
|
|
49
|
+
<Collapsible asChild data-slot="plan" {...props}>
|
|
50
|
+
<Card className={cn("shadow-none", className)}>{children}</Card>
|
|
51
|
+
</Collapsible>
|
|
52
|
+
</PlanContext.Provider>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export type PlanHeaderProps = ComponentProps<typeof CardHeader>;
|
|
56
|
+
|
|
57
|
+
export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
|
|
58
|
+
<CardHeader
|
|
59
|
+
className={cn("flex items-start justify-between", className)}
|
|
60
|
+
data-slot="plan-header"
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export type PlanTitleProps = Omit<
|
|
66
|
+
ComponentProps<typeof CardTitle>,
|
|
67
|
+
"children"
|
|
68
|
+
> & {
|
|
69
|
+
children: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
|
|
73
|
+
const { isStreaming } = usePlan();
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<CardTitle data-slot="plan-title" {...props}>
|
|
77
|
+
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
|
78
|
+
</CardTitle>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type PlanDescriptionProps = Omit<
|
|
83
|
+
ComponentProps<typeof CardDescription>,
|
|
84
|
+
"children"
|
|
85
|
+
> & {
|
|
86
|
+
children: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const PlanDescription = ({
|
|
90
|
+
className,
|
|
91
|
+
children,
|
|
92
|
+
...props
|
|
93
|
+
}: PlanDescriptionProps) => {
|
|
94
|
+
const { isStreaming } = usePlan();
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<CardDescription
|
|
98
|
+
className={cn("text-balance", className)}
|
|
99
|
+
data-slot="plan-description"
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
|
103
|
+
</CardDescription>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type PlanActionProps = ComponentProps<typeof CardAction>;
|
|
108
|
+
|
|
109
|
+
export const PlanAction = (props: PlanActionProps) => (
|
|
110
|
+
<CardAction data-slot="plan-action" {...props} />
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
export type PlanContentProps = ComponentProps<typeof CardContent>;
|
|
114
|
+
|
|
115
|
+
export const PlanContent = (props: PlanContentProps) => (
|
|
116
|
+
<CollapsibleContent asChild>
|
|
117
|
+
<CardContent data-slot="plan-content" {...props} />
|
|
118
|
+
</CollapsibleContent>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
export type PlanFooterProps = ComponentProps<"div">;
|
|
122
|
+
|
|
123
|
+
export const PlanFooter = (props: PlanFooterProps) => (
|
|
124
|
+
<CardFooter data-slot="plan-footer" {...props} />
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
|
|
128
|
+
|
|
129
|
+
export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
|
|
130
|
+
<CollapsibleTrigger asChild>
|
|
131
|
+
<Button
|
|
132
|
+
className={cn("size-8", className)}
|
|
133
|
+
data-slot="plan-trigger"
|
|
134
|
+
size="icon"
|
|
135
|
+
variant="ghost"
|
|
136
|
+
{...props}
|
|
137
|
+
>
|
|
138
|
+
<ChevronsUpDownIcon className="size-4" />
|
|
139
|
+
<span className="sr-only">Toggle plan</span>
|
|
140
|
+
</Button>
|
|
141
|
+
</CollapsibleTrigger>
|
|
142
|
+
);
|