@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,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 };