@rovula/ui 0.0.63 → 0.0.65
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 -20
- 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 +61 -22
|
@@ -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 = 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", "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) => {
|
|
@@ -52,26 +67,36 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
52
67
|
});
|
|
53
68
|
}, [options, filterMode, textValue]);
|
|
54
69
|
const updateDropdownPosition = useCallback(() => {
|
|
55
|
-
|
|
56
|
-
if (inputRef.current) {
|
|
70
|
+
if (inputRef.current && dropdownRef.current) {
|
|
57
71
|
const rect = inputRef.current.getBoundingClientRect();
|
|
58
|
-
const dropdownHeight =
|
|
72
|
+
const dropdownHeight = dropdownRef.current.offsetHeight;
|
|
59
73
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
60
74
|
const spaceAbove = rect.top;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
const shouldOpenAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
|
|
76
|
+
setIsAbove(shouldOpenAbove);
|
|
77
|
+
const usePortal = isInsideDialog ? false : modal;
|
|
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, // Ensure it's above everything
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
setDropdownStyles({
|
|
91
|
+
position: "absolute",
|
|
92
|
+
top: shouldOpenAbove ? `-${dropdownHeight}px` : "100%",
|
|
93
|
+
left: "0",
|
|
94
|
+
width: "100%",
|
|
95
|
+
zIndex: 10,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
73
98
|
}
|
|
74
|
-
}, []);
|
|
99
|
+
}, [modal, isInsideDialog]);
|
|
75
100
|
useEffect(() => {
|
|
76
101
|
if (isFocused) {
|
|
77
102
|
updateDropdownPosition();
|
|
@@ -93,7 +118,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
93
118
|
dropdownRef,
|
|
94
119
|
});
|
|
95
120
|
}
|
|
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-
|
|
121
|
+
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", isAbove ? "bottom-full mb-1" : "top-full mt-1", optionContainerClassName), style: dropdownStyles, ref: dropdownRef, children: [optionsFiltered.map((option) => {
|
|
97
122
|
if (option.renderLabel) {
|
|
98
123
|
return (_jsx(Fragment, { children: option.renderLabel({
|
|
99
124
|
value: option.value,
|
|
@@ -104,7 +129,9 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
104
129
|
}),
|
|
105
130
|
}) }, option.value));
|
|
106
131
|
}
|
|
107
|
-
return (_jsx("li", { onMouseDown: () =>
|
|
132
|
+
return (_jsx("li", { onMouseDown: () => {
|
|
133
|
+
handleOptionClick(option);
|
|
134
|
+
}, className: cn(`px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`, optionItemClassName, {
|
|
108
135
|
"bg-base-popup-highligh": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
|
|
109
136
|
}), children: option.label }, option.value));
|
|
110
137
|
}), optionsFiltered.length === 0 && (_jsx("li", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }))] }));
|
|
@@ -136,7 +163,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
136
163
|
}, [optionsFiltered, textValue]);
|
|
137
164
|
const handleOnBlur = useCallback((e) => {
|
|
138
165
|
var _a;
|
|
139
|
-
setIsFocused(false);
|
|
166
|
+
setTimeout(() => setIsFocused(false), 200);
|
|
140
167
|
clearMismatchValue(e);
|
|
141
168
|
(_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
142
169
|
}, [props === null || props === void 0 ? void 0 : props.onBlur, clearMismatchValue]);
|
|
@@ -145,6 +172,7 @@ const Dropdown = forwardRef((_a, ref) => {
|
|
|
145
172
|
keyCode.current = e.code;
|
|
146
173
|
(_a = props === null || props === void 0 ? void 0 : props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, e);
|
|
147
174
|
}, [props === null || props === void 0 ? void 0 : props.onKeyDown]);
|
|
148
|
-
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 &&
|
|
175
|
+
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 &&
|
|
176
|
+
((isInsideDialog ? false : modal) ? (_jsx(Portal.Root, { container: document.body, children: renderOptions() })) : (renderOptions()))] }));
|
|
149
177
|
});
|
|
150
178
|
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
|
}
|