@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/backend/DataTable.js +28 -62
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
- package/dist/primitives/activity-feed.js +196 -0
- package/dist/primitives/activity-feed.js.map +7 -0
- package/dist/primitives/avatar.js +140 -14
- package/dist/primitives/avatar.js.map +3 -3
- package/dist/primitives/badge.js +94 -5
- package/dist/primitives/badge.js.map +2 -2
- package/dist/primitives/button-group.js +52 -0
- package/dist/primitives/button-group.js.map +7 -0
- package/dist/primitives/color-picker.js +520 -0
- package/dist/primitives/color-picker.js.map +7 -0
- package/dist/primitives/command-menu.js +355 -0
- package/dist/primitives/command-menu.js.map +7 -0
- package/dist/primitives/dialog.js +210 -35
- package/dist/primitives/dialog.js.map +2 -2
- package/dist/primitives/drawer.js +219 -0
- package/dist/primitives/drawer.js.map +7 -0
- package/dist/primitives/notification-feed.js +213 -0
- package/dist/primitives/notification-feed.js.map +7 -0
- package/dist/primitives/notification.js +23 -2
- package/dist/primitives/notification.js.map +2 -2
- package/dist/primitives/pagination.js +245 -0
- package/dist/primitives/pagination.js.map +7 -0
- package/dist/primitives/progress.js +178 -5
- package/dist/primitives/progress.js.map +2 -2
- package/dist/primitives/rating.js +174 -0
- package/dist/primitives/rating.js.map +7 -0
- package/dist/primitives/scroll-area.js +82 -0
- package/dist/primitives/scroll-area.js.map +7 -0
- package/dist/primitives/segmented-control.js +101 -0
- package/dist/primitives/segmented-control.js.map +7 -0
- package/dist/primitives/separator.js +103 -4
- package/dist/primitives/separator.js.map +2 -2
- package/dist/primitives/slider.js +115 -0
- package/dist/primitives/slider.js.map +7 -0
- package/dist/primitives/step-indicator.js +187 -0
- package/dist/primitives/step-indicator.js.map +7 -0
- package/dist/primitives/table.js +102 -10
- package/dist/primitives/table.js.map +2 -2
- package/dist/primitives/tabs.js +122 -14
- package/dist/primitives/tabs.js.map +2 -2
- package/dist/primitives/time-picker.js +9 -11
- package/dist/primitives/time-picker.js.map +2 -2
- package/package.json +8 -4
- package/src/backend/DataTable.tsx +43 -60
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
- package/src/primitives/__tests__/avatar.test.tsx +131 -0
- package/src/primitives/__tests__/badge.test.tsx +189 -0
- package/src/primitives/__tests__/button-group.test.tsx +117 -0
- package/src/primitives/__tests__/color-picker.test.tsx +589 -0
- package/src/primitives/__tests__/command-menu.test.tsx +231 -0
- package/src/primitives/__tests__/dialog.test.tsx +290 -0
- package/src/primitives/__tests__/drawer.test.tsx +264 -0
- package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
- package/src/primitives/__tests__/notification.test.tsx +102 -2
- package/src/primitives/__tests__/pagination.test.tsx +268 -0
- package/src/primitives/__tests__/progress.test.tsx +222 -0
- package/src/primitives/__tests__/rating.test.tsx +164 -0
- package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
- package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
- package/src/primitives/__tests__/separator.test.tsx +143 -0
- package/src/primitives/__tests__/slider.test.tsx +86 -0
- package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
- package/src/primitives/__tests__/table.test.tsx +199 -0
- package/src/primitives/__tests__/tabs.test.tsx +139 -0
- package/src/primitives/activity-feed.tsx +326 -0
- package/src/primitives/avatar.tsx +225 -22
- package/src/primitives/badge.tsx +141 -6
- package/src/primitives/button-group.tsx +100 -0
- package/src/primitives/color-picker.tsx +693 -0
- package/src/primitives/command-menu.tsx +503 -0
- package/src/primitives/dialog.tsx +315 -51
- package/src/primitives/drawer.tsx +338 -0
- package/src/primitives/notification-feed.tsx +358 -0
- package/src/primitives/notification.tsx +42 -2
- package/src/primitives/pagination.tsx +376 -0
- package/src/primitives/progress.tsx +267 -6
- package/src/primitives/rating.tsx +245 -0
- package/src/primitives/scroll-area.tsx +169 -0
- package/src/primitives/segmented-control.tsx +162 -0
- package/src/primitives/separator.tsx +163 -3
- package/src/primitives/slider.tsx +160 -0
- package/src/primitives/step-indicator.tsx +287 -0
- package/src/primitives/table.tsx +142 -11
- package/src/primitives/tabs.tsx +174 -12
- 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
|
+
})
|