@rovula/ui 0.0.62 → 0.0.63

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.
@@ -1,4 +1,4 @@
1
- import React, { ReactNode } from "react";
1
+ import React, { CSSProperties, ReactNode } from "react";
2
2
  import { InputProps } from "../TextInput/TextInput";
3
3
  type RenderLabelCallbackArg = {
4
4
  value: string;
@@ -36,6 +36,8 @@ export type DropdownProps = {
36
36
  optionsFiltered: Options[];
37
37
  selectedOption: Options | null | undefined;
38
38
  onClick: (option: Options) => void;
39
+ style?: CSSProperties;
40
+ dropdownRef?: React.RefObject<HTMLUListElement>;
39
41
  }) => ReactNode;
40
42
  } & Omit<InputProps, "value" | "onSelect">;
41
43
  declare const Dropdown: React.ForwardRefExoticComponent<{
@@ -63,6 +65,8 @@ declare const Dropdown: React.ForwardRefExoticComponent<{
63
65
  optionsFiltered: Options[];
64
66
  selectedOption: Options | null | undefined;
65
67
  onClick: (option: Options) => void;
68
+ style?: CSSProperties;
69
+ dropdownRef?: React.RefObject<HTMLUListElement>;
66
70
  }) => ReactNode) | undefined;
67
71
  } & Omit<InputProps, "onSelect" | "value"> & React.RefAttributes<HTMLInputElement>>;
68
72
  export default Dropdown;
@@ -27,6 +27,8 @@ declare const meta: {
27
27
  optionsFiltered: Options[];
28
28
  selectedOption: Options | null | undefined;
29
29
  onClick: (option: Options) => void;
30
+ style?: React.CSSProperties | undefined;
31
+ dropdownRef?: React.RefObject<HTMLUListElement> | undefined;
30
32
  }) => React.ReactNode) | undefined;
31
33
  } & Omit<import("../..").InputProps, "onSelect" | "value"> & React.RefAttributes<HTMLInputElement>>;
32
34
  tags: string[];
@@ -58,6 +60,8 @@ declare const meta: {
58
60
  optionsFiltered: Options[];
59
61
  selectedOption: Options | null | undefined;
60
62
  onClick: (option: Options) => void;
63
+ style?: React.CSSProperties | undefined;
64
+ dropdownRef?: React.RefObject<HTMLUListElement> | undefined;
61
65
  }) => React.ReactNode) | undefined;
62
66
  suppressHydrationWarning?: boolean | undefined;
63
67
  color?: string | undefined;
@@ -322,6 +322,8 @@ declare const meta: {
322
322
  optionsFiltered: Options[];
323
323
  selectedOption: Options | null | undefined;
324
324
  onClick: (option: Options) => void;
325
+ style?: React.CSSProperties | undefined;
326
+ dropdownRef?: React.RefObject<HTMLUListElement> | undefined;
325
327
  }) => React.ReactNode) | undefined;
326
328
  optionContainerClassName?: string | undefined;
327
329
  optionItemClassName?: string | undefined;
@@ -30,6 +30,7 @@ type TabsProps = {
30
30
  leftAction?: React.ReactNode;
31
31
  rightAction?: React.ReactNode;
32
32
  disabled?: boolean;
33
+ keepMounted?: boolean;
33
34
  onAddTab?: () => void;
34
35
  onTabChange?: (tabIndex: number) => void;
35
36
  };
@@ -30,6 +30,7 @@ declare const meta: {
30
30
  leftAction?: React.ReactNode;
31
31
  rightAction?: React.ReactNode;
32
32
  disabled?: boolean | undefined;
33
+ keepMounted?: boolean | undefined;
33
34
  onAddTab?: (() => void) | undefined;
34
35
  onTabChange?: ((tabIndex: number) => void) | undefined;
35
36
  }>;
