@yuno-payments/dashboard-design-system 0.0.1

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 (71) hide show
  1. package/.storybook/main.ts +20 -0
  2. package/.storybook/preview.ts +18 -0
  3. package/.storybook/vitest.setup.ts +7 -0
  4. package/README.md +69 -0
  5. package/components.json +21 -0
  6. package/eslint.config.js +26 -0
  7. package/index.html +13 -0
  8. package/package.json +57 -0
  9. package/public/vite.svg +1 -0
  10. package/src/App.css +42 -0
  11. package/src/App.tsx +11 -0
  12. package/src/assets/react.svg +1 -0
  13. package/src/components/atoms/button/button.stories.tsx +222 -0
  14. package/src/components/atoms/button/button.test.tsx +78 -0
  15. package/src/components/atoms/button/index.tsx +80 -0
  16. package/src/components/atoms/checkbox/checkbox.stories.tsx +314 -0
  17. package/src/components/atoms/checkbox/checkbox.test.tsx +278 -0
  18. package/src/components/atoms/checkbox/index.tsx +103 -0
  19. package/src/components/atoms/chip/chip.stories.tsx +317 -0
  20. package/src/components/atoms/chip/chip.test.tsx +300 -0
  21. package/src/components/atoms/chip/index.tsx +114 -0
  22. package/src/components/atoms/input/index.tsx +27 -0
  23. package/src/components/atoms/link/index.tsx +79 -0
  24. package/src/components/atoms/link/link.stories.tsx +159 -0
  25. package/src/components/atoms/link/link.test.tsx +176 -0
  26. package/src/components/atoms/radiobutton/index.tsx +103 -0
  27. package/src/components/atoms/radiobutton/radiobutton.stories.tsx +314 -0
  28. package/src/components/atoms/radiobutton/radiobutton.test.tsx +245 -0
  29. package/src/components/atoms/tag/index.tsx +196 -0
  30. package/src/components/atoms/tag/tag.stories.tsx +281 -0
  31. package/src/components/atoms/tag/tag.test.tsx +282 -0
  32. package/src/components/atoms/typography/index.tsx +62 -0
  33. package/src/components/atoms/typography/typography.stories.tsx +214 -0
  34. package/src/components/atoms/typography/typography.test.tsx +187 -0
  35. package/src/components/index.tsx +17 -0
  36. package/src/components/molecules/announcement/announcement.stories.tsx +277 -0
  37. package/src/components/molecules/announcement/announcement.test.tsx +354 -0
  38. package/src/components/molecules/announcement/index.tsx +200 -0
  39. package/src/components/molecules/notification-alert/index.tsx +293 -0
  40. package/src/components/molecules/notification-alert/notification-alert.stories.tsx +418 -0
  41. package/src/components/molecules/notification-alert/notification-alert.test.tsx +454 -0
  42. package/src/components/molecules/popover/index.tsx +175 -0
  43. package/src/components/molecules/popover/popover.stories.tsx +241 -0
  44. package/src/components/molecules/popover/popover.test.tsx +191 -0
  45. package/src/components/molecules/textfield/index.tsx +154 -0
  46. package/src/components/molecules/textfield/textfield.stories.tsx +168 -0
  47. package/src/components/molecules/textfield/textfield.test.tsx +157 -0
  48. package/src/components/molecules/tooltip/index.tsx +263 -0
  49. package/src/components/molecules/tooltip/tooltip.stories.tsx +363 -0
  50. package/src/components/molecules/tooltip/tooltip.test.tsx +468 -0
  51. package/src/components/organisms/dialog/dialog.stories.tsx +522 -0
  52. package/src/components/organisms/dialog/dialog.test.tsx +525 -0
  53. package/src/components/organisms/dialog/index.tsx +233 -0
  54. package/src/components/organisms/dropdown/dropdown.stories.tsx +529 -0
  55. package/src/components/organisms/dropdown/dropdown.test.tsx +390 -0
  56. package/src/components/organisms/dropdown/index.tsx +624 -0
  57. package/src/index.css +184 -0
  58. package/src/lib/color-utils.ts +94 -0
  59. package/src/lib/utils.ts +6 -0
  60. package/src/main.tsx +10 -0
  61. package/src/stories/Colors.stories.tsx +107 -0
  62. package/src/stories/Shadows.stories.tsx +110 -0
  63. package/src/stories/Spacing.stories.tsx +121 -0
  64. package/src/stories/Typography.stories.tsx +197 -0
  65. package/src/vite-env.d.ts +1 -0
  66. package/tsconfig.app.json +33 -0
  67. package/tsconfig.json +13 -0
  68. package/tsconfig.node.json +25 -0
  69. package/vite.config.ts +43 -0
  70. package/vitest.config.ts +15 -0
  71. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,525 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import * as React from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { act } from 'react-dom/test-utils'
