@moontra/moonui 2.0.8 → 2.0.9

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 (94) hide show
  1. package/package.json +9 -2
  2. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  3. package/src/__tests__/use-local-storage.test.tsx +174 -0
  4. package/src/__tests__/use-pro-access.test.tsx +183 -0
  5. package/src/components/ui/__tests__/accordion.test.tsx +571 -0
  6. package/src/components/ui/__tests__/alert.test.tsx +484 -0
  7. package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
  8. package/src/components/ui/__tests__/avatar.test.tsx +382 -0
  9. package/src/components/ui/__tests__/badge.test.tsx +364 -0
  10. package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
  11. package/src/components/ui/__tests__/button.test.tsx +91 -0
  12. package/src/components/ui/__tests__/card.test.tsx +104 -0
  13. package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
  14. package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
  15. package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
  16. package/src/components/ui/__tests__/command.test.tsx +677 -0
  17. package/src/components/ui/__tests__/data-table.test.tsx +256 -0
  18. package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
  19. package/src/components/ui/__tests__/dialog.test.tsx +764 -0
  20. package/src/components/ui/__tests__/input.test.tsx +318 -0
  21. package/src/components/ui/__tests__/label.test.tsx +103 -0
  22. package/src/components/ui/__tests__/popover.test.tsx +637 -0
  23. package/src/components/ui/__tests__/progress.test.tsx +382 -0
  24. package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
  25. package/src/components/ui/__tests__/select.test.tsx +705 -0
  26. package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
  27. package/src/components/ui/__tests__/slider.test.tsx +493 -0
  28. package/src/components/ui/__tests__/switch.test.tsx +372 -0
  29. package/src/components/ui/__tests__/table.test.tsx +824 -0
  30. package/src/components/ui/__tests__/tabs.test.tsx +887 -0
  31. package/src/components/ui/__tests__/toast.test.tsx +458 -0
  32. package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
  33. package/src/components/ui/accordion.tsx +63 -0
  34. package/src/components/ui/alert.tsx +130 -0
  35. package/src/components/ui/aspect-ratio.tsx +72 -0
  36. package/src/components/ui/avatar.tsx +135 -0
  37. package/src/components/ui/badge.tsx +225 -0
  38. package/src/components/ui/breadcrumb.tsx +207 -0
  39. package/src/components/ui/button.stories.tsx +162 -0
  40. package/src/components/ui/button.tsx +221 -0
  41. package/src/components/ui/calendar.tsx +262 -0
  42. package/src/components/ui/card.stories.tsx +208 -0
  43. package/src/components/ui/card.tsx +141 -0
  44. package/src/components/ui/checkbox.tsx +256 -0
  45. package/src/components/ui/collapsible.tsx +129 -0
  46. package/src/components/ui/color-picker.tsx +664 -0
  47. package/src/components/ui/command.tsx +218 -0
  48. package/src/components/ui/data-table.stories.tsx +509 -0
  49. package/src/components/ui/data-table.tsx +623 -0
  50. package/src/components/ui/date-picker.stories.tsx +321 -0
  51. package/src/components/ui/date-picker.tsx +603 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/file-upload.tsx +400 -0
  55. package/src/components/ui/github-stars.tsx +201 -0
  56. package/src/components/ui/index.ts +12 -0
  57. package/src/components/ui/input.stories.tsx +255 -0
  58. package/src/components/ui/input.tsx +219 -0
  59. package/src/components/ui/label.tsx +26 -0
  60. package/src/components/ui/locked-component.tsx +215 -0
  61. package/src/components/ui/moon-logo.tsx +97 -0
  62. package/src/components/ui/pagination.tsx +116 -0
  63. package/src/components/ui/popover.tsx +183 -0
  64. package/src/components/ui/progress.tsx +182 -0
  65. package/src/components/ui/radio-group.tsx +243 -0
  66. package/src/components/ui/select.tsx +273 -0
  67. package/src/components/ui/separator.tsx +140 -0
  68. package/src/components/ui/simple-editor.tsx +652 -0
  69. package/src/components/ui/skeleton.tsx +351 -0
  70. package/src/components/ui/slider.tsx +351 -0
  71. package/src/components/ui/switch.tsx +83 -0
  72. package/src/components/ui/table.tsx +322 -0
  73. package/src/components/ui/tabs.tsx +195 -0
  74. package/src/components/ui/textarea.tsx +46 -0
  75. package/src/components/ui/toast.tsx +313 -0
  76. package/src/components/ui/toggle.tsx +47 -0
  77. package/src/components/ui/tooltip.tsx +152 -0
  78. package/src/docs/theme-system.md +1194 -0
  79. package/src/index.tsx +39 -0
  80. package/src/lib/micro-interactions.ts +255 -0
  81. package/src/lib/theme.tsx +398 -0
  82. package/src/lib/utils.ts +69 -0
  83. package/src/styles/design-system.css +365 -0
  84. package/src/styles/index.css +4 -0
  85. package/src/styles/tailwind.css +6 -0
  86. package/src/styles/tokens.css +453 -0
  87. package/src/use-intersection-observer.tsx +154 -0
  88. package/src/use-local-storage.tsx +71 -0
  89. package/src/use-paddle.ts +138 -0
  90. package/src/use-performance-optimizer.ts +379 -0
  91. package/src/use-pro-access.ts +141 -0
  92. package/src/use-scroll-animation.ts +221 -0
  93. package/src/use-subscription.ts +37 -0
  94. package/src/use-toast.ts +32 -0
