@elabs-ai/components-ai 4.0.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 (180) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/_audio-player-media-chrome-KA5DY54G.js +81 -0
  4. package/dist/_audio-player-media-chrome-KA5DY54G.js.map +1 -0
  5. package/dist/_flow-boundary-D63PJ65S.js +186 -0
  6. package/dist/_flow-boundary-D63PJ65S.js.map +1 -0
  7. package/dist/_interactive-terminal-xterm-ATJ5EW3G.js +11 -0
  8. package/dist/_interactive-terminal-xterm-ATJ5EW3G.js.map +1 -0
  9. package/dist/_persona-rive-RFR2EUWP.js +164 -0
  10. package/dist/_persona-rive-RFR2EUWP.js.map +1 -0
  11. package/dist/index.d.ts +4079 -0
  12. package/dist/index.js +11770 -0
  13. package/dist/index.js.map +1 -0
  14. package/package.json +87 -0
  15. package/src/_audio-player-media-chrome.tsx +146 -0
  16. package/src/_chat-shell-rail.tsx +18 -0
  17. package/src/_code-block-theme.test.ts +134 -0
  18. package/src/_code-block-theme.ts +184 -0
  19. package/src/_flow-boundary.tsx +237 -0
  20. package/src/_flow-lazy.ts +34 -0
  21. package/src/_interactive-terminal-xterm.ts +32 -0
  22. package/src/_lazy-mermaid.test.ts +101 -0
  23. package/src/_lazy-mermaid.ts +117 -0
  24. package/src/_persona-rive.tsx +252 -0
  25. package/src/_streamdown-i18n.ts +119 -0
  26. package/src/agent-timeline.stories.tsx +66 -0
  27. package/src/agent-timeline.test.tsx +74 -0
  28. package/src/agent-timeline.tsx +106 -0
  29. package/src/agent.tsx +113 -0
  30. package/src/agentic-workspace.stories.tsx +1099 -0
  31. package/src/artifact.stories.tsx +62 -0
  32. package/src/artifact.test.tsx +112 -0
  33. package/src/artifact.tsx +167 -0
  34. package/src/asset-preview.stories.tsx +100 -0
  35. package/src/asset-preview.test.tsx +126 -0
  36. package/src/asset-preview.tsx +218 -0
  37. package/src/attachments.tsx +393 -0
  38. package/src/audio-player.stories.tsx +99 -0
  39. package/src/audio-player.tsx +170 -0
  40. package/src/blocks-ai-composer.stories.tsx +83 -0
  41. package/src/canvas.stories.tsx +142 -0
  42. package/src/canvas.tsx +29 -0
  43. package/src/chain-of-thought.tsx +193 -0
  44. package/src/chat-greeting.stories.tsx +96 -0
  45. package/src/chat-greeting.test.tsx +91 -0
  46. package/src/chat-greeting.tsx +91 -0
  47. package/src/chat-shell.stories.tsx +123 -0
  48. package/src/chat-shell.test.tsx +113 -0
  49. package/src/chat-shell.tsx +97 -0
  50. package/src/chat.stories.tsx +99 -0
  51. package/src/checkpoint.tsx +54 -0
  52. package/src/code-block.stories.tsx +81 -0
  53. package/src/code-block.test.tsx +191 -0
  54. package/src/code-block.tsx +673 -0
  55. package/src/commit.tsx +343 -0
  56. package/src/composer.stories.tsx +188 -0
  57. package/src/composer.test.tsx +182 -0
  58. package/src/composer.tsx +202 -0
  59. package/src/confirmation.stories.tsx +61 -0
  60. package/src/confirmation.test.tsx +136 -0
  61. package/src/confirmation.tsx +273 -0
  62. package/src/connection.tsx +23 -0
  63. package/src/context-panel.stories.tsx +137 -0
  64. package/src/context-panel.test.tsx +198 -0
  65. package/src/context-panel.tsx +609 -0
  66. package/src/context.stories.tsx +16 -0
  67. package/src/context.tsx +363 -0
  68. package/src/controls.tsx +20 -0
  69. package/src/conversation.stories.tsx +26 -0
  70. package/src/conversation.test.tsx +123 -0
  71. package/src/conversation.tsx +143 -0
  72. package/src/dark-theme-variant.test.ts +47 -0
  73. package/src/edge.tsx +33 -0
  74. package/src/environment-variables.tsx +304 -0
  75. package/src/file-tree.stories.tsx +55 -0
  76. package/src/file-tree.test.tsx +101 -0
  77. package/src/file-tree.tsx +390 -0
  78. package/src/gallery.stories.tsx +182 -0
  79. package/src/gallery.test.tsx +150 -0
  80. package/src/gallery.tsx +737 -0
  81. package/src/grouped-parts.stories.tsx +104 -0
  82. package/src/grouped-parts.test.tsx +167 -0
  83. package/src/grouped-parts.tsx +247 -0
  84. package/src/image.stories.tsx +67 -0
  85. package/src/image.test.tsx +105 -0
  86. package/src/image.tsx +115 -0
  87. package/src/index.ts +93 -0
  88. package/src/inline-citation.stories.tsx +91 -0
  89. package/src/inline-citation.test.tsx +96 -0
  90. package/src/inline-citation.tsx +314 -0
  91. package/src/interactive-terminal.stories.tsx +165 -0
  92. package/src/interactive-terminal.test.tsx +448 -0
  93. package/src/interactive-terminal.tsx +444 -0
  94. package/src/jsx-preview.stories.tsx +265 -0
  95. package/src/jsx-preview.test.tsx +75 -0
  96. package/src/jsx-preview.tsx +409 -0
  97. package/src/markdown-view.stories.tsx +57 -0
  98. package/src/markdown-view.test.tsx +36 -0
  99. package/src/markdown-view.tsx +113 -0
  100. package/src/message-edit.stories.tsx +150 -0
  101. package/src/message-edit.test.tsx +115 -0
  102. package/src/message-edit.tsx +336 -0
  103. package/src/message-feedback.stories.tsx +53 -0
  104. package/src/message-feedback.test.tsx +62 -0
  105. package/src/message-feedback.tsx +117 -0
  106. package/src/message-form-spec.ts +349 -0
  107. package/src/message-form.stories.tsx +182 -0
  108. package/src/message-form.test.tsx +227 -0
  109. package/src/message-form.tsx +845 -0
  110. package/src/message-table-spec.ts +281 -0
  111. package/src/message-table.stories.tsx +176 -0
  112. package/src/message-table.test.tsx +144 -0
  113. package/src/message-table.tsx +366 -0
  114. package/src/message.stories.tsx +262 -0
  115. package/src/message.test.tsx +508 -0
  116. package/src/message.tsx +619 -0
  117. package/src/mic-selector.tsx +339 -0
  118. package/src/microcopy.test.tsx +123 -0
  119. package/src/model-selector.stories.tsx +73 -0
  120. package/src/model-selector.test.tsx +55 -0
  121. package/src/model-selector.tsx +225 -0
  122. package/src/motion-config.tsx +49 -0
  123. package/src/node.tsx +71 -0
  124. package/src/open-in-chat.tsx +340 -0
  125. package/src/package-info.tsx +205 -0
  126. package/src/panel.tsx +20 -0
  127. package/src/part-groups.ts +258 -0
  128. package/src/persona-sources.ts +59 -0
  129. package/src/persona.stories.tsx +111 -0
  130. package/src/persona.test.tsx +67 -0
  131. package/src/persona.tsx +148 -0
  132. package/src/plan.tsx +135 -0
  133. package/src/prompt-input.stories.tsx +261 -0
  134. package/src/prompt-input.test.tsx +392 -0
  135. package/src/prompt-input.tsx +1668 -0
  136. package/src/queue.tsx +237 -0
  137. package/src/reasoning.stories.tsx +81 -0
  138. package/src/reasoning.test.tsx +50 -0
  139. package/src/reasoning.tsx +229 -0
  140. package/src/sandbox.stories.tsx +62 -0
  141. package/src/sandbox.test.tsx +42 -0
  142. package/src/sandbox.tsx +149 -0
  143. package/src/schema-display.tsx +412 -0
  144. package/src/selection-toolbar.stories.tsx +86 -0
  145. package/src/selection-toolbar.test.tsx +89 -0
  146. package/src/selection-toolbar.tsx +192 -0
  147. package/src/shimmer.stories.tsx +10 -0
  148. package/src/shimmer.tsx +81 -0
  149. package/src/snippet.stories.tsx +19 -0
  150. package/src/snippet.test.tsx +116 -0
  151. package/src/snippet.tsx +138 -0
  152. package/src/sources.stories.tsx +34 -0
  153. package/src/sources.test.tsx +129 -0
  154. package/src/sources.tsx +93 -0
  155. package/src/speech-input.tsx +312 -0
  156. package/src/stack-trace.tsx +479 -0
  157. package/src/streamdown-i18n.test.tsx +163 -0
  158. package/src/suggestion.stories.tsx +75 -0
  159. package/src/suggestion.test.tsx +72 -0
  160. package/src/suggestion.tsx +120 -0
  161. package/src/task.stories.tsx +22 -0
  162. package/src/task.test.tsx +36 -0
  163. package/src/task.tsx +89 -0
  164. package/src/templates-ai-assistant.stories.tsx +142 -0
  165. package/src/terminal.tsx +244 -0
  166. package/src/test-results.stories.tsx +177 -0
  167. package/src/test-results.test.tsx +241 -0
  168. package/src/test-results.tsx +408 -0
  169. package/src/tool-result-card.stories.tsx +143 -0
  170. package/src/tool-result-card.test.tsx +72 -0
  171. package/src/tool-result-card.tsx +86 -0
  172. package/src/tool.stories.tsx +60 -0
  173. package/src/tool.test.tsx +63 -0
  174. package/src/tool.tsx +235 -0
  175. package/src/toolbar.tsx +44 -0
  176. package/src/transcription.tsx +118 -0
  177. package/src/voice-selector.tsx +469 -0
  178. package/src/web-preview.stories.tsx +67 -0
  179. package/src/web-preview.test.tsx +117 -0
  180. package/src/web-preview.tsx +283 -0
