@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.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets/empty-module-CLMscLYw.js +1 -0
- package/dist/assets/main-BBZ_3lkn.css +5999 -0
- package/dist/assets/main-C5zNUkXH.js +7 -0
- package/dist/assets/main-Dz64ENQg.js +614 -0
- package/dist/assets/react-vendor-DM5m98rr.js +545 -0
- package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
- package/dist/elizaos-avatar.png +0 -0
- package/dist/elizaos-icon.png +0 -0
- package/dist/elizaos-logo-light.png +0 -0
- package/dist/elizaos.webp +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/images/agents/agent1.png +0 -0
- package/dist/images/agents/agent2.png +0 -0
- package/dist/images/agents/agent3.png +0 -0
- package/dist/images/agents/agent4.png +0 -0
- package/dist/images/agents/agent5.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +24 -0
- package/package.json +159 -0
- package/postcss.config.js +3 -0
- package/public/elizaos-avatar.png +0 -0
- package/public/elizaos-icon.png +0 -0
- package/public/elizaos-logo-light.png +0 -0
- package/public/elizaos.webp +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/agents/agent1.png +0 -0
- package/public/images/agents/agent2.png +0 -0
- package/public/images/agents/agent3.png +0 -0
- package/public/images/agents/agent4.png +0 -0
- package/public/images/agents/agent5.png +0 -0
- package/src/App.tsx +222 -0
- package/src/components/AgentDetailsPanel.tsx +147 -0
- package/src/components/ChatInputArea.tsx +196 -0
- package/src/components/ChatMessageListComponent.tsx +139 -0
- package/src/components/actionTool.tsx +186 -0
- package/src/components/add-agent-card.tsx +77 -0
- package/src/components/agent-action-viewer.tsx +816 -0
- package/src/components/agent-avatar-stack.tsx +121 -0
- package/src/components/agent-card.cy.tsx +259 -0
- package/src/components/agent-card.tsx +177 -0
- package/src/components/agent-creator.tsx +142 -0
- package/src/components/agent-log-viewer.tsx +645 -0
- package/src/components/agent-memory-edit-overlay.tsx +461 -0
- package/src/components/agent-memory-viewer.tsx +504 -0
- package/src/components/agent-settings.tsx +270 -0
- package/src/components/agent-sidebar.tsx +178 -0
- package/src/components/api-key-dialog.tsx +113 -0
- package/src/components/app-sidebar.tsx +685 -0
- package/src/components/array-input.tsx +116 -0
- package/src/components/audio-recorder.tsx +292 -0
- package/src/components/avatar-panel.tsx +141 -0
- package/src/components/character-form.tsx +1138 -0
- package/src/components/chat.tsx +1813 -0
- package/src/components/combobox.tsx +187 -0
- package/src/components/confirmation-dialog.tsx +59 -0
- package/src/components/connection-error-banner.tsx +101 -0
- package/src/components/connection-status.cy.tsx +73 -0
- package/src/components/connection-status.tsx +155 -0
- package/src/components/copy-button.tsx +35 -0
- package/src/components/delete-button.tsx +24 -0
- package/src/components/env-settings.tsx +261 -0
- package/src/components/group-card.tsx +160 -0
- package/src/components/group-panel.tsx +543 -0
- package/src/components/input-copy.tsx +21 -0
- package/src/components/logs-page.tsx +41 -0
- package/src/components/media-content.tsx +385 -0
- package/src/components/memory-graph.tsx +170 -0
- package/src/components/missing-secrets-dialog.tsx +72 -0
- package/src/components/onboarding-tour.tsx +247 -0
- package/src/components/page-title.tsx +8 -0
- package/src/components/plugins-panel.tsx +383 -0
- package/src/components/profile-card.tsx +66 -0
- package/src/components/profile-overlay.tsx +283 -0
- package/src/components/retry-button.tsx +28 -0
- package/src/components/secret-panel.tsx +1505 -0
- package/src/components/server-management.tsx +264 -0
- package/src/components/split-button.tsx +148 -0
- package/src/components/stop-agent-button.tsx +99 -0
- package/src/components/ui/alert-dialog.cy.tsx +333 -0
- package/src/components/ui/alert-dialog.tsx +115 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.cy.tsx +180 -0
- package/src/components/ui/avatar.tsx +57 -0
- package/src/components/ui/badge.cy.tsx +146 -0
- package/src/components/ui/badge.tsx +43 -0
- package/src/components/ui/button.cy.tsx +177 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.cy.tsx +160 -0
- package/src/components/ui/card.tsx +73 -0
- package/src/components/ui/chat/animated-markdown.tsx +59 -0
- package/src/components/ui/chat/chat-bubble.tsx +178 -0
- package/src/components/ui/chat/chat-container.tsx +51 -0
- package/src/components/ui/chat/chat-input.cy.tsx +169 -0
- package/src/components/ui/chat/chat-input.tsx +47 -0
- package/src/components/ui/chat/chat-message-list.tsx +61 -0
- package/src/components/ui/chat/chat-tts-button.tsx +199 -0
- package/src/components/ui/chat/code-block.tsx +79 -0
- package/src/components/ui/chat/expandable-chat.tsx +131 -0
- package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
- package/src/components/ui/chat/markdown.tsx +209 -0
- package/src/components/ui/chat/message-loading.tsx +48 -0
- package/src/components/ui/checkbox.cy.tsx +170 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.cy.tsx +283 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.cy.tsx +313 -0
- package/src/components/ui/command.tsx +143 -0
- package/src/components/ui/dialog.cy.tsx +279 -0
- package/src/components/ui/dialog.tsx +104 -0
- package/src/components/ui/dropdown-menu.cy.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +281 -0
- package/src/components/ui/input.cy.tsx +82 -0
- package/src/components/ui/input.tsx +27 -0
- package/src/components/ui/label.cy.tsx +157 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/resizable.tsx +42 -0
- package/src/components/ui/scroll-area.cy.tsx +242 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.cy.tsx +277 -0
- package/src/components/ui/select.tsx +155 -0
- package/src/components/ui/separator.cy.tsx +145 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.cy.tsx +324 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sidebar.tsx +734 -0
- package/src/components/ui/skeleton.cy.tsx +149 -0
- package/src/components/ui/skeleton.tsx +17 -0
- package/src/components/ui/split-button.cy.tsx +274 -0
- package/src/components/ui/split-button.tsx +112 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/tabs.cy.tsx +271 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.cy.tsx +136 -0
- package/src/components/ui/textarea.tsx +26 -0
- package/src/components/ui/toast.cy.tsx +209 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/toaster.tsx +29 -0
- package/src/components/ui/tooltip.cy.tsx +244 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/config/agent-templates.ts +349 -0
- package/src/config/voice-models.ts +181 -0
- package/src/constants.ts +23 -0
- package/src/context/AuthContext.tsx +44 -0
- package/src/context/ConnectionContext.tsx +194 -0
- package/src/entry.tsx +9 -0
- package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
- package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
- package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
- package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
- package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
- package/src/hooks/use-agent-management.ts +130 -0
- package/src/hooks/use-agent-tab-state.ts +74 -0
- package/src/hooks/use-agent-update.ts +469 -0
- package/src/hooks/use-character-convert.ts +138 -0
- package/src/hooks/use-confirmation.ts +55 -0
- package/src/hooks/use-delete-agent.ts +123 -0
- package/src/hooks/use-dm-channels.ts +198 -0
- package/src/hooks/use-elevenlabs-voices.ts +83 -0
- package/src/hooks/use-file-upload.ts +224 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-onboarding.tsx +49 -0
- package/src/hooks/use-panel-width-state.ts +147 -0
- package/src/hooks/use-partial-update.ts +288 -0
- package/src/hooks/use-plugin-details.ts +462 -0
- package/src/hooks/use-plugins.ts +119 -0
- package/src/hooks/use-query-hooks.ts +1263 -0
- package/src/hooks/use-server-agents.ts +62 -0
- package/src/hooks/use-server-version.tsx +47 -0
- package/src/hooks/use-sidebar-state.ts +50 -0
- package/src/hooks/use-socket-chat.ts +264 -0
- package/src/hooks/use-toast.ts +260 -0
- package/src/hooks/use-version.tsx +64 -0
- package/src/index.css +146 -0
- package/src/lib/api-client-config.ts +53 -0
- package/src/lib/api-type-mappers.ts +196 -0
- package/src/lib/export-utils.ts +123 -0
- package/src/lib/logger.ts +19 -0
- package/src/lib/media-utils.ts +170 -0
- package/src/lib/pca.test.ts +17 -0
- package/src/lib/pca.ts +52 -0
- package/src/lib/socketio-manager.ts +664 -0
- package/src/lib/utils.ts +168 -0
- package/src/main.tsx +16 -0
- package/src/mocks/empty-module.ts +12 -0
- package/src/mocks/node-module.ts +57 -0
- package/src/polyfills.ts +37 -0
- package/src/routes/agent-detail.tsx +30 -0
- package/src/routes/agent-list.tsx +27 -0
- package/src/routes/agent-settings.tsx +48 -0
- package/src/routes/character-detail.tsx +52 -0
- package/src/routes/character-form.tsx +79 -0
- package/src/routes/character-list.tsx +38 -0
- package/src/routes/chat.tsx +128 -0
- package/src/routes/createAgent.tsx +13 -0
- package/src/routes/group-new.tsx +50 -0
- package/src/routes/group.tsx +29 -0
- package/src/routes/home.tsx +218 -0
- package/src/routes/not-found.tsx +71 -0
- package/src/test/setup.ts +154 -0
- package/src/types/crypto-browserify.d.ts +4 -0
- package/src/types/index.ts +13 -0
- package/src/types/rooms.ts +8 -0
- package/src/types.ts +84 -0
- package/src/vite-env.d.ts +40 -0
- package/tailwind.config.ts +90 -0
- package/tsconfig.json +10 -0
- 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
|
+
});
|