@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,57 @@
1
+ import * as AvatarPrimitive from '@radix-ui/react-avatar';
2
+ import * as React from 'react';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ interface AvatarProps extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {
7
+ 'data-testid'?: string;
8
+ }
9
+
10
+ const Avatar = React.forwardRef<React.ElementRef<typeof AvatarPrimitive.Root>, AvatarProps>(
11
+ ({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-md', className)}
15
+ data-testid={props['data-testid'] || 'avatar'}
16
+ {...props}
17
+ />
18
+ )
19
+ );
20
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
21
+
22
+ interface AvatarImageProps extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> {
23
+ 'data-testid'?: string;
24
+ }
25
+
26
+ const AvatarImage = React.forwardRef<
27
+ React.ElementRef<typeof AvatarPrimitive.Image>,
28
+ AvatarImageProps
29
+ >(({ className, ...props }, ref) => (
30
+ <AvatarPrimitive.Image
31
+ ref={ref}
32
+ className={cn('aspect-square h-full w-full object-cover', className)}
33
+ data-testid={props['data-testid'] || 'avatar-image'}
34
+ {...props}
35
+ />
36
+ ));
37
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
38
+
39
+ interface AvatarFallbackProps
40
+ extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> {
41
+ 'data-testid'?: string;
42
+ }
43
+
44
+ const AvatarFallback = React.forwardRef<
45
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
46
+ AvatarFallbackProps
47
+ >(({ className, ...props }, ref) => (
48
+ <AvatarPrimitive.Fallback
49
+ ref={ref}
50
+ className={cn('flex h-full w-full items-center justify-center rounded-md bg-muted', className)}
51
+ data-testid={props['data-testid'] || 'avatar-fallback'}
52
+ {...props}
53
+ />
54
+ ));
55
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
56
+
57
+ export { Avatar, AvatarImage, AvatarFallback };
@@ -0,0 +1,146 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Badge, badgeVariants } from './badge';
6
+
7
+ describe('Badge Component', () => {
8
+ it('renders correctly with default props', () => {
9
+ cy.mount(<Badge>Default Badge</Badge>);
10
+
11
+ cy.get('[data-testid="badge"]').should('exist');
12
+ cy.get('[data-testid="badge"]').should('contain', 'Default Badge');
13
+ cy.get('[data-testid="badge"]').should('have.class', 'inline-flex');
14
+ });
15
+
16
+ describe('Variants', () => {
17
+ it('renders default variant correctly', () => {
18
+ cy.mount(<Badge variant="default">Default</Badge>);
19
+
20
+ cy.get('[data-testid="badge"]')
21
+ .should('have.class', 'bg-primary')
22
+ .should('have.class', 'text-primary-foreground');
23
+ });
24
+
25
+ it('renders secondary variant correctly', () => {
26
+ cy.mount(<Badge variant="secondary">Secondary</Badge>);
27
+
28
+ cy.get('[data-testid="badge"]')
29
+ .should('have.class', 'bg-secondary')
30
+ .should('have.class', 'text-secondary-foreground');
31
+ });
32
+
33
+ it('renders destructive variant correctly', () => {
34
+ cy.mount(<Badge variant="destructive">Destructive</Badge>);
35
+
36
+ cy.get('[data-testid="badge"]')
37
+ .should('have.class', 'bg-destructive')
38
+ .should('have.class', 'text-destructive-foreground');
39
+ });
40
+
41
+ it('renders outline variant correctly', () => {
42
+ cy.mount(<Badge variant="outline">Outline</Badge>);
43
+
44
+ cy.get('[data-testid="badge"]').should('have.class', 'border');
45
+ });
46
+ });
47
+
48
+ it('applies custom className', () => {
49
+ cy.mount(<Badge className="custom-badge ml-2">Custom</Badge>);
50
+
51
+ cy.get('[data-testid="badge"]')
52
+ .should('have.class', 'custom-badge')
53
+ .should('have.class', 'ml-2');
54
+ });
55
+
56
+ it('renders with different content types', () => {
57
+ cy.mount(
58
+ <div>
59
+ <Badge>Text only</Badge>
60
+ <Badge>
61
+ <span>With span</span>
62
+ </Badge>
63
+ <Badge>
64
+ <span className="mr-1">🔥</span>
65
+ With emoji
66
+ </Badge>
67
+ </div>
68
+ );
69
+
70
+ cy.get('[data-testid="badge"]').should('have.length', 3);
71
+ cy.get('[data-testid="badge"]').first().should('contain', 'Text only');
72
+ cy.get('[data-testid="badge"]').eq(1).find('span').should('exist');
73
+ cy.get('[data-testid="badge"]').last().should('contain', '🔥');
74
+ });
75
+
76
+ it('renders inline with text', () => {
77
+ cy.mount(
78
+ <p>
79
+ This is some text with a <Badge>badge</Badge> inline.
80
+ </p>
81
+ );
82
+
83
+ cy.get('[data-testid="badge"]').should('be.visible');
84
+ cy.get('p').should('contain', 'This is some text with a');
85
+ cy.get('p').should('contain', 'inline.');
86
+ });
87
+
88
+ it('can be used as a clickable element', () => {
89
+ const onClick = cy.stub();
90
+
91
+ cy.mount(
92
+ <Badge className="cursor-pointer" onClick={onClick}>
93
+ Clickable
94
+ </Badge>
95
+ );
96
+
97
+ cy.get('[data-testid="badge"]').click();
98
+ cy.wrap(onClick).should('have.been.calledOnce');
99
+ });
100
+
101
+ it('renders multiple badges in a group', () => {
102
+ cy.mount(
103
+ <div className="flex gap-2">
104
+ <Badge>Badge 1</Badge>
105
+ <Badge variant="secondary">Badge 2</Badge>
106
+ <Badge variant="outline">Badge 3</Badge>
107
+ </div>
108
+ );
109
+
110
+ cy.get('[data-testid="badge"]').should('have.length', 3);
111
+ cy.get('.flex.gap-2').should('exist');
112
+ });
113
+
114
+ it('supports data attributes', () => {
115
+ cy.mount(
116
+ <Badge data-testid="custom-badge" data-status="active">
117
+ Active
118
+ </Badge>
119
+ );
120
+
121
+ cy.get('[data-testid="custom-badge"]')
122
+ .should('exist')
123
+ .should('have.attr', 'data-status', 'active');
124
+ });
125
+
126
+ it('has correct accessibility attributes', () => {
127
+ cy.mount(
128
+ <Badge role="status" aria-label="User status">
129
+ Online
130
+ </Badge>
131
+ );
132
+
133
+ cy.get('[data-testid="badge"]')
134
+ .should('have.attr', 'role', 'status')
135
+ .should('have.attr', 'aria-label', 'User status');
136
+ });
137
+
138
+ it('badgeVariants function works correctly', () => {
139
+ // Test the badgeVariants function directly
140
+ const defaultClasses = badgeVariants({ variant: 'default' });
141
+ const secondaryClasses = badgeVariants({ variant: 'secondary' });
142
+
143
+ expect(defaultClasses).to.include('bg-primary');
144
+ expect(secondaryClasses).to.include('bg-secondary');
145
+ });
146
+ });
@@ -0,0 +1,43 @@
1
+ import { type VariantProps, cva } from 'class-variance-authority';
2
+ import type * as React from 'react';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const badgeVariants = cva(
7
+ 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
12
+ secondary:
13
+ 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
14
+ destructive:
15
+ 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
16
+ outline: 'text-foreground',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: 'default',
21
+ },
22
+ }
23
+ );
24
+
25
+ export interface BadgeProps
26
+ extends React.HTMLAttributes<HTMLDivElement>,
27
+ VariantProps<typeof badgeVariants> {}
28
+
29
+ interface ExtendedBadgeProps extends BadgeProps {
30
+ 'data-testid'?: string;
31
+ }
32
+
33
+ function Badge({ className, variant, ...props }: ExtendedBadgeProps) {
34
+ return (
35
+ <div
36
+ className={cn(badgeVariants({ variant }), className)}
37
+ data-testid={props['data-testid'] || 'badge'}
38
+ {...props}
39
+ />
40
+ );
41
+ }
42
+
43
+ export { Badge, badgeVariants };
@@ -0,0 +1,177 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Button } from './button';
6
+
7
+ describe('Button Component', () => {
8
+ // Test basic rendering
9
+ it('renders correctly with default props', () => {
10
+ cy.mount(<Button>Click me</Button>);
11
+
12
+ cy.get('[data-slot="button"]')
13
+ .should('exist')
14
+ .should('have.text', 'Click me')
15
+ .should('have.class', 'bg-primary');
16
+ });
17
+
18
+ // Test all variants
19
+ describe('Variants', () => {
20
+ const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'] as const;
21
+
22
+ variants.forEach((variant) => {
23
+ it(`renders ${variant} variant correctly`, () => {
24
+ cy.mount(<Button variant={variant}>{variant} Button</Button>);
25
+
26
+ cy.get('[data-slot="button"]').should('exist').should('contain.text', `${variant} Button`);
27
+
28
+ // Take a screenshot for visual regression
29
+ cy.screenshot(`button-variant-${variant}`);
30
+ });
31
+ });
32
+ });
33
+
34
+ // Test all sizes
35
+ describe('Sizes', () => {
36
+ const sizes = ['default', 'sm', 'lg', 'icon'] as const;
37
+
38
+ sizes.forEach((size) => {
39
+ it(`renders ${size} size correctly`, () => {
40
+ cy.mount(<Button size={size}>{size === 'icon' ? '🚀' : `${size} Button`}</Button>);
41
+
42
+ cy.get('[data-slot="button"]').should('exist');
43
+
44
+ // Verify specific size classes
45
+ if (size === 'sm') {
46
+ cy.get('[data-slot="button"]').should('have.class', 'h-8');
47
+ } else if (size === 'lg') {
48
+ cy.get('[data-slot="button"]').should('have.class', 'h-10');
49
+ } else if (size === 'icon') {
50
+ cy.get('[data-slot="button"]').should('have.class', 'h-auto').and('have.class', 'w-auto');
51
+ } else {
52
+ cy.get('[data-slot="button"]').should('have.class', 'h-9');
53
+ }
54
+ });
55
+ });
56
+ });
57
+
58
+ // Test interactions
59
+ describe('Interactions', () => {
60
+ it('handles click events', () => {
61
+ const onClick = cy.stub();
62
+
63
+ cy.mount(<Button onClick={onClick}>Click me</Button>);
64
+
65
+ cy.get('[data-slot="button"]').click();
66
+ cy.wrap(onClick).should('have.been.calledOnce');
67
+ });
68
+
69
+ it('can be disabled', () => {
70
+ const onClick = cy.stub();
71
+
72
+ cy.mount(
73
+ <Button disabled onClick={onClick}>
74
+ Disabled Button
75
+ </Button>
76
+ );
77
+
78
+ cy.get('[data-slot="button"]')
79
+ .should('be.disabled')
80
+ .should('have.class', 'disabled:opacity-50')
81
+ .click({ force: true });
82
+
83
+ cy.wrap(onClick).should('not.have.been.called');
84
+ });
85
+
86
+ it('supports keyboard navigation', () => {
87
+ const onClick = cy.stub();
88
+
89
+ cy.mount(<Button onClick={onClick}>Keyboard Button</Button>);
90
+
91
+ // Trigger click with keyboard
92
+ cy.get('[data-slot="button"]')
93
+ .focus()
94
+ .trigger('keydown', { key: 'Enter', code: 'Enter' })
95
+ .trigger('click');
96
+
97
+ cy.wrap(onClick).should('have.been.calledOnce');
98
+ });
99
+ });
100
+
101
+ // Test asChild prop
102
+ describe('asChild prop', () => {
103
+ it('renders as a different element when asChild is true', () => {
104
+ cy.mount(
105
+ <Button asChild>
106
+ <a href="/test">Link Button</a>
107
+ </Button>
108
+ );
109
+
110
+ cy.get('a[data-slot="button"]')
111
+ .should('exist')
112
+ .should('have.attr', 'href', '/test')
113
+ .should('have.text', 'Link Button');
114
+ });
115
+ });
116
+
117
+ // Test with icons
118
+ describe('With Icons', () => {
119
+ it('renders correctly with an icon', () => {
120
+ cy.mount(
121
+ <Button>
122
+ <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24">
123
+ <path d="M12 4v16m8-8H4" stroke="currentColor" strokeWidth={2} />
124
+ </svg>
125
+ With Icon
126
+ </Button>
127
+ );
128
+
129
+ cy.get('[data-slot="button"]').should('contain.text', 'With Icon');
130
+ cy.get('[data-slot="button"] svg').should('exist');
131
+ });
132
+ });
133
+
134
+ // Test custom className
135
+ describe('Custom styling', () => {
136
+ it('accepts custom className', () => {
137
+ cy.mount(<Button className="custom-class bg-blue-500">Custom Styled</Button>);
138
+
139
+ cy.get('[data-slot="button"]')
140
+ .should('have.class', 'custom-class')
141
+ .should('have.class', 'bg-blue-500');
142
+ });
143
+ });
144
+
145
+ // Test focus states
146
+ describe('Focus states', () => {
147
+ it('shows focus ring when focused', () => {
148
+ cy.mount(<Button>Focus me</Button>);
149
+
150
+ cy.get('[data-slot="button"]').focus().should('have.class', 'focus-visible:ring-ring/50');
151
+ });
152
+ });
153
+
154
+ // Test loading state (if supported)
155
+ describe('Loading state', () => {
156
+ it('can show loading state', () => {
157
+ cy.mount(
158
+ <Button disabled>
159
+ <svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
160
+ <circle
161
+ className="opacity-25"
162
+ cx="12"
163
+ cy="12"
164
+ r="10"
165
+ stroke="currentColor"
166
+ strokeWidth="4"
167
+ />
168
+ </svg>
169
+ Loading...
170
+ </Button>
171
+ );
172
+
173
+ cy.get('[data-slot="button"]').should('be.disabled').should('contain.text', 'Loading...');
174
+ cy.get('[data-slot="button"] svg').should('have.class', 'animate-spin');
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '@/lib/utils';
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] cursor-pointer",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-red-800 text-white shadow-xs hover:bg-red-600 focus-visible:ring-red-400/20 dark:focus-visible:ring-red-500',
15
+ outline:
16
+ 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
17
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
18
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
19
+ link: 'text-primary underline-offset-4 hover:underline',
20
+ },
21
+ size: {
22
+ default: 'h-9 px-4 py-2',
23
+ sm: 'h-8 rounded-md px-3 text-xs',
24
+ lg: 'h-10 rounded-md px-8',
25
+ icon: 'h-auto w-auto',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'default',
30
+ size: 'default',
31
+ },
32
+ }
33
+ );
34
+
35
+ function Button({
36
+ className,
37
+ variant,
38
+ size,
39
+ asChild = false,
40
+ ...props
41
+ }: React.ComponentProps<'button'> &
42
+ VariantProps<typeof buttonVariants> & {
43
+ asChild?: boolean;
44
+ }) {
45
+ const Comp = asChild ? Slot : 'button';
46
+
47
+ return (
48
+ <Comp
49
+ data-slot="button"
50
+ className={cn(buttonVariants({ variant, size, className }))}
51
+ {...props}
52
+ />
53
+ );
54
+ }
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,160 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card';
6
+
7
+ describe('Card Component', () => {
8
+ it('renders basic card correctly', () => {
9
+ cy.mount(
10
+ <Card>
11
+ <CardContent>Basic Card Content</CardContent>
12
+ </Card>
13
+ );
14
+
15
+ cy.get('[data-slot="card"]').should('exist');
16
+ cy.contains('Basic Card Content').should('be.visible');
17
+ });
18
+
19
+ it('renders complete card with all sections', () => {
20
+ cy.mount(
21
+ <Card>
22
+ <CardHeader>
23
+ <CardTitle>Card Title</CardTitle>
24
+ <CardDescription>Card Description</CardDescription>
25
+ </CardHeader>
26
+ <CardContent>
27
+ <p>Card content goes here</p>
28
+ </CardContent>
29
+ <CardFooter>
30
+ <button>Action</button>
31
+ </CardFooter>
32
+ </Card>
33
+ );
34
+
35
+ // Check all sections are rendered
36
+ cy.get('[data-slot="card"]').should('exist');
37
+ cy.get('[data-slot="card-header"]').should('exist');
38
+ cy.get('[data-slot="card-title"]').should('contain', 'Card Title');
39
+ cy.get('[data-slot="card-description"]').should('contain', 'Card Description');
40
+ cy.get('[data-slot="card-content"]').should('contain', 'Card content goes here');
41
+ cy.get('[data-slot="card-footer"]').should('contain', 'Action');
42
+ });
43
+
44
+ it('applies custom className to card', () => {
45
+ cy.mount(
46
+ <Card className="custom-card bg-blue-500">
47
+ <CardContent>Custom styled card</CardContent>
48
+ </Card>
49
+ );
50
+
51
+ cy.get('[data-slot="card"]')
52
+ .should('have.class', 'custom-card')
53
+ .should('have.class', 'bg-blue-500');
54
+ });
55
+
56
+ it('renders card without header', () => {
57
+ cy.mount(
58
+ <Card>
59
+ <CardContent>No header card</CardContent>
60
+ <CardFooter>Footer only</CardFooter>
61
+ </Card>
62
+ );
63
+
64
+ cy.get('[data-slot="card-header"]').should('not.exist');
65
+ cy.get('[data-slot="card-content"]').should('exist');
66
+ cy.get('[data-slot="card-footer"]').should('exist');
67
+ });
68
+
69
+ it('renders card without footer', () => {
70
+ cy.mount(
71
+ <Card>
72
+ <CardHeader>
73
+ <CardTitle>Header Only</CardTitle>
74
+ </CardHeader>
75
+ <CardContent>No footer card</CardContent>
76
+ </Card>
77
+ );
78
+
79
+ cy.get('[data-slot="card-header"]').should('exist');
80
+ cy.get('[data-slot="card-content"]').should('exist');
81
+ cy.get('[data-slot="card-footer"]').should('not.exist');
82
+ });
83
+
84
+ it('supports nested content in card sections', () => {
85
+ cy.mount(
86
+ <Card>
87
+ <CardHeader>
88
+ <CardTitle>Complex Card</CardTitle>
89
+ <CardDescription>
90
+ <span className="text-red-500">Colored</span> description
91
+ </CardDescription>
92
+ </CardHeader>
93
+ <CardContent>
94
+ <div className="grid gap-2">
95
+ <div>Row 1</div>
96
+ <div>Row 2</div>
97
+ </div>
98
+ </CardContent>
99
+ </Card>
100
+ );
101
+
102
+ // Check nested content renders correctly
103
+ cy.get('.text-red-500').should('contain', 'Colored');
104
+ cy.get('.grid').should('exist');
105
+ cy.get('.grid').children().should('have.length', 2);
106
+ });
107
+
108
+ it('handles click events on card', () => {
109
+ const onClick = cy.stub();
110
+
111
+ cy.mount(
112
+ <Card onClick={onClick} className="cursor-pointer">
113
+ <CardContent>Clickable Card</CardContent>
114
+ </Card>
115
+ );
116
+
117
+ cy.get('[data-slot="card"]').click();
118
+ cy.wrap(onClick).should('have.been.calledOnce');
119
+ });
120
+
121
+ it('renders multiple cards in a grid', () => {
122
+ cy.mount(
123
+ <div className="grid grid-cols-3 gap-4">
124
+ <Card>
125
+ <CardContent>Card 1</CardContent>
126
+ </Card>
127
+ <Card>
128
+ <CardContent>Card 2</CardContent>
129
+ </Card>
130
+ <Card>
131
+ <CardContent>Card 3</CardContent>
132
+ </Card>
133
+ </div>
134
+ );
135
+
136
+ cy.get('[data-slot="card"]').should('have.length', 3);
137
+ });
138
+
139
+ it('applies hover styles', () => {
140
+ cy.mount(
141
+ <Card className="hover:shadow-lg transition-shadow">
142
+ <CardContent>Hover me</CardContent>
143
+ </Card>
144
+ );
145
+
146
+ cy.get('[data-slot="card"]')
147
+ .should('have.class', 'hover:shadow-lg')
148
+ .should('have.class', 'transition-shadow');
149
+ });
150
+
151
+ it('supports data attributes', () => {
152
+ cy.mount(
153
+ <Card data-testid="custom-card" data-id="123">
154
+ <CardContent>Card with data attributes</CardContent>
155
+ </Card>
156
+ );
157
+
158
+ cy.get('[data-testid="custom-card"]').should('exist').should('have.attr', 'data-id', '123');
159
+ });
160
+ });