@sero-ai/ui 0.1.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.
Files changed (113) hide show
  1. package/.turbo/turbo-typecheck.log +4 -0
  2. package/components.json +18 -0
  3. package/package.json +70 -0
  4. package/src/components/ai-elements/agent.tsx +142 -0
  5. package/src/components/ai-elements/artifact.tsx +149 -0
  6. package/src/components/ai-elements/attachments.tsx +427 -0
  7. package/src/components/ai-elements/audio-player.tsx +232 -0
  8. package/src/components/ai-elements/canvas.tsx +26 -0
  9. package/src/components/ai-elements/chain-of-thought.tsx +223 -0
  10. package/src/components/ai-elements/checkpoint.tsx +72 -0
  11. package/src/components/ai-elements/code-block.tsx +555 -0
  12. package/src/components/ai-elements/commit.tsx +449 -0
  13. package/src/components/ai-elements/confirmation.tsx +173 -0
  14. package/src/components/ai-elements/connection.tsx +28 -0
  15. package/src/components/ai-elements/context.tsx +410 -0
  16. package/src/components/ai-elements/controls.tsx +19 -0
  17. package/src/components/ai-elements/conversation.tsx +167 -0
  18. package/src/components/ai-elements/edge.tsx +144 -0
  19. package/src/components/ai-elements/environment-variables.tsx +325 -0
  20. package/src/components/ai-elements/file-tree.tsx +298 -0
  21. package/src/components/ai-elements/image.tsx +25 -0
  22. package/src/components/ai-elements/inline-citation.tsx +294 -0
  23. package/src/components/ai-elements/jsx-preview.tsx +250 -0
  24. package/src/components/ai-elements/message.tsx +359 -0
  25. package/src/components/ai-elements/mic-selector.tsx +372 -0
  26. package/src/components/ai-elements/model-selector.tsx +214 -0
  27. package/src/components/ai-elements/node.tsx +72 -0
  28. package/src/components/ai-elements/open-in-chat.tsx +367 -0
  29. package/src/components/ai-elements/package-info.tsx +235 -0
  30. package/src/components/ai-elements/panel.tsx +16 -0
  31. package/src/components/ai-elements/persona.tsx +280 -0
  32. package/src/components/ai-elements/plan.tsx +144 -0
  33. package/src/components/ai-elements/prompt-input.tsx +1341 -0
  34. package/src/components/ai-elements/queue.tsx +275 -0
  35. package/src/components/ai-elements/reasoning.tsx +229 -0
  36. package/src/components/ai-elements/sandbox.tsx +133 -0
  37. package/src/components/ai-elements/schema-display.tsx +473 -0
  38. package/src/components/ai-elements/shimmer.tsx +78 -0
  39. package/src/components/ai-elements/snippet.tsx +141 -0
  40. package/src/components/ai-elements/sources.tsx +78 -0
  41. package/src/components/ai-elements/speech-input.tsx +324 -0
  42. package/src/components/ai-elements/stack-trace.tsx +531 -0
  43. package/src/components/ai-elements/suggestion.tsx +58 -0
  44. package/src/components/ai-elements/task.tsx +88 -0
  45. package/src/components/ai-elements/terminal.tsx +277 -0
  46. package/src/components/ai-elements/test-results.tsx +497 -0
  47. package/src/components/ai-elements/tool.tsx +174 -0
  48. package/src/components/ai-elements/toolbar.tsx +17 -0
  49. package/src/components/ai-elements/transcription.tsx +126 -0
  50. package/src/components/ai-elements/voice-selector.tsx +525 -0
  51. package/src/components/ai-elements/web-preview.tsx +282 -0
  52. package/src/components/ui/accordion.tsx +64 -0
  53. package/src/components/ui/alert-dialog.tsx +196 -0
  54. package/src/components/ui/alert.tsx +66 -0
  55. package/src/components/ui/aspect-ratio.tsx +11 -0
  56. package/src/components/ui/avatar.tsx +107 -0
  57. package/src/components/ui/badge.tsx +48 -0
  58. package/src/components/ui/breadcrumb.tsx +109 -0
  59. package/src/components/ui/button-group.tsx +83 -0
  60. package/src/components/ui/button.tsx +64 -0
  61. package/src/components/ui/calendar.tsx +218 -0
  62. package/src/components/ui/card.tsx +92 -0
  63. package/src/components/ui/carousel.tsx +239 -0
  64. package/src/components/ui/chart.tsx +355 -0
  65. package/src/components/ui/checkbox.tsx +32 -0
  66. package/src/components/ui/collapsible.tsx +31 -0
  67. package/src/components/ui/combobox.tsx +310 -0
  68. package/src/components/ui/command.tsx +184 -0
  69. package/src/components/ui/context-menu.tsx +252 -0
  70. package/src/components/ui/dialog.tsx +158 -0
  71. package/src/components/ui/direction.tsx +22 -0
  72. package/src/components/ui/drawer.tsx +133 -0
  73. package/src/components/ui/dropdown-menu.tsx +257 -0
  74. package/src/components/ui/empty.tsx +104 -0
  75. package/src/components/ui/field.tsx +248 -0
  76. package/src/components/ui/form.tsx +165 -0
  77. package/src/components/ui/hover-card.tsx +42 -0
  78. package/src/components/ui/input-group.tsx +168 -0
  79. package/src/components/ui/input-otp.tsx +77 -0
  80. package/src/components/ui/input.tsx +21 -0
  81. package/src/components/ui/item.tsx +193 -0
  82. package/src/components/ui/kbd.tsx +28 -0
  83. package/src/components/ui/label.tsx +22 -0
  84. package/src/components/ui/menubar.tsx +276 -0
  85. package/src/components/ui/native-select.tsx +53 -0
  86. package/src/components/ui/navigation-menu.tsx +168 -0
  87. package/src/components/ui/pagination.tsx +127 -0
  88. package/src/components/ui/popover.tsx +87 -0
  89. package/src/components/ui/progress.tsx +31 -0
  90. package/src/components/ui/radio-group.tsx +43 -0
  91. package/src/components/ui/resizable.tsx +53 -0
  92. package/src/components/ui/scroll-area.tsx +56 -0
  93. package/src/components/ui/select.tsx +190 -0
  94. package/src/components/ui/separator.tsx +28 -0
  95. package/src/components/ui/sheet.tsx +145 -0
  96. package/src/components/ui/sidebar.tsx +724 -0
  97. package/src/components/ui/skeleton.tsx +13 -0
  98. package/src/components/ui/slider.tsx +61 -0
  99. package/src/components/ui/sonner.tsx +40 -0
  100. package/src/components/ui/spinner.tsx +16 -0
  101. package/src/components/ui/switch.tsx +33 -0
  102. package/src/components/ui/table.tsx +116 -0
  103. package/src/components/ui/tabs.tsx +91 -0
  104. package/src/components/ui/textarea.tsx +18 -0
  105. package/src/components/ui/toggle-group.tsx +83 -0
  106. package/src/components/ui/toggle.tsx +47 -0
  107. package/src/components/ui/tooltip.tsx +57 -0
  108. package/src/components/ui/tree.tsx +207 -0
  109. package/src/hooks/use-mobile.ts +17 -0
  110. package/src/index.ts +16 -0
  111. package/src/lib/utils.ts +6 -0
  112. package/src/styles/globals.css +373 -0
  113. package/tsconfig.json +18 -0
