@meta-1/design 0.0.198 → 0.0.200

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 (43) hide show
  1. package/package.json +3 -1
  2. package/src/assets/locales/en-us.ts +4 -0
  3. package/src/assets/locales/zh-cn.ts +4 -0
  4. package/src/assets/locales/zh-tw.ts +4 -0
  5. package/src/assets/style/theme.css +72 -21
  6. package/src/components/ui/button.tsx +28 -18
  7. package/src/components/uix/action/index.tsx +7 -5
  8. package/src/components/uix/action/style.css +21 -0
  9. package/src/components/uix/alert/index.tsx +5 -5
  10. package/src/components/uix/alert-dialog/index.tsx +56 -16
  11. package/src/components/uix/badge/index.tsx +8 -2
  12. package/src/components/uix/button/index.tsx +7 -5
  13. package/src/components/uix/card/index.tsx +7 -4
  14. package/src/components/uix/checkbox-group/index.tsx +3 -4
  15. package/src/components/uix/combo-select/index.tsx +99 -129
  16. package/src/components/uix/command/index.tsx +126 -0
  17. package/src/components/uix/data-grid/index.tsx +625 -0
  18. package/src/components/uix/data-grid/overlays.tsx +26 -0
  19. package/src/components/uix/data-grid/style.css +6 -0
  20. package/src/components/uix/data-table/index.tsx +17 -8
  21. package/src/components/uix/date-picker/index.tsx +55 -46
  22. package/src/components/uix/date-range-picker/index.tsx +116 -36
  23. package/src/components/uix/dialog/index.tsx +5 -5
  24. package/src/components/uix/dropdown/index.tsx +1 -1
  25. package/src/components/uix/input/index.tsx +23 -0
  26. package/src/components/uix/message/index.tsx +4 -4
  27. package/src/components/uix/pagination/index.tsx +17 -31
  28. package/src/components/uix/popover/index.tsx +30 -0
  29. package/src/components/uix/radio-group/index.tsx +2 -2
  30. package/src/components/uix/select/index.tsx +6 -2
  31. package/src/components/uix/sheet/index.tsx +76 -0
  32. package/src/components/uix/tabs/index.tsx +38 -0
  33. package/src/components/uix/tags-input/index.tsx +22 -3
  34. package/src/components/uix/textarea/index.tsx +23 -0
  35. package/src/components/uix/tooltip/index.tsx +5 -4
  36. package/src/components/uix/tree/style.css +1 -0
  37. package/src/components/uix/tree-select/index.tsx +59 -64
  38. package/src/components/uix/uploader/index.tsx +1 -1
  39. package/src/components/uix/value-formatter/index.tsx +40 -46
  40. package/src/index.ts +19 -35
  41. package/src/lib/utils.ts +50 -1
  42. package/src/components/uix/breadcrumbs/index.tsx +0 -38
  43. package/src/components/uix/space/index.tsx +0 -24
@@ -23,10 +23,12 @@ import {
23
23
  type DropdownMenuItemProps,
24
24
  Filters,
25
25
  type FiltersProps,
26
+ type Formatters,
26
27
  generateColumnStorageKey,
27
28
  Pagination,
28
29
  type PaginationProps,
29
30
  Spin,
31
+ ValueFormatter,
30
32
  } from "@meta-1/design";
31
33
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../ui/table";
32
34
  import "./style.css";
@@ -36,7 +38,6 @@ import classNames from "classnames";
36
38
  import get from "lodash/get";
37
39
 
38
40
  import { UIXContext } from "@meta-1/design/components/uix/config-provider";
39
- import { type Formatters, type FunctionMap, formatValue } from "@meta-1/design/components/uix/value-formatter";
40
41
 
