@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;
package/dist/index.d.ts CHANGED
@@ -165,6 +165,7 @@ type DropdownProps = {
165
165
  disabled?: boolean;
166
166
  error?: boolean;
167
167
  required?: boolean;
168
+ modal?: boolean;
168
169
  className?: string;
169
170
  optionContainerClassName?: string;
170
171
  optionItemClassName?: string;
@@ -194,6 +195,7 @@ declare const Dropdown: React__default.ForwardRefExoticComponent<{
194
195
  disabled?: boolean | undefined;
195
196
  error?: boolean | undefined;
196
197
  required?: boolean | undefined;
198
+ modal?: boolean | undefined;
197
199
  className?: string | undefined;
198
200
  optionContainerClassName?: string | undefined;
199
201
  optionItemClassName?: string | undefined;
@@ -2260,6 +2260,10 @@ input[type=number] {
2260
2260
  bottom: 40px;
2261
2261
  }
2262
2262
 
2263
+ .bottom-full {
2264
+ bottom: 100%;
2265
+ }
2266
+
2263
2267
  .left-0 {
2264
2268
  left: 0px;
2265
2269
  }
@@ -2312,6 +2316,10 @@ input[type=number] {
2312
2316
  top: 50%;
2313
2317
  }
2314
2318
 
2319
+ .top-full {
2320
+ top: 100%;
2321
+ }
2322
+
2315
2323
  .z-0 {
2316
2324
  z-index: 0;
2317
2325
  }
@@ -2328,6 +2336,10 @@ input[type=number] {
2328
2336
  z-index: 100;
2329
2337
  }
2330
2338
 
2339
+ .z-\[9999\] {
2340
+ z-index: 9999;
2341
+ }
2342
+
2331
2343
  .col-span-3 {
2332
2344
  grid-column: span 3 / span 3;
2333
2345
  }
@@ -2364,6 +2376,10 @@ input[type=number] {
2364
2376
  margin-top: -30px;
2365
2377
  }
2366
2378
 
2379
+ .mb-1 {
2380
+ margin-bottom: 0.25rem;
2381
+ }
2382
+
2367
2383
  .ml-2 {
2368
2384
  margin-left: 0.5rem;
2369
2385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.0.63",
3
+ "version": "0.0.65",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,6 +47,7 @@ export type DropdownProps = {
47
47
  disabled?: boolean;
48
48
  error?: boolean;
49
49
  required?: boolean;
50
+ modal?: boolean;
50
51
  className?: string;
51
52
  optionContainerClassName?: string;
52
53
  optionItemClassName?: string;
@@ -81,6 +82,7 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
81
82
  error = false,
82
83
  filterMode = false,
83
84
  required = true,
85
+ modal = true,
84
86
  onChangeText,
85
87
  onSelect,
86
88
  renderOptions: customRenderOptions,
@@ -102,7 +104,9 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
102
104
 
103
105
  const dropdownRef = useRef<HTMLUListElement>(null);
104
106
  const inputRef = useRef<HTMLInputElement>(null);
105
- const [dropdownStyles, setDropdownStyles] = useState({});
107
+ const [dropdownStyles, setDropdownStyles] = useState<CSSProperties>({});
108
+ const [isAbove, setIsAbove] = useState(false);
109
+ const [isInsideDialog, setIsInsideDialog] = useState(false);
106
110
 
107
111
  useImperativeHandle(ref, () => inputRef?.current as HTMLInputElement);
108
112
 
@@ -111,6 +115,19 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
111
115
  setTextValue(value?.label ?? "");
112
116
  }, [value]);
113
117
 
118
+ /** ✅ Auto-detect if inside a Dialog */
119
+ useEffect(() => {
120
+ let node: HTMLElement | null = inputRef.current;
121
+ while (node) {
122
+ if (node.getAttribute("role") === "dialog") {
123
+ setIsInsideDialog(true);
124
+ return;
125
+ }
126
+ node = node.parentElement;
127
+ }
128
+ setIsInsideDialog(false);
129
+ }, []);
130
+
114
131
  const handleOnChangeText = useCallback(
115
132
  (event: React.ChangeEvent<HTMLInputElement>) => {
116
133
  onChangeText?.(event);
@@ -128,6 +145,7 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
128
145
  setSelectedOption(option);
129
146
  setTextValue(option.label);
130
147
  onSelect?.(option);
148
+ setIsFocused(false);
131
149
  },
132
150
  [onSelect]
133
151
  );
@@ -141,28 +159,39 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
141
159
  }, [options, filterMode, textValue]);
