@startsimpli/ui 0.1.0

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 (86) hide show
  1. package/README.md +537 -0
  2. package/package.json +80 -0
  3. package/src/components/index.ts +50 -0
  4. package/src/components/navigation/sidebar.tsx +178 -0
  5. package/src/components/ui/accordion.tsx +58 -0
  6. package/src/components/ui/alert.tsx +59 -0
  7. package/src/components/ui/badge.tsx +36 -0
  8. package/src/components/ui/button.tsx +57 -0
  9. package/src/components/ui/calendar.tsx +70 -0
  10. package/src/components/ui/card.tsx +68 -0
  11. package/src/components/ui/checkbox.tsx +30 -0
  12. package/src/components/ui/collapsible.tsx +12 -0
  13. package/src/components/ui/dialog.tsx +122 -0
  14. package/src/components/ui/dropdown-menu.tsx +200 -0
  15. package/src/components/ui/index.ts +24 -0
  16. package/src/components/ui/input.tsx +25 -0
  17. package/src/components/ui/label.tsx +26 -0
  18. package/src/components/ui/popover.tsx +31 -0
  19. package/src/components/ui/progress.tsx +28 -0
  20. package/src/components/ui/scroll-area.tsx +48 -0
  21. package/src/components/ui/select.tsx +160 -0
  22. package/src/components/ui/separator.tsx +31 -0
  23. package/src/components/ui/skeleton.tsx +15 -0
  24. package/src/components/ui/table.tsx +117 -0
  25. package/src/components/ui/tabs.tsx +55 -0
  26. package/src/components/ui/textarea.tsx +24 -0
  27. package/src/components/ui/tooltip.tsx +30 -0
  28. package/src/components/unified-table/UnifiedTable.tsx +553 -0
  29. package/src/components/unified-table/__tests__/components/BulkActionBar.test.tsx +477 -0
  30. package/src/components/unified-table/__tests__/components/ExportButton.test.tsx +467 -0
  31. package/src/components/unified-table/__tests__/components/InlineEditCell.test.tsx +159 -0
  32. package/src/components/unified-table/__tests__/components/SavedViewsDropdown.test.tsx +128 -0
  33. package/src/components/unified-table/__tests__/components/TablePagination.test.tsx +374 -0
  34. package/src/components/unified-table/__tests__/hooks/useColumnReorder.test.ts +191 -0
  35. package/src/components/unified-table/__tests__/hooks/useColumnResize.test.ts +122 -0
  36. package/src/components/unified-table/__tests__/hooks/useColumnVisibility.test.ts +594 -0
  37. package/src/components/unified-table/__tests__/hooks/useFilters.test.ts +460 -0
  38. package/src/components/unified-table/__tests__/hooks/usePagination.test.ts +439 -0
  39. package/src/components/unified-table/__tests__/hooks/useResponsive.test.ts +421 -0
  40. package/src/components/unified-table/__tests__/hooks/useSelection.test.ts +367 -0
  41. package/src/components/unified-table/__tests__/hooks/useTableKeyboard.test.ts +803 -0
  42. package/src/components/unified-table/__tests__/hooks/useTableState.test.ts +210 -0
  43. package/src/components/unified-table/__tests__/integration/table-with-selection.test.tsx +624 -0
  44. package/src/components/unified-table/__tests__/utils/export.test.ts +427 -0
  45. package/src/components/unified-table/components/BulkActionBar/index.tsx +119 -0
  46. package/src/components/unified-table/components/DataTableCore/index.tsx +473 -0
  47. package/src/components/unified-table/components/InlineEditCell/index.tsx +159 -0
  48. package/src/components/unified-table/components/MobileView/Card.tsx +218 -0
  49. package/src/components/unified-table/components/MobileView/CardActions.tsx +126 -0
  50. package/src/components/unified-table/components/MobileView/README.md +411 -0
  51. package/src/components/unified-table/components/MobileView/index.tsx +77 -0
  52. package/src/components/unified-table/components/MobileView/types.ts +77 -0
  53. package/src/components/unified-table/components/TableFilters/index.tsx +298 -0
  54. package/src/components/unified-table/components/TablePagination/index.tsx +157 -0
  55. package/src/components/unified-table/components/Toolbar/ExportButton.tsx +229 -0
  56. package/src/components/unified-table/components/Toolbar/SavedViewsDropdown.tsx +251 -0
  57. package/src/components/unified-table/components/Toolbar/StandardTableToolbar.tsx +146 -0
  58. package/src/components/unified-table/components/Toolbar/index.tsx +3 -0
  59. package/src/components/unified-table/hooks/index.ts +21 -0
  60. package/src/components/unified-table/hooks/useColumnReorder.ts +90 -0
  61. package/src/components/unified-table/hooks/useColumnResize.ts +123 -0
  62. package/src/components/unified-table/hooks/useColumnVisibility.ts +92 -0
  63. package/src/components/unified-table/hooks/useFilters.ts +53 -0
  64. package/src/components/unified-table/hooks/usePagination.ts +120 -0
  65. package/src/components/unified-table/hooks/useResponsive.ts +50 -0
  66. package/src/components/unified-table/hooks/useSelection.ts +152 -0
  67. package/src/components/unified-table/hooks/useTableKeyboard.ts +206 -0
  68. package/src/components/unified-table/hooks/useTablePreferences.ts +198 -0
  69. package/src/components/unified-table/hooks/useTableState.ts +103 -0
  70. package/src/components/unified-table/hooks/useTableURL.test.tsx +921 -0
  71. package/src/components/unified-table/hooks/useTableURL.ts +301 -0
  72. package/src/components/unified-table/index.ts +16 -0
  73. package/src/components/unified-table/types.ts +393 -0
  74. package/src/components/unified-table/utils/export.ts +236 -0
  75. package/src/components/unified-table/utils/index.ts +4 -0
  76. package/src/components/unified-table/utils/renderers.ts +105 -0
  77. package/src/components/unified-table/utils/themes.ts +87 -0
  78. package/src/components/unified-table/utils/validation.ts +122 -0
  79. package/src/index.ts +6 -0
  80. package/src/lib/utils.ts +1 -0
  81. package/src/theme/contract.ts +46 -0
  82. package/src/theme/index.ts +9 -0
  83. package/src/theme/tailwind.config.js +70 -0
  84. package/src/theme/tailwind.preset.ts +93 -0
  85. package/src/utils/cn.ts +6 -0
  86. package/src/utils/index.ts +91 -0
