@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,271 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs';
|
|
6
|
+
|
|
7
|
+
describe('Tabs Component', () => {
|
|
8
|
+
it('renders basic tabs correctly', () => {
|
|
9
|
+
cy.mountRadix(
|
|
10
|
+
<Tabs defaultValue="tab1">
|
|
11
|
+
<TabsList>
|
|
12
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
13
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
14
|
+
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
|
15
|
+
</TabsList>
|
|
16
|
+
<TabsContent value="tab1">Content for tab 1</TabsContent>
|
|
17
|
+
<TabsContent value="tab2">Content for tab 2</TabsContent>
|
|
18
|
+
<TabsContent value="tab3">Content for tab 3</TabsContent>
|
|
19
|
+
</Tabs>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Check tabs are rendered
|
|
23
|
+
cy.contains('Tab 1').should('be.visible');
|
|
24
|
+
cy.contains('Tab 2').should('be.visible');
|
|
25
|
+
cy.contains('Tab 3').should('be.visible');
|
|
26
|
+
|
|
27
|
+
// Check default content is visible
|
|
28
|
+
cy.contains('Content for tab 1').should('be.visible');
|
|
29
|
+
cy.contains('Content for tab 2').should('not.exist');
|
|
30
|
+
cy.contains('Content for tab 3').should('not.exist');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('switches between tabs on click', () => {
|
|
34
|
+
cy.mountRadix(
|
|
35
|
+
<Tabs defaultValue="home">
|
|
36
|
+
<TabsList>
|
|
37
|
+
<TabsTrigger value="home">Home</TabsTrigger>
|
|
38
|
+
<TabsTrigger value="profile">Profile</TabsTrigger>
|
|
39
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
40
|
+
</TabsList>
|
|
41
|
+
<TabsContent value="home">Home content</TabsContent>
|
|
42
|
+
<TabsContent value="profile">Profile content</TabsContent>
|
|
43
|
+
<TabsContent value="settings">Settings content</TabsContent>
|
|
44
|
+
</Tabs>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Initially home tab is active
|
|
48
|
+
cy.contains('Home content').should('be.visible');
|
|
49
|
+
|
|
50
|
+
// Click profile tab
|
|
51
|
+
cy.contains('button', 'Profile').click();
|
|
52
|
+
cy.contains('Profile content').should('be.visible');
|
|
53
|
+
cy.contains('Home content').should('not.exist');
|
|
54
|
+
|
|
55
|
+
// Click settings tab
|
|
56
|
+
cy.contains('button', 'Settings').click();
|
|
57
|
+
cy.contains('Settings content').should('be.visible');
|
|
58
|
+
cy.contains('Profile content').should('not.exist');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('works as controlled component', () => {
|
|
62
|
+
const TestComponent = () => {
|
|
63
|
+
const [activeTab, setActiveTab] = React.useState('tab1');
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
<p>Active tab: {activeTab}</p>
|
|
68
|
+
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
69
|
+
<TabsList>
|
|
70
|
+
<TabsTrigger value="tab1">First</TabsTrigger>
|
|
71
|
+
<TabsTrigger value="tab2">Second</TabsTrigger>
|
|
72
|
+
</TabsList>
|
|
73
|
+
<TabsContent value="tab1">First content</TabsContent>
|
|
74
|
+
<TabsContent value="tab2">Second content</TabsContent>
|
|
75
|
+
</Tabs>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
cy.mountRadix(<TestComponent />);
|
|
81
|
+
|
|
82
|
+
cy.contains('Active tab: tab1').should('be.visible');
|
|
83
|
+
cy.contains('First content').should('be.visible');
|
|
84
|
+
|
|
85
|
+
cy.contains('button', 'Second').click();
|
|
86
|
+
cy.contains('Active tab: tab2').should('be.visible');
|
|
87
|
+
cy.contains('Second content').should('be.visible');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('supports disabled tabs', () => {
|
|
91
|
+
cy.mountRadix(
|
|
92
|
+
<Tabs defaultValue="enabled">
|
|
93
|
+
<TabsList>
|
|
94
|
+
<TabsTrigger value="enabled">Enabled</TabsTrigger>
|
|
95
|
+
<TabsTrigger value="disabled" disabled>
|
|
96
|
+
Disabled
|
|
97
|
+
</TabsTrigger>
|
|
98
|
+
<TabsTrigger value="another">Another</TabsTrigger>
|
|
99
|
+
</TabsList>
|
|
100
|
+
<TabsContent value="enabled">Enabled content</TabsContent>
|
|
101
|
+
<TabsContent value="disabled">Disabled content</TabsContent>
|
|
102
|
+
<TabsContent value="another">Another content</TabsContent>
|
|
103
|
+
</Tabs>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
cy.contains('button', 'Disabled').should('be.disabled');
|
|
107
|
+
cy.contains('button', 'Disabled').click({ force: true });
|
|
108
|
+
cy.contains('Disabled content').should('not.exist');
|
|
109
|
+
cy.contains('Enabled content').should('be.visible');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('applies custom className', () => {
|
|
113
|
+
cy.mountRadix(
|
|
114
|
+
<Tabs defaultValue="tab1" className="custom-tabs">
|
|
115
|
+
<TabsList className="custom-list bg-gray-200">
|
|
116
|
+
<TabsTrigger value="tab1" className="custom-trigger">
|
|
117
|
+
Tab 1
|
|
118
|
+
</TabsTrigger>
|
|
119
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
120
|
+
</TabsList>
|
|
121
|
+
<TabsContent value="tab1" className="custom-content p-4">
|
|
122
|
+
Content 1
|
|
123
|
+
</TabsContent>
|
|
124
|
+
<TabsContent value="tab2">Content 2</TabsContent>
|
|
125
|
+
</Tabs>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
cy.get('.custom-tabs').should('exist');
|
|
129
|
+
cy.get('.custom-list').should('have.class', 'bg-gray-200');
|
|
130
|
+
cy.get('.custom-trigger').should('exist');
|
|
131
|
+
cy.get('.custom-content').should('have.class', 'p-4');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles keyboard navigation', () => {
|
|
135
|
+
cy.mountRadix(
|
|
136
|
+
<Tabs defaultValue="tab1">
|
|
137
|
+
<TabsList>
|
|
138
|
+
<TabsTrigger value="tab1">First</TabsTrigger>
|
|
139
|
+
<TabsTrigger value="tab2">Second</TabsTrigger>
|
|
140
|
+
<TabsTrigger value="tab3">Third</TabsTrigger>
|
|
141
|
+
</TabsList>
|
|
142
|
+
<TabsContent value="tab1">First content</TabsContent>
|
|
143
|
+
<TabsContent value="tab2">Second content</TabsContent>
|
|
144
|
+
<TabsContent value="tab3">Third content</TabsContent>
|
|
145
|
+
</Tabs>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Focus first tab
|
|
149
|
+
cy.contains('button', 'First').focus();
|
|
150
|
+
|
|
151
|
+
// Arrow right should move to next tab
|
|
152
|
+
cy.focused().type('{rightarrow}');
|
|
153
|
+
cy.focused().should('contain', 'Second');
|
|
154
|
+
|
|
155
|
+
// Arrow right again
|
|
156
|
+
cy.focused().type('{rightarrow}');
|
|
157
|
+
cy.focused().should('contain', 'Third');
|
|
158
|
+
|
|
159
|
+
// Arrow left should move back
|
|
160
|
+
cy.focused().type('{leftarrow}');
|
|
161
|
+
cy.focused().should('contain', 'Second');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('supports orientation prop', () => {
|
|
165
|
+
cy.mountRadix(
|
|
166
|
+
<Tabs defaultValue="tab1" orientation="vertical">
|
|
167
|
+
<TabsList>
|
|
168
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
169
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
170
|
+
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
|
|
171
|
+
</TabsList>
|
|
172
|
+
<TabsContent value="tab1">Content 1</TabsContent>
|
|
173
|
+
<TabsContent value="tab2">Content 2</TabsContent>
|
|
174
|
+
<TabsContent value="tab3">Content 3</TabsContent>
|
|
175
|
+
</Tabs>
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
cy.get('[data-orientation="vertical"]').should('exist');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('renders complex content in tabs', () => {
|
|
182
|
+
cy.mountRadix(
|
|
183
|
+
<Tabs defaultValue="form">
|
|
184
|
+
<TabsList>
|
|
185
|
+
<TabsTrigger value="form">Form</TabsTrigger>
|
|
186
|
+
<TabsTrigger value="preview">Preview</TabsTrigger>
|
|
187
|
+
</TabsList>
|
|
188
|
+
<TabsContent value="form">
|
|
189
|
+
<form className="space-y-4">
|
|
190
|
+
<input placeholder="Name" className="border p-2" />
|
|
191
|
+
<textarea placeholder="Description" className="border p-2" />
|
|
192
|
+
<button type="submit">Submit</button>
|
|
193
|
+
</form>
|
|
194
|
+
</TabsContent>
|
|
195
|
+
<TabsContent value="preview">
|
|
196
|
+
<div className="p-4 border rounded">
|
|
197
|
+
<h3>Preview</h3>
|
|
198
|
+
<p>Form data will appear here</p>
|
|
199
|
+
</div>
|
|
200
|
+
</TabsContent>
|
|
201
|
+
</Tabs>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
cy.get('input[placeholder="Name"]').should('be.visible');
|
|
205
|
+
cy.get('textarea[placeholder="Description"]').should('be.visible');
|
|
206
|
+
|
|
207
|
+
cy.contains('button', 'Preview').click();
|
|
208
|
+
cy.contains('h3', 'Preview').should('be.visible');
|
|
209
|
+
cy.contains('Form data will appear here').should('be.visible');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('maintains tab state when content changes', () => {
|
|
213
|
+
const TestComponent = () => {
|
|
214
|
+
const [count, setCount] = React.useState(0);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div>
|
|
218
|
+
<button onClick={() => setCount(count + 1)}>Increment: {count}</button>
|
|
219
|
+
<Tabs defaultValue="tab1">
|
|
220
|
+
<TabsList>
|
|
221
|
+
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
222
|
+
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
223
|
+
</TabsList>
|
|
224
|
+
<TabsContent value="tab1">Tab 1 content with count: {count}</TabsContent>
|
|
225
|
+
<TabsContent value="tab2">Tab 2 content with count: {count}</TabsContent>
|
|
226
|
+
</Tabs>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
cy.mountRadix(<TestComponent />);
|
|
232
|
+
|
|
233
|
+
// Select tab 2
|
|
234
|
+
cy.contains('button', 'Tab 2').click();
|
|
235
|
+
cy.contains('Tab 2 content with count: 0').should('be.visible');
|
|
236
|
+
|
|
237
|
+
// Increment counter
|
|
238
|
+
cy.contains('button', 'Increment: 0').click();
|
|
239
|
+
|
|
240
|
+
// Tab 2 should still be selected
|
|
241
|
+
cy.contains('Tab 2 content with count: 1').should('be.visible');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('supports icons in tab triggers', () => {
|
|
245
|
+
cy.mountRadix(
|
|
246
|
+
<Tabs defaultValue="users">
|
|
247
|
+
<TabsList>
|
|
248
|
+
<TabsTrigger value="users">
|
|
249
|
+
<span className="flex items-center gap-2">
|
|
250
|
+
<span>👤</span>
|
|
251
|
+
<span>Users</span>
|
|
252
|
+
</span>
|
|
253
|
+
</TabsTrigger>
|
|
254
|
+
<TabsTrigger value="settings">
|
|
255
|
+
<span className="flex items-center gap-2">
|
|
256
|
+
<span>⚙️</span>
|
|
257
|
+
<span>Settings</span>
|
|
258
|
+
</span>
|
|
259
|
+
</TabsTrigger>
|
|
260
|
+
</TabsList>
|
|
261
|
+
<TabsContent value="users">Users list</TabsContent>
|
|
262
|
+
<TabsContent value="settings">Settings panel</TabsContent>
|
|
263
|
+
</Tabs>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
cy.contains('👤').should('be.visible');
|
|
267
|
+
cy.contains('⚙️').should('be.visible');
|
|
268
|
+
cy.contains('Users').should('be.visible');
|
|
269
|
+
cy.contains('Settings').should('be.visible');
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const Tabs = TabsPrimitive.Root;
|
|
7
|
+
|
|
8
|
+
const TabsList = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<TabsPrimitive.List
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn(
|
|
15
|
+
'inline-flex items-center bg-transparent border-b border-sidebar-border w-full',
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
));
|
|
21
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
22
|
+
|
|
23
|
+
const TabsTrigger = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
26
|
+
>(({ className, ...props }, ref) => (
|
|
27
|
+
<TabsPrimitive.Trigger
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cn(
|
|
30
|
+
'inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium text-muted-foreground bg-transparent transition-all hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-b-primary relative',
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
));
|
|
36
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
37
|
+
|
|
38
|
+
const TabsContent = React.forwardRef<
|
|
39
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
40
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
41
|
+
>(({ className, ...props }, ref) => (
|
|
42
|
+
<TabsPrimitive.Content
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn(
|
|
45
|
+
'text-foreground ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
));
|
|
51
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
52
|
+
|
|
53
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Textarea } from './textarea';
|
|
6
|
+
|
|
7
|
+
describe('Textarea Component', () => {
|
|
8
|
+
it('renders correctly with default props', () => {
|
|
9
|
+
cy.mount(<Textarea placeholder="Enter your message" />);
|
|
10
|
+
|
|
11
|
+
cy.get('textarea').should('exist');
|
|
12
|
+
cy.get('textarea').should('have.attr', 'placeholder', 'Enter your message');
|
|
13
|
+
cy.get('textarea').should('have.class', 'flex');
|
|
14
|
+
cy.get('textarea').should('have.class', 'min-h-[120px]');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('accepts user input', () => {
|
|
18
|
+
cy.mount(<Textarea />);
|
|
19
|
+
|
|
20
|
+
const testText = 'This is a multiline\ntext input\nwith multiple lines';
|
|
21
|
+
cy.get('textarea').type(testText);
|
|
22
|
+
cy.get('textarea').should('have.value', testText);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('can be disabled', () => {
|
|
26
|
+
cy.mount(<Textarea disabled />);
|
|
27
|
+
|
|
28
|
+
cy.get('textarea').should('be.disabled');
|
|
29
|
+
cy.get('textarea').should('have.class', 'disabled:cursor-not-allowed');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies custom className', () => {
|
|
33
|
+
cy.mount(<Textarea className="custom-textarea h-32" />);
|
|
34
|
+
|
|
35
|
+
cy.get('textarea').should('have.class', 'custom-textarea').should('have.class', 'h-32');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('supports rows attribute', () => {
|
|
39
|
+
cy.mount(<Textarea rows={5} />);
|
|
40
|
+
|
|
41
|
+
cy.get('textarea').should('have.attr', 'rows', '5');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('supports maxLength', () => {
|
|
45
|
+
cy.mount(<Textarea maxLength={100} />);
|
|
46
|
+
|
|
47
|
+
const longText = 'a'.repeat(150);
|
|
48
|
+
cy.get('textarea').type(longText);
|
|
49
|
+
cy.get('textarea').invoke('val').should('have.length', 100);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('handles onChange events', () => {
|
|
53
|
+
const onChange = cy.stub();
|
|
54
|
+
|
|
55
|
+
cy.mount(<Textarea onChange={onChange} />);
|
|
56
|
+
|
|
57
|
+
cy.get('textarea').type('test');
|
|
58
|
+
cy.wrap(onChange).should('have.been.called');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('supports readonly state', () => {
|
|
62
|
+
cy.mount(<Textarea readOnly value="Read only text" />);
|
|
63
|
+
|
|
64
|
+
cy.get('textarea').should('have.attr', 'readonly');
|
|
65
|
+
cy.get('textarea').should('have.value', 'Read only text');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('auto-resizes if configured', () => {
|
|
69
|
+
cy.mount(<Textarea placeholder="Type to see auto-resize" className="resize-none" />);
|
|
70
|
+
|
|
71
|
+
cy.get('textarea').should('have.class', 'resize-none');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('works with form validation', () => {
|
|
75
|
+
cy.mount(
|
|
76
|
+
<form>
|
|
77
|
+
<Textarea required name="message" />
|
|
78
|
+
<button type="submit">Submit</button>
|
|
79
|
+
</form>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
cy.get('textarea').should('have.attr', 'required');
|
|
83
|
+
cy.get('textarea').should('have.attr', 'name', 'message');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('supports data attributes', () => {
|
|
87
|
+
cy.mount(<Textarea data-testid="custom-textarea" data-field="description" />);
|
|
88
|
+
|
|
89
|
+
cy.get('[data-testid="custom-textarea"]')
|
|
90
|
+
.should('exist')
|
|
91
|
+
.should('have.attr', 'data-field', 'description');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('maintains proper focus states', () => {
|
|
95
|
+
cy.mount(<Textarea />);
|
|
96
|
+
|
|
97
|
+
cy.get('textarea').focus();
|
|
98
|
+
cy.get('textarea').should('have.focus');
|
|
99
|
+
cy.get('textarea').should('have.class', 'focus-visible:ring-1');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('handles long content properly', () => {
|
|
103
|
+
const longText = Array(10).fill('This is a long line of text.').join('\n');
|
|
104
|
+
|
|
105
|
+
cy.mount(<Textarea value={longText} />);
|
|
106
|
+
|
|
107
|
+
cy.get('textarea').should('have.value', longText);
|
|
108
|
+
cy.get('textarea').scrollTo(0, 100);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('works with labels', () => {
|
|
112
|
+
cy.mount(
|
|
113
|
+
<div className="space-y-2">
|
|
114
|
+
<label htmlFor="comment">Comment</label>
|
|
115
|
+
<Textarea id="comment" />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
cy.get('label').click();
|
|
120
|
+
cy.get('#comment').should('have.focus');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('supports different sizes via className', () => {
|
|
124
|
+
cy.mount(
|
|
125
|
+
<div className="space-y-4">
|
|
126
|
+
<Textarea className="min-h-[40px]" placeholder="Small" />
|
|
127
|
+
<Textarea className="min-h-[100px]" placeholder="Medium" />
|
|
128
|
+
<Textarea className="min-h-[200px]" placeholder="Large" />
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
cy.get('textarea').should('have.length', 3);
|
|
133
|
+
cy.get('textarea').first().should('have.class', 'min-h-[40px]');
|
|
134
|
+
cy.get('textarea').last().should('have.class', 'min-h-[200px]');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
|
|
5
|
+
interface TextareaProps extends React.ComponentProps<'textarea'> {
|
|
6
|
+
'data-testid'?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
10
|
+
({ className, ...props }, ref) => {
|
|
11
|
+
return (
|
|
12
|
+
<textarea
|
|
13
|
+
className={cn(
|
|
14
|
+
'flex min-h-[120px] w-full rounded border border-input bg-card px-4 py-3 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
ref={ref}
|
|
18
|
+
data-testid={props['data-testid'] || 'textarea'}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
Textarea.displayName = 'Textarea';
|
|
25
|
+
|
|
26
|
+
export { Textarea };
|