@charcuterie/ui 3.28.1 → 3.29.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/SearchInput/SearchInput.d.ts +22 -0
- package/dist/SearchInput/SearchInput.d.ts.map +1 -0
- package/dist/SearchInput/SearchInput.js +36 -0
- package/dist/SearchInput/SearchInput.js.map +1 -0
- package/dist/VirtualizedGrid/ElementGrid.d.ts +7 -0
- package/dist/VirtualizedGrid/ElementGrid.d.ts.map +1 -0
- package/dist/VirtualizedGrid/ElementGrid.js +19 -0
- package/dist/VirtualizedGrid/ElementGrid.js.map +1 -0
- package/dist/VirtualizedGrid/GridRows.d.ts +4 -0
- package/dist/VirtualizedGrid/GridRows.d.ts.map +1 -0
- package/dist/VirtualizedGrid/GridRows.js +40 -0
- package/dist/VirtualizedGrid/GridRows.js.map +1 -0
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +6 -8
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js +30 -87
- package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
- package/dist/VirtualizedGrid/WindowGrid.d.ts +3 -0
- package/dist/VirtualizedGrid/WindowGrid.d.ts.map +1 -0
- package/dist/VirtualizedGrid/WindowGrid.js +20 -0
- package/dist/VirtualizedGrid/WindowGrid.js.map +1 -0
- package/dist/VirtualizedGrid/virtualizedGridScroll.d.ts +25 -0
- package/dist/VirtualizedGrid/virtualizedGridScroll.d.ts.map +1 -0
- package/dist/VirtualizedGrid/virtualizedGridScroll.js +50 -0
- package/dist/VirtualizedGrid/virtualizedGridScroll.js.map +1 -0
- package/dist/VirtualizedGrid/virtualizedGridTypes.d.ts +27 -0
- package/dist/VirtualizedGrid/virtualizedGridTypes.d.ts.map +1 -0
- package/dist/VirtualizedGrid/virtualizedGridTypes.js +2 -0
- package/dist/VirtualizedGrid/virtualizedGridTypes.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/SearchInput/SearchInput.mdx +15 -0
- package/src/SearchInput/SearchInput.stories.tsx +136 -0
- package/src/SearchInput/SearchInput.test.tsx +28 -0
- package/src/SearchInput/SearchInput.tsx +105 -0
- package/src/VirtualizedGrid/ElementGrid.tsx +45 -0
- package/src/VirtualizedGrid/GridRows.tsx +90 -0
- package/src/VirtualizedGrid/VirtualizedGrid.mdx +9 -8
- package/src/VirtualizedGrid/VirtualizedGrid.stories.tsx +26 -0
- package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +60 -1
- package/src/VirtualizedGrid/VirtualizedGrid.tsx +42 -142
- package/src/VirtualizedGrid/WindowGrid.tsx +39 -0
- package/src/VirtualizedGrid/virtualizedGridScroll.ts +69 -0
- package/src/VirtualizedGrid/virtualizedGridTypes.ts +32 -0
- package/src/index.ts +2 -0
- package/src/sourceRules.test.ts +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,iCAAiC,CAAA;AAKxC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAA;AAK7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAKzC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAGzC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAK3D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AASzC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,cAAc,GACf,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAMxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAGtC,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA2C,CAAA;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAO3B,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAMlE,OAAO,EACL,2BAA2B,EAC3B,cAAc,GACf,MAAM,gCAAgC,CAAA;AAMvC,OAAO,EACL,OAAO,EACP,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,YAAY,EACZ,SAAS,GACV,MAAM,2BAA2B,CAAA;AAKlC,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,2CAA2C,CAAA;AAKlD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAwB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAE9D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAExD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAyB,CAAA;AAKlD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,8CAA+C,CAAA;AAKnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAKtC,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAqC,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AAOtE,OAAO,EACL,WAAW,EACX,eAAe,GAChB,MAAM,mCAAmC,CAAA;AAE1C,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAK9D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA2B,CAAA;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAK7D,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAmB,CAAA;AAKzC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAe,CAAA;AAKnC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAO1B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAKpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAK5C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAqB,CAAA;AAE5C,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAuB,CAAA;AAK/C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAE3D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gCAAgC,CAAA;AAKvC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAiC,CAAA;AAK9D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AAMxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAiB,CAAA;AAMtC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA+B,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA6B,CAAA;AACxD,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,oCAAqC,CAAA;AAK5C,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Canvas, Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
import * as Stories from "./SearchInput.stories";
|
|
3
|
+
|
|
4
|
+
<Meta of={Stories} />
|
|
5
|
+
|
|
6
|
+
# SearchInput
|
|
7
|
+
|
|
8
|
+
The search field with a labelled icon button for clearing its value. The app supplies the
|
|
9
|
+
glyph and owns the query. The component owns the field skin, the full control-sized target,
|
|
10
|
+
the native cancel-widget suppression, and focus return after a clear.
|
|
11
|
+
|
|
12
|
+
Use `Field` for a visible label, or pass `aria-label` when the surrounding layout already
|
|
13
|
+
names the search.
|
|
14
|
+
|
|
15
|
+
<Canvas of={Stories.Default} />
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { useState } from "react"
|
|
3
|
+
|
|
4
|
+
import { controlSizeArgType } from "../argTypes.storyHelpers.ts"
|
|
5
|
+
import {
|
|
6
|
+
ContainerBoard,
|
|
7
|
+
StoryCell,
|
|
8
|
+
StoryGrid,
|
|
9
|
+
StorySection,
|
|
10
|
+
} from "../board.storyHelpers.tsx"
|
|
11
|
+
import { Field } from "../Field/Field.tsx"
|
|
12
|
+
import { SearchInput } from "./SearchInput.tsx"
|
|
13
|
+
|
|
14
|
+
const ClearIcon = () => (
|
|
15
|
+
<svg aria-hidden="true" fill="none" viewBox="0 0 24 24">
|
|
16
|
+
<path
|
|
17
|
+
d="m6 6 12 12M18 6 6 18"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeWidth="2"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const ControlledSearch = ({
|
|
26
|
+
size = "md",
|
|
27
|
+
}: {
|
|
28
|
+
size?: "sm" | "md" | "lg"
|
|
29
|
+
}) => {
|
|
30
|
+
const [value, setValue] = useState("filter paper")
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Field label="Search inventory">
|
|
34
|
+
<SearchInput
|
|
35
|
+
clearIcon={<ClearIcon />}
|
|
36
|
+
onChange={(event) => setValue(event.target.value)}
|
|
37
|
+
onClear={() => setValue("")}
|
|
38
|
+
placeholder="Name or location…"
|
|
39
|
+
size={size}
|
|
40
|
+
value={value}
|
|
41
|
+
/>
|
|
42
|
+
</Field>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const meta = {
|
|
47
|
+
title: "Components/Controls/SearchInput",
|
|
48
|
+
component: SearchInput,
|
|
49
|
+
parameters: { layout: "padded" },
|
|
50
|
+
argTypes: { size: controlSizeArgType },
|
|
51
|
+
args: {
|
|
52
|
+
clearIcon: <ClearIcon />,
|
|
53
|
+
onClear: () => undefined,
|
|
54
|
+
placeholder: "Name or location…",
|
|
55
|
+
readOnly: true,
|
|
56
|
+
size: "md",
|
|
57
|
+
value: "filter paper",
|
|
58
|
+
},
|
|
59
|
+
} satisfies Meta<typeof SearchInput>
|
|
60
|
+
|
|
61
|
+
export default meta
|
|
62
|
+
type Story = StoryObj<typeof meta>
|
|
63
|
+
|
|
64
|
+
export const Default: Story = {
|
|
65
|
+
render: () => <ControlledSearch />,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const AllVariants: Story = {
|
|
69
|
+
render: () => (
|
|
70
|
+
<StorySection title="The field and its clear button share one density-aware control size.">
|
|
71
|
+
<StoryGrid columns={3}>
|
|
72
|
+
{(["sm", "md", "lg"] as const).map((size) => (
|
|
73
|
+
<StoryCell key={size} label={size}>
|
|
74
|
+
<ControlledSearch size={size} />
|
|
75
|
+
</StoryCell>
|
|
76
|
+
))}
|
|
77
|
+
</StoryGrid>
|
|
78
|
+
</StorySection>
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const AllStates: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<StoryGrid columns={2}>
|
|
85
|
+
<StoryCell label="empty">
|
|
86
|
+
<SearchInput
|
|
87
|
+
aria-label="Empty search"
|
|
88
|
+
clearIcon={<ClearIcon />}
|
|
89
|
+
onClear={() => undefined}
|
|
90
|
+
value=""
|
|
91
|
+
/>
|
|
92
|
+
</StoryCell>
|
|
93
|
+
<StoryCell label="filled">
|
|
94
|
+
<SearchInput
|
|
95
|
+
aria-label="Filled search"
|
|
96
|
+
clearIcon={<ClearIcon />}
|
|
97
|
+
onClear={() => undefined}
|
|
98
|
+
readOnly
|
|
99
|
+
value="filter paper"
|
|
100
|
+
/>
|
|
101
|
+
</StoryCell>
|
|
102
|
+
<StoryCell label="disabled">
|
|
103
|
+
<SearchInput
|
|
104
|
+
aria-label="Disabled search"
|
|
105
|
+
clearIcon={<ClearIcon />}
|
|
106
|
+
disabled
|
|
107
|
+
onClear={() => undefined}
|
|
108
|
+
readOnly
|
|
109
|
+
value="filter paper"
|
|
110
|
+
/>
|
|
111
|
+
</StoryCell>
|
|
112
|
+
<StoryCell label="invalid">
|
|
113
|
+
<SearchInput
|
|
114
|
+
aria-label="Invalid search"
|
|
115
|
+
aria-invalid="true"
|
|
116
|
+
clearIcon={<ClearIcon />}
|
|
117
|
+
onClear={() => undefined}
|
|
118
|
+
readOnly
|
|
119
|
+
value="filter paper"
|
|
120
|
+
/>
|
|
121
|
+
</StoryCell>
|
|
122
|
+
</StoryGrid>
|
|
123
|
+
),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const Responsive: Story = {
|
|
127
|
+
render: () => (
|
|
128
|
+
<ContainerBoard>
|
|
129
|
+
<ControlledSearch />
|
|
130
|
+
</ContainerBoard>
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const Interactive: Story = {
|
|
135
|
+
render: () => <ControlledSearch />,
|
|
136
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent } 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 "./SearchInput.stories.tsx"
|
|
9
|
+
|
|
10
|
+
const { Interactive } = composeStories(stories)
|
|
11
|
+
|
|
12
|
+
test("the clear affordance is a labelled button and returns focus", async () => {
|
|
13
|
+
const { canvas, canvasElement } =
|
|
14
|
+
await mountStory(Interactive)
|
|
15
|
+
const input = expectAgentDrivable(canvas, {
|
|
16
|
+
name: "Search inventory",
|
|
17
|
+
role: "searchbox",
|
|
18
|
+
})
|
|
19
|
+
const clear = expectAgentDrivable(canvas, {
|
|
20
|
+
name: "Clear search",
|
|
21
|
+
role: "button",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
await userEvent.click(clear)
|
|
25
|
+
await expect(input).toHaveValue("")
|
|
26
|
+
await expect(input).toHaveFocus()
|
|
27
|
+
await expectNoAxeViolations(canvasElement)
|
|
28
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { ControlSize } from "@charcuterie/tokens"
|
|
2
|
+
import {
|
|
3
|
+
type ComponentPropsWithRef,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useRef,
|
|
6
|
+
} from "react"
|
|
7
|
+
|
|
8
|
+
import { IconButton } from "../IconButton/IconButton.tsx"
|
|
9
|
+
import {
|
|
10
|
+
DISABLED_CLASS,
|
|
11
|
+
FOCUS_RING_CLASS,
|
|
12
|
+
} from "../intentStyles.ts"
|
|
13
|
+
import { toClassName } from "../toClassName.ts"
|
|
14
|
+
|
|
15
|
+
const INPUT_SIZE_CLASS: Record<ControlSize, string> = {
|
|
16
|
+
sm: "h-(--control-height-sm) ps-(--control-padding-inline-sm) pe-(--control-height-sm) text-sm",
|
|
17
|
+
md: "h-(--control-height-md) ps-(--control-padding-inline-md) pe-(--control-height-md) text-md",
|
|
18
|
+
lg: "h-(--control-height-lg) ps-(--control-padding-inline-lg) pe-(--control-height-lg) text-lg",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SearchInputProps = Omit<
|
|
22
|
+
ComponentPropsWithRef<"input">,
|
|
23
|
+
"size" | "type"
|
|
24
|
+
> & {
|
|
25
|
+
/** The app-owned glyph inside the clear button. */
|
|
26
|
+
clearIcon: ReactNode
|
|
27
|
+
clearLabel?: string
|
|
28
|
+
/** Override value-derived visibility for an uncontrolled input. */
|
|
29
|
+
isClearVisible?: boolean
|
|
30
|
+
onClear: () => void
|
|
31
|
+
size?: ControlSize
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A search field whose clear affordance is a real, labelled icon
|
|
36
|
+
* button. The native search cancel widget is suppressed because it
|
|
37
|
+
* is platform-painted, cannot use the token system, and provides a
|
|
38
|
+
* much smaller pointer target than the control beside it.
|
|
39
|
+
*
|
|
40
|
+
* The app supplies the icon and owns the value. `SearchInput` owns
|
|
41
|
+
* the shape, focus return, accessible button name, and target size.
|
|
42
|
+
*/
|
|
43
|
+
export const SearchInput = ({
|
|
44
|
+
className,
|
|
45
|
+
clearIcon,
|
|
46
|
+
clearLabel = "Clear search",
|
|
47
|
+
defaultValue,
|
|
48
|
+
isClearVisible,
|
|
49
|
+
onClear,
|
|
50
|
+
ref,
|
|
51
|
+
size = "md",
|
|
52
|
+
value,
|
|
53
|
+
...inputProps
|
|
54
|
+
}: SearchInputProps): ReactNode => {
|
|
55
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
56
|
+
const hasValue =
|
|
57
|
+
isClearVisible ??
|
|
58
|
+
String(value ?? defaultValue ?? "").length > 0
|
|
59
|
+
|
|
60
|
+
const setInputRef = (node: HTMLInputElement | null) => {
|
|
61
|
+
inputRef.current = node
|
|
62
|
+
|
|
63
|
+
if (typeof ref === "function") ref(node)
|
|
64
|
+
else if (ref) ref.current = node
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<span
|
|
69
|
+
className={toClassName("relative block", className)}
|
|
70
|
+
>
|
|
71
|
+
<input
|
|
72
|
+
{...inputProps}
|
|
73
|
+
className={toClassName(
|
|
74
|
+
"w-full min-w-0 appearance-none rounded-md border border-border-default bg-surface-raised text-content-primary transition-colors duration-(--duration-fast) ease-standard placeholder:text-content-muted hover:border-border-strong aria-invalid:border-intent-danger-border [&::-webkit-search-cancel-button]:appearance-none",
|
|
75
|
+
INPUT_SIZE_CLASS[size],
|
|
76
|
+
FOCUS_RING_CLASS,
|
|
77
|
+
DISABLED_CLASS,
|
|
78
|
+
)}
|
|
79
|
+
defaultValue={defaultValue}
|
|
80
|
+
ref={setInputRef}
|
|
81
|
+
type="search"
|
|
82
|
+
value={value}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
{hasValue && !inputProps.disabled ? (
|
|
86
|
+
<span className="absolute inset-y-0 end-0 flex items-center">
|
|
87
|
+
<IconButton
|
|
88
|
+
appearance="ghost"
|
|
89
|
+
label={clearLabel}
|
|
90
|
+
onClick={() => {
|
|
91
|
+
onClear()
|
|
92
|
+
inputRef.current?.focus()
|
|
93
|
+
}}
|
|
94
|
+
onPointerDown={(event) =>
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
}
|
|
97
|
+
size={size}
|
|
98
|
+
>
|
|
99
|
+
{clearIcon}
|
|
100
|
+
</IconButton>
|
|
101
|
+
</span>
|
|
102
|
+
) : null}
|
|
103
|
+
</span>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useVirtualizer } from "@tanstack/react-virtual"
|
|
2
|
+
import { useCallback } from "react"
|
|
3
|
+
|
|
4
|
+
import { GridRows } from "./GridRows.tsx"
|
|
5
|
+
import { useScrollMargin } from "./virtualizedGridScroll.ts"
|
|
6
|
+
import type {
|
|
7
|
+
GridWindowProps,
|
|
8
|
+
ScrollOwner,
|
|
9
|
+
} from "./virtualizedGridTypes.ts"
|
|
10
|
+
|
|
11
|
+
export const ElementGrid = <Item,>(
|
|
12
|
+
props: GridWindowProps<Item> & {
|
|
13
|
+
scrollOwner: Extract<ScrollOwner, { kind: "element" }>
|
|
14
|
+
},
|
|
15
|
+
) => {
|
|
16
|
+
const { gridElement, scrollMargin } = useScrollMargin(
|
|
17
|
+
props.scrollOwner,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const measureRow = useCallback(
|
|
21
|
+
(element: Element) =>
|
|
22
|
+
element.getBoundingClientRect().height + props.gap,
|
|
23
|
+
[props.gap],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const rowVirtualizer = useVirtualizer({
|
|
27
|
+
count: props.rowCount,
|
|
28
|
+
estimateSize: () => props.itemBlockSize + props.gap,
|
|
29
|
+
getScrollElement: () => props.scrollOwner.element,
|
|
30
|
+
measureElement: measureRow,
|
|
31
|
+
overscan: props.overscanRows,
|
|
32
|
+
scrollMargin,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<GridRows
|
|
37
|
+
{...props}
|
|
38
|
+
gridElement={gridElement}
|
|
39
|
+
measureElement={rowVirtualizer.measureElement}
|
|
40
|
+
scrollMargin={scrollMargin}
|
|
41
|
+
totalSize={rowVirtualizer.getTotalSize()}
|
|
42
|
+
virtualRows={rowVirtualizer.getVirtualItems()}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getContentMaxInlineSize } from "../AdaptiveGrid/chooseColumns.ts"
|
|
2
|
+
import type { GridRowsProps } from "./virtualizedGridTypes.ts"
|
|
3
|
+
|
|
4
|
+
/** The shared markup. Only the scroll observer differs between the two owners. */
|
|
5
|
+
export const GridRows = <Item,>({
|
|
6
|
+
drawnColumns,
|
|
7
|
+
gap,
|
|
8
|
+
getItemKey,
|
|
9
|
+
gridElement,
|
|
10
|
+
items,
|
|
11
|
+
label,
|
|
12
|
+
measureElement,
|
|
13
|
+
renderItem,
|
|
14
|
+
scrollMargin,
|
|
15
|
+
totalSize,
|
|
16
|
+
virtualRows,
|
|
17
|
+
}: GridRowsProps<Item>) => {
|
|
18
|
+
const firstRow = virtualRows[0]
|
|
19
|
+
const lastRow = virtualRows[virtualRows.length - 1]
|
|
20
|
+
|
|
21
|
+
// Everything scrolled past, and everything still to come. The two
|
|
22
|
+
// numbers that stand in for the rows that do not exist.
|
|
23
|
+
const paddingBlockStart = firstRow?.start ?? 0
|
|
24
|
+
|
|
25
|
+
const paddingBlockEnd = lastRow
|
|
26
|
+
? Math.max(0, totalSize - lastRow.end)
|
|
27
|
+
: 0
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<ul
|
|
31
|
+
aria-label={label}
|
|
32
|
+
className="grid w-full list-none p-0 [&>*]:min-w-0"
|
|
33
|
+
ref={gridElement}
|
|
34
|
+
style={{
|
|
35
|
+
gap: `${gap}px`,
|
|
36
|
+
gridTemplateColumns: `repeat(${drawnColumns}, minmax(0, 1fr))`,
|
|
37
|
+
maxInlineSize: getContentMaxInlineSize({
|
|
38
|
+
columns: drawnColumns,
|
|
39
|
+
}),
|
|
40
|
+
// Offset by `scrollMargin` because `start` is measured
|
|
41
|
+
// from the top of the scroll owner, and this padding is
|
|
42
|
+
// measured from the top of the grid.
|
|
43
|
+
paddingBlockEnd: `${paddingBlockEnd}px`,
|
|
44
|
+
paddingBlockStart: `${Math.max(0, paddingBlockStart - scrollMargin)}px`,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{virtualRows.flatMap((row) => {
|
|
48
|
+
const firstIndex = row.index * drawnColumns
|
|
49
|
+
|
|
50
|
+
return items
|
|
51
|
+
.slice(firstIndex, firstIndex + drawnColumns)
|
|
52
|
+
.map((item, offset) => {
|
|
53
|
+
const index = firstIndex + offset
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<li
|
|
57
|
+
aria-posinset={index + 1}
|
|
58
|
+
aria-setsize={items.length}
|
|
59
|
+
// `data-index` names the ROW, so it goes on
|
|
60
|
+
// every cell in it — they all sit in that row
|
|
61
|
+
// and the attribute is true of all of them.
|
|
62
|
+
//
|
|
63
|
+
// Putting it only on the measured cell is the
|
|
64
|
+
// version that looks tidier and warns on every
|
|
65
|
+
// resize: the virtualizer keeps a
|
|
66
|
+
// `ResizeObserver` on whatever it measured and
|
|
67
|
+
// re-reads the attribute whenever that node
|
|
68
|
+
// changes size. Let the node stop being the
|
|
69
|
+
// leading cell — a column count change is all it
|
|
70
|
+
// takes — and it is still observed, now with no
|
|
71
|
+
// attribute to resolve.
|
|
72
|
+
data-index={row.index}
|
|
73
|
+
key={getItemKey?.(item, index) ?? index}
|
|
74
|
+
// Measured on the leading cell alone. A CSS grid
|
|
75
|
+
// stretches every cell to its row's height, so
|
|
76
|
+
// one is enough — and measuring all of them
|
|
77
|
+
// would have the virtualizer record the same row
|
|
78
|
+
// once per column.
|
|
79
|
+
ref={
|
|
80
|
+
offset === 0 ? measureElement : undefined
|
|
81
|
+
}
|
|
82
|
+
>
|
|
83
|
+
{renderItem(item, index)}
|
|
84
|
+
</li>
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
})}
|
|
88
|
+
</ul>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -47,16 +47,17 @@ commitment — every mounted row is measured and the estimate corrected. It stil
|
|
|
47
47
|
be close, because it is what the scrollbar is sized from before anything has been
|
|
48
48
|
measured. A bad guess makes the bar jump under the thumb on the first scroll.
|
|
49
49
|
|
|
50
|
-
## It
|
|
50
|
+
## It follows the page's scroll region
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
page header above it.
|
|
52
|
+
The grid never creates a scroll box. It follows the nearest ancestor whose vertical
|
|
53
|
+
overflow is `auto` or `scroll`. Inside `Shell`, that is `Main`, so the header and rail stay
|
|
54
|
+
available while the grid scrolls. A standalone grid falls back to the browser window.
|
|
56
55
|
|
|
57
|
-
The cost is that the grid has to know how far down
|
|
58
|
-
|
|
59
|
-
wrong and every row is offset by the
|
|
56
|
+
The cost is that the grid has to know how far down its scroll region it starts. It measures
|
|
57
|
+
that offset in a layout effect and feeds it to the virtualizer as `scrollMargin`. Get that
|
|
58
|
+
wrong and every row is offset by the chrome above it.
|
|
59
|
+
|
|
60
|
+
<Canvas of={VirtualizedGridStories.InShellScrollRegion} />
|
|
60
61
|
|
|
61
62
|
## The column rule is inherited, not reimplemented
|
|
62
63
|
|
|
@@ -3,9 +3,13 @@ import { useState } from "react"
|
|
|
3
3
|
|
|
4
4
|
import { getColumnChoices } from "../AdaptiveGrid/chooseColumns.ts"
|
|
5
5
|
import type { BlockSizeResolver } from "../AdaptiveGrid/useAdaptiveColumns.ts"
|
|
6
|
+
import { Button } from "../Button/Button.tsx"
|
|
6
7
|
import { StorySection } from "../board.storyHelpers.tsx"
|
|
7
8
|
import { Card } from "../Card/Card.tsx"
|
|
9
|
+
import { Header } from "../Header/Header.tsx"
|
|
10
|
+
import { Main } from "../Main/Main.tsx"
|
|
8
11
|
import { SegmentedControl } from "../SegmentedControl/SegmentedControl.tsx"
|
|
12
|
+
import { Shell } from "../Shell/Shell.tsx"
|
|
9
13
|
import { VirtualizedGrid } from "./VirtualizedGrid.tsx"
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -104,6 +108,28 @@ type Story = StoryObj<typeof meta>
|
|
|
104
108
|
*/
|
|
105
109
|
export const Default: Story = {}
|
|
106
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The app-shell contract: `Main` is the one vertical scroll region,
|
|
113
|
+
* and the grid follows it rather than adding document height.
|
|
114
|
+
*
|
|
115
|
+
* This is QueuePilot's Pending shape. It is a separate story because
|
|
116
|
+
* a standalone grid still follows the browser window and both paths
|
|
117
|
+
* need a real consumer.
|
|
118
|
+
*/
|
|
119
|
+
export const InShellScrollRegion: Story = {
|
|
120
|
+
parameters: { layout: "fullscreen" },
|
|
121
|
+
render: (gridProps) => (
|
|
122
|
+
<Shell contentWidth="full">
|
|
123
|
+
<Header heading="Pending" />
|
|
124
|
+
|
|
125
|
+
<Main>
|
|
126
|
+
<Button onClick={() => {}}>Mark all seen</Button>
|
|
127
|
+
<VirtualizedGrid {...gridProps} />
|
|
128
|
+
</Main>
|
|
129
|
+
</Shell>
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
/**
|
|
108
134
|
* The same 2,000 items at three window heights.
|
|
109
135
|
*
|
|
@@ -33,7 +33,8 @@ import * as stories from "./VirtualizedGrid.stories.tsx"
|
|
|
33
33
|
* on.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
const { AllStates, Default } =
|
|
36
|
+
const { AllStates, Default, InShellScrollRegion } =
|
|
37
|
+
composeStories(stories)
|
|
37
38
|
|
|
38
39
|
const getLists = (canvasElement: HTMLElement) => [
|
|
39
40
|
...canvasElement.querySelectorAll<HTMLElement>("ul"),
|
|
@@ -146,6 +147,64 @@ test("scrolling changes which rows exist", async () => {
|
|
|
146
147
|
}
|
|
147
148
|
})
|
|
148
149
|
|
|
150
|
+
test("a grid inside Shell follows Main's one scroll region", async () => {
|
|
151
|
+
const { canvasElement } = await mountStory(
|
|
152
|
+
InShellScrollRegion,
|
|
153
|
+
)
|
|
154
|
+
const main =
|
|
155
|
+
canvasElement.querySelector<HTMLElement>("main")
|
|
156
|
+
|
|
157
|
+
if (!main) {
|
|
158
|
+
throw new Error("The shell story has no Main element.")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await waitFor(async () => {
|
|
162
|
+
await expect(
|
|
163
|
+
getRowIndexes(canvasElement).length,
|
|
164
|
+
).toBeGreaterThan(0)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await expect(getComputedStyle(main).overflowY).toBe(
|
|
168
|
+
"auto",
|
|
169
|
+
)
|
|
170
|
+
await expect(main.scrollHeight).toBeGreaterThan(
|
|
171
|
+
main.clientHeight,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const before = getRowIndexes(canvasElement)
|
|
175
|
+
|
|
176
|
+
main.scrollTop = 6000
|
|
177
|
+
main.dispatchEvent(new Event("scroll"))
|
|
178
|
+
|
|
179
|
+
await waitFor(async () => {
|
|
180
|
+
await expect(
|
|
181
|
+
getRowIndexes(canvasElement).some((index) =>
|
|
182
|
+
before.includes(index),
|
|
183
|
+
),
|
|
184
|
+
).toBe(false)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// The last row must replace the virtual spacer. This is the
|
|
188
|
+
// reported failure's visible half: the old window observer left
|
|
189
|
+
// tens of thousands of blank pixels below the first mounted rows
|
|
190
|
+
// when Main scrolled.
|
|
191
|
+
main.scrollTop = main.scrollHeight
|
|
192
|
+
main.dispatchEvent(new Event("scroll"))
|
|
193
|
+
|
|
194
|
+
await waitFor(async () => {
|
|
195
|
+
await expect(
|
|
196
|
+
getCells(canvasElement).some(
|
|
197
|
+
(cell) =>
|
|
198
|
+
cell.getAttribute("aria-posinset") === "2000",
|
|
199
|
+
),
|
|
200
|
+
).toBe(true)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
await expect(
|
|
204
|
+
getList(canvasElement).style.paddingBlockEnd,
|
|
205
|
+
).toBe("0px")
|
|
206
|
+
})
|
|
207
|
+
|
|
149
208
|
test("a screen reader is told the real length", async () => {
|
|
150
209
|
const { canvasElement } = await mountStory(Default)
|
|
151
210
|
|