@open-mercato/ui 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe

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 (92) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +14 -0
  3. package/dist/backend/DataTable.js +28 -62
  4. package/dist/backend/DataTable.js.map +2 -2
  5. package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
  6. package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
  7. package/dist/primitives/activity-feed.js +196 -0
  8. package/dist/primitives/activity-feed.js.map +7 -0
  9. package/dist/primitives/avatar.js +140 -14
  10. package/dist/primitives/avatar.js.map +3 -3
  11. package/dist/primitives/badge.js +94 -5
  12. package/dist/primitives/badge.js.map +2 -2
  13. package/dist/primitives/button-group.js +52 -0
  14. package/dist/primitives/button-group.js.map +7 -0
  15. package/dist/primitives/color-picker.js +520 -0
  16. package/dist/primitives/color-picker.js.map +7 -0
  17. package/dist/primitives/command-menu.js +355 -0
  18. package/dist/primitives/command-menu.js.map +7 -0
  19. package/dist/primitives/dialog.js +210 -35
  20. package/dist/primitives/dialog.js.map +2 -2
  21. package/dist/primitives/drawer.js +219 -0
  22. package/dist/primitives/drawer.js.map +7 -0
  23. package/dist/primitives/notification-feed.js +213 -0
  24. package/dist/primitives/notification-feed.js.map +7 -0
  25. package/dist/primitives/notification.js +23 -2
  26. package/dist/primitives/notification.js.map +2 -2
  27. package/dist/primitives/pagination.js +245 -0
  28. package/dist/primitives/pagination.js.map +7 -0
  29. package/dist/primitives/progress.js +178 -5
  30. package/dist/primitives/progress.js.map +2 -2
  31. package/dist/primitives/rating.js +174 -0
  32. package/dist/primitives/rating.js.map +7 -0
  33. package/dist/primitives/scroll-area.js +82 -0
  34. package/dist/primitives/scroll-area.js.map +7 -0
  35. package/dist/primitives/segmented-control.js +101 -0
  36. package/dist/primitives/segmented-control.js.map +7 -0
  37. package/dist/primitives/separator.js +103 -4
  38. package/dist/primitives/separator.js.map +2 -2
  39. package/dist/primitives/slider.js +115 -0
  40. package/dist/primitives/slider.js.map +7 -0
  41. package/dist/primitives/step-indicator.js +187 -0
  42. package/dist/primitives/step-indicator.js.map +7 -0
  43. package/dist/primitives/table.js +102 -10
  44. package/dist/primitives/table.js.map +2 -2
  45. package/dist/primitives/tabs.js +122 -14
  46. package/dist/primitives/tabs.js.map +2 -2
  47. package/dist/primitives/time-picker.js +9 -11
  48. package/dist/primitives/time-picker.js.map +2 -2
  49. package/package.json +8 -4
  50. package/src/backend/DataTable.tsx +43 -60
  51. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  52. package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
  53. package/src/primitives/__tests__/avatar.test.tsx +131 -0
  54. package/src/primitives/__tests__/badge.test.tsx +189 -0
  55. package/src/primitives/__tests__/button-group.test.tsx +117 -0
  56. package/src/primitives/__tests__/color-picker.test.tsx +589 -0
  57. package/src/primitives/__tests__/command-menu.test.tsx +231 -0
  58. package/src/primitives/__tests__/dialog.test.tsx +290 -0
  59. package/src/primitives/__tests__/drawer.test.tsx +264 -0
  60. package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
  61. package/src/primitives/__tests__/notification.test.tsx +102 -2
  62. package/src/primitives/__tests__/pagination.test.tsx +268 -0
  63. package/src/primitives/__tests__/progress.test.tsx +222 -0
  64. package/src/primitives/__tests__/rating.test.tsx +164 -0
  65. package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
  66. package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
  67. package/src/primitives/__tests__/separator.test.tsx +143 -0
  68. package/src/primitives/__tests__/slider.test.tsx +86 -0
  69. package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
  70. package/src/primitives/__tests__/table.test.tsx +199 -0
  71. package/src/primitives/__tests__/tabs.test.tsx +139 -0
  72. package/src/primitives/activity-feed.tsx +326 -0
  73. package/src/primitives/avatar.tsx +225 -22
  74. package/src/primitives/badge.tsx +141 -6
  75. package/src/primitives/button-group.tsx +100 -0
  76. package/src/primitives/color-picker.tsx +693 -0
  77. package/src/primitives/command-menu.tsx +503 -0
  78. package/src/primitives/dialog.tsx +315 -51
  79. package/src/primitives/drawer.tsx +338 -0
  80. package/src/primitives/notification-feed.tsx +358 -0
  81. package/src/primitives/notification.tsx +42 -2
  82. package/src/primitives/pagination.tsx +376 -0
  83. package/src/primitives/progress.tsx +267 -6
  84. package/src/primitives/rating.tsx +245 -0
  85. package/src/primitives/scroll-area.tsx +169 -0
  86. package/src/primitives/segmented-control.tsx +162 -0
  87. package/src/primitives/separator.tsx +163 -3
  88. package/src/primitives/slider.tsx +160 -0
  89. package/src/primitives/step-indicator.tsx +287 -0
  90. package/src/primitives/table.tsx +142 -11
  91. package/src/primitives/tabs.tsx +174 -12
  92. package/src/primitives/time-picker.tsx +9 -11
@@ -0,0 +1,189 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as React from 'react'
4
+ import { render, fireEvent } from '@testing-library/react'
5
+
6
+ import { Badge } from '../badge'
7
+
8
+ describe('Badge (Phase B.8)', () => {
9
+ it('renders the badge root with data-slot + default variant + default size', () => {
10
+ const { container } = render(<Badge>Active</Badge>)
11
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
12
+ expect(root).not.toBeNull()
13
+ expect(root.getAttribute('data-variant')).toBe('default')
14
+ expect(root.getAttribute('data-size')).toBe('default')
15
+ expect(root.textContent).toBe('Active')
16
+ expect(root.className).toContain('bg-primary')
17
+ expect(root.className).toContain('rounded-full')
18
+ })
19
+
20
+ it('renders all 10 pre-existing variants verbatim (backward compat for 83 import sites)', () => {
21
+ const cases: Array<{
22
+ variant: 'default' | 'secondary' | 'destructive' | 'outline' | 'muted' | 'success' | 'warning' | 'info' | 'neutral' | 'error'
23
+ cls: string
24
+ }> = [
25
+ { variant: 'default', cls: 'bg-primary' },
26
+ { variant: 'secondary', cls: 'bg-secondary' },
27
+ // destructive stays solid loud (bg-destructive) per BC guarantee
28
+ // in spec 2026-05-13-ds-foundation-v5.md. Soft error-tinted look
29
+ // is the new `error` variant below.
30
+ { variant: 'destructive', cls: 'bg-destructive' },
31
+ { variant: 'outline', cls: 'text-foreground' },
32
+ { variant: 'muted', cls: 'bg-muted' },
33
+ { variant: 'success', cls: 'bg-status-success-bg' },
34
+ { variant: 'warning', cls: 'bg-status-warning-bg' },
35
+ { variant: 'info', cls: 'bg-status-info-bg' },
36
+ { variant: 'neutral', cls: 'bg-status-neutral-bg' },
37
+ { variant: 'error', cls: 'bg-status-error-bg' },
38
+ ]
39
+ for (const { variant, cls } of cases) {
40
+ const { container, unmount } = render(<Badge variant={variant}>x</Badge>)
41
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
42
+ expect(root.getAttribute('data-variant')).toBe(variant)
43
+ expect(root.className).toContain(cls)
44
+ unmount()
45
+ }
46
+ })
47
+
48
+ it('adds the brand variant per Tag brand-violet pattern', () => {
49
+ const { container } = render(<Badge variant="brand">Q1 2026</Badge>)
50
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
51
+ expect(root.getAttribute('data-variant')).toBe('brand')
52
+ expect(root.className).toContain('bg-brand-violet/10')
53
+ expect(root.className).toContain('text-brand-violet')
54
+ expect(root.className).toContain('border-brand-violet/30')
55
+ })
56
+
57
+ it('applies size variants (sm / default / lg)', () => {
58
+ const cases: Array<{ size: 'sm' | 'default' | 'lg'; cls: string }> = [
59
+ { size: 'sm', cls: 'text-[10px]' },
60
+ { size: 'default', cls: 'text-xs' },
61
+ { size: 'lg', cls: 'text-sm' },
62
+ ]
63
+ for (const { size, cls } of cases) {
64
+ const { container, unmount } = render(<Badge size={size}>x</Badge>)
65
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
66
+ expect(root.getAttribute('data-size')).toBe(size)
67
+ expect(root.className).toContain(cls)
68
+ unmount()
69
+ }
70
+ })
71
+
72
+ it('renders the leading dot when dot=true with the variant tone', () => {
73
+ const { container } = render(
74
+ <Badge variant="success" dot>
75
+ Active
76
+ </Badge>,
77
+ )
78
+ const dot = container.querySelector('[data-slot="badge-dot"]') as HTMLElement
79
+ expect(dot).not.toBeNull()
80
+ expect(dot.className).toContain('bg-status-success-icon')
81
+ expect(dot.className).toContain('rounded-full')
82
+ expect(dot.className).toContain('size-1.5')
83
+ })
84
+
85
+ it('omits the dot by default', () => {
86
+ const { container } = render(<Badge variant="success">No dot</Badge>)
87
+ expect(container.querySelector('[data-slot="badge-dot"]')).toBeNull()
88
+ })
89
+
90
+ it('dot tone matches each variant', () => {
91
+ const cases: Array<{ variant: 'success' | 'warning' | 'info' | 'error' | 'brand'; cls: string }> = [
92
+ { variant: 'success', cls: 'bg-status-success-icon' },
93
+ { variant: 'warning', cls: 'bg-status-warning-icon' },
94
+ { variant: 'info', cls: 'bg-status-info-icon' },
95
+ { variant: 'error', cls: 'bg-status-error-icon' },
96
+ { variant: 'brand', cls: 'bg-brand-violet' },
97
+ ]
98
+ for (const { variant, cls } of cases) {
99
+ const { container, unmount } = render(
100
+ <Badge variant={variant} dot>
101
+ x
102
+ </Badge>,
103
+ )
104
+ const dot = container.querySelector('[data-slot="badge-dot"]') as HTMLElement
105
+ expect(dot.className).toContain(cls)
106
+ unmount()
107
+ }
108
+ })
109
+
110
+ it('dot size scales with badge size (sm/default = size-1.5, lg = size-2)', () => {
111
+ const { container, rerender } = render(
112
+ <Badge variant="info" size="sm" dot>
113
+ x
114
+ </Badge>,
115
+ )
116
+ let dot = container.querySelector('[data-slot="badge-dot"]') as HTMLElement
117
+ expect(dot.className).toContain('size-1.5')
118
+
119
+ rerender(
120
+ <Badge variant="info" size="default" dot>
121
+ x
122
+ </Badge>,
123
+ )
124
+ dot = container.querySelector('[data-slot="badge-dot"]') as HTMLElement
125
+ expect(dot.className).toContain('size-1.5')
126
+
127
+ rerender(
128
+ <Badge variant="info" size="lg" dot>
129
+ x
130
+ </Badge>,
131
+ )
132
+ dot = container.querySelector('[data-slot="badge-dot"]') as HTMLElement
133
+ expect(dot.className).toContain('size-2')
134
+ })
135
+
136
+ it('renders the remove button when removable=true and fires onRemove on click', () => {
137
+ const onRemove = jest.fn()
138
+ const { container } = render(
139
+ <Badge variant="info" removable onRemove={onRemove}>
140
+ Tag
141
+ </Badge>,
142
+ )
143
+ const remove = container.querySelector('[data-slot="badge-remove"]') as HTMLButtonElement
144
+ expect(remove).not.toBeNull()
145
+ expect(remove.getAttribute('aria-label')).toBe('Remove')
146
+ fireEvent.click(remove)
147
+ expect(onRemove).toHaveBeenCalledTimes(1)
148
+ })
149
+
150
+ it('omits the remove button by default', () => {
151
+ const { container } = render(<Badge>x</Badge>)
152
+ expect(container.querySelector('[data-slot="badge-remove"]')).toBeNull()
153
+ })
154
+
155
+ it('honors removeAriaLabel override', () => {
156
+ const { container } = render(
157
+ <Badge removable onRemove={() => {}} removeAriaLabel="Remove tag">
158
+ x
159
+ </Badge>,
160
+ )
161
+ const remove = container.querySelector('[data-slot="badge-remove"]') as HTMLButtonElement
162
+ expect(remove.getAttribute('aria-label')).toBe('Remove tag')
163
+ })
164
+
165
+ it('combines dot + removable + size=lg without conflict', () => {
166
+ const { container } = render(
167
+ <Badge variant="error" size="lg" dot removable onRemove={() => {}}>
168
+ Blocked
169
+ </Badge>,
170
+ )
171
+ expect(container.querySelector('[data-slot="badge-dot"]')).not.toBeNull()
172
+ expect(container.querySelector('[data-slot="badge-remove"]')).not.toBeNull()
173
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
174
+ expect(root.textContent).toBe('Blocked')
175
+ expect(root.className).toContain('text-sm') // size=lg
176
+ })
177
+
178
+ it('forwards className without dropping variant or size classes', () => {
179
+ const { container } = render(
180
+ <Badge variant="success" size="lg" className="custom-class">
181
+ x
182
+ </Badge>,
183
+ )
184
+ const root = container.querySelector('[data-slot="badge"]') as HTMLElement
185
+ expect(root.className).toContain('custom-class')
186
+ expect(root.className).toContain('bg-status-success-bg')
187
+ expect(root.className).toContain('text-sm')
188
+ })
189
+ })
@@ -0,0 +1,117 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as React from 'react'
4
+ import { render } from '@testing-library/react'
5
+ import { ButtonGroup, buttonGroupVariants } from '../button-group'
6
+
7
+ describe('ButtonGroup', () => {
8
+ it('renders all children inside a role="group" wrapper', () => {
9
+ const { container, getByText } = render(
10
+ <ButtonGroup>
11
+ <button type="button">One</button>
12
+ <button type="button">Two</button>
13
+ <button type="button">Three</button>
14
+ </ButtonGroup>,
15
+ )
16
+ expect(getByText('One')).toBeInTheDocument()
17
+ expect(getByText('Two')).toBeInTheDocument()
18
+ expect(getByText('Three')).toBeInTheDocument()
19
+ const wrapper = container.querySelector('[data-slot="button-group"]')
20
+ expect(wrapper).not.toBeNull()
21
+ expect(wrapper?.getAttribute('role')).toBe('group')
22
+ expect(wrapper?.children.length).toBe(3)
23
+ })
24
+
25
+ it('applies horizontal flex-row layout by default', () => {
26
+ const { container } = render(
27
+ <ButtonGroup>
28
+ <button type="button">A</button>
29
+ <button type="button">B</button>
30
+ </ButtonGroup>,
31
+ )
32
+ const wrapper = container.querySelector('[data-slot="button-group"]') as HTMLElement
33
+ expect(wrapper.className).toContain('flex-row')
34
+ expect(wrapper.className).toContain('rounded-md')
35
+ expect(wrapper.className).not.toContain('flex-col')
36
+ })
37
+
38
+ it('applies vertical flex-col layout when orientation="vertical"', () => {
39
+ const { container } = render(
40
+ <ButtonGroup orientation="vertical">
41
+ <button type="button">Up</button>
42
+ <button type="button">Down</button>
43
+ </ButtonGroup>,
44
+ )
45
+ const wrapper = container.querySelector('[data-slot="button-group"]') as HTMLElement
46
+ expect(wrapper.className).toContain('flex-col')
47
+ expect(wrapper.className).not.toContain('flex-row')
48
+ })
49
+
50
+ it('maps size="2xs" to rounded-sm per Figma 2X-Small (24) cornerRadius 6', () => {
51
+ const { container } = render(
52
+ <ButtonGroup size="2xs">
53
+ <button type="button">A</button>
54
+ </ButtonGroup>,
55
+ )
56
+ const wrapper = container.querySelector('[data-slot="button-group"]') as HTMLElement
57
+ expect(wrapper.className).toContain('rounded-sm')
58
+ expect(wrapper.className).not.toMatch(/\brounded-md\b/)
59
+ })
60
+
61
+ it('maps size="sm" and size="default" to rounded-md', () => {
62
+ const small = render(
63
+ <ButtonGroup size="sm">
64
+ <button type="button">A</button>
65
+ </ButtonGroup>,
66
+ )
67
+ expect(small.container.querySelector('[data-slot="button-group"]')?.className).toContain('rounded-md')
68
+
69
+ const def = render(
70
+ <ButtonGroup size="default">
71
+ <button type="button">A</button>
72
+ </ButtonGroup>,
73
+ )
74
+ expect(def.container.querySelector('[data-slot="button-group"]')?.className).toContain('rounded-md')
75
+ })
76
+
77
+ it('forwards className to the wrapper without dropping variant classes', () => {
78
+ const { container } = render(
79
+ <ButtonGroup className="custom-class">
80
+ <button type="button">A</button>
81
+ </ButtonGroup>,
82
+ )
83
+ const wrapper = container.querySelector('[data-slot="button-group"]') as HTMLElement
84
+ expect(wrapper.className).toContain('custom-class')
85
+ expect(wrapper.className).toContain('rounded-md')
86
+ expect(wrapper.className).toContain('border')
87
+ })
88
+
89
+ it('forwards ref to the wrapper element', () => {
90
+ const ref = React.createRef<HTMLDivElement>()
91
+ render(
92
+ <ButtonGroup ref={ref}>
93
+ <button type="button">A</button>
94
+ </ButtonGroup>,
95
+ )
96
+ expect(ref.current).not.toBeNull()
97
+ expect(ref.current?.getAttribute('data-slot')).toBe('button-group')
98
+ })
99
+
100
+ it('forwards aria-label to the wrapper for screen-reader context', () => {
101
+ const { container } = render(
102
+ <ButtonGroup aria-label="View mode">
103
+ <button type="button">List</button>
104
+ <button type="button">Grid</button>
105
+ </ButtonGroup>,
106
+ )
107
+ const wrapper = container.querySelector('[data-slot="button-group"]') as HTMLElement
108
+ expect(wrapper.getAttribute('aria-label')).toBe('View mode')
109
+ })
110
+
111
+ it('exposes the buttonGroupVariants cva helper for reuse', () => {
112
+ expect(typeof buttonGroupVariants).toBe('function')
113
+ expect(buttonGroupVariants({ orientation: 'horizontal', size: 'default' })).toContain('rounded-md')
114
+ expect(buttonGroupVariants({ orientation: 'vertical', size: '2xs' })).toContain('flex-col')
115
+ expect(buttonGroupVariants({ orientation: 'vertical', size: '2xs' })).toContain('rounded-sm')
116
+ })
117
+ })