@charcuterie/ui 1.0.1 → 2.0.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.
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts +35 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.d.ts.map +1 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js +33 -0
- package/dist/ColorSchemeSwitcher/ColorSchemeSwitcher.js.map +1 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts +54 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.d.ts.map +1 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.js +30 -0
- package/dist/ColorSchemeToggle/ColorSchemeToggle.js.map +1 -0
- package/dist/Combobox/Combobox.d.ts +54 -0
- package/dist/Combobox/Combobox.d.ts.map +1 -0
- package/dist/Combobox/Combobox.js +216 -0
- package/dist/Combobox/Combobox.js.map +1 -0
- package/dist/Combobox/ComboboxOption.d.ts +36 -0
- package/dist/Combobox/ComboboxOption.d.ts.map +1 -0
- package/dist/Combobox/ComboboxOption.js +32 -0
- package/dist/Combobox/ComboboxOption.js.map +1 -0
- package/dist/Dialog/Dialog.d.ts +47 -0
- package/dist/Dialog/Dialog.d.ts.map +1 -0
- package/dist/Dialog/Dialog.js +52 -0
- package/dist/Dialog/Dialog.js.map +1 -0
- package/dist/Lightbox/Lightbox.d.ts +57 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -0
- package/dist/Lightbox/Lightbox.js +50 -0
- package/dist/Lightbox/Lightbox.js.map +1 -0
- package/dist/Listbox/Listbox.d.ts +56 -0
- package/dist/Listbox/Listbox.d.ts.map +1 -0
- package/dist/Listbox/Listbox.js +151 -0
- package/dist/Listbox/Listbox.js.map +1 -0
- package/dist/Listbox/ListboxOption.d.ts +36 -0
- package/dist/Listbox/ListboxOption.d.ts.map +1 -0
- package/dist/Listbox/ListboxOption.js +42 -0
- package/dist/Listbox/ListboxOption.js.map +1 -0
- package/dist/Menu/Menu.d.ts.map +1 -1
- package/dist/Menu/Menu.js +47 -97
- package/dist/Menu/Menu.js.map +1 -1
- package/dist/Modal/Modal.d.ts +11 -59
- package/dist/Modal/Modal.d.ts.map +1 -1
- package/dist/Modal/Modal.js +28 -101
- package/dist/Modal/Modal.js.map +1 -1
- package/dist/Overlay/OverlayPanel.d.ts +50 -0
- package/dist/Overlay/OverlayPanel.d.ts.map +1 -0
- package/dist/Overlay/OverlayPanel.js +72 -0
- package/dist/Overlay/OverlayPanel.js.map +1 -0
- package/dist/Overlay/OverlayStack.d.ts +47 -0
- package/dist/Overlay/OverlayStack.d.ts.map +1 -0
- package/dist/Overlay/OverlayStack.js +46 -0
- package/dist/Overlay/OverlayStack.js.map +1 -0
- package/dist/Overlay/SharedBackdrop.d.ts +24 -0
- package/dist/Overlay/SharedBackdrop.d.ts.map +1 -0
- package/dist/Overlay/SharedBackdrop.js +28 -0
- package/dist/Overlay/SharedBackdrop.js.map +1 -0
- package/dist/Overlay/lockScrollBehind.d.ts.map +1 -0
- package/dist/Overlay/lockScrollBehind.js.map +1 -0
- package/dist/Overlay/overlayPanelClass.d.ts +17 -0
- package/dist/Overlay/overlayPanelClass.d.ts.map +1 -0
- package/dist/Overlay/overlayPanelClass.js +17 -0
- package/dist/Overlay/overlayPanelClass.js.map +1 -0
- package/dist/Overlay/useAnchoredOverlay.d.ts +97 -0
- package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -0
- package/dist/Overlay/useAnchoredOverlay.js +81 -0
- package/dist/Overlay/useAnchoredOverlay.js.map +1 -0
- package/dist/Popover/Popover.d.ts +16 -24
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +32 -68
- package/dist/Popover/Popover.js.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +25 -25
- package/dist/Tooltip/Tooltip.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mountStory.testHelpers.d.ts +1 -0
- package/dist/mountStory.testHelpers.d.ts.map +1 -1
- package/dist/mountStory.testHelpers.js +21 -1
- package/dist/mountStory.testHelpers.js.map +1 -1
- package/package.json +5 -4
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.mdx +70 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.stories.tsx +118 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.test.tsx +53 -0
- package/src/ColorSchemeSwitcher/ColorSchemeSwitcher.tsx +90 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.mdx +48 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.stories.tsx +161 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.test.tsx +80 -0
- package/src/ColorSchemeToggle/ColorSchemeToggle.tsx +107 -0
- package/src/Combobox/Combobox.mdx +71 -0
- package/src/Combobox/Combobox.stories.tsx +245 -0
- package/src/Combobox/Combobox.test.tsx +289 -0
- package/src/Combobox/Combobox.tsx +513 -0
- package/src/Combobox/ComboboxOption.tsx +88 -0
- package/src/Dialog/Dialog.mdx +87 -0
- package/src/Dialog/Dialog.stories.tsx +347 -0
- package/src/Dialog/Dialog.test.tsx +312 -0
- package/src/Dialog/Dialog.tsx +127 -0
- package/src/Field/Field.test.tsx +6 -4
- package/src/Lightbox/Lightbox.mdx +63 -0
- package/src/Lightbox/Lightbox.stories.tsx +133 -0
- package/src/Lightbox/Lightbox.test.tsx +124 -0
- package/src/Lightbox/Lightbox.tsx +143 -0
- package/src/Listbox/Listbox.mdx +68 -0
- package/src/Listbox/Listbox.stories.tsx +205 -0
- package/src/Listbox/Listbox.test.tsx +198 -0
- package/src/Listbox/Listbox.tsx +295 -0
- package/src/Listbox/ListboxOption.tsx +104 -0
- package/src/Menu/Menu.mdx +11 -13
- package/src/Menu/Menu.test.tsx +47 -38
- package/src/Menu/Menu.tsx +81 -142
- package/src/Modal/Modal.mdx +29 -82
- package/src/Modal/Modal.stories.tsx +55 -188
- package/src/Modal/Modal.test.tsx +44 -177
- package/src/Modal/Modal.tsx +54 -204
- package/src/Overlay/OverlayPanel.tsx +195 -0
- package/src/Overlay/OverlayStack.test.tsx +235 -0
- package/src/Overlay/OverlayStack.tsx +132 -0
- package/src/Overlay/SharedBackdrop.tsx +40 -0
- package/src/Overlay/overlayPanelClass.ts +17 -0
- package/src/Overlay/useAnchoredOverlay.ts +197 -0
- package/src/Popover/Popover.mdx +17 -20
- package/src/Popover/Popover.stories.tsx +33 -0
- package/src/Popover/Popover.test.tsx +68 -32
- package/src/Popover/Popover.tsx +56 -95
- package/src/Tooltip/Tooltip.mdx +12 -9
- package/src/Tooltip/Tooltip.test.tsx +17 -19
- package/src/Tooltip/Tooltip.tsx +35 -32
- package/src/icons.storyHelpers.tsx +22 -0
- package/src/index.ts +22 -4
- package/src/mountStory.testHelpers.ts +25 -1
- package/src/sourceRules.test.ts +26 -1
- package/src/storyControls.test.ts +25 -12
- package/dist/Modal/lockScrollBehind.d.ts.map +0 -1
- package/dist/Modal/lockScrollBehind.js.map +0 -1
- /package/dist/{Modal → Overlay}/lockScrollBehind.d.ts +0 -0
- /package/dist/{Modal → Overlay}/lockScrollBehind.js +0 -0
- /package/src/{Modal → Overlay}/lockScrollBehind.ts +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
6
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
7
|
+
import { expectAgentDrivable } from "../testing/index.ts"
|
|
8
|
+
import * as stories from "./Combobox.stories.tsx"
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
AllVariants,
|
|
12
|
+
Default,
|
|
13
|
+
DisabledFirstOption,
|
|
14
|
+
Interactive,
|
|
15
|
+
Virtualized,
|
|
16
|
+
} = composeStories(stories)
|
|
17
|
+
|
|
18
|
+
const openMulti = async () => {
|
|
19
|
+
const mounted = await mountStory(AllVariants)
|
|
20
|
+
|
|
21
|
+
await userEvent.click(
|
|
22
|
+
expectAgentDrivable(mounted.canvas, {
|
|
23
|
+
name: "Pick languages",
|
|
24
|
+
role: "button",
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const input = expectAgentDrivable(mounted.body, {
|
|
29
|
+
role: "combobox",
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(input).toHaveFocus()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return { ...mounted, input }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const openDefault = async () => {
|
|
40
|
+
const mounted = await mountStory(Default)
|
|
41
|
+
|
|
42
|
+
await userEvent.click(
|
|
43
|
+
expectAgentDrivable(mounted.canvas, {
|
|
44
|
+
name: "Search languages",
|
|
45
|
+
role: "button",
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const input = expectAgentDrivable(mounted.body, {
|
|
50
|
+
role: "combobox",
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await waitFor(() => {
|
|
54
|
+
expect(input).toHaveFocus()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return { ...mounted, input }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test("a button opens a combobox whose popup is a listbox", async () => {
|
|
61
|
+
const { body, input } = await openDefault()
|
|
62
|
+
|
|
63
|
+
await expect(input).toHaveAttribute(
|
|
64
|
+
"aria-autocomplete",
|
|
65
|
+
"list",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const listbox = expectAgentDrivable(body, {
|
|
69
|
+
role: "listbox",
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
await expect(input).toHaveAttribute(
|
|
73
|
+
"aria-controls",
|
|
74
|
+
listbox.id,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
await expectNoAxeViolations(listbox)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("typing filters the options, and focus stays in the input", async () => {
|
|
81
|
+
const { body, input } = await openDefault()
|
|
82
|
+
|
|
83
|
+
await expect(body.getAllByRole("option")).toHaveLength(7)
|
|
84
|
+
|
|
85
|
+
await userEvent.keyboard("fr")
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
expect(body.getAllByRole("option")).toHaveLength(1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
expect(body.getByRole("option")).toHaveTextContent(
|
|
92
|
+
"French",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// The caret never left the input.
|
|
96
|
+
await expect(input).toHaveFocus()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("no match shows the empty label and keeps the popup open", async () => {
|
|
100
|
+
const { body } = await openDefault()
|
|
101
|
+
|
|
102
|
+
await userEvent.keyboard("zzz")
|
|
103
|
+
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(body.queryAllByRole("option")).toHaveLength(0)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Still open, still typeable.
|
|
109
|
+
await expect(
|
|
110
|
+
body.getByText("No matches"),
|
|
111
|
+
).toBeInTheDocument()
|
|
112
|
+
|
|
113
|
+
await expect(
|
|
114
|
+
body.getByRole("combobox"),
|
|
115
|
+
).toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test("the arrow keys move aria-activedescendant, not focus", async () => {
|
|
119
|
+
const { body, input } = await openDefault()
|
|
120
|
+
|
|
121
|
+
const options = body.getAllByRole("option")
|
|
122
|
+
|
|
123
|
+
// Opens on the first option.
|
|
124
|
+
await expect(input).toHaveAttribute(
|
|
125
|
+
"aria-activedescendant",
|
|
126
|
+
options[0]?.id,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
await userEvent.keyboard("{ArrowDown}")
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(input).toHaveAttribute(
|
|
133
|
+
"aria-activedescendant",
|
|
134
|
+
options[1]?.id,
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Focus never moved to the option.
|
|
139
|
+
await expect(input).toHaveFocus()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("Enter selects the active option and closes", async () => {
|
|
143
|
+
const { body, input } = await openDefault()
|
|
144
|
+
|
|
145
|
+
await userEvent.keyboard("{ArrowDown}{Enter}")
|
|
146
|
+
|
|
147
|
+
await waitFor(() => {
|
|
148
|
+
expect(body.queryByRole("combobox")).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Single-select flows back through `onSelect`.
|
|
152
|
+
await expect(input).not.toBeInTheDocument()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("Escape clears the query first, then closes", async () => {
|
|
156
|
+
const { body, canvas } = await mountStory(Interactive)
|
|
157
|
+
|
|
158
|
+
await userEvent.click(
|
|
159
|
+
expectAgentDrivable(canvas, {
|
|
160
|
+
name: "Search languages",
|
|
161
|
+
role: "button",
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const input = body.getByRole("combobox")
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(input).toHaveFocus()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
await userEvent.keyboard("fr")
|
|
172
|
+
|
|
173
|
+
await expect(input).toHaveValue("fr")
|
|
174
|
+
|
|
175
|
+
await userEvent.keyboard("{Escape}")
|
|
176
|
+
|
|
177
|
+
// First Escape clears the query but leaves the popup open.
|
|
178
|
+
await expect(input).toHaveValue("")
|
|
179
|
+
|
|
180
|
+
await expect(
|
|
181
|
+
body.getByRole("combobox"),
|
|
182
|
+
).toBeInTheDocument()
|
|
183
|
+
|
|
184
|
+
await userEvent.keyboard("{Escape}")
|
|
185
|
+
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(body.queryByRole("combobox")).toBeNull()
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test("multi-select Tab escapes the field instead of committing", async () => {
|
|
192
|
+
const { body, canvas, input } = await openMulti()
|
|
193
|
+
|
|
194
|
+
// Nothing chosen yet.
|
|
195
|
+
await expect(
|
|
196
|
+
canvas.getByText("Chosen: —"),
|
|
197
|
+
).toBeInTheDocument()
|
|
198
|
+
|
|
199
|
+
await userEvent.tab()
|
|
200
|
+
|
|
201
|
+
// Tab did not commit the active option — no chip, still nothing chosen…
|
|
202
|
+
await expect(
|
|
203
|
+
canvas.getByText("Chosen: —"),
|
|
204
|
+
).toBeInTheDocument()
|
|
205
|
+
|
|
206
|
+
await expect(
|
|
207
|
+
body.queryByRole("button", { name: /^Remove / }),
|
|
208
|
+
).toBeNull()
|
|
209
|
+
|
|
210
|
+
// …and focus left the input rather than being trapped back in it.
|
|
211
|
+
await expect(input).not.toHaveFocus()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("removing a chip reports the removal through onSelect", async () => {
|
|
215
|
+
const { body, canvas } = await openMulti()
|
|
216
|
+
|
|
217
|
+
// Enter commits the active option in multi-select and keeps the popup
|
|
218
|
+
// open, so a chip appears and the parent is told.
|
|
219
|
+
await userEvent.keyboard("{Enter}")
|
|
220
|
+
|
|
221
|
+
await waitFor(() => {
|
|
222
|
+
expect(
|
|
223
|
+
canvas.getByText("Chosen: eng"),
|
|
224
|
+
).toBeInTheDocument()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await userEvent.click(
|
|
228
|
+
body.getByRole("button", { name: "Remove eng" }),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// The removal flowed back through `onSelect`, so the parent cleared it
|
|
232
|
+
// — a chip removal and a list toggle-off now agree.
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
expect(
|
|
235
|
+
canvas.getByText("Chosen: —"),
|
|
236
|
+
).toBeInTheDocument()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("a disabled first option is skipped, never active or committed", async () => {
|
|
241
|
+
const { body, canvas } = await mountStory(
|
|
242
|
+
DisabledFirstOption,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
const input = body.getByRole("combobox")
|
|
246
|
+
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(input).toHaveFocus()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const options = body.getAllByRole("option")
|
|
252
|
+
|
|
253
|
+
// The seed lands on index 0 (disabled English); the resolved active
|
|
254
|
+
// descendant skips to the first enabled option, Spanish.
|
|
255
|
+
await expect(input).toHaveAttribute(
|
|
256
|
+
"aria-activedescendant",
|
|
257
|
+
options[1]?.id,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
await userEvent.keyboard("{Enter}")
|
|
261
|
+
|
|
262
|
+
// Enter committed Spanish, not the disabled English, and closed.
|
|
263
|
+
await waitFor(() => {
|
|
264
|
+
expect(body.queryByRole("combobox")).toBeNull()
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await expect(
|
|
268
|
+
canvas.getByText("Chosen: spa"),
|
|
269
|
+
).toBeInTheDocument()
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("a windowed list carries aria-setsize and aria-posinset", async () => {
|
|
273
|
+
const { body } = await mountStory(Virtualized)
|
|
274
|
+
|
|
275
|
+
const options = body.getAllByRole("option")
|
|
276
|
+
|
|
277
|
+
// Only a window of the 500 is in the DOM.
|
|
278
|
+
await expect(options.length).toBeLessThan(500)
|
|
279
|
+
|
|
280
|
+
await expect(options.length).toBeGreaterThan(0)
|
|
281
|
+
|
|
282
|
+
// Each announces its place in the whole set, not the window.
|
|
283
|
+
await expect(options[0]).toHaveAttribute(
|
|
284
|
+
"aria-setsize",
|
|
285
|
+
"500",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
await expect(options[0]).toHaveAttribute("aria-posinset")
|
|
289
|
+
})
|