@elizaos/client 1.5.5-alpha.10

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. package/vite.config.ts +102 -0
@@ -0,0 +1,333 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import {
6
+ AlertDialog,
7
+ AlertDialogAction,
8
+ AlertDialogCancel,
9
+ AlertDialogContent,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogHeader,
13
+ AlertDialogTitle,
14
+ AlertDialogTrigger,
15
+ } from './alert-dialog';
16
+ import { Button } from './button';
17
+
18
+ describe('AlertDialog Component', () => {
19
+ it('renders basic alert dialog', () => {
20
+ cy.mount(
21
+ <AlertDialog>
22
+ <AlertDialogTrigger asChild>
23
+ <Button variant="destructive">Delete Item</Button>
24
+ </AlertDialogTrigger>
25
+ <AlertDialogContent>
26
+ <AlertDialogHeader>
27
+ <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
28
+ <AlertDialogDescription>
29
+ This action cannot be undone. This will permanently delete your account and remove
30
+ your data from our servers.
31
+ </AlertDialogDescription>
32
+ </AlertDialogHeader>
33
+ <AlertDialogFooter>
34
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
35
+ <AlertDialogAction>Continue</AlertDialogAction>
36
+ </AlertDialogFooter>
37
+ </AlertDialogContent>
38
+ </AlertDialog>
39
+ );
40
+
41
+ cy.contains('button', 'Delete Item').should('be.visible');
42
+ cy.contains('Are you absolutely sure?').should('not.exist');
43
+
44
+ // Open dialog
45
+ cy.contains('button', 'Delete Item').click();
46
+ cy.contains('Are you absolutely sure?').should('be.visible');
47
+ cy.contains('This action cannot be undone').should('be.visible');
48
+ });
49
+
50
+ it('handles action button click', () => {
51
+ const onAction = cy.stub();
52
+
53
+ cy.mount(
54
+ <AlertDialog>
55
+ <AlertDialogTrigger>Delete</AlertDialogTrigger>
56
+ <AlertDialogContent>
57
+ <AlertDialogTitle>Confirm Deletion</AlertDialogTitle>
58
+ <AlertDialogDescription>
59
+ Are you sure you want to delete this item?
60
+ </AlertDialogDescription>
61
+ <AlertDialogFooter>
62
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
63
+ <AlertDialogAction onClick={onAction}>Delete</AlertDialogAction>
64
+ </AlertDialogFooter>
65
+ </AlertDialogContent>
66
+ </AlertDialog>
67
+ );
68
+
69
+ // Open the dialog
70
+ cy.contains('Delete').click();
71
+
72
+ // Wait for dialog to appear and ensure it's visible
73
+ cy.contains('Confirm Deletion').should('be.visible');
74
+
75
+ // Try clicking the specific action button using a more precise selector
76
+ cy.get('[role="alertdialog"]').within(() => {
77
+ cy.contains('button', 'Delete').click({ force: true });
78
+ });
79
+ cy.wrap(onAction).should('have.been.called');
80
+
81
+ // Dialog should close
82
+ cy.contains('Confirm Deletion').should('not.exist');
83
+ });
84
+
85
+ it('handles cancel button click', () => {
86
+ const onCancel = cy.stub();
87
+
88
+ cy.mount(
89
+ <AlertDialog>
90
+ <AlertDialogTrigger>Show Alert</AlertDialogTrigger>
91
+ <AlertDialogContent>
92
+ <AlertDialogTitle>Warning</AlertDialogTitle>
93
+ <AlertDialogDescription>This is a warning message.</AlertDialogDescription>
94
+ <AlertDialogFooter>
95
+ <AlertDialogCancel onClick={onCancel}>Cancel</AlertDialogCancel>
96
+ <AlertDialogAction>OK</AlertDialogAction>
97
+ </AlertDialogFooter>
98
+ </AlertDialogContent>
99
+ </AlertDialog>
100
+ );
101
+
102
+ cy.contains('Show Alert').click();
103
+ cy.contains('button', 'Cancel').click({ force: true });
104
+ cy.wrap(onCancel).should('have.been.called');
105
+
106
+ // Dialog should close
107
+ cy.contains('Warning').should('not.exist');
108
+ });
109
+
110
+ it('works as controlled component', () => {
111
+ const TestComponent = () => {
112
+ const [open, setOpen] = React.useState(false);
113
+ const [confirmed, setConfirmed] = React.useState(false);
114
+
115
+ return (
116
+ <div>
117
+ <button onClick={() => setOpen(true)}>Open Alert</button>
118
+ <p>Confirmed: {confirmed ? 'Yes' : 'No'}</p>
119
+
120
+ <AlertDialog open={open} onOpenChange={setOpen}>
121
+ <AlertDialogContent>
122
+ <AlertDialogTitle>Confirm Action</AlertDialogTitle>
123
+ <AlertDialogDescription>Do you want to proceed?</AlertDialogDescription>
124
+ <AlertDialogFooter>
125
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
126
+ <AlertDialogAction
127
+ onClick={() => {
128
+ setConfirmed(true);
129
+ setOpen(false);
130
+ }}
131
+ >
132
+ Confirm
133
+ </AlertDialogAction>
134
+ </AlertDialogFooter>
135
+ </AlertDialogContent>
136
+ </AlertDialog>
137
+ </div>
138
+ );
139
+ };
140
+
141
+ cy.mount(<TestComponent />);
142
+
143
+ cy.contains('Confirmed: No').should('be.visible');
144
+ cy.contains('Open Alert').click();
145
+ cy.contains('Confirm Action').should('be.visible');
146
+
147
+ cy.contains('button', 'Confirm').click({ force: true });
148
+ cy.contains('Confirmed: Yes').should('be.visible');
149
+ cy.contains('Confirm Action').should('not.exist');
150
+ });
151
+
152
+ it('prevents closing on escape by default', () => {
153
+ cy.mount(
154
+ <AlertDialog>
155
+ <AlertDialogTrigger>Important Action</AlertDialogTrigger>
156
+ <AlertDialogContent>
157
+ <AlertDialogTitle>Critical Decision</AlertDialogTitle>
158
+ <AlertDialogDescription>This requires your explicit action.</AlertDialogDescription>
159
+ <AlertDialogFooter>
160
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
161
+ <AlertDialogAction>Proceed</AlertDialogAction>
162
+ </AlertDialogFooter>
163
+ </AlertDialogContent>
164
+ </AlertDialog>
165
+ );
166
+
167
+ cy.contains('Important Action').click();
168
+ cy.contains('Critical Decision').should('be.visible');
169
+
170
+ // Try to close with ESC
171
+ cy.get('body').type('{esc}');
172
+ cy.contains('Critical Decision').should('be.visible');
173
+ });
174
+
175
+ it('supports custom className', () => {
176
+ cy.mount(
177
+ <AlertDialog>
178
+ <AlertDialogTrigger className="custom-trigger">Open</AlertDialogTrigger>
179
+ <AlertDialogContent className="bg-red-50 custom-content">
180
+ <AlertDialogTitle className="text-red-900">Error</AlertDialogTitle>
181
+ <AlertDialogDescription className="text-red-700">
182
+ Something went wrong!
183
+ </AlertDialogDescription>
184
+ <AlertDialogFooter>
185
+ <AlertDialogAction className="bg-red-600">OK</AlertDialogAction>
186
+ </AlertDialogFooter>
187
+ </AlertDialogContent>
188
+ </AlertDialog>
189
+ );
190
+
191
+ cy.get('.custom-trigger').should('exist');
192
+ cy.contains('Open').click();
193
+ cy.get('.custom-content').should('have.class', 'bg-red-50');
194
+ cy.get('.text-red-900').should('contain', 'Error');
195
+ });
196
+
197
+ it('handles async operations', () => {
198
+ const TestComponent = () => {
199
+ const [loading, setLoading] = React.useState(false);
200
+ const [open, setOpen] = React.useState(false);
201
+
202
+ const handleDelete = async () => {
203
+ setLoading(true);
204
+ // Simulate async operation
205
+ await new Promise((resolve) => setTimeout(resolve, 100));
206
+ setLoading(false);
207
+ setOpen(false);
208
+ };
209
+
210
+ return (
211
+ <AlertDialog open={open} onOpenChange={setOpen}>
212
+ <AlertDialogTrigger asChild>
213
+ <Button>Delete</Button>
214
+ </AlertDialogTrigger>
215
+ <AlertDialogContent>
216
+ <AlertDialogTitle>Delete Item</AlertDialogTitle>
217
+ <AlertDialogDescription>
218
+ Are you sure you want to delete this item?
219
+ </AlertDialogDescription>
220
+ <AlertDialogFooter>
221
+ <AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
222
+ <AlertDialogAction onClick={handleDelete} disabled={loading}>
223
+ {loading ? 'Deleting...' : 'Delete'}
224
+ </AlertDialogAction>
225
+ </AlertDialogFooter>
226
+ </AlertDialogContent>
227
+ </AlertDialog>
228
+ );
229
+ };
230
+
231
+ cy.mount(<TestComponent />);
232
+
233
+ // Open the dialog
234
+ cy.contains('button', 'Delete').click();
235
+
236
+ // Wait for dialog content to appear
237
+ cy.contains('Delete Item').should('be.visible');
238
+
239
+ // Click the action button within the dialog
240
+ cy.get('[role="alertdialog"]').within(() => {
241
+ cy.contains('button', 'Delete').click({ force: true });
242
+ });
243
+ cy.contains('Deleting...').should('be.visible');
244
+ cy.wait(150);
245
+ cy.contains('Delete Item').should('not.exist');
246
+ });
247
+
248
+ it('supports warning variant styling', () => {
249
+ cy.mount(
250
+ <AlertDialog>
251
+ <AlertDialogTrigger>
252
+ <Button variant="outline">Show Warning</Button>
253
+ </AlertDialogTrigger>
254
+ <AlertDialogContent>
255
+ <AlertDialogHeader>
256
+ <AlertDialogTitle>⚠️ Warning</AlertDialogTitle>
257
+ <AlertDialogDescription>
258
+ This action may have unintended consequences.
259
+ </AlertDialogDescription>
260
+ </AlertDialogHeader>
261
+ <AlertDialogFooter>
262
+ <AlertDialogCancel>Go Back</AlertDialogCancel>
263
+ <AlertDialogAction className="bg-yellow-600 hover:bg-yellow-700">
264
+ I Understand
265
+ </AlertDialogAction>
266
+ </AlertDialogFooter>
267
+ </AlertDialogContent>
268
+ </AlertDialog>
269
+ );
270
+
271
+ cy.contains('Show Warning').click();
272
+ cy.contains('⚠️ Warning').should('be.visible');
273
+ cy.get('.bg-yellow-600').should('contain', 'I Understand');
274
+ });
275
+
276
+ it('handles multiple dialogs', () => {
277
+ cy.mount(
278
+ <div className="space-x-4">
279
+ <AlertDialog>
280
+ <AlertDialogTrigger>First Dialog</AlertDialogTrigger>
281
+ <AlertDialogContent>
282
+ <AlertDialogTitle>First Alert</AlertDialogTitle>
283
+ <AlertDialogFooter>
284
+ <AlertDialogAction>OK</AlertDialogAction>
285
+ </AlertDialogFooter>
286
+ </AlertDialogContent>
287
+ </AlertDialog>
288
+
289
+ <AlertDialog>
290
+ <AlertDialogTrigger>Second Dialog</AlertDialogTrigger>
291
+ <AlertDialogContent>
292
+ <AlertDialogTitle>Second Alert</AlertDialogTitle>
293
+ <AlertDialogFooter>
294
+ <AlertDialogAction>OK</AlertDialogAction>
295
+ </AlertDialogFooter>
296
+ </AlertDialogContent>
297
+ </AlertDialog>
298
+ </div>
299
+ );
300
+
301
+ // Open first dialog
302
+ cy.contains('First Dialog').click();
303
+ cy.contains('First Alert').should('be.visible');
304
+ cy.contains('button', 'OK').click();
305
+
306
+ // Open second dialog
307
+ cy.contains('Second Dialog').click();
308
+ cy.contains('Second Alert').should('be.visible');
309
+ });
310
+
311
+ it('maintains focus management', () => {
312
+ cy.mount(
313
+ <AlertDialog>
314
+ <AlertDialogTrigger>Open Alert</AlertDialogTrigger>
315
+ <AlertDialogContent>
316
+ <AlertDialogTitle>Focus Test</AlertDialogTitle>
317
+ <AlertDialogDescription>
318
+ Focus should be trapped within the dialog.
319
+ </AlertDialogDescription>
320
+ <AlertDialogFooter>
321
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
322
+ <AlertDialogAction>Confirm</AlertDialogAction>
323
+ </AlertDialogFooter>
324
+ </AlertDialogContent>
325
+ </AlertDialog>
326
+ );
327
+
328
+ cy.contains('Open Alert').click();
329
+
330
+ // Focus should be within dialog
331
+ cy.focused().should('exist');
332
+ });
333
+ });
@@ -0,0 +1,115 @@
1
+ import * as React from 'react';
2
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
3
+
4
+ import { cn } from '@/lib/utils';
5
+ import { buttonVariants } from '@/components/ui/button';
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root;
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal;
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
20
+ className
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ));
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ));
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
45
+
46
+ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
47
+ <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
48
+ );
49
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
50
+
51
+ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div
53
+ className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
54
+ {...props}
55
+ />
56
+ );
57
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
58
+
59
+ const AlertDialogTitle = React.forwardRef<
60
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
61
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
62
+ >(({ className, ...props }, ref) => (
63
+ <AlertDialogPrimitive.Title
64
+ ref={ref}
65
+ className={cn('text-lg font-semibold', className)}
66
+ {...props}
67
+ />
68
+ ));
69
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
70
+
71
+ const AlertDialogDescription = React.forwardRef<
72
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
73
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
74
+ >(({ className, ...props }, ref) => (
75
+ <AlertDialogPrimitive.Description
76
+ ref={ref}
77
+ className={cn('text-sm text-muted-foreground', className)}
78
+ {...props}
79
+ />
80
+ ));
81
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
82
+
83
+ const AlertDialogAction = React.forwardRef<
84
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
85
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
86
+ >(({ className, ...props }, ref) => (
87
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
88
+ ));
89
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
90
+
91
+ const AlertDialogCancel = React.forwardRef<
92
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
93
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
94
+ >(({ className, ...props }, ref) => (
95
+ <AlertDialogPrimitive.Cancel
96
+ ref={ref}
97
+ className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
98
+ {...props}
99
+ />
100
+ ));
101
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
102
+
103
+ export {
104
+ AlertDialog,
105
+ AlertDialogPortal,
106
+ AlertDialogOverlay,
107
+ AlertDialogTrigger,
108
+ AlertDialogContent,
109
+ AlertDialogHeader,
110
+ AlertDialogFooter,
111
+ AlertDialogTitle,
112
+ AlertDialogDescription,
113
+ AlertDialogAction,
114
+ AlertDialogCancel,
115
+ };
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const alertVariants = cva(
7
+ 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-background text-foreground',
12
+ destructive:
13
+ 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ }
20
+ );
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
27
+ ));
28
+ Alert.displayName = 'Alert';
29
+
30
+ const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
31
+ ({ className, ...props }, ref) => (
32
+ <h5
33
+ ref={ref}
34
+ className={cn('mb-1 font-medium leading-none tracking-tight', className)}
35
+ {...props}
36
+ />
37
+ )
38
+ );
39
+ AlertTitle.displayName = 'AlertTitle';
40
+
41
+ const AlertDescription = React.forwardRef<
42
+ HTMLParagraphElement,
43
+ React.HTMLAttributes<HTMLParagraphElement>
44
+ >(({ className, ...props }, ref) => (
45
+ <div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
46
+ ));
47
+ AlertDescription.displayName = 'AlertDescription';
48
+
49
+ export { Alert, AlertTitle, AlertDescription };
@@ -0,0 +1,180 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Avatar, AvatarFallback, AvatarImage } from './avatar';
6
+
7
+ describe('Avatar Component', () => {
8
+ it('renders image correctly', () => {
9
+ cy.mount(
10
+ <Avatar>
11
+ <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
12
+ <AvatarFallback>CN</AvatarFallback>
13
+ </Avatar>
14
+ );
15
+
16
+ cy.get('[data-testid="avatar"]').should('exist');
17
+ cy.get('[data-testid="avatar-image"]').should(
18
+ 'have.attr',
19
+ 'src',
20
+ 'https://github.com/shadcn.png'
21
+ );
22
+ cy.get('[data-testid="avatar-image"]').should('have.attr', 'alt', '@shadcn');
23
+ });
24
+
25
+ it('shows fallback when image fails to load', () => {
26
+ cy.mount(
27
+ <Avatar>
28
+ <AvatarImage src="https://broken-image-url.com/404.png" alt="broken" />
29
+ <AvatarFallback>FB</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+
33
+ // Wait for image to fail loading
34
+ cy.wait(500);
35
+ cy.get('[data-testid="avatar-fallback"]').should('be.visible');
36
+ cy.get('[data-testid="avatar-fallback"]').should('contain', 'FB');
37
+ });
38
+
39
+ it('renders fallback only when no image provided', () => {
40
+ cy.mount(
41
+ <Avatar>
42
+ <AvatarFallback>JD</AvatarFallback>
43
+ </Avatar>
44
+ );
45
+
46
+ cy.get('[data-testid="avatar-fallback"]').should('be.visible');
47
+ cy.get('[data-testid="avatar-fallback"]').should('contain', 'JD');
48
+ cy.get('[data-testid="avatar-image"]').should('not.exist');
49
+ });
50
+
51
+ it('applies custom className to avatar', () => {
52
+ cy.mount(
53
+ <Avatar className="w-20 h-20">
54
+ <AvatarFallback>LG</AvatarFallback>
55
+ </Avatar>
56
+ );
57
+
58
+ cy.get('[data-testid="avatar"]').should('have.class', 'w-20').should('have.class', 'h-20');
59
+ });
60
+
61
+ it('renders multiple avatars in a group', () => {
62
+ cy.mount(
63
+ <div className="flex -space-x-4">
64
+ <Avatar>
65
+ <AvatarImage src="https://github.com/user1.png" />
66
+ <AvatarFallback>U1</AvatarFallback>
67
+ </Avatar>
68
+ <Avatar>
69
+ <AvatarImage src="https://github.com/user2.png" />
70
+ <AvatarFallback>U2</AvatarFallback>
71
+ </Avatar>
72
+ <Avatar>
73
+ <AvatarImage src="https://github.com/user3.png" />
74
+ <AvatarFallback>U3</AvatarFallback>
75
+ </Avatar>
76
+ </div>
77
+ );
78
+
79
+ cy.get('[data-testid="avatar"]').should('have.length', 3);
80
+ });
81
+
82
+ it('supports different sizes via className', () => {
83
+ cy.mount(
84
+ <div className="flex gap-4">
85
+ <Avatar className="h-8 w-8">
86
+ <AvatarFallback>SM</AvatarFallback>
87
+ </Avatar>
88
+ <Avatar className="h-12 w-12">
89
+ <AvatarFallback>MD</AvatarFallback>
90
+ </Avatar>
91
+ <Avatar className="h-16 w-16">
92
+ <AvatarFallback>LG</AvatarFallback>
93
+ </Avatar>
94
+ </div>
95
+ );
96
+
97
+ cy.get('[data-testid="avatar"]').first().should('have.class', 'h-8');
98
+ cy.get('[data-testid="avatar"]').eq(1).should('have.class', 'h-12');
99
+ cy.get('[data-testid="avatar"]').last().should('have.class', 'h-16');
100
+ });
101
+
102
+ it('displays initials correctly in fallback', () => {
103
+ cy.mount(
104
+ <Avatar>
105
+ <AvatarFallback>John Doe</AvatarFallback>
106
+ </Avatar>
107
+ );
108
+
109
+ // Note: The component should ideally show "JD" but shows full text
110
+ cy.get('[data-testid="avatar-fallback"]').should('contain', 'John Doe');
111
+ });
112
+
113
+ it('supports custom fallback styles', () => {
114
+ cy.mount(
115
+ <Avatar>
116
+ <AvatarFallback className="bg-blue-500 text-white">AB</AvatarFallback>
117
+ </Avatar>
118
+ );
119
+
120
+ cy.get('[data-testid="avatar-fallback"]')
121
+ .should('have.class', 'bg-blue-500')
122
+ .should('have.class', 'text-white');
123
+ });
124
+
125
+ it('maintains aspect ratio', () => {
126
+ cy.mount(
127
+ <Avatar>
128
+ <AvatarImage src="https://github.com/shadcn.png" />
129
+ <AvatarFallback>CN</AvatarFallback>
130
+ </Avatar>
131
+ );
132
+
133
+ cy.get('[data-testid="avatar-image"]').should('have.class', 'aspect-square');
134
+ });
135
+
136
+ it('handles click events', () => {
137
+ const onClick = cy.stub();
138
+
139
+ cy.mount(
140
+ <Avatar onClick={onClick} className="cursor-pointer">
141
+ <AvatarFallback>CL</AvatarFallback>
142
+ </Avatar>
143
+ );
144
+
145
+ cy.get('[data-testid="avatar"]').click();
146
+ cy.wrap(onClick).should('have.been.calledOnce');
147
+ });
148
+
149
+ it('works with status indicators', () => {
150
+ cy.mount(
151
+ <div className="relative inline-flex">
152
+ <Avatar>
153
+ <AvatarFallback>ON</AvatarFallback>
154
+ </Avatar>
155
+ <span
156
+ className="absolute -bottom-1 -right-1 h-3.5 w-3.5 rounded-full bg-green-500 ring-2 ring-white"
157
+ data-testid="status-indicator"
158
+ />
159
+ </div>
160
+ );
161
+
162
+ cy.get('[data-testid="avatar"]').should('exist');
163
+ cy.get('[data-testid="status-indicator"]')
164
+ .should('exist')
165
+ .should('have.class', 'bg-green-500')
166
+ .should('have.class', 'ring-2');
167
+ });
168
+
169
+ it('supports data attributes', () => {
170
+ cy.mount(
171
+ <Avatar data-testid="custom-avatar" data-user-id="123">
172
+ <AvatarFallback>DA</AvatarFallback>
173
+ </Avatar>
174
+ );
175
+
176
+ cy.get('[data-testid="custom-avatar"]')
177
+ .should('exist')
178
+ .should('have.attr', 'data-user-id', '123');
179
+ });
180
+ });