@openconsole/shadcn 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +460 -380
  2. package/components/ai-elements/agent.tsx +141 -0
  3. package/components/ai-elements/artifact.tsx +148 -0
  4. package/components/ai-elements/attachments.tsx +426 -0
  5. package/components/ai-elements/audio-player.tsx +231 -0
  6. package/components/ai-elements/canvas.tsx +26 -0
  7. package/components/ai-elements/chain-of-thought.tsx +222 -0
  8. package/components/ai-elements/checkpoint.tsx +71 -0
  9. package/components/ai-elements/code-block.tsx +562 -0
  10. package/components/ai-elements/commit.tsx +458 -0
  11. package/components/ai-elements/confirmation.tsx +174 -0
  12. package/components/ai-elements/connection.tsx +28 -0
  13. package/components/ai-elements/context.tsx +409 -0
  14. package/components/ai-elements/controls.tsx +18 -0
  15. package/components/ai-elements/conversation.tsx +168 -0
  16. package/components/ai-elements/edge.tsx +143 -0
  17. package/components/ai-elements/environment-variables.tsx +324 -0
  18. package/components/ai-elements/file-tree.tsx +304 -0
  19. package/components/ai-elements/image.tsx +24 -0
  20. package/components/ai-elements/index.ts +51 -0
  21. package/components/ai-elements/inline-citation.tsx +296 -0
  22. package/components/ai-elements/jsx-preview.tsx +310 -0
  23. package/components/ai-elements/message.tsx +360 -0
  24. package/components/ai-elements/mic-selector.tsx +375 -0
  25. package/components/ai-elements/model-selector.tsx +213 -0
  26. package/components/ai-elements/node.tsx +71 -0
  27. package/components/ai-elements/open-in-chat.tsx +370 -0
  28. package/components/ai-elements/package-info.tsx +239 -0
  29. package/components/ai-elements/panel.tsx +15 -0
  30. package/components/ai-elements/persona.tsx +306 -0
  31. package/components/ai-elements/plan.tsx +147 -0
  32. package/components/ai-elements/prompt-input.tsx +1463 -0
  33. package/components/ai-elements/queue.tsx +274 -0
  34. package/components/ai-elements/reasoning.tsx +228 -0
  35. package/components/ai-elements/sandbox.tsx +132 -0
  36. package/components/ai-elements/schema-display.tsx +471 -0
  37. package/components/ai-elements/shimmer.tsx +77 -0
  38. package/components/ai-elements/snippet.tsx +145 -0
  39. package/components/ai-elements/sources.tsx +77 -0
  40. package/components/ai-elements/speech-input.tsx +323 -0
  41. package/components/ai-elements/stack-trace.tsx +528 -0
  42. package/components/ai-elements/suggestion.tsx +57 -0
  43. package/components/ai-elements/task.tsx +87 -0
  44. package/components/ai-elements/terminal.tsx +273 -0
  45. package/components/ai-elements/test-results.tsx +496 -0
  46. package/components/ai-elements/tool.tsx +173 -0
  47. package/components/ai-elements/toolbar.tsx +16 -0
  48. package/components/ai-elements/transcription.tsx +125 -0
  49. package/components/ai-elements/voice-selector.tsx +524 -0
  50. package/components/ai-elements/web-preview.tsx +281 -0
  51. package/components/index.ts +3 -0
  52. package/{accordion.tsx → components/ui/accordion.tsx} +66 -66
  53. package/{alert-dialog.tsx → components/ui/alert-dialog.tsx} +196 -196
  54. package/{alert.tsx → components/ui/alert.tsx} +66 -66
  55. package/{aspect-ratio.tsx → components/ui/aspect-ratio.tsx} +11 -11
  56. package/{avatar.tsx → components/ui/avatar.tsx} +53 -53
  57. package/{badge.tsx → components/ui/badge.tsx} +46 -46
  58. package/{breadcrumb.tsx → components/ui/breadcrumb.tsx} +109 -109
  59. package/{button-group.tsx → components/ui/button-group.tsx} +83 -83
  60. package/{button.tsx → components/ui/button.tsx} +60 -60
  61. package/{calendar.tsx → components/ui/calendar.tsx} +219 -219
  62. package/{card.tsx → components/ui/card.tsx} +92 -92
  63. package/{carousel.tsx → components/ui/carousel.tsx} +241 -241
  64. package/{chart.tsx → components/ui/chart.tsx} +374 -374
  65. package/{checkbox.tsx → components/ui/checkbox.tsx} +32 -32
  66. package/{collapsible.tsx → components/ui/collapsible.tsx} +33 -33
  67. package/{command.tsx → components/ui/command.tsx} +184 -184
  68. package/{context-menu.tsx → components/ui/context-menu.tsx} +252 -252
  69. package/{dialog.tsx → components/ui/dialog.tsx} +143 -143
  70. package/{direction.tsx → components/ui/direction.tsx} +22 -22
  71. package/{drawer.tsx → components/ui/drawer.tsx} +135 -135
  72. package/{dropdown-menu.tsx → components/ui/dropdown-menu.tsx} +257 -257
  73. package/{empty.tsx → components/ui/empty.tsx} +104 -104
  74. package/{field.tsx → components/ui/field.tsx} +248 -248
  75. package/{form.tsx → components/ui/form.tsx} +167 -167
  76. package/{hover-card.tsx → components/ui/hover-card.tsx} +44 -44
  77. package/{icon.tsx → components/ui/icon.tsx} +55 -55
  78. package/components/ui/index.ts +59 -0
  79. package/{input-group.tsx → components/ui/input-group.tsx} +170 -170
  80. package/{input-otp.tsx → components/ui/input-otp.tsx} +77 -77
  81. package/{input.tsx → components/ui/input.tsx} +21 -21
  82. package/{item.tsx → components/ui/item.tsx} +193 -193
  83. package/{kbd.tsx → components/ui/kbd.tsx} +28 -28
  84. package/{label.tsx → components/ui/label.tsx} +24 -24
  85. package/{menubar.tsx → components/ui/menubar.tsx} +276 -276
  86. package/{native-select.tsx → components/ui/native-select.tsx} +62 -62
  87. package/{navigation-menu.tsx → components/ui/navigation-menu.tsx} +168 -168
  88. package/{pagination.tsx → components/ui/pagination.tsx} +127 -127
  89. package/{popover.tsx → components/ui/popover.tsx} +89 -89
  90. package/{progress.tsx → components/ui/progress.tsx} +31 -31
  91. package/{radio-group.tsx → components/ui/radio-group.tsx} +45 -45
  92. package/{resizable.tsx → components/ui/resizable.tsx} +53 -53
  93. package/{scroll-area.tsx → components/ui/scroll-area.tsx} +58 -58
  94. package/{select.tsx → components/ui/select.tsx} +187 -187
  95. package/{separator.tsx → components/ui/separator.tsx} +28 -28
  96. package/{sheet.tsx → components/ui/sheet.tsx} +139 -139
  97. package/{sidebar.tsx → components/ui/sidebar.tsx} +724 -724
  98. package/{skeleton.tsx → components/ui/skeleton.tsx} +13 -13
  99. package/{slider.tsx → components/ui/slider.tsx} +63 -63
  100. package/{sonner.tsx → components/ui/sonner.tsx} +40 -40
  101. package/{spinner.tsx → components/ui/spinner.tsx} +16 -16
  102. package/{switch.tsx → components/ui/switch.tsx} +35 -35
  103. package/{table.tsx → components/ui/table.tsx} +116 -116
  104. package/{tabs.tsx → components/ui/tabs.tsx} +66 -66
  105. package/{textarea.tsx → components/ui/textarea.tsx} +18 -18
  106. package/{toggle-group.tsx → components/ui/toggle-group.tsx} +83 -83
  107. package/{toggle.tsx → components/ui/toggle.tsx} +47 -47
  108. package/{tooltip.tsx → components/ui/tooltip.tsx} +61 -61
  109. package/hooks/index.ts +1 -1
  110. package/hooks/use-mobile.ts +19 -19
  111. package/index.ts +3 -59
  112. package/lib/index.ts +1 -1
  113. package/lib/utils.ts +6 -6
  114. package/package.json +79 -1
  115. package/styles.css +124 -124
  116. package/tsconfig.json +0 -12
  117. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,306 @@
