@discourser/design-system 0.19.0 → 0.20.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.
- package/dist/{chunk-PFWU7QSM.cjs → chunk-L5FO6K6L.cjs} +23 -2
- package/dist/chunk-L5FO6K6L.cjs.map +1 -0
- package/dist/{chunk-3M22GRHH.js → chunk-NU6GI57K.js} +65 -4
- package/dist/chunk-NU6GI57K.js.map +1 -0
- package/dist/{chunk-SNUJBT5R.js → chunk-WFKEAD5P.js} +23 -2
- package/dist/chunk-WFKEAD5P.js.map +1 -0
- package/dist/{chunk-DDXDLT4K.cjs → chunk-WSJLKVXZ.cjs} +65 -4
- package/dist/chunk-WSJLKVXZ.cjs.map +1 -0
- package/dist/components/Breadcrumb.d.ts +31 -2
- package/dist/components/Breadcrumb.d.ts.map +1 -1
- package/dist/components/index.cjs +69 -69
- package/dist/components/index.js +1 -1
- package/dist/figma-codex.json +9 -8
- package/dist/index.cjs +73 -73
- package/dist/index.js +2 -2
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.js +1 -1
- package/dist/preset/recipes/breadcrumb.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Breadcrumb.tsx +104 -2
- package/src/components/__tests__/Breadcrumb.test.tsx +180 -47
- package/src/preset/recipes/breadcrumb.ts +22 -0
- package/dist/chunk-3M22GRHH.js.map +0 -1
- package/dist/chunk-DDXDLT4K.cjs.map +0 -1
- package/dist/chunk-PFWU7QSM.cjs.map +0 -1
- package/dist/chunk-SNUJBT5R.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* global describe, it, expect */
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { render, screen } from '@testing-library/react'
|
|
4
|
-
import * as Breadcrumb from '../Breadcrumb'
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import * as Breadcrumb from '../Breadcrumb';
|
|
5
5
|
|
|
6
|
-
type BreadcrumbVariant = 'plain' | 'underline' | 'discourser'
|
|
6
|
+
type BreadcrumbVariant = 'plain' | 'underline' | 'discourser';
|
|
7
7
|
|
|
8
8
|
function renderBreadcrumb(variant?: BreadcrumbVariant) {
|
|
9
9
|
return render(
|
|
@@ -22,73 +22,206 @@ function renderBreadcrumb(variant?: BreadcrumbVariant) {
|
|
|
22
22
|
</Breadcrumb.Item>
|
|
23
23
|
</Breadcrumb.List>
|
|
24
24
|
</Breadcrumb.Root>,
|
|
25
|
-
)
|
|
25
|
+
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
describe('Breadcrumb', () => {
|
|
29
29
|
describe('Rendering', () => {
|
|
30
30
|
it('renders breadcrumb navigation with correct aria-label', () => {
|
|
31
|
-
renderBreadcrumb()
|
|
32
|
-
expect(
|
|
33
|
-
|
|
31
|
+
renderBreadcrumb();
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByRole('navigation', { name: 'breadcrumb' }),
|
|
34
|
+
).toBeDefined();
|
|
35
|
+
});
|
|
34
36
|
|
|
35
37
|
it('renders all breadcrumb items', () => {
|
|
36
|
-
renderBreadcrumb()
|
|
37
|
-
expect(screen.getByText('Home')).toBeDefined()
|
|
38
|
-
expect(screen.getByText('Docs')).toBeDefined()
|
|
39
|
-
expect(screen.getByText('Components')).toBeDefined()
|
|
40
|
-
})
|
|
38
|
+
renderBreadcrumb();
|
|
39
|
+
expect(screen.getByText('Home')).toBeDefined();
|
|
40
|
+
expect(screen.getByText('Docs')).toBeDefined();
|
|
41
|
+
expect(screen.getByText('Components')).toBeDefined();
|
|
42
|
+
});
|
|
41
43
|
|
|
42
44
|
it('renders separator between items', () => {
|
|
43
|
-
const { container } = renderBreadcrumb()
|
|
45
|
+
const { container } = renderBreadcrumb();
|
|
44
46
|
// Separators are aria-hidden li elements with svg children
|
|
45
|
-
const separators = container.querySelectorAll('[aria-hidden="true"]')
|
|
46
|
-
expect(separators.length).toBeGreaterThan(0)
|
|
47
|
-
})
|
|
47
|
+
const separators = container.querySelectorAll('[aria-hidden="true"]');
|
|
48
|
+
expect(separators.length).toBeGreaterThan(0);
|
|
49
|
+
});
|
|
48
50
|
|
|
49
51
|
it('marks the last item as current page with aria-current', () => {
|
|
50
|
-
renderBreadcrumb()
|
|
51
|
-
const current = screen.getByText('Components')
|
|
52
|
-
expect(current.closest('[aria-current="page"]')).toBeDefined()
|
|
53
|
-
})
|
|
54
|
-
})
|
|
52
|
+
renderBreadcrumb();
|
|
53
|
+
const current = screen.getByText('Components');
|
|
54
|
+
expect(current.closest('[aria-current="page"]')).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
55
57
|
|
|
56
58
|
describe('Discourser Variant', () => {
|
|
57
59
|
it('renders with discourser variant class when variant="discourser"', () => {
|
|
58
|
-
const { container } = renderBreadcrumb('discourser')
|
|
60
|
+
const { container } = renderBreadcrumb('discourser');
|
|
59
61
|
// The root nav element should exist with the breadcrumb class
|
|
60
|
-
const nav = container.querySelector('nav')
|
|
61
|
-
expect(nav).toBeDefined()
|
|
62
|
-
})
|
|
62
|
+
const nav = container.querySelector('nav');
|
|
63
|
+
expect(nav).toBeDefined();
|
|
64
|
+
});
|
|
63
65
|
|
|
64
66
|
it('CurrentLink renders as span, not anchor', () => {
|
|
65
|
-
renderBreadcrumb()
|
|
66
|
-
const current = screen.getByText('Components')
|
|
67
|
-
expect(current.tagName).toBe('SPAN')
|
|
68
|
-
})
|
|
67
|
+
renderBreadcrumb();
|
|
68
|
+
const current = screen.getByText('Components');
|
|
69
|
+
expect(current.tagName).toBe('SPAN');
|
|
70
|
+
});
|
|
69
71
|
|
|
70
72
|
it('CurrentLink has aria-current="page" by default', () => {
|
|
71
|
-
renderBreadcrumb()
|
|
72
|
-
const current = screen.getByText('Components')
|
|
73
|
-
expect(current.getAttribute('aria-current')).toBe('page')
|
|
74
|
-
})
|
|
75
|
-
})
|
|
73
|
+
renderBreadcrumb();
|
|
74
|
+
const current = screen.getByText('Components');
|
|
75
|
+
expect(current.getAttribute('aria-current')).toBe('page');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
76
78
|
|
|
77
79
|
describe('Accessibility', () => {
|
|
78
80
|
it('has role navigation on root element', () => {
|
|
79
|
-
renderBreadcrumb()
|
|
80
|
-
expect(screen.getByRole('navigation')).toBeDefined()
|
|
81
|
-
})
|
|
81
|
+
renderBreadcrumb();
|
|
82
|
+
expect(screen.getByRole('navigation')).toBeDefined();
|
|
83
|
+
});
|
|
82
84
|
|
|
83
85
|
it('renders ordered list for semantic structure', () => {
|
|
84
|
-
const { container } = renderBreadcrumb()
|
|
85
|
-
expect(container.querySelector('ol')).toBeDefined()
|
|
86
|
-
})
|
|
86
|
+
const { container } = renderBreadcrumb();
|
|
87
|
+
expect(container.querySelector('ol')).toBeDefined();
|
|
88
|
+
});
|
|
87
89
|
|
|
88
90
|
it('separators are aria-hidden', () => {
|
|
89
|
-
const { container } = renderBreadcrumb()
|
|
90
|
-
const hiddenElements = container.querySelectorAll('[aria-hidden="true"]')
|
|
91
|
-
expect(hiddenElements.length).toBeGreaterThan(0)
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
})
|
|
91
|
+
const { container } = renderBreadcrumb();
|
|
92
|
+
const hiddenElements = container.querySelectorAll('[aria-hidden="true"]');
|
|
93
|
+
expect(hiddenElements.length).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Breadcrumb.Link disabled state', () => {
|
|
99
|
+
it('renders as span when disabled', () => {
|
|
100
|
+
const { container } = render(
|
|
101
|
+
<Breadcrumb.Root variant="discourser">
|
|
102
|
+
<Breadcrumb.List>
|
|
103
|
+
<Breadcrumb.Item>
|
|
104
|
+
<Breadcrumb.Link disabled>Lobby</Breadcrumb.Link>
|
|
105
|
+
</Breadcrumb.Item>
|
|
106
|
+
</Breadcrumb.List>
|
|
107
|
+
</Breadcrumb.Root>,
|
|
108
|
+
);
|
|
109
|
+
const span = container.querySelector('span[data-disabled]');
|
|
110
|
+
expect(span).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does not render an anchor when disabled', () => {
|
|
114
|
+
const { container } = render(
|
|
115
|
+
<Breadcrumb.Root variant="discourser">
|
|
116
|
+
<Breadcrumb.List>
|
|
117
|
+
<Breadcrumb.Item>
|
|
118
|
+
<Breadcrumb.Link disabled>Lobby</Breadcrumb.Link>
|
|
119
|
+
</Breadcrumb.Item>
|
|
120
|
+
</Breadcrumb.List>
|
|
121
|
+
</Breadcrumb.Root>,
|
|
122
|
+
);
|
|
123
|
+
expect(container.querySelector('a')).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('renders as anchor when not disabled', () => {
|
|
127
|
+
const { container } = render(
|
|
128
|
+
<Breadcrumb.Root variant="discourser">
|
|
129
|
+
<Breadcrumb.List>
|
|
130
|
+
<Breadcrumb.Item>
|
|
131
|
+
<Breadcrumb.Link href="/scenarios">Scenarios</Breadcrumb.Link>
|
|
132
|
+
</Breadcrumb.Item>
|
|
133
|
+
</Breadcrumb.List>
|
|
134
|
+
</Breadcrumb.Root>,
|
|
135
|
+
);
|
|
136
|
+
expect(container.querySelector('a')).toBeDefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('disabled link has data-disabled attribute', () => {
|
|
140
|
+
const { container } = render(
|
|
141
|
+
<Breadcrumb.Root variant="discourser">
|
|
142
|
+
<Breadcrumb.List>
|
|
143
|
+
<Breadcrumb.Item>
|
|
144
|
+
<Breadcrumb.Link disabled>Lobby</Breadcrumb.Link>
|
|
145
|
+
</Breadcrumb.Item>
|
|
146
|
+
</Breadcrumb.List>
|
|
147
|
+
</Breadcrumb.Root>,
|
|
148
|
+
);
|
|
149
|
+
expect(container.querySelector('[data-disabled]')).toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('ParentItem flexibility', () => {
|
|
154
|
+
it('renders as span by default (no href)', () => {
|
|
155
|
+
const { container } = render(
|
|
156
|
+
<Breadcrumb.TwoRowRoot>
|
|
157
|
+
<Breadcrumb.ParentRow show={true}>
|
|
158
|
+
<Breadcrumb.ParentItem>Scenarios</Breadcrumb.ParentItem>
|
|
159
|
+
</Breadcrumb.ParentRow>
|
|
160
|
+
</Breadcrumb.TwoRowRoot>,
|
|
161
|
+
);
|
|
162
|
+
expect(container.querySelector('span')).toBeDefined();
|
|
163
|
+
expect(container.querySelector('a')).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('renders as anchor when href is provided', () => {
|
|
167
|
+
const { container } = render(
|
|
168
|
+
<Breadcrumb.TwoRowRoot>
|
|
169
|
+
<Breadcrumb.ParentRow show={true}>
|
|
170
|
+
<Breadcrumb.ParentItem href="/scenarios">
|
|
171
|
+
Scenarios
|
|
172
|
+
</Breadcrumb.ParentItem>
|
|
173
|
+
</Breadcrumb.ParentRow>
|
|
174
|
+
</Breadcrumb.TwoRowRoot>,
|
|
175
|
+
);
|
|
176
|
+
expect(container.querySelector('a[href="/scenarios"]')).toBeDefined();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('TwoRowRoot + ParentRow', () => {
|
|
181
|
+
it('renders ParentRow children when show=true', () => {
|
|
182
|
+
render(
|
|
183
|
+
<Breadcrumb.TwoRowRoot>
|
|
184
|
+
<Breadcrumb.ParentRow show={true}>
|
|
185
|
+
<Breadcrumb.ParentItem>Scenarios</Breadcrumb.ParentItem>
|
|
186
|
+
<Breadcrumb.ParentSeparator />
|
|
187
|
+
</Breadcrumb.ParentRow>
|
|
188
|
+
</Breadcrumb.TwoRowRoot>,
|
|
189
|
+
);
|
|
190
|
+
expect(screen.getByText('Scenarios')).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('renders nothing for ParentRow when show=false', () => {
|
|
194
|
+
render(
|
|
195
|
+
<Breadcrumb.TwoRowRoot>
|
|
196
|
+
<Breadcrumb.ParentRow show={false}>
|
|
197
|
+
<Breadcrumb.ParentItem>Scenarios</Breadcrumb.ParentItem>
|
|
198
|
+
</Breadcrumb.ParentRow>
|
|
199
|
+
</Breadcrumb.TwoRowRoot>,
|
|
200
|
+
);
|
|
201
|
+
expect(screen.queryByText('Scenarios')).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('ParentRow is aria-hidden when visible', () => {
|
|
205
|
+
const { container } = render(
|
|
206
|
+
<Breadcrumb.TwoRowRoot>
|
|
207
|
+
<Breadcrumb.ParentRow show={true}>
|
|
208
|
+
<Breadcrumb.ParentItem>Scenarios</Breadcrumb.ParentItem>
|
|
209
|
+
</Breadcrumb.ParentRow>
|
|
210
|
+
</Breadcrumb.TwoRowRoot>,
|
|
211
|
+
);
|
|
212
|
+
const parentDiv = container.querySelector('[aria-hidden="true"]');
|
|
213
|
+
expect(parentDiv).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('TwoRowRoot renders a nav element with correct aria-label', () => {
|
|
217
|
+
const { container } = render(
|
|
218
|
+
<Breadcrumb.TwoRowRoot aria-label="page navigation">
|
|
219
|
+
<Breadcrumb.ParentRow show={false}>
|
|
220
|
+
<Breadcrumb.ParentItem>Home</Breadcrumb.ParentItem>
|
|
221
|
+
</Breadcrumb.ParentRow>
|
|
222
|
+
</Breadcrumb.TwoRowRoot>,
|
|
223
|
+
);
|
|
224
|
+
const nav = container.querySelector('nav[aria-label="page navigation"]');
|
|
225
|
+
expect(nav).toBeDefined();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -102,6 +102,28 @@ export const breadcrumb = defineSlotRecipe({
|
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
|
|
105
|
+
compoundVariants: [
|
|
106
|
+
{
|
|
107
|
+
variant: 'discourser',
|
|
108
|
+
css: {
|
|
109
|
+
link: {
|
|
110
|
+
'&[data-disabled]': {
|
|
111
|
+
color: 'fg.subtle',
|
|
112
|
+
opacity: 0.45,
|
|
113
|
+
pointerEvents: 'none',
|
|
114
|
+
cursor: 'default',
|
|
115
|
+
_hover: { color: 'fg.subtle' },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
item: {
|
|
119
|
+
'&[data-disabled]': {
|
|
120
|
+
opacity: 0.45,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
|
|
105
127
|
defaultVariants: {
|
|
106
128
|
variant: 'plain',
|
|
107
129
|
size: 'md',
|