@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
package/src/Tooltip/Tooltip.mdx
CHANGED
|
@@ -45,15 +45,18 @@ instead of falling out of it.** Here `useRole` writes `aria-describedby` on the
|
|
|
45
45
|
An `IconButton` under a tooltip still carries its own `aria-label`. `AllVariants` proves the
|
|
46
46
|
button stays findable by the same query whether or not the tip is open.
|
|
47
47
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
## Portalled, and no focus manager
|
|
49
|
+
|
|
50
|
+
The tip renders in a `FloatingPortal` at `document.body` — the same reversal as the other
|
|
51
|
+
overlays
|
|
52
|
+
([decision](../../../docs/decisions/2026-08-03-overlays-portal-to-the-body-not-the-top-layer.md)):
|
|
53
|
+
`strategy: "fixed"` alone still let an `overflow: hidden` ancestor clip it, and a portal
|
|
54
|
+
does not. `--layer-tooltip` keeps it above a modal.
|
|
55
|
+
|
|
56
|
+
There is no `FloatingFocusManager`, unlike `Popover`. Focus never enters a tooltip — moving
|
|
57
|
+
it there is what would make one inescapable — so the tip stays out of the focus manager and
|
|
58
|
+
the overlay stack. A scoped query for the tip now reaches for `body`; `useRole` writes
|
|
59
|
+
`aria-describedby` on the trigger, so the link survives the boundary.
|
|
57
60
|
|
|
58
61
|
<Canvas of={TooltipStories.AllStates} />
|
|
59
62
|
|
|
@@ -11,7 +11,7 @@ const { AllVariants, Default, Interactive } =
|
|
|
11
11
|
composeStories(stories)
|
|
12
12
|
|
|
13
13
|
test("nothing is shown until something asks", async () => {
|
|
14
|
-
const { canvas, canvasElement } =
|
|
14
|
+
const { body, canvas, canvasElement } =
|
|
15
15
|
await mountStory(Default)
|
|
16
16
|
|
|
17
17
|
expectAgentDrivable(canvas, {
|
|
@@ -19,9 +19,9 @@ test("nothing is shown until something asks", async () => {
|
|
|
19
19
|
role: "button",
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
await expect(
|
|
23
|
-
|
|
24
|
-
)
|
|
22
|
+
await expect(body.queryAllByRole("tooltip")).toHaveLength(
|
|
23
|
+
0,
|
|
24
|
+
)
|
|
25
25
|
|
|
26
26
|
await expectNoAxeViolations(canvasElement)
|
|
27
27
|
})
|
|
@@ -34,8 +34,7 @@ test("nothing is shown until something asks", async () => {
|
|
|
34
34
|
* floating node nobody is pointed at.
|
|
35
35
|
*/
|
|
36
36
|
test("the trigger is described by the tip that opened", async () => {
|
|
37
|
-
const {
|
|
38
|
-
await mountStory(Interactive)
|
|
37
|
+
const { body, canvas } = await mountStory(Interactive)
|
|
39
38
|
|
|
40
39
|
const trigger = expectAgentDrivable(canvas, {
|
|
41
40
|
name: "Keyboard tip",
|
|
@@ -52,8 +51,10 @@ test("the trigger is described by the tip that opened", async () => {
|
|
|
52
51
|
expect(trigger).toHaveFocus()
|
|
53
52
|
})
|
|
54
53
|
|
|
54
|
+
// Portalled now, so found through `body`; the `aria-describedby`
|
|
55
|
+
// link survives the boundary.
|
|
55
56
|
const tip = await waitFor(() =>
|
|
56
|
-
expectAgentDrivable(
|
|
57
|
+
expectAgentDrivable(body, { role: "tooltip" }),
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
await expect(trigger).toHaveAttribute(
|
|
@@ -65,10 +66,7 @@ test("the trigger is described by the tip that opened", async () => {
|
|
|
65
66
|
"Opened by focus, closed by Escape.",
|
|
66
67
|
)
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
// runs axe once, when `run()` resolves — before anything has been
|
|
70
|
-
// focused — so an open tip would never be seen by it.
|
|
71
|
-
await expectNoAxeViolations(canvasElement)
|
|
69
|
+
await expectNoAxeViolations(tip)
|
|
72
70
|
})
|
|
73
71
|
|
|
74
72
|
/**
|
|
@@ -77,28 +75,28 @@ test("the trigger is described by the tip that opened", async () => {
|
|
|
77
75
|
* failure on the one control whose explanation lives there.
|
|
78
76
|
*/
|
|
79
77
|
test("focus opens it and blur closes it", async () => {
|
|
80
|
-
const {
|
|
78
|
+
const { body } = await mountStory(Interactive)
|
|
81
79
|
|
|
82
80
|
await userEvent.tab()
|
|
83
81
|
|
|
84
82
|
await waitFor(() => {
|
|
85
|
-
expectAgentDrivable(
|
|
83
|
+
expectAgentDrivable(body, { role: "tooltip" })
|
|
86
84
|
})
|
|
87
85
|
|
|
88
86
|
await userEvent.tab()
|
|
89
87
|
|
|
90
88
|
await waitFor(() => {
|
|
91
|
-
expect(
|
|
89
|
+
expect(body.queryAllByRole("tooltip")).toHaveLength(0)
|
|
92
90
|
})
|
|
93
91
|
})
|
|
94
92
|
|
|
95
93
|
test("Escape dismisses it", async () => {
|
|
96
|
-
const {
|
|
94
|
+
const { body } = await mountStory(Interactive)
|
|
97
95
|
|
|
98
96
|
await userEvent.tab()
|
|
99
97
|
|
|
100
98
|
await waitFor(() => {
|
|
101
|
-
expectAgentDrivable(
|
|
99
|
+
expectAgentDrivable(body, { role: "tooltip" })
|
|
102
100
|
})
|
|
103
101
|
|
|
104
102
|
// WCAG 1.4.13 requires content shown on hover or focus to be
|
|
@@ -106,7 +104,7 @@ test("Escape dismisses it", async () => {
|
|
|
106
104
|
await userEvent.keyboard("{Escape}")
|
|
107
105
|
|
|
108
106
|
await waitFor(() => {
|
|
109
|
-
expect(
|
|
107
|
+
expect(body.queryAllByRole("tooltip")).toHaveLength(0)
|
|
110
108
|
})
|
|
111
109
|
})
|
|
112
110
|
|
|
@@ -117,7 +115,7 @@ test("Escape dismisses it", async () => {
|
|
|
117
115
|
* whether or not the tip is open.
|
|
118
116
|
*/
|
|
119
117
|
test("the tip does not become the control's name", async () => {
|
|
120
|
-
const { canvas } = await mountStory(AllVariants)
|
|
118
|
+
const { body, canvas } = await mountStory(AllVariants)
|
|
121
119
|
|
|
122
120
|
const iconButton = expectAgentDrivable(canvas, {
|
|
123
121
|
name: "Bay settings",
|
|
@@ -127,7 +125,7 @@ test("the tip does not become the control's name", async () => {
|
|
|
127
125
|
await userEvent.hover(iconButton)
|
|
128
126
|
|
|
129
127
|
await waitFor(() => {
|
|
130
|
-
expectAgentDrivable(
|
|
128
|
+
expectAgentDrivable(body, { role: "tooltip" })
|
|
131
129
|
})
|
|
132
130
|
|
|
133
131
|
// Still "Bay settings", not "Opens the bay's rip settings."
|
package/src/Tooltip/Tooltip.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import type { Placement } from "@floating-ui/react"
|
|
6
6
|
import {
|
|
7
7
|
autoUpdate,
|
|
8
|
+
FloatingPortal,
|
|
8
9
|
flip,
|
|
9
10
|
offset,
|
|
10
11
|
shift,
|
|
@@ -155,38 +156,40 @@ export const Tooltip = ({
|
|
|
155
156
|
{clonedTrigger}
|
|
156
157
|
|
|
157
158
|
{isVisible ? (
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
159
|
+
<FloatingPortal>
|
|
160
|
+
<div
|
|
161
|
+
{...getFloatingProps()}
|
|
162
|
+
className={toClassName(
|
|
163
|
+
// `intent-neutral-solid` + its own `on-solid`, rather
|
|
164
|
+
// than `surface-inverse` + `content-on-accent`. The
|
|
165
|
+
// second pair looks like the obvious one and is not a
|
|
166
|
+
// *pair* at all — nothing in the token set promises those
|
|
167
|
+
// two contrast, and in `daylight` dark they land at
|
|
168
|
+
// 2.9:1. The `solid`/`on-solid` roles exist precisely to
|
|
169
|
+
// be a guaranteed couple, and the contrast gate checks
|
|
170
|
+
// them as one.
|
|
171
|
+
"z-[var(--layer-tooltip)] max-w-xs rounded-md bg-intent-neutral-solid px-2 py-1 text-intent-neutral-on-solid text-xs shadow-medium",
|
|
172
|
+
className,
|
|
173
|
+
)}
|
|
174
|
+
// Portalled to `document.body`, the same reversal as the
|
|
175
|
+
// other overlays: `strategy: "fixed"` alone still let an
|
|
176
|
+
// `overflow: hidden` ancestor clip the tip, and a portal
|
|
177
|
+
// does not. No `FloatingFocusManager`, unlike `Popover` —
|
|
178
|
+
// focus never enters a tooltip, and moving it here is what
|
|
179
|
+
// would make one unescapable. `--layer-tooltip` keeps it
|
|
180
|
+
// above a modal.
|
|
181
|
+
ref={refs.setFloating}
|
|
182
|
+
// Duplicated from `getFloatingProps()` deliberately, and
|
|
183
|
+
// the two cannot differ — `useRole(context, { role:
|
|
184
|
+
// "tooltip" })` above is where it comes from. Stated here
|
|
185
|
+
// because a linter cannot see a role through a spread, and
|
|
186
|
+
// neither can the next reader.
|
|
187
|
+
role="tooltip"
|
|
188
|
+
style={floatingStyles}
|
|
189
|
+
>
|
|
190
|
+
{label}
|
|
191
|
+
</div>
|
|
192
|
+
</FloatingPortal>
|
|
190
193
|
) : null}
|
|
191
194
|
</>
|
|
192
195
|
)
|
|
@@ -104,6 +104,28 @@ export const ImageOffIcon = (): ReactNode => (
|
|
|
104
104
|
</svg>
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
+
export const SunIcon = (): ReactNode => (
|
|
108
|
+
<svg {...iconProps} aria-hidden="true">
|
|
109
|
+
<circle cx="12" cy="12" r="4" />
|
|
110
|
+
|
|
111
|
+
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5.6 5.6 4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4" />
|
|
112
|
+
</svg>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
export const MoonIcon = (): ReactNode => (
|
|
116
|
+
<svg {...iconProps} aria-hidden="true">
|
|
117
|
+
<path d="M20 14.5A8 8 0 1 1 9.5 4a6.2 6.2 0 0 0 10.5 10.5z" />
|
|
118
|
+
</svg>
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
export const MonitorIcon = (): ReactNode => (
|
|
122
|
+
<svg {...iconProps} aria-hidden="true">
|
|
123
|
+
<rect height="12" rx="2" width="18" x="3" y="4.5" />
|
|
124
|
+
|
|
125
|
+
<path d="M8 20.5h8M12 16.5v4" />
|
|
126
|
+
</svg>
|
|
127
|
+
)
|
|
128
|
+
|
|
107
129
|
export const DotIcon = (): ReactNode => (
|
|
108
130
|
<svg
|
|
109
131
|
{...iconProps}
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,15 @@ export type {
|
|
|
52
52
|
CardSurface,
|
|
53
53
|
} from "./Card/Card.tsx"
|
|
54
54
|
export { Card } from "./Card/Card.tsx"
|
|
55
|
+
export type { ColorSchemeSwitcherProps } from "./ColorSchemeSwitcher/ColorSchemeSwitcher.tsx"
|
|
56
|
+
export { ColorSchemeSwitcher } from "./ColorSchemeSwitcher/ColorSchemeSwitcher.tsx"
|
|
57
|
+
export type {
|
|
58
|
+
ColorSchemeIcons,
|
|
59
|
+
ColorSchemeToggleProps,
|
|
60
|
+
} from "./ColorSchemeToggle/ColorSchemeToggle.tsx"
|
|
61
|
+
export { ColorSchemeToggle } from "./ColorSchemeToggle/ColorSchemeToggle.tsx"
|
|
62
|
+
export type { ComboboxProps } from "./Combobox/Combobox.tsx"
|
|
63
|
+
export { Combobox } from "./Combobox/Combobox.tsx"
|
|
55
64
|
export type { BadgeSize } from "./controlStyles.ts"
|
|
56
65
|
export {
|
|
57
66
|
BADGE_SIZE_CLASS,
|
|
@@ -61,6 +70,11 @@ export {
|
|
|
61
70
|
MIN_TOUCH_TARGET_CLASS,
|
|
62
71
|
SPINNER_SIZE_CLASS,
|
|
63
72
|
} from "./controlStyles.ts"
|
|
73
|
+
export type {
|
|
74
|
+
DialogProps,
|
|
75
|
+
DialogSize,
|
|
76
|
+
} from "./Dialog/Dialog.tsx"
|
|
77
|
+
export { Dialog } from "./Dialog/Dialog.tsx"
|
|
64
78
|
export type {
|
|
65
79
|
EmptyStateProps,
|
|
66
80
|
EmptyStateSize,
|
|
@@ -83,6 +97,13 @@ export {
|
|
|
83
97
|
INTENT_HOVER_CLASS,
|
|
84
98
|
INTENT_SOLID_FILL_CLASS,
|
|
85
99
|
} from "./intentStyles.ts"
|
|
100
|
+
export type { LightboxProps } from "./Lightbox/Lightbox.tsx"
|
|
101
|
+
export { Lightbox } from "./Lightbox/Lightbox.tsx"
|
|
102
|
+
export type {
|
|
103
|
+
ListboxItem,
|
|
104
|
+
ListboxProps,
|
|
105
|
+
} from "./Listbox/Listbox.tsx"
|
|
106
|
+
export { Listbox } from "./Listbox/Listbox.tsx"
|
|
86
107
|
export type {
|
|
87
108
|
LiveStatusIndicatorProps,
|
|
88
109
|
LiveStatusIndicatorSize,
|
|
@@ -105,10 +126,7 @@ export type {
|
|
|
105
126
|
MenuProps,
|
|
106
127
|
} from "./Menu/Menu.tsx"
|
|
107
128
|
export { Menu } from "./Menu/Menu.tsx"
|
|
108
|
-
export type {
|
|
109
|
-
ModalProps,
|
|
110
|
-
ModalSize,
|
|
111
|
-
} from "./Modal/Modal.tsx"
|
|
129
|
+
export type { ModalProps } from "./Modal/Modal.tsx"
|
|
112
130
|
export { Modal } from "./Modal/Modal.tsx"
|
|
113
131
|
export type { PopoverProps } from "./Popover/Popover.tsx"
|
|
114
132
|
export { Popover } from "./Popover/Popover.tsx"
|
|
@@ -46,12 +46,26 @@ type MountableStory = {
|
|
|
46
46
|
* The stale tree is removed rather than unmounted because portable
|
|
47
47
|
* stories expose no unmount. Detaching is enough: it takes the node
|
|
48
48
|
* out of the layout, which is the entire problem.
|
|
49
|
+
*
|
|
50
|
+
* ### Portalled panels leave the canvas
|
|
51
|
+
*
|
|
52
|
+
* Once the overlays portal to `document.body`, their panels are no
|
|
53
|
+
* longer inside `canvasElement`, and floating-ui's portal roots
|
|
54
|
+
* (`[data-floating-ui-portal]`) are body children a portable story
|
|
55
|
+
* never unmounts. A previous test's panel therefore lingers next to
|
|
56
|
+
* the live one, and `expectAgentDrivable(body, …)` fails with "2
|
|
57
|
+
* elements match" — the portal analogue of the fixed-position
|
|
58
|
+
* `ToastRegion` bug above. So the same cleanup that detaches stale
|
|
59
|
+
* canvases also removes stale portal roots, and `mountStory` now hands
|
|
60
|
+
* back `body` (scoped to `document.body`) for querying the panels the
|
|
61
|
+
* triggers still open from inside `canvas`.
|
|
49
62
|
*/
|
|
50
63
|
const mountedCanvases: HTMLElement[] = []
|
|
51
64
|
|
|
52
65
|
export const mountStory = async (
|
|
53
66
|
story: MountableStory,
|
|
54
67
|
): Promise<{
|
|
68
|
+
body: Canvas
|
|
55
69
|
canvas: Canvas
|
|
56
70
|
canvasElement: HTMLElement
|
|
57
71
|
}> => {
|
|
@@ -59,6 +73,12 @@ export const mountStory = async (
|
|
|
59
73
|
previous.remove()
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
for (const portalRoot of document.querySelectorAll(
|
|
77
|
+
"[data-floating-ui-portal]",
|
|
78
|
+
)) {
|
|
79
|
+
portalRoot.remove()
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
const canvasElement = document.createElement("div")
|
|
63
83
|
|
|
64
84
|
document.body.append(canvasElement)
|
|
@@ -67,5 +87,9 @@ export const mountStory = async (
|
|
|
67
87
|
|
|
68
88
|
await story.run({ canvasElement })
|
|
69
89
|
|
|
70
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
body: within(document.body),
|
|
92
|
+
canvas: within(canvasElement),
|
|
93
|
+
canvasElement,
|
|
94
|
+
}
|
|
71
95
|
}
|
package/src/sourceRules.test.ts
CHANGED
|
@@ -290,7 +290,17 @@ test("the barrel is the only place components are re-exported", async () => {
|
|
|
290
290
|
// directory because a region with no toasts in it is not a thing
|
|
291
291
|
// anybody renders. It is therefore outside this count by the same
|
|
292
292
|
// rule, and the barrel assertion below does not reach it.
|
|
293
|
-
|
|
293
|
+
//
|
|
294
|
+
// 2026-08-03 adds the two colour-scheme controls — `ColorSchemeToggle`
|
|
295
|
+
// (Layer 2, presentational) and `ColorSchemeSwitcher` (Layer 3,
|
|
296
|
+
// connected) — and `Lightbox`, the thumbnail-over-`Dialog` viewer —
|
|
297
|
+
// taking the count to 28.
|
|
298
|
+
//
|
|
299
|
+
// The M8 overlay rebuild renames the old chrome-bearing `Modal` to
|
|
300
|
+
// `Dialog` and reintroduces `Modal` as the base layer, so both are
|
|
301
|
+
// components: +1 → 29. The picker family adds `Listbox` (single-select)
|
|
302
|
+
// and `Combobox` (searchable/virtualized): +2 → 31.
|
|
303
|
+
expect(componentNames.length).toBe(31)
|
|
294
304
|
|
|
295
305
|
for (const name of componentNames) {
|
|
296
306
|
expect(barrel).toContain(`export { ${name} }`)
|
|
@@ -364,6 +374,13 @@ const ENTRY_POINT_RUNTIMES: Record<
|
|
|
364
374
|
> = {
|
|
365
375
|
logic: {
|
|
366
376
|
".": ["react"],
|
|
377
|
+
// The DOM defaults for `useColorScheme`. It reaches no bare
|
|
378
|
+
// specifier at all: `matchMedia`/`localStorage`/`document` are
|
|
379
|
+
// globals, not imports, and its only imports are type-only ones
|
|
380
|
+
// into `core` — which is why `ColorSchemeApplier` lives in the
|
|
381
|
+
// core rather than beside the hook, so this entry never pulls
|
|
382
|
+
// `react`.
|
|
383
|
+
"./browser": [],
|
|
367
384
|
"./core": [],
|
|
368
385
|
"./jotai": ["jotai"],
|
|
369
386
|
"./preact": ["preact", "preact/hooks"],
|
|
@@ -376,8 +393,16 @@ const ENTRY_POINT_RUNTIMES: Record<
|
|
|
376
393
|
ui: {
|
|
377
394
|
".": [
|
|
378
395
|
"@charcuterie/logic",
|
|
396
|
+
// `ColorSchemeSwitcher` is the one component that reaches the
|
|
397
|
+
// browser, and it does so through this subpath so the coupling
|
|
398
|
+
// is named rather than smuggled in through the main entry.
|
|
399
|
+
"@charcuterie/logic/browser",
|
|
379
400
|
"@charcuterie/tokens",
|
|
380
401
|
"@floating-ui/react",
|
|
402
|
+
// `Combobox`'s virtualization — the second runtime dependency
|
|
403
|
+
// this package has ever taken. MIT, US-origin (Tanner Linsley),
|
|
404
|
+
// tree-shakeable, ~4 KB gz.
|
|
405
|
+
"@tanstack/react-virtual",
|
|
381
406
|
"react",
|
|
382
407
|
],
|
|
383
408
|
"./testing": [],
|
|
@@ -24,21 +24,32 @@ import { expect, test } from "vitest"
|
|
|
24
24
|
const sourceDirectory = resolve(import.meta.dirname)
|
|
25
25
|
|
|
26
26
|
const readComponent = async (name: string) => {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// A `<Name>/<Name>.tsx` is the component-folder signal. An
|
|
28
|
+
// uppercase folder without one — `Overlay/`, which holds the shared
|
|
29
|
+
// portal/backdrop/anchoring infrastructure and no storied component
|
|
30
|
+
// — is not a component and is skipped, so this rule stays about the
|
|
31
|
+
// things that have a Controls panel to get wrong. A real component
|
|
32
|
+
// missing only its *story* still throws below, which is the guard.
|
|
33
|
+
let component: string
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
component = await readFile(
|
|
29
37
|
join(sourceDirectory, name, `${name}.tsx`),
|
|
30
38
|
"utf8",
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
)
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const story = await readFile(
|
|
45
|
+
join(sourceDirectory, name, `${name}.stories.tsx`),
|
|
46
|
+
"utf8",
|
|
47
|
+
)
|
|
37
48
|
|
|
38
49
|
return { component, name, story }
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
const
|
|
52
|
+
const componentDirectories = (
|
|
42
53
|
await readdir(sourceDirectory, { withFileTypes: true })
|
|
43
54
|
)
|
|
44
55
|
.filter(
|
|
@@ -48,9 +59,11 @@ const componentNames = (
|
|
|
48
59
|
.map((entry) => entry.name)
|
|
49
60
|
.sort()
|
|
50
61
|
|
|
51
|
-
const components =
|
|
52
|
-
|
|
53
|
-
)
|
|
62
|
+
const components = (
|
|
63
|
+
await Promise.all(componentDirectories.map(readComponent))
|
|
64
|
+
).filter((one) => one !== null)
|
|
65
|
+
|
|
66
|
+
const componentNames = components.map((one) => one.name)
|
|
54
67
|
|
|
55
68
|
/**
|
|
56
69
|
* `import type { A, B } from "@scope/pkg"` — bare specifiers only.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lockScrollBehind.d.ts","sourceRoot":"","sources":["../../src/Modal/lockScrollBehind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,eAAO,MAAM,gBAAgB,QAAO,CAAC,MAAM,IAAI,CAiC9C,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lockScrollBehind.js","sourceRoot":"","sources":["../../src/Modal/lockScrollBehind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,IAAI,SAAS,GAAG,CAAC,CAAA;AAEjB,IAAI,gBAAgB,GAAG,EAAE,CAAA;AAEzB,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAiB,EAAE;IACjD,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAA;IAEpC,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,gBAAgB,GAAG,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAA;QAEjD,eAAe,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC3C,CAAC;IAED,SAAS,IAAI,CAAC,CAAA;IAEd,IAAI,UAAU,GAAG,KAAK,CAAA;IAEtB,OAAO,GAAG,EAAE;QACV,6DAA6D;QAC7D,6DAA6D;QAC7D,qCAAqC;QACrC,IAAI,UAAU,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QAED,UAAU,GAAG,IAAI,CAAA;QAEjB,SAAS,IAAI,CAAC,CAAA;QAEd,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,eAAe,CAAC,KAAK,CAAC,QAAQ,GAAG,gBAAgB,CAAA;QACnD,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|