@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,279 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ DialogClose,
14
+ } from './dialog';
15
+ import { Button } from './button';
16
+
17
+ describe('Dialog Component', () => {
18
+ it('renders dialog with trigger', () => {
19
+ cy.mount(
20
+ <Dialog>
21
+ <DialogTrigger asChild>
22
+ <Button>Open Dialog</Button>
23
+ </DialogTrigger>
24
+ <DialogContent>
25
+ <DialogHeader>
26
+ <DialogTitle>Dialog Title</DialogTitle>
27
+ <DialogDescription>This is a dialog description.</DialogDescription>
28
+ </DialogHeader>
29
+ </DialogContent>
30
+ </Dialog>
31
+ );
32
+
33
+ cy.contains('button', 'Open Dialog').should('be.visible');
34
+ cy.contains('Dialog Title').should('not.exist');
35
+
36
+ // Open dialog
37
+ cy.contains('button', 'Open Dialog').click();
38
+ cy.contains('Dialog Title').should('be.visible');
39
+ cy.contains('This is a dialog description.').should('be.visible');
40
+ });
41
+
42
+ it('closes dialog when clicking outside', () => {
43
+ cy.mount(
44
+ <Dialog>
45
+ <DialogTrigger>Open</DialogTrigger>
46
+ <DialogContent>
47
+ <DialogTitle>Click Outside Test</DialogTitle>
48
+ </DialogContent>
49
+ </Dialog>
50
+ );
51
+
52
+ cy.contains('Open').click();
53
+ cy.contains('Click Outside Test').should('be.visible');
54
+
55
+ // Test escape key instead of clicking outside
56
+ cy.get('body').type('{esc}');
57
+ cy.contains('Click Outside Test').should('not.exist');
58
+ });
59
+
60
+ it('renders with footer actions', () => {
61
+ const onSave = cy.stub();
62
+ const onCancel = cy.stub();
63
+
64
+ cy.mount(
65
+ <Dialog>
66
+ <DialogTrigger>Open Form</DialogTrigger>
67
+ <DialogContent>
68
+ <DialogHeader>
69
+ <DialogTitle>Edit Profile</DialogTitle>
70
+ <DialogDescription>Make changes to your profile here.</DialogDescription>
71
+ </DialogHeader>
72
+ <div className="py-4">
73
+ <input placeholder="Name" className="w-full p-2 border rounded" />
74
+ </div>
75
+ <DialogFooter>
76
+ <Button variant="outline" onClick={onCancel}>
77
+ Cancel
78
+ </Button>
79
+ <Button onClick={onSave}>Save changes</Button>
80
+ </DialogFooter>
81
+ </DialogContent>
82
+ </Dialog>
83
+ );
84
+
85
+ cy.contains('Open Form').click();
86
+ cy.get('input[placeholder="Name"]').should('be.visible');
87
+
88
+ cy.contains('button', 'Save changes').click({ force: true });
89
+ cy.wrap(onSave).should('have.been.called');
90
+ });
91
+
92
+ it('works as controlled component', () => {
93
+ const TestComponent = () => {
94
+ const [open, setOpen] = React.useState(false);
95
+
96
+ return (
97
+ <>
98
+ <button onClick={() => setOpen(true)}>External Open</button>
99
+ <Dialog open={open} onOpenChange={setOpen}>
100
+ <DialogContent>
101
+ <DialogTitle>Controlled Dialog</DialogTitle>
102
+ <DialogClose asChild>
103
+ <button>Close</button>
104
+ </DialogClose>
105
+ </DialogContent>
106
+ </Dialog>
107
+ </>
108
+ );
109
+ };
110
+
111
+ cy.mount(<TestComponent />);
112
+
113
+ cy.contains('Controlled Dialog').should('not.exist');
114
+ cy.contains('External Open').click();
115
+ cy.contains('Controlled Dialog').should('be.visible');
116
+
117
+ cy.contains('button', 'Close').click({ force: true });
118
+ cy.contains('Controlled Dialog').should('not.exist');
119
+ });
120
+
121
+ it('handles keyboard navigation', () => {
122
+ cy.mount(
123
+ <Dialog>
124
+ <DialogTrigger>Open</DialogTrigger>
125
+ <DialogContent>
126
+ <DialogTitle>Keyboard Test</DialogTitle>
127
+ <button>First Button</button>
128
+ <button>Second Button</button>
129
+ </DialogContent>
130
+ </Dialog>
131
+ );
132
+
133
+ cy.contains('Open').click();
134
+ cy.contains('Keyboard Test').should('be.visible');
135
+
136
+ // ESC key should close
137
+ cy.get('body').type('{esc}');
138
+ cy.contains('Keyboard Test').should('not.exist');
139
+ });
140
+
141
+ it('supports custom className', () => {
142
+ cy.mount(
143
+ <Dialog>
144
+ <DialogTrigger>Open</DialogTrigger>
145
+ <DialogContent className="bg-blue-50 max-w-lg">
146
+ <DialogTitle>Custom Styled Dialog</DialogTitle>
147
+ </DialogContent>
148
+ </Dialog>
149
+ );
150
+
151
+ cy.contains('Open').click();
152
+ cy.get('[role="dialog"]').should('have.class', 'bg-blue-50');
153
+ cy.get('[role="dialog"]').should('have.class', 'max-w-lg');
154
+ });
155
+
156
+ it('renders form inside dialog', () => {
157
+ const onSubmit = cy.stub();
158
+
159
+ cy.mount(
160
+ <Dialog>
161
+ <DialogTrigger>Add Item</DialogTrigger>
162
+ <DialogContent>
163
+ <form
164
+ onSubmit={(e) => {
165
+ e.preventDefault();
166
+ onSubmit('submitted');
167
+ }}
168
+ >
169
+ <DialogHeader>
170
+ <DialogTitle>Add New Item</DialogTitle>
171
+ <DialogDescription>Fill in the details below</DialogDescription>
172
+ </DialogHeader>
173
+ <div className="space-y-4 py-4">
174
+ <input name="title" placeholder="Title" required />
175
+ <textarea name="description" placeholder="Description" />
176
+ </div>
177
+ <DialogFooter>
178
+ <DialogClose asChild>
179
+ <Button type="button" variant="outline">
180
+ Cancel
181
+ </Button>
182
+ </DialogClose>
183
+ <Button type="submit">Add</Button>
184
+ </DialogFooter>
185
+ </form>
186
+ </DialogContent>
187
+ </Dialog>
188
+ );
189
+
190
+ cy.contains('Add Item').click();
191
+ cy.get('input[name="title"]').type('Test Item');
192
+ cy.get('textarea[name="description"]').type('Test Description');
193
+ cy.get('[role="dialog"]').within(() => {
194
+ cy.contains('button', 'Add').click({ force: true });
195
+ });
196
+ cy.wrap(onSubmit).should('have.been.calledWith', 'submitted');
197
+ });
198
+
199
+ it('prevents closing when modal', () => {
200
+ cy.mount(
201
+ <Dialog modal={true}>
202
+ <DialogTrigger>Open Modal</DialogTrigger>
203
+ <DialogContent>
204
+ <DialogTitle>Modal Dialog</DialogTitle>
205
+ <DialogDescription>
206
+ This dialog is modal and requires explicit action to close.
207
+ </DialogDescription>
208
+ </DialogContent>
209
+ </Dialog>
210
+ );
211
+
212
+ cy.contains('Open Modal').click();
213
+ cy.contains('Modal Dialog').should('be.visible');
214
+ });
215
+
216
+ it('supports nested content', () => {
217
+ cy.mount(
218
+ <Dialog>
219
+ <DialogTrigger>View Details</DialogTrigger>
220
+ <DialogContent>
221
+ <DialogHeader>
222
+ <DialogTitle>User Details</DialogTitle>
223
+ </DialogHeader>
224
+ <div className="space-y-2">
225
+ <div>
226
+ <strong>Name:</strong> John Doe
227
+ </div>
228
+ <div>
229
+ <strong>Email:</strong> john@example.com
230
+ </div>
231
+ <div>
232
+ <strong>Role:</strong> Administrator
233
+ </div>
234
+ </div>
235
+ </DialogContent>
236
+ </Dialog>
237
+ );
238
+
239
+ cy.contains('View Details').click();
240
+ cy.contains('John Doe').should('be.visible');
241
+ cy.contains('john@example.com').should('be.visible');
242
+ cy.contains('Administrator').should('be.visible');
243
+ });
244
+
245
+ it('handles long content with scroll', () => {
246
+ cy.mount(
247
+ <Dialog>
248
+ <DialogTrigger>Open Long Content</DialogTrigger>
249
+ <DialogContent className="max-h-[300px]">
250
+ <DialogHeader>
251
+ <DialogTitle>Terms and Conditions</DialogTitle>
252
+ </DialogHeader>
253
+ <div className="overflow-y-auto max-h-[200px]">
254
+ {Array.from({ length: 50 }, (_, i) => (
255
+ <p key={i} className="py-2">
256
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
257
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
258
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
259
+ </p>
260
+ ))}
261
+ </div>
262
+ </DialogContent>
263
+ </Dialog>
264
+ );
265
+
266
+ cy.contains('Open Long Content').click();
267
+
268
+ // Wait for dialog to open and verify scroll container exists
269
+ cy.get('[role="dialog"]').should('be.visible');
270
+ cy.get('.overflow-y-auto').should('exist');
271
+
272
+ // Verify the container has the correct CSS classes for scrolling
273
+ cy.get('.overflow-y-auto').should('have.class', 'overflow-y-auto');
274
+ cy.get('.overflow-y-auto').should('have.class', 'max-h-[200px]');
275
+
276
+ // Check that content exists inside the scroll container
277
+ cy.get('.overflow-y-auto p').should('have.length', 50);
278
+ });
279
+ });
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+
3
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
4
+ import { X } from 'lucide-react';
5
+ import * as React from 'react';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ const Dialog = DialogPrimitive.Root;
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger;
12
+
13
+ const DialogPortal = DialogPrimitive.Portal;
14
+
15
+ const DialogClose = DialogPrimitive.Close;
16
+
17
+ const DialogOverlay = React.forwardRef<
18
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
19
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
20
+ >(({ className, ...props }, ref) => (
21
+ <DialogPrimitive.Overlay
22
+ ref={ref}
23
+ className={cn(
24
+ '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',
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ));
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
31
+
32
+ const DialogContent = React.forwardRef<
33
+ React.ElementRef<typeof DialogPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DialogPortal>
37
+ <DialogOverlay />
38
+ <DialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ 'fixed left-[50%] top-[50%] z-50 bg-card grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border 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',
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48
+ <X className="h-4 w-4" />
49
+ <span className="sr-only">Close</span>
50
+ </DialogPrimitive.Close>
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ ));
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
55
+
56
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
57
+ <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
58
+ );
59
+ DialogHeader.displayName = 'DialogHeader';
60
+
61
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
62
+ <div
63
+ className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
64
+ {...props}
65
+ />
66
+ );
67
+ DialogFooter.displayName = 'DialogFooter';
68
+
69
+ const DialogTitle = React.forwardRef<
70
+ React.ElementRef<typeof DialogPrimitive.Title>,
71
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
72
+ >(({ className, ...props }, ref) => (
73
+ <DialogPrimitive.Title
74
+ ref={ref}
75
+ className={cn('text-lg font-semibold leading-none tracking-tight', className)}
76
+ {...props}
77
+ />
78
+ ));
79
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
80
+
81
+ const DialogDescription = React.forwardRef<
82
+ React.ElementRef<typeof DialogPrimitive.Description>,
83
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
84
+ >(({ className, ...props }, ref) => (
85
+ <DialogPrimitive.Description
86
+ ref={ref}
87
+ className={cn('text-sm text-muted-foreground', className)}
88
+ {...props}
89
+ />
90
+ ));
91
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
92
+
93
+ export {
94
+ Dialog,
95
+ DialogPortal,
96
+ DialogOverlay,
97
+ DialogTrigger,
98
+ DialogClose,
99
+ DialogContent,
100
+ DialogHeader,
101
+ DialogFooter,
102
+ DialogTitle,
103
+ DialogDescription,
104
+ };
@@ -0,0 +1,273 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ DropdownMenuGroup,
13
+ DropdownMenuSub,
14
+ DropdownMenuSubContent,
15
+ DropdownMenuSubTrigger,
16
+ DropdownMenuCheckboxItem,
17
+ DropdownMenuRadioGroup,
18
+ DropdownMenuRadioItem,
19
+ DropdownMenuShortcut,
20
+ } from './dropdown-menu';
21
+ import { Button } from './button';
22
+
23
+ describe('DropdownMenu Component', () => {
24
+ it('renders basic dropdown menu', () => {
25
+ cy.mountRadix(
26
+ <DropdownMenu>
27
+ <DropdownMenuTrigger asChild>
28
+ <Button>Open Menu</Button>
29
+ </DropdownMenuTrigger>
30
+ <DropdownMenuContent>
31
+ <DropdownMenuItem>Item 1</DropdownMenuItem>
32
+ <DropdownMenuItem>Item 2</DropdownMenuItem>
33
+ <DropdownMenuItem>Item 3</DropdownMenuItem>
34
+ </DropdownMenuContent>
35
+ </DropdownMenu>
36
+ );
37
+
38
+ cy.get('button').contains('Open Menu').should('exist');
39
+ cy.get('button').click();
40
+ cy.contains('Item 1').should('be.visible');
41
+ cy.contains('Item 2').should('be.visible');
42
+ cy.contains('Item 3').should('be.visible');
43
+ });
44
+
45
+ it('handles menu item clicks', () => {
46
+ const onClick = cy.stub();
47
+
48
+ cy.mountRadix(
49
+ <DropdownMenu>
50
+ <DropdownMenuTrigger asChild>
51
+ <Button>Actions</Button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent>
54
+ <DropdownMenuItem onClick={onClick}>Action 1</DropdownMenuItem>
55
+ <DropdownMenuItem>Action 2</DropdownMenuItem>
56
+ </DropdownMenuContent>
57
+ </DropdownMenu>
58
+ );
59
+
60
+ cy.get('button').click();
61
+ cy.contains('Action 1').click();
62
+ cy.wrap(onClick).should('have.been.called');
63
+ });
64
+
65
+ it('renders with labels and separators', () => {
66
+ cy.mountRadix(
67
+ <DropdownMenu>
68
+ <DropdownMenuTrigger asChild>
69
+ <Button>Options</Button>
70
+ </DropdownMenuTrigger>
71
+ <DropdownMenuContent>
72
+ <DropdownMenuLabel>Account</DropdownMenuLabel>
73
+ <DropdownMenuItem>Profile</DropdownMenuItem>
74
+ <DropdownMenuItem>Settings</DropdownMenuItem>
75
+ <DropdownMenuSeparator />
76
+ <DropdownMenuItem>Log out</DropdownMenuItem>
77
+ </DropdownMenuContent>
78
+ </DropdownMenu>
79
+ );
80
+
81
+ cy.get('button').click();
82
+ cy.contains('Account').should('be.visible');
83
+ cy.contains('Profile').should('be.visible');
84
+ cy.get('[role="separator"]').should('exist');
85
+ });
86
+
87
+ it('supports disabled items', () => {
88
+ cy.mountRadix(
89
+ <DropdownMenu>
90
+ <DropdownMenuTrigger asChild>
91
+ <Button>Menu</Button>
92
+ </DropdownMenuTrigger>
93
+ <DropdownMenuContent>
94
+ <DropdownMenuItem>Enabled</DropdownMenuItem>
95
+ <DropdownMenuItem disabled>Disabled</DropdownMenuItem>
96
+ <DropdownMenuItem>Also Enabled</DropdownMenuItem>
97
+ </DropdownMenuContent>
98
+ </DropdownMenu>
99
+ );
100
+
101
+ cy.get('button').click();
102
+ cy.contains('Disabled').should('have.attr', 'data-disabled');
103
+ });
104
+
105
+ it('renders checkbox items', () => {
106
+ const CheckboxTestComponent = () => {
107
+ const [checked, setChecked] = React.useState(false);
108
+
109
+ return (
110
+ <DropdownMenu>
111
+ <DropdownMenuTrigger asChild>
112
+ <Button>Settings</Button>
113
+ </DropdownMenuTrigger>
114
+ <DropdownMenuContent>
115
+ <DropdownMenuCheckboxItem checked={checked} onCheckedChange={setChecked}>
116
+ Show Status Bar
117
+ </DropdownMenuCheckboxItem>
118
+ <DropdownMenuCheckboxItem>Show Activity Bar</DropdownMenuCheckboxItem>
119
+ </DropdownMenuContent>
120
+ </DropdownMenu>
121
+ );
122
+ };
123
+
124
+ cy.mountRadix(<CheckboxTestComponent />);
125
+
126
+ cy.get('button').click();
127
+ cy.contains('Show Status Bar').click();
128
+ cy.contains('Show Status Bar').should('have.attr', 'data-state', 'checked');
129
+ });
130
+
131
+ it('renders radio group items', () => {
132
+ const RadioTestComponent = () => {
133
+ const [value, setValue] = React.useState('option1');
134
+
135
+ return (
136
+ <DropdownMenu>
137
+ <DropdownMenuTrigger asChild>
138
+ <Button>View</Button>
139
+ </DropdownMenuTrigger>
140
+ <DropdownMenuContent>
141
+ <DropdownMenuRadioGroup value={value} onValueChange={setValue}>
142
+ <DropdownMenuRadioItem value="option1">Option 1</DropdownMenuRadioItem>
143
+ <DropdownMenuRadioItem value="option2">Option 2</DropdownMenuRadioItem>
144
+ <DropdownMenuRadioItem value="option3">Option 3</DropdownMenuRadioItem>
145
+ </DropdownMenuRadioGroup>
146
+ </DropdownMenuContent>
147
+ </DropdownMenu>
148
+ );
149
+ };
150
+
151
+ cy.mountRadix(<RadioTestComponent />);
152
+
153
+ cy.get('button').click();
154
+ cy.contains('Option 1').should('have.attr', 'data-state', 'checked');
155
+ cy.contains('Option 2').click();
156
+ cy.contains('Option 2').should('have.attr', 'data-state', 'checked');
157
+ });
158
+
159
+ it('supports sub menus', () => {
160
+ cy.mountRadix(
161
+ <DropdownMenu>
162
+ <DropdownMenuTrigger asChild>
163
+ <Button>Menu</Button>
164
+ </DropdownMenuTrigger>
165
+ <DropdownMenuContent>
166
+ <DropdownMenuItem>New File</DropdownMenuItem>
167
+ <DropdownMenuSub>
168
+ <DropdownMenuSubTrigger>More Tools</DropdownMenuSubTrigger>
169
+ <DropdownMenuSubContent>
170
+ <DropdownMenuItem>Save Page As...</DropdownMenuItem>
171
+ <DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
172
+ <DropdownMenuItem>Name Window...</DropdownMenuItem>
173
+ </DropdownMenuSubContent>
174
+ </DropdownMenuSub>
175
+ </DropdownMenuContent>
176
+ </DropdownMenu>
177
+ );
178
+
179
+ cy.get('button').click();
180
+ cy.contains('More Tools').should('be.visible');
181
+
182
+ // Try clicking instead of hover for submenu
183
+ cy.contains('More Tools').click();
184
+ cy.contains('Save Page As...').should('be.visible');
185
+ });
186
+
187
+ it('renders with shortcuts', () => {
188
+ cy.mountRadix(
189
+ <DropdownMenu>
190
+ <DropdownMenuTrigger asChild>
191
+ <Button>Edit</Button>
192
+ </DropdownMenuTrigger>
193
+ <DropdownMenuContent>
194
+ <DropdownMenuItem>
195
+ Undo
196
+ <DropdownMenuShortcut>⌘Z</DropdownMenuShortcut>
197
+ </DropdownMenuItem>
198
+ <DropdownMenuItem>
199
+ Redo
200
+ <DropdownMenuShortcut>⇧⌘Z</DropdownMenuShortcut>
201
+ </DropdownMenuItem>
202
+ </DropdownMenuContent>
203
+ </DropdownMenu>
204
+ );
205
+
206
+ cy.get('button').click();
207
+ cy.contains('⌘Z').should('be.visible');
208
+ cy.contains('⇧⌘Z').should('be.visible');
209
+ });
210
+
211
+ it('supports custom className', () => {
212
+ cy.mountRadix(
213
+ <DropdownMenu>
214
+ <DropdownMenuTrigger asChild>
215
+ <Button>Styled</Button>
216
+ </DropdownMenuTrigger>
217
+ <DropdownMenuContent className="w-56">
218
+ <DropdownMenuItem className="text-red-500">Delete</DropdownMenuItem>
219
+ </DropdownMenuContent>
220
+ </DropdownMenu>
221
+ );
222
+
223
+ cy.get('button').click();
224
+ cy.get('[role="menu"]').should('have.class', 'w-56');
225
+ cy.contains('Delete').should('have.class', 'text-red-500');
226
+ });
227
+
228
+ it('closes on escape key', () => {
229
+ cy.mountRadix(
230
+ <DropdownMenu>
231
+ <DropdownMenuTrigger asChild>
232
+ <Button>Menu</Button>
233
+ </DropdownMenuTrigger>
234
+ <DropdownMenuContent>
235
+ <DropdownMenuItem>Item 1</DropdownMenuItem>
236
+ <DropdownMenuItem>Item 2</DropdownMenuItem>
237
+ </DropdownMenuContent>
238
+ </DropdownMenu>
239
+ );
240
+
241
+ cy.get('button').click();
242
+ cy.contains('Item 1').should('be.visible');
243
+ cy.get('body').type('{esc}');
244
+ cy.contains('Item 1').should('not.exist');
245
+ });
246
+
247
+ it('supports grouped items', () => {
248
+ cy.mountRadix(
249
+ <DropdownMenu>
250
+ <DropdownMenuTrigger asChild>
251
+ <Button>Grouped</Button>
252
+ </DropdownMenuTrigger>
253
+ <DropdownMenuContent>
254
+ <DropdownMenuGroup>
255
+ <DropdownMenuLabel>Group 1</DropdownMenuLabel>
256
+ <DropdownMenuItem>Item 1</DropdownMenuItem>
257
+ <DropdownMenuItem>Item 2</DropdownMenuItem>
258
+ </DropdownMenuGroup>
259
+ <DropdownMenuSeparator />
260
+ <DropdownMenuGroup>
261
+ <DropdownMenuLabel>Group 2</DropdownMenuLabel>
262
+ <DropdownMenuItem>Item 3</DropdownMenuItem>
263
+ <DropdownMenuItem>Item 4</DropdownMenuItem>
264
+ </DropdownMenuGroup>
265
+ </DropdownMenuContent>
266
+ </DropdownMenu>
267
+ );
268
+
269
+ cy.get('button').click();
270
+ cy.contains('Group 1').should('be.visible');
271
+ cy.contains('Group 2').should('be.visible');
272
+ });
273
+ });