@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,48 @@
1
+ // @hidden
2
+ export default function MessageLoading() {
3
+ return (
4
+ <svg
5
+ role="img"
6
+ aria-label="Loading animation"
7
+ width="24"
8
+ height="24"
9
+ viewBox="0 0 24 24"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ className="text-foreground"
12
+ data-testid="loading"
13
+ >
14
+ <circle cx="4" cy="12" r="2" fill="currentColor">
15
+ <animate
16
+ id="spinner_qFRN"
17
+ begin="0;spinner_OcgL.end+0.25s"
18
+ attributeName="cy"
19
+ calcMode="spline"
20
+ dur="0.6s"
21
+ values="12;6;12"
22
+ keySplines=".33,.66,.66,1;.33,0,.66,.33"
23
+ />
24
+ </circle>
25
+ <circle cx="12" cy="12" r="2" fill="currentColor">
26
+ <animate
27
+ begin="spinner_qFRN.begin+0.1s"
28
+ attributeName="cy"
29
+ calcMode="spline"
30
+ dur="0.6s"
31
+ values="12;6;12"
32
+ keySplines=".33,.66,.66,1;.33,0,.66,.33"
33
+ />
34
+ </circle>
35
+ <circle cx="20" cy="12" r="2" fill="currentColor">
36
+ <animate
37
+ id="spinner_OcgL"
38
+ begin="spinner_qFRN.begin+0.2s"
39
+ attributeName="cy"
40
+ calcMode="spline"
41
+ dur="0.6s"
42
+ values="12;6;12"
43
+ keySplines=".33,.66,.66,1;.33,0,.66,.33"
44
+ />
45
+ </circle>
46
+ </svg>
47
+ );
48
+ }
@@ -0,0 +1,170 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Checkbox } from './checkbox';
6
+
7
+ describe('Checkbox Component', () => {
8
+ it('renders correctly with default props', () => {
9
+ cy.mount(<Checkbox />);
10
+
11
+ cy.get('[data-testid="checkbox"]').should('exist');
12
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'role', 'checkbox');
13
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'false');
14
+ });
15
+
16
+ it('can be checked and unchecked', () => {
17
+ cy.mount(<Checkbox />);
18
+
19
+ // Initially unchecked
20
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'false');
21
+
22
+ // Click to check
23
+ cy.get('[data-testid="checkbox"]').click();
24
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'true');
25
+
26
+ // Click to uncheck
27
+ cy.get('[data-testid="checkbox"]').click();
28
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'false');
29
+ });
30
+
31
+ it('works as controlled component', () => {
32
+ const TestComponent = () => {
33
+ const [checked, setChecked] = React.useState(false);
34
+
35
+ return (
36
+ <div>
37
+ <Checkbox
38
+ checked={checked}
39
+ onCheckedChange={(value) => {
40
+ if (typeof value === 'boolean') {
41
+ setChecked(value);
42
+ }
43
+ }}
44
+ />
45
+ <p>{checked ? 'Checked' : 'Unchecked'}</p>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ cy.mount(<TestComponent />);
51
+
52
+ cy.contains('Unchecked').should('exist');
53
+ cy.get('[data-testid="checkbox"]').click();
54
+ cy.contains('Checked').should('exist');
55
+ });
56
+
57
+ it('can be disabled', () => {
58
+ cy.mount(<Checkbox disabled />);
59
+
60
+ cy.get('[data-testid="checkbox"]').should('be.disabled');
61
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'data-disabled');
62
+ });
63
+
64
+ it('supports indeterminate state', () => {
65
+ cy.mount(<Checkbox checked="indeterminate" />);
66
+
67
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'mixed');
68
+ });
69
+
70
+ it('applies custom className', () => {
71
+ cy.mount(<Checkbox className="custom-checkbox h-6 w-6" />);
72
+
73
+ cy.get('[data-testid="checkbox"]')
74
+ .should('have.class', 'custom-checkbox')
75
+ .should('have.class', 'h-6')
76
+ .should('have.class', 'w-6');
77
+ });
78
+
79
+ it('works with labels', () => {
80
+ cy.mount(
81
+ <div className="flex items-center space-x-2">
82
+ <Checkbox id="terms" />
83
+ <label
84
+ htmlFor="terms"
85
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
86
+ >
87
+ Accept terms and conditions
88
+ </label>
89
+ </div>
90
+ );
91
+
92
+ // Clicking the label should check the checkbox
93
+ cy.get('label').click();
94
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'true');
95
+ });
96
+
97
+ it('handles onCheckedChange callback', () => {
98
+ const onCheckedChange = cy.stub();
99
+
100
+ cy.mount(<Checkbox onCheckedChange={onCheckedChange} />);
101
+
102
+ cy.get('[data-testid="checkbox"]').click();
103
+ cy.wrap(onCheckedChange).should('have.been.calledWith', true);
104
+
105
+ cy.get('[data-testid="checkbox"]').click();
106
+ cy.wrap(onCheckedChange).should('have.been.calledWith', false);
107
+ });
108
+
109
+ it('renders multiple checkboxes in a form', () => {
110
+ cy.mount(
111
+ <form className="space-y-4">
112
+ <div className="flex items-center space-x-2">
113
+ <Checkbox id="option1" />
114
+ <label htmlFor="option1">Option 1</label>
115
+ </div>
116
+ <div className="flex items-center space-x-2">
117
+ <Checkbox id="option2" />
118
+ <label htmlFor="option2">Option 2</label>
119
+ </div>
120
+ <div className="flex items-center space-x-2">
121
+ <Checkbox id="option3" />
122
+ <label htmlFor="option3">Option 3</label>
123
+ </div>
124
+ </form>
125
+ );
126
+
127
+ cy.get('[data-testid="checkbox"]').should('have.length', 3);
128
+
129
+ // Check first and third options
130
+ cy.get('#option1').click();
131
+ cy.get('#option3').click();
132
+
133
+ cy.get('#option1').should('have.attr', 'aria-checked', 'true');
134
+ cy.get('#option2').should('have.attr', 'aria-checked', 'false');
135
+ cy.get('#option3').should('have.attr', 'aria-checked', 'true');
136
+ });
137
+
138
+ it('supports keyboard navigation', () => {
139
+ cy.mount(<Checkbox />);
140
+
141
+ // Initially unchecked
142
+ cy.get('[data-testid="checkbox"]').should('have.attr', 'aria-checked', 'false');
143
+
144
+ // Focus and verify focus
145
+ cy.get('[data-testid="checkbox"]').focus();
146
+ cy.get('[data-testid="checkbox"]').should('have.focus');
147
+
148
+ // Test that the checkbox is focusable and accessible via keyboard
149
+ // Note: Radix UI checkbox may handle space key internally
150
+ // For now, let's verify it's keyboard accessible
151
+ cy.get('[data-testid="checkbox"]')
152
+ .should('have.attr', 'role', 'checkbox')
153
+ .should('not.have.attr', 'disabled');
154
+ });
155
+
156
+ it('maintains visual focus indicator', () => {
157
+ cy.mount(<Checkbox />);
158
+
159
+ cy.get('[data-testid="checkbox"]').focus();
160
+ cy.get('[data-testid="checkbox"]').should('have.class', 'focus-visible:ring-1');
161
+ });
162
+
163
+ it('supports data attributes', () => {
164
+ cy.mount(<Checkbox data-testid="custom-checkbox" data-field="agree" />);
165
+
166
+ cy.get('[data-testid="custom-checkbox"]')
167
+ .should('exist')
168
+ .should('have.attr', 'data-field', 'agree');
169
+ });
170
+ });
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
3
+ import { Check } from 'lucide-react';
4
+
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
8
+ 'data-testid'?: string;
9
+ }
10
+
11
+ const Checkbox = React.forwardRef<React.ElementRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(
12
+ ({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
17
+ className
18
+ )}
19
+ data-testid={props['data-testid'] || 'checkbox'}
20
+ {...props}
21
+ >
22
+ <CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
23
+ <Check className="h-4 w-4" />
24
+ </CheckboxPrimitive.Indicator>
25
+ </CheckboxPrimitive.Root>
26
+ )
27
+ );
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
29
+
30
+ export { Checkbox };
@@ -0,0 +1,283 @@
1
+ /// <reference types="cypress" />
2
+ /// <reference path="../../../cypress/support/types.d.ts" />
3
+
4
+ import React from 'react';
5
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './collapsible';
6
+ import { Button } from './button';
7
+
8
+ describe('Collapsible Component', () => {
9
+ it('renders basic collapsible', () => {
10
+ cy.mount(
11
+ <Collapsible>
12
+ <CollapsibleTrigger>Toggle Content</CollapsibleTrigger>
13
+ <CollapsibleContent>
14
+ <p>This is collapsible content</p>
15
+ </CollapsibleContent>
16
+ </Collapsible>
17
+ );
18
+
19
+ cy.contains('Toggle Content').should('be.visible');
20
+ cy.contains('This is collapsible content').should('not.exist');
21
+ });
22
+
23
+ it('toggles content on click', () => {
24
+ cy.mount(
25
+ <Collapsible>
26
+ <CollapsibleTrigger asChild>
27
+ <Button variant="outline">Show More</Button>
28
+ </CollapsibleTrigger>
29
+ <CollapsibleContent>
30
+ <div className="p-4 border rounded">
31
+ <p>Additional content that can be toggled</p>
32
+ </div>
33
+ </CollapsibleContent>
34
+ </Collapsible>
35
+ );
36
+
37
+ // Initially collapsed
38
+ cy.contains('Additional content').should('not.exist');
39
+
40
+ // Click to expand
41
+ cy.contains('button', 'Show More').click();
42
+ cy.contains('Additional content').should('be.visible');
43
+
44
+ // Click to collapse
45
+ cy.contains('button', 'Show More').click();
46
+ cy.contains('Additional content').should('not.exist');
47
+ });
48
+
49
+ it('works as controlled component', () => {
50
+ const TestComponent = () => {
51
+ const [open, setOpen] = React.useState(false);
52
+
53
+ return (
54
+ <div>
55
+ <p>Is open: {open ? 'Yes' : 'No'}</p>
56
+ <Collapsible open={open} onOpenChange={setOpen}>
57
+ <CollapsibleTrigger>Toggle</CollapsibleTrigger>
58
+ <CollapsibleContent>
59
+ <p>Controlled content</p>
60
+ </CollapsibleContent>
61
+ </Collapsible>
62
+ <button onClick={() => setOpen(!open)}>External Toggle</button>
63
+ </div>
64
+ );
65
+ };
66
+
67
+ cy.mount(<TestComponent />);
68
+
69
+ cy.contains('Is open: No').should('be.visible');
70
+ cy.contains('Controlled content').should('not.exist');
71
+
72
+ // Use external toggle
73
+ cy.contains('button', 'External Toggle').click();
74
+ cy.contains('Is open: Yes').should('be.visible');
75
+ cy.contains('Controlled content').should('be.visible');
76
+
77
+ // Use internal trigger
78
+ cy.contains('Toggle').click();
79
+ cy.contains('Is open: No').should('be.visible');
80
+ cy.contains('Controlled content').should('not.exist');
81
+ });
82
+
83
+ it('supports default open state', () => {
84
+ cy.mount(
85
+ <Collapsible defaultOpen>
86
+ <CollapsibleTrigger>Toggle</CollapsibleTrigger>
87
+ <CollapsibleContent>
88
+ <p>Initially visible content</p>
89
+ </CollapsibleContent>
90
+ </Collapsible>
91
+ );
92
+
93
+ cy.contains('Initially visible content').should('be.visible');
94
+
95
+ // Click to collapse
96
+ cy.contains('Toggle').click();
97
+ cy.contains('Initially visible content').should('not.exist');
98
+ });
99
+
100
+ it('can be disabled', () => {
101
+ cy.mount(
102
+ <Collapsible disabled>
103
+ <CollapsibleTrigger asChild>
104
+ <Button disabled>Disabled Toggle</Button>
105
+ </CollapsibleTrigger>
106
+ <CollapsibleContent>
107
+ <p>This content cannot be toggled</p>
108
+ </CollapsibleContent>
109
+ </Collapsible>
110
+ );
111
+
112
+ cy.contains('button', 'Disabled Toggle').should('be.disabled');
113
+ cy.contains('This content cannot be toggled').should('not.exist');
114
+
115
+ // Try to click
116
+ cy.contains('button', 'Disabled Toggle').click({ force: true });
117
+ cy.contains('This content cannot be toggled').should('not.exist');
118
+ });
119
+
120
+ it('supports custom className', () => {
121
+ cy.mount(
122
+ <Collapsible className="custom-collapsible border p-4">
123
+ <CollapsibleTrigger className="custom-trigger text-blue-600">
124
+ Click to expand
125
+ </CollapsibleTrigger>
126
+ <CollapsibleContent className="custom-content mt-4">
127
+ <p>Styled content</p>
128
+ </CollapsibleContent>
129
+ </Collapsible>
130
+ );
131
+
132
+ cy.get('.custom-collapsible').should('have.class', 'border');
133
+ cy.get('.custom-trigger').should('have.class', 'text-blue-600');
134
+
135
+ cy.contains('Click to expand').click();
136
+ cy.get('.custom-content').should('have.class', 'mt-4');
137
+ });
138
+
139
+ it('handles multiple collapsibles', () => {
140
+ cy.mount(
141
+ <div className="space-y-4">
142
+ <Collapsible>
143
+ <CollapsibleTrigger>First Section</CollapsibleTrigger>
144
+ <CollapsibleContent>
145
+ <p>First content</p>
146
+ </CollapsibleContent>
147
+ </Collapsible>
148
+
149
+ <Collapsible>
150
+ <CollapsibleTrigger>Second Section</CollapsibleTrigger>
151
+ <CollapsibleContent>
152
+ <p>Second content</p>
153
+ </CollapsibleContent>
154
+ </Collapsible>
155
+ </div>
156
+ );
157
+
158
+ // Both initially collapsed
159
+ cy.contains('First content').should('not.exist');
160
+ cy.contains('Second content').should('not.exist');
161
+
162
+ // Expand first
163
+ cy.contains('First Section').click();
164
+ cy.contains('First content').should('be.visible');
165
+ cy.contains('Second content').should('not.exist');
166
+
167
+ // Expand second
168
+ cy.contains('Second Section').click();
169
+ cy.contains('First content').should('be.visible');
170
+ cy.contains('Second content').should('be.visible');
171
+ });
172
+
173
+ it('works with complex content', () => {
174
+ cy.mount(
175
+ <Collapsible>
176
+ <div className="flex items-center justify-between">
177
+ <h3 className="font-semibold">Advanced Settings</h3>
178
+ <CollapsibleTrigger asChild>
179
+ <Button variant="ghost" size="sm">
180
+ <span>Show</span>
181
+ </Button>
182
+ </CollapsibleTrigger>
183
+ </div>
184
+ <CollapsibleContent>
185
+ <div className="mt-4 space-y-4">
186
+ <div>
187
+ <label>Option 1</label>
188
+ <input type="checkbox" />
189
+ </div>
190
+ <div>
191
+ <label>Option 2</label>
192
+ <input type="checkbox" />
193
+ </div>
194
+ </div>
195
+ </CollapsibleContent>
196
+ </Collapsible>
197
+ );
198
+
199
+ cy.contains('Advanced Settings').should('be.visible');
200
+ cy.get('input[type="checkbox"]').should('not.exist');
201
+
202
+ cy.contains('button', 'Show').click();
203
+ cy.get('input[type="checkbox"]').should('have.length', 2);
204
+ });
205
+
206
+ it('animates smoothly', () => {
207
+ cy.mount(
208
+ <Collapsible>
209
+ <CollapsibleTrigger>Animate</CollapsibleTrigger>
210
+ <CollapsibleContent>
211
+ <div className="p-4 bg-gray-100">
212
+ <p>This content animates in and out</p>
213
+ </div>
214
+ </CollapsibleContent>
215
+ </Collapsible>
216
+ );
217
+
218
+ cy.contains('Animate').click();
219
+ cy.contains('This content animates').should('be.visible');
220
+ });
221
+
222
+ it('handles nested collapsibles', () => {
223
+ cy.mount(
224
+ <Collapsible>
225
+ <CollapsibleTrigger>Outer Collapsible</CollapsibleTrigger>
226
+ <CollapsibleContent>
227
+ <div className="p-4 border">
228
+ <p>Outer content</p>
229
+ <Collapsible>
230
+ <CollapsibleTrigger>Inner Collapsible</CollapsibleTrigger>
231
+ <CollapsibleContent>
232
+ <p className="p-4 bg-gray-100">Inner content</p>
233
+ </CollapsibleContent>
234
+ </Collapsible>
235
+ </div>
236
+ </CollapsibleContent>
237
+ </Collapsible>
238
+ );
239
+
240
+ // Both initially collapsed
241
+ cy.contains('Outer content').should('not.exist');
242
+ cy.contains('Inner content').should('not.exist');
243
+
244
+ // Expand outer
245
+ cy.contains('Outer Collapsible').click();
246
+ cy.contains('Outer content').should('be.visible');
247
+ cy.contains('Inner content').should('not.exist');
248
+
249
+ // Expand inner
250
+ cy.contains('Inner Collapsible').click();
251
+ cy.contains('Inner content').should('be.visible');
252
+ });
253
+
254
+ it('maintains state during content updates', () => {
255
+ const TestComponent = () => {
256
+ const [count, setCount] = React.useState(0);
257
+
258
+ return (
259
+ <div>
260
+ <button onClick={() => setCount(count + 1)}>Update Count: {count}</button>
261
+ <Collapsible>
262
+ <CollapsibleTrigger>Toggle</CollapsibleTrigger>
263
+ <CollapsibleContent>
264
+ <p>Content with count: {count}</p>
265
+ </CollapsibleContent>
266
+ </Collapsible>
267
+ </div>
268
+ );
269
+ };
270
+
271
+ cy.mount(<TestComponent />);
272
+
273
+ // Expand collapsible
274
+ cy.contains('Toggle').click();
275
+ cy.contains('Content with count: 0').should('be.visible');
276
+
277
+ // Update count
278
+ cy.contains('button', 'Update Count: 0').click();
279
+
280
+ // Content should still be visible with new count
281
+ cy.contains('Content with count: 1').should('be.visible');
282
+ });
283
+ });
@@ -0,0 +1,9 @@
1
+ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root;
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent };