@teamix-evo/ui 0.1.1
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/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: table
|
|
3
|
+
name: Table
|
|
4
|
+
type: component
|
|
5
|
+
category: data-display
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Table
|
|
11
|
+
|
|
12
|
+
基础表格 — 原生 HTML 元素薄包装。**仅视觉与可访问性骨架**(列宽 / 边框 / hover / 选中态);**无排序 / 筛选 / 分页 / 数据流**(那是 `DataTable` 的范畴)。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 简单数据展示(< 50 行,无交互)
|
|
17
|
+
- 静态信息表(键值对 / 配置项)
|
|
18
|
+
- 自定义"业务表"的视觉骨架(自己写排序/筛选/选择)
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 大量数据 / 排序 / 筛选 / 分页 → `DataTable`(基于 TanStack Table)
|
|
23
|
+
- 键值对详情 → `Descriptions`(antd 风格,v0.x)
|
|
24
|
+
- 树结构 → `Tree`(v0.x)
|
|
25
|
+
|
|
26
|
+
## Props
|
|
27
|
+
|
|
28
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
|
|
29
|
+
|
|
30
|
+
<!-- auto:props:begin -->
|
|
31
|
+
_(组件无 `<Name>Props` interface — props 详见 [`table.tsx`](./table.tsx))_
|
|
32
|
+
<!-- auto:props:end -->
|
|
33
|
+
|
|
34
|
+
## 依赖
|
|
35
|
+
|
|
36
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
37
|
+
|
|
38
|
+
<!-- auto:deps:begin -->
|
|
39
|
+
### 同库依赖
|
|
40
|
+
|
|
41
|
+
> `teamix-evo ui add table` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
42
|
+
|
|
43
|
+
| Entry | 类型 | 描述 |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
46
|
+
|
|
47
|
+
### npm 依赖
|
|
48
|
+
|
|
49
|
+
_无 — 本组件不依赖任何 npm 包。_
|
|
50
|
+
<!-- auto:deps:end -->
|
|
51
|
+
|
|
52
|
+
> 子组件:`Table` / `TableHeader`(thead)/ `TableBody`(tbody)/ `TableFooter`(tfoot)/ `TableHead`(th)/ `TableRow`(tr)/ `TableCell`(td)/ `TableCaption`(caption)。
|
|
53
|
+
|
|
54
|
+
## AI 生成纪律
|
|
55
|
+
|
|
56
|
+
- **行选中态**:配 `data-state="selected"` 在 `<TableRow>`,自动套高亮样式
|
|
57
|
+
- **列宽自适应**:不要硬编码 `width: 100px`,用 `<col>` 或 className 控制
|
|
58
|
+
- **数字列右对齐**:数字 / 金额 / 时长用 `className="text-right tabular-nums"`
|
|
59
|
+
- **不嵌套 Table**:嵌套表格语义破碎;改用展开行(`expandable`,DataTable 提供)
|
|
60
|
+
- **大数据集 → DataTable**:Table 不做虚拟滚动;> 200 行用 DataTable + virtualizer
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import {
|
|
66
|
+
Table, TableHeader, TableBody, TableHead, TableRow, TableCell, TableCaption,
|
|
67
|
+
} from '@/components/ui/table';
|
|
68
|
+
import { Badge } from '@/components/ui/badge';
|
|
69
|
+
|
|
70
|
+
<Table>
|
|
71
|
+
<TableCaption>近期发票</TableCaption>
|
|
72
|
+
<TableHeader>
|
|
73
|
+
<TableRow>
|
|
74
|
+
<TableHead className="w-32">编号</TableHead>
|
|
75
|
+
<TableHead>状态</TableHead>
|
|
76
|
+
<TableHead>方式</TableHead>
|
|
77
|
+
<TableHead className="text-right">金额</TableHead>
|
|
78
|
+
</TableRow>
|
|
79
|
+
</TableHeader>
|
|
80
|
+
<TableBody>
|
|
81
|
+
<TableRow>
|
|
82
|
+
<TableCell className="font-medium">INV001</TableCell>
|
|
83
|
+
<TableCell><Badge variant="success">已支付</Badge></TableCell>
|
|
84
|
+
<TableCell>信用卡</TableCell>
|
|
85
|
+
<TableCell className="text-right">¥250.00</TableCell>
|
|
86
|
+
</TableRow>
|
|
87
|
+
<TableRow data-state="selected">
|
|
88
|
+
<TableCell className="font-medium">INV002</TableCell>
|
|
89
|
+
<TableCell><Badge>待支付</Badge></TableCell>
|
|
90
|
+
<TableCell>转账</TableCell>
|
|
91
|
+
<TableCell className="text-right">¥150.00</TableCell>
|
|
92
|
+
</TableRow>
|
|
93
|
+
</TableBody>
|
|
94
|
+
</Table>
|
|
95
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
TableHeader,
|
|
5
|
+
TableBody,
|
|
6
|
+
TableHead,
|
|
7
|
+
TableRow,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableCaption,
|
|
10
|
+
TableFooter,
|
|
11
|
+
} from './table';
|
|
12
|
+
import { Badge } from '@/components/badge/badge';
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof Table> = {
|
|
15
|
+
title: '数据展示 · Data Display/Table',
|
|
16
|
+
component: Table,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof Table>;
|
|
22
|
+
|
|
23
|
+
const invoices = [
|
|
24
|
+
{ id: 'INV001', status: 'paid', method: '信用卡', amount: 250 },
|
|
25
|
+
{ id: 'INV002', status: 'pending', method: '转账', amount: 150 },
|
|
26
|
+
{ id: 'INV003', status: 'paid', method: '支付宝', amount: 350 },
|
|
27
|
+
{ id: 'INV004', status: 'failed', method: '信用卡', amount: 450 },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<Table>
|
|
33
|
+
<TableCaption>近期发票</TableCaption>
|
|
34
|
+
<TableHeader>
|
|
35
|
+
<TableRow>
|
|
36
|
+
<TableHead className="w-32">编号</TableHead>
|
|
37
|
+
<TableHead>状态</TableHead>
|
|
38
|
+
<TableHead>方式</TableHead>
|
|
39
|
+
<TableHead className="text-right">金额</TableHead>
|
|
40
|
+
</TableRow>
|
|
41
|
+
</TableHeader>
|
|
42
|
+
<TableBody>
|
|
43
|
+
{invoices.map((inv) => (
|
|
44
|
+
<TableRow key={inv.id}>
|
|
45
|
+
<TableCell className="font-medium">{inv.id}</TableCell>
|
|
46
|
+
<TableCell>
|
|
47
|
+
{inv.status === 'paid' ? (
|
|
48
|
+
<Badge variant="success">已支付</Badge>
|
|
49
|
+
) : inv.status === 'failed' ? (
|
|
50
|
+
<Badge variant="destructive">失败</Badge>
|
|
51
|
+
) : (
|
|
52
|
+
<Badge>待支付</Badge>
|
|
53
|
+
)}
|
|
54
|
+
</TableCell>
|
|
55
|
+
<TableCell>{inv.method}</TableCell>
|
|
56
|
+
<TableCell className="text-right tabular-nums">
|
|
57
|
+
¥{inv.amount.toFixed(2)}
|
|
58
|
+
</TableCell>
|
|
59
|
+
</TableRow>
|
|
60
|
+
))}
|
|
61
|
+
</TableBody>
|
|
62
|
+
<TableFooter>
|
|
63
|
+
<TableRow>
|
|
64
|
+
<TableCell colSpan={3}>合计</TableCell>
|
|
65
|
+
<TableCell className="text-right tabular-nums">
|
|
66
|
+
¥
|
|
67
|
+
{invoices
|
|
68
|
+
.reduce((sum, x) => sum + x.amount, 0)
|
|
69
|
+
.toFixed(2)}
|
|
70
|
+
</TableCell>
|
|
71
|
+
</TableRow>
|
|
72
|
+
</TableFooter>
|
|
73
|
+
</Table>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<
|
|
6
|
+
HTMLTableElement,
|
|
7
|
+
React.HTMLAttributes<HTMLTableElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div className="relative w-full overflow-auto">
|
|
10
|
+
<table
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn('w-full caption-bottom text-sm', className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
));
|
|
17
|
+
Table.displayName = 'Table';
|
|
18
|
+
|
|
19
|
+
const TableHeader = React.forwardRef<
|
|
20
|
+
HTMLTableSectionElement,
|
|
21
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
|
|
24
|
+
));
|
|
25
|
+
TableHeader.displayName = 'TableHeader';
|
|
26
|
+
|
|
27
|
+
const TableBody = React.forwardRef<
|
|
28
|
+
HTMLTableSectionElement,
|
|
29
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
30
|
+
>(({ className, ...props }, ref) => (
|
|
31
|
+
<tbody
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={cn('[&_tr:last-child]:border-0', className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
));
|
|
37
|
+
TableBody.displayName = 'TableBody';
|
|
38
|
+
|
|
39
|
+
const TableFooter = React.forwardRef<
|
|
40
|
+
HTMLTableSectionElement,
|
|
41
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
42
|
+
>(({ className, ...props }, ref) => (
|
|
43
|
+
<tfoot
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(
|
|
46
|
+
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
));
|
|
52
|
+
TableFooter.displayName = 'TableFooter';
|
|
53
|
+
|
|
54
|
+
const TableRow = React.forwardRef<
|
|
55
|
+
HTMLTableRowElement,
|
|
56
|
+
React.HTMLAttributes<HTMLTableRowElement>
|
|
57
|
+
>(({ className, ...props }, ref) => (
|
|
58
|
+
<tr
|
|
59
|
+
ref={ref}
|
|
60
|
+
className={cn(
|
|
61
|
+
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
));
|
|
67
|
+
TableRow.displayName = 'TableRow';
|
|
68
|
+
|
|
69
|
+
const TableHead = React.forwardRef<
|
|
70
|
+
HTMLTableCellElement,
|
|
71
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
72
|
+
>(({ className, ...props }, ref) => (
|
|
73
|
+
<th
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={cn(
|
|
76
|
+
'h-10 px-3 text-left align-middle text-xs font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
));
|
|
82
|
+
TableHead.displayName = 'TableHead';
|
|
83
|
+
|
|
84
|
+
const TableCell = React.forwardRef<
|
|
85
|
+
HTMLTableCellElement,
|
|
86
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
87
|
+
>(({ className, ...props }, ref) => (
|
|
88
|
+
<td
|
|
89
|
+
ref={ref}
|
|
90
|
+
className={cn(
|
|
91
|
+
'p-3 align-middle [&:has([role=checkbox])]:pr-0',
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
));
|
|
97
|
+
TableCell.displayName = 'TableCell';
|
|
98
|
+
|
|
99
|
+
const TableCaption = React.forwardRef<
|
|
100
|
+
HTMLTableCaptionElement,
|
|
101
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
102
|
+
>(({ className, ...props }, ref) => (
|
|
103
|
+
<caption
|
|
104
|
+
ref={ref}
|
|
105
|
+
className={cn('mt-4 text-sm text-muted-foreground', className)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
));
|
|
109
|
+
TableCaption.displayName = 'TableCaption';
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
Table,
|
|
113
|
+
TableHeader,
|
|
114
|
+
TableBody,
|
|
115
|
+
TableFooter,
|
|
116
|
+
TableHead,
|
|
117
|
+
TableRow,
|
|
118
|
+
TableCell,
|
|
119
|
+
TableCaption,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type TableProps = React.HTMLAttributes<HTMLTableElement>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tabs
|
|
3
|
+
name: Tabs
|
|
4
|
+
type: component
|
|
5
|
+
category: navigation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Tabs
|
|
11
|
+
|
|
12
|
+
标签页 — Radix Tabs + antd `type` (line/card) + `tabBarExtraContent`(`extra`) 并集。
|
|
13
|
+
两种风格切换:`type="card"`(shadcn 默认胶囊),`type="line"`(antd 默认下划线)。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 平级内容切换(详情页的 Overview / Logs / Settings 多视图)
|
|
18
|
+
- 表单分组(基本信息 / 高级配置)
|
|
19
|
+
- 工具栏顶部多视图
|
|
20
|
+
|
|
21
|
+
## When NOT to use
|
|
22
|
+
|
|
23
|
+
- 多步流程 → `Steps`(v0.x)
|
|
24
|
+
- 内容互斥但需对照 → `RadioGroup` + 切换内容区
|
|
25
|
+
- 折叠展开 → `Accordion` / `Collapsible`
|
|
26
|
+
|
|
27
|
+
## Props
|
|
28
|
+
|
|
29
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `TabsList` 的 props;`Tabs`(Root)透传 Radix `value / defaultValue / onValueChange / orientation / activationMode`。
|
|
30
|
+
|
|
31
|
+
<!-- auto:props:begin -->
|
|
32
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
33
|
+
| --- | --- | --- | --- | --- |
|
|
34
|
+
| `type` | `'line' \| 'card'` | `"card"` | – | 视觉风格(antd `type` 并集)。`card` 是 shadcn 默认胶囊;`line` 是 antd 默认下划线。 |
|
|
35
|
+
| `extra` | `React.ReactNode` | – | – | 右侧附加内容(antd `tabBarExtraContent` 并集);仅 `type="line"` 显式支持(布局灵活)。 用法:把节点放进来即可,组件用 `flex justify-between` 自动撑开。 |
|
|
36
|
+
<!-- auto:props:end -->
|
|
37
|
+
|
|
38
|
+
## 依赖
|
|
39
|
+
|
|
40
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
41
|
+
|
|
42
|
+
<!-- auto:deps:begin -->
|
|
43
|
+
### 同库依赖
|
|
44
|
+
|
|
45
|
+
> `teamix-evo ui add tabs` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
46
|
+
|
|
47
|
+
| Entry | 类型 | 描述 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
50
|
+
|
|
51
|
+
### npm 依赖
|
|
52
|
+
|
|
53
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm add @radix-ui/react-tabs@^1.1.0 class-variance-authority@^0.7.0
|
|
57
|
+
```
|
|
58
|
+
<!-- auto:deps:end -->
|
|
59
|
+
|
|
60
|
+
> 子组件:`Tabs` / `TabsList` / `TabsTrigger` / `TabsContent`。
|
|
61
|
+
|
|
62
|
+
## AI 生成纪律
|
|
63
|
+
|
|
64
|
+
- **`type="card"` 默认**:用于嵌入卡片内 / 紧凑场景(shadcn 胶囊视觉)
|
|
65
|
+
- **`type="line"` 用于页面级**:大区块切换、主导航(antd 风格)
|
|
66
|
+
- **`extra` 仅 `type="line"` 视觉对齐**:右侧附加按钮(刷新 / 新建)
|
|
67
|
+
- **value 必稳定**:切换 tab 间不改变 value 字符串
|
|
68
|
+
- **orientation="vertical"** 罕用,改用 Sidebar 模式
|
|
69
|
+
|
|
70
|
+
## Examples
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
|
74
|
+
import { Button } from '@/components/ui/button';
|
|
75
|
+
import { RefreshCw } from 'lucide-react';
|
|
76
|
+
|
|
77
|
+
// shadcn 胶囊风格
|
|
78
|
+
<Tabs defaultValue="overview" className="w-96">
|
|
79
|
+
<TabsList>
|
|
80
|
+
<TabsTrigger value="overview">概览</TabsTrigger>
|
|
81
|
+
<TabsTrigger value="logs">日志</TabsTrigger>
|
|
82
|
+
<TabsTrigger value="settings">设置</TabsTrigger>
|
|
83
|
+
</TabsList>
|
|
84
|
+
<TabsContent value="overview">...</TabsContent>
|
|
85
|
+
<TabsContent value="logs">...</TabsContent>
|
|
86
|
+
<TabsContent value="settings">...</TabsContent>
|
|
87
|
+
</Tabs>
|
|
88
|
+
|
|
89
|
+
// antd 下划线风格 + extra
|
|
90
|
+
<Tabs defaultValue="all">
|
|
91
|
+
<TabsList type="line" extra={<Button size="sm" icon={<RefreshCw />}>刷新</Button>}>
|
|
92
|
+
<TabsTrigger value="all">全部</TabsTrigger>
|
|
93
|
+
<TabsTrigger value="active">活跃</TabsTrigger>
|
|
94
|
+
<TabsTrigger value="archived">已归档</TabsTrigger>
|
|
95
|
+
</TabsList>
|
|
96
|
+
<TabsContent value="all">...</TabsContent>
|
|
97
|
+
</Tabs>
|
|
98
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { RefreshCw } from 'lucide-react';
|
|
3
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs';
|
|
4
|
+
import { Button } from '@/components/button/button';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof TabsList> = {
|
|
7
|
+
title: '导航 · Navigation/Tabs',
|
|
8
|
+
component: TabsList,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
type: { control: 'inline-radio', options: ['card', 'line'] },
|
|
12
|
+
},
|
|
13
|
+
args: { type: 'card' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof TabsList>;
|
|
18
|
+
|
|
19
|
+
export const Card: Story = {
|
|
20
|
+
args: { type: 'card' },
|
|
21
|
+
render: (args) => (
|
|
22
|
+
<Tabs defaultValue="overview" className="w-96">
|
|
23
|
+
<TabsList {...args}>
|
|
24
|
+
<TabsTrigger value="overview">概览</TabsTrigger>
|
|
25
|
+
<TabsTrigger value="logs">日志</TabsTrigger>
|
|
26
|
+
<TabsTrigger value="settings">设置</TabsTrigger>
|
|
27
|
+
</TabsList>
|
|
28
|
+
<TabsContent value="overview">概览内容</TabsContent>
|
|
29
|
+
<TabsContent value="logs">日志内容</TabsContent>
|
|
30
|
+
<TabsContent value="settings">设置内容</TabsContent>
|
|
31
|
+
</Tabs>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Line: Story = {
|
|
36
|
+
args: { type: 'line' },
|
|
37
|
+
render: (args) => (
|
|
38
|
+
<Tabs defaultValue="all" className="w-96">
|
|
39
|
+
<TabsList {...args}>
|
|
40
|
+
<TabsTrigger value="all">全部</TabsTrigger>
|
|
41
|
+
<TabsTrigger value="active">活跃</TabsTrigger>
|
|
42
|
+
<TabsTrigger value="archived">已归档</TabsTrigger>
|
|
43
|
+
</TabsList>
|
|
44
|
+
<TabsContent value="all">全部内容</TabsContent>
|
|
45
|
+
<TabsContent value="active">活跃内容</TabsContent>
|
|
46
|
+
<TabsContent value="archived">已归档</TabsContent>
|
|
47
|
+
</Tabs>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const WithExtra: Story = {
|
|
52
|
+
parameters: { controls: { disable: true } },
|
|
53
|
+
render: () => (
|
|
54
|
+
<Tabs defaultValue="all" className="w-[480px]">
|
|
55
|
+
<TabsList
|
|
56
|
+
type="line"
|
|
57
|
+
extra={
|
|
58
|
+
<Button size="sm" variant="outline" icon={<RefreshCw />}>
|
|
59
|
+
刷新
|
|
60
|
+
</Button>
|
|
61
|
+
}
|
|
62
|
+
>
|
|
63
|
+
<TabsTrigger value="all">全部</TabsTrigger>
|
|
64
|
+
<TabsTrigger value="active">活跃</TabsTrigger>
|
|
65
|
+
<TabsTrigger value="archived">已归档</TabsTrigger>
|
|
66
|
+
</TabsList>
|
|
67
|
+
<TabsContent value="all">全部内容...</TabsContent>
|
|
68
|
+
</Tabs>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
|
|
7
|
+
const Tabs = TabsPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const tabsListVariants = cva('inline-flex items-center justify-center', {
|
|
10
|
+
variants: {
|
|
11
|
+
type: {
|
|
12
|
+
line: 'border-b border-border w-full justify-start gap-4',
|
|
13
|
+
card: 'h-9 rounded-md bg-muted p-1 text-muted-foreground',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: { type: 'card' },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const tabsTriggerVariants = cva(
|
|
20
|
+
'inline-flex items-center justify-center whitespace-nowrap text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
type: {
|
|
24
|
+
line: 'relative px-1 py-2 -mb-px border-b-2 border-transparent text-muted-foreground hover:text-foreground data-[state=active]:border-primary data-[state=active]:text-foreground',
|
|
25
|
+
card: 'rounded-sm px-3 py-1 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: { type: 'card' },
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface TabsListProps
|
|
33
|
+
extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>,
|
|
34
|
+
VariantProps<typeof tabsListVariants> {
|
|
35
|
+
/**
|
|
36
|
+
* 视觉风格(antd `type` 并集)。`card` 是 shadcn 默认胶囊;`line` 是 antd 默认下划线。
|
|
37
|
+
* @default "card"
|
|
38
|
+
*/
|
|
39
|
+
type?: 'line' | 'card';
|
|
40
|
+
/**
|
|
41
|
+
* 右侧附加内容(antd `tabBarExtraContent` 并集);仅 `type="line"` 显式支持(布局灵活)。
|
|
42
|
+
* 用法:把节点放进来即可,组件用 `flex justify-between` 自动撑开。
|
|
43
|
+
*/
|
|
44
|
+
extra?: React.ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const TabsListContext = React.createContext<{ type: 'line' | 'card' }>({
|
|
48
|
+
type: 'card',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const TabsList = React.forwardRef<
|
|
52
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
53
|
+
TabsListProps
|
|
54
|
+
>(({ className, type = 'card', extra, children, ...props }, ref) => {
|
|
55
|
+
const list = (
|
|
56
|
+
<TabsPrimitive.List
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(tabsListVariants({ type }), className)}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<TabsListContext.Provider value={{ type }}>
|
|
62
|
+
{children}
|
|
63
|
+
</TabsListContext.Provider>
|
|
64
|
+
</TabsPrimitive.List>
|
|
65
|
+
);
|
|
66
|
+
if (extra) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={cn(
|
|
70
|
+
'flex items-center justify-between',
|
|
71
|
+
type === 'line' && 'border-b border-border',
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{React.cloneElement(list, {
|
|
75
|
+
className: cn(
|
|
76
|
+
tabsListVariants({ type }),
|
|
77
|
+
type === 'line' && 'border-b-0',
|
|
78
|
+
className,
|
|
79
|
+
),
|
|
80
|
+
})}
|
|
81
|
+
<div className="shrink-0">{extra}</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return list;
|
|
86
|
+
});
|
|
87
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
88
|
+
|
|
89
|
+
const TabsTrigger = React.forwardRef<
|
|
90
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
91
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
92
|
+
>(({ className, ...props }, ref) => {
|
|
93
|
+
const { type } = React.useContext(TabsListContext);
|
|
94
|
+
return (
|
|
95
|
+
<TabsPrimitive.Trigger
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(tabsTriggerVariants({ type }), className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
103
|
+
|
|
104
|
+
const TabsContent = React.forwardRef<
|
|
105
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
106
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
107
|
+
>(({ className, ...props }, ref) => (
|
|
108
|
+
<TabsPrimitive.Content
|
|
109
|
+
ref={ref}
|
|
110
|
+
className={cn(
|
|
111
|
+
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
112
|
+
className,
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
));
|
|
117
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
118
|
+
|
|
119
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tag
|
|
3
|
+
name: Tag
|
|
4
|
+
type: component
|
|
5
|
+
category: foundation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Tag
|
|
11
|
+
|
|
12
|
+
标签 — antd 独有补足。**与 Badge 区别**:Tag 偏向**用户可关闭的关键词标签**(分类 / 标签 / 筛选条件),支持 `closable` + 6 种语义色;Badge 偏向**状态徽标**(数字 / 红点 / 状态条)。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 关键词 / 标签 / 分类(博客标签、用户兴趣)
|
|
17
|
+
- 当前筛选条件展示(可关闭以移除条件)
|
|
18
|
+
- 列表项的次级标签(优先级 / 类型)
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 状态指示 → `Badge`
|
|
23
|
+
- 切换按钮 → `Toggle` / `ToggleGroup`
|
|
24
|
+
- 选择类输入 → `Combobox` / `Select`
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
| `color` | `'default' \| 'primary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `"default"` | – | 语义颜色(antd `color` 并集) — 与 Badge 不同,Tag 偏向**用户可关闭的标签**(关键词 / 分类),Badge 偏向状态徽标。 |
|
|
30
|
+
| `closable` | `boolean` | `false` | – | 显示关闭按钮(antd `closable` 并集)。 |
|
|
31
|
+
| `onClose` | `() => void` | – | – | 关闭回调。 |
|
|
32
|
+
<!-- auto:props:end -->
|
|
33
|
+
|
|
34
|
+
<!-- auto:deps:begin -->
|
|
35
|
+
### 同库依赖
|
|
36
|
+
|
|
37
|
+
> `teamix-evo ui add tag` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
38
|
+
|
|
39
|
+
| Entry | 类型 | 描述 |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
42
|
+
|
|
43
|
+
### npm 依赖
|
|
44
|
+
|
|
45
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add class-variance-authority@^0.7.0 lucide-react@^0.460.0
|
|
49
|
+
```
|
|
50
|
+
<!-- auto:deps:end -->
|
|
51
|
+
|
|
52
|
+
## AI 生成纪律
|
|
53
|
+
|
|
54
|
+
- **`color` 反映语义**:不要随便选 — `error` 仅用于真错误,`success` 仅用于已通过
|
|
55
|
+
- **`closable` 必配 `onClose`**:让父组件知道哪个 tag 被移除,业务侧维护数组
|
|
56
|
+
- **文字 ≤ 4 个汉字 / 8 字符**:过长的标签视觉破碎
|
|
57
|
+
- **不要嵌套交互**:Tag 内不要放 Button / Link
|
|
58
|
+
- **筛选条件场景**:配 `closable` 让用户一键移除条件
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Tag } from '@/components/ui/tag';
|
|
64
|
+
import * as React from 'react';
|
|
65
|
+
|
|
66
|
+
// 基础
|
|
67
|
+
<Tag>React</Tag>
|
|
68
|
+
<Tag color="primary">Vue</Tag>
|
|
69
|
+
<Tag color="success">已通过</Tag>
|
|
70
|
+
<Tag color="warning">待审核</Tag>
|
|
71
|
+
<Tag color="error">失败</Tag>
|
|
72
|
+
|
|
73
|
+
// 可关闭
|
|
74
|
+
const [tags, setTags] = React.useState(['React', 'Vue', 'Angular']);
|
|
75
|
+
<div className="flex gap-2">
|
|
76
|
+
{tags.map((t) => (
|
|
77
|
+
<Tag
|
|
78
|
+
key={t}
|
|
79
|
+
color="primary"
|
|
80
|
+
closable
|
|
81
|
+
onClose={() => setTags(tags.filter((x) => x !== t))}
|
|
82
|
+
>
|
|
83
|
+
{t}
|
|
84
|
+
</Tag>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
// 筛选条件
|
|
89
|
+
<div className="flex flex-wrap gap-2">
|
|
90
|
+
<Tag closable>状态: 运行中</Tag>
|
|
91
|
+
<Tag closable>类型: 服务</Tag>
|
|
92
|
+
<Tag closable>地区: 杭州</Tag>
|
|
93
|
+
</div>
|
|
94
|
+
```
|