142
160
 
143
161
  const updateDropdownPosition = useCallback(() => {
144
- if (inputRef.current) {
162
+ if (inputRef.current && dropdownRef.current) {
145
163
  const rect = inputRef.current.getBoundingClientRect();
146
- const dropdownHeight = dropdownRef.current?.offsetHeight || 0;
164
+ const dropdownHeight = dropdownRef.current.offsetHeight;
147
165
  const spaceBelow = window.innerHeight - rect.bottom;
148
166
  const spaceAbove = rect.top;
149
167
 
150
- const position =
151
- spaceBelow >= dropdownHeight || spaceBelow > spaceAbove
152
- ? {
153
- top: `${rect.bottom + window.scrollY}px`,
154
- left: `${rect.left + window.scrollX}px`,
155
- width: `${rect.width}px`,
156
- }
157
- : {
158
- top: `${rect.top - dropdownHeight + window.scrollY}px`,
159
- left: `${rect.left + window.scrollX}px`,
160
- width: `${rect.width}px`,
161
- };
162
-
163
- setDropdownStyles(position);
168
+ const shouldOpenAbove =
169
+ spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
170
+ setIsAbove(shouldOpenAbove);
171
+
172
+ const usePortal = isInsideDialog ? false : modal;
173
+
174
+ if (usePortal) {
175
+ setDropdownStyles({
176
+ position: "absolute",
177
+ top: shouldOpenAbove
178
+ ? `${rect.top - dropdownHeight}px`
179
+ : `${rect.bottom}px`,
180
+ left: `${rect.left}px`,
181
+ width: `${rect.width}px`,
182
+ zIndex: 9999, // Ensure it's above everything
183
+ });
184
+ } else {
185
+ setDropdownStyles({
186
+ position: "absolute",
187
+ top: shouldOpenAbove ? `-${dropdownHeight}px` : "100%",
188
+ left: "0",
189
+ width: "100%",
190
+ zIndex: 10,
191
+ });
192
+ }
164
193
  }
165
- }, []);
194
+ }, [modal, isInsideDialog]);
166
195
 
167
196
  useEffect(() => {
168
197
  if (isFocused) {
@@ -191,7 +220,8 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
191
220
  return (
192
221
  <ul
193
222
  className={cn(
194
- "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",
223
+ "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",
224
+ isAbove ? "bottom-full mb-1" : "top-full mt-1",
195
225
  optionContainerClassName
196
226
  )}
197
227
  style={dropdownStyles}
@@ -220,7 +250,9 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
220
250
  return (
221
251
  <li
222
252
  key={option.value}
223
- onMouseDown={() => handleOptionClick(option)}
253
+ onMouseDown={() => {
254
+ handleOptionClick(option);
255
+ }}
224
256
  className={cn(
225
257
  `px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`,
226
258
  optionItemClassName,
@@ -289,7 +321,7 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
289
321
 
290
322
  const handleOnBlur = useCallback(
291
323
  (e: React.FocusEvent<HTMLInputElement, Element>) => {
292
- setIsFocused(false);
324
+ setTimeout(() => setIsFocused(false), 200);
293
325
  clearMismatchValue(e);
294
326
  props?.onBlur?.(e);
295
327
  },
@@ -339,7 +371,14 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
339
371
  onBlur={handleOnBlur}
340
372
  onKeyDown={handleOnKeyDown}
341
373
  />
342
- {isFocused && <Portal.Root>{renderOptions()}</Portal.Root>}
374
+ {isFocused &&
375
+ ((isInsideDialog ? false : modal) ? (
376
+ <Portal.Root container={document.body}>
377
+ {renderOptions()}
378
+ </Portal.Root>
379
+ ) : (
380
+ renderOptions()
381
+ ))}
343
382
  </div>
344
383
  );
345
384
  }