@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.
@@ -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?: "foreground" | "background" | undefined;
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
- var _a;
56
- if (inputRef.current) {
70
+ if (inputRef.current && dropdownRef.current) {
57
71
  const rect = inputRef.current.getBoundingClientRect();
58
- const dropdownHeight = ((_a = dropdownRef.current) === null || _a === void 0 ? void 0 : _a.offsetHeight) || 0;
72
+ const dropdownHeight = dropdownRef.current.offsetHeight;
59
73
  const spaceBelow = window.innerHeight - rect.bottom;
60
74
  const spaceAbove = rect.top;
61
- const position = spaceBelow >= dropdownHeight || spaceBelow > spaceAbove
62
- ? {
63
- top: `${rect.bottom + window.scrollY}px`,
64
- left: `${rect.left + window.scrollX}px`,
65
- width: `${rect.width}px`,
66
- }
67
- : {
68
- top: `${rect.top - dropdownHeight + window.scrollY}px`,
69
- left: `${rect.left + window.scrollX}px`,
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
- setDropdownStyles(position);
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-10 max-h-60 overflow-y-auto", optionContainerClassName), style: dropdownStyles, ref: dropdownRef, children: [optionsFiltered.map((option) => {
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: () => handleOptionClick(option), className: cn(`px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`, optionItemClassName, {
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 && _jsx(Portal.Root, { children: renderOptions() })] }));
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;
@@ -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
  }