@charcuterie/ui 1.1.0 → 2.0.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/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 +53 -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 +7 -7
- package/dist/Lightbox/Lightbox.js +9 -9
- package/dist/Lightbox/Lightbox.js.map +1 -1
- 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 +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +3 -2
- 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 +397 -0
- package/src/Dialog/Dialog.test.tsx +340 -0
- package/src/Dialog/Dialog.tsx +133 -0
- package/src/Field/Field.test.tsx +6 -4
- package/src/Lightbox/Lightbox.mdx +9 -8
- package/src/Lightbox/Lightbox.stories.tsx +5 -5
- package/src/Lightbox/Lightbox.test.tsx +25 -36
- package/src/Lightbox/Lightbox.tsx +10 -10
- 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/index.ts +13 -4
- package/src/mountStory.testHelpers.ts +25 -1
- package/src/sourceRules.test.ts +12 -3
- 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,54 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { ListboxItem } from "../Listbox/Listbox.tsx";
|
|
3
|
+
export type ComboboxProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Shown when nothing matches. Default "No matches". */
|
|
6
|
+
emptyLabel?: string;
|
|
7
|
+
/** The error panel state (`PathPicker`). */
|
|
8
|
+
error?: ReactNode;
|
|
9
|
+
/** A sticky footer hint (`LinkPicker`'s "type a path directly"). */
|
|
10
|
+
footer?: ReactNode;
|
|
11
|
+
/** Enter commits the raw query as a value (`AssFieldPicker`). */
|
|
12
|
+
isCreatable?: boolean;
|
|
13
|
+
/** The loading panel state. */
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
/** Multi-select, rendered as removable chips (`LanguageCodeField`). */
|
|
16
|
+
isMultiple?: boolean;
|
|
17
|
+
/** Windowing. Auto-on above ~100 options when left undefined. */
|
|
18
|
+
isVirtualized?: boolean;
|
|
19
|
+
isVisible: boolean;
|
|
20
|
+
onDismiss: () => void;
|
|
21
|
+
onSelect: (value: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Omit to filter internally by `textValue`; supply to own the query
|
|
24
|
+
* (a react-query search, say), in which case `options` arrives
|
|
25
|
+
* pre-filtered.
|
|
26
|
+
*/
|
|
27
|
+
onQueryChange?: (query: string) => void;
|
|
28
|
+
options: readonly ListboxItem[];
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
/** Controlled query; uncontrolled when absent. */
|
|
31
|
+
query?: string;
|
|
32
|
+
selectedValue?: readonly string[] | string;
|
|
33
|
+
/** The control the popup hangs off. **Cloned, not wrapped.** */
|
|
34
|
+
trigger: ReactElement;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The searchable, filtering, virtualized picker — the other half of
|
|
38
|
+
* the picker family, and the one that consolidates the fleet's
|
|
39
|
+
* hand-rolled dropdowns.
|
|
40
|
+
*
|
|
41
|
+
* The ARIA contract differs from `Listbox` and the difference is
|
|
42
|
+
* forced, not stylistic: **focus stays in the text input**, so the
|
|
43
|
+
* active option is tracked with `aria-activedescendant`, not roving
|
|
44
|
+
* tabindex. The input is the `role="combobox"`; the popup is a
|
|
45
|
+
* `role="listbox"`. Arrows move the active descendant while the caret
|
|
46
|
+
* stays put; Enter selects; Escape clears the query before it closes.
|
|
47
|
+
*
|
|
48
|
+
* Filtering is the consumer's when `onQueryChange` is supplied (the
|
|
49
|
+
* `options` then arrive pre-filtered — a react-query search owns the
|
|
50
|
+
* debounce, the request token, the cache), and internal on `textValue`
|
|
51
|
+
* otherwise.
|
|
52
|
+
*/
|
|
53
|
+
export declare const Combobox: ({ className, emptyLabel, error, footer, isCreatable, isLoading, isMultiple, isVirtualized, isVisible, onDismiss, onQueryChange, onSelect, options, placeholder, query, selectedValue, trigger, }: ComboboxProps) => ReactNode;
|
|
54
|
+
//# sourceMappingURL=Combobox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Combobox.d.ts","sourceRoot":"","sources":["../../src/Combobox/Combobox.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAMzD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAA;IAC1C,gEAAgE;IAChE,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AA4BD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,GAAI,kMAkBtB,aAAa,KAAG,SAmZlB,CAAA"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
|
|
3
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
|
|
6
|
+
import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
|
|
7
|
+
import { toClassName } from "../toClassName.js";
|
|
8
|
+
import { ComboboxOption } from "./ComboboxOption.js";
|
|
9
|
+
const AUTO_VIRTUALIZE_THRESHOLD = 100;
|
|
10
|
+
const getOptionText = (item) => (item.textValue ??
|
|
11
|
+
(typeof item.label === "string" ? item.label : "")).toLowerCase();
|
|
12
|
+
const nextEnabledIndex = (options, from, step) => {
|
|
13
|
+
for (let index = from + step; index >= 0 && index < options.length; index += step) {
|
|
14
|
+
if (!options[index]?.isDisabled) {
|
|
15
|
+
return index;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return from;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The searchable, filtering, virtualized picker — the other half of
|
|
22
|
+
* the picker family, and the one that consolidates the fleet's
|
|
23
|
+
* hand-rolled dropdowns.
|
|
24
|
+
*
|
|
25
|
+
* The ARIA contract differs from `Listbox` and the difference is
|
|
26
|
+
* forced, not stylistic: **focus stays in the text input**, so the
|
|
27
|
+
* active option is tracked with `aria-activedescendant`, not roving
|
|
28
|
+
* tabindex. The input is the `role="combobox"`; the popup is a
|
|
29
|
+
* `role="listbox"`. Arrows move the active descendant while the caret
|
|
30
|
+
* stays put; Enter selects; Escape clears the query before it closes.
|
|
31
|
+
*
|
|
32
|
+
* Filtering is the consumer's when `onQueryChange` is supplied (the
|
|
33
|
+
* `options` then arrive pre-filtered — a react-query search owns the
|
|
34
|
+
* debounce, the request token, the cache), and internal on `textValue`
|
|
35
|
+
* otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export const Combobox = ({ className, emptyLabel = "No matches", error, footer, isCreatable = false, isLoading = false, isMultiple = false, isVirtualized, isVisible, onDismiss, onQueryChange, onSelect, options, placeholder = "Search…", query, selectedValue, trigger, }) => {
|
|
38
|
+
const inputElement = useRef(null);
|
|
39
|
+
const listElement = useRef(null);
|
|
40
|
+
const isQueryControlled = query !== undefined;
|
|
41
|
+
const [internalQuery, setInternalQuery] = useState("");
|
|
42
|
+
const currentQuery = query ?? internalQuery;
|
|
43
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
44
|
+
const [selected, setSelected] = useState(selectedValue === undefined
|
|
45
|
+
? []
|
|
46
|
+
: typeof selectedValue === "string"
|
|
47
|
+
? [selectedValue]
|
|
48
|
+
: [...selectedValue]);
|
|
49
|
+
// The consumer owns filtering when it owns the query; otherwise it is
|
|
50
|
+
// a plain `textValue` contains-match.
|
|
51
|
+
const filtered = onQueryChange === undefined
|
|
52
|
+
? options.filter((option) => getOptionText(option).includes(currentQuery.toLowerCase()))
|
|
53
|
+
: options;
|
|
54
|
+
const isWindowed = isVirtualized ??
|
|
55
|
+
filtered.length > AUTO_VIRTUALIZE_THRESHOLD;
|
|
56
|
+
const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, triggerId, } = useAnchoredOverlay({
|
|
57
|
+
// Escape is handled here so it can clear the query before closing.
|
|
58
|
+
isEscapeDismissable: false,
|
|
59
|
+
isVisible,
|
|
60
|
+
maxHeightPx: 384,
|
|
61
|
+
offsetValue: 4,
|
|
62
|
+
onDismiss,
|
|
63
|
+
role: "listbox",
|
|
64
|
+
trigger,
|
|
65
|
+
});
|
|
66
|
+
// The panel wraps an input *and* a listbox, so the wrapper is a
|
|
67
|
+
// plain div: the `role="listbox"`/`id` `useRole` produced go on the
|
|
68
|
+
// inner list, which is what the trigger's and input's `aria-controls`
|
|
69
|
+
// then resolve to.
|
|
70
|
+
const { id: listboxId, role: _discardedPanelRole, ...floatingProps } = getFloatingProps();
|
|
71
|
+
const setQuery = (nextQuery) => {
|
|
72
|
+
if (!isQueryControlled) {
|
|
73
|
+
setInternalQuery(nextQuery);
|
|
74
|
+
}
|
|
75
|
+
setActiveIndex(0);
|
|
76
|
+
onQueryChange?.(nextQuery);
|
|
77
|
+
};
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (isVisible) {
|
|
80
|
+
inputElement.current?.focus();
|
|
81
|
+
}
|
|
82
|
+
}, [isVisible]);
|
|
83
|
+
const rowVirtualizer = useVirtualizer({
|
|
84
|
+
count: filtered.length,
|
|
85
|
+
estimateSize: () => 36,
|
|
86
|
+
getScrollElement: () => listElement.current,
|
|
87
|
+
overscan: 8,
|
|
88
|
+
});
|
|
89
|
+
const optionDomId = (value) => `${listboxId}-opt-${value}`;
|
|
90
|
+
// `activeIndex` is seeded at 0 on open and on every query reset, which
|
|
91
|
+
// can land on a disabled option; arrowing skips disabled but the seed
|
|
92
|
+
// does not. Resolve to the nearest enabled row and drive the highlight,
|
|
93
|
+
// `aria-activedescendant`, arrow moves and commit off *this* — never the
|
|
94
|
+
// raw index — so a disabled option is neither announced nor committable.
|
|
95
|
+
const resolvedActiveIndex = filtered.length === 0
|
|
96
|
+
? -1
|
|
97
|
+
: filtered[activeIndex] !== undefined &&
|
|
98
|
+
!filtered[activeIndex]?.isDisabled
|
|
99
|
+
? activeIndex
|
|
100
|
+
: nextEnabledIndex(filtered, -1, 1);
|
|
101
|
+
const activeValue = resolvedActiveIndex < 0
|
|
102
|
+
? undefined
|
|
103
|
+
: filtered[resolvedActiveIndex]?.value;
|
|
104
|
+
const commitValue = (value) => {
|
|
105
|
+
if (isMultiple) {
|
|
106
|
+
setSelected((previous) => previous.includes(value)
|
|
107
|
+
? previous.filter((one) => one !== value)
|
|
108
|
+
: [...previous, value]);
|
|
109
|
+
onSelect(value);
|
|
110
|
+
// Multi-select stays open and clears the query for the next one.
|
|
111
|
+
setQuery("");
|
|
112
|
+
inputElement.current?.focus();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setSelected([value]);
|
|
116
|
+
onSelect(value);
|
|
117
|
+
onDismiss();
|
|
118
|
+
};
|
|
119
|
+
const handleKeyDown = (keyEvent) => {
|
|
120
|
+
if (keyEvent.key === "ArrowDown") {
|
|
121
|
+
keyEvent.preventDefault();
|
|
122
|
+
setActiveIndex(nextEnabledIndex(filtered, resolvedActiveIndex, 1));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (keyEvent.key === "ArrowUp") {
|
|
126
|
+
keyEvent.preventDefault();
|
|
127
|
+
setActiveIndex(nextEnabledIndex(filtered, resolvedActiveIndex, -1));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (keyEvent.key === "Enter" ||
|
|
131
|
+
// Tab-to-accept (`PathPicker`): the active suggestion is taken.
|
|
132
|
+
// Single-select only — in multi-select `commitValue` refocuses the
|
|
133
|
+
// input, so accepting on Tab would trap forward focus in the field.
|
|
134
|
+
(keyEvent.key === "Tab" &&
|
|
135
|
+
!keyEvent.shiftKey &&
|
|
136
|
+
!isMultiple &&
|
|
137
|
+
activeValue !== undefined)) {
|
|
138
|
+
if (activeValue !== undefined) {
|
|
139
|
+
keyEvent.preventDefault();
|
|
140
|
+
commitValue(activeValue);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Creatable: Enter on a query with no match commits it raw.
|
|
144
|
+
if (isCreatable &&
|
|
145
|
+
keyEvent.key === "Enter" &&
|
|
146
|
+
currentQuery.trim().length > 0) {
|
|
147
|
+
keyEvent.preventDefault();
|
|
148
|
+
commitValue(currentQuery.trim());
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (keyEvent.key === "Escape") {
|
|
153
|
+
// Clears the query first, closes only when it is already empty.
|
|
154
|
+
if (currentQuery.length > 0) {
|
|
155
|
+
keyEvent.preventDefault();
|
|
156
|
+
setQuery("");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
onDismiss();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// Keep the active row in view — a real bug in every fleet picker:
|
|
163
|
+
// arrowing past the panel edge did not scroll.
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (!isVisible) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (isWindowed) {
|
|
169
|
+
rowVirtualizer.scrollToIndex(resolvedActiveIndex);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (activeValue !== undefined) {
|
|
173
|
+
listElement.current
|
|
174
|
+
?.querySelector(`#${CSS.escape(`${listboxId}-opt-${activeValue}`)}`)
|
|
175
|
+
?.scrollIntoView({ block: "nearest" });
|
|
176
|
+
}
|
|
177
|
+
}, [
|
|
178
|
+
resolvedActiveIndex,
|
|
179
|
+
activeValue,
|
|
180
|
+
isVisible,
|
|
181
|
+
isWindowed,
|
|
182
|
+
listboxId,
|
|
183
|
+
rowVirtualizer.scrollToIndex,
|
|
184
|
+
]);
|
|
185
|
+
const renderOption = (option, index) => (_jsx(ComboboxOption, { id: optionDomId(option.value), isActive: index === resolvedActiveIndex, isSelected: selected.includes(option.value), item: option, onSelect: commitValue, posInSet: isWindowed ? index + 1 : undefined, setSize: isWindowed ? filtered.length : undefined }, option.value));
|
|
186
|
+
return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context, initialFocus: inputElement, modal: false, children: _jsxs("div", { ...floatingProps, className: toClassName(PANEL_SURFACE_CLASS, "z-[var(--layer-modal)] flex min-w-64 flex-col overflow-hidden", className), ref: setFloating, style: floatingStyles, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1 border-border-subtle border-b p-2", children: [isMultiple
|
|
187
|
+
? selected.map((value) => {
|
|
188
|
+
const chip = options.find((option) => option.value === value);
|
|
189
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-sm bg-intent-neutral-surface px-1.5 py-0.5 text-content-primary text-xs", children: [chip?.textValue ?? value, _jsx("button", { "aria-label": `Remove ${chip?.textValue ?? value}`, className: "cursor-pointer text-content-secondary hover:text-content-primary", onClick: () => {
|
|
190
|
+
setSelected((previous) => previous.filter((one) => one !== value));
|
|
191
|
+
// Mirror the list toggle-off: the parent
|
|
192
|
+
// tracks selection through `onSelect`, so a
|
|
193
|
+
// chip removal must report the same value.
|
|
194
|
+
onSelect(value);
|
|
195
|
+
}, type: "button", children: "\u2715" })] }, value));
|
|
196
|
+
})
|
|
197
|
+
: null, _jsx("input", { "aria-activedescendant": activeValue === undefined
|
|
198
|
+
? undefined
|
|
199
|
+
: optionDomId(activeValue), "aria-autocomplete": "list", "aria-controls": listboxId, "aria-expanded": "true", "aria-labelledby": triggerId, className: "min-w-24 flex-1 bg-transparent text-content-primary text-sm outline-none placeholder:text-content-muted", onChange: (changeEvent) => {
|
|
200
|
+
setQuery(changeEvent.target.value);
|
|
201
|
+
}, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputElement, role: "combobox", type: "text", value: currentQuery })] }), _jsx("div", { "aria-labelledby": triggerId, className: "min-h-0 flex-1 overflow-auto p-1", id: listboxId, ref: listElement, role: "listbox", children: isLoading ? (_jsx("p", { className: "px-2 py-3 text-center text-content-secondary text-sm", children: "Loading\u2026" })) : error ? (_jsx("div", { className: "px-2 py-3 text-center text-intent-danger-content text-sm", children: error })) : filtered.length === 0 ? (_jsx("p", { className: "px-2 py-3 text-center text-content-secondary text-sm", children: emptyLabel })) : isWindowed ? (_jsx("div", { className: "relative w-full", style: {
|
|
202
|
+
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
203
|
+
}, children: rowVirtualizer
|
|
204
|
+
.getVirtualItems()
|
|
205
|
+
.map((virtualRow) => {
|
|
206
|
+
const option = filtered[virtualRow.index];
|
|
207
|
+
if (!option) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return (_jsx("div", { className: "absolute start-0 top-0 w-full", style: {
|
|
211
|
+
height: `${virtualRow.size}px`,
|
|
212
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
213
|
+
}, children: renderOption(option, virtualRow.index) }, option.value));
|
|
214
|
+
}) })) : (filtered.map((option, index) => renderOption(option, index))) }), footer ? (_jsx("div", { className: "shrink-0 border-border-subtle border-t p-2 text-content-secondary text-xs", children: footer })) : null] }) }) })) : null] }));
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=Combobox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Combobox.js","sourceRoot":"","sources":["../../src/Combobox/Combobox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ListboxItem } from "../Listbox/Listbox.tsx";
|
|
3
|
+
export type ComboboxOptionProps = {
|
|
4
|
+
/** The stable DOM id `aria-activedescendant` points at. */
|
|
5
|
+
id: string;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
item: ListboxItem;
|
|
9
|
+
onSelect: (value: string) => void;
|
|
10
|
+
/**
|
|
11
|
+
* `aria-posinset`/`aria-setsize`, required when the list is
|
|
12
|
+
* virtualized: most of it is not in the DOM, so a screen reader
|
|
13
|
+
* would otherwise announce "2 of 12" for a 4,000-item list.
|
|
14
|
+
*/
|
|
15
|
+
posInSet?: number;
|
|
16
|
+
setSize?: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* `role="option"` with **`tabIndex={-1}`**, and both halves matter.
|
|
20
|
+
* The APG combobox keeps focus in the text input and tracks the
|
|
21
|
+
* active option with `aria-activedescendant`, so no option is ever a
|
|
22
|
+
* tab stop — `tabIndex={-1}` is that. It is a `<button>` (which
|
|
23
|
+
* ARIA-in-HTML allows `role="option"` on, and `Listbox` uses too)
|
|
24
|
+
* rather than a `<div>`, so `onClick` carries its own keyboard route
|
|
25
|
+
* and the package keeps its no-Biome-suppression record.
|
|
26
|
+
*
|
|
27
|
+
* The one caveat is not to run `expectAgentDrivable` on an individual
|
|
28
|
+
* option: its roving-tabindex rule would (correctly) reject a native
|
|
29
|
+
* control with `tabIndex={-1}`, and a combobox legitimately has zero
|
|
30
|
+
* tab stops in its popup. Tests query the options with `getByRole`.
|
|
31
|
+
*
|
|
32
|
+
* "Active" is a visual state only — the option the arrow keys have
|
|
33
|
+
* moved to, highlighted because nothing here holds real focus.
|
|
34
|
+
*/
|
|
35
|
+
export declare const ComboboxOption: ({ id, isActive, isSelected, item, onSelect, posInSet, setSize, }: ComboboxOptionProps) => ReactNode;
|
|
36
|
+
//# sourceMappingURL=ComboboxOption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComboboxOption.d.ts","sourceRoot":"","sources":["../../src/Combobox/ComboboxOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,mBAAmB,GAAG;IAChC,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAI,kEAQ5B,mBAAmB,KAAG,SAyCxB,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { toClassName } from "../toClassName.js";
|
|
3
|
+
/**
|
|
4
|
+
* `role="option"` with **`tabIndex={-1}`**, and both halves matter.
|
|
5
|
+
* The APG combobox keeps focus in the text input and tracks the
|
|
6
|
+
* active option with `aria-activedescendant`, so no option is ever a
|
|
7
|
+
* tab stop — `tabIndex={-1}` is that. It is a `<button>` (which
|
|
8
|
+
* ARIA-in-HTML allows `role="option"` on, and `Listbox` uses too)
|
|
9
|
+
* rather than a `<div>`, so `onClick` carries its own keyboard route
|
|
10
|
+
* and the package keeps its no-Biome-suppression record.
|
|
11
|
+
*
|
|
12
|
+
* The one caveat is not to run `expectAgentDrivable` on an individual
|
|
13
|
+
* option: its roving-tabindex rule would (correctly) reject a native
|
|
14
|
+
* control with `tabIndex={-1}`, and a combobox legitimately has zero
|
|
15
|
+
* tab stops in its popup. Tests query the options with `getByRole`.
|
|
16
|
+
*
|
|
17
|
+
* "Active" is a visual state only — the option the arrow keys have
|
|
18
|
+
* moved to, highlighted because nothing here holds real focus.
|
|
19
|
+
*/
|
|
20
|
+
export const ComboboxOption = ({ id, isActive, isSelected, item, onSelect, posInSet, setSize, }) => {
|
|
21
|
+
const { isDisabled = false, label, value } = item;
|
|
22
|
+
return (_jsxs("button", { "aria-posinset": posInSet, "aria-selected": isSelected, "aria-setsize": setSize, className: toClassName("flex w-full cursor-pointer items-center justify-between gap-2 rounded-sm bg-transparent px-2 py-1.5 text-start text-content-primary text-sm", isActive && "bg-intent-neutral-surface", isSelected && "bg-intent-accent-surface", isDisabled &&
|
|
23
|
+
"cursor-not-allowed text-content-disabled"), disabled: isDisabled, id: id,
|
|
24
|
+
// `onMouseDown` rather than `onClick`: the input holds focus, and
|
|
25
|
+
// a click would blur it first. Prevented default keeps the caret
|
|
26
|
+
// in the input while the choice is made.
|
|
27
|
+
onMouseDown: (mouseEvent) => {
|
|
28
|
+
mouseEvent.preventDefault();
|
|
29
|
+
onSelect(value);
|
|
30
|
+
}, role: "option", tabIndex: -1, type: "button", children: [label, isSelected ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-intent-accent-content", children: "\u2713" })) : null] }));
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=ComboboxOption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComboboxOption.js","sourceRoot":"","sources":["../../src/Combobox/ComboboxOption.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,EAAE,EACF,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,GACa,EAAa,EAAE;IACnC,MAAM,EAAE,UAAU,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEjD,OAAO,CACL,mCACiB,QAAQ,mBACR,UAAU,kBACX,OAAO,EACrB,SAAS,EAAE,WAAW,CACpB,6IAA6I,EAC7I,QAAQ,IAAI,2BAA2B,EACvC,UAAU,IAAI,0BAA0B,EACxC,UAAU;YACR,0CAA0C,CAC7C,EACD,QAAQ,EAAE,UAAU,EACpB,EAAE,EAAE,EAAE;QACN,kEAAkE;QAClE,iEAAiE;QACjE,yCAAyC;QACzC,WAAW,EAAE,CAAC,UAAU,EAAE,EAAE;YAC1B,UAAU,CAAC,cAAc,EAAE,CAAA;YAE3B,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,EACD,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,EACZ,IAAI,EAAC,QAAQ,aAEZ,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BACc,MAAM,EAClB,SAAS,EAAC,qCAAqC,uBAG1C,CACR,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type DialogSize = "full" | "lg" | "md" | "sm" | "xl";
|
|
3
|
+
export type DialogProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The scrolling body. Optional: a confirm dialog whose question is
|
|
6
|
+
* its `heading` and whose answers are its `footer` has no body, and
|
|
7
|
+
* the old chrome `Modal` (which extended `<dialog>`'s DOM props)
|
|
8
|
+
* allowed that — `Dialog` keeps it.
|
|
9
|
+
*/
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
/** The footer row. Buttons, usually. */
|
|
13
|
+
footer?: ReactNode;
|
|
14
|
+
heading: string;
|
|
15
|
+
headingLevel?: 2 | 3 | 4;
|
|
16
|
+
/**
|
|
17
|
+
* Escape and a backdrop press both close. Off for the one honest
|
|
18
|
+
* case — a destructive confirm the user has to answer.
|
|
19
|
+
*/
|
|
20
|
+
isDismissable?: boolean;
|
|
21
|
+
isVisible: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Every route out lands here: the close button, Escape, a backdrop
|
|
24
|
+
* press. Wire it to a `useVisibility()`'s `hide`.
|
|
25
|
+
*/
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
size?: DialogSize;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* `Modal` plus the chrome — heading, a Close button, a scrolling body,
|
|
31
|
+
* an optional footer, and five sizes. This is what `Modal` *was*
|
|
32
|
+
* before the M8 rebuild renamed it: a dialog is one kind of modal, so
|
|
33
|
+
* it takes the specific name and hands the generic one back to the
|
|
34
|
+
* base layer.
|
|
35
|
+
*
|
|
36
|
+
* Everything modal — the portal, the scrim, Escape and outside-press
|
|
37
|
+
* dismiss, the focus trap, the scroll lock, the stacking — is `Modal`'s
|
|
38
|
+
* (via `OverlayPanel`), so this component is just the box's contents
|
|
39
|
+
* and the size class it wants that box to be. `SIZE_CLASS` is composed
|
|
40
|
+
* over `Modal`'s default surface, which is why `full` carries
|
|
41
|
+
* `rounded-none` to square off the base's `rounded-lg` at the viewport
|
|
42
|
+
* edge.
|
|
43
|
+
*
|
|
44
|
+
* ### The accessible name
|
|
45
|
+
*
|
|
46
|
+
* `heading` is required and is a string, not a `ReactNode`. It names
|
|
47
|
+
* both the visible header and — through `aria-labelledby` on the
|
|
48
|
+
* modal — the dialog itself. A dialog with no name is announced as
|
|
49
|
+
* "dialog" and nothing else, which is what every hand-rolled modal in
|
|
50
|
+
* the inventory does.
|
|
51
|
+
*/
|
|
52
|
+
export declare const Dialog: ({ children, className, footer, heading, headingLevel, isDismissable, isVisible, onClose, size, }: DialogProps) => ReactNode;
|
|
53
|
+
//# sourceMappingURL=Dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,kGAUpB,WAAW,KAAG,SA4ChB,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useUniqueId } from "@charcuterie/logic";
|
|
3
|
+
import { Button } from "../Button/Button.js";
|
|
4
|
+
import { Modal } from "../Modal/Modal.js";
|
|
5
|
+
import { toClassName } from "../toClassName.js";
|
|
6
|
+
/**
|
|
7
|
+
* `85dvh` rather than `85vh` — on a phone the two differ by the
|
|
8
|
+
* address bar, and a dialog whose footer is under it is a dialog
|
|
9
|
+
* with no buttons.
|
|
10
|
+
*/
|
|
11
|
+
const SIZE_CLASS = {
|
|
12
|
+
sm: "max-h-[85dvh] w-full max-w-sm",
|
|
13
|
+
md: "max-h-[85dvh] w-full max-w-md",
|
|
14
|
+
lg: "max-h-[85dvh] w-full max-w-2xl",
|
|
15
|
+
// M5 added this, because the scale jumped from 42rem straight to
|
|
16
|
+
// the whole viewport and rip-deck's capture tail lands in the
|
|
17
|
+
// gap: a MakeMKV robot log is fixed-width `MSG:5072,0,1,"…"`
|
|
18
|
+
// lines that wrap badly at 42rem, and a full-screen dialog over a
|
|
19
|
+
// nine-bay dashboard hides the thing being diagnosed. 56rem is
|
|
20
|
+
// what that app's hand-rolled `<dialog>` used before the swap.
|
|
21
|
+
xl: "max-h-[85dvh] w-full max-w-4xl",
|
|
22
|
+
full: "h-[100dvh] max-h-[100dvh] w-full max-w-full rounded-none",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* `Modal` plus the chrome — heading, a Close button, a scrolling body,
|
|
26
|
+
* an optional footer, and five sizes. This is what `Modal` *was*
|
|
27
|
+
* before the M8 rebuild renamed it: a dialog is one kind of modal, so
|
|
28
|
+
* it takes the specific name and hands the generic one back to the
|
|
29
|
+
* base layer.
|
|
30
|
+
*
|
|
31
|
+
* Everything modal — the portal, the scrim, Escape and outside-press
|
|
32
|
+
* dismiss, the focus trap, the scroll lock, the stacking — is `Modal`'s
|
|
33
|
+
* (via `OverlayPanel`), so this component is just the box's contents
|
|
34
|
+
* and the size class it wants that box to be. `SIZE_CLASS` is composed
|
|
35
|
+
* over `Modal`'s default surface, which is why `full` carries
|
|
36
|
+
* `rounded-none` to square off the base's `rounded-lg` at the viewport
|
|
37
|
+
* edge.
|
|
38
|
+
*
|
|
39
|
+
* ### The accessible name
|
|
40
|
+
*
|
|
41
|
+
* `heading` is required and is a string, not a `ReactNode`. It names
|
|
42
|
+
* both the visible header and — through `aria-labelledby` on the
|
|
43
|
+
* modal — the dialog itself. A dialog with no name is announced as
|
|
44
|
+
* "dialog" and nothing else, which is what every hand-rolled modal in
|
|
45
|
+
* the inventory does.
|
|
46
|
+
*/
|
|
47
|
+
export const Dialog = ({ children, className, footer, heading, headingLevel = 2, isDismissable = true, isVisible, onClose, size = "md", }) => {
|
|
48
|
+
const headingId = useUniqueId();
|
|
49
|
+
const Heading = `h${headingLevel}`;
|
|
50
|
+
return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-start justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children }), footer ? (_jsx("footer", { className: "flex shrink-0 flex-wrap justify-end gap-2 border-t border-border-subtle p-4", children: footer })) : null] }));
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=Dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,mFAAmF,aACnG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAET,cAAK,SAAS,EAAC,kCAAkC,YAC9C,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -37,13 +37,13 @@ export type LightboxProps = {
|
|
|
37
37
|
/**
|
|
38
38
|
* A thumbnail that opens its own full-size view — the one image
|
|
39
39
|
* interaction three apps hand-roll and none get the focus trap or
|
|
40
|
-
* the scroll lock right. It is a **skin over `
|
|
41
|
-
* dialog: `
|
|
42
|
-
*
|
|
43
|
-
* makes those correct in every browser the fleet
|
|
44
|
-
* adds is the image-specific part — a `cursor-zoom-in`
|
|
45
|
-
* an `object-contain` image clamped to the viewport so a
|
|
46
|
-
* and a 16:9 still both land whole.
|
|
40
|
+
* the scroll lock right. It is a **skin over `Dialog`**, not a second
|
|
41
|
+
* dialog: `Dialog` (through `Modal`/`OverlayPanel`) owns the portal,
|
|
42
|
+
* the scrim, Escape, the focus trap and `lockScrollBehind`, and
|
|
43
|
+
* everything that makes those correct in every browser the fleet
|
|
44
|
+
* runs. What this adds is the image-specific part — a `cursor-zoom-in`
|
|
45
|
+
* trigger and an `object-contain` image clamped to the viewport so a
|
|
46
|
+
* 2:3 poster and a 16:9 still both land whole.
|
|
47
47
|
*
|
|
48
48
|
* ### Controlled and not, one component
|
|
49
49
|
*
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useVisibility } from "@charcuterie/logic";
|
|
3
|
-
import {
|
|
3
|
+
import { Dialog } from "../Dialog/Dialog.js";
|
|
4
4
|
import { toClassName } from "../toClassName.js";
|
|
5
5
|
/**
|
|
6
6
|
* A thumbnail that opens its own full-size view — the one image
|
|
7
7
|
* interaction three apps hand-roll and none get the focus trap or
|
|
8
|
-
* the scroll lock right. It is a **skin over `
|
|
9
|
-
* dialog: `
|
|
10
|
-
*
|
|
11
|
-
* makes those correct in every browser the fleet
|
|
12
|
-
* adds is the image-specific part — a `cursor-zoom-in`
|
|
13
|
-
* an `object-contain` image clamped to the viewport so a
|
|
14
|
-
* and a 16:9 still both land whole.
|
|
8
|
+
* the scroll lock right. It is a **skin over `Dialog`**, not a second
|
|
9
|
+
* dialog: `Dialog` (through `Modal`/`OverlayPanel`) owns the portal,
|
|
10
|
+
* the scrim, Escape, the focus trap and `lockScrollBehind`, and
|
|
11
|
+
* everything that makes those correct in every browser the fleet
|
|
12
|
+
* runs. What this adds is the image-specific part — a `cursor-zoom-in`
|
|
13
|
+
* trigger and an `object-contain` image clamped to the viewport so a
|
|
14
|
+
* 2:3 poster and a 16:9 still both land whole.
|
|
15
15
|
*
|
|
16
16
|
* ### Controlled and not, one component
|
|
17
17
|
*
|
|
@@ -41,7 +41,7 @@ export const Lightbox = ({ alt, caption, className, heading, isOpen, onOpenChang
|
|
|
41
41
|
// `cursor-zoom-in` is the affordance a bare `<img>`
|
|
42
42
|
// never had, and the focus ring is the fleet's standard
|
|
43
43
|
// — the same one `MediaTile`'s link wears.
|
|
44
|
-
"group block cursor-zoom-in rounded-md outline-offset-2 focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-focus-ring", className), onClick: requestOpen, type: "button", children: thumbnail })), _jsx(
|
|
44
|
+
"group block cursor-zoom-in rounded-md outline-offset-2 focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-focus-ring", className), onClick: requestOpen, type: "button", children: thumbnail })), _jsx(Dialog, { heading: heading ?? alt, isVisible: isShown, onClose: requestClose, size: "xl", children: _jsxs("figure", { className: "flex flex-col items-center gap-3", children: [_jsx("img", { alt: alt,
|
|
45
45
|
// `object-contain`, not `cover`: the enlarged view must
|
|
46
46
|
// show the whole image, and the clamp is `dvh` for the
|
|
47
47
|
// same address-bar reason `Modal`'s is.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../src/Lightbox/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../src/Lightbox/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAsC/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,GAAG,EACH,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,YAAY,EACZ,GAAG,EACH,SAAS,GACK,EAAa,EAAE;IAC7B,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,CAAA;IAEzC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAEjD,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC,CAAA;IAED,OAAO,CACL,8BACG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAChC,+BAMc,WAAW,OAAO,IAAI,GAAG,EAAE,EACvC,SAAS,EAAE,WAAW;gBACpB,oDAAoD;gBACpD,wDAAwD;gBACxD,2CAA2C;gBAC3C,uKAAuK,EACvK,SAAS,CACV,EACD,OAAO,EAAE,WAAW,EACpB,IAAI,EAAC,QAAQ,YAEZ,SAAS,GACH,CACV,EAED,KAAC,MAAM,IACL,OAAO,EAAE,OAAO,IAAI,GAAG,EACvB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAC,IAAI,YAET,kBAAQ,SAAS,EAAC,kCAAkC,aAClD,cACE,GAAG,EAAE,GAAG;4BACR,wDAAwD;4BACxD,uDAAuD;4BACvD,wCAAwC;4BACxC,SAAS,EAAC,2DAA2D,EACrE,GAAG,EAAE,GAAG,GACR,EAED,OAAO,CAAC,CAAC,CAAC,CACT,qBAAY,SAAS,EAAC,wCAAwC,YAC3D,OAAO,GACG,CACd,CAAC,CAAC,CAAC,IAAI,IACD,GACF,IACR,CACJ,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Placement } from "@floating-ui/react";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
|
+
export type ListboxItem = {
|
|
4
|
+
isDisabled?: boolean;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Required when `label` is rich (an icon, two lines, a badge): it is
|
|
8
|
+
* the type-ahead target and the accessible name a screen reader
|
|
9
|
+
* reads. When `label` is a plain string it is used as the fallback.
|
|
10
|
+
*/
|
|
11
|
+
textValue?: string;
|
|
12
|
+
value: string;
|
|
13
|
+
};
|
|
14
|
+
export type ListboxProps = {
|
|
15
|
+
className?: string;
|
|
16
|
+
isVisible: boolean;
|
|
17
|
+
/** Outside press and Escape both land here. */
|
|
18
|
+
onDismiss: () => void;
|
|
19
|
+
onSelect: (value: string) => void;
|
|
20
|
+
options: readonly ListboxItem[];
|
|
21
|
+
placement?: Placement;
|
|
22
|
+
/** The initial seed. The live selection is the listbox's after that. */
|
|
23
|
+
selectedValue?: string;
|
|
24
|
+
/** The control the listbox hangs off. **Cloned, not wrapped.** */
|
|
25
|
+
trigger: ReactElement;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The single-select fancy dropdown — the rich-option sibling of the
|
|
29
|
+
* native `Select`. `Select` keeps type-ahead, the mobile wheel, form
|
|
30
|
+
* submission and autofill; `Listbox` is for the cases `<option>`
|
|
31
|
+
* cannot render: an icon, two lines, a trailing badge.
|
|
32
|
+
*
|
|
33
|
+
* A listbox is **not** a menu, and that distinction is the whole
|
|
34
|
+
* reason it is a separate component from `Menu`. An `option` *is*
|
|
35
|
+
* something you are choosing — so it carries `aria-selected`, it
|
|
36
|
+
* type-aheads (which the APG requires of a listbox and `Menu`
|
|
37
|
+
* deliberately omits), and a screen reader announces "listbox,
|
|
38
|
+
* selected, 2 of 4". A `menuitem` only ever *does* something.
|
|
39
|
+
*
|
|
40
|
+
* ### Two kinds of state, composed
|
|
41
|
+
*
|
|
42
|
+
* `useRovingFocus` owns which option is tabbable (exactly one, so Tab
|
|
43
|
+
* enters and leaves the group once and the arrow keys move within it),
|
|
44
|
+
* and `useSinglePicker` owns which option is chosen. They are the same
|
|
45
|
+
* two kinds `Tabs` composes, and they never disturb each other:
|
|
46
|
+
* arrowing moves focus without selecting, Enter selects the focused
|
|
47
|
+
* option.
|
|
48
|
+
*
|
|
49
|
+
* ### Portalled, and named by its trigger
|
|
50
|
+
*
|
|
51
|
+
* Like `Menu`, the panel portals to `document.body` and `useRole` names
|
|
52
|
+
* it by pointing `aria-labelledby` at the trigger — so there is no
|
|
53
|
+
* `heading` prop; the button that opens it is its label.
|
|
54
|
+
*/
|
|
55
|
+
export declare const Listbox: ({ className, isVisible, onDismiss, onSelect, options, placement, selectedValue, trigger, }: ListboxProps) => ReactNode;
|
|
56
|
+
//# sourceMappingURL=Listbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Listbox.d.ts","sourceRoot":"","sources":["../../src/Listbox/Listbox.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,OAAO,GAAI,4FASrB,YAAY,KAAG,SA+MjB,CAAA"}
|