@openconsole/shadcn 0.2.4 → 0.2.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.
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,304 @@
1
+ "use client";
2
+
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "../ui/collapsible";
8
+ import { cn } from "../../lib/utils";
9
+ import {
10
+ ChevronRightIcon,
11
+ FileIcon,
12
+ FolderIcon,
13
+ FolderOpenIcon,
14
+ } from "lucide-react";
15
+ import type { HTMLAttributes, ReactNode } from "react";
16
+ import {
17
+ createContext,
18
+ useCallback,
19
+ useContext,
20
+ useMemo,
21
+ useState,
22
+ } from "react";
23
+
24
+ interface FileTreeContextType {
25
+ expandedPaths: Set<string>;
26
+ togglePath: (path: string) => void;
27
+ selectedPath?: string;
28
+ onSelect?: (path: string) => void;
29
+ }
30
+
31
+ // Default noop for context default value
32
+ // oxlint-disable-next-line eslint(no-empty-function)
33
+ const noop = () => {};
34
+
35
+ const FileTreeContext = createContext<FileTreeContextType>({
36
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-new-builtin)
37
+ expandedPaths: new Set(),
38
+ togglePath: noop,
39
+ });
40
+
41
+ export type FileTreeProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
42
+ expanded?: Set<string>;
43
+ defaultExpanded?: Set<string>;
44
+ selectedPath?: string;
45
+ onSelect?: (path: string) => void;
46
+ onExpandedChange?: (expanded: Set<string>) => void;
47
+ };
48
+
49
+ export const FileTree = ({
50
+ expanded: controlledExpanded,
51
+ defaultExpanded = new Set(),
52
+ selectedPath,
53
+ onSelect,
54
+ onExpandedChange,
55
+ className,
56
+ children,
57
+ ...props
58
+ }: FileTreeProps) => {
59
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
60
+ const expandedPaths = controlledExpanded ?? internalExpanded;
61
+
62
+ const togglePath = useCallback(
63
+ (path: string) => {
64
+ const newExpanded = new Set(expandedPaths);
65
+ if (newExpanded.has(path)) {
66
+ newExpanded.delete(path);
67
+ } else {
68
+ newExpanded.add(path);
69
+ }
70
+ setInternalExpanded(newExpanded);
71
+ onExpandedChange?.(newExpanded);
72
+ },
73
+ [expandedPaths, onExpandedChange]
74
+ );
75
+
76
+ const contextValue = useMemo(
77
+ () => ({ expandedPaths, onSelect, selectedPath, togglePath }),
78
+ [expandedPaths, onSelect, selectedPath, togglePath]
79
+ );
80
+
81
+ return (
82
+ <FileTreeContext.Provider value={contextValue}>
83
+ <div
84
+ className={cn(
85
+ "rounded-lg border bg-background font-mono text-sm",
86
+ className
87
+ )}
88
+ role="tree"
89
+ {...props}
90
+ >
91
+ <div className="p-2">{children}</div>
92
+ </div>
93
+ </FileTreeContext.Provider>
94
+ );
95
+ };
96
+
97
+ export type FileTreeIconProps = HTMLAttributes<HTMLSpanElement>;
98
+
99
+ export const FileTreeIcon = ({
100
+ className,
101
+ children,
102
+ ...props
103
+ }: FileTreeIconProps) => (
104
+ <span className={cn("shrink-0", className)} {...props}>
105
+ {children}
106
+ </span>
107
+ );
108
+
109
+ export type FileTreeNameProps = HTMLAttributes<HTMLSpanElement>;
110
+
111
+ export const FileTreeName = ({
112
+ className,
113
+ children,
114
+ ...props
115
+ }: FileTreeNameProps) => (
116
+ <span className={cn("truncate", className)} {...props}>
117
+ {children}
118
+ </span>
119
+ );
120
+
121
+ interface FileTreeFolderContextType {
122
+ path: string;
123
+ name: string;
124
+ isExpanded: boolean;
125
+ }
126
+
127
+ const FileTreeFolderContext = createContext<FileTreeFolderContextType>({
128
+ isExpanded: false,
129
+ name: "",
130
+ path: "",
131
+ });
132
+
133
+ export type FileTreeFolderProps = HTMLAttributes<HTMLDivElement> & {
134
+ path: string;
135
+ name: string;
136
+ };
137
+
138
+ export const FileTreeFolder = ({
139
+ path,
140
+ name,
141
+ className,
142
+ children,
143
+ ...props
144
+ }: FileTreeFolderProps) => {
145
+ const { expandedPaths, togglePath, selectedPath, onSelect } =
146
+ useContext(FileTreeContext);
147
+ const isExpanded = expandedPaths.has(path);
148
+ const isSelected = selectedPath === path;
149
+
150
+ const handleOpenChange = useCallback(() => {
151
+ togglePath(path);
152
+ }, [togglePath, path]);
153
+
154
+ const handleSelect = useCallback(() => {
155
+ onSelect?.(path);
156
+ }, [onSelect, path]);
157
+
158
+ const folderContextValue = useMemo(
159
+ () => ({ isExpanded, name, path }),
160
+ [isExpanded, name, path]
161
+ );
162
+
163
+ return (
164
+ <FileTreeFolderContext.Provider value={folderContextValue}>
165
+ <Collapsible onOpenChange={handleOpenChange} open={isExpanded}>
166
+ <div
167
+ className={cn("", className)}
168
+ role="treeitem"
169
+ tabIndex={0}
170
+ {...props}
171
+ >
172
+ <div
173
+ className={cn(
174
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
175
+ isSelected && "bg-muted"
176
+ )}
177
+ >
178
+ <CollapsibleTrigger asChild>
179
+ <button
180
+ className="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0"
181
+ type="button"
182
+ >
183
+ <ChevronRightIcon
184
+ className={cn(
185
+ "size-4 shrink-0 text-muted-foreground transition-transform",
186
+ isExpanded && "rotate-90"
187
+ )}
188
+ />
189
+ </button>
190
+ </CollapsibleTrigger>
191
+ <button
192
+ className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left"
193
+ onClick={handleSelect}
194
+ type="button"
195
+ >
196
+ <FileTreeIcon>
197
+ {isExpanded ? (
198
+ <FolderOpenIcon className="size-4 text-blue-500" />
199
+ ) : (
200
+ <FolderIcon className="size-4 text-blue-500" />
201
+ )}
202
+ </FileTreeIcon>
203
+ <FileTreeName>{name}</FileTreeName>
204
+ </button>
205
+ </div>
206
+ <CollapsibleContent>
207
+ <div className="ml-4 border-l pl-2">{children}</div>
208
+ </CollapsibleContent>
209
+ </div>
210
+ </Collapsible>
211
+ </FileTreeFolderContext.Provider>
212
+ );
213
+ };
214
+
215
+ interface FileTreeFileContextType {
216
+ path: string;
217
+ name: string;
218
+ }
219
+
220
+ const FileTreeFileContext = createContext<FileTreeFileContextType>({
221
+ name: "",
222
+ path: "",
223
+ });
224
+
225
+ export type FileTreeFileProps = HTMLAttributes<HTMLDivElement> & {
226
+ path: string;
227
+ name: string;
228
+ icon?: ReactNode;
229
+ };
230
+
231
+ export const FileTreeFile = ({
232
+ path,
233
+ name,
234
+ icon,
235
+ className,
236
+ children,
237
+ ...props
238
+ }: FileTreeFileProps) => {
239
+ const { selectedPath, onSelect } = useContext(FileTreeContext);
240
+ const isSelected = selectedPath === path;
241
+
242
+ const handleClick = useCallback(() => {
243
+ onSelect?.(path);
244
+ }, [onSelect, path]);
245
+
246
+ const handleKeyDown = useCallback(
247
+ (e: React.KeyboardEvent) => {
248
+ if (e.key === "Enter" || e.key === " ") {
249
+ onSelect?.(path);
250
+ }
251
+ },
252
+ [onSelect, path]
253
+ );
254
+
255
+ const fileContextValue = useMemo(() => ({ name, path }), [name, path]);
256
+
257
+ return (
258
+ <FileTreeFileContext.Provider value={fileContextValue}>
259
+ <div
260
+ className={cn(
261
+ "flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
262
+ isSelected && "bg-muted",
263
+ className
264
+ )}
265
+ onClick={handleClick}
266
+ onKeyDown={handleKeyDown}
267
+ role="treeitem"
268
+ tabIndex={0}
269
+ {...props}
270
+ >
271
+ {children ?? (
272
+ <>
273
+ {/* Spacer for alignment */}
274
+ <span className="size-4 shrink-0" />
275
+ <FileTreeIcon>
276
+ {icon ?? <FileIcon className="size-4 text-muted-foreground" />}
277
+ </FileTreeIcon>
278
+ <FileTreeName>{name}</FileTreeName>
279
+ </>
280
+ )}
281
+ </div>
282
+ </FileTreeFileContext.Provider>
283
+ );
284
+ };
285
+
286
+ export type FileTreeActionsProps = HTMLAttributes<HTMLDivElement>;
287
+
288
+ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
289
+
290
+ export const FileTreeActions = ({
291
+ className,
292
+ children,
293
+ ...props
294
+ }: FileTreeActionsProps) => (
295
+ <div
296
+ className={cn("ml-auto flex items-center gap-1", className)}
297
+ onClick={stopPropagation}
298
+ onKeyDown={stopPropagation}
299
+ role="group"
300
+ {...props}
301
+ >
302
+ {children}
303
+ </div>
304
+ );
@@ -0,0 +1,24 @@
1
+ import { cn } from "../../lib/utils";
2
+ import type { Experimental_GeneratedImage } from "ai";
3
+
4
+ export type ImageProps = Experimental_GeneratedImage & {
5
+ className?: string;
6
+ alt?: string;
7
+ };
8
+
9
+ export const Image = ({
10
+ base64,
11
+ uint8Array: _uint8Array,
12
+ mediaType,
13
+ ...props
14
+ }: ImageProps) => (
15
+ <img
16
+ {...props}
17
+ alt={props.alt}
18
+ className={cn(
19
+ "h-auto max-w-full overflow-hidden rounded-md",
20
+ props.className
21
+ )}
22
+ src={`data:${mediaType};base64,${base64}`}
23
+ />
24
+ );
@@ -0,0 +1,51 @@
1
+ // Barrel — re-export every AI SDK Element in this directory.
2
+ // Add a line when you add a component (keep sorted).
3
+
4
+ export * from "./agent";
5
+ export * from "./artifact";
6
+ export * from "./attachments";
7
+ export * from "./audio-player";
8
+ export * from "./canvas";
9
+ export * from "./chain-of-thought";
10
+ export * from "./checkpoint";
11
+ export * from "./code-block";
12
+ export * from "./commit";
13
+ export * from "./confirmation";
14
+ export * from "./connection";
15
+ export * from "./context";
16
+ export * from "./controls";
17
+ export * from "./conversation";
18
+ export * from "./edge";
19
+ export * from "./environment-variables";
20
+ export * from "./file-tree";
21
+ export * from "./image";
22
+ export * from "./inline-citation";
23
+ export * from "./jsx-preview";
24
+ export * from "./message";
25
+ export * from "./mic-selector";
26
+ export * from "./model-selector";
27
+ export * from "./node";
28
+ export * from "./open-in-chat";
29
+ export * from "./package-info";
30
+ export * from "./panel";
31
+ export * from "./persona";
32
+ export * from "./plan";
33
+ export * from "./prompt-input";
34
+ export * from "./queue";
35
+ export * from "./reasoning";
36
+ export * from "./sandbox";
37
+ export * from "./schema-display";
38
+ export * from "./shimmer";
39
+ export * from "./snippet";
40
+ export * from "./sources";
41
+ export * from "./speech-input";
42
+ export * from "./stack-trace";
43
+ export * from "./suggestion";
44
+ export * from "./task";
45
+ export * from "./terminal";
46
+ export * from "./test-results";
47
+ export * from "./tool";
48
+ export * from "./toolbar";
49
+ export * from "./transcription";
50
+ export * from "./voice-selector";
51
+ export * from "./web-preview";
@@ -0,0 +1,296 @@
1
+ "use client";
2
+
3
+ import { Badge } from "../ui/badge";
4
+ import type { CarouselApi } from "../ui/carousel";
5
+ import {
6
+ Carousel,
7
+ CarouselContent,
8
+ CarouselItem,
9
+ } from "../ui/carousel";
10
+ import {
11
+ HoverCard,
12
+ HoverCardContent,
13
+ HoverCardTrigger,
14
+ } from "../ui/hover-card";
15
+ import { cn } from "../../lib/utils";
16
+ import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
17
+ import type { ComponentProps } from "react";
18
+ import {
19
+ createContext,
20
+ useCallback,
21
+ useContext,
22
+ useEffect,
23
+ useState,
24
+ } from "react";
25
+
26
+ export type InlineCitationProps = ComponentProps<"span">;
27
+
28
+ export const InlineCitation = ({
29
+ className,
30
+ ...props
31
+ }: InlineCitationProps) => (
32
+ <span
33
+ className={cn("group inline items-center gap-1", className)}
34
+ {...props}
35
+ />
36
+ );
37
+
38
+ export type InlineCitationTextProps = ComponentProps<"span">;
39
+
40
+ export const InlineCitationText = ({
41
+ className,
42
+ ...props
43
+ }: InlineCitationTextProps) => (
44
+ <span
45
+ className={cn("transition-colors group-hover:bg-accent", className)}
46
+ {...props}
47
+ />
48
+ );
49
+
50
+ export type InlineCitationCardProps = ComponentProps<typeof HoverCard>;
51
+
52
+ export const InlineCitationCard = (props: InlineCitationCardProps) => (
53
+ <HoverCard closeDelay={0} openDelay={0} {...props} />
54
+ );
55
+
56
+ export type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & {
57
+ sources: string[];
58
+ };
59
+
60
+ export const InlineCitationCardTrigger = ({
61
+ sources,
62
+ className,
63
+ ...props
64
+ }: InlineCitationCardTriggerProps) => (
65
+ <HoverCardTrigger asChild>
66
+ <Badge
67
+ className={cn("ml-1 rounded-full", className)}
68
+ variant="secondary"
69
+ {...props}
70
+ >
71
+ {sources[0] ? (
72
+ <>
73
+ {new URL(sources[0]).hostname}{" "}
74
+ {sources.length > 1 && `+${sources.length - 1}`}
75
+ </>
76
+ ) : (
77
+ "unknown"
78
+ )}
79
+ </Badge>
80
+ </HoverCardTrigger>
81
+ );
82
+
83
+ export type InlineCitationCardBodyProps = ComponentProps<"div">;
84
+
85
+ export const InlineCitationCardBody = ({
86
+ className,
87
+ ...props
88
+ }: InlineCitationCardBodyProps) => (
89
+ <HoverCardContent className={cn("relative w-80 p-0", className)} {...props} />
90
+ );
91
+
92
+ const CarouselApiContext = createContext<CarouselApi | undefined>(undefined);
93
+
94
+ const useCarouselApi = () => {
95
+ const context = useContext(CarouselApiContext);
96
+ return context;
97
+ };
98
+
99
+ export type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;
100
+
101
+ export const InlineCitationCarousel = ({
102
+ className,
103
+ children,
104
+ ...props
105
+ }: InlineCitationCarouselProps) => {
106
+ const [api, setApi] = useState<CarouselApi>();
107
+
108
+ return (
109
+ <CarouselApiContext.Provider value={api}>
110
+ <Carousel className={cn("w-full", className)} setApi={setApi} {...props}>
111
+ {children}
112
+ </Carousel>
113
+ </CarouselApiContext.Provider>
114
+ );
115
+ };
116
+
117
+ export type InlineCitationCarouselContentProps = ComponentProps<"div">;
118
+
119
+ export const InlineCitationCarouselContent = (
120
+ props: InlineCitationCarouselContentProps
121
+ ) => <CarouselContent {...props} />;
122
+
123
+ export type InlineCitationCarouselItemProps = ComponentProps<"div">;
124
+
125
+ export const InlineCitationCarouselItem = ({
126
+ className,
127
+ ...props
128
+ }: InlineCitationCarouselItemProps) => (
129
+ <CarouselItem
130
+ className={cn("w-full space-y-2 p-4 pl-8", className)}
131
+ {...props}
132
+ />
133
+ );
134
+
135
+ export type InlineCitationCarouselHeaderProps = ComponentProps<"div">;
136
+
137
+ export const InlineCitationCarouselHeader = ({
138
+ className,
139
+ ...props
140
+ }: InlineCitationCarouselHeaderProps) => (
141
+ <div
142
+ className={cn(
143
+ "flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2",
144
+ className
145
+ )}
146
+ {...props}
147
+ />
148
+ );
149
+
150
+ export type InlineCitationCarouselIndexProps = ComponentProps<"div">;
151
+
152
+ export const InlineCitationCarouselIndex = ({
153
+ children,
154
+ className,
155
+ ...props
156
+ }: InlineCitationCarouselIndexProps) => {
157
+ const api = useCarouselApi();
158
+ const [current, setCurrent] = useState(0);
159
+ const [count, setCount] = useState(0);
160
+
161
+ const syncState = useCallback(() => {
162
+ if (!api) {
163
+ return;
164
+ }
165
+ setCount(api.scrollSnapList().length);
166
+ setCurrent(api.selectedScrollSnap() + 1);
167
+ }, [api]);
168
+
169
+ useEffect(() => {
170
+ if (!api) {
171
+ return;
172
+ }
173
+
174
+ syncState();
175
+
176
+ api.on("select", syncState);
177
+
178
+ return () => {
179
+ api.off("select", syncState);
180
+ };
181
+ }, [api, syncState]);
182
+
183
+ return (
184
+ <div
185
+ className={cn(
186
+ "flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground text-xs",
187
+ className
188
+ )}
189
+ {...props}
190
+ >
191
+ {children ?? `${current}/${count}`}
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export type InlineCitationCarouselPrevProps = ComponentProps<"button">;
197
+
198
+ export const InlineCitationCarouselPrev = ({
199
+ className,
200
+ ...props
201
+ }: InlineCitationCarouselPrevProps) => {
202
+ const api = useCarouselApi();
203
+
204
+ const handleClick = useCallback(() => {
205
+ if (api) {
206
+ api.scrollPrev();
207
+ }
208
+ }, [api]);
209
+
210
+ return (
211
+ <button
212
+ aria-label="Previous"
213
+ className={cn("shrink-0", className)}
214
+ onClick={handleClick}
215
+ type="button"
216
+ {...props}
217
+ >
218
+ <ArrowLeftIcon className="size-4 text-muted-foreground" />
219
+ </button>
220
+ );
221
+ };
222
+
223
+ export type InlineCitationCarouselNextProps = ComponentProps<"button">;
224
+
225
+ export const InlineCitationCarouselNext = ({
226
+ className,
227
+ ...props
228
+ }: InlineCitationCarouselNextProps) => {
229
+ const api = useCarouselApi();
230
+
231
+ const handleClick = useCallback(() => {
232
+ if (api) {
233
+ api.scrollNext();
234
+ }
235
+ }, [api]);
236
+
237
+ return (
238
+ <button
239
+ aria-label="Next"
240
+ className={cn("shrink-0", className)}
241
+ onClick={handleClick}
242
+ type="button"
243
+ {...props}
244
+ >
245
+ <ArrowRightIcon className="size-4 text-muted-foreground" />
246
+ </button>
247
+ );
248
+ };
249
+
250
+ export type InlineCitationSourceProps = ComponentProps<"div"> & {
251
+ title?: string;
252
+ url?: string;
253
+ description?: string;
254
+ };
255
+
256
+ export const InlineCitationSource = ({
257
+ title,
258
+ url,
259
+ description,
260
+ className,
261
+ children,
262
+ ...props
263
+ }: InlineCitationSourceProps) => (
264
+ <div className={cn("space-y-1", className)} {...props}>
265
+ {title && (
266
+ <h4 className="truncate font-medium text-sm leading-tight">{title}</h4>
267
+ )}
268
+ {url && (
269
+ <p className="truncate break-all text-muted-foreground text-xs">{url}</p>
270
+ )}
271
+ {description && (
272
+ <p className="line-clamp-3 text-muted-foreground text-sm leading-relaxed">
273
+ {description}
274
+ </p>
275
+ )}
276
+ {children}
277
+ </div>
278
+ );
279
+
280
+ export type InlineCitationQuoteProps = ComponentProps<"blockquote">;
281
+
282
+ export const InlineCitationQuote = ({
283
+ children,
284
+ className,
285
+ ...props
286
+ }: InlineCitationQuoteProps) => (
287
+ <blockquote
288
+ className={cn(
289
+ "border-muted border-l-2 pl-3 text-muted-foreground text-sm italic",
290
+ className
291
+ )}
292
+ {...props}
293
+ >
294
+ {children}
295
+ </blockquote>
296
+ );