@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,364 @@
1
+ import { type ForwardedRef, forwardRef, type ReactNode, useContext, useEffect, useMemo, useRef, useState } from "react";
2
+ import { CaretSortIcon, CheckIcon, Cross2Icon, PlusCircledIcon } from "@radix-ui/react-icons";
3
+ import type { DebouncedFunc } from "lodash";
4
+ import cloneDeep from "lodash/cloneDeep";
5
+ import get from "lodash/get";
6
+ import remove from "lodash/remove";
7
+
8
+ import { Button } from "@meta-1/design/components/ui/button";
9
+ import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from "@meta-1/design/components/ui/command";
10
+ import { Popover, PopoverContent, PopoverTrigger } from "@meta-1/design/components/ui/popover";
11
+ import { Skeleton } from "@meta-1/design/components/ui/skeleton";
12
+ import { UIXContext } from "@meta-1/design/components/uix/config-provider";
13
+ import { Spin } from "@meta-1/design/components/uix/spin";
14
+ import { useSize } from "@meta-1/design/hooks";
15
+ import { cn } from "@meta-1/design/lib";
16
+ import type { ButtonProps } from "../button";
17
+ import { Checkbox } from "../checkbox";
18
+
19
+ export interface ComboSelectOptionProps<Data> {
20
+ value: string;
21
+ label: ReactNode;
22
+ raw?: Data;
23
+ }
24
+
25
+ type SearchFunction = (value: string) => Promise<void> | void;
26
+
27
+ export interface ComboSelectProps<Data = unknown> {
28
+ options?: ComboSelectOptionProps<Data>[];
29
+ placeholder?: string;
30
+ searchPlaceholder?: string;
31
+ empty?: ReactNode;
32
+ className?: string;
33
+ onChange?: (value: string | string[]) => void;
34
+ value?: string | string[];
35
+ loading?: boolean;
36
+ filter?: (value: string, search: string, option?: ComboSelectOptionProps<Data>) => boolean;
37
+ multiple?: boolean;
38
+ clearable?: boolean;
39
+ clearText?: string;
40
+ limit?: number;
41
+ search?: boolean;
42
+ onSearch?: SearchFunction | DebouncedFunc<SearchFunction>;
43
+ initLoading?: boolean;
44
+ }
45
+
46
+ function SelectedLabels({
47
+ selectedValues,
48
+ options,
49
+ placeholderDom,
50
+ }: {
51
+ selectedValues: string[];
52
+ // biome-ignore lint/suspicious/noExplicitAny: <options>
53
+ options: ComboSelectOptionProps<any>[];
54
+ placeholderDom: ReactNode;
55
+ }) {
56
+ if (selectedValues?.length) {
57
+ return (
58
+ <>
59
+ {selectedValues.map((v) => {
60
+ // biome-ignore lint/suspicious/noExplicitAny: <options>
61
+ const label = options.find((option: any) => option.value === v)?.label || v;
62
+ return label ? (
63
+ <div className="my-0.5 mr-1 rounded bg-secondary px-2 py-[3px] text-sm" key={v}>
64
+ {label}
65
+ </div>
66
+ ) : null;
67
+ })}
68
+ </>
69
+ );
70
+ }
71
+ return (
72
+ <div className="flex items-center">
73
+ <PlusCircledIcon className="mr-2 h-4 w-4" />
74
+ {placeholderDom}
75
+ </div>
76
+ );
77
+ }
78
+
79
+ function handleSelect(
80
+ optionValue: string,
81
+ multiple: boolean,
82
+ isSelected: boolean,
83
+ selectedValues: string[],
84
+ limit: number,
85
+ setSelectedValues: (v: string[]) => void,
86
+ setValueState: (v: string) => void,
87
+ onChange: (v: string | string[]) => void,
88
+ setOpen: (v: boolean) => void,
89
+ ) {
90
+ if (multiple) {
91
+ if (isSelected) {
92
+ remove(selectedValues, (item) => item === optionValue);
93
+ } else {
94
+ if (selectedValues.length >= limit) {
95
+ return;
96
+ }
97
+ selectedValues.push(optionValue);
98
+ }
99
+ const v = cloneDeep(selectedValues);
100
+ setSelectedValues(v);
101
+ onChange?.(v);
102
+ } else {
103
+ const v = optionValue;
104
+ setValueState(v);
105
+ onChange?.(v);
106
+ setOpen(false);
107
+ }
108
+ }
109
+
110
+ function ComboSelectCommandList<Data>({
111
+ loading,
112
+ options,
113
+ selectedValues,
114
+ multiple,
115
+ limit,
116
+ setSelectedValues,
117
+ setValueState,
118
+ onChange,
119
+ value,
120
+ setOpen,
121
+ }: {
122
+ loading: boolean;
123
+ options: ComboSelectOptionProps<Data>[];
124
+ selectedValues: string[];
125
+ multiple: boolean;
126
+ limit: number;
127
+ setSelectedValues: (v: string[]) => void;
128
+ setValueState: (v: string) => void;
129
+ onChange?: (v: string | string[]) => void;
130
+ onSearch?: (v: string) => void;
131
+ value: string | string[];
132
+ setOpen: (v: boolean) => void;
133
+ }) {
134
+ return (
135
+ <CommandList>
136
+ {loading
137
+ ? null
138
+ : // biome-ignore lint/suspicious/noExplicitAny: <options>
139
+ options.map((option: any) => {
140
+ const isSelected = selectedValues.includes(option.value);
141
+ return (
142
+ <CommandItem
143
+ key={option.value}
144
+ onSelect={() =>
145
+ handleSelect(
146
+ option.value,
147
+ multiple,
148
+ isSelected,
149
+ selectedValues,
150
+ limit,
151
+ setSelectedValues,
152
+ setValueState,
153
+ onChange!,
154
+ setOpen,
155
+ )
156
+ }
157
+ value={option.value}
158
+ >
159
+ {multiple ? <Checkbox checked={isSelected} /> : null}
160
+ {option.label}
161
+ {multiple ? null : (
162
+ <CheckIcon className={cn("ml-auto h-4 w-4", value === option.value ? "opacity-100" : "opacity-0")} />
163
+ )}
164
+ </CommandItem>
165
+ );
166
+ })}
167
+ </CommandList>
168
+ );
169
+ }
170
+
171
+ type ComboSelectButtonProps<Data> = {
172
+ multiple: boolean;
173
+ selectedValues: string[];
174
+ className?: string;
175
+ placeholderDom: ReactNode;
176
+ options: ComboSelectOptionProps<Data>[];
177
+ valueState?: string | string[];
178
+ loading: boolean;
179
+ showClear: boolean;
180
+ setSelectedValues: (v: string[]) => void;
181
+ setValueState: (v: string) => void;
182
+ onChange?: (v: string | string[]) => void;
183
+ onSearch?: (v: string) => void;
184
+ initLoading: boolean;
185
+ } & Omit<ButtonProps, "onChange">;
186
+
187
+ const ComboSelectButton = forwardRef(
188
+ <Data,>(props: ComboSelectButtonProps<Data>, forwardedRef: ForwardedRef<HTMLButtonElement>) => {
189
+ const {
190
+ multiple,
191
+ selectedValues,
192
+ className,
193
+ placeholderDom,
194
+ options,
195
+ valueState,
196
+ loading,
197
+ initLoading,
198
+ showClear,
199
+ setSelectedValues,
200
+ setValueState,
201
+ onChange,
202
+ // onSearch,
203
+ onClick,
204
+ } = props;
205
+ const elementRef = forwardedRef;
206
+
207
+ return (
208
+ <Button
209
+ className={cn(
210
+ "group h-9 min-w-[150px] items-center justify-between px-2 py-1 align-middle hover:bg-secondary/40",
211
+ multiple ? "border-dashed" : null,
212
+ multiple && selectedValues && selectedValues.length ? "h-auto" : null,
213
+ className,
214
+ )}
215
+ onClick={onClick}
216
+ ref={elementRef}
217
+ type="button"
218
+ variant="outline"
219
+ >
220
+ <div className={cn("flex min-h-7.5 flex-1 flex-wrap items-center justify-start", multiple ? "-my-0.5" : null)}>
221
+ {initLoading ? (
222
+ <Skeleton className="h-6 w-full min-w-20" />
223
+ ) : multiple ? (
224
+ <SelectedLabels options={options} placeholderDom={placeholderDom} selectedValues={selectedValues} />
225
+ ) : (
226
+ <span>
227
+ {valueState ? options.find((option) => option.value === valueState)?.label || valueState : placeholderDom}
228
+ </span>
229
+ )}
230
+ </div>
231
+ <div className="ml-2 flex w-4 shrink-0 items-center justify-center opacity-50">
232
+ {loading || initLoading ? (
233
+ <Spin />
234
+ ) : (
235
+ <>
236
+ {multiple ? null : (
237
+ <CaretSortIcon className={cn("block h-4 w-4", showClear ? "group-hover:hidden" : "")} />
238
+ )}
239
+ <div
240
+ onClick={(e) => {
241
+ e.stopPropagation();
242
+ const v = multiple ? [] : "";
243
+ setSelectedValues([]);
244
+ setValueState("");
245
+ onChange?.(v);
246
+ // onSearch?.('')
247
+ }}
248
+ >
249
+ <Cross2Icon className={cn("hidden h-4 w-4", showClear ? "group-hover:block" : "")} />
250
+ </div>
251
+ </>
252
+ )}
253
+ </div>
254
+ </Button>
255
+ );
256
+ },
257
+ );
258
+
259
+ export function ComboSelect<Data = unknown>(props: ComboSelectProps<Data>) {
260
+ const {
261
+ options = [],
262
+ placeholder,
263
+ searchPlaceholder,
264
+ className,
265
+ onChange,
266
+ value,
267
+ loading = false,
268
+ multiple = false,
269
+ clearable = true,
270
+ limit = Number.MAX_VALUE,
271
+ search = false,
272
+ onSearch,
273
+ initLoading = false,
274
+ filter = (value, search) => value.includes(search),
275
+ } = props;
276
+
277
+ const config = useContext(UIXContext);
278
+ const empty = props.empty || get(config.locale, "ComboSelect.empty");
279
+
280
+ const [open, setOpen] = useState(false);
281
+ const containerRef = useRef(null);
282
+ const [valueState, setValueState] = useState<string | string[] | undefined>(value);
283
+ const [selectedValues, setSelectedValues] = useState<string[]>(
284
+ multiple ? ((value || []) as string[]).map((v: string) => `${v || ""}`) : value ? [`${value || ""}`] : [],
285
+ );
286
+ const size = useSize(containerRef);
287
+
288
+ useEffect(() => {
289
+ if (multiple) {
290
+ setSelectedValues(
291
+ multiple ? ((value || []) as string[]).map((v: string) => `${v || ""}`) : value ? [`${value || ""}`] : [],
292
+ );
293
+ } else {
294
+ setValueState(value);
295
+ }
296
+ }, [value, multiple]);
297
+
298
+ const showClear = useMemo<boolean>(
299
+ () => !!(clearable && (selectedValues.length > 0 || valueState)),
300
+ [clearable, selectedValues, valueState],
301
+ );
302
+
303
+ const placeholderDom = <span className="font-normal text-muted-foreground">{placeholder}</span>;
304
+
305
+ return (
306
+ <Popover onOpenChange={setOpen} open={open}>
307
+ <PopoverTrigger asChild>
308
+ <ComboSelectButton
309
+ className={className}
310
+ initLoading={initLoading}
311
+ loading={loading}
312
+ multiple={multiple}
313
+ onChange={onChange}
314
+ onSearch={onSearch}
315
+ options={options}
316
+ placeholderDom={placeholderDom}
317
+ ref={containerRef}
318
+ selectedValues={selectedValues}
319
+ setSelectedValues={setSelectedValues}
320
+ setValueState={setValueState}
321
+ showClear={showClear}
322
+ valueState={valueState}
323
+ />
324
+ </PopoverTrigger>
325
+ <PopoverContent className="p-0" style={{ width: size.width }}>
326
+ <Command
327
+ filter={(value, search) =>
328
+ filter(
329
+ value,
330
+ search,
331
+ options.find((option) => option.value === value),
332
+ )
333
+ ? 1
334
+ : 0
335
+ }
336
+ >
337
+ {search ? <CommandInput className="h-9" onValueChange={onSearch} placeholder={searchPlaceholder} /> : null}
338
+ {loading ? (
339
+ <div className="px-2">
340
+ <Skeleton className="my-2 h-6 w-full" />
341
+ <Skeleton className="my-2 h-6 w-full" />
342
+ <Skeleton className="my-2 h-6 w-full" />
343
+ </div>
344
+ ) : (
345
+ <CommandEmpty>{empty}</CommandEmpty>
346
+ )}
347
+ <ComboSelectCommandList
348
+ limit={limit}
349
+ loading={loading}
350
+ multiple={multiple}
351
+ onChange={onChange}
352
+ onSearch={onSearch}
353
+ options={options}
354
+ selectedValues={selectedValues}
355
+ setOpen={setOpen}
356
+ setSelectedValues={setSelectedValues}
357
+ setValueState={setValueState}
358
+ value={value || ""}
359
+ />
360
+ </Command>
361
+ </PopoverContent>
362
+ </Popover>
363
+ );
364
+ }
@@ -0,0 +1,31 @@
1
+ import { createContext, type FC, type PropsWithChildren } from "react";
2
+ import omit from "lodash/omit";
3
+
4
+ import { Toaster } from "@meta-1/design/components/ui/sonner";
5
+ import zhCN from "@meta-1/design/locales/zh-cn";
6
+
7
+ export interface UIXContextProps {
8
+ locale?: typeof zhCN;
9
+ toasterPosition?: "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
10
+ }
11
+
12
+ const defaultContextProps: UIXContextProps = {
13
+ locale: zhCN,
14
+ };
15
+
16
+ export const UIXContext = createContext<UIXContextProps>(defaultContextProps);
17
+
18
+ export type ConfigProviderProps = PropsWithChildren<UIXContextProps>;
19
+
20
+ export const ConfigProvider: FC<ConfigProviderProps> = (props) => {
21
+ const config: UIXContextProps = {
22
+ ...omit(props, ["children"]),
23
+ };
24
+ const { toasterPosition = "top-center" } = config;
25
+ return (
26
+ <>
27
+ <UIXContext.Provider value={config}>{props.children}</UIXContext.Provider>
28
+ <Toaster position={toasterPosition} />
29
+ </>
30
+ );
31
+ };