@meta-1/design 0.0.159

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.
Files changed (99) hide show
  1. package/README.md +412 -0
  2. package/package.json +138 -0
  3. package/src/assets/icons/empty.svg +1 -0
  4. package/src/assets/icons/spin.svg +1 -0
  5. package/src/assets/locales/en-us.ts +74 -0
  6. package/src/assets/locales/zh-cn.ts +74 -0
  7. package/src/assets/locales/zh-tw.ts +74 -0
  8. package/src/assets/style/theme.css +173 -0
  9. package/src/components/icons/Empty.tsx +18 -0
  10. package/src/components/icons/Spin.tsx +16 -0
  11. package/src/components/icons/index.ts +2 -0
  12. package/src/components/ui/alert-dialog.tsx +111 -0
  13. package/src/components/ui/alert.tsx +49 -0
  14. package/src/components/ui/avatar.tsx +32 -0
  15. package/src/components/ui/badge.tsx +36 -0
  16. package/src/components/ui/breadcrumb.tsx +92 -0
  17. package/src/components/ui/button.tsx +52 -0
  18. package/src/components/ui/calendar.tsx +56 -0
  19. package/src/components/ui/card.tsx +56 -0
  20. package/src/components/ui/checkbox.tsx +28 -0
  21. package/src/components/ui/command.tsx +137 -0
  22. package/src/components/ui/dialog.tsx +127 -0
  23. package/src/components/ui/dropdown-menu.tsx +217 -0
  24. package/src/components/ui/form.tsx +138 -0
  25. package/src/components/ui/hover-card.tsx +36 -0
  26. package/src/components/ui/input-otp.tsx +66 -0
  27. package/src/components/ui/input.tsx +21 -0
  28. package/src/components/ui/label.tsx +21 -0
  29. package/src/components/ui/navigation-menu.tsx +142 -0
  30. package/src/components/ui/pagination.tsx +118 -0
  31. package/src/components/ui/popover.tsx +40 -0
  32. package/src/components/ui/progress.tsx +22 -0
  33. package/src/components/ui/radio-group.tsx +31 -0
  34. package/src/components/ui/resizable.tsx +46 -0
  35. package/src/components/ui/scroll-area.tsx +46 -0
  36. package/src/components/ui/select.tsx +158 -0
  37. package/src/components/ui/separator.tsx +26 -0
  38. package/src/components/ui/sheet.tsx +101 -0
  39. package/src/components/ui/skeleton.tsx +7 -0
  40. package/src/components/ui/sonner.tsx +23 -0
  41. package/src/components/ui/switch.tsx +26 -0
  42. package/src/components/ui/table.tsx +73 -0
  43. package/src/components/ui/tabs.tsx +40 -0
  44. package/src/components/ui/textarea.tsx +18 -0
  45. package/src/components/ui/tooltip.tsx +46 -0
  46. package/src/components/uix/action/index.tsx +37 -0
  47. package/src/components/uix/alert/index.tsx +43 -0
  48. package/src/components/uix/alert-dialog/index.tsx +109 -0
  49. package/src/components/uix/avatar/index.tsx +25 -0
  50. package/src/components/uix/breadcrumbs/index.tsx +38 -0
  51. package/src/components/uix/broadcast-channel-context/index.tsx +28 -0
  52. package/src/components/uix/button/index.tsx +29 -0
  53. package/src/components/uix/card/index.tsx +32 -0
  54. package/src/components/uix/checkbox/index.tsx +79 -0
  55. package/src/components/uix/checkbox-group/index.tsx +60 -0
  56. package/src/components/uix/combo-select/index.tsx +364 -0
  57. package/src/components/uix/config-provider/index.tsx +31 -0
  58. package/src/components/uix/data-table/index.tsx +491 -0
  59. package/src/components/uix/data-table/style.css +40 -0
  60. package/src/components/uix/date-picker/index.tsx +88 -0
  61. package/src/components/uix/date-range-picker/index.tsx +71 -0
  62. package/src/components/uix/dialog/index.tsx +70 -0
  63. package/src/components/uix/divider/index.tsx +23 -0
  64. package/src/components/uix/dropdown/index.tsx +117 -0
  65. package/src/components/uix/empty/index.tsx +29 -0
  66. package/src/components/uix/filters/index.tsx +105 -0
  67. package/src/components/uix/form/index.tsx +274 -0
  68. package/src/components/uix/image/index.tsx +13 -0
  69. package/src/components/uix/loading/index.tsx +24 -0
  70. package/src/components/uix/message/index.tsx +21 -0
  71. package/src/components/uix/pagination/index.tsx +180 -0
  72. package/src/components/uix/radio-group/index.tsx +35 -0
  73. package/src/components/uix/result/index.tsx +45 -0
  74. package/src/components/uix/select/index.tsx +93 -0
  75. package/src/components/uix/space/index.tsx +24 -0
  76. package/src/components/uix/spin/index.tsx +12 -0
  77. package/src/components/uix/steps/index.tsx +67 -0
  78. package/src/components/uix/switch/index.tsx +33 -0
  79. package/src/components/uix/tooltip/index.tsx +29 -0
  80. package/src/components/uix/tree/index.tsx +39 -0
  81. package/src/components/uix/tree/style.css +75 -0
  82. package/src/components/uix/tree-select/index.tsx +137 -0
  83. package/src/components/uix/tree-table/action.tsx +24 -0
  84. package/src/components/uix/tree-table/config.ts +2 -0
  85. package/src/components/uix/tree-table/index.tsx +86 -0
  86. package/src/components/uix/tree-table/utils.tsx +63 -0
  87. package/src/components/uix/uploader/index.tsx +237 -0
  88. package/src/components/uix/uploader/type.ts +20 -0
  89. package/src/components/uix/uploader/utils.ts +41 -0
  90. package/src/components/uix/value-formatter/index.tsx +59 -0
  91. package/src/hooks/index.ts +2 -0
  92. package/src/hooks/resize.ts +29 -0
  93. package/src/hooks/use.outside.ts +30 -0
  94. package/src/index.ts +159 -0
  95. package/src/lib/formatters.ts +13 -0
  96. package/src/lib/index.ts +4 -0
  97. package/src/lib/is.ts +6 -0
  98. package/src/lib/react-dom.ts +98 -0
  99. package/src/lib/utils.ts +39 -0
