@meta-1/design 0.0.169 → 0.0.171

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meta-1/design",
3
- "version": "0.0.169",
3
+ "version": "0.0.171",
4
4
  "keywords": [
5
5
  "easykit",
6
6
  "design",
@@ -52,7 +52,7 @@ function DialogContent({
52
52
  <DialogOverlay onClick={onOverlayClick} />
53
53
  <DialogPrimitive.Content
54
54
  className={cn(
55
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
55
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border bg-background shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
56
56
  className,
57
57
  )}
58
58
  data-slot="dialog-content"
@@ -61,7 +61,7 @@ function DialogContent({
61
61
  {children}
62
62
  {showClose ? (
63
63
  <DialogPrimitive.Close
64
- className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
64
+ className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 z-10"
65
65
  onClick={onCloseClick}
66
66
  >
67
67
  <XIcon />
@@ -76,7 +76,7 @@ function DialogContent({
76
76
  function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
77
77
  return (
78
78
  <div
79
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
79
+ className={cn("flex flex-col gap-2 text-center sm:text-left flex-shrink-0 p-6 pb-0", className)}
80
80
  data-slot="dialog-header"
81
81
  {...props}
82
82
  />
@@ -86,7 +86,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
86
86
  function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
87
87
  return (
88
88
  <div
89
- className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
89
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end flex-shrink-0 p-6 pt-0", className)}
90
90
  data-slot="dialog-footer"
91
91
  {...props}
92
92
  />
@@ -28,6 +28,7 @@ import {
28
28
  type PaginationProps,
29
29
  Spin,
30
30
  } from "@meta-1/design";
31
+ import { ScrollArea } from "@meta-1/design/components/ui/scroll-area";
31
32
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../ui/table";
32
33
  import "./style.css";
33
34
 
@@ -68,6 +69,8 @@ export interface DataTableProps<TData> {
68
69
  empty?: string;
69
70
  showHeader?: boolean;
70
71
  onRowClick?: (row: Row<TData>) => void;
72
+ /** 表格最大高度,设置后内容区将滚动,表头固定。支持 CSS 单位如 '500px', '50vh' 等 */
73
+ maxHeight?: string | number;
71
74
  }
72
75
 
73
76
  // 本地存储相关函数
@@ -173,6 +176,7 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
173
176
  showHeader = true,
174
177
  autoHidePagination = true,
175
178
  inCard = false,
179
+ maxHeight,
176
180
  } = props;
177
181
 
178
182
  const config = useContext(UIXContext);
@@ -363,6 +367,108 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
363
367
  return pagination;
364
368
  }, [pagination, autoHidePagination]);
365
369
 
370
+ // 渲染表头的通用函数
371
+ const renderTableHeader = (options: { fixedHeader?: boolean } = {}) => {
372
+ const { fixedHeader = false } = options;
373
+ return (
374
+ <TableHeader className={cn(!showHeader && "hidden", fixedHeader && "sticky top-0 z-20 bg-background")}>
375
+ {table.getHeaderGroups().map((headerGroup) => (
376
+ <TableRow key={headerGroup.id}>
377
+ {headerGroup.headers.map((header) => {
378
+ const sticky = mounted
379
+ ? getSticky(header.column.id, leftStickyColumns, rightStickyColumns)
380
+ : { enable: false };
381
+ const content = header.isPlaceholder
382
+ ? null
383
+ : flexRender(header.column.columnDef.header, header.getContext());
384
+ return (
385
+ <TableHead
386
+ className={cn(
387
+ sticky.enable ? "table-sticky-col sticky" : null,
388
+ sticky.last ? "table-sticky-col-last" : null,
389
+ sticky.first ? "table-sticky-col-first" : null,
390
+ // biome-ignore lint/suspicious/noExplicitAny: <className>
391
+ (header.column.columnDef as any).className,
392
+ )}
393
+ key={header.id}
394
+ style={
395
+ sticky.enable
396
+ ? {
397
+ zIndex: fixedHeader ? 30 : 10,
398
+ minWidth: sticky.width,
399
+ [sticky.position as string]: sticky.offset,
400
+ }
401
+ : undefined
402
+ }
403
+ >
404
+ {sticky.enable ? <div className="inner flex h-10 items-center px-2">{content}</div> : content}
405
+ </TableHead>
406
+ );
407
+ })}
408
+ </TableRow>
409
+ ))}
410
+ </TableHeader>
411
+ );
412
+ };
413
+
414
+ // 渲染表体的通用函数
415
+ const renderTableBody = () => {
416
+ return (
417
+ <TableBody>
418
+ {table.getRowModel().rows?.length ? (
419
+ table.getRowModel().rows.map((row) => (
420
+ <TableRow
421
+ data-state={row.getIsSelected() && "selected"}
422
+ key={row.id}
423
+ onClick={() => props.onRowClick?.(row)}
424
+ >
425
+ {row.getVisibleCells().map((cell) => {
426
+ const sticky = mounted
427
+ ? getSticky(cell.column.id, leftStickyColumns, rightStickyColumns)
428
+ : { enable: false };
429
+ const ctx = cell.getContext();
430
+ const render = ctx.renderValue;
431
+ // biome-ignore lint/suspicious/noExplicitAny: <formatters>
432
+ const formatters = (cell.column.columnDef as any).formatters || [];
433
+ ctx.renderValue = () => {
434
+ return formatValue(render(), formatters, cellHandles);
435
+ };
436
+ const content = flexRender(cell.column.columnDef.cell, ctx);
437
+ return (
438
+ <TableCell
439
+ className={cn(
440
+ sticky.enable ? "table-sticky-col sticky" : null,
441
+ sticky.last ? "table-sticky-col-last" : null,
442
+ sticky.first ? "table-sticky-col-first" : null,
443
+ )}
444
+ key={cell.id}
445
+ style={
446
+ sticky.enable
447
+ ? {
448
+ zIndex: 10,
449
+ minWidth: sticky.width,
450
+ [sticky.position as string]: sticky.offset,
451
+ }
452
+ : undefined
453
+ }
454
+ >
455
+ {sticky.enable ? <div className="inner flex h-10 items-center px-2">{content}</div> : content}
456
+ </TableCell>
457
+ );
458
+ })}
459
+ </TableRow>
460
+ ))
461
+ ) : (
462
+ <TableRow>
463
+ <TableCell className="h-24 text-center" colSpan={tableColumns.length}>
464
+ {empty}
465
+ </TableCell>
466
+ </TableRow>
467
+ )}
468
+ </TableBody>
469
+ );
470
+ };
471
+
366
472
  return (
367
473
  <>
368
474
  {showToolbar ? (
@@ -384,99 +490,23 @@ export function DataTable<TData>(props: DataTableProps<TData>) {
384
490
  </div>
385
491
  ) : null}
386
492
  <div className={cn("relative")}>
387
- <Table className={classNames("data-table", inCard ? "in-card" : null, !mounted && "invisible")}>
388
- <TableHeader className={cn(!showHeader && "hidden")}>
389
- {table.getHeaderGroups().map((headerGroup) => (
390
- <TableRow key={headerGroup.id}>
391
- {headerGroup.headers.map((header) => {
392
- // 在未挂载时禁用粘性列功能,避免 SSR 水合错误
393
- const sticky = mounted
394
- ? getSticky(header.column.id, leftStickyColumns, rightStickyColumns)
395
- : { enable: false };
396
- const content = header.isPlaceholder
397
- ? null
398
- : flexRender(header.column.columnDef.header, header.getContext());
399
- return (
400
- <TableHead
401
- className={cn(
402
- sticky.enable ? "table-sticky-col sticky" : null,
403
- sticky.last ? "table-sticky-col-last" : null,
404
- sticky.first ? "table-sticky-col-first" : null,
405
- // biome-ignore lint/suspicious/noExplicitAny: <className>
406
- (header.column.columnDef as any).className,
407
- )}
408
- key={header.id}
409
- style={
410
- sticky.enable
411
- ? {
412
- zIndex: 10,
413
- minWidth: sticky.width,
414
- [sticky.position as string]: sticky.offset,
415
- }
416
- : undefined
417
- }
418
- >
419
- {sticky.enable ? <div className="inner flex h-10 items-center px-2">{content}</div> : content}
420
- </TableHead>
421
- );
422
- })}
423
- </TableRow>
424
- ))}
425
- </TableHeader>
426
- <TableBody>
427
- {table.getRowModel().rows?.length ? (
428
- table.getRowModel().rows.map((row) => (
429
- <TableRow
430
- data-state={row.getIsSelected() && "selected"}
431
- key={row.id}
432
- onClick={() => props.onRowClick?.(row)}
433
- >
434
- {row.getVisibleCells().map((cell) => {
435
- // 在未挂载时禁用粘性列功能,避免 SSR 水合错误
436
- const sticky = mounted
437
- ? getSticky(cell.column.id, leftStickyColumns, rightStickyColumns)
438
- : { enable: false };
439
- const ctx = cell.getContext();
440
- const render = ctx.renderValue;
441
- // biome-ignore lint/suspicious/noExplicitAny: <formatters>
442
- const formatters = (cell.column.columnDef as any).formatters || [];
443
- ctx.renderValue = () => {
444
- return formatValue(render(), formatters, cellHandles);
445
- };
446
- const content = flexRender(cell.column.columnDef.cell, ctx);
447
- return (
448
- <TableCell
449
- className={cn(
450
- sticky.enable ? "table-sticky-col sticky" : null,
451
- sticky.last ? "table-sticky-col-last" : null,
452
- sticky.first ? "table-sticky-col-first" : null,
453
- )}
454
- key={cell.id}
455
- style={
456
- sticky.enable
457
- ? {
458
- zIndex: 10,
459
- minWidth: sticky.width,
460
- [sticky.position as string]: sticky.offset,
461
- }
462
- : undefined
463
- }
464
- >
465
- {sticky.enable ? <div className="inner flex h-10 items-center px-2">{content}</div> : content}
466
- </TableCell>
467
- );
468
- })}
469
- </TableRow>
470
- ))
471
- ) : (
472
- <TableRow>
473
- <TableCell className="h-24 text-center" colSpan={tableColumns.length}>
474
- {empty}
475
- </TableCell>
476
- </TableRow>
477
- )}
478
- </TableBody>
479
- </Table>
493
+ {maxHeight ? (
494
+ // 有高度限制时,使用 ScrollArea 包裹 TableBody,表头固定
495
+ <div className={cn("rounded-md border", !mounted && "invisible")}>
496
+ <Table className={classNames("data-table", inCard ? "in-card" : null)}>
497
+ {renderTableHeader({ fixedHeader: true })}
498
+ </Table>
499
+ <ScrollArea style={{ maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight }}>
500
+ <Table className={classNames("data-table", inCard ? "in-card" : null)}>{renderTableBody()}</Table>
501
+ </ScrollArea>
502
+ </div>
503
+ ) : (
504
+ // 无高度限制时,使用原有布局
505
+ <Table className={classNames("data-table", inCard ? "in-card" : null, !mounted && "invisible")}>
506
+ {renderTableHeader()}
507
+ {renderTableBody()}
508
+ </Table>
509
+ )}
480
510
  <div className={cn("py-4", !mounted && "invisible")}>
481
511
  {showPagination && (
482
512
  <Pagination
@@ -9,6 +9,7 @@ import {
9
9
  DialogTitle,
10
10
  Dialog as UIDialog,
11
11
  } from "@meta-1/design/components/ui/dialog";
12
+ import { ScrollArea } from "@meta-1/design/components/ui/scroll-area";
12
13
  import { Spin } from "@meta-1/design/components/uix/spin";
13
14
  import { cn } from "@meta-1/design/lib";
14
15
 
@@ -51,13 +52,11 @@ export const Dialog: FC<DialogProps> = (props) => {
51
52
  onOverlayClick={maskClosable ? onCancel : () => {}}
52
53
  showClose={closable}
53
54
  >
54
- {title || description ? (
55
- <DialogHeader>
56
- {title ? <DialogTitle>{title}</DialogTitle> : null}
57
- {description ? <DialogDescription>{description}</DialogDescription> : null}
58
- </DialogHeader>
59
- ) : null}
60
- <div className="my-2">{props.children}</div>
55
+ <DialogHeader className={cn(!title && !description && "sr-only")}>
56
+ <DialogTitle className={cn(!title && "sr-only")}>{title || "Dialog"}</DialogTitle>
57
+ {description ? <DialogDescription>{description}</DialogDescription> : null}
58
+ </DialogHeader>
59
+ <ScrollArea className="min-h-0 flex-1 px-6 py-4">{props.children}</ScrollArea>
61
60
  {footer ? <DialogFooter>{footer}</DialogFooter> : null}
62
61
  {loading ? (
63
62
  <div className={cn("absolute top-0 right-0 bottom-0 left-0 bg-white/50", "flex items-center justify-center")}>
@@ -21,6 +21,14 @@ export interface PaginationProps {
21
21
  onChange?: (page: number) => void;
22
22
  onSizeChange?: (size: number) => void;
23
23
  sizeOptions?: number[];
24
+ /** 简单模式:只显示上一页、当前页、下一页 */
25
+ simple?: boolean;
26
+ /** 是否显示总数信息 */
27
+ showTotal?: boolean;
28
+ /** 是否显示页面大小选择器 */
29
+ showSizeChanger?: boolean;
30
+ /** 是否显示快速跳转 */
31
+ showQuickJumper?: boolean;
24
32
  }
25
33
 
26
34
  export const SIZE_OPTIONS = [10, 20, 50, 100];
@@ -66,7 +74,18 @@ const generatePageNumbers = (currentPage: number, totalPages: number) => {
66
74
  };
67
75
 
68
76
  export const Pagination: FC<PaginationProps> = (props) => {
69
- const { total = 0, page = 1, size = 20, sizeOptions = SIZE_OPTIONS, onChange, onSizeChange } = props;
77
+ const {
78
+ total = 0,
79
+ page = 1,
80
+ size = 20,
81
+ sizeOptions = SIZE_OPTIONS,
82
+ onChange,
83
+ onSizeChange,
84
+ simple = false,
85
+ showTotal = true,
86
+ showSizeChanger = true,
87
+ showQuickJumper = true,
88
+ } = props;
70
89
 
71
90
  const config = useContext(UIXContext);
72
91
  const totalPageText = get(config.locale, "Pagination.totalPage");
@@ -101,14 +120,53 @@ export const Pagination: FC<PaginationProps> = (props) => {
101
120
 
102
121
  const pageNumbers = generatePageNumbers(page, totalPage);
103
122
 
123
+ if (simple) {
124
+ // 简单模式渲染
125
+ return (
126
+ <div className="flex items-center justify-center">
127
+ <ShadcnPagination>
128
+ <PaginationContent>
129
+ {/* 上一页 */}
130
+ <PaginationItem>
131
+ <PaginationPrevious
132
+ className={page === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
133
+ onClick={page === 1 ? undefined : () => onChange?.(page - 1)}
134
+ size="default"
135
+ />
136
+ </PaginationItem>
137
+
138
+ {/* 当前页 */}
139
+ <PaginationItem>
140
+ <PaginationLink isActive size="default">
141
+ {page}
142
+ </PaginationLink>
143
+ </PaginationItem>
144
+
145
+ {/* 下一页 */}
146
+ <PaginationItem>
147
+ <PaginationNext
148
+ className={page === totalPage ? "pointer-events-none opacity-50" : "cursor-pointer"}
149
+ onClick={page === totalPage ? undefined : () => onChange?.(page + 1)}
150
+ size="default"
151
+ />
152
+ </PaginationItem>
153
+ </PaginationContent>
154
+ </ShadcnPagination>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ // 标准模式渲染
104
160
  return (
105
161
  <div className="flex items-center justify-center space-x-6">
106
162
  <ShadcnPagination>
107
163
  {/* 总数信息 */}
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>
164
+ {showTotal ? (
165
+ <div className="flex items-center space-x-2 text-muted-foreground text-sm">
166
+ <span>{totalPageText?.replace("%total", `${total}`)}</span>
167
+ <span>{totalText?.replace("%page", `${totalPage}`)}</span>
168
+ </div>
169
+ ) : null}
112
170
  {/* 分页导航 */}
113
171
  <PaginationContent>
114
172
  {/* 上一页 */}
@@ -148,32 +206,38 @@ export const Pagination: FC<PaginationProps> = (props) => {
148
206
  </PaginationItem>
149
207
  </PaginationContent>
150
208
  {/* 控制区域 */}
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>
209
+ {showSizeChanger || showQuickJumper ? (
210
+ <div className="flex items-center space-x-3">
211
+ {/* 页面大小选择 */}
212
+ {showSizeChanger ? (
213
+ <Select
214
+ className="w-auto"
215
+ onChange={(v) => onSizeChange?.(Number(v))}
216
+ options={sizeOptions.map((v) => ({ label: sizeText?.replace("%size", `${v}`) || "", value: `${v}` }))}
217
+ value={`${size}`}
218
+ />
219
+ ) : null}
220
+
221
+ {/* 跳转输入框 */}
222
+ {showQuickJumper ? (
223
+ <div className="flex items-center space-x-1 text-sm">
224
+ <span>{go}</span>
225
+ <Input
226
+ className="h-9 w-12 rounded px-2 text-center"
227
+ onChange={handleChange}
228
+ onKeyDown={(e) => {
229
+ if (e.key === "Enter") {
230
+ onChange?.(Number(pageValue));
231
+ }
232
+ }}
233
+ type="text"
234
+ value={pageValue}
235
+ />
236
+ <span>{goSuffix}</span>
237
+ </div>
238
+ ) : null}
175
239
  </div>
176
- </div>
240
+ ) : null}
177
241
  </ShadcnPagination>
178
242
  </div>
179
243
  );