@rovula/ui 0.0.61 → 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.
- package/dist/cjs/bundle.js +3 -3
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +5 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
- package/dist/cjs/types/components/Search/Search.stories.d.ts +2 -0
- package/dist/cjs/types/components/Tabs/Tabs.d.ts +1 -0
- package/dist/cjs/types/components/Tabs/Tabs.stories.d.ts +2 -0
- package/dist/cjs/types/components/Tree/Tree.stories.d.ts +1 -0
- package/dist/cjs/types/components/Tree/type.d.ts +1 -0
- package/dist/cjs/types/hooks/index.d.ts +1 -0
- package/dist/cjs/types/hooks/usePrevious.d.ts +2 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/components/Dropdown/Dropdown.js +42 -3
- package/dist/components/Input/Input.js +2 -1
- package/dist/components/Tabs/Tabs.js +48 -44
- package/dist/components/Tree/Tree.js +26 -1
- package/dist/components/Tree/Tree.stories.js +12 -0
- package/dist/esm/bundle.js +3 -3
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +5 -1
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
- package/dist/esm/types/components/Search/Search.stories.d.ts +2 -0
- package/dist/esm/types/components/Tabs/Tabs.d.ts +1 -0
- package/dist/esm/types/components/Tabs/Tabs.stories.d.ts +2 -0
- package/dist/esm/types/components/Tree/Tree.stories.d.ts +1 -0
- package/dist/esm/types/components/Tree/type.d.ts +1 -0
- package/dist/esm/types/hooks/index.d.ts +1 -0
- package/dist/esm/types/hooks/usePrevious.d.ts +2 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/usePrevious.js +9 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +2 -0
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.tsx +54 -3
- package/src/components/Input/Input.tsx +2 -0
- package/src/components/Tabs/Tabs.tsx +57 -42
- package/src/components/Tree/Tree.stories.tsx +35 -0
- package/src/components/Tree/Tree.tsx +33 -0
- package/src/components/Tree/type.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePrevious.ts +13 -0
- package/src/index.ts +3 -0
|
@@ -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 @@ 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)[];
|
|
@@ -15,3 +15,4 @@ export declare const MaximumLevel: StoryObj<typeof Tree>;
|
|
|
15
15
|
export declare const Leaf: StoryObj<typeof Tree>;
|
|
16
16
|
export declare const HideCheckboxMode: StoryObj<typeof Tree>;
|
|
17
17
|
export declare const RadioMode: StoryObj<typeof Tree>;
|
|
18
|
+
export declare const CheckAll: StoryObj<typeof Tree>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as usePrevious } from "./usePrevious";
|
|
@@ -40,4 +40,5 @@ export type { NavbarProps } from "./components/Navbar/Navbar";
|
|
|
40
40
|
export type { AvatarProps } from "./components/Avatar/Avatar";
|
|
41
41
|
export type { AvatarGroupProps } from "./components/Avatar/AvatarGroup";
|
|
42
42
|
export { resloveTimestamp, getStartDateOfDay, getEndDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, } from "./utils/datetime";
|
|
43
|
+
export * from "./hooks";
|
|
43
44
|
export { cn } from "./utils/cn";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as usePrevious } from "./usePrevious";
|
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
|
|
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
|
|
|
@@ -710,6 +715,7 @@ interface TreeProps extends Pick<TreeItemProps, "renderIcon" | "renderRightSecti
|
|
|
710
715
|
maxLevel?: number;
|
|
711
716
|
mode?: "checkbox" | "radio";
|
|
712
717
|
autoDisabled?: boolean;
|
|
718
|
+
checkedAll?: boolean;
|
|
713
719
|
}
|
|
714
720
|
|
|
715
721
|
declare const Tree: FC<TreeProps>;
|
|
@@ -725,6 +731,8 @@ declare const getStartEndTimestampOfDay: () => {
|
|
|
725
731
|
};
|
|
726
732
|
declare const getTimestampUTC: (date: Date) => number;
|
|
727
733
|
|
|
734
|
+
declare function usePrevious<T>(value: T): T | undefined;
|
|
735
|
+
|
|
728
736
|
declare function cn(...inputs: ClassValue[]): string;
|
|
729
737
|
|
|
730
|
-
export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, DataTable, type DataTableProps, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, Navbar, type NavbarProps, type Options$1 as Options, Popover, PopoverContent, PopoverTrigger, ProgressBar, Search, type SearchProps, Slider, type SliderProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextInput, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, cn, getEndDateOfDay, getStartDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, reducer, resloveTimestamp, toast, useToast };
|
|
738
|
+
export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, DataTable, type DataTableProps, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, Navbar, type NavbarProps, type Options$1 as Options, Popover, PopoverContent, PopoverTrigger, ProgressBar, Search, type SearchProps, Slider, type SliderProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextInput, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, cn, getEndDateOfDay, getStartDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, reducer, resloveTimestamp, toast, usePrevious, useToast };
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ export * from "./components/Toast/useToast";
|
|
|
37
37
|
export * from "./components/Tree";
|
|
38
38
|
// UTILS
|
|
39
39
|
export { resloveTimestamp, getStartDateOfDay, getEndDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, } from "./utils/datetime";
|
|
40
|
+
// Hooks
|
|
41
|
+
export * from "./hooks";
|
|
40
42
|
export { cn } from "./utils/cn";
|
|
41
43
|
// const mainPreset = require("./theme/main-preset");
|
|
42
44
|
// export { mainPreset };
|
package/package.json
CHANGED
|
@@ -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={
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
92
|
-
|
|
96
|
+
if (isInitialMount.current) {
|
|
97
|
+
isInitialMount.current = false;
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// });
|
|
109
|
+
// Trigger reflow
|
|
110
|
+
timeout = setTimeout(() => {
|
|
111
|
+
updateSliderStyle();
|
|
112
|
+
}, 50);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
updateSliderStyle();
|
|
116
|
+
}
|
|
106
117
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// }, 50);
|
|
111
|
-
// }
|
|
112
|
-
// } else {
|
|
113
|
-
// updateSliderStyle();
|
|
114
|
-
// }
|
|
118
|
+
const handleResize = () => {
|
|
119
|
+
updateSliderStyle();
|
|
120
|
+
};
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
window.addEventListener("resize", handleResize);
|
|
123
|
+
return () => {
|
|
124
|
+
window.removeEventListener("resize", handleResize);
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
);
|
|
@@ -411,3 +411,38 @@ export const RadioMode: StoryObj<typeof Tree> = {
|
|
|
411
411
|
);
|
|
412
412
|
},
|
|
413
413
|
};
|
|
414
|
+
|
|
415
|
+
export const CheckAll: StoryObj<typeof Tree> = {
|
|
416
|
+
args: {
|
|
417
|
+
data: exampleData,
|
|
418
|
+
},
|
|
419
|
+
render: (args) => {
|
|
420
|
+
const [isCheckedAll, setIsCheckedAll] = useState(false);
|
|
421
|
+
const [checkedId, onCheckedId] = useState<string[]>([]);
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<div className="flex flex-col gap-4 w-full">
|
|
425
|
+
<div className="flex gap-2">
|
|
426
|
+
<Button
|
|
427
|
+
variant="outline"
|
|
428
|
+
onClick={() => setIsCheckedAll(!isCheckedAll)}
|
|
429
|
+
>
|
|
430
|
+
{isCheckedAll ? "Unchecked" : "Checked"} All
|
|
431
|
+
</Button>
|
|
432
|
+
<Button variant="outline" onClick={() => onCheckedId([])}>
|
|
433
|
+
Clear
|
|
434
|
+
</Button>
|
|
435
|
+
</div>
|
|
436
|
+
<Tree
|
|
437
|
+
{...args}
|
|
438
|
+
hierarchicalCheck
|
|
439
|
+
checkedAll={isCheckedAll}
|
|
440
|
+
checkedId={checkedId}
|
|
441
|
+
onCheckedChange={(state) => {
|
|
442
|
+
onCheckedId(Object.keys(state).filter((key) => state?.[key]));
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
</div>
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
};
|
|
@@ -2,6 +2,7 @@ import React, { FC, useCallback, useEffect, useState } from "react";
|
|
|
2
2
|
import TreeItem from "./TreeItem";
|
|
3
3
|
import { TreeData, TreeProps } from "./type";
|
|
4
4
|
import { cn } from "@/utils/cn";
|
|
5
|
+
import { usePrevious } from "@/hooks";
|
|
5
6
|
|
|
6
7
|
const Tree: FC<TreeProps> = ({
|
|
7
8
|
classes,
|
|
@@ -32,6 +33,7 @@ const Tree: FC<TreeProps> = ({
|
|
|
32
33
|
maxLevel,
|
|
33
34
|
mode = "checkbox",
|
|
34
35
|
autoDisabled = false,
|
|
36
|
+
checkedAll = false,
|
|
35
37
|
}) => {
|
|
36
38
|
const [checkedState, setCheckedState] = useState<Record<string, boolean>>({});
|
|
37
39
|
const [expandedState, setExpandedState] = useState<Record<string, boolean>>(
|
|
@@ -93,6 +95,7 @@ const Tree: FC<TreeProps> = ({
|
|
|
93
95
|
}
|
|
94
96
|
}, [checkedId]);
|
|
95
97
|
|
|
98
|
+
// For usecase loaded data a children coming after.
|
|
96
99
|
useEffect(() => {
|
|
97
100
|
if (!hierarchicalCheck) {
|
|
98
101
|
return;
|
|
@@ -124,6 +127,36 @@ const Tree: FC<TreeProps> = ({
|
|
|
124
127
|
});
|
|
125
128
|
}, [data, hierarchicalCheck]);
|
|
126
129
|
|
|
130
|
+
const prevCheckedAll = usePrevious(checkedAll);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (prevCheckedAll === undefined || prevCheckedAll === checkedAll) return;
|
|
133
|
+
if (checkedAll) {
|
|
134
|
+
const newState: Record<string, boolean> = {};
|
|
135
|
+
|
|
136
|
+
traverseTree(data, (node) => {
|
|
137
|
+
if (node.id) {
|
|
138
|
+
newState[node.id] = true;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
setCheckedState(newState);
|
|
142
|
+
onCheckedChange?.(newState, undefined, null);
|
|
143
|
+
} else {
|
|
144
|
+
let newState: Record<string, boolean> = {};
|
|
145
|
+
|
|
146
|
+
setCheckedState((prev) => {
|
|
147
|
+
newState = Object.keys(prev).reduce(
|
|
148
|
+
(p, key) => ({
|
|
149
|
+
...p,
|
|
150
|
+
[key]: false,
|
|
151
|
+
}),
|
|
152
|
+
{}
|
|
153
|
+
);
|
|
154
|
+
return newState;
|
|
155
|
+
});
|
|
156
|
+
onCheckedChange?.(newState, undefined, null);
|
|
157
|
+
}
|
|
158
|
+
}, [data, checkedAll, prevCheckedAll]);
|
|
159
|
+
|
|
127
160
|
const handleExpandChange = useCallback(
|
|
128
161
|
(id: string, expanded: boolean, itemData: any) => {
|
|
129
162
|
onExpandChange?.(id, expanded, itemData);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as usePrevious } from "./usePrevious";
|