@rovula/ui 0.0.64 → 0.0.66
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 +12 -0
- package/dist/cjs/bundle.js +1 -1
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +2 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
- package/dist/cjs/types/components/Search/Search.stories.d.ts +1 -0
- package/dist/cjs/types/components/Toast/Toast.stories.d.ts +1 -1
- package/dist/components/Dropdown/Dropdown.js +48 -22
- package/dist/esm/bundle.css +12 -0
- package/dist/esm/bundle.js +1 -1
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +2 -0
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
- package/dist/esm/types/components/Search/Search.stories.d.ts +1 -0
- package/dist/esm/types/components/Toast/Toast.stories.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/src/theme/global.css +16 -0
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.tsx +62 -25
|
@@ -24,6 +24,7 @@ export type DropdownProps = {
|
|
|
24
24
|
disabled?: boolean;
|
|
25
25
|
error?: boolean;
|
|
26
26
|
required?: boolean;
|
|
27
|
+
modal?: boolean;
|
|
27
28
|
className?: string;
|
|
28
29
|
optionContainerClassName?: string;
|
|
29
30
|
optionItemClassName?: string;
|
|
@@ -53,6 +54,7 @@ declare const Dropdown: React.ForwardRefExoticComponent<{
|
|
|
53
54
|
disabled?: boolean | undefined;
|
|
54
55
|
error?: boolean | undefined;
|
|
55
56
|
required?: boolean | undefined;
|
|
57
|
+
modal?: boolean | undefined;
|
|
56
58
|
className?: string | undefined;
|
|
57
59
|
optionContainerClassName?: string | undefined;
|
|
58
60
|
optionItemClassName?: string | undefined;
|
|
@@ -15,6 +15,7 @@ declare const meta: {
|
|
|
15
15
|
disabled?: boolean | undefined;
|
|
16
16
|
error?: boolean | undefined;
|
|
17
17
|
required?: boolean | undefined;
|
|
18
|
+
modal?: boolean | undefined;
|
|
18
19
|
className?: string | undefined;
|
|
19
20
|
optionContainerClassName?: string | undefined;
|
|
20
21
|
optionItemClassName?: string | undefined;
|
|
@@ -48,6 +49,7 @@ declare const meta: {
|
|
|
48
49
|
disabled?: boolean | undefined;
|
|
49
50
|
error?: boolean | undefined;
|
|
50
51
|
required?: boolean | undefined;
|
|
52
|
+
modal?: boolean | undefined;
|
|
51
53
|
className?: string | undefined;
|
|
52
54
|
optionContainerClassName?: string | undefined;
|
|
53
55
|
optionItemClassName?: string | undefined;
|
|
@@ -317,6 +317,7 @@ declare const meta: {
|
|
|
317
317
|
renderEndIcon?: (() => React.ReactNode) | undefined;
|
|
318
318
|
onClickStartIcon?: (() => void) | undefined;
|
|
319
319
|
options: Options[];
|
|
320
|
+
modal?: boolean | undefined;
|
|
320
321
|
onChangeText?: React.ChangeEventHandler<HTMLInputElement> | undefined;
|
|
321
322
|
renderOptions?: ((value: {
|
|
322
323
|
optionsFiltered: Options[];
|
|
@@ -15,7 +15,7 @@ declare const meta: {
|
|
|
15
15
|
id?: string | undefined;
|
|
16
16
|
lang?: string | undefined;
|
|
17
17
|
style?: React.CSSProperties | undefined;
|
|
18
|
-
type?: "
|
|
18
|
+
type?: "background" | "foreground" | undefined;
|
|
19
19
|
role?: React.AriaRole | undefined;
|
|
20
20
|
tabIndex?: number | undefined;
|
|
21
21
|
"aria-activedescendant"?: string | undefined;
|
|
@@ -17,7 +17,7 @@ import { customInputVariant, dropdownIconVariant, iconWrapperVariant, } from "./
|
|
|
17
17
|
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|
18
18
|
import { cn } from "@/utils/cn";
|
|
19
19
|
const Dropdown = forwardRef((_a, ref) => {
|
|
20
|
-
var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName"]);
|
|
20
|
+
var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal = false, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName } = _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"]);
|
|
21
21
|
const _id = id || `${label}-select`;
|
|
22
22
|
const [isFocused, setIsFocused] = useState(false);
|
|
23
23
|
const [selectedOption, setSelectedOption] = useState(null);
|
|
@@ -26,12 +26,26 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
26
26
|
const dropdownRef = useRef(null);
|
|
27
27
|
const inputRef = useRef(null);
|
|
28
28
|
const [dropdownStyles, setDropdownStyles] = useState({});
|
|
29
|
+
const [isAbove, setIsAbove] = useState(false);
|
|
30
|
+
const [isInsideDialog, setIsInsideDialog] = useState(false);
|
|
29
31
|
useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
|
|
30
32
|
useEffect(() => {
|
|
31
33
|
var _a;
|
|
32
34
|
setSelectedOption(value);
|
|
33
35
|
setTextValue((_a = value === null || value === void 0 ? void 0 : value.label) !== null && _a !== void 0 ? _a : "");
|
|
34
36
|
}, [value]);
|
|
37
|
+
/** ✅ Auto-detect if inside a Dialog */
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
let node = inputRef.current;
|
|
40
|
+
while (node) {
|
|
41
|
+
if (node.getAttribute("role") === "dialog") {
|
|
42
|
+
setIsInsideDialog(true);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
node = node.parentElement;
|
|
46
|
+
}
|
|
47
|
+
setIsInsideDialog(false);
|
|
48
|
+
}, []);
|
|
35
49
|
const handleOnChangeText = useCallback((event) => {
|
|
36
50
|
onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(event);
|
|
37
51
|
setTextValue(event.target.value);
|
|
@@ -43,6 +57,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
43
57
|
setSelectedOption(option);
|
|
44
58
|
setTextValue(option.label);
|
|
45
59
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
60
|
+
setIsFocused(false);
|
|
46
61
|
}, [onSelect]);
|
|
47
62
|
const optionsFiltered = useMemo(() => {
|
|
48
63
|
return options.filter((option) => {
|
|
@@ -51,36 +66,44 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
51
66
|
((_a = option.label) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(textValue === null || textValue === void 0 ? void 0 : textValue.toLowerCase()));
|
|
52
67
|
});
|
|
53
68
|
}, [options, filterMode, textValue]);
|
|
69
|
+
const usePortal = isInsideDialog ? false : modal;
|
|
54
70
|
const updateDropdownPosition = useCallback(() => {
|
|
55
|
-
|
|
56
|
-
if (inputRef.current) {
|
|
71
|
+
if (inputRef.current && dropdownRef.current) {
|
|
57
72
|
const rect = inputRef.current.getBoundingClientRect();
|
|
58
|
-
const dropdownHeight =
|
|
73
|
+
const dropdownHeight = dropdownRef.current.offsetHeight;
|
|
59
74
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
60
75
|
const spaceAbove = rect.top;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
left: `${rect.left
|
|
76
|
+
const shouldOpenAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
|
|
77
|
+
setIsAbove(shouldOpenAbove);
|
|
78
|
+
if (usePortal) {
|
|
79
|
+
setDropdownStyles({
|
|
80
|
+
position: "absolute",
|
|
81
|
+
top: shouldOpenAbove
|
|
82
|
+
? `${rect.top - dropdownHeight}px`
|
|
83
|
+
: `${rect.bottom}px`,
|
|
84
|
+
left: `${rect.left}px`,
|
|
70
85
|
width: `${rect.width}px`,
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
zIndex: 9999,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
setDropdownStyles({
|
|
91
|
+
position: "absolute",
|
|
92
|
+
top: shouldOpenAbove ? `-${dropdownHeight}px` : "100%",
|
|
93
|
+
left: "0",
|
|
94
|
+
width: "100%",
|
|
95
|
+
zIndex: 9999,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
73
98
|
}
|
|
74
|
-
}, []);
|
|
99
|
+
}, [modal, isInsideDialog, usePortal]);
|
|
75
100
|
useEffect(() => {
|
|
76
101
|
if (isFocused) {
|
|
77
102
|
updateDropdownPosition();
|
|
78
103
|
window.addEventListener("resize", updateDropdownPosition);
|
|
79
|
-
window.addEventListener("scroll", updateDropdownPosition, true);
|
|
80
104
|
}
|
|
81
105
|
return () => {
|
|
82
106
|
window.removeEventListener("resize", updateDropdownPosition);
|
|
83
|
-
window.removeEventListener("scroll", updateDropdownPosition, true);
|
|
84
107
|
};
|
|
85
108
|
}, [isFocused, updateDropdownPosition]);
|
|
86
109
|
const renderOptions = () => {
|
|
@@ -93,7 +116,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
93
116
|
dropdownRef,
|
|
94
117
|
});
|
|
95
118
|
}
|
|
96
|
-
return (_jsxs("ul", { className: cn("absolute mt-1 w-full bg-base-popup border border-base-popup text-base-popup-foreground rounded-md shadow-md z-
|
|
119
|
+
return (_jsxs("ul", { className: cn("absolute mt-1 w-full bg-base-popup border border-base-popup text-base-popup-foreground rounded-md shadow-md z-[9999] max-h-60 overflow-y-auto", !usePortal && (isAbove ? "bottom-full mb-1" : "top-full mt-1"), optionContainerClassName), style: dropdownStyles, ref: dropdownRef, children: [optionsFiltered.map((option) => {
|
|
97
120
|
if (option.renderLabel) {
|
|
98
121
|
return (_jsx(Fragment, { children: option.renderLabel({
|
|
99
122
|
value: option.value,
|
|
@@ -104,7 +127,9 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
104
127
|
}),
|
|
105
128
|
}) }, option.value));
|
|
106
129
|
}
|
|
107
|
-
return (_jsx("li", { onMouseDown: () =>
|
|
130
|
+
return (_jsx("li", { onMouseDown: () => {
|
|
131
|
+
handleOptionClick(option);
|
|
132
|
+
}, className: cn(`px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`, optionItemClassName, {
|
|
108
133
|
"bg-base-popup-highligh": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
|
|
109
134
|
}), children: option.label }, option.value));
|
|
110
135
|
}), optionsFiltered.length === 0 && (_jsx("li", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }))] }));
|
|
@@ -136,7 +161,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
136
161
|
}, [optionsFiltered, textValue]);
|
|
137
162
|
const handleOnBlur = useCallback((e) => {
|
|
138
163
|
var _a;
|
|
139
|
-
setIsFocused(false);
|
|
164
|
+
setTimeout(() => setIsFocused(false), 200);
|
|
140
165
|
clearMismatchValue(e);
|
|
141
166
|
(_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
142
167
|
}, [props === null || props === void 0 ? void 0 : props.onBlur, clearMismatchValue]);
|
|
@@ -145,6 +170,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
145
170
|
keyCode.current = e.code;
|
|
146
171
|
(_a = props === null || props === void 0 ? void 0 : props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
147
172
|
}, [props === null || props === void 0 ? void 0 : props.onKeyDown]);
|
|
148
|
-
return (_jsxs("div", { className: `relative
|
|
173
|
+
return (_jsxs("div", { className: `relative ${fullwidth ? "w-full" : ""}`, children: [_jsx(TextInput, Object.assign({ hasClearIcon: false, endIcon: _jsx("div", { className: iconWrapperVariant({ size }), children: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ size, 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: customInputVariant({ size }), onFocus: handleOnFocus, onBlur: handleOnBlur, onKeyDown: handleOnKeyDown })), isFocused &&
|
|
174
|
+
(usePortal ? (_jsx(Portal.Root, { container: document.body, children: renderOptions() })) : (renderOptions()))] }));
|
|
149
175
|
});
|
|
150
176
|
export default Dropdown;
|
package/dist/esm/bundle.css
CHANGED
|
@@ -599,6 +599,9 @@ input[type=number] {
|
|
|
599
599
|
.bottom-\[40px\]{
|
|
600
600
|
bottom: 40px;
|
|
601
601
|
}
|
|
602
|
+
.bottom-full{
|
|
603
|
+
bottom: 100%;
|
|
604
|
+
}
|
|
602
605
|
.left-0{
|
|
603
606
|
left: 0px;
|
|
604
607
|
}
|
|
@@ -638,6 +641,9 @@ input[type=number] {
|
|
|
638
641
|
.top-\[50\%\]{
|
|
639
642
|
top: 50%;
|
|
640
643
|
}
|
|
644
|
+
.top-full{
|
|
645
|
+
top: 100%;
|
|
646
|
+
}
|
|
641
647
|
.z-0{
|
|
642
648
|
z-index: 0;
|
|
643
649
|
}
|
|
@@ -650,6 +656,9 @@ input[type=number] {
|
|
|
650
656
|
.z-\[100\]{
|
|
651
657
|
z-index: 100;
|
|
652
658
|
}
|
|
659
|
+
.z-\[9999\]{
|
|
660
|
+
z-index: 9999;
|
|
661
|
+
}
|
|
653
662
|
.col-span-3{
|
|
654
663
|
grid-column: span 3 / span 3;
|
|
655
664
|
}
|
|
@@ -678,6 +687,9 @@ input[type=number] {
|
|
|
678
687
|
.-mt-\[30px\]{
|
|
679
688
|
margin-top: -30px;
|
|
680
689
|
}
|
|
690
|
+
.mb-1{
|
|
691
|
+
margin-bottom: 0.25rem;
|
|
692
|
+
}
|
|
681
693
|
.ml-2{
|
|
682
694
|
margin-left: 0.5rem;
|
|
683
695
|
}
|