@@ -0,0 +1,180 @@
1
+ import { type FC, useContext, useEffect, useState } from "react";
2
+ import get from "lodash/get";
3
+
4
+ import { Input } from "@meta-1/design/components/ui/input";
5
+ import {
6
+ PaginationContent,
7
+ PaginationEllipsis,
8
+ PaginationItem,
9
+ PaginationLink,
10
+ PaginationNext,
11
+ PaginationPrevious,
12
+ Pagination as ShadcnPagination,
13
+ } from "@meta-1/design/components/ui/pagination";
14
+ import { UIXContext } from "@meta-1/design/components/uix/config-provider";
15
+ import { Select } from "@meta-1/design/components/uix/select";
16
+
17
+ export interface PaginationProps {
18
+ total: number;
19
+ page: number;
20
+ size: number;
21
+ onChange?: (page: number) => void;
22
+ onSizeChange?: (size: number) => void;
23
+ sizeOptions?: number[];
24
+ }
25
+
26
+ export const SIZE_OPTIONS = [10, 20, 50, 100];
27
+
28
+ // 生成页码数组的辅助函数
29
+ const generatePageNumbers = (currentPage: number, totalPages: number) => {
30
+ const pages: (number | "ellipsis")[] = [];
31
+
32
+ if (totalPages <= 7) {
33
+ // 如果总页数小于等于7,显示所有页码
34
+ for (let i = 1; i <= totalPages; i++) {
35
+ pages.push(i);
36
+ }
37
+ } else {
38
+ // 总是显示第一页
39
+ pages.push(1);
40
+
41
+ if (currentPage <= 4) {
42
+ // 当前页在前面时
43
+ for (let i = 2; i <= 5; i++) {
44
+ pages.push(i);
45
+ }
46
+ pages.push("ellipsis");
47
+ pages.push(totalPages);
48
+ } else if (currentPage >= totalPages - 3) {
49
+ // 当前页在后面时
50
+ pages.push("ellipsis");
51
+ for (let i = totalPages - 4; i <= totalPages; i++) {
52
+ pages.push(i);
53
+ }
54
+ } else {
55
+ // 当前页在中间时
56
+ pages.push("ellipsis");
57
+ for (let i = currentPage - 1; i <= currentPage + 1; i++) {
58
+ pages.push(i);
59
+ }
60
+ pages.push("ellipsis");
61
+ pages.push(totalPages);
62
+ }
63
+ }
64
+
65
+ return pages;
66
+ };
67
+
68
+ export const Pagination: FC<PaginationProps> = (props) => {
69
+ const { total = 0, page = 1, size = 20, sizeOptions = SIZE_OPTIONS, onChange, onSizeChange } = props;
70
+
71
+ const config = useContext(UIXContext);
72
+ const totalPageText = get(config.locale, "Pagination.totalPage");
73
+ const totalText = get(config.locale, "Pagination.total");
74
+ const sizeText = get(config.locale, "Pagination.size");
75
+ const go = get(config.locale, "Pagination.go");
76
+ const goSuffix = get(config.locale, "Pagination.goSuffix");
77
+
78
+ const totalPage = Math.ceil(Number(total) / Number(size));
79
+ // biome-ignore lint/suspicious/noExplicitAny: <pageValue>
80
+ const [pageValue, setPageValue] = useState<any>(page);
81
+
82
+ useEffect(() => {
83
+ setPageValue(page);
84
+ }, [page]);
85
+
86
+ // biome-ignore lint/suspicious/noExplicitAny: <e>
87
+ const handleChange = (e: any) => {
88
+ let v = e.target.value.replace(/[^\d]/g, "");
89
+ if (v === "") {
90
+ setPageValue(v);
91
+ return;
92
+ }
93
+ if (v < 1) {
94
+ v = 1;
95
+ }
96
+ if (v > totalPage) {
97
+ v = totalPage;
98
+ }
99
+ setPageValue(v);
100
+ };
101
+
102
+ const pageNumbers = generatePageNumbers(page, totalPage);
103
+
104
+ return (
105
+ <div className="flex items-center justify-center space-x-6">
106
+ <ShadcnPagination>
107
+ {/* 总数信息 */}
108
+ <div className="flex items-center space-x-2 text-muted-foreground text-sm">
109
+ <span>{totalPageText?.replace("%total", `${total}`)}</span>
110
+ <span>{totalText?.replace("%page", `${totalPage}`)}</span>
111
+ </div>
112
+ {/* 分页导航 */}
113
+ <PaginationContent>
114
+ {/* 上一页 */}
115
+ <PaginationItem>
116
+ <PaginationPrevious
117
+ className={page === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
118
+ onClick={page === 1 ? undefined : () => onChange?.(page - 1)}
119
+ size="default"
120
+ />
121
+ </PaginationItem>
122
+
123
+ {/* 页码数字 */}
124
+ {pageNumbers.map((pageNum, index) => (
125
+ <PaginationItem key={pageNum === "ellipsis" ? `ellipsis-${index}` : `page-${pageNum}`}>
126
+ {pageNum === "ellipsis" ? (
127
+ <PaginationEllipsis />
128
+ ) : (
129
+ <PaginationLink
130
+ className="cursor-pointer"
131
+ isActive={pageNum === page}
132
+ onClick={() => onChange?.(pageNum)}
133
+ size="default"
134
+ >
135
+ {pageNum}
136
+ </PaginationLink>
137
+ )}
138
+ </PaginationItem>
139
+ ))}
140
+
141
+ {/* 下一页 */}
142
+ <PaginationItem>
143
+ <PaginationNext
144
+ className={page === totalPage ? "pointer-events-none opacity-50" : "cursor-pointer"}
145
+ onClick={page === totalPage ? undefined : () => onChange?.(page + 1)}
146
+ size="default"
147
+ />
148
+ </PaginationItem>
149
+ </PaginationContent>
150
+ {/* 控制区域 */}
151
+ <div className="flex items-center space-x-3">
152
+ {/* 页面大小选择 */}
153
+ <Select
154
+ className="w-auto"
155
+ onChange={(v) => onSizeChange?.(Number(v))}
156
+ options={sizeOptions.map((v) => ({ label: sizeText?.replace("%size", `${v}`) || "", value: `${v}` }))}
157
+ value={`${size}`}
158
+ />
159
+
160
+ {/* 跳转输入框 */}
161
+ <div className="flex items-center space-x-1 text-sm">
162
+ <span>{go}</span>
163
+ <Input
164
+ className="h-9 w-12 rounded px-2 text-center"
165
+ onChange={handleChange}
166
+ onKeyDown={(e) => {
167
+ if (e.key === "Enter") {
168
+ onChange?.(Number(pageValue));
169
+ }
170
+ }}
171
+ type="text"
172
+ value={pageValue}
173
+ />
174
+ <span>{goSuffix}</span>
175
+ </div>
176
+ </div>
177
+ </ShadcnPagination>
178
+ </div>
179
+ );
180
+ };
@@ -0,0 +1,35 @@
1
+ import { type FC, forwardRef, type PropsWithChildren, useState } from "react";
2
+
3
+ import { Label } from "@meta-1/design/components/ui/label";
4
+ import { RadioGroupItem, RadioGroup as UIRadioGroup } from "@meta-1/design/components/ui/radio-group";
5
+
6
+ export interface SimpleRadioGroupOptionProps {
7
+ label: string;
8
+ value: string;
9
+ }
10
+
11
+ export interface SimpleRadioGroupProps extends PropsWithChildren {
12
+ value?: string;
13
+ onChange?: (value: string) => void;
14
+ options?: SimpleRadioGroupOptionProps[];
15
+ }
16
+
17
+ export const SimpleRadioGroup: FC<SimpleRadioGroupProps> = forwardRef((props, _ref) => {
18
+ const { options = [], value, onChange } = props;
19
+
20
+ const [labelKey] = useState(Date.now());
21
+
22
+ return (
23
+ <UIRadioGroup onValueChange={onChange} value={value}>
24
+ {options.map((option) => {
25
+ const id = `${labelKey}-${option.value}`;
26
+ return (
27
+ <div className="flex items-center space-x-2" key={option.value}>
28
+ <RadioGroupItem id={id} value={option.value} />
29
+ <Label htmlFor={id}>{option.label}</Label>
30
+ </div>
31
+ );
32
+ })}
33
+ </UIRadioGroup>
34
+ );
35
+ });
@@ -0,0 +1,45 @@
1
+ import type { FC, PropsWithChildren, ReactNode } from "react";
2
+ import { CheckIcon, Cross2Icon, ExclamationTriangleIcon, InfoCircledIcon } from "@radix-ui/react-icons";
3
+
4
+ import { cn } from "@meta-1/design/lib";
5
+
6
+ export interface ResultProps extends PropsWithChildren {
7
+ status: "success" | "error" | "info" | "warning";
8
+ title?: string;
9
+ subTitle?: string;
10
+ extra?: ReactNode;
11
+ }
12
+
13
+ const ICON_MAP = {
14
+ success: CheckIcon,
15
+ error: Cross2Icon,
16
+ info: InfoCircledIcon,
17
+ warning: ExclamationTriangleIcon,
18
+ };
19
+
20
+ export const Result: FC<ResultProps> = (props) => {
21
+ const { status = "info", title, subTitle, extra } = props;
22
+
23
+ const Icon = ICON_MAP[status];
24
+
25
+ return (
26
+ <div className="flex flex-col items-center justify-center">
27
+ {Icon ? (
28
+ <div
29
+ className={cn(
30
+ "flex h-12 w-12 items-center justify-center rounded-full",
31
+ status === "success" ? "bg-success text-success-foreground" : null,
32
+ status === "error" ? "bg-error text-error-foreground" : null,
33
+ status === "info" ? "bg-secondary text-secondary-foreground" : null,
34
+ status === "warning" ? "bg-warning text-warning-foreground" : null,
35
+ )}
36
+ >
37
+ <Icon className="h-6 w-6" />
38
+ </div>
39
+ ) : null}
40
+ {title ? <div className="mt-4 text-lg">{title}</div> : null}
41
+ {subTitle ? <div className="mt-4 text-gray-500">{subTitle}</div> : null}
42
+ {extra ? <div className="mt-4">{extra}</div> : null}
43
+ </div>
44
+ );
45
+ };
@@ -0,0 +1,93 @@
1
+ import { forwardRef, type MouseEvent, type ReactNode, useState } from "react";
2
+ import { XIcon } from "lucide-react";
3
+
4
+ import { Empty } from "@meta-1/design/components/uix/empty";
5
+ import { cn } from "@meta-1/design/lib";
6
+ import { SelectContent, SelectItem, SelectTrigger, SelectValue, Select as UISelect } from "../../ui/select";
7
+
8
+ export interface SelectOptionProps {
9
+ value: string;
10
+ label: ReactNode;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export interface SelectProps {
15
+ options: SelectOptionProps[];
16
+ value?: string;
17
+ defaultValue?: string;
18
+ onChange?: (value: string) => void;
19
+ placeholder?: string;
20
+ className?: string;
21
+ side?: "top" | "right" | "bottom" | "left";
22
+ sideOffset?: number;
23
+ align?: "start" | "center" | "end";
24
+ alignOffset?: number;
25
+ empty?: ReactNode;
26
+ allowClear?: boolean;
27
+ triggerClassName?: string;
28
+ }
29
+
30
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) => {
31
+ const {
32
+ options = [],
33
+ value,
34
+ defaultValue,
35
+ onChange,
36
+ placeholder,
37
+ className,
38
+ empty,
39
+ allowClear,
40
+ triggerClassName,
41
+ ...rest
42
+ } = props;
43
+
44
+ const [innerValue, setInnerValue] = useState<string | undefined>(defaultValue);
45
+ const isControlled = value !== undefined;
46
+ const currentValue = isControlled ? value : innerValue;
47
+
48
+ const handleChange = (val: string) => {
49
+ if (onChange) {
50
+ onChange(val);
51
+ }
52
+ if (!isControlled) {
53
+ setInnerValue(val);
54
+ }
55
+ };
56
+
57
+ const clear = (e: MouseEvent<HTMLDivElement>) => {
58
+ e.stopPropagation();
59
+ handleChange("");
60
+ };
61
+
62
+ return (
63
+ <UISelect {...rest} defaultValue={defaultValue} onValueChange={handleChange} value={currentValue}>
64
+ <div className={cn("relative inline-block", allowClear && currentValue ? "group" : "", className)}>
65
+ <SelectTrigger className={cn("flex w-full", triggerClassName)}>
66
+ <SelectValue placeholder={placeholder} />
67
+ </SelectTrigger>
68
+ {allowClear && currentValue && (
69
+ <div
70
+ className="absolute top-0 right-0 z-50 hidden h-full cursor-pointer items-center justify-center px-3 group-hover:flex"
71
+ onClick={clear}
72
+ >
73
+ <XIcon className="size-4 opacity-50" />
74
+ </div>
75
+ )}
76
+ </div>
77
+ <SelectContent
78
+ align={props.align}
79
+ alignOffset={props.alignOffset}
80
+ side={props.side}
81
+ sideOffset={props.sideOffset}
82
+ >
83
+ {options?.length
84
+ ? options.map((option) => (
85
+ <SelectItem disabled={option.disabled} key={option.value} value={option.value}>
86
+ {option.label}
87
+ </SelectItem>
88
+ ))
89
+ : empty || <Empty />}
90
+ </SelectContent>
91
+ </UISelect>
92
+ );
93
+ });
@@ -0,0 +1,24 @@
1
+ import type { FC, PropsWithChildren } from "react";
2
+
3
+ import { cn } from "@meta-1/design/lib";
4
+
5
+ export interface SpaceProps extends PropsWithChildren {
6
+ className?: string;
7
+ direction?: "horizontal" | "vertical";
8
+ }
9
+
10
+ export const Space: FC<SpaceProps> = (props) => {
11
+ const { direction = "horizontal", className } = props;
12
+ return (
13
+ <div
14
+ className={cn(
15
+ "flex items-start justify-start",
16
+ direction === "vertical" ? "flex-col space-y-2" : null,
17
+ direction === "horizontal" ? "flex-row items-start space-x-2" : null,
18
+ className,
19
+ )}
20
+ >
21
+ {props.children}
22
+ </div>
23
+ );
24
+ };
@@ -0,0 +1,12 @@
1
+ import type { FC } from "react";
2
+
3
+ import { Spin as IconSpin } from "@meta-1/design/components/icons";
4
+ import { cn } from "@meta-1/design/lib";
5
+
6
+ export type SpinProps = {
7
+ className?: string;
8
+ };
9
+
10
+ export const Spin: FC<SpinProps> = (props) => {
11
+ return <IconSpin className={cn("animate-spin", props.className)} />;
12
+ };
@@ -0,0 +1,67 @@
1
+ import { Children, cloneElement, type FC, type PropsWithChildren, type ReactElement } from "react";
2
+ import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons";
3
+
4
+ import { cn } from "@meta-1/design/lib";
5
+
6
+ export interface StepsProps extends PropsWithChildren {
7
+ current?: number;
8
+ className?: string;
9
+ }
10
+
11
+ export interface StepsItemProps extends PropsWithChildren {
12
+ title: string;
13
+ description?: string;
14
+ className?: string;
15
+ status?: "wait" | "process" | "finish" | "error";
16
+ index?: number;
17
+ last?: boolean;
18
+ }
19
+
20
+ export const StepsItem: FC<StepsItemProps> = (props) => {
21
+ const { index, status, last, description } = props;
22
+ return (
23
+ <div className={cn("flex", last ? null : "flex-1")}>
24
+ <div
25
+ className={cn(
26
+ "mr-2 flex h-8 w-8 items-center justify-center rounded-full",
27
+ status === "wait" ? "bg-secondary text-secondary-foreground" : null,
28
+ status === "process" ? "bg-primary text-primary-foreground" : null,
29
+ status === "finish" ? "bg-success text-success-foreground" : null,
30
+ )}
31
+ >
32
+ {status === "finish" ? <CheckIcon /> : null}
33
+ {status === "error" ? <Cross2Icon /> : null}
34
+ {["wait", "process"].includes(status!) ? index! + 1 : null}
35
+ </div>
36
+ <div>
37
+ <div className="leading-8">{props.title}</div>
38
+ {description ? <div>{description}</div> : null}
39
+ </div>
40
+ {last ? null : (
41
+ <div className="mx-2 flex h-8 flex-1 items-center justify-center">
42
+ <div className={cn("h-px w-full bg-secondary", status === "finish" ? "bg-primary" : null)} />
43
+ </div>
44
+ )}
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export const Steps: FC<StepsProps> = (props) => {
50
+ const { current } = props;
51
+ const size = Children.count(props.children);
52
+ return (
53
+ <div className={cn("flex", props.className)}>
54
+ {Children.map(props.children, (child, index) => {
55
+ const c = child as ReactElement;
56
+ // biome-ignore lint/suspicious/noExplicitAny: <props>
57
+ return cloneElement<any>(c, {
58
+ // biome-ignore lint/suspicious/noExplicitAny: <props>
59
+ ...(c as any).props,
60
+ index,
61
+ last: index === size - 1,
62
+ status: index < current! ? "finish" : index === current ? "process" : "wait",
63
+ });
64
+ })}
65
+ </div>
66
+ );
67
+ };
@@ -0,0 +1,33 @@
1
+ import { type FC, forwardRef, useState } from "react";
2
+
3
+ import { Switch as UISwitch } from "@meta-1/design/components/ui/switch";
4
+
5
+ export interface SwitchProps {
6
+ value?: boolean;
7
+ onChange?: (value: boolean) => void;
8
+ readonly?: boolean;
9
+ }
10
+
11
+ export const Switch: FC<SwitchProps> = forwardRef((props, _ref) => {
12
+ const { onChange, readonly = false } = props;
13
+
14
+ const [checked, setChecked] = useState(props.value);
15
+
16
+ const core = (
17
+ <UISwitch
18
+ checked={checked}
19
+ onCheckedChange={(checked) => {
20
+ setChecked(checked);
21
+ onChange?.(checked);
22
+ }}
23
+ />
24
+ );
25
+ return readonly ? (
26
+ <div className="relative inline-block">
27
+ {core}
28
+ <div className="absolute top-0 right-0 bottom-0 left-0" />
29
+ </div>
30
+ ) : (
31
+ core
32
+ );
33
+ });
@@ -0,0 +1,29 @@
1
+ import type { FC, PropsWithChildren, ReactNode } from "react";
2
+
3
+ import {
4
+ TooltipContent,
5
+ TooltipProvider,
6
+ TooltipTrigger,
7
+ Tooltip as TooltipUI,
8
+ } from "@meta-1/design/components/ui/tooltip";
9
+
10
+ type Side = "top" | "right" | "bottom" | "left";
11
+
12
+ export type TooltipProps = PropsWithChildren & {
13
+ content: ReactNode;
14
+ side?: Side;
15
+ open?: boolean;
16
+ onOpenChange?: (open: boolean) => void;
17
+ };
18
+
19
+ export const Tooltip: FC<TooltipProps> = (props) => {
20
+ const { open, onOpenChange } = props;
21
+ return (
22
+ <TooltipProvider>
23
+ <TooltipUI onOpenChange={onOpenChange} open={open}>
24
+ <TooltipTrigger asChild={true}>{props.children}</TooltipTrigger>
25
+ <TooltipContent side={props.side}>{props.content}</TooltipContent>
26
+ </TooltipUI>
27
+ </TooltipProvider>
28
+ );
29
+ };
@@ -0,0 +1,39 @@
1
+ import RCTree, { type BasicDataNode, type TreeProps as RCTreeProps, type TreeNodeProps } from "rc-tree";
2
+ import "rc-tree/assets/index.css";
3
+ import "./style.css";
4
+
5
+ import type { FC, ReactNode } from "react";
6
+ import { TriangleDownIcon, TriangleRightIcon } from "@radix-ui/react-icons";
7
+
8
+ export type TreeData = {
9
+ key: string;
10
+ title: ReactNode;
11
+ children?: TreeData[];
12
+ } & BasicDataNode;
13
+
14
+ export type TreeProps = Omit<RCTreeProps<TreeData>, "prefixCls"> & {
15
+ prefixCls?: string;
16
+ };
17
+
18
+ export const Tree: FC<TreeProps> = (props) => {
19
+ return (
20
+ <RCTree<TreeData>
21
+ dropIndicatorRender={({ dropPosition, dropLevelOffset, indent }) => {
22
+ return (
23
+ <div
24
+ className="pointer-events-none absolute right-1 rounded-sm bg-primary"
25
+ style={{ left: indent * -(dropLevelOffset + dropPosition), bottom: -13, height: 3 }}
26
+ />
27
+ );
28
+ }}
29
+ prefixCls="rc-tree"
30
+ showIcon={false}
31
+ switcherIcon={(props: TreeNodeProps) => {
32
+ const { expanded, isLeaf } = props;
33
+ if (isLeaf) return null;
34
+ return expanded ? <TriangleDownIcon className="h-5 w-5" /> : <TriangleRightIcon className="h-5 w-5" />;
35
+ }}
36
+ {...props}
37
+ />
38
+ );
39
+ };
@@ -0,0 +1,75 @@
1
+ @import "../../../assets/style/theme.css";
2
+
3
+ .rc-tree-focused:not(.rc-tree-active-focused) {
4
+ @apply border-none;
5
+ }
6
+
7
+ .rc-tree .rc-tree-treenode {
8
+ @apply w-full px-2 py-1.5 my-0.5 rounded-sm relative;
9
+ @apply action-effect;
10
+ @apply action-effect-active;
11
+ @apply flex justify-start items-center;
12
+ }
13
+
14
+ .rc-tree .rc-tree-treenode span.rc-tree-switcher,
15
+ .rc-tree .rc-tree-treenode span.rc-tree-iconEle {
16
+ @apply bg-none w-5 h-5 mr-0 ml-0.5;
17
+ }
18
+
19
+ .rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper {
20
+ @apply ml-2 flex-1 h-auto w-0 flex-shrink-0;
21
+ }
22
+
23
+ .rc-tree .rc-tree-node-selected {
24
+ @apply bg-transparent shadow-none opacity-100;
25
+ }
26
+
27
+ .rc-tree .rc-tree-treenode.drop-target {
28
+ @apply bg-primary/20;
29
+ }
30
+
31
+ .rc-tree .rc-tree-treenode.drop-container ~ .rc-tree-treenode {
32
+ @apply border-none;
33
+ }
34
+
35
+ .rc-tree .rc-tree-treenode.dragging {
36
+ @apply opacity-50;
37
+ }
38
+
39
+ .rc-tree .rc-tree-title {
40
+ @apply w-full;
41
+ }
42
+
43
+ .rc-tree .rc-tree-treenode-selected {
44
+ @apply action-active;
45
+ }
46
+
47
+ .rc-tree .rc-tree-treenode span.rc-tree-checkbox {
48
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAC1klEQVRYhe2WT0iTYRzHv77vu3eHOmQLdmiwXEq1KW7GazYjyg2ZnraE6BIdtDYRBOkghII38aBCFLbcDpKH6hAesoaoRKQR4lLcv2ybe2Mddhi79Idt79vTZVsvFZG4vad94Xf6HT4feHgenm8NIQTSbH8M6X0br/s2PwS6kulUnSCKShwgDE1nNSp1/OypRp+Nu+hpbtCHpPuaokA+n2cnn3qnnr1Z6gdAHQT6j/y4cqFz9vbV3iGFQvG9JJDNZtnhhxOL66EtKwDc6LTD0tIeqtdoPQzNvASwByC7T5gSgE4QBVs0yfet+Nf0c0sLAACz3rg6cWu4S6lU5kAIwfj8zH3O5SA9o/0knIhmCSEDhBCKEIIyDUUIcYYT0W89o/2EcznI+PzMPUIIsBnZMXAuh8C5HEW4tYzg36cjnIhmOZeDcC6HsBnZMdB1bU13wp/i5huddnS3XR4E8KRsJ/5n9o4dOZrJ5bPd27EIxTJMjvLvBm0AYGlpDwJwVxBezIMCC/7doI1KplM6AKjXaD0ARBkExAILyXRKRwmiyAIAQzM+GeCQsgRRZKX3PS6XgJQlFcjJKFBiVerF++9UBaoCVYGqQFVAKsDKyD38NwGdjALakgBD0zkAEETBJhddEAUrADA0/YXSqNRxAIgm+V4AtAx8OprkewBAo1LzlKlB7wOAFf9aIwCnDALOAgumBv0yZTGZvQDEuaUFRPjYNABrBeEdET42XSgoosVk9lCtBmPAbra4AWDEO8lG+NgigAGU94pSAJwRPvZ8xDvJAoDdbHG3GoyBGkIIMpkMO/bo7uLb8La0mgXrNdrZwgcyDiC/T+ghACcEUbAWqlljsZqdP9O8OnZ9sKu2tjZXKqfpdJp1v3g8tbC+UtFyajdbZp3d14ZUKtWvcirN2vsNw6addze3YhHb53RKJ4ii4iBEhqa/HlepE8aTp5cvNZ3ztJu4gHT/E4eYrk2ZjgXVAAAAAElFTkSuQmCC");
49
+ @apply bg-contain w-4 h-4 bg-center;
50
+ }
51
+
52
+ .rc-tree .rc-tree-treenode span.rc-tree-checkbox.rc-tree-checkbox-checked {
53
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAACwUlEQVRYhcWXX0hTURzHvzv792puhiYEDqd1LzrpbtbKNETBzZcGjt4SY4IVWwwKogeX0kNQrtowFGdB0EMW1Evmw5AiQqmMrfyXhhaUBWtRYNn0uvWgW3eic7tz1y9cOHB+934/vy/3nnuOKBKJgCvf1Dj15OVTy5upMcOXYKCADbNypCEJkYTyFTkz+4roAUP5EU9ZETXOnRdFAZaWlmRX7/U4H77wngRA0jFNoLDpUE3P2WPNdqlUuhADCIVCsnNdlx8PT/prMmQcpwN7NINXWs4b5HL5IgGAjj7PNaHMAWB40l/d0dfrBADRyMRbuuWGww9ALBTAqpa7zrRpxLvLqQvvP88eFNgcAIiYkEXxjpKC67/+zCu3AQALob9ZZC4YUGXKgFHTCefnggEVYcOsLBPmFqMZLmsrOq2ODWvYMCvLyPfOqGk01TUAAMrUVMLajACcWDUHgNsDD4QFsBjNsa590+Pw9N8XDoAbPQDc2qT7LQdYG/3I9JhwAKlGv6UAfKJPGsBiNG+6oPCJPimAaGcuayssRvOGNXyiTwqA20lTXcO6q5rL2hobpxJ9UgAAYHO3x8ZlaioOgjtONfqoxPjavRcTFXz9EYD/wwTysnOQq1i5mEIau5Q7YdhfBWAl+kt3b6ZsDgAiXYspsnnZijqtjnXXdpu7nVf3QIqf4Wl3G3zTcZta3tHzAohCRH8wfN76tRLpT5lDfPYEjJpOq3MAkBDJPMnLVs7wuTldcwDIy1Z+IhpV8UDaT+IpjarYS2qZil4Ay9vgv1zLVHiIvpQZrddVdgvtXq+r7NaXMqMEAGymRru2kPIKZa4tpAZtpkY7wDmcBoNBmfvRHWf/6+cZPZwatYd7rEeP2xUKxf/DKVfPXg3Rg/6h5nezU3Xffn5XseGwNB1HCSG/c7OUH0sKirzVGr2nSqcf5c7/A2XQD+i/hQ92AAAAAElFTkSuQmCC");
54
+ }
55
+
56
+ .rc-tree .rc-tree-treenode span.rc-tree-checkbox.rc-tree-checkbox-indeterminate {
57
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB4klEQVRYhWP8//8/AzK4cOua1vZTB1LO3brq+fTta8U///6wM1AAWJhYfkoLi94zUtPe4WnmMMdATesasjwjzAG/f/9m61k5u2/90T2ZDAwMTJRYigf8C7R2mV0SnlrIysr6He6Anz9/spXO6Nh64sZFFxpZjAIsNPT3dWdUeLKzs/9iYmBgYOhdNaefXpYzMDAwnLhx0al31dw+BgYGBsaz1y9pZ0ysv8jAwMBMLwdAwd8Z+Y36zHJmWlU3n9y3orPlDAwMDEzMTEy/mAV1FSd8/PZFZAAcwPD95w8BpmdvXysNhOUMDAwMz96+VmL68+8P20A54M+/P2y0yu9Eg1EHjDpgwB3Agkvi1PR1jNS0yCwz6D828QEPAUbTjECsLqMXGPAQGHXAqAMG3gEsTCy/BspyFiaWL0ySQiL3BsoBkkIiD5n0ldR3DJQD9JXU9zC5GtvMZWBg+DsA9v91NbaZw2SpZ3zF29RuJr1t9za1m2mpZ3yFiYGBgSEvML7QREVrD70sN1HR2pcXGF/IwIDUOX379i3b5A2L+radOUzTzqmXie3s3IC4QmFhYUTnFBkcPH1ce9/F46mX79/yePHhjdKff/9YKbGRhYnpq4SAyANdRbU9TvqWc+xNLa8gywMAuU2irA5cYeUAAAAASUVORK5CYII=");
58
+ }
59
+
60
+ .rc-tree .rc-tree-treenode span.rc-tree-checkbox.rc-tree-checkbox-disabled {
61
+ opacity: 0.5;
62
+ }
63
+
64
+ .dark .rc-tree .rc-tree-treenode span.rc-tree-checkbox {
65
+ background-image: url("data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAACyUlEQVRYhe2Xz0uTcRzH39/noUdcG8rzGVuX6TZJZS4kyIOnoiZN8lYQQbcW0o+L/iF5khLXLYigboqGS+rUwSBGm+hCp+6SY5+HjS2HTzz7dpnroSIS3U57X7+H1+t7eB6+LyGlhH29FwZCesgfc/k841qXMyBU0YETTFry0CxVtsu5/LKxvhPf/bK5bj8XRwJEpPVELj1xD/c9AKCcBPqP1QrJrfm9xKcpZq42BIhIO3/z8qLLfy4CAPm1DRibufVqoRiXVm0JQJaZD49DIqIOAEGhKtFOd3dMH/CFPCODAIDyzrfVr28+jDOzKaSUuHj7+qx7uO/hYbGC7MJH82DfmAbwlJlrp3FtIlIA3Hd49ZnAxGhnR7cTheTW7OdXbx+LnnD/UP+da0kA6saLFfNg37jBzInTAP9F5KrDqy8N3h3TAFiZl++GFT3kjwFQ82sbONg3ppoFBwBmXj3YN6bzaxsAoOohf0xx+TxRADA2c2kAc82C2/aszoLL54kqWpczCADVQjHOzFaz6cxsVQvFOABoXc6gIlShAYC0asvNhh/tiCVUodm/9+1WCdhZDQFmNltFt7Oa9cf777UF2gJtgbZAW6AhQERaq6BE5PxDAECwVQIAehsC0pImAAhVibaKLlQlAgDSkhXFLFW2AaDT3X2PiNRmw4lI7XR3xwDALFV2lXIuvwwA+oAvDGCy2QIAJusslHP5hMLp7HMAlmdkEA6vPkNEkWaR68/ymXqgWJzOxpW9VCZVSG7NAUBgYlRzePVFInpUj4nTAitENOnw6guBiVENAArJrbm9VCYlpJTQdV3rv3Xl9zRLVwvF+foDcpuZfxwTehaAX6hKpJ5mYXuaZV6/HzcMw2zEqa7rWu/YSEvidHdlbcowjF9xal9PuH+IhgL3XT5PVOtyBoUqzpyEKC353SxVdsq5fILT2fheKpOyn/8ESjQ22fqGVzwAAAAASUVORK5CYII=");
66
+ }
67
+
68
+ .dark .rc-tree .rc-tree-treenode span.rc-tree-checkbox.rc-tree-checkbox-checked {
69
+ background-image: url("data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAACiUlEQVRYhcWXMUwTURjH/++dHktLE84Ay1Va22CoDSToAjEm2qGNTi6AkQ0HExmIg6uTkQUH40DEiURccDOQ0BAcYDAdWnvFQE9beg4FepfQYgxNrucgV4+SSHuld7/kkkveu/v/3vfed8kRTdNg5HKwt6+jr2fCyXdGWJfDQxjShibQVO2ofHD4oyTtLSub2bmd5NamcZzoAhzHse7Q9ZlL/VceA6DNhP6HSiHx/W0uGpuSZfl3VYDjONZ3/9andk93qEXBJyhm8qvix88RWZbLFADcocFXVoUDQLun+7Y7NDgDAIQP+AO9D0IJAIxVAseoW++j/ZQL9EzYEA4ADBfomaBOvitsQzgAwMl3hSnrcnjtEmBdDi8lDGHtEiAMYVvV73XTMoEB3m+fwPhwBC9Gn2B6ZNIegbGhv40l/BStFxgfjlTvkzmLBcaHI9XVL2wsIy6lrRXQw5M5EfPrS3U9c24CxtIvbCzX/dy5CJgpfUMC0yOTJ1ZYi5nS61w4a8IA70fQ7UPQ7QOAUwFGsXrarpYzKxCX0tV2GhsKnwisLX2jqwcAxnvnxvOzJkVTX0AJQZD3Icj7QAnBV0nEy9F/X7pnH143HA7UsQU6+urGhsLVS6eRU19LQ10wv750Ksxs6asCmqqVzUqYOfVGNFU7JDefPvzW1uG8avotTXCklFK0JO2a38AmKUm7USqnsu8AqDbkq3IqO0dzwrZQSIizVqcXEuJsTtgWKADsrMSmipl81KrwYia/urMSmwKO21BRlHJ6ce3uflx8A6DSwuzKflycTS+u3VMUpQwY/o51+Gv+ANfnedTu7gyzLoeXMPRiM4maWvlVPjjMFnN7UXkzMycJacE4/gcJ4gNYSrcwdgAAAABJRU5ErkJggg==");
70
+ }
71
+
72
+ .dark .rc-tree .rc-tree-treenode span.rc-tree-checkbox.rc-tree-checkbox-indeterminate {
73
+ background-image: url("data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB6klEQVRYhe2XvW/TQBiHf3cGUyQnkTjUdnFIAlWlGNShiLVS8ZAKxNCROQwd80ewlokhIqxs7VAJqVJN1a4oQyPcSG1M87WkbWwpifmy5BxLWzlSEwEhuSXPdvfq7nkG32DCOUeQe4/mk3eSsXRInV6RI0qcSOQWhoD7/JfXck869bNtp1jJVb8cFYNzchnAGJOj+uP1uwv31wDQYaQD6DYLX9/VjHzGtu0fVwGMMfnB6tLHcHxWH5G4h3a5sWtt7q/Ytu1RAIjqi2/GJQeAcHx2OaovrgMAUbU5bf6lXgAgjSvgAv/og7FAmRZLC5ADgMS0WJqG1JmUADkAIKTOpKgcURKiAuSIkqBEIrKoACIReVTv/Y+ZBEwChAfc6DdIL73A6pOnPXvuz++BFel7qTJ1u2e9+fkTcvtbfxdwPf2lAB8wG3Cj/nrt307+J4R/A5OASYD4AO5zT5Sc+9ylXss9ERXgtdwq7dRPt0UFdOqnBrUPK+8B+AL8vn1YydGaeWw2C1Z23PZmwcrWzGOTAkB1J59plxvGuOTtcmO3upPPABfP0HEcr7Sx9+z8wHoLoDtCd/f8wMqWNvaeO47jAYG/40vUh3MaS8ZfhaPTKTmiJIhEbw5j5H73m9dyK+3amWEXy7m6WTKD89+j97MqngYOywAAAABJRU5ErkJggg==");
74
+ }
75
+