@@ -66,6 +67,7 @@ declare const meta: {
66
67
  leftAction?: React.ReactNode;
67
68
  rightAction?: React.ReactNode;
68
69
  disabled?: boolean | undefined;
70
+ keepMounted?: boolean | undefined;
69
71
  onAddTab?: (() => void) | undefined;
70
72
  onTabChange?: ((tabIndex: number) => void) | undefined;
71
73
  }>) => import("react/jsx-runtime").JSX.Element)[];
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { ReactElement, ReactNode, FC, ComponentPropsWithoutRef, CSSProperties } from 'react';
2
+ import React__default, { ReactElement, ReactNode, CSSProperties, FC, ComponentPropsWithoutRef } from 'react';
3
3
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
4
4
  import * as class_variance_authority_dist_types from 'class-variance-authority/dist/types';
5
5
  import * as LabelPrimitive from '@radix-ui/react-label';
@@ -135,6 +135,7 @@ type TabsProps = {
135
135
  leftAction?: React__default.ReactNode;
136
136
  rightAction?: React__default.ReactNode;
137
137
  disabled?: boolean;
138
+ keepMounted?: boolean;
138
139
  onAddTab?: () => void;
139
140
  onTabChange?: (tabIndex: number) => void;
140
141
  };
@@ -176,6 +177,8 @@ type DropdownProps = {
176
177
  optionsFiltered: Options$1[];
177
178
  selectedOption: Options$1 | null | undefined;
178
179
  onClick: (option: Options$1) => void;
180
+ style?: CSSProperties;
181
+ dropdownRef?: React__default.RefObject<HTMLUListElement>;
179
182
  }) => ReactNode;
180
183
  } & Omit<InputProps, "value" | "onSelect">;
181
184
  declare const Dropdown: React__default.ForwardRefExoticComponent<{
@@ -203,6 +206,8 @@ declare const Dropdown: React__default.ForwardRefExoticComponent<{
203
206
  optionsFiltered: Options$1[];
204
207
  selectedOption: Options$1 | null | undefined;
205
208
  onClick: (option: Options$1) => void;
209
+ style?: CSSProperties;
210
+ dropdownRef?: React__default.RefObject<HTMLUListElement>;
206
211
  }) => ReactNode) | undefined;
207
212
  } & Omit<InputProps, "onSelect" | "value"> & React__default.RefAttributes<HTMLInputElement>>;
208
213
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.0.62",
3
+ "version": "0.0.63",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,14 +1,16 @@
1
1
  import React, {
2
+ CSSProperties,
2
3
  Fragment,
3
4
  ReactNode,
4
5
  forwardRef,
5
6
  useCallback,
6
7
  useEffect,
8
+ useImperativeHandle,
7
9
  useMemo,
8
10
  useRef,
9
11
  useState,
10
12
  } from "react";
11
-
13
+ import * as Portal from "@radix-ui/react-portal";
12
14
  import TextInput, { InputProps } from "../TextInput/TextInput";
13
15
  import {
14
16
  customInputVariant,
@@ -57,6 +59,8 @@ export type DropdownProps = {
57
59
  optionsFiltered: Options[];
58
60
  selectedOption: Options | null | undefined;
59
61
  onClick: (option: Options) => void;
62
+ style?: CSSProperties;
63
+ dropdownRef?: React.RefObject<HTMLUListElement>;
60
64
  }) => ReactNode;
61
65
  } & Omit<InputProps, "value" | "onSelect">;
62
66
 
@@ -96,6 +100,12 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
96
100
  const [textValue, setTextValue] = useState("");
97
101
  const keyCode = useRef("");
98
102
 
103
+ const dropdownRef = useRef<HTMLUListElement>(null);
104
+ const inputRef = useRef<HTMLInputElement>(null);
105
+ const [dropdownStyles, setDropdownStyles] = useState({});
106
+
107
+ useImperativeHandle(ref, () => inputRef?.current as HTMLInputElement);
108
+
99
109
  useEffect(() => {
100
110
  setSelectedOption(value);
101
111
  setTextValue(value?.label ?? "");
@@ -130,12 +140,51 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
130
140
  );
131
141
  }, [options, filterMode, textValue]);
