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