@flanksource/clicky-ui 0.2.11 → 0.2.13
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/components/Combobox.cjs +47 -33
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts +12 -0
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +48 -34
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/IconButton.cjs +26 -0
- package/dist/components/IconButton.cjs.map +1 -0
- package/dist/components/IconButton.d.ts +25 -0
- package/dist/components/IconButton.d.ts.map +1 -0
- package/dist/components/IconButton.js +26 -0
- package/dist/components/IconButton.js.map +1 -0
- package/dist/components/JsonSchemaForm.cjs +1 -1
- package/dist/components/JsonSchemaForm.cjs.map +1 -1
- package/dist/components/JsonSchemaForm.js +1 -1
- package/dist/components/JsonSchemaForm.js.map +1 -1
- package/dist/components/SecretKeySelector.cjs +151 -0
- package/dist/components/SecretKeySelector.cjs.map +1 -0
- package/dist/components/SecretKeySelector.d.ts +47 -0
- package/dist/components/SecretKeySelector.d.ts.map +1 -0
- package/dist/components/SecretKeySelector.js +151 -0
- package/dist/components/SecretKeySelector.js.map +1 -0
- package/dist/components/WorkloadPicker.cjs +144 -0
- package/dist/components/WorkloadPicker.cjs.map +1 -0
- package/dist/components/WorkloadPicker.d.ts +52 -0
- package/dist/components/WorkloadPicker.d.ts.map +1 -0
- package/dist/components/WorkloadPicker.js +144 -0
- package/dist/components/WorkloadPicker.js.map +1 -0
- package/dist/components/icon-menu-picker.cjs +83 -112
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.d.ts.map +1 -1
- package/dist/components/icon-menu-picker.js +85 -114
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components/json-schema-form-fields.cjs +9 -7
- package/dist/components/json-schema-form-fields.cjs.map +1 -1
- package/dist/components/json-schema-form-fields.d.ts.map +1 -1
- package/dist/components/json-schema-form-fields.js +9 -7
- package/dist/components/json-schema-form-fields.js.map +1 -1
- package/dist/components/json-schema-form-types.d.ts +8 -4
- package/dist/components/json-schema-form-types.d.ts.map +1 -1
- package/dist/components.cjs +10 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +11 -1
- package/dist/components.js.map +1 -1
- package/dist/data/Clicky.cjs +1 -1
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.js +1 -1
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/LogDetails.cjs +4 -2
- package/dist/data/LogDetails.cjs.map +1 -1
- package/dist/data/LogDetails.js +4 -2
- package/dist/data/LogDetails.js.map +1 -1
- package/dist/data/Version.cjs +42 -0
- package/dist/data/Version.cjs.map +1 -0
- package/dist/data/Version.d.ts +36 -0
- package/dist/data/Version.d.ts.map +1 -0
- package/dist/data/Version.js +42 -0
- package/dist/data/Version.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.js +165 -0
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
- package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.js +78 -0
- package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
- package/dist/data/test-runner/TestFilterBar.cjs +108 -0
- package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
- package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
- package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
- package/dist/data/test-runner/TestFilterBar.js +108 -0
- package/dist/data/test-runner/TestFilterBar.js.map +1 -0
- package/dist/data/test-runner/TestRunSummary.cjs +81 -0
- package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
- package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
- package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunSummary.js +81 -0
- package/dist/data/test-runner/TestRunSummary.js.map +1 -0
- package/dist/data/test-runner/TestRunner.cjs +143 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -0
- package/dist/data/test-runner/TestRunner.d.ts +42 -0
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.js +143 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -0
- package/dist/data/test-runner/TestTree.cjs +42 -0
- package/dist/data/test-runner/TestTree.cjs.map +1 -0
- package/dist/data/test-runner/TestTree.d.ts +9 -0
- package/dist/data/test-runner/TestTree.d.ts.map +1 -0
- package/dist/data/test-runner/TestTree.js +42 -0
- package/dist/data/test-runner/TestTree.js.map +1 -0
- package/dist/data/test-runner/TestTreeNode.cjs +99 -0
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
- package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
- package/dist/data/test-runner/TestTreeNode.js +99 -0
- package/dist/data/test-runner/TestTreeNode.js.map +1 -0
- package/dist/data/test-runner/adapter.cjs +57 -0
- package/dist/data/test-runner/adapter.cjs.map +1 -0
- package/dist/data/test-runner/adapter.d.ts +60 -0
- package/dist/data/test-runner/adapter.d.ts.map +1 -0
- package/dist/data/test-runner/adapter.js +57 -0
- package/dist/data/test-runner/adapter.js.map +1 -0
- package/dist/data/test-runner/context.cjs +15 -0
- package/dist/data/test-runner/context.cjs.map +1 -0
- package/dist/data/test-runner/context.d.ts +40 -0
- package/dist/data/test-runner/context.d.ts.map +1 -0
- package/dist/data/test-runner/context.js +15 -0
- package/dist/data/test-runner/context.js.map +1 -0
- package/dist/data/test-runner/filterState.cjs +47 -0
- package/dist/data/test-runner/filterState.cjs.map +1 -0
- package/dist/data/test-runner/filterState.d.ts +14 -0
- package/dist/data/test-runner/filterState.d.ts.map +1 -0
- package/dist/data/test-runner/filterState.js +47 -0
- package/dist/data/test-runner/filterState.js.map +1 -0
- package/dist/data/test-runner/frameworkIcon.cjs +27 -0
- package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
- package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
- package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
- package/dist/data/test-runner/frameworkIcon.js +27 -0
- package/dist/data/test-runner/frameworkIcon.js.map +1 -0
- package/dist/data/test-runner/index.d.ts +14 -0
- package/dist/data/test-runner/index.d.ts.map +1 -0
- package/dist/data/test-runner/status.cjs +234 -0
- package/dist/data/test-runner/status.cjs.map +1 -0
- package/dist/data/test-runner/status.d.ts +52 -0
- package/dist/data/test-runner/status.d.ts.map +1 -0
- package/dist/data/test-runner/status.js +234 -0
- package/dist/data/test-runner/status.js.map +1 -0
- package/dist/data/test-runner/types.d.ts +148 -0
- package/dist/data/test-runner/types.d.ts.map +1 -0
- package/dist/data/version-info.cjs +26 -0
- package/dist/data/version-info.cjs.map +1 -0
- package/dist/data/version-info.d.ts +11 -0
- package/dist/data/version-info.d.ts.map +1 -0
- package/dist/data/version-info.js +26 -0
- package/dist/data/version-info.js.map +1 -0
- package/dist/data.cjs +49 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +50 -1
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +59 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/SplitPane.cjs +34 -27
- package/dist/layout/SplitPane.cjs.map +1 -1
- package/dist/layout/SplitPane.d.ts.map +1 -1
- package/dist/layout/SplitPane.js +34 -27
- package/dist/layout/SplitPane.js.map +1 -1
- package/dist/overlay/DropdownMenu.cjs +57 -30
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +57 -30
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/overlay/Modal.cjs +62 -6
- package/dist/overlay/Modal.cjs.map +1 -1
- package/dist/overlay/Modal.d.ts +19 -1
- package/dist/overlay/Modal.d.ts.map +1 -1
- package/dist/overlay/Modal.js +62 -6
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/rpc/CommandForm.cjs +2 -2
- package/dist/rpc/CommandForm.cjs.map +1 -1
- package/dist/rpc/CommandForm.d.ts.map +1 -1
- package/dist/rpc/CommandForm.js +3 -3
- package/dist/rpc/CommandForm.js.map +1 -1
- package/dist/rpc/formMetadata.cjs +5 -4
- package/dist/rpc/formMetadata.cjs.map +1 -1
- package/dist/rpc/formMetadata.d.ts.map +1 -1
- package/dist/rpc/formMetadata.js +6 -5
- package/dist/rpc/formMetadata.js.map +1 -1
- package/dist/rpc/types.cjs +4 -0
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +10 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js +5 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -1
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const react = require("react");
|
|
5
|
+
const react$1 = require("@floating-ui/react");
|
|
5
6
|
const utils = require("../lib/utils.cjs");
|
|
6
7
|
const Icon = require("../data/Icon.cjs");
|
|
7
8
|
const button = require("./button.cjs");
|
|
9
|
+
const IconButton = require("./IconButton.cjs");
|
|
8
10
|
const UiChevronDown = require("../icons/components/UiChevronDown.cjs");
|
|
9
11
|
const UiCheck = require("../icons/components/UiCheck.cjs");
|
|
10
12
|
function IconMenuPickerInner({
|
|
@@ -18,137 +20,104 @@ function IconMenuPickerInner({
|
|
|
18
20
|
className,
|
|
19
21
|
triggerClassName,
|
|
20
22
|
menuClassName
|
|
21
|
-
},
|
|
22
|
-
const rootRef = react.useRef(null);
|
|
23
|
-
const triggerRef = react.useRef(null);
|
|
24
|
-
const itemRefs = react.useRef([]);
|
|
23
|
+
}, forwardedRef) {
|
|
25
24
|
const [open, setOpen] = react.useState(false);
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
),
|
|
25
|
+
const [activeIndex, setActiveIndex] = react.useState(null);
|
|
26
|
+
const listRef = react.useRef([]);
|
|
27
|
+
const labelsRef = react.useRef(options.map((o) => o.label));
|
|
28
|
+
labelsRef.current = options.map((o) => o.label);
|
|
29
|
+
const selectedIndex = react.useMemo(
|
|
30
|
+
() => options.findIndex((option) => option.value === value),
|
|
32
31
|
[options, value]
|
|
33
32
|
);
|
|
34
|
-
const
|
|
35
|
-
|
|
33
|
+
const { refs, floatingStyles, context } = react$1.useFloating({
|
|
34
|
+
open,
|
|
35
|
+
onOpenChange: setOpen,
|
|
36
|
+
placement: "bottom-start",
|
|
37
|
+
whileElementsMounted: react$1.autoUpdate,
|
|
38
|
+
// offset(6) reproduces the previous 0.375rem gap; flip opens the menu
|
|
39
|
+
// upward when the trigger is near the bottom edge; shift slides it back
|
|
40
|
+
// on-screen at the left/right edges. The 8px padding keeps a gutter.
|
|
41
|
+
middleware: [react$1.offset(6), react$1.flip({ padding: 8 }), react$1.shift({ padding: 8 })]
|
|
42
|
+
});
|
|
43
|
+
const click = react$1.useClick(context);
|
|
44
|
+
const dismiss = react$1.useDismiss(context);
|
|
45
|
+
const role = react$1.useRole(context, { role: "menu" });
|
|
46
|
+
const listNav = react$1.useListNavigation(context, {
|
|
47
|
+
listRef,
|
|
48
|
+
activeIndex,
|
|
49
|
+
selectedIndex,
|
|
50
|
+
onNavigate: setActiveIndex,
|
|
51
|
+
loop: true
|
|
52
|
+
});
|
|
53
|
+
const typeahead = react$1.useTypeahead(context, {
|
|
54
|
+
listRef: labelsRef,
|
|
55
|
+
activeIndex,
|
|
56
|
+
onMatch: setActiveIndex
|
|
57
|
+
});
|
|
58
|
+
const { getReferenceProps, getFloatingProps, getItemProps } = react$1.useInteractions([
|
|
59
|
+
click,
|
|
60
|
+
dismiss,
|
|
61
|
+
role,
|
|
62
|
+
listNav,
|
|
63
|
+
typeahead
|
|
64
|
+
]);
|
|
65
|
+
const referenceRef = react$1.useMergeRefs([refs.setReference, forwardedRef]);
|
|
66
|
+
const select = (next) => {
|
|
67
|
+
onChange(next);
|
|
36
68
|
setOpen(false);
|
|
37
|
-
if (returnFocus) (_a = triggerRef.current) == null ? void 0 : _a.focus();
|
|
38
|
-
}, []);
|
|
39
|
-
const select = react.useCallback(
|
|
40
|
-
(next) => {
|
|
41
|
-
onChange(next);
|
|
42
|
-
close(true);
|
|
43
|
-
},
|
|
44
|
-
[close, onChange]
|
|
45
|
-
);
|
|
46
|
-
react.useEffect(() => {
|
|
47
|
-
if (!open) return;
|
|
48
|
-
const onPointerDown = (event) => {
|
|
49
|
-
var _a;
|
|
50
|
-
if (event.button !== 0) return;
|
|
51
|
-
if (!((_a = rootRef.current) == null ? void 0 : _a.contains(event.target))) {
|
|
52
|
-
close(false);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
const onKeyDown = (event) => {
|
|
56
|
-
if (event.key === "Escape") {
|
|
57
|
-
event.preventDefault();
|
|
58
|
-
close(true);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
62
|
-
document.addEventListener("keydown", onKeyDown);
|
|
63
|
-
return () => {
|
|
64
|
-
document.removeEventListener("pointerdown", onPointerDown);
|
|
65
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
66
|
-
};
|
|
67
|
-
}, [close, open]);
|
|
68
|
-
react.useEffect(() => {
|
|
69
|
-
if (!open) return;
|
|
70
|
-
const node = itemRefs.current[activeIndex];
|
|
71
|
-
node == null ? void 0 : node.focus();
|
|
72
|
-
}, [activeIndex, open]);
|
|
73
|
-
const focusItem = (index) => {
|
|
74
|
-
var _a;
|
|
75
|
-
const wrapped = (index + options.length) % options.length;
|
|
76
|
-
(_a = itemRefs.current[wrapped]) == null ? void 0 : _a.focus();
|
|
77
69
|
};
|
|
78
|
-
const
|
|
79
|
-
const focusedIndex = itemRefs.current.findIndex((node) => node === document.activeElement);
|
|
80
|
-
if (event.key === "ArrowDown") {
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) + 1);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (event.key === "ArrowUp") {
|
|
86
|
-
event.preventDefault();
|
|
87
|
-
focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) - 1);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (event.key === "Home") {
|
|
91
|
-
event.preventDefault();
|
|
92
|
-
focusItem(0);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (event.key === "End") {
|
|
96
|
-
event.preventDefault();
|
|
97
|
-
focusItem(options.length - 1);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (event.key === "Tab") {
|
|
101
|
-
close(false);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
const onTriggerKeyDown = (event) => {
|
|
105
|
-
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
|
106
|
-
event.preventDefault();
|
|
107
|
-
setOpen(true);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const selected = options[activeIndex];
|
|
70
|
+
const selected = options[selectedIndex];
|
|
111
71
|
if (!selected) {
|
|
112
72
|
throw new Error(`IconMenuPicker: value "${value}" not found in options for ${ariaLabel}`);
|
|
113
73
|
}
|
|
114
|
-
|
|
115
|
-
|
|
74
|
+
const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;
|
|
75
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: referenceRef, className: utils.cn("relative inline-flex", showLabel && "w-full", className), children: [
|
|
76
|
+
showLabel ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
116
77
|
button.Button,
|
|
117
78
|
{
|
|
118
|
-
ref: triggerRef,
|
|
119
79
|
type: "button",
|
|
120
80
|
variant: "ghost",
|
|
121
|
-
size:
|
|
81
|
+
size: "default",
|
|
122
82
|
"aria-label": ariaLabel,
|
|
123
83
|
"aria-haspopup": "menu",
|
|
124
84
|
"aria-expanded": open,
|
|
125
|
-
title:
|
|
126
|
-
onClick: () => setOpen((current) => !current),
|
|
127
|
-
onKeyDown: onTriggerKeyDown,
|
|
85
|
+
title: triggerTitleText,
|
|
128
86
|
className: utils.cn(
|
|
129
|
-
"text-muted-foreground hover:text-foreground",
|
|
130
|
-
showLabel && "w-full justify-start",
|
|
87
|
+
"w-full justify-start text-muted-foreground hover:text-foreground",
|
|
131
88
|
triggerClassName
|
|
132
89
|
),
|
|
90
|
+
...getReferenceProps(),
|
|
133
91
|
children: [
|
|
134
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: selected.icon,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiChevronDown.UiChevronDown, className: "shrink-0 text-muted-foreground" })
|
|
138
|
-
] })
|
|
92
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: selected.icon, className: "shrink-0 text-foreground" }),
|
|
93
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left capitalize", children: `${ariaLabel}: ${selected.label}` }),
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiChevronDown.UiChevronDown, className: "shrink-0 text-muted-foreground" })
|
|
139
95
|
]
|
|
140
96
|
}
|
|
97
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
98
|
+
IconButton.IconButton,
|
|
99
|
+
{
|
|
100
|
+
icon: selected.icon,
|
|
101
|
+
label: ariaLabel,
|
|
102
|
+
title: triggerTitleText,
|
|
103
|
+
"aria-haspopup": "menu",
|
|
104
|
+
"aria-expanded": open,
|
|
105
|
+
className: triggerClassName,
|
|
106
|
+
...getReferenceProps()
|
|
107
|
+
}
|
|
141
108
|
),
|
|
142
|
-
open && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
109
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(react$1.FloatingPortal, { children: /* @__PURE__ */ jsxRuntime.jsx(react$1.FloatingFocusManager, { context, modal: false, initialFocus: selectedIndex, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
143
110
|
"div",
|
|
144
111
|
{
|
|
112
|
+
ref: refs.setFloating,
|
|
145
113
|
role: "menu",
|
|
146
114
|
"aria-label": ariaLabel,
|
|
147
|
-
|
|
115
|
+
style: floatingStyles,
|
|
148
116
|
className: utils.cn(
|
|
149
|
-
"
|
|
117
|
+
"z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
|
|
150
118
|
menuClassName
|
|
151
119
|
),
|
|
120
|
+
...getFloatingProps(),
|
|
152
121
|
children: [
|
|
153
122
|
options.map((option, index) => {
|
|
154
123
|
const active = option.value === value;
|
|
@@ -156,24 +125,26 @@ function IconMenuPickerInner({
|
|
|
156
125
|
"button",
|
|
157
126
|
{
|
|
158
127
|
ref: (node) => {
|
|
159
|
-
|
|
128
|
+
listRef.current[index] = node;
|
|
160
129
|
},
|
|
161
130
|
type: "button",
|
|
162
131
|
role: "menuitemradio",
|
|
163
132
|
"aria-checked": active,
|
|
164
|
-
tabIndex:
|
|
165
|
-
onClick: () => select(option.value),
|
|
166
|
-
onKeyDown: (event) => {
|
|
167
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
168
|
-
event.preventDefault();
|
|
169
|
-
select(option.value);
|
|
170
|
-
}
|
|
171
|
-
},
|
|
133
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
172
134
|
className: utils.cn(
|
|
173
135
|
"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors",
|
|
174
136
|
"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none",
|
|
175
137
|
active && "text-foreground"
|
|
176
138
|
),
|
|
139
|
+
...getItemProps({
|
|
140
|
+
onClick: () => select(option.value),
|
|
141
|
+
onKeyDown: (event) => {
|
|
142
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
select(option.value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
177
148
|
children: [
|
|
178
149
|
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: option.icon, className: "shrink-0 text-muted-foreground" }),
|
|
179
150
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate capitalize", children: option.label }),
|
|
@@ -186,7 +157,7 @@ function IconMenuPickerInner({
|
|
|
186
157
|
footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground", children: footer }) : null
|
|
187
158
|
]
|
|
188
159
|
}
|
|
189
|
-
)
|
|
160
|
+
) }) })
|
|
190
161
|
] });
|
|
191
162
|
}
|
|
192
163
|
const IconMenuPicker = react.forwardRef(IconMenuPickerInner);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon-menu-picker.cjs","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n ref: React.Ref<HTMLDivElement>,\n) {\n const rootRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const [open, setOpen] = useState(false);\n\n useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);\n\n const activeIndex = useMemo(\n () =>\n Math.max(\n 0,\n options.findIndex((option) => option.value === value),\n ),\n [options, value],\n );\n\n const close = useCallback((returnFocus: boolean) => {\n setOpen(false);\n if (returnFocus) triggerRef.current?.focus();\n }, []);\n\n const select = useCallback(\n (next: T) => {\n onChange(next);\n close(true);\n },\n [close, onChange],\n );\n\n useEffect(() => {\n if (!open) return;\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) return;\n if (!rootRef.current?.contains(event.target as Node)) {\n close(false);\n }\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n close(true);\n }\n };\n\n document.addEventListener(\"pointerdown\", onPointerDown);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"pointerdown\", onPointerDown);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [close, open]);\n\n useEffect(() => {\n if (!open) return;\n const node = itemRefs.current[activeIndex];\n node?.focus();\n }, [activeIndex, open]);\n\n const focusItem = (index: number) => {\n const wrapped = (index + options.length) % options.length;\n itemRefs.current[wrapped]?.focus();\n };\n\n const onMenuKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const focusedIndex = itemRefs.current.findIndex((node) => node === document.activeElement);\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) + 1);\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) - 1);\n return;\n }\n if (event.key === \"Home\") {\n event.preventDefault();\n focusItem(0);\n return;\n }\n if (event.key === \"End\") {\n event.preventDefault();\n focusItem(options.length - 1);\n return;\n }\n if (event.key === \"Tab\") {\n close(false);\n }\n };\n\n const onTriggerKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n if (event.key === \"ArrowDown\" || event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setOpen(true);\n }\n };\n\n const selected = options[activeIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"ghost\"\n size={showLabel ? \"default\" : \"icon\"}\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitle ?? `${ariaLabel}: ${selected.label}`}\n onClick={() => setOpen((current) => !current)}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n \"text-muted-foreground hover:text-foreground\",\n showLabel && \"w-full justify-start\",\n triggerClassName,\n )}\n >\n <Icon icon={selected.icon} {...(showLabel ? { className: \"shrink-0 text-foreground\" } : {})} />\n {showLabel && (\n <>\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </>\n )}\n </Button>\n {open && (\n <div\n role=\"menu\"\n aria-label={ariaLabel}\n onKeyDown={onMenuKeyDown}\n className={cn(\n \"absolute left-0 top-[calc(100%+0.375rem)] z-50 min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n itemRefs.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => select(option.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n }}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":["useRef","useState","useImperativeHandle","useMemo","useCallback","useEffect","jsxs","cn","Button","jsx","Icon","Fragment","UiChevronDown","UiCheck","forwardRef"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"icon-menu-picker.cjs","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useMergeRefs,\n useRole,\n useTypeahead,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./IconButton\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n const [open, setOpen] = useState(false);\n // Index of the item highlighted by keyboard navigation. Null until the user\n // arrows/typeaheads, so a pointer-opened menu starts with the selected row\n // focused (selectedIndex below) rather than the first.\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n const labelsRef = useRef<string[]>(options.map((o) => o.label));\n labelsRef.current = options.map((o) => o.label);\n\n const selectedIndex = useMemo(\n () => options.findIndex((option) => option.value === value),\n [options, value],\n );\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(6) reproduces the previous 0.375rem gap; flip opens the menu\n // upward when the trigger is near the bottom edge; shift slides it back\n // on-screen at the left/right edges. The 8px padding keeps a gutter.\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n selectedIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n const typeahead = useTypeahead(context, {\n listRef: labelsRef,\n activeIndex,\n onMatch: setActiveIndex,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n typeahead,\n ]);\n\n // Keep the trigger wrapper available both to floating-ui (as the positioning\n // reference) and to the consumer's forwarded ref.\n const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);\n\n const select = (next: T) => {\n onChange(next);\n setOpen(false);\n };\n\n const selected = options[selectedIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;\n\n return (\n <div ref={referenceRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n {showLabel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitleText}\n className={cn(\n \"w-full justify-start text-muted-foreground hover:text-foreground\",\n triggerClassName,\n )}\n {...getReferenceProps()}\n >\n <Icon icon={selected.icon} className=\"shrink-0 text-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n ) : (\n <IconButton\n icon={selected.icon}\n label={ariaLabel}\n title={triggerTitleText}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={triggerClassName}\n {...getReferenceProps()}\n />\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false} initialFocus={selectedIndex}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={ariaLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n listRef.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={index === activeIndex ? 0 : -1}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n {...getItemProps({\n onClick: () => select(option.value),\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n },\n })}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":["useState","useRef","useMemo","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useTypeahead","useInteractions","useMergeRefs","jsxs","cn","Button","jsx","Icon","UiChevronDown","IconButton","FloatingPortal","FloatingFocusManager","UiCheck","forwardRef"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon-menu-picker.d.ts","sourceRoot":"","sources":["../../src/components/icon-menu-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"icon-menu-picker.d.ts","sourceRoot":"","sources":["../../src/components/icon-menu-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAkBf,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI;IAC7C,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,IAAI;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAoLF,eAAO,MAAM,cAAc,EAAsC,CAAC,CAAC,SAAS,MAAM,EAChF,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,KAChE,GAAG,CAAC,OAAO,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { jsxs, jsx
|
|
2
|
-
import { forwardRef,
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useState, useRef, useMemo } from "react";
|
|
3
|
+
import { useFloating, autoUpdate, offset, flip, shift, useClick, useDismiss, useRole, useListNavigation, useTypeahead, useInteractions, useMergeRefs, FloatingPortal, FloatingFocusManager } from "@floating-ui/react";
|
|
3
4
|
import { cn } from "../lib/utils.js";
|
|
4
5
|
import { Icon } from "../data/Icon.js";
|
|
5
6
|
import { Button } from "./button.js";
|
|
7
|
+
import { IconButton } from "./IconButton.js";
|
|
6
8
|
import { UiChevronDown } from "../icons/components/UiChevronDown.js";
|
|
7
9
|
import { UiCheck } from "../icons/components/UiCheck.js";
|
|
8
10
|
function IconMenuPickerInner({
|
|
@@ -16,137 +18,104 @@ function IconMenuPickerInner({
|
|
|
16
18
|
className,
|
|
17
19
|
triggerClassName,
|
|
18
20
|
menuClassName
|
|
19
|
-
},
|
|
20
|
-
const rootRef = useRef(null);
|
|
21
|
-
const triggerRef = useRef(null);
|
|
22
|
-
const itemRefs = useRef([]);
|
|
21
|
+
}, forwardedRef) {
|
|
23
22
|
const [open, setOpen] = useState(false);
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
),
|
|
23
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
24
|
+
const listRef = useRef([]);
|
|
25
|
+
const labelsRef = useRef(options.map((o) => o.label));
|
|
26
|
+
labelsRef.current = options.map((o) => o.label);
|
|
27
|
+
const selectedIndex = useMemo(
|
|
28
|
+
() => options.findIndex((option) => option.value === value),
|
|
30
29
|
[options, value]
|
|
31
30
|
);
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
32
|
+
open,
|
|
33
|
+
onOpenChange: setOpen,
|
|
34
|
+
placement: "bottom-start",
|
|
35
|
+
whileElementsMounted: autoUpdate,
|
|
36
|
+
// offset(6) reproduces the previous 0.375rem gap; flip opens the menu
|
|
37
|
+
// upward when the trigger is near the bottom edge; shift slides it back
|
|
38
|
+
// on-screen at the left/right edges. The 8px padding keeps a gutter.
|
|
39
|
+
middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })]
|
|
40
|
+
});
|
|
41
|
+
const click = useClick(context);
|
|
42
|
+
const dismiss = useDismiss(context);
|
|
43
|
+
const role = useRole(context, { role: "menu" });
|
|
44
|
+
const listNav = useListNavigation(context, {
|
|
45
|
+
listRef,
|
|
46
|
+
activeIndex,
|
|
47
|
+
selectedIndex,
|
|
48
|
+
onNavigate: setActiveIndex,
|
|
49
|
+
loop: true
|
|
50
|
+
});
|
|
51
|
+
const typeahead = useTypeahead(context, {
|
|
52
|
+
listRef: labelsRef,
|
|
53
|
+
activeIndex,
|
|
54
|
+
onMatch: setActiveIndex
|
|
55
|
+
});
|
|
56
|
+
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
|
|
57
|
+
click,
|
|
58
|
+
dismiss,
|
|
59
|
+
role,
|
|
60
|
+
listNav,
|
|
61
|
+
typeahead
|
|
62
|
+
]);
|
|
63
|
+
const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);
|
|
64
|
+
const select = (next) => {
|
|
65
|
+
onChange(next);
|
|
34
66
|
setOpen(false);
|
|
35
|
-
if (returnFocus) (_a = triggerRef.current) == null ? void 0 : _a.focus();
|
|
36
|
-
}, []);
|
|
37
|
-
const select = useCallback(
|
|
38
|
-
(next) => {
|
|
39
|
-
onChange(next);
|
|
40
|
-
close(true);
|
|
41
|
-
},
|
|
42
|
-
[close, onChange]
|
|
43
|
-
);
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (!open) return;
|
|
46
|
-
const onPointerDown = (event) => {
|
|
47
|
-
var _a;
|
|
48
|
-
if (event.button !== 0) return;
|
|
49
|
-
if (!((_a = rootRef.current) == null ? void 0 : _a.contains(event.target))) {
|
|
50
|
-
close(false);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const onKeyDown = (event) => {
|
|
54
|
-
if (event.key === "Escape") {
|
|
55
|
-
event.preventDefault();
|
|
56
|
-
close(true);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
60
|
-
document.addEventListener("keydown", onKeyDown);
|
|
61
|
-
return () => {
|
|
62
|
-
document.removeEventListener("pointerdown", onPointerDown);
|
|
63
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
64
|
-
};
|
|
65
|
-
}, [close, open]);
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (!open) return;
|
|
68
|
-
const node = itemRefs.current[activeIndex];
|
|
69
|
-
node == null ? void 0 : node.focus();
|
|
70
|
-
}, [activeIndex, open]);
|
|
71
|
-
const focusItem = (index) => {
|
|
72
|
-
var _a;
|
|
73
|
-
const wrapped = (index + options.length) % options.length;
|
|
74
|
-
(_a = itemRefs.current[wrapped]) == null ? void 0 : _a.focus();
|
|
75
67
|
};
|
|
76
|
-
const
|
|
77
|
-
const focusedIndex = itemRefs.current.findIndex((node) => node === document.activeElement);
|
|
78
|
-
if (event.key === "ArrowDown") {
|
|
79
|
-
event.preventDefault();
|
|
80
|
-
focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) + 1);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (event.key === "ArrowUp") {
|
|
84
|
-
event.preventDefault();
|
|
85
|
-
focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) - 1);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (event.key === "Home") {
|
|
89
|
-
event.preventDefault();
|
|
90
|
-
focusItem(0);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (event.key === "End") {
|
|
94
|
-
event.preventDefault();
|
|
95
|
-
focusItem(options.length - 1);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (event.key === "Tab") {
|
|
99
|
-
close(false);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
const onTriggerKeyDown = (event) => {
|
|
103
|
-
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
|
104
|
-
event.preventDefault();
|
|
105
|
-
setOpen(true);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const selected = options[activeIndex];
|
|
68
|
+
const selected = options[selectedIndex];
|
|
109
69
|
if (!selected) {
|
|
110
70
|
throw new Error(`IconMenuPicker: value "${value}" not found in options for ${ariaLabel}`);
|
|
111
71
|
}
|
|
112
|
-
|
|
113
|
-
|
|
72
|
+
const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { ref: referenceRef, className: cn("relative inline-flex", showLabel && "w-full", className), children: [
|
|
74
|
+
showLabel ? /* @__PURE__ */ jsxs(
|
|
114
75
|
Button,
|
|
115
76
|
{
|
|
116
|
-
ref: triggerRef,
|
|
117
77
|
type: "button",
|
|
118
78
|
variant: "ghost",
|
|
119
|
-
size:
|
|
79
|
+
size: "default",
|
|
120
80
|
"aria-label": ariaLabel,
|
|
121
81
|
"aria-haspopup": "menu",
|
|
122
82
|
"aria-expanded": open,
|
|
123
|
-
title:
|
|
124
|
-
onClick: () => setOpen((current) => !current),
|
|
125
|
-
onKeyDown: onTriggerKeyDown,
|
|
83
|
+
title: triggerTitleText,
|
|
126
84
|
className: cn(
|
|
127
|
-
"text-muted-foreground hover:text-foreground",
|
|
128
|
-
showLabel && "w-full justify-start",
|
|
85
|
+
"w-full justify-start text-muted-foreground hover:text-foreground",
|
|
129
86
|
triggerClassName
|
|
130
87
|
),
|
|
88
|
+
...getReferenceProps(),
|
|
131
89
|
children: [
|
|
132
|
-
/* @__PURE__ */ jsx(Icon, { icon: selected.icon,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
/* @__PURE__ */ jsx(Icon, { icon: UiChevronDown, className: "shrink-0 text-muted-foreground" })
|
|
136
|
-
] })
|
|
90
|
+
/* @__PURE__ */ jsx(Icon, { icon: selected.icon, className: "shrink-0 text-foreground" }),
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left capitalize", children: `${ariaLabel}: ${selected.label}` }),
|
|
92
|
+
/* @__PURE__ */ jsx(Icon, { icon: UiChevronDown, className: "shrink-0 text-muted-foreground" })
|
|
137
93
|
]
|
|
138
94
|
}
|
|
95
|
+
) : /* @__PURE__ */ jsx(
|
|
96
|
+
IconButton,
|
|
97
|
+
{
|
|
98
|
+
icon: selected.icon,
|
|
99
|
+
label: ariaLabel,
|
|
100
|
+
title: triggerTitleText,
|
|
101
|
+
"aria-haspopup": "menu",
|
|
102
|
+
"aria-expanded": open,
|
|
103
|
+
className: triggerClassName,
|
|
104
|
+
...getReferenceProps()
|
|
105
|
+
}
|
|
139
106
|
),
|
|
140
|
-
open && /* @__PURE__ */ jsxs(
|
|
107
|
+
open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: false, initialFocus: selectedIndex, children: /* @__PURE__ */ jsxs(
|
|
141
108
|
"div",
|
|
142
109
|
{
|
|
110
|
+
ref: refs.setFloating,
|
|
143
111
|
role: "menu",
|
|
144
112
|
"aria-label": ariaLabel,
|
|
145
|
-
|
|
113
|
+
style: floatingStyles,
|
|
146
114
|
className: cn(
|
|
147
|
-
"
|
|
115
|
+
"z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
|
|
148
116
|
menuClassName
|
|
149
117
|
),
|
|
118
|
+
...getFloatingProps(),
|
|
150
119
|
children: [
|
|
151
120
|
options.map((option, index) => {
|
|
152
121
|
const active = option.value === value;
|
|
@@ -154,24 +123,26 @@ function IconMenuPickerInner({
|
|
|
154
123
|
"button",
|
|
155
124
|
{
|
|
156
125
|
ref: (node) => {
|
|
157
|
-
|
|
126
|
+
listRef.current[index] = node;
|
|
158
127
|
},
|
|
159
128
|
type: "button",
|
|
160
129
|
role: "menuitemradio",
|
|
161
130
|
"aria-checked": active,
|
|
162
|
-
tabIndex:
|
|
163
|
-
onClick: () => select(option.value),
|
|
164
|
-
onKeyDown: (event) => {
|
|
165
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
select(option.value);
|
|
168
|
-
}
|
|
169
|
-
},
|
|
131
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
170
132
|
className: cn(
|
|
171
133
|
"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors",
|
|
172
134
|
"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none",
|
|
173
135
|
active && "text-foreground"
|
|
174
136
|
),
|
|
137
|
+
...getItemProps({
|
|
138
|
+
onClick: () => select(option.value),
|
|
139
|
+
onKeyDown: (event) => {
|
|
140
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
select(option.value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}),
|
|
175
146
|
children: [
|
|
176
147
|
/* @__PURE__ */ jsx(Icon, { icon: option.icon, className: "shrink-0 text-muted-foreground" }),
|
|
177
148
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate capitalize", children: option.label }),
|
|
@@ -184,7 +155,7 @@ function IconMenuPickerInner({
|
|
|
184
155
|
footer ? /* @__PURE__ */ jsx("div", { className: "mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground", children: footer }) : null
|
|
185
156
|
]
|
|
186
157
|
}
|
|
187
|
-
)
|
|
158
|
+
) }) })
|
|
188
159
|
] });
|
|
189
160
|
}
|
|
190
161
|
const IconMenuPicker = forwardRef(IconMenuPickerInner);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon-menu-picker.js","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n ref: React.Ref<HTMLDivElement>,\n) {\n const rootRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const [open, setOpen] = useState(false);\n\n useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);\n\n const activeIndex = useMemo(\n () =>\n Math.max(\n 0,\n options.findIndex((option) => option.value === value),\n ),\n [options, value],\n );\n\n const close = useCallback((returnFocus: boolean) => {\n setOpen(false);\n if (returnFocus) triggerRef.current?.focus();\n }, []);\n\n const select = useCallback(\n (next: T) => {\n onChange(next);\n close(true);\n },\n [close, onChange],\n );\n\n useEffect(() => {\n if (!open) return;\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) return;\n if (!rootRef.current?.contains(event.target as Node)) {\n close(false);\n }\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n close(true);\n }\n };\n\n document.addEventListener(\"pointerdown\", onPointerDown);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"pointerdown\", onPointerDown);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [close, open]);\n\n useEffect(() => {\n if (!open) return;\n const node = itemRefs.current[activeIndex];\n node?.focus();\n }, [activeIndex, open]);\n\n const focusItem = (index: number) => {\n const wrapped = (index + options.length) % options.length;\n itemRefs.current[wrapped]?.focus();\n };\n\n const onMenuKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const focusedIndex = itemRefs.current.findIndex((node) => node === document.activeElement);\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) + 1);\n return;\n }\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n focusItem((focusedIndex < 0 ? activeIndex : focusedIndex) - 1);\n return;\n }\n if (event.key === \"Home\") {\n event.preventDefault();\n focusItem(0);\n return;\n }\n if (event.key === \"End\") {\n event.preventDefault();\n focusItem(options.length - 1);\n return;\n }\n if (event.key === \"Tab\") {\n close(false);\n }\n };\n\n const onTriggerKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n if (event.key === \"ArrowDown\" || event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setOpen(true);\n }\n };\n\n const selected = options[activeIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"ghost\"\n size={showLabel ? \"default\" : \"icon\"}\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitle ?? `${ariaLabel}: ${selected.label}`}\n onClick={() => setOpen((current) => !current)}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n \"text-muted-foreground hover:text-foreground\",\n showLabel && \"w-full justify-start\",\n triggerClassName,\n )}\n >\n <Icon icon={selected.icon} {...(showLabel ? { className: \"shrink-0 text-foreground\" } : {})} />\n {showLabel && (\n <>\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </>\n )}\n </Button>\n {open && (\n <div\n role=\"menu\"\n aria-label={ariaLabel}\n onKeyDown={onMenuKeyDown}\n className={cn(\n \"absolute left-0 top-[calc(100%+0.375rem)] z-50 min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n itemRefs.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => select(option.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n }}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"icon-menu-picker.js","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useMergeRefs,\n useRole,\n useTypeahead,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./IconButton\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n const [open, setOpen] = useState(false);\n // Index of the item highlighted by keyboard navigation. Null until the user\n // arrows/typeaheads, so a pointer-opened menu starts with the selected row\n // focused (selectedIndex below) rather than the first.\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n const labelsRef = useRef<string[]>(options.map((o) => o.label));\n labelsRef.current = options.map((o) => o.label);\n\n const selectedIndex = useMemo(\n () => options.findIndex((option) => option.value === value),\n [options, value],\n );\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(6) reproduces the previous 0.375rem gap; flip opens the menu\n // upward when the trigger is near the bottom edge; shift slides it back\n // on-screen at the left/right edges. The 8px padding keeps a gutter.\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n selectedIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n const typeahead = useTypeahead(context, {\n listRef: labelsRef,\n activeIndex,\n onMatch: setActiveIndex,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n typeahead,\n ]);\n\n // Keep the trigger wrapper available both to floating-ui (as the positioning\n // reference) and to the consumer's forwarded ref.\n const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);\n\n const select = (next: T) => {\n onChange(next);\n setOpen(false);\n };\n\n const selected = options[selectedIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;\n\n return (\n <div ref={referenceRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n {showLabel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitleText}\n className={cn(\n \"w-full justify-start text-muted-foreground hover:text-foreground\",\n triggerClassName,\n )}\n {...getReferenceProps()}\n >\n <Icon icon={selected.icon} className=\"shrink-0 text-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n ) : (\n <IconButton\n icon={selected.icon}\n label={ariaLabel}\n title={triggerTitleText}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={triggerClassName}\n {...getReferenceProps()}\n />\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false} initialFocus={selectedIndex}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={ariaLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n listRef.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={index === activeIndex ? 0 : -1}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n {...getItemProps({\n onClick: () => select(option.value),\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n },\n })}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":[],"mappings":"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"}
|