132
142
 
143
+ const updateDropdownPosition = useCallback(() => {
144
+ if (inputRef.current) {
145
+ const rect = inputRef.current.getBoundingClientRect();
146
+ const dropdownHeight = dropdownRef.current?.offsetHeight || 0;
147
+ const spaceBelow = window.innerHeight - rect.bottom;
148
+ const spaceAbove = rect.top;
149
+
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);
164
+ }
165
+ }, []);
166
+
167
+ useEffect(() => {
168
+ if (isFocused) {
169
+ updateDropdownPosition();
170
+ window.addEventListener("resize", updateDropdownPosition);
171
+ window.addEventListener("scroll", updateDropdownPosition, true);
172
+ }
173
+
174
+ return () => {
175
+ window.removeEventListener("resize", updateDropdownPosition);
176
+ window.removeEventListener("scroll", updateDropdownPosition, true);
177
+ };
178
+ }, [isFocused, updateDropdownPosition]);
179
+
133
180
  const renderOptions = () => {
134
181
  if (customRenderOptions) {
135
182
  return customRenderOptions({
136
183
  optionsFiltered,
137
184
  selectedOption,
138
185
  onClick: handleOptionClick,
186
+ style: dropdownStyles,
187
+ dropdownRef,
139
188
  });
140
189
  }
141
190
 
@@ -145,6 +194,8 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
145
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",
146
195
  optionContainerClassName
147
196
  )}
197
+ style={dropdownStyles}
198
+ ref={dropdownRef}
148
199
  >
149
200
  {optionsFiltered.map((option) => {
150
201
  if (option.renderLabel) {
@@ -265,7 +316,7 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
265
316
  </div>
266
317
  }
267
318
  {...props}
268
- ref={ref}
319
+ ref={inputRef}
269
320
  readOnly={!filterMode}
270
321
  value={textValue}
271
322
  onChange={handleOnChangeText}
@@ -288,7 +339,7 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
288
339
  onBlur={handleOnBlur}
289
340
  onKeyDown={handleOnKeyDown}
290
341
  />
291
- {isFocused && renderOptions()}
342
+ {isFocused && <Portal.Root>{renderOptions()}</Portal.Root>}
292
343
  </div>
293
344
  );
294
345
  }
@@ -21,6 +21,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
21
21
  type = "text",
22
22
  size = "md",
23
23
  variant = "outline",
24
+ rounded = "normal",
24
25
  fullwidth = false,
25
26
  disabled = false,
26
27
  error = false,
@@ -41,6 +42,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
41
42
  error,
42
43
  hiddenPlaceholder,
43
44
  disabled,
45
+ rounded,
44
46
  }),
45
47
  className
46
48
  )}
@@ -36,6 +36,7 @@ type TabsProps = {
36
36
  leftAction?: React.ReactNode;
37
37
  rightAction?: React.ReactNode;
38
38
  disabled?: boolean;
39
+ keepMounted?: boolean;
39
40
  onAddTab?: () => void;
40
41
  onTabChange?: (tabIndex: number) => void;
41
42
  };
