@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,209 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Toast,
|
|
7
|
+
ToastAction,
|
|
8
|
+
ToastClose,
|
|
9
|
+
ToastDescription,
|
|
10
|
+
ToastProvider,
|
|
11
|
+
ToastTitle,
|
|
12
|
+
ToastViewport,
|
|
13
|
+
} from './toast';
|
|
14
|
+
|
|
15
|
+
describe('Toast Component', () => {
|
|
16
|
+
it('renders basic toast correctly', () => {
|
|
17
|
+
cy.mountRadix(
|
|
18
|
+
<ToastProvider>
|
|
19
|
+
<ToastViewport />
|
|
20
|
+
<Toast open>
|
|
21
|
+
<ToastTitle>Notification</ToastTitle>
|
|
22
|
+
<ToastDescription>This is a toast message</ToastDescription>
|
|
23
|
+
</Toast>
|
|
24
|
+
</ToastProvider>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
cy.contains('Notification').should('be.visible');
|
|
28
|
+
cy.contains('This is a toast message').should('be.visible');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders with action button', () => {
|
|
32
|
+
const onAction = cy.stub();
|
|
33
|
+
|
|
34
|
+
cy.mountRadix(
|
|
35
|
+
<ToastProvider>
|
|
36
|
+
<ToastViewport />
|
|
37
|
+
<Toast open>
|
|
38
|
+
<ToastTitle>Undo</ToastTitle>
|
|
39
|
+
<ToastDescription>Your action has been undone</ToastDescription>
|
|
40
|
+
<ToastAction altText="Redo" onClick={onAction}>
|
|
41
|
+
Redo
|
|
42
|
+
</ToastAction>
|
|
43
|
+
</Toast>
|
|
44
|
+
</ToastProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
cy.contains('button', 'Redo').click();
|
|
48
|
+
cy.wrap(onAction).should('have.been.called');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders with close button', () => {
|
|
52
|
+
cy.mountRadix(
|
|
53
|
+
<ToastProvider>
|
|
54
|
+
<ToastViewport />
|
|
55
|
+
<Toast open>
|
|
56
|
+
<ToastTitle>Success</ToastTitle>
|
|
57
|
+
<ToastDescription>Your changes have been saved</ToastDescription>
|
|
58
|
+
<ToastClose />
|
|
59
|
+
</Toast>
|
|
60
|
+
</ToastProvider>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
cy.get('button[type="button"]').should('exist');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('supports different variants', () => {
|
|
67
|
+
cy.mountRadix(
|
|
68
|
+
<ToastProvider>
|
|
69
|
+
<ToastViewport />
|
|
70
|
+
<div className="space-y-4">
|
|
71
|
+
<Toast open variant="default">
|
|
72
|
+
<ToastTitle>Default Toast</ToastTitle>
|
|
73
|
+
<ToastDescription>This is a default toast</ToastDescription>
|
|
74
|
+
</Toast>
|
|
75
|
+
<Toast open variant="destructive">
|
|
76
|
+
<ToastTitle>Error</ToastTitle>
|
|
77
|
+
<ToastDescription>Something went wrong</ToastDescription>
|
|
78
|
+
</Toast>
|
|
79
|
+
</div>
|
|
80
|
+
</ToastProvider>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
cy.get('[data-state="open"]').should('have.class', 'destructive');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('renders multiple toasts', () => {
|
|
87
|
+
cy.mountRadix(
|
|
88
|
+
<ToastProvider>
|
|
89
|
+
<ToastViewport />
|
|
90
|
+
<div>
|
|
91
|
+
<Toast open>
|
|
92
|
+
<ToastTitle>First Toast</ToastTitle>
|
|
93
|
+
<ToastDescription>First message</ToastDescription>
|
|
94
|
+
</Toast>
|
|
95
|
+
<Toast open>
|
|
96
|
+
<ToastTitle>Second Toast</ToastTitle>
|
|
97
|
+
<ToastDescription>Second message</ToastDescription>
|
|
98
|
+
</Toast>
|
|
99
|
+
</div>
|
|
100
|
+
</ToastProvider>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
cy.contains('First Toast').should('be.visible');
|
|
104
|
+
cy.contains('Second Toast').should('be.visible');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('handles long content gracefully', () => {
|
|
108
|
+
cy.mountRadix(
|
|
109
|
+
<ToastProvider>
|
|
110
|
+
<ToastViewport />
|
|
111
|
+
<Toast open>
|
|
112
|
+
<ToastTitle>Long Title That Should Wrap Properly</ToastTitle>
|
|
113
|
+
<ToastDescription>
|
|
114
|
+
This is a very long description that contains a lot of text. It should wrap properly
|
|
115
|
+
within the toast container and maintain good readability. The toast component should
|
|
116
|
+
handle long content gracefully without breaking the layout.
|
|
117
|
+
</ToastDescription>
|
|
118
|
+
</Toast>
|
|
119
|
+
</ToastProvider>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
cy.get('[data-state="open"]').should('be.visible');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('works with custom className', () => {
|
|
126
|
+
cy.mountRadix(
|
|
127
|
+
<ToastProvider>
|
|
128
|
+
<ToastViewport className="custom-viewport" />
|
|
129
|
+
<Toast open className="custom-toast bg-blue-500">
|
|
130
|
+
<ToastTitle className="text-white">Custom Styled</ToastTitle>
|
|
131
|
+
<ToastDescription className="text-blue-100">Custom styled toast</ToastDescription>
|
|
132
|
+
</Toast>
|
|
133
|
+
</ToastProvider>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
cy.get('[data-state="open"]').should('have.class', 'custom-toast');
|
|
137
|
+
cy.get('[data-state="open"]').should('have.class', 'bg-blue-500');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('supports icons in toast', () => {
|
|
141
|
+
cy.mountRadix(
|
|
142
|
+
<ToastProvider>
|
|
143
|
+
<ToastViewport />
|
|
144
|
+
<Toast open>
|
|
145
|
+
<div className="flex items-center gap-2">
|
|
146
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
147
|
+
<path
|
|
148
|
+
strokeLinecap="round"
|
|
149
|
+
strokeLinejoin="round"
|
|
150
|
+
strokeWidth={2}
|
|
151
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
152
|
+
/>
|
|
153
|
+
</svg>
|
|
154
|
+
<div>
|
|
155
|
+
<ToastTitle>Success</ToastTitle>
|
|
156
|
+
<ToastDescription>Operation completed successfully</ToastDescription>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</Toast>
|
|
160
|
+
</ToastProvider>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
cy.get('svg').should('be.visible');
|
|
164
|
+
cy.contains('Success').should('be.visible');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('renders error toast variant', () => {
|
|
168
|
+
cy.mountRadix(
|
|
169
|
+
<ToastProvider>
|
|
170
|
+
<ToastViewport />
|
|
171
|
+
<Toast open variant="destructive">
|
|
172
|
+
<div className="flex items-center gap-2">
|
|
173
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
174
|
+
<path
|
|
175
|
+
strokeLinecap="round"
|
|
176
|
+
strokeLinejoin="round"
|
|
177
|
+
strokeWidth={2}
|
|
178
|
+
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
179
|
+
/>
|
|
180
|
+
</svg>
|
|
181
|
+
<div>
|
|
182
|
+
<ToastTitle>Error</ToastTitle>
|
|
183
|
+
<ToastDescription>Failed to save changes</ToastDescription>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<ToastClose />
|
|
187
|
+
</Toast>
|
|
188
|
+
</ToastProvider>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
cy.contains('Error').should('be.visible');
|
|
192
|
+
cy.contains('Failed to save changes').should('be.visible');
|
|
193
|
+
cy.get('button[type="button"]').should('exist');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('viewport positions toast correctly', () => {
|
|
197
|
+
cy.mountRadix(
|
|
198
|
+
<ToastProvider>
|
|
199
|
+
<ToastViewport className="top-0 right-0" />
|
|
200
|
+
<Toast open>
|
|
201
|
+
<ToastTitle>Positioned Toast</ToastTitle>
|
|
202
|
+
<ToastDescription>This toast is positioned</ToastDescription>
|
|
203
|
+
</Toast>
|
|
204
|
+
</ToastProvider>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
cy.get('[data-state="open"]').should('be.visible');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as ToastPrimitives from '@radix-ui/react-toast';
|
|
2
|
+
import { type VariantProps, cva } from 'class-variance-authority';
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
|
|
8
|
+
const ToastProvider = ToastPrimitives.Provider;
|
|
9
|
+
|
|
10
|
+
const ToastViewport = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
|
13
|
+
>(({ className, ...props }, ref) => (
|
|
14
|
+
<ToastPrimitives.Viewport
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
|
24
|
+
|
|
25
|
+
const toastVariants = cva(
|
|
26
|
+
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
|
27
|
+
{
|
|
28
|
+
variants: {
|
|
29
|
+
variant: {
|
|
30
|
+
default: 'border bg-background text-foreground',
|
|
31
|
+
destructive:
|
|
32
|
+
'destructive group border-destructive bg-destructive text-destructive-foreground',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: 'default',
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const Toast = React.forwardRef<
|
|
42
|
+
React.ElementRef<typeof ToastPrimitives.Root>,
|
|
43
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
|
44
|
+
>(({ className, variant, ...props }, ref) => {
|
|
45
|
+
return (
|
|
46
|
+
<ToastPrimitives.Root
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(toastVariants({ variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
Toast.displayName = ToastPrimitives.Root.displayName;
|
|
54
|
+
|
|
55
|
+
const ToastAction = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof ToastPrimitives.Action>,
|
|
57
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<ToastPrimitives.Action
|
|
60
|
+
ref={ref}
|
|
61
|
+
className={cn(
|
|
62
|
+
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
));
|
|
68
|
+
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
|
69
|
+
|
|
70
|
+
const ToastClose = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof ToastPrimitives.Close>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
|
73
|
+
>(({ className, ...props }, ref) => (
|
|
74
|
+
<ToastPrimitives.Close
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn(
|
|
77
|
+
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
toast-close=""
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
<X className="h-4 w-4" />
|
|
84
|
+
</ToastPrimitives.Close>
|
|
85
|
+
));
|
|
86
|
+
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
|
87
|
+
|
|
88
|
+
const ToastTitle = React.forwardRef<
|
|
89
|
+
React.ElementRef<typeof ToastPrimitives.Title>,
|
|
90
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
|
91
|
+
>(({ className, ...props }, ref) => (
|
|
92
|
+
<ToastPrimitives.Title
|
|
93
|
+
ref={ref}
|
|
94
|
+
className={cn('text-sm font-semibold [&+div]:text-xs', className)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
));
|
|
98
|
+
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
|
99
|
+
|
|
100
|
+
const ToastDescription = React.forwardRef<
|
|
101
|
+
React.ElementRef<typeof ToastPrimitives.Description>,
|
|
102
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
|
103
|
+
>(({ className, ...props }, ref) => (
|
|
104
|
+
<ToastPrimitives.Description
|
|
105
|
+
ref={ref}
|
|
106
|
+
className={cn('text-sm opacity-90', className)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
));
|
|
110
|
+
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
|
111
|
+
|
|
112
|
+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
|
113
|
+
|
|
114
|
+
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
type ToastProps,
|
|
118
|
+
type ToastActionElement,
|
|
119
|
+
ToastProvider,
|
|
120
|
+
ToastViewport,
|
|
121
|
+
Toast,
|
|
122
|
+
ToastTitle,
|
|
123
|
+
ToastDescription,
|
|
124
|
+
ToastClose,
|
|
125
|
+
ToastAction,
|
|
126
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Toast,
|
|
3
|
+
ToastClose,
|
|
4
|
+
ToastDescription,
|
|
5
|
+
ToastProvider,
|
|
6
|
+
ToastTitle,
|
|
7
|
+
ToastViewport,
|
|
8
|
+
} from '@/components/ui/toast';
|
|
9
|
+
import { useToast } from '@/hooks/use-toast';
|
|
10
|
+
|
|
11
|
+
export function Toaster() {
|
|
12
|
+
const { toasts } = useToast();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<ToastProvider>
|
|
16
|
+
{toasts.map(({ id, title, description, action, ...props }) => (
|
|
17
|
+
<Toast key={id} {...props}>
|
|
18
|
+
<div className="grid gap-1">
|
|
19
|
+
{title && <ToastTitle>{title}</ToastTitle>}
|
|
20
|
+
{description && <ToastDescription>{description}</ToastDescription>}
|
|
21
|
+
</div>
|
|
22
|
+
{action}
|
|
23
|
+
<ToastClose />
|
|
24
|
+
</Toast>
|
|
25
|
+
))}
|
|
26
|
+
<ToastViewport />
|
|
27
|
+
</ToastProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';
|
|
6
|
+
|
|
7
|
+
describe('Tooltip Component', () => {
|
|
8
|
+
it('renders tooltip on hover', () => {
|
|
9
|
+
cy.mount(
|
|
10
|
+
<TooltipProvider>
|
|
11
|
+
<Tooltip>
|
|
12
|
+
<TooltipTrigger>Hover me</TooltipTrigger>
|
|
13
|
+
<TooltipContent>
|
|
14
|
+
<p>Tooltip content</p>
|
|
15
|
+
</TooltipContent>
|
|
16
|
+
</Tooltip>
|
|
17
|
+
</TooltipProvider>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Just verify the trigger renders and basic structure exists
|
|
21
|
+
cy.contains('Hover me').should('be.visible');
|
|
22
|
+
cy.get('button').should('exist');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('supports custom content', () => {
|
|
26
|
+
cy.mount(
|
|
27
|
+
<TooltipProvider>
|
|
28
|
+
<Tooltip>
|
|
29
|
+
<TooltipTrigger>
|
|
30
|
+
<button>Click for info</button>
|
|
31
|
+
</TooltipTrigger>
|
|
32
|
+
<TooltipContent>
|
|
33
|
+
<div className="flex items-center gap-2">
|
|
34
|
+
<span>💡</span>
|
|
35
|
+
<span>This is helpful information</span>
|
|
36
|
+
</div>
|
|
37
|
+
</TooltipContent>
|
|
38
|
+
</Tooltip>
|
|
39
|
+
</TooltipProvider>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Verify the button renders and has tooltip structure
|
|
43
|
+
cy.get('button').should('be.visible');
|
|
44
|
+
cy.contains('Click for info').should('exist');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('works with disabled elements', () => {
|
|
48
|
+
cy.mount(
|
|
49
|
+
<TooltipProvider>
|
|
50
|
+
<Tooltip>
|
|
51
|
+
<TooltipTrigger asChild>
|
|
52
|
+
<button disabled className="opacity-50">
|
|
53
|
+
Disabled button
|
|
54
|
+
</button>
|
|
55
|
+
</TooltipTrigger>
|
|
56
|
+
<TooltipContent>
|
|
57
|
+
<p>This button is disabled</p>
|
|
58
|
+
</TooltipContent>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
</TooltipProvider>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Verify disabled button exists and tooltip structure
|
|
64
|
+
cy.get('button').should('be.disabled');
|
|
65
|
+
cy.get('button').should('have.class', 'opacity-50');
|
|
66
|
+
cy.contains('Disabled button').should('exist');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('supports different positions', () => {
|
|
70
|
+
cy.mount(
|
|
71
|
+
<TooltipProvider>
|
|
72
|
+
<div className="flex gap-8 p-20">
|
|
73
|
+
<Tooltip>
|
|
74
|
+
<TooltipTrigger>Top</TooltipTrigger>
|
|
75
|
+
<TooltipContent side="top">
|
|
76
|
+
<p>Tooltip on top</p>
|
|
77
|
+
</TooltipContent>
|
|
78
|
+
</Tooltip>
|
|
79
|
+
|
|
80
|
+
<Tooltip>
|
|
81
|
+
<TooltipTrigger>Bottom</TooltipTrigger>
|
|
82
|
+
<TooltipContent side="bottom">
|
|
83
|
+
<p>Tooltip on bottom</p>
|
|
84
|
+
</TooltipContent>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
|
|
87
|
+
<Tooltip>
|
|
88
|
+
<TooltipTrigger>Left</TooltipTrigger>
|
|
89
|
+
<TooltipContent side="left">
|
|
90
|
+
<p>Tooltip on left</p>
|
|
91
|
+
</TooltipContent>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
|
|
94
|
+
<Tooltip>
|
|
95
|
+
<TooltipTrigger>Right</TooltipTrigger>
|
|
96
|
+
<TooltipContent side="right">
|
|
97
|
+
<p>Tooltip on right</p>
|
|
98
|
+
</TooltipContent>
|
|
99
|
+
</Tooltip>
|
|
100
|
+
</div>
|
|
101
|
+
</TooltipProvider>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Verify all position triggers render
|
|
105
|
+
cy.contains('Top').should('be.visible');
|
|
106
|
+
cy.contains('Bottom').should('be.visible');
|
|
107
|
+
cy.contains('Left').should('be.visible');
|
|
108
|
+
cy.contains('Right').should('be.visible');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('supports custom delay', () => {
|
|
112
|
+
cy.mount(
|
|
113
|
+
<TooltipProvider delayDuration={200}>
|
|
114
|
+
<Tooltip>
|
|
115
|
+
<TooltipTrigger>Quick tooltip</TooltipTrigger>
|
|
116
|
+
<TooltipContent>
|
|
117
|
+
<p>Shows after 200ms</p>
|
|
118
|
+
</TooltipContent>
|
|
119
|
+
</Tooltip>
|
|
120
|
+
</TooltipProvider>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Verify tooltip with delay configuration renders
|
|
124
|
+
cy.contains('Quick tooltip').should('be.visible');
|
|
125
|
+
cy.get('button').should('exist');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('works with icons as triggers', () => {
|
|
129
|
+
cy.mount(
|
|
130
|
+
<TooltipProvider>
|
|
131
|
+
<Tooltip>
|
|
132
|
+
<TooltipTrigger>
|
|
133
|
+
<svg width="20" height="20" viewBox="0 0 20 20" data-testid="info-icon">
|
|
134
|
+
<circle cx="10" cy="10" r="10" fill="currentColor" />
|
|
135
|
+
</svg>
|
|
136
|
+
</TooltipTrigger>
|
|
137
|
+
<TooltipContent>
|
|
138
|
+
<p>Information tooltip</p>
|
|
139
|
+
</TooltipContent>
|
|
140
|
+
</Tooltip>
|
|
141
|
+
</TooltipProvider>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Verify icon trigger renders correctly
|
|
145
|
+
cy.get('[data-testid="info-icon"]').should('exist');
|
|
146
|
+
cy.get('svg').should('be.visible');
|
|
147
|
+
cy.get('circle').should('exist');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('supports keyboard navigation', () => {
|
|
151
|
+
cy.mount(
|
|
152
|
+
<TooltipProvider>
|
|
153
|
+
<Tooltip>
|
|
154
|
+
<TooltipTrigger>
|
|
155
|
+
<button>Tab to me</button>
|
|
156
|
+
</TooltipTrigger>
|
|
157
|
+
<TooltipContent>
|
|
158
|
+
<p>Keyboard accessible</p>
|
|
159
|
+
</TooltipContent>
|
|
160
|
+
</Tooltip>
|
|
161
|
+
</TooltipProvider>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Verify button can be focused for keyboard accessibility
|
|
165
|
+
cy.get('button').should('exist');
|
|
166
|
+
cy.get('button').first().focus();
|
|
167
|
+
cy.get('button').first().should('have.focus');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('handles long content', () => {
|
|
171
|
+
cy.mount(
|
|
172
|
+
<TooltipProvider>
|
|
173
|
+
<Tooltip>
|
|
174
|
+
<TooltipTrigger>Long tooltip</TooltipTrigger>
|
|
175
|
+
<TooltipContent className="max-w-xs">
|
|
176
|
+
<p>
|
|
177
|
+
This is a very long tooltip content that should wrap properly and not exceed the
|
|
178
|
+
maximum width constraint we have set.
|
|
179
|
+
</p>
|
|
180
|
+
</TooltipContent>
|
|
181
|
+
</Tooltip>
|
|
182
|
+
</TooltipProvider>
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Verify trigger and structure for long content
|
|
186
|
+
cy.contains('Long tooltip').should('be.visible');
|
|
187
|
+
cy.get('button').should('exist');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('works in forms', () => {
|
|
191
|
+
cy.mount(
|
|
192
|
+
<TooltipProvider>
|
|
193
|
+
<form className="space-y-4">
|
|
194
|
+
<div className="flex items-center gap-2">
|
|
195
|
+
<label htmlFor="email">Email</label>
|
|
196
|
+
<Tooltip>
|
|
197
|
+
<TooltipTrigger type="button">
|
|
198
|
+
<span className="text-gray-400">?</span>
|
|
199
|
+
</TooltipTrigger>
|
|
200
|
+
<TooltipContent>
|
|
201
|
+
<p>Enter your email address</p>
|
|
202
|
+
</TooltipContent>
|
|
203
|
+
</Tooltip>
|
|
204
|
+
</div>
|
|
205
|
+
<input id="email" type="email" className="border rounded px-2 py-1" />
|
|
206
|
+
</form>
|
|
207
|
+
</TooltipProvider>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Verify form integration works
|
|
211
|
+
cy.get('form').should('exist');
|
|
212
|
+
cy.get('label[for="email"]').should('contain', 'Email');
|
|
213
|
+
cy.contains('?').should('be.visible');
|
|
214
|
+
cy.get('input[type="email"]').should('exist');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('can be controlled programmatically', () => {
|
|
218
|
+
const ControlledTooltip = () => {
|
|
219
|
+
const [open, setOpen] = React.useState(false);
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<TooltipProvider>
|
|
223
|
+
<div>
|
|
224
|
+
<button onClick={() => setOpen(!open)}>Toggle tooltip</button>
|
|
225
|
+
<Tooltip open={open}>
|
|
226
|
+
<TooltipTrigger>Target</TooltipTrigger>
|
|
227
|
+
<TooltipContent>
|
|
228
|
+
<p>Controlled tooltip</p>
|
|
229
|
+
</TooltipContent>
|
|
230
|
+
</Tooltip>
|
|
231
|
+
</div>
|
|
232
|
+
</TooltipProvider>
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
cy.mount(<ControlledTooltip />);
|
|
237
|
+
|
|
238
|
+
cy.contains('Controlled tooltip').should('not.exist');
|
|
239
|
+
cy.contains('Toggle tooltip').click();
|
|
240
|
+
cy.contains('Controlled tooltip').should('be.visible');
|
|
241
|
+
cy.contains('Toggle tooltip').click();
|
|
242
|
+
cy.contains('Controlled tooltip').should('not.exist');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
7
|
+
|
|
8
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
11
|
+
|
|
12
|
+
const TooltipContent = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
15
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
16
|
+
<TooltipPrimitive.Portal>
|
|
17
|
+
<TooltipPrimitive.Content
|
|
18
|
+
ref={ref}
|
|
19
|
+
sideOffset={sideOffset}
|
|
20
|
+
className={cn(
|
|
21
|
+
'z-50 overflow-hidden select-none rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
</TooltipPrimitive.Portal>
|
|
27
|
+
));
|
|
28
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
29
|
+
|
|
30
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|