@charcuterie/ui 4.20.1 → 4.21.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/ComparisonChart/ComparisonChart.d.ts +12 -0
- package/dist/ComparisonChart/ComparisonChart.d.ts.map +1 -0
- package/dist/ComparisonChart/ComparisonChart.js +64 -0
- package/dist/ComparisonChart/ComparisonChart.js.map +1 -0
- package/dist/Lightbox/Lightbox.d.ts +10 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +46 -3
- package/dist/Lightbox/Lightbox.js.map +1 -1
- package/dist/TableColumns/TableColumns.d.ts +14 -0
- package/dist/TableColumns/TableColumns.d.ts.map +1 -0
- package/dist/TableColumns/TableColumns.js +19 -0
- package/dist/TableColumns/TableColumns.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/ComparisonChart/ComparisonChart.mdx +14 -0
- package/src/ComparisonChart/ComparisonChart.stories.tsx +85 -0
- package/src/ComparisonChart/ComparisonChart.test.tsx +33 -0
- package/src/ComparisonChart/ComparisonChart.tsx +161 -0
- package/src/Dialog/Dialog.test.tsx +38 -8
- package/src/LaneTimeline/LaneTimelineBar.tsx +3 -3
- package/src/Lightbox/Lightbox.mdx +9 -0
- package/src/Lightbox/Lightbox.stories.tsx +26 -0
- package/src/Lightbox/Lightbox.test.tsx +53 -0
- package/src/Lightbox/Lightbox.tsx +109 -5
- package/src/PortraitTiles/PortraitTiles.test.tsx +17 -11
- package/src/TableColumns/TableColumns.mdx +14 -0
- package/src/TableColumns/TableColumns.stories.tsx +81 -0
- package/src/TableColumns/TableColumns.test.tsx +35 -0
- package/src/TableColumns/TableColumns.tsx +93 -0
- package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +21 -8
- package/src/board.storyHelpers.tsx +7 -1
- package/src/htmlEditor/HtmlEditor.test.tsx +32 -2
- package/src/index.ts +11 -1
- package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +12 -7
- package/src/sourceRules.test.ts +2 -1
- package/src/vitestProvidedContext.d.ts +6 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { Button } from "../Button/Button.tsx"
|
|
3
|
+
import { Checkbox } from "../Checkbox/Checkbox.tsx"
|
|
4
|
+
import { Popover } from "../Popover/Popover.tsx"
|
|
5
|
+
import { ReorderList } from "../ReorderList/ReorderList.tsx"
|
|
6
|
+
|
|
7
|
+
export type TableColumnChoice = {
|
|
8
|
+
key: string
|
|
9
|
+
label: string
|
|
10
|
+
isVisible: boolean
|
|
11
|
+
isRequired?: boolean
|
|
12
|
+
}
|
|
13
|
+
export type TableColumnsProps = {
|
|
14
|
+
columns: readonly TableColumnChoice[]
|
|
15
|
+
onChange: (columns: TableColumnChoice[]) => void
|
|
16
|
+
label?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Column visibility and ordering, with keyboard moves as the primary path. */
|
|
20
|
+
export const TableColumns = ({
|
|
21
|
+
columns,
|
|
22
|
+
onChange,
|
|
23
|
+
label = "Columns",
|
|
24
|
+
}: TableColumnsProps) => {
|
|
25
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
26
|
+
return (
|
|
27
|
+
<Popover
|
|
28
|
+
heading="Table columns"
|
|
29
|
+
isVisible={isVisible}
|
|
30
|
+
onDismiss={() => setIsVisible(false)}
|
|
31
|
+
trigger={
|
|
32
|
+
<Button
|
|
33
|
+
intent="neutral"
|
|
34
|
+
appearance="soft"
|
|
35
|
+
onClick={() => setIsVisible(!isVisible)}
|
|
36
|
+
>
|
|
37
|
+
{label}
|
|
38
|
+
</Button>
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
<ReorderList
|
|
42
|
+
label="Column order"
|
|
43
|
+
items={columns}
|
|
44
|
+
onReorder={(from, to) => {
|
|
45
|
+
const next = [...columns]
|
|
46
|
+
const [column] = next.splice(from, 1)
|
|
47
|
+
next.splice(to, 0, column)
|
|
48
|
+
onChange(next)
|
|
49
|
+
}}
|
|
50
|
+
renderItem={({ item, isFirst, isLast, moveBy }) => (
|
|
51
|
+
<div className="flex flex-wrap items-center justify-between gap-2 py-1">
|
|
52
|
+
<Checkbox
|
|
53
|
+
label={item.label}
|
|
54
|
+
isChecked={item.isVisible}
|
|
55
|
+
isDisabled={item.isRequired}
|
|
56
|
+
onChange={(isChecked) =>
|
|
57
|
+
onChange(
|
|
58
|
+
columns.map((column) =>
|
|
59
|
+
column.key === item.key
|
|
60
|
+
? { ...column, isVisible: isChecked }
|
|
61
|
+
: column,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
/>
|
|
66
|
+
<div className="flex gap-1">
|
|
67
|
+
<Button
|
|
68
|
+
size="sm"
|
|
69
|
+
intent="neutral"
|
|
70
|
+
appearance="ghost"
|
|
71
|
+
aria-label={`Move ${item.label} earlier`}
|
|
72
|
+
isDisabled={isFirst}
|
|
73
|
+
onClick={() => moveBy(-1)}
|
|
74
|
+
>
|
|
75
|
+
Earlier
|
|
76
|
+
</Button>
|
|
77
|
+
<Button
|
|
78
|
+
size="sm"
|
|
79
|
+
intent="neutral"
|
|
80
|
+
appearance="ghost"
|
|
81
|
+
aria-label={`Move ${item.label} later`}
|
|
82
|
+
isDisabled={isLast}
|
|
83
|
+
onClick={() => moveBy(1)}
|
|
84
|
+
>
|
|
85
|
+
Later
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
/>
|
|
91
|
+
</Popover>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -308,15 +308,28 @@ test("a grid inside a hidden panel mounts nothing and reveals without scrolling"
|
|
|
308
308
|
// Wait for the revealed grid to have MEASURED, not merely to
|
|
309
309
|
// have mounted — the adjustment, when it happens, happens on the
|
|
310
310
|
// virtualizer's resize path.
|
|
311
|
+
//
|
|
312
|
+
// Two signals, because one of them depends on the window. The end
|
|
313
|
+
// padding stands in for rows below the window, so it is only ever
|
|
314
|
+
// positive when the list is longer than the window. In a 3440x1440
|
|
315
|
+
// window all 48 cards fit, the padding is rightly 0, and waiting for
|
|
316
|
+
// it timed out on a grid that was working. There the signal is the
|
|
317
|
+
// LAST card drawn with a real height.
|
|
311
318
|
await waitFor(async () => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
319
|
+
const list = getShownList()
|
|
320
|
+
|
|
321
|
+
const isPadded =
|
|
322
|
+
Number(list.style.paddingBlockEnd.replace("px", "")) >
|
|
323
|
+
0
|
|
324
|
+
|
|
325
|
+
const lastCell = list.querySelector<HTMLElement>(
|
|
326
|
+
`li[aria-posinset="${list.querySelector("li")?.getAttribute("aria-setsize")}"]`,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
const isEveryCardDrawn =
|
|
330
|
+
(lastCell?.getBoundingClientRect().height ?? 0) > 0
|
|
331
|
+
|
|
332
|
+
await expect(isPadded || isEveryCardDrawn).toBe(true)
|
|
320
333
|
})
|
|
321
334
|
|
|
322
335
|
// The symptom, kept as a guard rather than as the proof. It is
|
|
@@ -133,7 +133,13 @@ export const ContainerBoard = ({
|
|
|
133
133
|
},
|
|
134
134
|
].map((panel) => (
|
|
135
135
|
<div
|
|
136
|
-
|
|
136
|
+
// `shrink-0`, or the promise above is false: a flex item
|
|
137
|
+
// shrinks to its line, so in a window narrower than 34rem the
|
|
138
|
+
// widest panel quietly became the window's width and every
|
|
139
|
+
// "container, not window" test measured the window after all.
|
|
140
|
+
// A board wider than the window scrolls, which is the honest
|
|
141
|
+
// picture of three fixed containers on a phone.
|
|
142
|
+
className="flex shrink-0 flex-col gap-2"
|
|
137
143
|
key={panel.label}
|
|
138
144
|
style={{ inlineSize: panel.inlineSize }}
|
|
139
145
|
>
|
|
@@ -212,6 +212,35 @@ test("external defaults never overwrite the document; real edits emit HTML and k
|
|
|
212
212
|
).not.toHaveTextContent("data-old-format")
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* A toolbar action wherever the toolbar put it. On the phone the whole
|
|
217
|
+
* row folds into the overflow panel, so a test that looked for "Bold"
|
|
218
|
+
* in the row asserted about the window rather than the editor — and a
|
|
219
|
+
* read-only editor must refuse formatting from the panel too.
|
|
220
|
+
*/
|
|
221
|
+
const findToolbarAction = async (
|
|
222
|
+
toolbar: HTMLElement,
|
|
223
|
+
name: string,
|
|
224
|
+
) => {
|
|
225
|
+
const inRow = within(toolbar).queryByRole("button", {
|
|
226
|
+
name,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if (inRow) {
|
|
230
|
+
return inRow
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await userEvent.click(
|
|
234
|
+
within(toolbar).getByRole("button", {
|
|
235
|
+
name: "More actions",
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return within(
|
|
240
|
+
await within(document.body).findByRole("dialog"),
|
|
241
|
+
).getByRole("button", { name })
|
|
242
|
+
}
|
|
243
|
+
|
|
215
244
|
test("read-only and disabled editors refuse typing and formatting", async () => {
|
|
216
245
|
const { canvas } = await mountStory(AllStates)
|
|
217
246
|
const readOnly = await canvas.findByRole("textbox", {
|
|
@@ -234,11 +263,12 @@ test("read-only and disabled editors refuse typing and formatting", async () =>
|
|
|
234
263
|
)
|
|
235
264
|
await expect(disabled).toHaveAttribute("tabindex", "-1")
|
|
236
265
|
await expect(
|
|
237
|
-
|
|
266
|
+
await findToolbarAction(
|
|
238
267
|
canvas.getByRole("toolbar", {
|
|
239
268
|
name: "Read-only note formatting",
|
|
240
269
|
}),
|
|
241
|
-
|
|
270
|
+
"Bold",
|
|
271
|
+
),
|
|
242
272
|
).toBeDisabled()
|
|
243
273
|
const required = canvas.getByRole("textbox", {
|
|
244
274
|
name: "Required notes",
|
package/src/index.ts
CHANGED
|
@@ -130,6 +130,8 @@ export type {
|
|
|
130
130
|
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx"
|
|
131
131
|
export type { ComboboxProps } from "./Combobox/Combobox.tsx"
|
|
132
132
|
export { Combobox } from "./Combobox/Combobox.tsx"
|
|
133
|
+
export type { ComparisonChartProps } from "./ComparisonChart/ComparisonChart.tsx"
|
|
134
|
+
export { ComparisonChart } from "./ComparisonChart/ComparisonChart.tsx"
|
|
133
135
|
export type {
|
|
134
136
|
CopyButtonProps,
|
|
135
137
|
CopyStatus,
|
|
@@ -260,7 +262,10 @@ export {
|
|
|
260
262
|
toBarPlacement,
|
|
261
263
|
toTimelineAxis,
|
|
262
264
|
} from "./LaneTimeline/laneTimelineGeometry.ts"
|
|
263
|
-
export type {
|
|
265
|
+
export type {
|
|
266
|
+
LightboxImage,
|
|
267
|
+
LightboxProps,
|
|
268
|
+
} from "./Lightbox/Lightbox.tsx"
|
|
264
269
|
export { Lightbox } from "./Lightbox/Lightbox.tsx"
|
|
265
270
|
export type {
|
|
266
271
|
ListboxItem,
|
|
@@ -496,6 +501,11 @@ export {
|
|
|
496
501
|
getConnectionLabel,
|
|
497
502
|
getIsConnectionBusy,
|
|
498
503
|
} from "./statusIntent.ts"
|
|
504
|
+
export type {
|
|
505
|
+
TableColumnChoice,
|
|
506
|
+
TableColumnsProps,
|
|
507
|
+
} from "./TableColumns/TableColumns.tsx"
|
|
508
|
+
export { TableColumns } from "./TableColumns/TableColumns.tsx"
|
|
499
509
|
export type { TableViewportProps } from "./TableViewport/TableViewport.tsx"
|
|
500
510
|
export { TableViewport } from "./TableViewport/TableViewport.tsx"
|
|
501
511
|
export type {
|
|
@@ -59,14 +59,17 @@ test("it is an article whose headings are headings", async () => {
|
|
|
59
59
|
"false",
|
|
60
60
|
)
|
|
61
61
|
|
|
62
|
+
// `findBy`, not `getBy`: CodeMirror draws its decorations after the
|
|
63
|
+
// first paint, and with four browser windows sharing the runner the
|
|
64
|
+
// second heading was not drawn yet when a synchronous query looked.
|
|
62
65
|
await expect(
|
|
63
|
-
canvas.
|
|
66
|
+
await canvas.findByRole("heading", {
|
|
64
67
|
name: "Rack move, phase two",
|
|
65
68
|
}),
|
|
66
69
|
).toHaveAttribute("aria-level", "1")
|
|
67
70
|
|
|
68
71
|
await expect(
|
|
69
|
-
canvas.
|
|
72
|
+
await canvas.findByRole("heading", {
|
|
70
73
|
name: "Before the window",
|
|
71
74
|
}),
|
|
72
75
|
).toHaveAttribute("aria-level", "2")
|
|
@@ -202,7 +205,7 @@ test("a link is a real anchor in the tab order", async () => {
|
|
|
202
205
|
test("a checkbox is inert until a consumer says where the tick goes", async () => {
|
|
203
206
|
const { canvas } = await mountStory(AllStates)
|
|
204
207
|
|
|
205
|
-
const inert = canvas.
|
|
208
|
+
const inert = await canvas.findByRole("checkbox", {
|
|
206
209
|
name: "Photograph the current cabling",
|
|
207
210
|
})
|
|
208
211
|
|
|
@@ -239,9 +242,11 @@ test("hostile markdown renders as characters, not as behaviour", async () => {
|
|
|
239
242
|
const { canvas, canvasElement } =
|
|
240
243
|
await mountStory(HostileMarkdown)
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
// `findAllBy`: the links are CodeMirror decorations, drawn after the
|
|
246
|
+
// first paint — the same race as the headings test above.
|
|
247
|
+
const hrefs = (await canvas.findAllByRole("link")).map(
|
|
248
|
+
(link: HTMLElement) => link.getAttribute("href"),
|
|
249
|
+
)
|
|
245
250
|
|
|
246
251
|
await expect(hrefs).toEqual([
|
|
247
252
|
"https://example.invalid/runbook",
|
|
@@ -336,7 +341,7 @@ test("inline markup in link text does not drop the document to raw source", asyn
|
|
|
336
341
|
// The canary. This heading has nothing to do with links, and it
|
|
337
342
|
// is what stopped rendering when the plugin died.
|
|
338
343
|
await expect(
|
|
339
|
-
canvas.
|
|
344
|
+
await canvas.findByRole("heading", {
|
|
340
345
|
name: "Where the fix landed",
|
|
341
346
|
}),
|
|
342
347
|
).toHaveAttribute("aria-level", "2")
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -622,7 +622,8 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
622
622
|
// Chart shares portable SVG geometry with Preact and image consumers.
|
|
623
623
|
// TableViewport owns readable comparisons and full-window expansion.
|
|
624
624
|
// MetricCard shares prominent values and category-coloured icons through Card.
|
|
625
|
-
|
|
625
|
+
// ComparisonChart and TableColumns share compact comparisons and column controls.
|
|
626
|
+
expect(componentNames.length).toBe(77)
|
|
626
627
|
|
|
627
628
|
for (const name of componentNames) {
|
|
628
629
|
expect(barrel).toContain(`export { ${name} }`)
|