@@ -0,0 +1,427 @@
1
+ "use client";
2
+
3
+ import type { FileUIPart, SourceDocumentUIPart } from "ai";
4
+ import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
5
+
6
+ import { Button } from "../ui/button";
7
+ import {
8
+ HoverCard,
9
+ HoverCardContent,
10
+ HoverCardTrigger,
11
+ } from "../ui/hover-card";
12
+ import { cn } from "../../lib/utils";
13
+ import {
14
+ FileTextIcon,
15
+ GlobeIcon,
16
+ ImageIcon,
17
+ Music2Icon,
18
+ PaperclipIcon,
19
+ VideoIcon,
20
+ XIcon,
21
+ } from "lucide-react";
22
+ import { createContext, useCallback, useContext, useMemo } from "react";
23
+
24
+ // ============================================================================
25
+ // Types
26
+ // ============================================================================
27
+
28
+ export type AttachmentData =
29
+ | (FileUIPart & { id: string })
30
+ | (SourceDocumentUIPart & { id: string });
31
+
32
+ export type AttachmentMediaCategory =
33
+ | "image"
34
+ | "video"
35
+ | "audio"
36
+ | "document"
37
+ | "source"
38
+ | "unknown";
39
+
40
+ export type AttachmentVariant = "grid" | "inline" | "list";
41
+
42
+ const mediaCategoryIcons: Record<AttachmentMediaCategory, typeof ImageIcon> = {
43
+ audio: Music2Icon,
44
+ document: FileTextIcon,
45
+ image: ImageIcon,
46
+ source: GlobeIcon,
47
+ unknown: PaperclipIcon,
48
+ video: VideoIcon,
49
+ };
50
+
51
+ // ============================================================================
52
+ // Utility Functions
53
+ // ============================================================================
54
+
55
+ export const getMediaCategory = (
56
+ data: AttachmentData
57
+ ): AttachmentMediaCategory => {
58
+ if (data.type === "source-document") {
59
+ return "source";
60
+ }
61
+
62
+ const mediaType = data.mediaType ?? "";
63
+
64
+ if (mediaType.startsWith("image/")) {
65
+ return "image";
66
+ }
67
+ if (mediaType.startsWith("video/")) {
68
+ return "video";
69
+ }
70
+ if (mediaType.startsWith("audio/")) {
71
+ return "audio";
72
+ }
73
+ if (mediaType.startsWith("application/") || mediaType.startsWith("text/")) {
74
+ return "document";
75
+ }
76
+
77
+ return "unknown";
78
+ };
79
+
80
+ export const getAttachmentLabel = (data: AttachmentData): string => {
81
+ if (data.type === "source-document") {
82
+ return data.title || data.filename || "Source";
83
+ }
84
+
85
+ const category = getMediaCategory(data);
86
+ return data.filename || (category === "image" ? "Image" : "Attachment");
87
+ };
88
+
89
+ const renderAttachmentImage = (
90
+ url: string,
91
+ filename: string | undefined,
92
+ isGrid: boolean
93
+ ) =>
94
+ isGrid ? (
95
+ <img
96
+ alt={filename || "Image"}
97
+ className="size-full object-cover"
98
+ height={96}
99
+ src={url}
100
+ width={96}
101
+ />
102
+ ) : (
103
+ <img
104
+ alt={filename || "Image"}
105
+ className="size-full rounded object-cover"
106
+ height={20}
107
+ src={url}
108
+ width={20}
109
+ />
110
+ );
111
+
112
+ // ============================================================================
113
+ // Contexts
114
+ // ============================================================================
115
+
116
+ interface AttachmentsContextValue {
117
+ variant: AttachmentVariant;
118
+ }
119
+
120
+ const AttachmentsContext = createContext<AttachmentsContextValue | null>(null);
121
+
122
+ interface AttachmentContextValue {
123
+ data: AttachmentData;
124
+ mediaCategory: AttachmentMediaCategory;
125
+ onRemove?: () => void;
126
+ variant: AttachmentVariant;
127
+ }
128
+
129
+ const AttachmentContext = createContext<AttachmentContextValue | null>(null);
130
+
131
+ // ============================================================================
132
+ // Hooks
133
+ // ============================================================================
134
+
135
+ export const useAttachmentsContext = () =>
136
+ useContext(AttachmentsContext) ?? { variant: "grid" as const };
137
+
138
+ export const useAttachmentContext = () => {
139
+ const ctx = useContext(AttachmentContext);
140
+ if (!ctx) {
141
+ throw new Error("Attachment components must be used within <Attachment>");
142
+ }
143
+ return ctx;
144
+ };
145
+
146
+ // ============================================================================
147
+ // Attachments - Container
148
+ // ============================================================================
149
+
150
+ export type AttachmentsProps = HTMLAttributes<HTMLDivElement> & {
151
+ variant?: AttachmentVariant;
152
+ };
153
+
154
+ export const Attachments = ({
155
+ variant = "grid",
156
+ className,
157
+ children,
158
+ ...props
159
+ }: AttachmentsProps) => {
160
+ const contextValue = useMemo(() => ({ variant }), [variant]);
161
+
162
+ return (
163
+ <AttachmentsContext.Provider value={contextValue}>
164
+ <div
165
+ className={cn(
166
+ "flex items-start",
167
+ variant === "list" ? "flex-col gap-2" : "flex-wrap gap-2",
168
+ variant === "grid" && "ml-auto w-fit",
169
+ className
170
+ )}
171
+ {...props}
172
+ >
173
+ {children}
174
+ </div>
175
+ </AttachmentsContext.Provider>
176
+ );
177
+ };
178
+
179
+ // ============================================================================
180
+ // Attachment - Item
181
+ // ============================================================================
182
+
183
+ export type AttachmentProps = HTMLAttributes<HTMLDivElement> & {
184
+ data: AttachmentData;
185
+ onRemove?: () => void;
186
+ };
187
+
188
+ export const Attachment = ({
189
+ data,
190
+ onRemove,
191
+ className,
192
+ children,
193
+ ...props
194
+ }: AttachmentProps) => {
195
+ const { variant } = useAttachmentsContext();
196
+ const mediaCategory = getMediaCategory(data);
197
+
198
+ const contextValue = useMemo<AttachmentContextValue>(
199
+ () => ({ data, mediaCategory, onRemove, variant }),
200
+ [data, mediaCategory, onRemove, variant]
201
+ );
202
+
203
+ return (
204
+ <AttachmentContext.Provider value={contextValue}>
205
+ <div
206
+ className={cn(
207
+ "group relative",
208
+ variant === "grid" && "size-24 overflow-hidden rounded-lg",
209
+ variant === "inline" && [
210
+ "flex h-8 cursor-pointer select-none items-center gap-1.5",
211
+ "rounded-md border border-border px-1.5",
212
+ "font-medium text-sm transition-all",
213
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
214
+ ],
215
+ variant === "list" && [
216
+ "flex w-full items-center gap-3 rounded-lg border p-3",
217
+ "hover:bg-accent/50",
218
+ ],
219
+ className
220
+ )}
221
+ {...props}
222
+ >
223
+ {children}
224
+ </div>
225
+ </AttachmentContext.Provider>
226
+ );
227
+ };
228
+
229
+ // ============================================================================
230
+ // AttachmentPreview - Media preview
231
+ // ============================================================================
232
+
233
+ export type AttachmentPreviewProps = HTMLAttributes<HTMLDivElement> & {
234
+ fallbackIcon?: ReactNode;
235
+ };
236
+
237
+ export const AttachmentPreview = ({
238
+ fallbackIcon,
239
+ className,
240
+ ...props
241
+ }: AttachmentPreviewProps) => {
242
+ const { data, mediaCategory, variant } = useAttachmentContext();
243
+
244
+ const iconSize = variant === "inline" ? "size-3" : "size-4";
245
+
246
+ const renderIcon = (Icon: typeof ImageIcon) => (
247
+ <Icon className={cn(iconSize, "text-muted-foreground")} />
248
+ );
249
+
250
+ const renderContent = () => {
251
+ if (mediaCategory === "image" && data.type === "file" && data.url) {
252
+ return renderAttachmentImage(data.url, data.filename, variant === "grid");
253
+ }
254
+
255
+ if (mediaCategory === "video" && data.type === "file" && data.url) {
256
+ return <video className="size-full object-cover" muted src={data.url} />;
257
+ }
258
+
259
+ const Icon = mediaCategoryIcons[mediaCategory];
260
+ return fallbackIcon ?? renderIcon(Icon);
261
+ };
262
+
263
+ return (
264
+ <div
265
+ className={cn(
266
+ "flex shrink-0 items-center justify-center overflow-hidden",
267
+ variant === "grid" && "size-full bg-muted",
268
+ variant === "inline" && "size-5 rounded bg-background",
269
+ variant === "list" && "size-12 rounded bg-muted",
270
+ className
271
+ )}
272
+ {...props}
273
+ >
274
+ {renderContent()}
275
+ </div>
276
+ );
277
+ };
278
+
279
+ // ============================================================================
280
+ // AttachmentInfo - Name and type display
281
+ // ============================================================================
282
+
283
+ export type AttachmentInfoProps = HTMLAttributes<HTMLDivElement> & {
284
+ showMediaType?: boolean;
285
+ };
286
+
287
+ export const AttachmentInfo = ({
288
+ showMediaType = false,
289
+ className,
290
+ ...props
291
+ }: AttachmentInfoProps) => {
292
+ const { data, variant } = useAttachmentContext();
293
+ const label = getAttachmentLabel(data);
294
+
295
+ if (variant === "grid") {
296
+ return null;
297
+ }
298
+
299
+ return (
300
+ <div className={cn("min-w-0 flex-1", className)} {...props}>
301
+ <span className="block truncate">{label}</span>
302
+ {showMediaType && data.mediaType && (
303
+ <span className="block truncate text-muted-foreground text-xs">
304
+ {data.mediaType}
305
+ </span>
306
+ )}
307
+ </div>
308
+ );
309
+ };
310
+
311
+ // ============================================================================
312
+ // AttachmentRemove - Remove button
313
+ // ============================================================================
314
+
315
+ export type AttachmentRemoveProps = ComponentProps<typeof Button> & {
316
+ label?: string;
317
+ };
318
+
319
+ export const AttachmentRemove = ({
320
+ label = "Remove",
321
+ className,
322
+ children,
323
+ ...props
324
+ }: AttachmentRemoveProps) => {
325
+ const { onRemove, variant } = useAttachmentContext();
326
+
327
+ const handleClick = useCallback(
328
+ (e: React.MouseEvent) => {
329
+ e.stopPropagation();
330
+ onRemove?.();
331
+ },
332
+ [onRemove]
333
+ );
334
+
335
+ if (!onRemove) {
336
+ return null;
337
+ }
338
+
339
+ return (
340
+ <Button
341
+ aria-label={label}
342
+ className={cn(
343
+ variant === "grid" && [
344
+ "absolute top-2 right-2 size-6 rounded-full p-0",
345
+ "bg-background/80 backdrop-blur-sm",
346
+ "opacity-0 transition-opacity group-hover:opacity-100",
347
+ "hover:bg-background",
348
+ "[&>svg]:size-3",
349
+ ],
350
+ variant === "inline" && [
351
+ "size-5 rounded p-0",
352
+ "opacity-0 transition-opacity group-hover:opacity-100",
353
+ "[&>svg]:size-2.5",
354
+ ],
355
+ variant === "list" && ["size-8 shrink-0 rounded p-0", "[&>svg]:size-4"],
356
+ className
357
+ )}
358
+ onClick={handleClick}
359
+ type="button"
360
+ variant="ghost"
361
+ {...props}
362
+ >
363
+ {children ?? <XIcon />}
364
+ <span className="sr-only">{label}</span>
365
+ </Button>
366
+ );
367
+ };
368
+
369
+ // ============================================================================
370
+ // AttachmentHoverCard - Hover preview
371
+ // ============================================================================
372
+
373
+ export type AttachmentHoverCardProps = ComponentProps<typeof HoverCard>;
374
+
375
+ export const AttachmentHoverCard = ({
376
+ openDelay = 0,
377
+ closeDelay = 0,
378
+ ...props
379
+ }: AttachmentHoverCardProps) => (
380
+ <HoverCard closeDelay={closeDelay} openDelay={openDelay} {...props} />
381
+ );
382
+
383
+ export type AttachmentHoverCardTriggerProps = ComponentProps<
384
+ typeof HoverCardTrigger
385
+ >;
386
+
387
+ export const AttachmentHoverCardTrigger = (
388
+ props: AttachmentHoverCardTriggerProps
389
+ ) => <HoverCardTrigger {...props} />;
390
+
391
+ export type AttachmentHoverCardContentProps = ComponentProps<
392
+ typeof HoverCardContent
393
+ >;
394
+
395
+ export const AttachmentHoverCardContent = ({
396
+ align = "start",
397
+ className,
398
+ ...props
399
+ }: AttachmentHoverCardContentProps) => (
400
+ <HoverCardContent
401
+ align={align}
402
+ className={cn("w-auto p-2", className)}
403
+ {...props}
404
+ />
405
+ );
406
+
407
+ // ============================================================================
408
+ // AttachmentEmpty - Empty state
409
+ // ============================================================================
410
+
411
+ export type AttachmentEmptyProps = HTMLAttributes<HTMLDivElement>;
412
+
413
+ export const AttachmentEmpty = ({
414
+ className,
415
+ children,
416
+ ...props
417
+ }: AttachmentEmptyProps) => (
418
+ <div
419
+ className={cn(
420
+ "flex items-center justify-center p-4 text-muted-foreground text-sm",
421
+ className
422
+ )}
423
+ {...props}
424
+ >
425
+ {children ?? "No attachments"}
426
+ </div>
427
+ );
@@ -0,0 +1,232 @@
1
+ "use client";
2
+
3
+ import type { Experimental_SpeechResult as SpeechResult } from "ai";
4
+ import type { ComponentProps, CSSProperties } from "react";
5
+
6
+ import { Button } from "../ui/button";
7
+ import {
8
+ ButtonGroup,
9
+ ButtonGroupText,
10
+ } from "../ui/button-group";
11
+ import { cn } from "../../lib/utils";
12
+ import {
13
+ MediaControlBar,
14
+ MediaController,
15
+ MediaDurationDisplay,
16
+ MediaMuteButton,
17
+ MediaPlayButton,
18
+ MediaSeekBackwardButton,
19
+ MediaSeekForwardButton,
20
+ MediaTimeDisplay,
21
+ MediaTimeRange,
22
+ MediaVolumeRange,
23
+ } from "media-chrome/react";
24
+
25
+ export type AudioPlayerProps = Omit<
26
+ ComponentProps<typeof MediaController>,
27
+ "audio"
28
+ >;
29
+
30
+ export const AudioPlayer = ({
31
+ children,
32
+ style,
33
+ ...props
34
+ }: AudioPlayerProps) => (
35
+ <MediaController
36
+ audio
37
+ data-slot="audio-player"
38
+ style={
39
+ {
40
+ "--media-background-color": "transparent",
41
+ "--media-button-icon-height": "1rem",
42
+ "--media-button-icon-width": "1rem",
43
+ "--media-control-background": "transparent",
44
+ "--media-control-hover-background": "var(--color-accent)",
45
+ "--media-control-padding": "0",
46
+ "--media-font": "var(--font-sans)",
47
+ "--media-font-size": "10px",
48
+ "--media-icon-color": "currentColor",
49
+ "--media-preview-time-background": "var(--color-background)",
50
+ "--media-preview-time-border-radius": "var(--radius-md)",
51
+ "--media-preview-time-text-shadow": "none",
52
+ "--media-primary-color": "var(--color-primary)",
53
+ "--media-range-bar-color": "var(--color-primary)",
54
+ "--media-range-track-background": "var(--color-secondary)",
55
+ "--media-secondary-color": "var(--color-secondary)",
56
+ "--media-text-color": "var(--color-foreground)",
57
+ "--media-tooltip-arrow-display": "none",
58
+ "--media-tooltip-background": "var(--color-background)",
59
+ "--media-tooltip-border-radius": "var(--radius-md)",
60
+ ...style,
61
+ } as CSSProperties
62
+ }
63
+ {...props}
64
+ >
65
+ {children}
66
+ </MediaController>
67
+ );
68
+
69
+ export type AudioPlayerElementProps = Omit<ComponentProps<"audio">, "src"> &
70
+ (
71
+ | {
72
+ data: SpeechResult["audio"];
73
+ }
74
+ | {
75
+ src: string;
76
+ }
77
+ );
78
+
79
+ export const AudioPlayerElement = ({ ...props }: AudioPlayerElementProps) => (
80
+ // oxlint-disable-next-line eslint-plugin-jsx-a11y(media-has-caption) -- audio player captions are provided by consumer
81
+ <audio
82
+ data-slot="audio-player-element"
83
+ slot="media"
84
+ src={
85
+ "src" in props
86
+ ? props.src
87
+ : `data:${props.data.mediaType};base64,${props.data.base64}`
88
+ }
89
+ {...props}
90
+ />
91
+ );
92
+
93
+ export type AudioPlayerControlBarProps = ComponentProps<typeof MediaControlBar>;
94
+
95
+ export const AudioPlayerControlBar = ({
96
+ children,
97
+ ...props
98
+ }: AudioPlayerControlBarProps) => (
99
+ <MediaControlBar data-slot="audio-player-control-bar" {...props}>
100
+ <ButtonGroup orientation="horizontal">{children}</ButtonGroup>
101
+ </MediaControlBar>
102
+ );
103
+
104
+ export type AudioPlayerPlayButtonProps = ComponentProps<typeof MediaPlayButton>;
105
+
106
+ export const AudioPlayerPlayButton = ({
107
+ className,
108
+ ...props
109
+ }: AudioPlayerPlayButtonProps) => (
110
+ <Button asChild size="icon-sm" variant="outline">
111
+ <MediaPlayButton
112
+ className={cn("bg-transparent", className)}
113
+ data-slot="audio-player-play-button"
114
+ {...props}
115
+ />
116
+ </Button>
117
+ );
118
+
119
+ export type AudioPlayerSeekBackwardButtonProps = ComponentProps<
120
+ typeof MediaSeekBackwardButton
121
+ >;
122
+
123
+ export const AudioPlayerSeekBackwardButton = ({
124
+ seekOffset = 10,
125
+ ...props
126
+ }: AudioPlayerSeekBackwardButtonProps) => (
127
+ <Button asChild size="icon-sm" variant="outline">
128
+ <MediaSeekBackwardButton
129
+ data-slot="audio-player-seek-backward-button"
130
+ seekOffset={seekOffset}
131
+ {...props}
132
+ />
133
+ </Button>
134
+ );
135
+
136
+ export type AudioPlayerSeekForwardButtonProps = ComponentProps<
137
+ typeof MediaSeekForwardButton
138
+ >;
139
+
140
+ export const AudioPlayerSeekForwardButton = ({
141
+ seekOffset = 10,
142
+ ...props
143
+ }: AudioPlayerSeekForwardButtonProps) => (
144
+ <Button asChild size="icon-sm" variant="outline">
145
+ <MediaSeekForwardButton
146
+ data-slot="audio-player-seek-forward-button"
147
+ seekOffset={seekOffset}
148
+ {...props}
149
+ />
150
+ </Button>
151
+ );
152
+
153
+ export type AudioPlayerTimeDisplayProps = ComponentProps<
154
+ typeof MediaTimeDisplay
155
+ >;
156
+
157
+ export const AudioPlayerTimeDisplay = ({
158
+ className,
159
+ ...props
160
+ }: AudioPlayerTimeDisplayProps) => (
161
+ <ButtonGroupText asChild className="bg-transparent">
162
+ <MediaTimeDisplay
163
+ className={cn("tabular-nums", className)}
164
+ data-slot="audio-player-time-display"
165
+ {...props}
166
+ />
167
+ </ButtonGroupText>
168
+ );
169
+
170
+ export type AudioPlayerTimeRangeProps = ComponentProps<typeof MediaTimeRange>;
171
+
172
+ export const AudioPlayerTimeRange = ({
173
+ className,
174
+ ...props
175
+ }: AudioPlayerTimeRangeProps) => (
176
+ <ButtonGroupText asChild className="bg-transparent">
177
+ <MediaTimeRange
178
+ className={cn("", className)}
179
+ data-slot="audio-player-time-range"
180
+ {...props}
181
+ />
182
+ </ButtonGroupText>
183
+ );
184
+
185
+ export type AudioPlayerDurationDisplayProps = ComponentProps<
186
+ typeof MediaDurationDisplay
187
+ >;
188
+
189
+ export const AudioPlayerDurationDisplay = ({
190
+ className,
191
+ ...props
192
+ }: AudioPlayerDurationDisplayProps) => (
193
+ <ButtonGroupText asChild className="bg-transparent">
194
+ <MediaDurationDisplay
195
+ className={cn("tabular-nums", className)}
196
+ data-slot="audio-player-duration-display"
197
+ {...props}
198
+ />
199
+ </ButtonGroupText>
200
+ );
201
+
202
+ export type AudioPlayerMuteButtonProps = ComponentProps<typeof MediaMuteButton>;
203
+
204
+ export const AudioPlayerMuteButton = ({
205
+ className,
206
+ ...props
207
+ }: AudioPlayerMuteButtonProps) => (
208
+ <ButtonGroupText asChild className="bg-transparent">
209
+ <MediaMuteButton
210
+ className={cn("", className)}
211
+ data-slot="audio-player-mute-button"
212
+ {...props}
213
+ />
214
+ </ButtonGroupText>
215
+ );
216
+
217
+ export type AudioPlayerVolumeRangeProps = ComponentProps<
218
+ typeof MediaVolumeRange
219
+ >;
220
+
221
+ export const AudioPlayerVolumeRange = ({
222
+ className,
223
+ ...props
224
+ }: AudioPlayerVolumeRangeProps) => (
225
+ <ButtonGroupText asChild className="bg-transparent">
226
+ <MediaVolumeRange
227
+ className={cn("", className)}
228
+ data-slot="audio-player-volume-range"
229
+ {...props}
230
+ />
231
+ </ButtonGroupText>
232
+ );
@@ -0,0 +1,26 @@
1
+ import type { ReactFlowProps } from "@xyflow/react";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { Background, ReactFlow } from "@xyflow/react";
5
+ import "@xyflow/react/dist/style.css";
6
+
7
+ type CanvasProps = ReactFlowProps & {
8
+ children?: ReactNode;
9
+ };
10
+
11
+ const deleteKeyCode = ["Backspace", "Delete"];
12
+
13
+ export const Canvas = ({ children, ...props }: CanvasProps) => (
14
+ <ReactFlow
15
+ deleteKeyCode={deleteKeyCode}
16
+ fitView
17
+ panOnDrag={false}
18
+ panOnScroll
19
+ selectionOnDrag={true}
20
+ zoomOnDoubleClick={false}
21
+ {...props}
22
+ >
23
+ <Background bgColor="var(--sidebar)" />
24
+ {children}
25
+ </ReactFlow>
26
+ );