5
+ import { Dialog } from './index'
6
+
7
+ function render(ui: React.ReactElement) {
8
+ const container = document.createElement('div')
9
+ document.body.appendChild(container)
10
+ const root = createRoot(container)
11
+ act(() => {
12
+ root.render(ui)
13
+ })
14
+ return { container, root }
15
+ }
16
+
17
+ function getByText(container: HTMLElement, text: string) {
18
+ const el = Array.from(container.querySelectorAll('*')).find((n) => n.textContent?.includes(text))
19
+ if (!el) throw new Error(`Element with text "${text}" not found`)
20
+ return el as HTMLElement
21
+ }
22
+
23
+ function getByTestId(container: HTMLElement, testId: string) {
24
+ const el = container.querySelector(`[data-testid="${testId}"]`)
25
+ if (!el) throw new Error(`Element with data-testid="${testId}" not found`)
26
+ return el as HTMLElement
27
+ }
28
+
29
+ function queryByTestId(container: HTMLElement, testId: string) {
30
+ return container.querySelector(`[data-testid="${testId}"]`) as HTMLElement | null
31
+ }
32
+
33
+ function getByRole(container: HTMLElement, role: string, options?: { name?: string }) {
34
+ const elements = Array.from(container.querySelectorAll(`[role="${role}"]`))
35
+ if (options?.name) {
36
+ const el = elements.find((el) => {
37
+ const ariaLabel = el.getAttribute('aria-label')
38
+ return ariaLabel && ariaLabel.includes(options.name!)
39
+ })
40
+ if (!el) throw new Error(`Element with role "${role}" and name "${options.name}" not found`)
41
+ return el as HTMLElement
42
+ }
43
+ if (elements.length === 0) throw new Error(`Element with role "${role}" not found`)
44
+ return elements[0] as HTMLElement
45
+ }
46
+
47
+ describe('Dialog', () => {
48
+ beforeEach(() => {
49
+ document.body.innerHTML = ''
50
+ document.body.style.overflow = 'unset'
51
+ })
52
+
53
+ it('renders when open is true', () => {
54
+ const { container } = render(
55
+ <Dialog title="Test Dialog" open={true} closeIcon={() => {}}>
56
+ <p>Dialog content</p>
57
+ </Dialog>
58
+ )
59
+
60
+ expect(getByText(container, 'Test Dialog')).toBeTruthy()
61
+ expect(getByText(container, 'Dialog content')).toBeTruthy()
62
+ })
63
+
64
+ it('does not render when open is false', () => {
65
+ const { container } = render(
66
+ <Dialog title="Test Dialog" open={false} closeIcon={() => {}}>
67
+ <p>Dialog content</p>
68
+ </Dialog>
69
+ )
70
+
71
+ expect(container.firstChild).toBeNull()
72
+ })
73
+
74
+ it('renders title when provided', () => {
75
+ const { container } = render(
76
+ <Dialog title="Custom Title" open={true} closeIcon={() => {}}>
77
+ <p>Content</p>
78
+ </Dialog>
79
+ )
80
+
81
+ expect(getByText(container, 'Custom Title')).toBeTruthy()
82
+ })
83
+
84
+ it('renders JSX title when provided', () => {
85
+ const jsxTitle = <span>JSX Title</span>
86
+ const { container } = render(
87
+ <Dialog title={jsxTitle} open={true} closeIcon={() => {}}>
88
+ <p>Content</p>
89
+ </Dialog>
90
+ )
91
+
92
+ expect(getByText(container, 'JSX Title')).toBeTruthy()
93
+ })
94
+
95
+ it('calls closeIcon when close button is clicked', () => {
96
+ const handleClose = vi.fn()
97
+ const { container } = render(
98
+ <Dialog title="Test" open={true} closeIcon={handleClose}>
99
+ <p>Content</p>
100
+ </Dialog>
101
+ )
102
+
103
+ const closeButton = getByTestId(container, 'close-button')
104
+ act(() => {
105
+ closeButton.click()
106
+ })
107
+
108
+ expect(handleClose).toHaveBeenCalledTimes(1)
109
+ })
110
+
111
+ it('calls closeIcon when backdrop is clicked', () => {
112
+ const handleClose = vi.fn()
113
+ const { container } = render(
114
+ <Dialog title="Test" open={true} closeIcon={handleClose}>
115
+ <p>Content</p>
116
+ </Dialog>
117
+ )
118
+
119
+ const backdrop = container.querySelector('.bg-black\\/25')
120
+ expect(backdrop).toBeTruthy()
121
+
122
+ act(() => {
123
+ (backdrop as HTMLElement).click()
124
+ })
125
+
126
+ expect(handleClose).toHaveBeenCalledTimes(1)
127
+ })
128
+
129
+ it('renders back button when backButtonAction is provided', () => {
130
+ const handleBack = vi.fn()
131
+ const { container } = render(
132
+ <Dialog title="Test" open={true} closeIcon={() => {}} backButtonAction={handleBack}>
133
+ <p>Content</p>
134
+ </Dialog>
135
+ )
136
+
137
+ const backButton = getByTestId(container, 'back-button')
138
+ expect(backButton).toBeTruthy()
139
+
140
+ act(() => {
141
+ backButton.click()
142
+ })
143
+
144
+ expect(handleBack).toHaveBeenCalledTimes(1)
145
+ })
146
+
147
+ it('does not render back button when backButtonAction is not provided', () => {
148
+ const { container } = render(
149
+ <Dialog title="Test" open={true} closeIcon={() => {}}>
150
+ <p>Content</p>
151
+ </Dialog>
152
+ )
153
+
154
+ expect(queryByTestId(container, 'back-button')).toBeNull()
155
+ })
156
+
157
+ it('renders other actions when provided', () => {
158
+ const otherActions = <button data-testid="custom-action">Custom</button>
159
+ const { container } = render(
160
+ <Dialog title="Test" open={true} closeIcon={() => {}} otherActions={otherActions}>
161
+ <p>Content</p>
162
+ </Dialog>
163
+ )
164
+
165
+ expect(getByTestId(container, 'custom-action')).toBeTruthy()
166
+ })
167
+
168
+ it('renders buttons when provided', () => {
169
+ const buttons = [
170
+ {
171
+ variant: 'primary' as const,
172
+ label: 'Confirm',
173
+ event: vi.fn(),
174
+ testId: 'confirm-btn',
175
+ },
176
+ {
177
+ variant: 'secondary' as const,
178
+ label: 'Cancel',
179
+ event: vi.fn(),
180
+ testId: 'cancel-btn',
181
+ },
182
+ ]
183
+
184
+ const { container } = render(
185
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
186
+ <p>Content</p>
187
+ </Dialog>
188
+ )
189
+
190
+ expect(getByTestId(container, 'confirm-btn')).toBeTruthy()
191
+ expect(getByTestId(container, 'cancel-btn')).toBeTruthy()
192
+ })
193
+
194
+ it('calls button event handlers when clicked', () => {
195
+ const handleConfirm = vi.fn()
196
+ const handleCancel = vi.fn()
197
+ const buttons = [
198
+ {
199
+ variant: 'primary' as const,
200
+ label: 'Confirm',
201
+ event: handleConfirm,
202
+ testId: 'confirm-btn',
203
+ },
204
+ {
205
+ variant: 'secondary' as const,
206
+ label: 'Cancel',
207
+ event: handleCancel,
208
+ testId: 'cancel-btn',
209
+ },
210
+ ]
211
+
212
+ const { container } = render(
213
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
214
+ <p>Content</p>
215
+ </Dialog>
216
+ )
217
+
218
+ act(() => {
219
+ getByTestId(container, 'confirm-btn').click()
220
+ })
221
+
222
+ act(() => {
223
+ getByTestId(container, 'cancel-btn').click()
224
+ })
225
+
226
+ expect(handleConfirm).toHaveBeenCalledTimes(1)
227
+ expect(handleCancel).toHaveBeenCalledTimes(1)
228
+ })
229
+
230
+ it('does not render buttons when showActions is false', () => {
231
+ const buttons = [
232
+ {
233
+ variant: 'primary' as const,
234
+ label: 'Confirm',
235
+ event: vi.fn(),
236
+ testId: 'confirm-btn',
237
+ },
238
+ ]
239
+
240
+ const { container } = render(
241
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons} showActions={false}>
242
+ <p>Content</p>
243
+ </Dialog>
244
+ )
245
+
246
+ expect(queryByTestId(container, 'confirm-btn')).toBeNull()
247
+ })
248
+
249
+ it('does not render header when withHeader is false', () => {
250
+ const { container } = render(
251
+ <Dialog title="Test" open={true} closeIcon={() => {}} withHeader={false}>
252
+ <p>Content</p>
253
+ </Dialog>
254
+ )
255
+
256
+ expect(() => getByText(container, 'Test')).toThrow()
257
+ expect(queryByTestId(container, 'close-button')).toBeNull()
258
+ })
259
+
260
+ it('applies custom className', () => {
261
+ const { container } = render(
262
+ <Dialog title="Test" open={true} closeIcon={() => {}} className="custom-class">
263
+ <p>Content</p>
264
+ </Dialog>
265
+ )
266
+
267
+ const dialog = getByRole(container, 'dialog')
268
+ expect(dialog.className).toMatch(/custom-class/)
269
+ })
270
+
271
+ it('forwards ref correctly', () => {
272
+ const ref = { current: null }
273
+ render(
274
+ <Dialog ref={ref} title="Test" open={true} closeIcon={() => {}}>
275
+ <p>Content</p>
276
+ </Dialog>
277
+ )
278
+
279
+ expect(ref.current).toBeTruthy()
280
+ })
281
+
282
+ it('passes through HTML attributes', () => {
283
+ const { container } = render(
284
+ <Dialog title="Test" open={true} closeIcon={() => {}} data-custom="value">
285
+ <p>Content</p>
286
+ </Dialog>
287
+ )
288
+
289
+ const dialog = getByRole(container, 'dialog')
290
+ expect(dialog.getAttribute('data-custom')).toBe('value')
291
+ })
292
+
293
+ it('renders with start vertical alignment', () => {
294
+ const { container } = render(
295
+ <Dialog title="Test" open={true} closeIcon={() => {}} verticalAlign="start">
296
+ <p>Content</p>
297
+ </Dialog>
298
+ )
299
+
300
+ const dialogContainer = container.querySelector('.items-start')
301
+ expect(dialogContainer).toBeTruthy()
302
+ })
303
+
304
+ it('renders with end vertical alignment', () => {
305
+ const { container } = render(
306
+ <Dialog title="Test" open={true} closeIcon={() => {}} verticalAlign="end">
307
+ <p>Content</p>
308
+ </Dialog>
309
+ )
310
+
311
+ const dialogContainer = container.querySelector('.items-end')
312
+ expect(dialogContainer).toBeTruthy()
313
+ })
314
+
315
+ it('renders with center vertical alignment by default', () => {
316
+ const { container } = render(
317
+ <Dialog title="Test" open={true} closeIcon={() => {}}>
318
+ <p>Content</p>
319
+ </Dialog>
320
+ )
321
+
322
+ const dialogContainer = container.querySelector('.items-center')
323
+ expect(dialogContainer).toBeTruthy()
324
+ })
325
+
326
+ it('handles button loading state', () => {
327
+ const buttons = [
328
+ {
329
+ variant: 'primary' as const,
330
+ label: 'Submit',
331
+ event: vi.fn(),
332
+ loading: true,
333
+ testId: 'submit-btn',
334
+ },
335
+ ]
336
+
337
+ const { container } = render(
338
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
339
+ <p>Content</p>
340
+ </Dialog>
341
+ )
342
+
343
+ const button = getByTestId(container, 'submit-btn')
344
+ expect(button.textContent).toBe('Loading...')
345
+ expect(button.hasAttribute('disabled')).toBe(true)
346
+ })
347
+
348
+ it('handles button disabled state', () => {
349
+ const buttons = [
350
+ {
351
+ variant: 'primary' as const,
352
+ label: 'Submit',
353
+ event: vi.fn(),
354
+ disabled: true,
355
+ testId: 'submit-btn',
356
+ },
357
+ ]
358
+
359
+ const { container } = render(
360
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
361
+ <p>Content</p>
362
+ </Dialog>
363
+ )
364
+
365
+ const button = getByTestId(container, 'submit-btn')
366
+ expect(button.hasAttribute('disabled')).toBe(true)
367
+ })
368
+
369
+ it('maps button variants correctly', () => {
370
+ const buttons = [
371
+ {
372
+ variant: 'primary' as const,
373
+ label: 'Primary',
374
+ event: vi.fn(),
375
+ testId: 'primary-btn',
376
+ },
377
+ {
378
+ variant: 'secondary' as const,
379
+ label: 'Secondary',
380
+ event: vi.fn(),
381
+ testId: 'secondary-btn',
382
+ },
383
+ {
384
+ variant: 'tertiary' as const,
385
+ label: 'Tertiary',
386
+ event: vi.fn(),
387
+ testId: 'tertiary-btn',
388
+ },
389
+ ]
390
+
391
+ const { container } = render(
392
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
393
+ <p>Content</p>
394
+ </Dialog>
395
+ )
396
+
397
+ expect(getByTestId(container, 'primary-btn')).toBeTruthy()
398
+ expect(getByTestId(container, 'secondary-btn')).toBeTruthy()
399
+ expect(getByTestId(container, 'tertiary-btn')).toBeTruthy()
400
+ })
401
+
402
+ it('maps button sizes correctly', () => {
403
+ const buttons = [
404
+ {
405
+ variant: 'primary' as const,
406
+ label: 'Small',
407
+ event: vi.fn(),
408
+ size: 'small' as const,
409
+ testId: 'small-btn',
410
+ },
411
+ {
412
+ variant: 'primary' as const,
413
+ label: 'Medium',
414
+ event: vi.fn(),
415
+ size: 'medium' as const,
416
+ testId: 'medium-btn',
417
+ },
418
+ {
419
+ variant: 'primary' as const,
420
+ label: 'Large',
421
+ event: vi.fn(),
422
+ size: 'large' as const,
423
+ testId: 'large-btn',
424
+ },
425
+ ]
426
+
427
+ const { container } = render(
428
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
429
+ <p>Content</p>
430
+ </Dialog>
431
+ )
432
+
433
+ expect(getByTestId(container, 'small-btn')).toBeTruthy()
434
+ expect(getByTestId(container, 'medium-btn')).toBeTruthy()
435
+ expect(getByTestId(container, 'large-btn')).toBeTruthy()
436
+ })
437
+
438
+ it('sets body overflow hidden when open', () => {
439
+ render(
440
+ <Dialog title="Test" open={true} closeIcon={() => {}}>
441
+ <p>Content</p>
442
+ </Dialog>
443
+ )
444
+
445
+ expect(document.body.style.overflow).toBe('hidden')
446
+ })
447
+
448
+ it('resets body overflow when closed', () => {
449
+ const { root } = render(
450
+ <Dialog title="Test" open={true} closeIcon={() => {}}>
451
+ <p>Content</p>
452
+ </Dialog>
453
+ )
454
+
455
+ expect(document.body.style.overflow).toBe('hidden')
456
+
457
+ act(() => {
458
+ root.render(
459
+ <Dialog title="Test" open={false} closeIcon={() => {}}>
460
+ <p>Content</p>
461
+ </Dialog>
462
+ )
463
+ })
464
+
465
+ expect(document.body.style.overflow).toBe('unset')
466
+ })
467
+
468
+ it('handles custom width and height', () => {
469
+ const { container } = render(
470
+ <Dialog
471
+ title="Test"
472
+ open={true}
473
+ closeIcon={() => {}}
474
+ widthContainer={600}
475
+ heightContainer={400}
476
+ >
477
+ <p>Content</p>
478
+ </Dialog>
479
+ )
480
+
481
+ const dialog = getByRole(container, 'dialog')
482
+ expect(dialog.style.width).toBe('600px')
483
+ expect(dialog.style.height).toBe('400px')
484
+ })
485
+
486
+ it('handles JSX element as button label', () => {
487
+ const buttons = [
488
+ {
489
+ variant: 'primary' as const,
490
+ label: <span>JSX Label</span>,
491
+ event: vi.fn(),
492
+ testId: 'jsx-btn',
493
+ },
494
+ ]
495
+
496
+ const { container } = render(
497
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
498
+ <p>Content</p>
499
+ </Dialog>
500
+ )
501
+
502
+ expect(getByText(container, 'JSX Label')).toBeTruthy()
503
+ })
504
+
505
+ it('handles button type attribute', () => {
506
+ const buttons = [
507
+ {
508
+ variant: 'primary' as const,
509
+ label: 'Submit',
510
+ event: vi.fn(),
511
+ type: 'submit' as const,
512
+ testId: 'submit-btn',
513
+ },
514
+ ]
515
+
516
+ const { container } = render(
517
+ <Dialog title="Test" open={true} closeIcon={() => {}} buttons={buttons}>
518
+ <p>Content</p>
519
+ </Dialog>
520
+ )
521
+
522
+ const button = getByTestId(container, 'submit-btn')
523
+ expect(button.getAttribute('type')).toBe('submit')
524
+ })
525
+ })