@rovula/ui 0.1.20 → 0.1.22
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/cjs/bundle.css +316 -43
- package/dist/cjs/bundle.js +675 -675
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Badge/Badge.d.ts +40 -0
- package/dist/cjs/types/components/Badge/Badge.stories.d.ts +295 -0
- package/dist/cjs/types/components/Badge/Badge.styles.d.ts +7 -0
- package/dist/cjs/types/components/Badge/index.d.ts +2 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +4 -8
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
- package/dist/cjs/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
- package/dist/cjs/types/components/DropdownMenu/DropdownMenu.stories.d.ts +45 -30
- package/dist/cjs/types/components/Form/Form.d.ts +2 -1
- package/dist/cjs/types/components/Form/Form.stories.d.ts +4 -0
- package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +38 -0
- package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +301 -0
- package/dist/cjs/types/index.d.ts +4 -1
- package/dist/cjs/types/patterns/menu/Menu.d.ts +70 -0
- package/dist/cjs/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +17 -10
- package/dist/cjs/types/utils/mergeRefs.d.ts +20 -0
- package/dist/components/Avatar/Avatar.styles.js +2 -2
- package/dist/components/Badge/Badge.js +36 -0
- package/dist/components/Badge/Badge.stories.js +51 -0
- package/dist/components/Badge/Badge.styles.js +62 -0
- package/dist/components/Badge/index.js +2 -0
- package/dist/components/Dropdown/Dropdown.js +54 -163
- package/dist/components/Dropdown/Dropdown.stories.js +29 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +24 -13
- package/dist/components/DropdownMenu/DropdownMenu.stories.js +120 -88
- package/dist/components/Form/Form.js +11 -4
- package/dist/components/Form/Form.stories.js +27 -0
- package/dist/components/ScrollArea/ScrollArea.js +50 -0
- package/dist/components/ScrollArea/ScrollArea.stories.js +56 -0
- package/dist/components/TextInput/TextInput.js +6 -3
- package/dist/esm/bundle.css +316 -43
- package/dist/esm/bundle.js +1545 -1545
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Badge/Badge.d.ts +40 -0
- package/dist/esm/types/components/Badge/Badge.stories.d.ts +295 -0
- package/dist/esm/types/components/Badge/Badge.styles.d.ts +7 -0
- package/dist/esm/types/components/Badge/index.d.ts +2 -0
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +4 -8
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
- package/dist/esm/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
- package/dist/esm/types/components/DropdownMenu/DropdownMenu.stories.d.ts +45 -30
- package/dist/esm/types/components/Form/Form.d.ts +2 -1
- package/dist/esm/types/components/Form/Form.stories.d.ts +4 -0
- package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +38 -0
- package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +301 -0
- package/dist/esm/types/index.d.ts +4 -1
- package/dist/esm/types/patterns/menu/Menu.d.ts +70 -0
- package/dist/esm/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +17 -10
- package/dist/esm/types/utils/mergeRefs.d.ts +20 -0
- package/dist/index.d.ts +156 -74
- package/dist/index.js +3 -1
- package/dist/patterns/menu/Menu.js +95 -0
- package/dist/patterns/menu/Menu.stories.js +611 -0
- package/dist/src/theme/global.css +485 -57
- package/dist/utils/mergeRefs.js +42 -0
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.styles.ts +2 -2
- package/src/components/Badge/Badge.stories.tsx +128 -0
- package/src/components/Badge/Badge.styles.ts +70 -0
- package/src/components/Badge/Badge.tsx +103 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +170 -1
- package/src/components/Dropdown/Dropdown.tsx +186 -276
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +1375 -253
- package/src/components/DropdownMenu/DropdownMenu.tsx +118 -55
- package/src/components/Form/Form.stories.tsx +70 -0
- package/src/components/Form/Form.tsx +23 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +229 -0
- package/src/components/ScrollArea/ScrollArea.tsx +72 -0
- package/src/components/TextInput/TextInput.tsx +6 -3
- package/src/index.ts +4 -1
- package/src/patterns/menu/Menu.stories.tsx +1100 -0
- package/src/patterns/menu/Menu.tsx +282 -0
- package/src/theme/global.css +84 -11
- package/src/theme/themes/xspector/baseline.css +1 -1
- package/src/theme/themes/xspector/components/scrollbar.css +12 -0
- package/src/theme/tokens/baseline.css +3 -1
- package/src/theme/tokens/components/badge.css +54 -0
- package/src/theme/tokens/components/dropdown-menu.css +16 -5
- package/src/theme/tokens/components/scrollbar.css +18 -0
- package/src/utils/mergeRefs.ts +46 -0
- package/dist/cjs/types/components/Menu/Menu.d.ts +0 -65
- package/dist/cjs/types/components/Menu/helpers.d.ts +0 -19
- package/dist/cjs/types/components/Menu/index.d.ts +0 -4
- package/dist/components/Menu/Menu.js +0 -64
- package/dist/components/Menu/Menu.stories.js +0 -406
- package/dist/components/Menu/helpers.js +0 -28
- package/dist/components/Menu/index.js +0 -3
- package/dist/esm/types/components/Menu/Menu.d.ts +0 -65
- package/dist/esm/types/components/Menu/helpers.d.ts +0 -19
- package/dist/esm/types/components/Menu/index.d.ts +0 -4
- package/src/components/Menu/Menu.stories.tsx +0 -586
- package/src/components/Menu/Menu.tsx +0 -235
- package/src/components/Menu/helpers.ts +0 -45
- package/src/components/Menu/index.ts +0 -7
- package/src/theme/themes/xspector/components/dropdown-menu.css +0 -28
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
3
|
var t = {};
|
|
3
4
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
@@ -9,190 +10,80 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
10
|
}
|
|
10
11
|
return t;
|
|
11
12
|
};
|
|
12
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
14
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, Fragment, } from "react";
|
|
14
|
-
import
|
|
15
|
+
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from "@headlessui/react";
|
|
15
16
|
import TextInput from "../TextInput/TextInput";
|
|
16
17
|
import { customInputVariant, dropdownIconVariant } from "./Dropdown.styles";
|
|
17
|
-
import { Menu } from "../Menu/Menu";
|
|
18
|
-
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|
19
18
|
import { cn } from "@/utils/cn";
|
|
19
|
+
import Icon from "../Icon/Icon";
|
|
20
|
+
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Shared menu item styles (used by Dropdown items + renderOptions consumers)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export const menuItemBaseStyles = cn("relative flex gap-1 cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "hover:bg-[var(--dropdown-menu-hover-bg)] hover:text-[var(--dropdown-menu-hover-text)]");
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Dropdown
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
20
28
|
const Dropdown = forwardRef((_a, ref) => {
|
|
21
|
-
var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline",
|
|
29
|
+
var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal: _modal, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName, segmentedInput = true } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "modal", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName", "segmentedInput"]);
|
|
22
30
|
const _id = id || `${label}-select`;
|
|
23
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
24
31
|
const [selectedOption, setSelectedOption] = useState(null);
|
|
25
|
-
const [
|
|
26
|
-
const keyCode = useRef("");
|
|
27
|
-
const dropdownRef = useRef(null);
|
|
32
|
+
const [query, setQuery] = useState("");
|
|
28
33
|
const inputRef = useRef(null);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
|
|
34
|
+
// Expose the inner <input> element via forwardRef
|
|
35
|
+
useImperativeHandle(ref, () => inputRef.current, []);
|
|
36
|
+
// Sync external value prop
|
|
33
37
|
useEffect(() => {
|
|
34
|
-
|
|
35
|
-
setSelectedOption(value);
|
|
36
|
-
setTextValue((_a = value === null || value === void 0 ? void 0 : value.label) !== null && _a !== void 0 ? _a : "");
|
|
38
|
+
setSelectedOption(value !== null && value !== void 0 ? value : null);
|
|
37
39
|
}, [value]);
|
|
38
|
-
/** ✅ Auto-detect if inside a Dialog */
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
let node = inputRef.current;
|
|
41
|
-
while (node) {
|
|
42
|
-
if (node.getAttribute("role") === "dialog") {
|
|
43
|
-
setIsInsideDialog(true);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
node = node.parentElement;
|
|
47
|
-
}
|
|
48
|
-
setIsInsideDialog(false);
|
|
49
|
-
}, []);
|
|
50
|
-
const handleOnChangeText = useCallback((event) => {
|
|
51
|
-
onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(event);
|
|
52
|
-
setTextValue(event.target.value);
|
|
53
|
-
if (!event.target.value) {
|
|
54
|
-
clearMismatchValue(event);
|
|
55
|
-
}
|
|
56
|
-
}, [onChangeText]);
|
|
57
|
-
const handleOptionClick = useCallback((option) => {
|
|
58
|
-
setSelectedOption(option);
|
|
59
|
-
setTextValue(option.label);
|
|
60
|
-
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
61
|
-
setIsFocused(false);
|
|
62
|
-
}, [onSelect]);
|
|
63
40
|
const optionsFiltered = useMemo(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const spaceAbove = rect.top;
|
|
77
|
-
const shouldOpenAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
|
|
78
|
-
setIsAbove(shouldOpenAbove);
|
|
79
|
-
if (usePortal) {
|
|
80
|
-
setDropdownStyles({
|
|
81
|
-
position: "absolute",
|
|
82
|
-
top: shouldOpenAbove
|
|
83
|
-
? `${rect.top - dropdownHeight}px`
|
|
84
|
-
: `${rect.bottom}px`,
|
|
85
|
-
left: `${rect.left}px`,
|
|
86
|
-
width: `${rect.width}px`,
|
|
87
|
-
zIndex: 9999,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
setDropdownStyles({
|
|
92
|
-
position: "absolute",
|
|
93
|
-
top: shouldOpenAbove ? `-${dropdownHeight}px` : "100%",
|
|
94
|
-
left: "0",
|
|
95
|
-
width: "100%",
|
|
96
|
-
zIndex: 9999,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}, [modal, isInsideDialog, usePortal]);
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
if (isFocused) {
|
|
103
|
-
updateDropdownPosition();
|
|
104
|
-
window.addEventListener("resize", updateDropdownPosition);
|
|
41
|
+
if (!filterMode || !query)
|
|
42
|
+
return options;
|
|
43
|
+
return options.filter((opt) => opt.label.toLowerCase().includes(query.toLowerCase()));
|
|
44
|
+
}, [options, filterMode, query]);
|
|
45
|
+
const handleSelect = useCallback((option) => {
|
|
46
|
+
setSelectedOption(option);
|
|
47
|
+
setQuery("");
|
|
48
|
+
if (option) {
|
|
49
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
50
|
+
// After selection Headless UI keeps focus on the input (via rAF refocus).
|
|
51
|
+
// Blur after that rAF so the next click triggers onFocus → immediate open.
|
|
52
|
+
setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); }, 0);
|
|
105
53
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
54
|
+
}, [onSelect]);
|
|
55
|
+
const handleInputChange = useCallback((e) => {
|
|
56
|
+
if (filterMode)
|
|
57
|
+
setQuery(e.target.value);
|
|
58
|
+
onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(e);
|
|
59
|
+
}, [filterMode, onChangeText]);
|
|
60
|
+
const renderOptionList = () => {
|
|
111
61
|
if (customRenderOptions) {
|
|
112
62
|
return customRenderOptions({
|
|
113
63
|
optionsFiltered,
|
|
114
64
|
selectedOption,
|
|
115
|
-
onClick:
|
|
116
|
-
style: dropdownStyles,
|
|
117
|
-
dropdownRef,
|
|
65
|
+
onClick: handleSelect,
|
|
118
66
|
});
|
|
119
67
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
68
|
+
if (optionsFiltered.length === 0) {
|
|
69
|
+
return (_jsx("div", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }));
|
|
70
|
+
}
|
|
71
|
+
return optionsFiltered.map((option) => {
|
|
123
72
|
if (option.renderLabel) {
|
|
124
|
-
return {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitle5": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
|
|
132
|
-
}, optionItemClassName),
|
|
133
|
-
}) }, option.value)),
|
|
134
|
-
};
|
|
73
|
+
return (_jsx(Fragment, { children: option.renderLabel({
|
|
74
|
+
value: option.value,
|
|
75
|
+
label: option.label,
|
|
76
|
+
handleOnClick: () => handleSelect(option),
|
|
77
|
+
className: cn(menuItemBaseStyles, (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value &&
|
|
78
|
+
"bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)]", optionItemClassName),
|
|
79
|
+
}) }, option.value));
|
|
135
80
|
}
|
|
136
|
-
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
type: defaultMenuItemType,
|
|
140
|
-
value: option.value,
|
|
141
|
-
label: option.label,
|
|
142
|
-
},
|
|
143
|
-
};
|
|
81
|
+
return (_jsx(ComboboxOption, { value: option, className: ({ focus, selected }) => cn(menuItemBaseStyles, (selected || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value) &&
|
|
82
|
+
"bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)]", focus &&
|
|
83
|
+
"bg-[var(--dropdown-menu-hover-bg)] text-[var(--dropdown-menu-hover-text)]", optionItemClassName), children: ({ selected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "shrink-0 size-4 flex items-center justify-center", children: (selected || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value) && (_jsx(Icon, { type: "heroicons", name: "check", className: "size-4 text-[var(--dropdown-menu-selected-text)]" })) }), option.label] })) }, option.value));
|
|
144
84
|
});
|
|
145
|
-
// Add "not found" message if no results
|
|
146
|
-
if (finalMenuItems.length === 0) {
|
|
147
|
-
finalMenuItems.push({
|
|
148
|
-
type: "custom",
|
|
149
|
-
render: () => (_jsx("div", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }, "not-found")),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return (_jsx(Menu, { ref: dropdownRef, items: finalMenuItems, selectedValues: (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) ? [selectedOption.value] : [], onSelect: (value) => {
|
|
153
|
-
const option = optionsFiltered.find((opt) => opt.value === value);
|
|
154
|
-
if (option) {
|
|
155
|
-
handleOptionClick(option);
|
|
156
|
-
}
|
|
157
|
-
}, className: cn("absolute mt-1 w-full max-h-60 overflow-y-auto", !usePortal && (isAbove ? "bottom-full mb-1" : "top-full mt-1"), optionContainerClassName), style: dropdownStyles }));
|
|
158
85
|
};
|
|
159
|
-
|
|
160
|
-
var _a;
|
|
161
|
-
setIsFocused(true);
|
|
162
|
-
(_a = props === null || props === void 0 ? void 0 : props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
163
|
-
}, [props === null || props === void 0 ? void 0 : props.onFocus]);
|
|
164
|
-
const clearMismatchValue = useCallback((e) => {
|
|
165
|
-
const matchSelectedValue = optionsFiltered.find((opt) => { var _a, _b; return opt.value === ((_a = e.target) === null || _a === void 0 ? void 0 : _a.value) || opt.label === ((_b = e.target) === null || _b === void 0 ? void 0 : _b.value); });
|
|
166
|
-
const isMatchSelectedValue = !!matchSelectedValue;
|
|
167
|
-
let option = matchSelectedValue || {
|
|
168
|
-
value: "",
|
|
169
|
-
label: "",
|
|
170
|
-
};
|
|
171
|
-
if (!isMatchSelectedValue && textValue) {
|
|
172
|
-
option = {
|
|
173
|
-
value: "",
|
|
174
|
-
label: "",
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
if (keyCode.current === "Enter") {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
setSelectedOption(option);
|
|
181
|
-
setTextValue(option.label);
|
|
182
|
-
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
183
|
-
}, [optionsFiltered, textValue]);
|
|
184
|
-
const handleOnBlur = useCallback((e) => {
|
|
185
|
-
var _a;
|
|
186
|
-
setTimeout(() => setIsFocused(false), 200);
|
|
187
|
-
clearMismatchValue(e);
|
|
188
|
-
(_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
189
|
-
}, [props === null || props === void 0 ? void 0 : props.onBlur, clearMismatchValue]);
|
|
190
|
-
const handleOnKeyDown = useCallback((e) => {
|
|
191
|
-
var _a;
|
|
192
|
-
keyCode.current = e.code;
|
|
193
|
-
(_a = props === null || props === void 0 ? void 0 : props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
194
|
-
}, [props === null || props === void 0 ? void 0 : props.onKeyDown]);
|
|
195
|
-
return (_jsxs("div", { className: `relative ${fullwidth ? "w-full" : ""}`, children: [_jsx(TextInput, Object.assign({ hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: isFocused }) }) }, props, { ref: inputRef, readOnly: !filterMode, value: textValue, onChange: handleOnChangeText, label: label, placeholder: " ", type: "text", autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, onFocus: handleOnFocus, onBlur: handleOnBlur, onKeyDown: handleOnKeyDown })), isFocused &&
|
|
196
|
-
(usePortal ? (_jsx(Portal.Root, { container: document.body, children: renderOptions() })) : (renderOptions()))] }));
|
|
86
|
+
return (_jsx(Combobox, { value: selectedOption, onChange: handleSelect, immediate: true, by: "value", disabled: disabled, children: ({ open }) => (_jsxs("div", { className: cn("relative", fullwidth && "w-full"), children: [_jsx(ComboboxInput, Object.assign({ as: TextInput, ref: inputRef, hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: open }) }), label: label, placeholder: " ", autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, displayValue: (opt) => { var _a; return (_a = opt === null || opt === void 0 ? void 0 : opt.label) !== null && _a !== void 0 ? _a : ""; }, readOnly: !filterMode, onChange: handleInputChange }, props)), _jsx(ComboboxOptions, { className: cn("absolute top-full left-0 w-full mt-1 z-[51]", "min-w-[154px] max-h-60 overflow-y-auto", "rounded-lg bg-modal-surface text-text-g-contrast-high", optionContainerClassName), style: { boxShadow: "var(--dropdown-menu-shadow)" }, children: renderOptionList() })] })) }));
|
|
197
87
|
});
|
|
88
|
+
Dropdown.displayName = "Dropdown";
|
|
198
89
|
export default Dropdown;
|
|
@@ -3,6 +3,7 @@ import { useRef, useState } from "react";
|
|
|
3
3
|
import Dropdown from "./Dropdown";
|
|
4
4
|
import Button from "../Button/Button";
|
|
5
5
|
import { cn } from "@/utils/cn";
|
|
6
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogTrigger, DialogClose, } from "../Dialog/Dialog";
|
|
6
7
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
|
7
8
|
const meta = {
|
|
8
9
|
title: "Components/Dropdown",
|
|
@@ -113,3 +114,31 @@ export const WithIcons = {
|
|
|
113
114
|
return (_jsx("div", { className: "flex flex-row gap-4 w-full", children: _jsx(Dropdown, { id: "with-icons", size: "lg", label: "Select Food", fullwidth: true, options: optionsWithIcons, value: selectedValue, onSelect: (option) => setSelectedValue(option) }) }));
|
|
114
115
|
},
|
|
115
116
|
};
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Dropdown inside Dialog — showcases the fix for overflow:hidden bug
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
const dialogOptions = [
|
|
121
|
+
{ value: "design", label: "Design" },
|
|
122
|
+
{ value: "engineering", label: "Engineering" },
|
|
123
|
+
{ value: "product", label: "Product" },
|
|
124
|
+
{ value: "marketing", label: "Marketing" },
|
|
125
|
+
{ value: "data", label: "Data & Analytics" },
|
|
126
|
+
{ value: "ops", label: "Operations" },
|
|
127
|
+
];
|
|
128
|
+
const filterableOptions = new Array(20).fill("").map((_, i) => ({
|
|
129
|
+
value: `member-${i + 1}`,
|
|
130
|
+
label: `Team Member ${i + 1}`,
|
|
131
|
+
}));
|
|
132
|
+
export const InsideDialog = {
|
|
133
|
+
name: "Inside Dialog",
|
|
134
|
+
render: () => {
|
|
135
|
+
const [department, setDepartment] = useState();
|
|
136
|
+
const [member, setMember] = useState();
|
|
137
|
+
const [role, setRole] = useState();
|
|
138
|
+
return (_jsxs("div", { className: "flex gap-4 flex-wrap", children: [_jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Single Dropdown" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Assign to Department" }), _jsx(DialogDescription, { children: "Dropdown popup appears above the dialog overlay \u2014 not clipped by overflow." })] }), _jsx(DialogBody, { className: "gap-4 py-2", children: _jsx(Dropdown, { id: "dept", label: "Department", size: "md", fullwidth: true, options: dialogOptions, value: department, onSelect: setDepartment }) }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { children: "Confirm" })] })] })] })] }), _jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Multiple Dropdowns" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Invite Team Member" }), _jsx(DialogDescription, { children: "Multiple dropdowns \u2014 each opens its own popup independently." })] }), _jsxs(DialogBody, { className: "gap-4 py-2", children: [_jsx(Dropdown, { id: "member", label: "Member", size: "md", fullwidth: true, options: filterableOptions, value: member, onSelect: setMember, filterMode: true }), _jsx(Dropdown, { id: "role", label: "Role", size: "md", fullwidth: true, options: [
|
|
139
|
+
{ value: "viewer", label: "Viewer" },
|
|
140
|
+
{ value: "editor", label: "Editor" },
|
|
141
|
+
{ value: "admin", label: "Admin" },
|
|
142
|
+
], value: role, onSelect: setRole })] }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { disabled: !member || !role, children: "Send Invite" })] })] })] })] }), _jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Filter Mode" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Search & Select" }), _jsx(DialogDescription, { children: "filterMode=true \u2014 type to filter options, popup stays properly positioned." })] }), _jsx(DialogBody, { className: "gap-4 py-2", children: _jsx(Dropdown, { id: "member-filter", label: "Search member", size: "md", fullwidth: true, filterMode: true, options: filterableOptions, value: member, onSelect: setMember }) }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { disabled: !member, children: "Select" })] })] })] })] })] }));
|
|
143
|
+
},
|
|
144
|
+
};
|
|
@@ -23,47 +23,58 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
|
23
23
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
24
24
|
const DropdownMenuSubTrigger = React.forwardRef((_a, ref) => {
|
|
25
25
|
var { className, inset, children } = _a, props = __rest(_a, ["className", "inset", "children"]);
|
|
26
|
-
return (_jsxs(DropdownMenuPrimitive.SubTrigger, Object.assign({ ref: ref, className: cn(
|
|
27
|
-
// "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-primary data-[state=open]:bg-primary",
|
|
28
|
-
"relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-4 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className) }, props, { children: [children, _jsx(Icon, { type: "heroicons", name: "chevron-right", className: "ml-auto h-4 w-4" })] })));
|
|
26
|
+
return (_jsxs(DropdownMenuPrimitive.SubTrigger, Object.assign({ ref: ref, className: cn("relative flex gap-4 cursor-pointer select-none box-border items-center py-4 pl-4 pr-4 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className) }, props, { children: [children, _jsx(Icon, { type: "heroicons", name: "chevron-right", className: "ml-auto h-4 w-4" })] })));
|
|
29
27
|
});
|
|
30
28
|
DropdownMenuSubTrigger.displayName =
|
|
31
29
|
DropdownMenuPrimitive.SubTrigger.displayName;
|
|
32
30
|
const DropdownMenuSubContent = React.forwardRef((_a, ref) => {
|
|
33
31
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
34
|
-
return (_jsx(DropdownMenuPrimitive.SubContent, Object.assign({ ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-
|
|
32
|
+
return (_jsx(DropdownMenuPrimitive.SubContent, Object.assign({ ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-lg bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })));
|
|
35
33
|
});
|
|
36
34
|
DropdownMenuSubContent.displayName =
|
|
37
35
|
DropdownMenuPrimitive.SubContent.displayName;
|
|
38
36
|
const DropdownMenuContent = React.forwardRef((_a, ref) => {
|
|
39
37
|
var { className, sideOffset = 4 } = _a, props = __rest(_a, ["className", "sideOffset"]);
|
|
40
|
-
return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, Object.assign({ ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[154px] overflow-hidden rounded-
|
|
38
|
+
return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, Object.assign({ ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[154px] overflow-hidden rounded-lg bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })) }));
|
|
41
39
|
});
|
|
42
40
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
43
41
|
const DropdownMenuItem = React.forwardRef((_a, ref) => {
|
|
44
|
-
var { className, inset } = _a, props = __rest(_a, ["className", "inset"]);
|
|
45
|
-
|
|
42
|
+
var { className, inset, selected, icon, children } = _a, props = __rest(_a, ["className", "inset", "selected", "icon", "children"]);
|
|
43
|
+
const hasIcon = !!icon;
|
|
44
|
+
return (_jsxs(DropdownMenuPrimitive.Item, Object.assign({ ref: ref, className: cn("relative flex cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", selected &&
|
|
45
|
+
"bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitle5", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className, hasIcon ? "gap-4" : "gap-1") }, props, { children: [_jsxs("div", { className: "flex shrink-0 flex-row gap-1", children: [_jsx("span", { className: "size-4 flex items-center justify-center", children: selected && (_jsx(Icon, { type: "heroicons", name: "check", className: "size-4 text-[var(--dropdown-menu-selected-text)]" })) }), icon] }), children] })));
|
|
46
46
|
});
|
|
47
47
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
48
48
|
const DropdownMenuCheckboxItem = React.forwardRef((_a, ref) => {
|
|
49
|
-
var { className, children, checked } = _a, props = __rest(_a, ["className", "children", "checked"]);
|
|
50
|
-
return (_jsxs(DropdownMenuPrimitive.CheckboxItem, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-
|
|
49
|
+
var { className, children, checked, disabled } = _a, props = __rest(_a, ["className", "children", "checked", "disabled"]);
|
|
50
|
+
return (_jsxs(DropdownMenuPrimitive.CheckboxItem, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", className), checked: checked, disabled: disabled }, props, { children: [_jsx("span", { className: cn("shrink-0 size-4 rounded-[2px] border flex items-center justify-center transition-all overflow-hidden", checked &&
|
|
51
|
+
!disabled &&
|
|
52
|
+
"bg-[var(--dropdown-menu-checkbox-checked-bg)] border-[var(--dropdown-menu-checkbox-checked-bg)]", checked &&
|
|
53
|
+
disabled &&
|
|
54
|
+
"bg-[var(--dropdown-menu-checkbox-disabled-checked-bg)] border-transparent", !checked &&
|
|
55
|
+
disabled &&
|
|
56
|
+
"border-[var(--dropdown-menu-checkbox-disabled-border)]", !checked &&
|
|
57
|
+
!disabled &&
|
|
58
|
+
"border-[var(--dropdown-menu-checkbox-border)]"), children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Icon, { type: "heroicons", name: "check", className: "size-3 text-[var(--dropdown-menu-checkbox-checked-icon)]" }) }) }), children] })));
|
|
51
59
|
});
|
|
52
60
|
DropdownMenuCheckboxItem.displayName =
|
|
53
61
|
DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
54
62
|
const DropdownMenuRadioItem = React.forwardRef((_a, ref) => {
|
|
55
|
-
var { className, children } = _a, props = __rest(_a, ["className", "children"]);
|
|
56
|
-
|
|
63
|
+
var { className, children, disabled, icon } = _a, props = __rest(_a, ["className", "children", "disabled", "icon"]);
|
|
64
|
+
const hasIconSlot = !!icon;
|
|
65
|
+
return (_jsxs(DropdownMenuPrimitive.RadioItem, Object.assign({ ref: ref, className: cn("relative flex cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-radio-disabled-text)]", "data-[state='checked']:data-[disabled]:!text-[var(--dropdown-menu-radio-selected-disabled-text)]", className, hasIconSlot ? "gap-4" : "gap-1") }, props, { disabled: disabled, children: [_jsxs("div", { className: "flex shrink-0 flex-row gap-1", children: [_jsx("span", { className: "size-4", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { className: "shrink-0", children: _jsx(Icon, { type: "heroicons", name: "check", className: cn("size-4", disabled
|
|
66
|
+
? "text-[var(--dropdown-menu-radio-selected-disabled-text)]"
|
|
67
|
+
: "text-[var(--dropdown-menu-selected-text)]") }) }) }), icon] }), children] })));
|
|
57
68
|
});
|
|
58
69
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
59
70
|
const DropdownMenuLabel = React.forwardRef((_a, ref) => {
|
|
60
71
|
var { className, inset } = _a, props = __rest(_a, ["className", "inset"]);
|
|
61
|
-
return (_jsx(DropdownMenuPrimitive.Label, Object.assign({ ref: ref, className: cn("px-3 py-2 typography-small4 text-text-g-contrast-
|
|
72
|
+
return (_jsx(DropdownMenuPrimitive.Label, Object.assign({ ref: ref, className: cn("px-3 py-2 typography-small4 text-text-g-contrast-high", inset && "pl-8", className) }, props)));
|
|
62
73
|
});
|
|
63
74
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
64
75
|
const DropdownMenuSeparator = React.forwardRef((_a, ref) => {
|
|
65
76
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
66
|
-
return (_jsx(DropdownMenuPrimitive.Separator, Object.assign({ ref: ref, className: cn("
|
|
77
|
+
return (_jsx(DropdownMenuPrimitive.Separator, Object.assign({ ref: ref, className: cn("h-px bg-[var(--dropdown-menu-seperator-bg)]", className) }, props)));
|
|
67
78
|
});
|
|
68
79
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
69
80
|
const DropdownMenuShortcut = (_a) => {
|