@@ -0,0 +1,421 @@
1
+ import { renderHook, act, waitFor } from '@testing-library/react'
2
+ import { useResponsive, DEFAULT_BREAKPOINTS } from '../../hooks/useResponsive'
3
+
4
+ describe('useResponsive', () => {
5
+ // Mock window.innerWidth
6
+ const setWindowWidth = (width: number) => {
7
+ Object.defineProperty(window, 'innerWidth', {
8
+ writable: true,
9
+ configurable: true,
10
+ value: width,
11
+ })
12
+ }
13
+
14
+ // Trigger resize event
15
+ const triggerResize = (width: number) => {
16
+ setWindowWidth(width)
17
+ window.dispatchEvent(new Event('resize'))
18
+ }
19
+
20
+ beforeEach(() => {
21
+ // Reset to desktop size by default
22
+ setWindowWidth(1440)
23
+ })
24
+
25
+ describe('Initialization', () => {
26
+ it('should initialize with desktop view mode for large screens', () => {
27
+ setWindowWidth(1440)
28
+ const { result } = renderHook(() => useResponsive())
29
+
30
+ expect(result.current.viewMode).toBe('desktop')
31
+ expect(result.current.isDesktop).toBe(true)
32
+ expect(result.current.isMobile).toBe(false)
33
+ expect(result.current.isTablet).toBe(false)
34
+ expect(result.current.isMobileOrTablet).toBe(false)
35
+ })
36
+
37
+ it('should initialize with tablet view mode for medium screens', async () => {
38
+ setWindowWidth(800)
39
+ const { result } = renderHook(() => useResponsive())
40
+
41
+ await waitFor(() => {
42
+ expect(result.current.viewMode).toBe('tablet')
43
+ })
44
+
45
+ expect(result.current.isTablet).toBe(true)
46
+ expect(result.current.isMobile).toBe(false)
47
+ expect(result.current.isDesktop).toBe(false)
48
+ expect(result.current.isMobileOrTablet).toBe(true)
49
+ })
50
+
51
+ it('should initialize with mobile view mode for small screens', async () => {
52
+ setWindowWidth(500)
53
+ const { result } = renderHook(() => useResponsive())
54
+
55
+ await waitFor(() => {
56
+ expect(result.current.viewMode).toBe('mobile')
57
+ })
58
+
59
+ expect(result.current.isMobile).toBe(true)
60
+ expect(result.current.isTablet).toBe(false)
61
+ expect(result.current.isDesktop).toBe(false)
62
+ expect(result.current.isMobileOrTablet).toBe(true)
63
+ })
64
+
65
+ it('should track window width', () => {
66
+ setWindowWidth(1200)
67
+ const { result } = renderHook(() => useResponsive())
68
+
69
+ expect(result.current.windowWidth).toBeGreaterThan(0)
70
+ })
71
+ })
72
+
73
+ describe('Default Breakpoints', () => {
74
+ it('should use default mobile breakpoint (768px)', async () => {
75
+ setWindowWidth(767)
76
+ const { result } = renderHook(() => useResponsive())
77
+
78
+ await waitFor(() => {
79
+ expect(result.current.viewMode).toBe('mobile')
80
+ })
81
+ })
82
+
83
+ it('should use default tablet breakpoint (1024px)', async () => {
84
+ setWindowWidth(1023)
85
+ const { result } = renderHook(() => useResponsive())
86
+
87
+ await waitFor(() => {
88
+ expect(result.current.viewMode).toBe('tablet')
89
+ })
90
+ })
91
+
92
+ it('should use default desktop breakpoint (1280px)', async () => {
93
+ setWindowWidth(1280)
94
+ const { result } = renderHook(() => useResponsive())
95
+
96
+ await waitFor(() => {
97
+ expect(result.current.viewMode).toBe('desktop')
98
+ })
99
+ })
100
+ })
101
+
102
+ describe('Custom Breakpoints', () => {
103
+ it('should accept custom breakpoints', async () => {
104
+ const customBreakpoints = {
105
+ mobile: 600,
106
+ tablet: 900,
107
+ desktop: 1200,
108
+ }
109
+
110
+ setWindowWidth(650)
111
+ const { result } = renderHook(() => useResponsive(customBreakpoints))
112
+
113
+ await waitFor(() => {
114
+ expect(result.current.viewMode).toBe('tablet')
115
+ })
116
+ })
117
+
118
+ it('should use custom mobile breakpoint', async () => {
119
+ const customBreakpoints = {
120
+ mobile: 600,
121
+ tablet: 900,
122
+ desktop: 1200,
123
+ }
124
+
125
+ setWindowWidth(550)
126
+ const { result } = renderHook(() => useResponsive(customBreakpoints))
127
+
128
+ await waitFor(() => {
129
+ expect(result.current.viewMode).toBe('mobile')
130
+ })
131
+ })
132
+
133
+ it('should use custom desktop breakpoint', async () => {
134
+ const customBreakpoints = {
135
+ mobile: 600,
136
+ tablet: 900,
137
+ desktop: 1200,
138
+ }
139
+
140
+ setWindowWidth(1250)
141
+ const { result } = renderHook(() => useResponsive(customBreakpoints))
142
+
143
+ await waitFor(() => {
144
+ expect(result.current.viewMode).toBe('desktop')
145
+ })
146
+ })
147
+ })
148
+
149
+ describe('Resize Handling', () => {
150
+ it('should update viewMode on window resize', async () => {
151
+ const { result } = renderHook(() => useResponsive())
152
+
153
+ // Start at desktop
154
+ setWindowWidth(1440)
155
+ triggerResize(1440)
156
+
157
+ await waitFor(() => {
158
+ expect(result.current.viewMode).toBe('desktop')
159
+ })
160
+
161
+ // Resize to tablet
162
+ act(() => {
163
+ triggerResize(800)
164
+ })
165
+
166
+ await waitFor(() => {
167
+ expect(result.current.viewMode).toBe('tablet')
168
+ })
169
+
170
+ // Resize to mobile
171
+ act(() => {
172
+ triggerResize(500)
173
+ })
174
+
175
+ await waitFor(() => {
176
+ expect(result.current.viewMode).toBe('mobile')
177
+ })
178
+ })
179
+
180
+ it('should update windowWidth on resize', async () => {
181
+ const { result } = renderHook(() => useResponsive())
182
+
183
+ act(() => {
184
+ triggerResize(1200)
185
+ })
186
+
187
+ await waitFor(() => {
188
+ expect(result.current.windowWidth).toBe(1200)
189
+ })
190
+
191
+ act(() => {
192
+ triggerResize(800)
193
+ })
194
+
195
+ await waitFor(() => {
196
+ expect(result.current.windowWidth).toBe(800)
197
+ })
198
+ })
199
+
200
+ it('should update boolean flags on resize', async () => {
201
+ const { result } = renderHook(() => useResponsive())
202
+
203
+ // Desktop
204
+ act(() => {
205
+ triggerResize(1440)
206
+ })
207
+
208
+ await waitFor(() => {
209
+ expect(result.current.isDesktop).toBe(true)
210
+ expect(result.current.isTablet).toBe(false)
211
+ expect(result.current.isMobile).toBe(false)
212
+ })
213
+
214
+ // Tablet
215
+ act(() => {
216
+ triggerResize(800)
217
+ })
218
+
219
+ await waitFor(() => {
220
+ expect(result.current.isDesktop).toBe(false)
221
+ expect(result.current.isTablet).toBe(true)
222
+ expect(result.current.isMobile).toBe(false)
223
+ })
224
+
225
+ // Mobile
226
+ act(() => {
227
+ triggerResize(500)
228
+ })
229
+
230
+ await waitFor(() => {
231
+ expect(result.current.isDesktop).toBe(false)
232
+ expect(result.current.isTablet).toBe(false)
233
+ expect(result.current.isMobile).toBe(true)
234
+ })
235
+ })
236
+ })
237
+
238
+ describe('isMobileOrTablet Helper', () => {
239
+ it('should return true for mobile', async () => {
240
+ setWindowWidth(500)
241
+ const { result } = renderHook(() => useResponsive())
242
+
243
+ await waitFor(() => {
244
+ expect(result.current.isMobileOrTablet).toBe(true)
245
+ })
246
+ })
247
+
248
+ it('should return true for tablet', async () => {
249
+ setWindowWidth(800)
250
+ const { result } = renderHook(() => useResponsive())
251
+
252
+ await waitFor(() => {
253
+ expect(result.current.isMobileOrTablet).toBe(true)
254
+ })
255
+ })
256
+
257
+ it('should return false for desktop', async () => {
258
+ setWindowWidth(1440)
259
+ const { result } = renderHook(() => useResponsive())
260
+
261
+ await waitFor(() => {
262
+ expect(result.current.isMobileOrTablet).toBe(false)
263
+ })
264
+ })
265
+ })
266
+
267
+ describe('Boundary Conditions', () => {
268
+ it('should handle exact breakpoint values correctly', async () => {
269
+ const { result } = renderHook(() => useResponsive())
270
+
271
+ // Exactly at mobile breakpoint should be tablet
272
+ act(() => {
273
+ triggerResize(768)
274
+ })
275
+
276
+ await waitFor(() => {
277
+ expect(result.current.viewMode).toBe('tablet')
278
+ })
279
+
280
+ // One pixel below mobile breakpoint should be mobile
281
+ act(() => {
282
+ triggerResize(767)
283
+ })
284
+
285
+ await waitFor(() => {
286
+ expect(result.current.viewMode).toBe('mobile')
287
+ })
288
+
289
+ // Exactly at tablet breakpoint should be desktop
290
+ act(() => {
291
+ triggerResize(1024)
292
+ })
293
+
294
+ await waitFor(() => {
295
+ expect(result.current.viewMode).toBe('desktop')
296
+ })
297
+
298
+ // One pixel below tablet breakpoint should be tablet
299
+ act(() => {
300
+ triggerResize(1023)
301
+ })
302
+
303
+ await waitFor(() => {
304
+ expect(result.current.viewMode).toBe('tablet')
305
+ })
306
+ })
307
+
308
+ it('should handle very small widths', async () => {
309
+ setWindowWidth(320)
310
+ const { result } = renderHook(() => useResponsive())
311
+
312
+ await waitFor(() => {
313
+ expect(result.current.viewMode).toBe('mobile')
314
+ expect(result.current.windowWidth).toBe(320)
315
+ })
316
+ })
317
+
318
+ it('should handle very large widths', async () => {
319
+ setWindowWidth(2560)
320
+ const { result } = renderHook(() => useResponsive())
321
+
322
+ await waitFor(() => {
323
+ expect(result.current.viewMode).toBe('desktop')
324
+ expect(result.current.windowWidth).toBe(2560)
325
+ })
326
+ })
327
+ })
328
+
329
+ describe('Cleanup', () => {
330
+ it('should remove resize listener on unmount', () => {
331
+ const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
332
+
333
+ const { unmount } = renderHook(() => useResponsive())
334
+
335
+ unmount()
336
+
337
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function))
338
+
339
+ removeEventListenerSpy.mockRestore()
340
+ })
341
+
342
+ it('should not cause memory leaks on multiple mount/unmount cycles', () => {
343
+ const { unmount: unmount1 } = renderHook(() => useResponsive())
344
+ const { unmount: unmount2 } = renderHook(() => useResponsive())
345
+ const { unmount: unmount3 } = renderHook(() => useResponsive())
346
+
347
+ expect(() => {
348
+ unmount1()
349
+ unmount2()
350
+ unmount3()
351
+ }).not.toThrow()
352
+ })
353
+ })
354
+
355
+ describe('Breakpoint Changes', () => {
356
+ it('should update when breakpoints prop changes', async () => {
357
+ const { result, rerender } = renderHook(
358
+ ({ breakpoints }) => useResponsive(breakpoints),
359
+ {
360
+ initialProps: {
361
+ breakpoints: DEFAULT_BREAKPOINTS,
362
+ },
363
+ }
364
+ )
365
+
366
+ setWindowWidth(800)
367
+ triggerResize(800)
368
+
369
+ await waitFor(() => {
370
+ expect(result.current.viewMode).toBe('tablet')
371
+ })
372
+
373
+ // Change breakpoints so 800 is now mobile
374
+ rerender({
375
+ breakpoints: {
376
+ mobile: 900,
377
+ tablet: 1200,
378
+ desktop: 1400,
379
+ },
380
+ })
381
+
382
+ await waitFor(() => {
383
+ expect(result.current.viewMode).toBe('mobile')
384
+ })
385
+ })
386
+ })
387
+
388
+ describe('Multiple Resize Events', () => {
389
+ it('should handle rapid resize events', async () => {
390
+ const { result } = renderHook(() => useResponsive())
391
+
392
+ act(() => {
393
+ triggerResize(1440)
394
+ triggerResize(800)
395
+ triggerResize(500)
396
+ triggerResize(1200)
397
+ })
398
+
399
+ await waitFor(() => {
400
+ expect(result.current.windowWidth).toBe(1200)
401
+ expect(result.current.viewMode).toBe('desktop')
402
+ })
403
+ })
404
+
405
+ it('should maintain consistency during resize events', async () => {
406
+ const { result } = renderHook(() => useResponsive())
407
+
408
+ for (let i = 0; i < 10; i++) {
409
+ act(() => {
410
+ triggerResize(400 + i * 100)
411
+ })
412
+ }
413
+
414
+ await waitFor(() => {
415
+ // Final width should be 1300 (400 + 9*100)
416
+ expect(result.current.windowWidth).toBe(1300)
417
+ expect(result.current.viewMode).toBe('desktop')
418
+ })
419
+ })
420
+ })
421
+ })