1
+ "use client";
2
+
3
+ import { cn } from "../../lib/utils";
4
+ import type { RiveParameters } from "@rive-app/react-webgl2";
5
+ import {
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
+ // Delays Rive initialization by one frame so that React Strict Mode's
16
+ // immediate unmount cycle never creates a WebGL2 context. Only the
17
+ // second (real) mount will initialise, avoiding context exhaustion.
18
+ const useStrictModeSafeInit = () => {
19
+ const [ready, setReady] = useState(false);
20
+
21
+ useEffect(() => {
22
+ const id = requestAnimationFrame(() => setReady(true));
23
+ return () => {
24
+ cancelAnimationFrame(id);
25
+ setReady(false);
26
+ };
27
+ }, []);
28
+
29
+ return ready;
30
+ };
31
+
32
+ export type PersonaState =
33
+ | "idle"
34
+ | "listening"
35
+ | "thinking"
36
+ | "speaking"
37
+ | "asleep";
38
+
39
+ interface PersonaProps {
40
+ state: PersonaState;
41
+ onLoad?: RiveParameters["onLoad"];
42
+ onLoadError?: RiveParameters["onLoadError"];
43
+ onReady?: () => void;
44
+ onPause?: RiveParameters["onPause"];
45
+ onPlay?: RiveParameters["onPlay"];
46
+ onStop?: RiveParameters["onStop"];
47
+ className?: string;
48
+ variant?: keyof typeof sources;
49
+ }
50
+
51
+ // The state machine name is always 'default' for Elements AI visuals
52
+ const stateMachine = "default";
53
+
54
+ const sources = {
55
+ command: {
56
+ dynamicColor: true,
57
+ hasModel: true,
58
+ source:
59
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/command-2.0.riv",
60
+ },
61
+ glint: {
62
+ dynamicColor: true,
63
+ hasModel: true,
64
+ source:
65
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/glint-2.0.riv",
66
+ },
67
+ halo: {
68
+ dynamicColor: true,
69
+ hasModel: true,
70
+ source:
71
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/halo-2.0.riv",
72
+ },
73
+ mana: {
74
+ dynamicColor: false,
75
+ hasModel: true,
76
+ source:
77
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/mana-2.0.riv",
78
+ },
79
+ obsidian: {
80
+ dynamicColor: true,
81
+ hasModel: true,
82
+ source:
83
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/obsidian-2.0.riv",
84
+ },
85
+ opal: {
86
+ dynamicColor: false,
87
+ hasModel: false,
88
+ source:
89
+ "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/orb-1.2.riv",
90
+ },
91
+ };
92
+
93
+ const getCurrentTheme = (): "light" | "dark" => {
94
+ if (typeof window !== "undefined") {
95
+ if (document.documentElement.classList.contains("dark")) {
96
+ return "dark";
97
+ }
98
+ if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
99
+ return "dark";
100
+ }
101
+ }
102
+ return "light";
103
+ };
104
+
105
+ const useTheme = (enabled: boolean) => {
106
+ const [theme, setTheme] = useState<"light" | "dark">(getCurrentTheme);
107
+
108
+ useEffect(() => {
109
+ // Skip if not enabled (avoids unnecessary observers for non-dynamic-color variants)
110
+ if (!enabled) {
111
+ return;
112
+ }
113
+
114
+ // Watch for classList changes
115
+ const observer = new MutationObserver(() => {
116
+ setTheme(getCurrentTheme());
117
+ });
118
+
119
+ observer.observe(document.documentElement, {
120
+ attributeFilter: ["class"],
121
+ attributes: true,
122
+ });
123
+
124
+ // Watch for OS-level theme changes
125
+ let mql: MediaQueryList | null = null;
126
+ const handleMediaChange = () => {
127
+ setTheme(getCurrentTheme());
128
+ };
129
+
130
+ if (window.matchMedia) {
131
+ mql = window.matchMedia("(prefers-color-scheme: dark)");
132
+ mql.addEventListener("change", handleMediaChange);
133
+ }
134
+
135
+ return () => {
136
+ observer.disconnect();
137
+ if (mql) {
138
+ mql.removeEventListener("change", handleMediaChange);
139
+ }
140
+ };
141
+ }, [enabled]);
142
+
143
+ return theme;
144
+ };
145
+
146
+ interface PersonaWithModelProps {
147
+ rive: ReturnType<typeof useRive>["rive"];
148
+ source: (typeof sources)[keyof typeof sources];
149
+ children: React.ReactNode;
150
+ }
151
+
152
+ const PersonaWithModel = memo(
153
+ ({ rive, source, children }: PersonaWithModelProps) => {
154
+ const theme = useTheme(source.dynamicColor);
155
+ const viewModel = useViewModel(rive, { useDefault: true });
156
+ const viewModelInstance = useViewModelInstance(viewModel, {
157
+ rive,
158
+ useDefault: true,
159
+ });
160
+ const viewModelInstanceColor = useViewModelInstanceColor(
161
+ "color",
162
+ viewModelInstance
163
+ );
164
+
165
+ useEffect(() => {
166
+ if (!(viewModelInstanceColor && source.dynamicColor)) {
167
+ return;
168
+ }
169
+
170
+ const [r, g, b] = theme === "dark" ? [255, 255, 255] : [0, 0, 0];
171
+ viewModelInstanceColor.setRgb(r, g, b);
172
+ }, [viewModelInstanceColor, theme, source.dynamicColor]);
173
+
174
+ return children;
175
+ }
176
+ );
177
+
178
+ PersonaWithModel.displayName = "PersonaWithModel";
179
+
180
+ interface PersonaWithoutModelProps {
181
+ children: ReactNode;
182
+ }
183
+
184
+ const PersonaWithoutModel = memo(
185
+ ({ children }: PersonaWithoutModelProps) => children
186
+ );
187
+
188
+ PersonaWithoutModel.displayName = "PersonaWithoutModel";
189
+
190
+ export const Persona: FC<PersonaProps> = memo(
191
+ ({
192
+ variant = "obsidian",
193
+ state = "idle",
194
+ onLoad,
195
+ onLoadError,
196
+ onReady,
197
+ onPause,
198
+ onPlay,
199
+ onStop,
200
+ className,
201
+ }) => {
202
+ const source = sources[variant];
203
+
204
+ if (!source) {
205
+ throw new Error(`Invalid variant: ${variant}`);
206
+ }
207
+
208
+ // Stabilize callbacks to prevent useRive from reinitializing
209
+ const callbacksRef = useRef({
210
+ onLoad,
211
+ onLoadError,
212
+ onPause,
213
+ onPlay,
214
+ onReady,
215
+ onStop,
216
+ });
217
+
218
+ useEffect(() => {
219
+ callbacksRef.current = {
220
+ onLoad,
221
+ onLoadError,
222
+ onPause,
223
+ onPlay,
224
+ onReady,
225
+ onStop,
226
+ };
227
+ }, [onLoad, onLoadError, onPause, onPlay, onReady, onStop]);
228
+
229
+ const stableCallbacks = useMemo(
230
+ () => ({
231
+ onLoad: ((loadedRive) =>
232
+ callbacksRef.current.onLoad?.(
233
+ loadedRive
234
+ )) as RiveParameters["onLoad"],
235
+ onLoadError: ((err) =>
236
+ callbacksRef.current.onLoadError?.(
237
+ err
238
+ )) as RiveParameters["onLoadError"],
239
+ onPause: ((event) =>
240
+ callbacksRef.current.onPause?.(event)) as RiveParameters["onPause"],
241
+ onPlay: ((event) =>
242
+ callbacksRef.current.onPlay?.(event)) as RiveParameters["onPlay"],
243
+ onReady: () => callbacksRef.current.onReady?.(),
244
+ onStop: ((event) =>
245
+ callbacksRef.current.onStop?.(event)) as RiveParameters["onStop"],
246
+ }),
247
+ []
248
+ );
249
+
250
+ // Delay initialisation by one frame to avoid creating (and leaking)
251
+ // a WebGL2 context during React Strict Mode's first throw-away mount.
252
+ const ready = useStrictModeSafeInit();
253
+
254
+ const { rive, RiveComponent } = useRive(
255
+ ready
256
+ ? {
257
+ autoplay: true,
258
+ onLoad: stableCallbacks.onLoad,
259
+ onLoadError: stableCallbacks.onLoadError,
260
+ onPause: stableCallbacks.onPause,
261
+ onPlay: stableCallbacks.onPlay,
262
+ onRiveReady: stableCallbacks.onReady,
263
+ onStop: stableCallbacks.onStop,
264
+ src: source.source,
265
+ stateMachines: stateMachine,
266
+ }
267
+ : null
268
+ );
269
+
270
+ const listeningInput = useStateMachineInput(
271
+ rive,
272
+ stateMachine,
273
+ "listening"
274
+ );
275
+ const thinkingInput = useStateMachineInput(rive, stateMachine, "thinking");
276
+ const speakingInput = useStateMachineInput(rive, stateMachine, "speaking");
277
+ const asleepInput = useStateMachineInput(rive, stateMachine, "asleep");
278
+
279
+ // Rive state machine inputs are mutable objects that must be set via direct
280
+ // property assignment — this is the intended Rive API, not a React anti-pattern.
281
+ useEffect(() => {
282
+ if (listeningInput) {
283
+ listeningInput.value = state === "listening";
284
+ }
285
+ if (thinkingInput) {
286
+ thinkingInput.value = state === "thinking";
287
+ }
288
+ if (speakingInput) {
289
+ speakingInput.value = state === "speaking";
290
+ }
291
+ if (asleepInput) {
292
+ asleepInput.value = state === "asleep";
293
+ }
294
+ }, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);
295
+
296
+ const Component = source.hasModel ? PersonaWithModel : PersonaWithoutModel;
297
+
298
+ return (
299
+ <Component rive={rive} source={source}>
300
+ <RiveComponent className={cn("size-16 shrink-0", className)} />
301
+ </Component>
302
+ );
303
+ }
304
+ );
305
+
306
+ Persona.displayName = "Persona";
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import { Button } from "../ui/button";
4
+ import {
5
+ Card,
6
+ CardAction,
7
+ CardContent,
8
+ CardDescription,
9
+ CardFooter,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "../ui/card";
13
+ import {
14
+ Collapsible,
15
+ CollapsibleContent,
16
+ CollapsibleTrigger,
17
+ } from "../ui/collapsible";
18
+ import { cn } from "../../lib/utils";
19
+ import { ChevronsUpDownIcon } from "lucide-react";
20
+ import type { ComponentProps } from "react";
21
+ import { createContext, useContext, useMemo } from "react";
22
+
23
+ import { Shimmer } from "./shimmer";
24
+
25
+ interface PlanContextValue {
26
+ isStreaming: boolean;
27
+ }
28
+
29
+ const PlanContext = createContext<PlanContextValue | null>(null);
30
+
31
+ const usePlan = () => {
32
+ const context = useContext(PlanContext);
33
+ if (!context) {
34
+ throw new Error("Plan components must be used within Plan");
35
+ }
36
+ return context;
37
+ };
38
+
39
+ export type PlanProps = ComponentProps<typeof Collapsible> & {
40
+ isStreaming?: boolean;
41
+ };
42
+
43
+ export const Plan = ({
44
+ className,
45
+ isStreaming = false,
46
+ children,
47
+ ...props
48
+ }: PlanProps) => {
49
+ const contextValue = useMemo(() => ({ isStreaming }), [isStreaming]);
50
+
51
+ return (
52
+ <PlanContext.Provider value={contextValue}>
53
+ <Collapsible asChild data-slot="plan" {...props}>
54
+ <Card className={cn("shadow-none", className)}>{children}</Card>
55
+ </Collapsible>
56
+ </PlanContext.Provider>
57
+ );
58
+ };
59
+
60
+ export type PlanHeaderProps = ComponentProps<typeof CardHeader>;
61
+
62
+ export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
63
+ <CardHeader
64
+ className={cn("flex items-start justify-between", className)}
65
+ data-slot="plan-header"
66
+ {...props}
67
+ />
68
+ );
69
+
70
+ export type PlanTitleProps = Omit<
71
+ ComponentProps<typeof CardTitle>,
72
+ "children"
73
+ > & {
74
+ children: string;
75
+ };
76
+
77
+ export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
78
+ const { isStreaming } = usePlan();
79
+
80
+ return (
81
+ <CardTitle data-slot="plan-title" {...props}>
82
+ {isStreaming ? <Shimmer>{children}</Shimmer> : children}
83
+ </CardTitle>
84
+ );
85
+ };
86
+
87
+ export type PlanDescriptionProps = Omit<
88
+ ComponentProps<typeof CardDescription>,
89
+ "children"
90
+ > & {
91
+ children: string;
92
+ };
93
+
94
+ export const PlanDescription = ({
95
+ className,
96
+ children,
97
+ ...props
98
+ }: PlanDescriptionProps) => {
99
+ const { isStreaming } = usePlan();
100
+
101
+ return (
102
+ <CardDescription
103
+ className={cn("text-balance", className)}
104
+ data-slot="plan-description"
105
+ {...props}
106
+ >
107
+ {isStreaming ? <Shimmer>{children}</Shimmer> : children}
108
+ </CardDescription>
109
+ );
110
+ };
111
+
112
+ export type PlanActionProps = ComponentProps<typeof CardAction>;
113
+
114
+ export const PlanAction = (props: PlanActionProps) => (
115
+ <CardAction data-slot="plan-action" {...props} />
116
+ );
117
+
118
+ export type PlanContentProps = ComponentProps<typeof CardContent>;
119
+
120
+ export const PlanContent = (props: PlanContentProps) => (
121
+ <CollapsibleContent asChild>
122
+ <CardContent data-slot="plan-content" {...props} />
123
+ </CollapsibleContent>
124
+ );
125
+
126
+ export type PlanFooterProps = ComponentProps<"div">;
127
+
128
+ export const PlanFooter = (props: PlanFooterProps) => (
129
+ <CardFooter data-slot="plan-footer" {...props} />
130
+ );
131
+
132
+ export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
133
+
134
+ export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
135
+ <CollapsibleTrigger asChild>
136
+ <Button
137
+ className={cn("size-8", className)}
138
+ data-slot="plan-trigger"
139
+ size="icon"
140
+ variant="ghost"
141
+ {...props}
142
+ >
143
+ <ChevronsUpDownIcon className="size-4" />
144
+ <span className="sr-only">Toggle plan</span>
145
+ </Button>
146
+ </CollapsibleTrigger>
147
+ );