@@ -49,6 +50,7 @@ const Tabs: React.FC<TabsProps> = ({
49
50
  enableAddTabButton = false,
50
51
  keepIconSpace = true,
51
52
  disabled = false,
53
+ keepMounted = false,
52
54
  tabMode = "start",
53
55
  className,
54
56
  tabBarClassName,
@@ -78,53 +80,54 @@ const Tabs: React.FC<TabsProps> = ({
78
80
  }
79
81
  }, [value]);
80
82
 
81
- // const updateSliderStyle = () => {
82
- // const activeTabElement = tabRefs.current[activeTab];
83
- // if (activeTabElement) {
84
- // setSliderStyle({
85
- // width: `${activeTabElement.offsetWidth}px`,
86
- // transform: `translateX(${activeTabElement.offsetLeft}px)`,
87
- // });
88
- // }
89
- // };
83
+ const updateSliderStyle = () => {
84
+ const activeTabElement = tabRefs.current[activeTab];
85
+ if (activeTabElement) {
86
+ setSliderStyle({
87
+ width: `${activeTabElement.offsetWidth}px`,
88
+ transform: `translateX(${activeTabElement.offsetLeft}px)`,
89
+ });
90
+ }
91
+ };
92
+
93
+ useEffect(() => {
94
+ let timeout: NodeJS.Timeout;
90
95
 
91
- // useEffect(() => {
92
- // let timer: NodeJS.Timeout;
96
+ if (isInitialMount.current) {
97
+ isInitialMount.current = false;
93
98
 
94
- // if (isInitialMount.current) {
95
- // isInitialMount.current = false;
99
+ // Set initial position without animation
100
+ const activeTabElement = tabRefs.current[activeTab];
101
+ if (activeTabElement) {
102
+ setSliderStyle({
103
+ width: "0px",
104
+ transform: `translateX(${
105
+ activeTabElement.offsetLeft + activeTabElement.offsetWidth / 2
106
+ }px)`,
107
+ });
96
108
 
97
- // // Set initial position without animation
98
- // const activeTabElement = tabRefs.current[activeTab];
99
- // if (activeTabElement) {
100
- // setSliderStyle({
101
- // width: "0px",
102
- // transform: `translateX(${
103
- // activeTabElement.offsetLeft + activeTabElement.offsetWidth / 2
104
- // }px)`,
105
- // });
109
+ // Trigger reflow
110
+ timeout = setTimeout(() => {
111
+ updateSliderStyle();
112
+ }, 50);
113
+ }
114
+ } else {
115
+ updateSliderStyle();
116
+ }
106
117
 
107
- // // Trigger reflow
108
- // timer = setTimeout(() => {
109
- // updateSliderStyle();
110
- // }, 50);
111
- // }
112
- // } else {
113
- // updateSliderStyle();
114
- // }
118
+ const handleResize = () => {
119
+ updateSliderStyle();
120
+ };
115
121
 
116
- // const handleResize = () => {
117
- // updateSliderStyle();
118
- // };
122
+ window.addEventListener("resize", handleResize);
123
+ return () => {
124
+ window.removeEventListener("resize", handleResize);
119
125
 
120
- // window.addEventListener("resize", handleResize);
121
- // return () => {
122
- // window.removeEventListener("resize", handleResize);
123
- // if (timer) {
124
- // clearTimeout(timer);
125
- // }
126
- // };
127
- // }, [activeTab, tabs, tabMode, keepIconSpace]);
126
+ if (timeout) {
127
+ clearTimeout(timeout);
128
+ }
129
+ };
130
+ }, [activeTab, tabs, tabMode, keepIconSpace]);
128
131
 
129
132
  return (
130
133
  <div className={cn("w-full", className)}>
@@ -240,7 +243,19 @@ const Tabs: React.FC<TabsProps> = ({
240
243
  id={`tab-content-${activeTab}`}
241
244
  aria-labelledby={`tab-${activeTab}`}
242
245
  >
243
- {tabs[activeTab]?.content}
246
+ {tabs.map((tab, idx) => {
247
+ if (!keepMounted && activeTab !== idx) {
248
+ return null;
249
+ }
250
+ return (
251
+ <div
252
+ key={tab.id ?? idx}
253
+ className={`transition ${activeTab === idx ? "block" : "hidden"}`}
254
+ >
255
+ {tab.content}
256
+ </div>
257
+ );
258
+ })}
244
259
  </div>
245
260
  </div>
246
261
  );