@@ -0,0 +1,283 @@
1
+ "use client";
2
+
3
+ import { Button } from "@elabs-ai/components-ui";
4
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@elabs-ai/components-ui";
5
+ import { Input } from "@elabs-ai/components-ui";
6
+ import { Skeleton } from "@elabs-ai/components-ui";
7
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@elabs-ai/components-ui";
8
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
9
+ import { useLocale } from "@elabs-ai/components-ui";
10
+ import { ChevronDownIcon } from "lucide-react";
11
+ import type { ComponentProps, ReactNode } from "react";
12
+ import { createContext, useCallback, useContext, useMemo, useState } from "react";
13
+
14
+ export interface WebPreviewContextValue {
15
+ url: string;
16
+ setUrl: (url: string) => void;
17
+ consoleOpen: boolean;
18
+ setConsoleOpen: (open: boolean) => void;
19
+ }
20
+
21
+ const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
22
+
23
+ const useWebPreview = () => {
24
+ const context = useContext(WebPreviewContext);
25
+ if (!context) {
26
+ throw new Error("WebPreview components must be used within a WebPreview");
27
+ }
28
+ return context;
29
+ };
30
+
31
+ export type WebPreviewProps = ComponentProps<"div"> & {
32
+ defaultUrl?: string;
33
+ onUrlChange?: (url: string) => void;
34
+ };
35
+
36
+ export const WebPreview = ({
37
+ className,
38
+ children,
39
+ defaultUrl = "",
40
+ onUrlChange,
41
+ ...props
42
+ }: WebPreviewProps) => {
43
+ const [url, setUrl] = useState(defaultUrl);
44
+ const [consoleOpen, setConsoleOpen] = useState(false);
45
+
46
+ const handleUrlChange = useCallback(
47
+ (newUrl: string) => {
48
+ setUrl(newUrl);
49
+ onUrlChange?.(newUrl);
50
+ },
51
+ [onUrlChange],
52
+ );
53
+
54
+ const contextValue = useMemo<WebPreviewContextValue>(
55
+ () => ({
56
+ consoleOpen,
57
+ setConsoleOpen,
58
+ setUrl: handleUrlChange,
59
+ url,
60
+ }),
61
+ [consoleOpen, handleUrlChange, url],
62
+ );
63
+
64
+ return (
65
+ <WebPreviewContext.Provider value={contextValue}>
66
+ <div
67
+ className={cn("flex size-full flex-col rounded-lg border bg-card", className)}
68
+ {...props}
69
+ >
70
+ {children}
71
+ </div>
72
+ </WebPreviewContext.Provider>
73
+ );
74
+ };
75
+
76
+ export type WebPreviewNavigationProps = ComponentProps<"div">;
77
+
78
+ export const WebPreviewNavigation = ({
79
+ className,
80
+ children,
81
+ ...props
82
+ }: WebPreviewNavigationProps) => (
83
+ <div className={cn("flex items-center gap-1 border-b p-2", className)} {...props}>
84
+ {children}
85
+ </div>
86
+ );
87
+
88
+ export type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
89
+ tooltip?: string;
90
+ };
91
+
92
+ export const WebPreviewNavigationButton = ({
93
+ onClick,
94
+ disabled,
95
+ tooltip,
96
+ children,
97
+ ...props
98
+ }: WebPreviewNavigationButtonProps) => (
99
+ <TooltipProvider>
100
+ <Tooltip>
101
+ <TooltipTrigger asChild>
102
+ <Button
103
+ // These buttons are icon-only, and a Radix tooltip only contributes
104
+ // `aria-describedby` while it is OPEN — so at rest the control had no
105
+ // accessible name at all and axe's `button-name` red every story that
106
+ // mounted one (#386). The tooltip text IS the name; spreading
107
+ // `...props` afterwards keeps an explicit `aria-label` winning, and a
108
+ // button that ships real text children keeps its own content as the
109
+ // name because `tooltip` is then undefined. Mirrors `MessageAction`'s
110
+ // `sr-only` label in message.tsx.
111
+ aria-label={tooltip}
112
+ className="h-8 w-8 p-0 hover:text-foreground"
113
+ disabled={disabled}
114
+ onClick={onClick}
115
+ size="sm"
116
+ variant="ghost"
117
+ {...props}
118
+ >
119
+ {children}
120
+ </Button>
121
+ </TooltipTrigger>
122
+ <TooltipContent>
123
+ <p>{tooltip}</p>
124
+ </TooltipContent>
125
+ </Tooltip>
126
+ </TooltipProvider>
127
+ );
128
+
129
+ export type WebPreviewUrlProps = ComponentProps<typeof Input>;
130
+
131
+ export const WebPreviewUrl = ({ value, onChange, onKeyDown, ...props }: WebPreviewUrlProps) => {
132
+ const { t } = useLocale();
133
+ const { url, setUrl } = useWebPreview();
134
+ const [prevUrl, setPrevUrl] = useState(url);
135
+ const [inputValue, setInputValue] = useState(url);
136
+
137
+ // Sync input value with context URL when it changes externally (derived state pattern)
138
+ if (url !== prevUrl) {
139
+ setPrevUrl(url);
140
+ setInputValue(url);
141
+ }
142
+
143
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
144
+ setInputValue(event.target.value);
145
+ onChange?.(event);
146
+ };
147
+
148
+ const handleKeyDown = useCallback(
149
+ (event: React.KeyboardEvent<HTMLInputElement>) => {
150
+ if (event.key === "Enter") {
151
+ const target = event.target as HTMLInputElement;
152
+ setUrl(target.value);
153
+ }
154
+ onKeyDown?.(event);
155
+ },
156
+ [setUrl, onKeyDown],
157
+ );
158
+
159
+ return (
160
+ <Input
161
+ className="h-8 flex-1 text-sm"
162
+ onChange={onChange ?? handleChange}
163
+ onKeyDown={handleKeyDown}
164
+ placeholder={t("ai.webPreview.urlPlaceholder")}
165
+ value={value ?? inputValue}
166
+ {...props}
167
+ />
168
+ );
169
+ };
170
+
171
+ export type WebPreviewBodyProps = Omit<ComponentProps<"iframe">, "loading"> & {
172
+ /**
173
+ * Not-ready placeholder over the iframe (loading-states.md `loading`
174
+ * signal). Pass `true` for the default layout-shaped Skeleton filling the
175
+ * iframe box, or a ReactNode to override with custom placeholder content.
176
+ * `false`/`undefined` (default) renders nothing. Shadows (rather than
177
+ * forwards) the native `<iframe loading="eager"|"lazy">` attribute — this
178
+ * component's box is always fully reserved, so lazy-loading the iframe
179
+ * itself is not a use case here.
180
+ * @default false
181
+ */
182
+ loading?: boolean | ReactNode;
183
+ };
184
+
185
+ export const WebPreviewBody = ({ className, loading, src, ...props }: WebPreviewBodyProps) => {
186
+ const { url } = useWebPreview();
187
+ const { t } = useLocale();
188
+
189
+ return (
190
+ <div className="relative flex-1">
191
+ <iframe
192
+ className={cn("size-full", className)}
193
+ // oxlint-disable-next-line eslint-plugin-react(iframe-missing-sandbox)
194
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
195
+ src={(src ?? url) || undefined}
196
+ title="Preview"
197
+ {...props}
198
+ />
199
+ {loading === true ? (
200
+ <div
201
+ className="absolute inset-0 flex flex-col gap-2 bg-card p-4"
202
+ role="status"
203
+ aria-live="polite"
204
+ >
205
+ <span className="sr-only">{t("loading")}</span>
206
+ <Skeleton className="h-4 w-1/3" />
207
+ <Skeleton className="flex-1" />
208
+ </div>
209
+ ) : (
210
+ loading
211
+ )}
212
+ </div>
213
+ );
214
+ };
215
+
216
+ export type WebPreviewConsoleProps = ComponentProps<"div"> & {
217
+ logs?: {
218
+ level: "log" | "warn" | "error";
219
+ message: string;
220
+ timestamp: Date;
221
+ }[];
222
+ };
223
+
224
+ export const WebPreviewConsole = ({
225
+ className,
226
+ logs = [],
227
+ children,
228
+ ...props
229
+ }: WebPreviewConsoleProps) => {
230
+ const { consoleOpen, setConsoleOpen } = useWebPreview();
231
+
232
+ return (
233
+ <Collapsible
234
+ className={cn("border-t bg-muted/50 font-mono text-sm", className)}
235
+ onOpenChange={setConsoleOpen}
236
+ open={consoleOpen}
237
+ {...props}
238
+ >
239
+ <CollapsibleTrigger asChild>
240
+ <Button
241
+ className="flex w-full items-center justify-between p-4 text-start font-medium hover:bg-muted/50"
242
+ variant="ghost"
243
+ >
244
+ Console
245
+ <ChevronDownIcon
246
+ className={cn(
247
+ "h-4 w-4 transition-transform duration-base ease-standard",
248
+ consoleOpen && "rotate-180",
249
+ )}
250
+ />
251
+ </Button>
252
+ </CollapsibleTrigger>
253
+ <CollapsibleContent
254
+ className={cn(
255
+ "px-4 pb-4",
256
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
257
+ )}
258
+ >
259
+ <div className="max-h-48 space-y-1 overflow-y-auto">
260
+ {logs.length === 0 ? (
261
+ <p className="text-muted-foreground">No console output</p>
262
+ ) : (
263
+ logs.map((log) => (
264
+ <div
265
+ className={cn(
266
+ "text-xs",
267
+ log.level === "error" && "text-destructive",
268
+ log.level === "warn" && "text-yellow-600",
269
+ log.level === "log" && "text-foreground",
270
+ )}
271
+ key={`${log.timestamp.getTime()}-${log.level}-${log.message}`}
272
+ >
273
+ <span className="text-muted-foreground">{log.timestamp.toLocaleTimeString()}</span>{" "}
274
+ {log.message}
275
+ </div>
276
+ ))
277
+ )}
278
+ {children}
279
+ </div>
280
+ </CollapsibleContent>
281
+ </Collapsible>
282
+ );
283
+ };