@@ -0,0 +1,583 @@
1
+ import React from 'react'
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
3
+ import userEvent from '@testing-library/user-event'
4
+ import { Tooltip } from '../tooltip'
5
+
6
+ // Mock next-themes
7
+ jest.mock('next-themes', () => ({
8
+ useTheme: () => ({
9
+ theme: 'light',
10
+ }),
11
+ }))
12
+
13
+
14
+
15
+ describe('Tooltip Components', () => {
16
+ describe('Tooltip Component', () => {
17
+ describe('Basic Rendering', () => {
18
+ it('renders correctly with default props', () => {
19
+ render(
20
+ <Tooltip content="Test tooltip">
21
+ <button>Hover me</button>
22
+ </Tooltip>
23
+ )
24
+
25
+ expect(screen.getByRole('button')).toBeInTheDocument()
26
+ expect(screen.getByText('Hover me')).toBeInTheDocument()
27
+ })
28
+
29
+ it('shows tooltip on hover', async () => {
30
+ const user = userEvent.setup()
31
+ render(
32
+ <Tooltip content="Test tooltip">
33
+ <button>Hover me</button>
34
+ </Tooltip>
35
+ )
36
+
37
+ const trigger = screen.getByRole('button')
38
+ await user.hover(trigger)
39
+
40
+ await waitFor(() => {
41
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
42
+ expect(screen.getByText('Test tooltip')).toBeInTheDocument()
43
+ })
44
+ })
45
+
46
+ it('hides tooltip on mouse leave', async () => {
47
+ const user = userEvent.setup()
48
+ render(
49
+ <Tooltip content="Test tooltip">
50
+ <button>Hover me</button>
51
+ </Tooltip>
52
+ )
53
+
54
+ const trigger = screen.getByRole('button')
55
+ await user.hover(trigger)
56
+
57
+ await waitFor(() => {
58
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
59
+ })
60
+
61
+ await user.unhover(trigger)
62
+
63
+ await waitFor(() => {
64
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
65
+ })
66
+ })
67
+
68
+ it('applies custom className', () => {
69
+ render(
70
+ <Tooltip content="Test tooltip" className="custom-tooltip">
71
+ <button>Hover me</button>
72
+ </Tooltip>
73
+ )
74
+
75
+ const wrapper = screen.getByRole('button').parentElement
76
+ expect(wrapper).toHaveClass('relative', 'inline-block')
77
+ })
78
+
79
+ it('has correct displayName', () => {
80
+ expect(Tooltip.displayName || Tooltip.name || 'Tooltip').toBe('Tooltip')
81
+ })
82
+ })
83
+
84
+ describe('Variants', () => {
85
+ const variants = ['primary', 'secondary', 'success', 'caution', 'destructive', 'info'] as const
86
+
87
+ variants.forEach((variant) => {
88
+ it(`renders ${variant} variant correctly`, async () => {
89
+ const user = userEvent.setup()
90
+ render(
91
+ <Tooltip content="Test tooltip" variant={variant}>
92
+ <button>Hover me</button>
93
+ </Tooltip>
94
+ )
95
+
96
+ const trigger = screen.getByRole('button')
97
+ await user.hover(trigger)
98
+
99
+ await waitFor(() => {
100
+ const tooltip = screen.getByRole('tooltip')
101
+ expect(tooltip).toBeInTheDocument()
102
+ expect(tooltip).toHaveClass(`bg-${variant}`)
103
+ })
104
+ })
105
+ })
106
+ })
107
+
108
+ describe('Sizes', () => {
109
+ const sizes = ['sm', 'md', 'lg'] as const
110
+
111
+ sizes.forEach((size) => {
112
+ it(`renders ${size} size correctly`, async () => {
113
+ const user = userEvent.setup()
114
+ render(
115
+ <Tooltip content="Test tooltip" size={size}>
116
+ <button>Hover me</button>
117
+ </Tooltip>
118
+ )
119
+
120
+ const trigger = screen.getByRole('button')
121
+ await user.hover(trigger)
122
+
123
+ await waitFor(() => {
124
+ const tooltip = screen.getByRole('tooltip')
125
+ expect(tooltip).toBeInTheDocument()
126
+ // Size classes are applied through tooltipVariants
127
+ expect(tooltip).toHaveAttribute('class')
128
+ })
129
+ })
130
+ })
131
+ })
132
+
133
+ describe('Radius', () => {
134
+ const radiusOptions = ['none', 'sm', 'md', 'lg', 'full'] as const
135
+
136
+ radiusOptions.forEach((radius) => {
137
+ it(`renders ${radius} radius correctly`, async () => {
138
+ const user = userEvent.setup()
139
+ render(
140
+ <Tooltip content="Test tooltip" radius={radius}>
141
+ <button>Hover me</button>
142
+ </Tooltip>
143
+ )
144
+
145
+ const trigger = screen.getByRole('button')
146
+ await user.hover(trigger)
147
+
148
+ await waitFor(() => {
149
+ const tooltip = screen.getByRole('tooltip')
150
+ expect(tooltip).toBeInTheDocument()
151
+ expect(tooltip).toHaveClass(`rounded-${radius === 'none' ? 'none' : radius}`)
152
+ })
153
+ })
154
+ })
155
+ })
156
+
157
+ describe('Shadow', () => {
158
+ const shadowOptions = ['none', 'sm', 'md', 'lg'] as const
159
+
160
+ shadowOptions.forEach((shadow) => {
161
+ it(`renders ${shadow} shadow correctly`, async () => {
162
+ const user = userEvent.setup()
163
+ render(
164
+ <Tooltip content="Test tooltip" shadow={shadow}>
165
+ <button>Hover me</button>
166
+ </Tooltip>
167
+ )
168
+
169
+ const trigger = screen.getByRole('button')
170
+ await user.hover(trigger)
171
+
172
+ await waitFor(() => {
173
+ const tooltip = screen.getByRole('tooltip')
174
+ expect(tooltip).toBeInTheDocument()
175
+ expect(tooltip).toHaveClass(`shadow-${shadow}`)
176
+ })
177
+ })
178
+ })
179
+ })
180
+
181
+ describe('Positioning', () => {
182
+ const sides = ['top', 'right', 'bottom', 'left'] as const
183
+
184
+ sides.forEach((side) => {
185
+ it(`renders ${side} position correctly`, async () => {
186
+ const user = userEvent.setup()
187
+ render(
188
+ <Tooltip content="Test tooltip" side={side}>
189
+ <button>Hover me</button>
190
+ </Tooltip>
191
+ )
192
+
193
+ const trigger = screen.getByRole('button')
194
+ await user.hover(trigger)
195
+
196
+ await waitFor(() => {
197
+ const tooltip = screen.getByRole('tooltip')
198
+ expect(tooltip).toBeInTheDocument()
199
+ // Position classes are applied through sideClasses
200
+ expect(tooltip).toHaveAttribute('class')
201
+ })
202
+ })
203
+ })
204
+
205
+ const alignments = ['start', 'center', 'end'] as const
206
+
207
+ alignments.forEach((align) => {
208
+ it(`renders ${align} alignment correctly`, async () => {
209
+ const user = userEvent.setup()
210
+ render(
211
+ <Tooltip content="Test tooltip" align={align}>
212
+ <button>Hover me</button>
213
+ </Tooltip>
214
+ )
215
+
216
+ const trigger = screen.getByRole('button')
217
+ await user.hover(trigger)
218
+
219
+ await waitFor(() => {
220
+ const tooltip = screen.getByRole('tooltip')
221
+ expect(tooltip).toBeInTheDocument()
222
+ // Alignment classes are applied through alignClasses
223
+ expect(tooltip).toHaveAttribute('class')
224
+ })
225
+ })
226
+ })
227
+ })
228
+
229
+ describe('Delay Duration', () => {
230
+ it('respects custom delay duration', async () => {
231
+ const user = userEvent.setup()
232
+ render(
233
+ <Tooltip content="Test tooltip" delayDuration={100}>
234
+ <button>Hover me</button>
235
+ </Tooltip>
236
+ )
237
+
238
+ const trigger = screen.getByRole('button')
239
+ await user.hover(trigger)
240
+
241
+ // Should appear after 100ms
242
+ await waitFor(() => {
243
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
244
+ }, { timeout: 200 })
245
+ })
246
+
247
+ it('works with zero delay', async () => {
248
+ const user = userEvent.setup()
249
+ render(
250
+ <Tooltip content="Test tooltip" delayDuration={0}>
251
+ <button>Hover me</button>
252
+ </Tooltip>
253
+ )
254
+
255
+ const trigger = screen.getByRole('button')
256
+ await user.hover(trigger)
257
+
258
+ // Should appear immediately
259
+ await waitFor(() => {
260
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
261
+ })
262
+ })
263
+ })
264
+
265
+ describe('Side Offset', () => {
266
+ it('applies custom side offset', async () => {
267
+ const user = userEvent.setup()
268
+ render(
269
+ <Tooltip content="Test tooltip" sideOffset={10}>
270
+ <button>Hover me</button>
271
+ </Tooltip>
272
+ )
273
+
274
+ const trigger = screen.getByRole('button')
275
+ await user.hover(trigger)
276
+
277
+ await waitFor(() => {
278
+ const tooltip = screen.getByRole('tooltip')
279
+ expect(tooltip).toBeInTheDocument()
280
+ expect(tooltip).toHaveStyle({ marginBottom: '10px' })
281
+ })
282
+ })
283
+ })
284
+
285
+ describe('Content Types', () => {
286
+ it('renders text content', async () => {
287
+ const user = userEvent.setup()
288
+ render(
289
+ <Tooltip content="Simple text">
290
+ <button>Hover me</button>
291
+ </Tooltip>
292
+ )
293
+
294
+ const trigger = screen.getByRole('button')
295
+ await user.hover(trigger)
296
+
297
+ await waitFor(() => {
298
+ expect(screen.getByText('Simple text')).toBeInTheDocument()
299
+ })
300
+ })
301
+
302
+ it('renders JSX content', async () => {
303
+ const user = userEvent.setup()
304
+ render(
305
+ <Tooltip content={<div data-testid="jsx-content">JSX Content</div>}>
306
+ <button>Hover me</button>
307
+ </Tooltip>
308
+ )
309
+
310
+ const trigger = screen.getByRole('button')
311
+ await user.hover(trigger)
312
+
313
+ await waitFor(() => {
314
+ expect(screen.getByTestId('jsx-content')).toBeInTheDocument()
315
+ })
316
+ })
317
+
318
+ it('renders complex content', async () => {
319
+ const user = userEvent.setup()
320
+ const complexContent = (
321
+ <div>
322
+ <strong>Title</strong>
323
+ <p>Description</p>
324
+ </div>
325
+ )
326
+
327
+ render(
328
+ <Tooltip content={complexContent}>
329
+ <button>Hover me</button>
330
+ </Tooltip>
331
+ )
332
+
333
+ const trigger = screen.getByRole('button')
334
+ await user.hover(trigger)
335
+
336
+ await waitFor(() => {
337
+ expect(screen.getByText('Title')).toBeInTheDocument()
338
+ expect(screen.getByText('Description')).toBeInTheDocument()
339
+ })
340
+ })
341
+ })
342
+
343
+ describe('Focus Events', () => {
344
+ it('shows tooltip on focus', async () => {
345
+ render(
346
+ <Tooltip content="Test tooltip">
347
+ <button>Focus me</button>
348
+ </Tooltip>
349
+ )
350
+
351
+ const trigger = screen.getByRole('button')
352
+ fireEvent.focus(trigger)
353
+
354
+ await waitFor(() => {
355
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
356
+ })
357
+ })
358
+
359
+ it('hides tooltip on blur', async () => {
360
+ render(
361
+ <Tooltip content="Test tooltip">
362
+ <button>Focus me</button>
363
+ </Tooltip>
364
+ )
365
+
366
+ const trigger = screen.getByRole('button')
367
+ fireEvent.focus(trigger)
368
+
369
+ await waitFor(() => {
370
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
371
+ })
372
+
373
+ fireEvent.blur(trigger)
374
+
375
+ await waitFor(() => {
376
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
377
+ })
378
+ })
379
+ })
380
+
381
+ describe('Complex Combinations', () => {
382
+ it('renders with all props', async () => {
383
+ const user = userEvent.setup()
384
+ render(
385
+ <Tooltip
386
+ content="Complex tooltip"
387
+ variant="success"
388
+ size="lg"
389
+ radius="lg"
390
+ shadow="lg"
391
+ side="bottom"
392
+ align="end"
393
+ delayDuration={50}
394
+ sideOffset={8}
395
+ className="custom-tooltip"
396
+ >
397
+ <button>Complex button</button>
398
+ </Tooltip>
399
+ )
400
+
401
+ const trigger = screen.getByRole('button')
402
+ await user.hover(trigger)
403
+
404
+ await waitFor(() => {
405
+ const tooltip = screen.getByRole('tooltip')
406
+ expect(tooltip).toBeInTheDocument()
407
+ expect(tooltip).toHaveClass('bg-success')
408
+ expect(tooltip).toHaveClass('rounded-lg')
409
+ expect(tooltip).toHaveClass('shadow-lg')
410
+ expect(tooltip).toHaveStyle({ marginTop: '8px' })
411
+ })
412
+ })
413
+
414
+ it('works with different trigger elements', async () => {
415
+ const user = userEvent.setup()
416
+ render(
417
+ <div>
418
+ <Tooltip content="Button tooltip">
419
+ <button data-testid="tooltip-button">Button</button>
420
+ </Tooltip>
421
+ <Tooltip content="Link tooltip">
422
+ <a href="#" role="link">Link</a>
423
+ </Tooltip>
424
+ <Tooltip content="Div tooltip">
425
+ <div role="button" tabIndex={0}>Div</div>
426
+ </Tooltip>
427
+ </div>
428
+ )
429
+
430
+ // Test button
431
+ await user.hover(screen.getByTestId('tooltip-button'))
432
+ await waitFor(() => {
433
+ expect(screen.getByText('Button tooltip')).toBeInTheDocument()
434
+ })
435
+
436
+ await user.unhover(screen.getByTestId('tooltip-button'))
437
+ await waitFor(() => {
438
+ expect(screen.queryByText('Button tooltip')).not.toBeInTheDocument()
439
+ })
440
+
441
+ // Test link
442
+ await user.hover(screen.getByRole('link'))
443
+ await waitFor(() => {
444
+ expect(screen.getByText('Link tooltip')).toBeInTheDocument()
445
+ })
446
+ })
447
+ })
448
+
449
+ describe('Edge Cases', () => {
450
+ it('handles empty content', async () => {
451
+ const user = userEvent.setup()
452
+ render(
453
+ <Tooltip content="">
454
+ <button>Hover me</button>
455
+ </Tooltip>
456
+ )
457
+
458
+ const trigger = screen.getByRole('button')
459
+ await user.hover(trigger)
460
+
461
+ await waitFor(() => {
462
+ const tooltip = screen.getByRole('tooltip')
463
+ expect(tooltip).toBeInTheDocument()
464
+ expect(tooltip).toBeEmptyDOMElement()
465
+ })
466
+ })
467
+
468
+ it('handles null content', async () => {
469
+ const user = userEvent.setup()
470
+ render(
471
+ <Tooltip content={null}>
472
+ <button>Hover me</button>
473
+ </Tooltip>
474
+ )
475
+
476
+ const trigger = screen.getByRole('button')
477
+ await user.hover(trigger)
478
+
479
+ await waitFor(() => {
480
+ const tooltip = screen.getByRole('tooltip')
481
+ expect(tooltip).toBeInTheDocument()
482
+ })
483
+ })
484
+
485
+ it('handles undefined content', async () => {
486
+ const user = userEvent.setup()
487
+ render(
488
+ <Tooltip content={undefined as React.ReactNode}>
489
+ <button>Hover me</button>
490
+ </Tooltip>
491
+ )
492
+
493
+ const trigger = screen.getByRole('button')
494
+ await user.hover(trigger)
495
+
496
+ await waitFor(() => {
497
+ const tooltip = screen.getByRole('tooltip')
498
+ expect(tooltip).toBeInTheDocument()
499
+ })
500
+ })
501
+
502
+ it('handles single child requirement', () => {
503
+ // Tooltip component expects single child due to React.Children.only
504
+ render(
505
+ <Tooltip content="Test tooltip">
506
+ <div>
507
+ <button>Button 1</button>
508
+ <button>Button 2</button>
509
+ </div>
510
+ </Tooltip>
511
+ )
512
+
513
+ // Should render both buttons wrapped in div
514
+ expect(screen.getByText('Button 1')).toBeInTheDocument()
515
+ expect(screen.getByText('Button 2')).toBeInTheDocument()
516
+ })
517
+
518
+ it('passes through HTML attributes', async () => {
519
+ const user = userEvent.setup()
520
+ render(
521
+ <Tooltip
522
+ content="Test tooltip"
523
+ data-testid="tooltip-wrapper"
524
+ aria-label="Custom tooltip"
525
+ >
526
+ <button>Hover me</button>
527
+ </Tooltip>
528
+ )
529
+
530
+ const trigger = screen.getByRole('button')
531
+ await user.hover(trigger)
532
+
533
+ await waitFor(() => {
534
+ const tooltip = screen.getByRole('tooltip')
535
+ expect(tooltip).toBeInTheDocument()
536
+ expect(tooltip).toHaveAttribute('data-testid', 'tooltip-wrapper')
537
+ expect(tooltip).toHaveAttribute('aria-label', 'Custom tooltip')
538
+ })
539
+ })
540
+
541
+ it('handles rapid hover/unhover', async () => {
542
+ const user = userEvent.setup()
543
+ render(
544
+ <Tooltip content="Test tooltip" delayDuration={100}>
545
+ <button>Hover me</button>
546
+ </Tooltip>
547
+ )
548
+
549
+ const trigger = screen.getByRole('button')
550
+
551
+ // Rapid hover/unhover
552
+ await user.hover(trigger)
553
+ await user.unhover(trigger)
554
+ await user.hover(trigger)
555
+
556
+ await waitFor(() => {
557
+ expect(screen.getByRole('tooltip')).toBeInTheDocument()
558
+ })
559
+ })
560
+
561
+ it('handles very long content', async () => {
562
+ const user = userEvent.setup()
563
+ const longContent = 'This is a very long tooltip content that should wrap properly and not break the layout. '.repeat(10)
564
+
565
+ render(
566
+ <Tooltip content={longContent}>
567
+ <button>Hover me</button>
568
+ </Tooltip>
569
+ )
570
+
571
+ const trigger = screen.getByRole('button')
572
+ await user.hover(trigger)
573
+
574
+ await waitFor(() => {
575
+ const tooltip = screen.getByRole('tooltip')
576
+ expect(tooltip).toBeInTheDocument()
577
+ // Check if content contains the expected text (ignoring whitespace differences)
578
+ expect(tooltip.textContent?.trim()).toContain('This is a very long tooltip content')
579
+ })
580
+ })
581
+ })
582
+ })
583
+ })
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b border-gray-200 dark:border-gray-800", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all dark:text-gray-100",
32
+ "hover:underline hover:text-primary dark:hover:text-primary-400",
33
+ "[&[data-state=open]>svg]:rotate-180",
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ {children}
39
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200 opacity-70 group-hover:opacity-100" />
40
+ </AccordionPrimitive.Trigger>
41
+ </AccordionPrimitive.Header>
42
+ ))
43
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
44
+
45
+ const AccordionContent = React.forwardRef<
46
+ React.ElementRef<typeof AccordionPrimitive.Content>,
47
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
48
+ >(({ className, children, ...props }, ref) => (
49
+ <AccordionPrimitive.Content
50
+ ref={ref}
51
+ className={cn(
52
+ "overflow-hidden text-sm text-gray-700 dark:text-gray-300 transition-all",
53
+ "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
54
+ className
55
+ )}
56
+ {...props}
57
+ >
58
+ <div className="pb-4 pt-0">{children}</div>
59
+ </AccordionPrimitive.Content>
60
+ ))
61
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
62
+
63
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }