@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,150 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { Gallery, type GalleryImage } from "./gallery";
5
+
6
+ afterEach(cleanup);
7
+
8
+ function img(n: number, extra?: Partial<GalleryImage>): GalleryImage {
9
+ return {
10
+ src: `https://images.test/${n}.png`,
11
+ alt: `Photo ${n}`,
12
+ title: `Title ${n}`,
13
+ ...extra,
14
+ };
15
+ }
16
+
17
+ const list = (count: number): GalleryImage[] => Array.from({ length: count }, (_, i) => img(i + 1));
18
+
19
+ describe("Gallery — layout by image count", () => {
20
+ it("renders an empty state for no images", () => {
21
+ render(<Gallery images={[]} />);
22
+ expect(screen.getByText(/no images/i)).toBeInTheDocument();
23
+ });
24
+
25
+ it("renders a single framed image with an always-on expand button (no grid)", () => {
26
+ render(<Gallery images={[img(1)]} />);
27
+ expect(screen.getByRole("button", { name: /expand image/i })).toBeInTheDocument();
28
+ // The standalone image exposes its alt; grid thumbnails (decorative) would not.
29
+ expect(screen.getByRole("img", { name: "Photo 1" })).toBeInTheDocument();
30
+ });
31
+
32
+ it("renders a grid of tile buttons for multiple images", () => {
33
+ render(<Gallery images={list(3)} />);
34
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(3);
35
+ // No standalone expand button in grid mode (tiles are the expand affordance).
36
+ expect(screen.queryByRole("button", { name: /expand image/i })).not.toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ describe("Gallery — '+N more' overflow math", () => {
41
+ it("collapses the overflow into a '+N more' tile past maxVisible (default 5)", () => {
42
+ render(<Gallery images={list(12)} />);
43
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(5);
44
+ expect(screen.getByText(/\+7 more/)).toBeInTheDocument(); // 12 − 5
45
+ expect(screen.getByRole("button", { name: /show all 12 images/i })).toBeInTheDocument();
46
+ });
47
+
48
+ it("shows every tile with no overflow at exactly maxVisible", () => {
49
+ render(<Gallery images={list(5)} />);
50
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(5);
51
+ expect(screen.queryByText(/more/i)).not.toBeInTheDocument();
52
+ });
53
+
54
+ it("honours a custom maxVisible", () => {
55
+ render(<Gallery images={list(6)} maxVisible={3} />);
56
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(3);
57
+ expect(screen.getByText(/\+3 more/)).toBeInTheDocument(); // 6 − 3
58
+ });
59
+ });
60
+
61
+ describe("Gallery — lightbox modal", () => {
62
+ it("opens an accessible dialog with the image's metadata on expand", async () => {
63
+ const user = userEvent.setup();
64
+ render(<Gallery images={[img(1, { description: "A scenic render." })]} />);
65
+
66
+ await user.click(screen.getByRole("button", { name: /expand image/i }));
67
+
68
+ const dialog = await screen.findByRole("dialog");
69
+ expect(dialog).toHaveAccessibleName(); // DialogTitle always present (Radix a11y)
70
+ expect(within(dialog).getByText("A scenic render.")).toBeInTheDocument();
71
+ });
72
+
73
+ it("opens the carousel (prev/next) from the '+N more' tile", async () => {
74
+ const user = userEvent.setup();
75
+ render(<Gallery images={list(12)} />);
76
+
77
+ await user.click(screen.getByRole("button", { name: /show all 12 images/i }));
78
+
79
+ const dialog = await screen.findByRole("dialog");
80
+ // True per-slide sync is verified by the Storybook play test in a real
81
+ // browser; Embla cannot measure layout in jsdom. Here we assert the carousel
82
+ // navigation surface mounted and is a named landmark.
83
+ expect(within(dialog).getByRole("region", { name: /image gallery/i })).toBeInTheDocument();
84
+ expect(within(dialog).getByRole("button", { name: /next slide/i })).toBeInTheDocument();
85
+ expect(within(dialog).getByRole("button", { name: /previous slide/i })).toBeInTheDocument();
86
+ });
87
+
88
+ it("shows 'No details' when the active image has no metadata", async () => {
89
+ const user = userEvent.setup();
90
+ render(<Gallery images={[{ src: "https://images.test/x.png", alt: "Just alt" }]} />);
91
+
92
+ await user.click(screen.getByRole("button", { name: /expand image/i }));
93
+
94
+ const dialog = await screen.findByRole("dialog");
95
+ expect(within(dialog).getByText(/no details/i)).toBeInTheDocument();
96
+ });
97
+
98
+ it("renders custom metadata via renderMeta", async () => {
99
+ const user = userEvent.setup();
100
+ render(<Gallery images={[img(7)]} renderMeta={(image) => <p>custom: {image.title}</p>} />);
101
+
102
+ await user.click(screen.getByRole("button", { name: /expand image/i }));
103
+
104
+ const dialog = await screen.findByRole("dialog");
105
+ expect(within(dialog).getByText("custom: Title 7")).toBeInTheDocument();
106
+ });
107
+ });
108
+
109
+ describe("Gallery — loading & partial sets (loading-states.md)", () => {
110
+ it("renders a skeleton grid (not the empty state) while loading from empty", () => {
111
+ render(<Gallery images={[]} loading expectedCount={4} />);
112
+ // The persistent live region carries the "Loading images" announcement text.
113
+ expect(screen.getByText(/loading images/i)).toBeInTheDocument();
114
+ expect(screen.queryByText(/no images/i)).not.toBeInTheDocument();
115
+ });
116
+
117
+ it("fills the gap with skeleton tiles while a partial set arrives (no '+N more')", () => {
118
+ render(<Gallery images={list(2)} expectedCount={5} />);
119
+ // The arrived tiles render...
120
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(2);
121
+ // ...and the overflow collapse does NOT kick in while the set is still arriving.
122
+ expect(screen.queryByText(/more/i)).not.toBeInTheDocument();
123
+ });
124
+
125
+ it("returns to normal overflow once the expected set has fully arrived", () => {
126
+ render(<Gallery images={list(6)} expectedCount={6} />);
127
+ expect(screen.getAllByRole("button", { name: /^Title \d+$/ })).toHaveLength(5);
128
+ expect(screen.getByText(/\+1 more/)).toBeInTheDocument();
129
+ });
130
+
131
+ it("shows a per-image skeleton until the image loads, then removes it", () => {
132
+ const { container } = render(<Gallery images={[img(1)]} />);
133
+ // jsdom never fires `load`, so the skeleton overlay is present initially.
134
+ expect(container.querySelector(".animate-pulse")).toBeInTheDocument();
135
+ fireEvent.load(screen.getByRole("img", { name: "Photo 1" }));
136
+ expect(container.querySelector(".animate-pulse")).not.toBeInTheDocument();
137
+ });
138
+ });
139
+
140
+ describe("Gallery — download affordance", () => {
141
+ it("exposes a labelled download button (present in the DOM at rest, revealed on hover)", () => {
142
+ render(<Gallery images={[img(1)]} />);
143
+ expect(screen.getByRole("button", { name: /download image/i })).toBeInTheDocument();
144
+ });
145
+
146
+ it("removes every download affordance when hideDownload is set", () => {
147
+ render(<Gallery images={list(3)} hideDownload />);
148
+ expect(screen.queryByRole("button", { name: /download image/i })).not.toBeInTheDocument();
149
+ });
150
+ });