41
42
  export interface StickyColumnProps {
42
43
  key: string;
@@ -63,7 +64,6 @@ export interface DataTableProps<TData> {
63
64
  load?: (params?: unknown) => Promise<unknown> | unknown;
64
65
  filter?: FiltersProps;
65
66
  pagination?: PaginationProps | boolean;
66
- cellHandles?: FunctionMap;
67
67
  empty?: string;
68
68
  showHeader?: boolean;
69
69
  onRowClick?: (row: Row<TData>) => void;
@@ -170,7 +170,6 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
170
170
  loading,
171
171
  pagination,
172
172
  load,
173
- cellHandles,
174
173
  showHeader = true,
175
174
  inCard = false,
176
175
  maxHeight,
@@ -358,7 +357,13 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
358
357
  const showToolbar = useMemo(() => filter || showVisibilityControl, [filter, showVisibilityControl]);
359
358
 
360
359
  const showPagination = useMemo(() => {
361
- return pagination;
360
+ if (!pagination) {
361
+ return false;
362
+ }
363
+ if (typeof pagination === "boolean") {
364
+ return pagination;
365
+ }
366
+ return pagination.total > 0;
362
367
  }, [pagination]);
363
368
 
364
369
  // 渲染表头的通用函数
@@ -423,10 +428,14 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
423
428
  const render = ctx.renderValue;
424
429
  // biome-ignore lint/suspicious/noExplicitAny: <formatters>
425
430
  const formatters = (cell.column.columnDef as any).formatters || [];
426
- ctx.renderValue = () => {
427
- return formatValue(render(), formatters, cellHandles);
428
- };
429
- const content = flexRender(cell.column.columnDef.cell, ctx);
431
+ const hasFormatters = formatters.length > 0;
432
+ // biome-ignore lint/suspicious/noExplicitAny: <cellValue can be any type>
433
+ const cellValue: any = render();
434
+ const content = hasFormatters ? (
435
+ <ValueFormatter formatters={formatters} value={cellValue} />
436
+ ) : (
437
+ flexRender(cell.column.columnDef.cell, ctx)
438
+ );
430
439
  return (
431
440
  <TableCell
432
441
  className={cn(
@@ -4,7 +4,7 @@ import { addDays, format } from "date-fns";
4
4
  import get from "lodash/get";
5
5
  import { Calendar as CalendarIcon } from "lucide-react";
6
6
 
7
- import { Button, Calendar, cn, Popover, PopoverContent, PopoverTrigger, Select } from "@meta-1/design";
7
+ import { Button, Calendar, cn, Popover, Select } from "@meta-1/design";
8
8
  import { UIXContext } from "@meta-1/design/components/uix/config-provider";
9
9
 
10
10
  export type DatePickerProps = {
@@ -118,53 +118,62 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, _r
118
118
 
119
119
  const calendar = <Calendar locale={locale} mode="single" onSelect={handleSelect} selected={selectedDate} />;
120
120
 
121
+ const content = (
122
+ <>
123
+ {preset ? (
124
+ <>
125
+ <Select
126
+ className="w-full"
127
+ onChange={handlePresetChange}
128
+ options={options || []}
129
+ placeholder="请选择"
130
+ value={presetValue}
131
+ />
132
+ <div className="rounded-md border">{calendar}</div>
133
+ </>
134
+ ) : (
135
+ calendar
136
+ )}
137
+ </>
138
+ );
139
+
121
140
  return (
122
- <Popover onOpenChange={handleOpenChange} open={popoverOpen}>
123
- <PopoverTrigger asChild disabled={disabled}>
124
- <Button
125
- className={cn(
126
- "group w-full justify-start space-x-1 text-left font-normal",
127
- !selectedDate && "text-muted-foreground",
128
- className,
129
- )}
130
- disabled={disabled}
131
- variant="outline"
132
- >
133
- <CalendarIcon className="mr-2 h-4 w-4" />
134
- <span className="flex-1">{selectedDate ? format(selectedDate, formatConfig) : placeholder}</span>
135
- {allowClear && selectedDate && !disabled ? (
136
- <span
137
- aria-label="Clear date"
138
- className="hidden cursor-pointer items-center justify-center group-hover:flex"
139
- onClick={handleClear}
140
- onPointerDown={(pointerEvent) => {
141
- pointerEvent.preventDefault();
142
- pointerEvent.stopPropagation();
143
- }}
144
- role="button"
145
- tabIndex={-1}
146
- >
147
- <Cross2Icon />
148
- </span>
149
- ) : null}
150
- </Button>
151
- </PopoverTrigger>
152
- <PopoverContent align="start" className={cn("flex w-fit", preset ? "flex-col space-y-2 p-2" : "p-0")}>
153
- {preset ? (
154
- <>
155
- <Select
156
- className="w-full"
157
- onChange={handlePresetChange}
158
- options={options || []}
159
- placeholder="请选择"
160
- value={presetValue}
161
- />
162
- <div className="rounded-md border">{calendar}</div>
163
- </>
164
- ) : (
165
- calendar
141
+ <Popover
142
+ align="start"
143
+ asChild
144
+ className={cn("flex w-fit", preset ? "flex-col gap-calendar p-calendar" : "p-0")}
145
+ content={content}
146
+ disabled={disabled}
147
+ onOpenChange={handleOpenChange}
148
+ open={popoverOpen}
149
+ >
150
+ <Button
151
+ className={cn(
152
+ "group min-h-8 w-full justify-start gap-1 p-select text-left font-normal",
153
+ !selectedDate && "text-muted-foreground",
154
+ className,
166
155
  )}
167
- </PopoverContent>
156
+ disabled={disabled}
157
+ variant="outline"
158
+ >
159
+ <CalendarIcon className="mr-2 h-4 w-4" />
160
+ <span className="flex-1">{selectedDate ? format(selectedDate, formatConfig) : placeholder}</span>
161
+ {allowClear && selectedDate && !disabled ? (
162
+ <span
163
+ aria-label="Clear date"
164
+ className="hidden cursor-pointer items-center justify-center group-hover:flex"
165
+ onClick={handleClear}
166
+ onPointerDown={(pointerEvent) => {
167
+ pointerEvent.preventDefault();
168
+ pointerEvent.stopPropagation();
169
+ }}
170
+ role="button"
171
+ tabIndex={-1}
172
+ >
173
+ <Cross2Icon />
174
+ </span>
175
+ ) : null}
176
+ </Button>
168
177
  </Popover>
169
178
  );
170
179
  });
@@ -1,68 +1,148 @@
1
- import { forwardRef, type HTMLAttributes, useContext, useState } from "react";
1
+ import { forwardRef, type HTMLAttributes, type MouseEvent, useContext, useEffect, useState } from "react";
2
+ import { Cross2Icon } from "@radix-ui/react-icons";
2
3
  import { format } from "date-fns";
3
4
  import get from "lodash/get";
4
5
  import { Calendar as CalendarIcon } from "lucide-react";
5
6
  import type { DateRange } from "react-day-picker";
6
7
 
7
- import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "@meta-1/design";
8
+ import { Button, Calendar, Popover } from "@meta-1/design";
8
9
  import { UIXContext } from "@meta-1/design/components/uix/config-provider";
9
10
  import { cn } from "@meta-1/design/lib";
10
11
 
12
+ export type { DateRange } from "react-day-picker";
13
+
11
14
  export type DateRangePickerProps = {
12
15
  value?: DateRange;
13
- onChange?: (value: DateRange) => void;
16
+ onChange?: (value: DateRange | undefined) => void;
14
17
  placeholder?: string;
15
18
  format?: string;
16
- } & HTMLAttributes<HTMLDivElement>;
19
+ allowClear?: boolean;
20
+ open?: boolean;
21
+ onOpenChange?: (open: boolean) => void;
22
+ } & Omit<HTMLAttributes<HTMLDivElement>, "onChange">;
17
23
 
18
24
  export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>((props, forwardedRef) => {
19
- const { className, value, onChange } = props;
25
+ const { className, value, onChange, allowClear = false, open, onOpenChange } = props;
20
26
  const elementRef = forwardedRef;
21
27
 
28
+ const hasValueProp = Object.hasOwn(props, "value");
29
+ const isValueControlled = hasValueProp;
30
+ const hasOpenProp = Object.hasOwn(props, "open");
31
+ const isOpenControlled = hasOpenProp;
32
+ const [internalDate, setInternalDate] = useState<DateRange | undefined>(value);
33
+ const [internalOpen, setInternalOpen] = useState(false);
34
+
35
+ useEffect(() => {
36
+ if (isValueControlled) {
37
+ setInternalDate(value);
38
+ }
39
+ }, [isValueControlled, value]);
40
+
41
+ useEffect(() => {
42
+ if (isOpenControlled) {
43
+ setInternalOpen(Boolean(open));
44
+ }
45
+ }, [isOpenControlled, open]);
46
+
22
47
  const config = useContext(UIXContext);
23
48
  const locale = get(config.locale, "DateRangePicker.locale");
24
49
  const formatConfig = props.format || get(config.locale, "DateRangePicker.format") || "yyyy-MM-dd";
25
50
 
26
- const [date, setDate] = useState<DateRange | undefined>(value);
51
+ const selectedDate = isValueControlled ? value : internalDate;
52
+ const popoverOpen = isOpenControlled ? open : internalOpen;
53
+
54
+ const closePopover = () => {
55
+ if (!isOpenControlled) {
56
+ setInternalOpen(false);
57
+ }
58
+ onOpenChange?.(false);
59
+ };
60
+
61
+ const handleOpenChange = (nextOpen: boolean) => {
62
+ onOpenChange?.(nextOpen);
63
+ if (!isOpenControlled) {
64
+ setInternalOpen(nextOpen);
65
+ }
66
+ };
67
+
68
+ const handleClear = (event: MouseEvent<SVGSVGElement | HTMLSpanElement>) => {
69
+ event.preventDefault();
70
+ event.stopPropagation();
71
+ if (!isValueControlled) {
72
+ setInternalDate(undefined);
73
+ }
74
+ onChange?.(undefined);
75
+ };
76
+
77
+ const content = (
78
+ <Calendar
79
+ defaultMonth={selectedDate?.from}
80
+ initialFocus
81
+ locale={locale}
82
+ mode="range"
83
+ numberOfMonths={2}
84
+ onSelect={(v) => {
85
+ if (!isValueControlled) {
86
+ setInternalDate(v);
87
+ }
88
+ onChange?.(v);
89
+ if (v?.to) {
90
+ closePopover();
91
+ }
92
+ }}
93
+ selected={selectedDate}
94
+ />
95
+ );
27
96
 
28
97
  return (
29
98
  <div className={cn("grid gap-2", className)} ref={elementRef}>
30
- <Popover>
31
- <PopoverTrigger asChild>
32
- <Button
33
- className={cn("justify-start text-left font-normal", !date && "text-muted-foreground")}
34
- id="date"
35
- type="button"
36
- variant="outline"
37
- >
38
- <CalendarIcon className="mr-2 h-4 w-4" />
39
- {date?.from ? (
40
- date.to ? (
99
+ <Popover
100
+ align="start"
101
+ asChild
102
+ className="w-auto p-0"
103
+ content={content}
104
+ onOpenChange={handleOpenChange}
105
+ open={popoverOpen}
106
+ >
107
+ <Button
108
+ className={cn(
109
+ "group min-h-8 justify-start gap-1 p-select text-left font-normal",
110
+ !selectedDate && "text-muted-foreground",
111
+ )}
112
+ id="date"
113
+ type="button"
114
+ variant="outline"
115
+ >
116
+ <CalendarIcon className="mr-2 h-4 w-4" />
117
+ <span className="flex-1">
118
+ {selectedDate?.from ? (
119
+ selectedDate.to ? (
41
120
  <>
42
- {format(date.from, formatConfig)} - {format(date.to, formatConfig)}
121
+ {format(selectedDate.from, formatConfig)} - {format(selectedDate.to, formatConfig)}
43
122
  </>
44
123
  ) : (
45
- format(date.from, formatConfig)
124
+ format(selectedDate.from, formatConfig)
46
125
  )
47
126
  ) : (
48
- <span>{props.placeholder}</span>
127
+ props.placeholder
49
128
  )}
50
- </Button>
51
- </PopoverTrigger>
52
- <PopoverContent align="start" className="w-auto p-0">
53
- <Calendar
54
- defaultMonth={date?.from}
55
- initialFocus
56
- locale={locale}
57
- mode="range"
58
- numberOfMonths={2}
59
- onSelect={(v) => {
60
- setDate(v);
61
- onChange?.(v!);
62
- }}
63
- selected={date}
64
- />
65
- </PopoverContent>
129
+ </span>
130
+ {allowClear && selectedDate?.from ? (
131
+ <span
132
+ aria-label="Clear date range"
133
+ className="hidden cursor-pointer items-center justify-center group-hover:flex"
134
+ onClick={handleClear}
135
+ onPointerDown={(pointerEvent) => {
136
+ pointerEvent.preventDefault();
137
+ pointerEvent.stopPropagation();
138
+ }}
139
+ role="button"
140
+ tabIndex={-1}
141
+ >
142
+ <Cross2Icon />
143
+ </span>
144
+ ) : null}
145
+ </Button>
66
146
  </Popover>
67
147
  </div>
68
148
  );
@@ -49,22 +49,22 @@ export const Dialog: FC<DialogProps> = (props) => {
49
49
  <DialogContent
50
50
  aria-describedby={undefined}
51
51
  autoFocus={false}
52
- className={cn(className)}
52
+ className={cn("gap-dialog rounded-dialog", className)}
53
53
  onClick={(e) => e.stopPropagation()}
54
54
  onCloseClick={onCancel}
55
55
  onOverlayClick={maskClosable ? onCancel : () => {}}
56
56
  showClose={closable}
57
57
  >
58
- <DialogHeader className={cn(!title && !description && "sr-only")}>
59
- <DialogTitle className={cn(!title && "sr-only")}>{title || "Dialog"}</DialogTitle>
58
+ <DialogHeader className={cn("p-dialog pb-0", !title && !description && "sr-only")}>
59
+ <DialogTitle className={cn("leading-normal", !title && "sr-only")}>{title || "Dialog"}</DialogTitle>
60
60
  {description ? <DialogDescription>{description}</DialogDescription> : null}
61
61
  </DialogHeader>
62
62
  <div className="min-h-0 flex-1 overflow-auto">
63
- <div className={cn("px-6 py-1", fitContent && "w-full min-w-fit", !footer && "!pb-6", contentClassName)}>
63
+ <div className={cn("px-dialog", fitContent && "w-full min-w-fit", !footer && "pb-dialog", contentClassName)}>
64
64
  {props.children}
65
65
  </div>
66
66
  </div>
67
- {footer ? <DialogFooter>{footer}</DialogFooter> : null}
67
+ {footer ? <DialogFooter className="p-dialog pt-0">{footer}</DialogFooter> : null}
68
68
  {loading ? (
69
69
  <div
70
70
  className={cn(
@@ -109,7 +109,7 @@ export const Dropdown: FC<DropdownProps> = (props) => {
109
109
  return (
110
110
  <DropdownMenu modal={modal} onOpenChange={props.onOpenChange} open={open}>
111
111
  <DropdownMenuTrigger asChild={asChild}>{props.children}</DropdownMenuTrigger>
112
- <DropdownMenuContent align={align} className={cn(props.className)} side={side}>
112
+ <DropdownMenuContent align={align} className={cn("rounded-popover p-popover", props.className)} side={side}>
113
113
  {items.filter((item) => !item.hidden).map((child) => renderItem(child, { onItemClick, onCheckedChange }))}
114
114
  </DropdownMenuContent>
115
115
  </DropdownMenu>
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps } from "react";
2
+ import { forwardRef } from "react";
3
+
4
+ import { Input as UIInput } from "@meta-1/design/components/ui/input";
5
+ import { cn } from "@meta-1/design/lib";
6
+
7
+ export type InputProps = Omit<ComponentProps<typeof UIInput>, "size" | "onChange"> & {
8
+ onChange?: (value: string) => void;
9
+ };
10
+
11
+ export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
12
+ const { className, onChange, ...rest } = props;
13
+ return (
14
+ <UIInput
15
+ {...rest}
16
+ className={cn("h-input rounded-input px-input", className)}
17
+ onChange={(e) => onChange?.(e.target.value)}
18
+ ref={ref}
19
+ />
20
+ );
21
+ });
22
+
23
+ Input.displayName = "Input";
@@ -9,7 +9,7 @@ export const useMessage = () => {
9
9
  unstyled: true,
10
10
  ...options,
11
11
  className:
12
- "group w-full max-w-sm border-success-foreground/20 bg-success/80 text-success-foreground rounded-lg border px-4 py-3 shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
12
+ "group w-full max-w-sm border-success-foreground/20 bg-success/80 text-success-foreground rounded-message border p-message shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
13
13
  descriptionClassName: "text-sm truncate",
14
14
  });
15
15
  },
@@ -18,7 +18,7 @@ export const useMessage = () => {
18
18
  unstyled: true,
19
19
  ...options,
20
20
  className:
21
- "group w-full max-w-sm border-error-foreground/20 bg-error/80 text-error-foreground rounded-lg border px-4 py-3 shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
21
+ "group w-full max-w-sm border-error-foreground/20 bg-error/80 text-error-foreground rounded-message border p-message shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
22
22
  descriptionClassName: "text-sm truncate",
23
23
  });
24
24
  },
@@ -27,7 +27,7 @@ export const useMessage = () => {
27
27
  unstyled: true,
28
28
  ...options,
29
29
  className:
30
- "group w-full max-w-sm border-warning-foreground/20 bg-warning/80 text-warning-foreground rounded-lg border px-4 py-3 shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
30
+ "group w-full max-w-sm border-warning-foreground/20 bg-warning/80 text-warning-foreground rounded-message border p-message shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
31
31
  descriptionClassName: "text-sm truncate",
32
32
  });
33
33
  },
@@ -36,7 +36,7 @@ export const useMessage = () => {
36
36
  unstyled: true,
37
37
  ...options,
38
38
  className:
39
- "group w-full max-w-sm border-info-foreground/20 bg-info/80 text-info-foreground rounded-lg border px-4 py-3 shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
39
+ "group w-full max-w-sm border-info-foreground/20 bg-info/80 text-info-foreground rounded-message border p-message shadow-lg flex items-start gap-3 backdrop-blur-sm overflow-hidden",
40
40
  descriptionClassName: "text-sm truncate",
41
41
  });
42
42
  },
@@ -1,9 +1,9 @@
1
1
  import { type FC, useRef, useState } from "react";
2
2
  import { ChevronLeft, ChevronRight } from "lucide-react";
3
3
 
4
- import { Button } from "@meta-1/design/components/ui/button";
5
- import { Input } from "@meta-1/design/components/ui/input";
6
- import { Tabs, TabsList, TabsTrigger } from "@meta-1/design/components/ui/tabs";
4
+ import { Button } from "@meta-1/design/components/uix/button";
5
+ import { Input } from "@meta-1/design/components/uix/input";
6
+ import { Tabs } from "@meta-1/design/components/uix/tabs";
7
7
 
8
8
  export interface PaginationProps {
9
9
  total: number;
@@ -67,8 +67,8 @@ export const Pagination: FC<PaginationProps> = ({
67
67
  setTimeout(() => inputRef.current?.select(), 0);
68
68
  };
69
69
 
70
- const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
71
- const value = e.target.value.replace(/[^\d]/g, "");
70
+ const handleInputChange = (v: string) => {
71
+ const value = v.replace(/[^\d]/g, "");
72
72
  setInputValue(value);
73
73
  };
74
74
 
@@ -90,19 +90,12 @@ export const Pagination: FC<PaginationProps> = ({
90
90
  return (
91
91
  <div className="flex w-full items-center justify-between gap-3">
92
92
  <div className="flex items-center gap-1">
93
- <Button
94
- className="h-9 w-9"
95
- disabled={page === 1}
96
- onClick={() => onChange?.(page - 1)}
97
- size="icon"
98
- variant="outline"
99
- >
100
- <ChevronLeft className="h-4 w-4" />
93
+ <Button className="size-button" disabled={page === 1} onClick={() => onChange?.(page - 1)} variant="outline">
94
+ <ChevronLeft className="size-4" />
101
95
  </Button>
102
-
103
96
  {isEditing ? (
104
97
  <Input
105
- className="h-9 w-16 rounded px-2 text-center"
98
+ className="w-16 min-w-[70px] rounded text-center"
106
99
  onBlur={handleJump}
107
100
  onChange={handleInputChange}
108
101
  onKeyDown={handleKeyDown}
@@ -112,31 +105,28 @@ export const Pagination: FC<PaginationProps> = ({
112
105
  />
113
106
  ) : (
114
107
  <div
115
- className="flex h-9 min-w-[60px] cursor-pointer items-center justify-center rounded-md border bg-background px-3 font-medium text-sm transition-colors hover:bg-accent"
108
+ className="flex h-input min-w-[70px] cursor-pointer items-center justify-center rounded-md border bg-background px-3 font-medium text-sm transition-colors hover:bg-accent"
116
109
  onClick={handlePageClick}
117
110
  >
118
111
  {page} / {totalPages}
119
112
  </div>
120
113
  )}
121
-
122
114
  <Button
123
- className="h-9 w-9"
115
+ className="size-button"
124
116
  disabled={page === totalPages}
125
117
  onClick={() => onChange?.(page + 1)}
126
- size="icon"
127
118
  variant="outline"
128
119
  >
129
- <ChevronRight className="h-4 w-4" />
120
+ <ChevronRight className="size-4" />
130
121
  </Button>
131
122
  </div>
132
123
 
133
124
  <div className="hidden items-center gap-1 md:flex">
134
125
  {pageNumbers.map((pageNum) => (
135
126
  <Button
136
- className="h-9 w-9"
127
+ className="min-w-button p-0"
137
128
  key={pageNum}
138
129
  onClick={() => onChange?.(pageNum)}
139
- size="icon"
140
130
  variant={pageNum === page ? "default" : "outline"}
141
131
  >
142
132
  {pageNum}
@@ -145,15 +135,11 @@ export const Pagination: FC<PaginationProps> = ({
145
135
  </div>
146
136
 
147
137
  {onPageSizeChange && (
148
- <Tabs onValueChange={(v) => onPageSizeChange(Number(v))} value={`${size}`}>
149
- <TabsList>
150
- {pageSizeOptions.map((option) => (
151
- <TabsTrigger key={option} value={`${option}`}>
152
- {option}
153
- </TabsTrigger>
154
- ))}
155
- </TabsList>
156
- </Tabs>
138
+ <Tabs
139
+ items={pageSizeOptions.map((option) => ({ label: `${option}`, value: `${option}` }))}
140
+ onChange={(v) => onPageSizeChange(Number(v))}
141
+ value={`${size}`}
142
+ />
157
143
  )}
158
144
  </div>
159
145
  );
@@ -0,0 +1,30 @@
1
+ import { CSSProperties, FC, PropsWithChildren, ReactNode } from "react";
2
+
3
+ import { PopoverContent, PopoverTrigger, Popover as UIPopover } from "@meta-1/design/components/ui/popover";
4
+ import { cn } from "@meta-1/design/lib";
5
+
6
+ export type PopoverProps = PropsWithChildren<{
7
+ content: ReactNode;
8
+ className?: string;
9
+ asChild?: boolean;
10
+ open?: boolean;
11
+ onOpenChange?: (open: boolean) => void;
12
+ style?: CSSProperties;
13
+ align?: "start" | "center" | "end";
14
+ side?: "top" | "right" | "bottom" | "left";
15
+ disabled?: boolean;
16
+ }>;
17
+
18
+ export const Popover: FC<PopoverProps> = (props) => {
19
+ const { children, content, className, asChild, open, onOpenChange, style, align, side, disabled } = props;
20
+ return (
21
+ <UIPopover onOpenChange={onOpenChange} open={open}>
22
+ <PopoverTrigger asChild={asChild} disabled={disabled}>
23
+ {children}
24
+ </PopoverTrigger>
25
+ <PopoverContent align={align} className={cn("rounded-popover p-popover", className)} side={side} style={style}>
26
+ {content}
27
+ </PopoverContent>
28
+ </UIPopover>
29
+ );
30
+ };
@@ -1,4 +1,4 @@
1
- import { type FC, forwardRef, type PropsWithChildren, useState } from "react";
1
+ import { type FC, forwardRef, type PropsWithChildren, useId } from "react";
2
2
 
3
3
  import { Label } from "@meta-1/design/components/ui/label";
4
4
  import { RadioGroupItem, RadioGroup as UIRadioGroup } from "@meta-1/design/components/ui/radio-group";
@@ -18,7 +18,7 @@ export interface RadioGroupProps extends PropsWithChildren {
18
18
  export const RadioGroup: FC<RadioGroupProps> = forwardRef((props, _ref) => {
19
19
  const { options = [], value, onChange, disabled = false } = props;
20
20
 
21
- const [labelKey] = useState(Date.now());
21
+ const labelKey = useId();
22
22
 
23
23
  return (
24
24
  <UIRadioGroup disabled={disabled} onValueChange={onChange} value={value}>
@@ -85,12 +85,15 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) =
85
85
  value={currentValue as string | undefined}
86
86
  >
87
87
  <div className={cn("relative inline-block", allowClear && currentValue && !disabled ? "group" : "", className)}>
88
- <SelectTrigger className={cn("flex w-full", triggerClassName)}>
88
+ <SelectTrigger
89
+ className={cn("flex h-select w-full rounded-select px-select", triggerClassName)}
90
+ size={"none" as "sm" | "default"}
91
+ >
89
92
  <SelectValue placeholder={placeholder} />
90
93
  </SelectTrigger>
91
94
  {allowClear && currentValue && !disabled && (
92
95
  <div
93
- className="absolute top-0 right-0 z-50 hidden h-full cursor-pointer items-center justify-center px-3 group-hover:flex"
96
+ className="absolute top-0 right-0 z-50 hidden h-full cursor-pointer items-center justify-center px-2 group-hover:flex"
94
97
  onClick={clear}
95
98
  >
96
99
  <XIcon className="size-4 opacity-50" />
@@ -100,6 +103,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>((props, _ref) =
100
103
  <SelectContent
101
104
  align={props.align}
102
105
  alignOffset={props.alignOffset}
106
+ className="rounded-select"
103
107
  side={props.side}
104
108
  sideOffset={props.sideOffset}
105
109
  >