@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.
Files changed (270) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -0
  3. package/_data.json +12 -0
  4. package/manifest.json +1688 -0
  5. package/package.json +90 -0
  6. package/src/components/accordion/accordion.meta.md +87 -0
  7. package/src/components/accordion/accordion.stories.tsx +67 -0
  8. package/src/components/accordion/accordion.tsx +58 -0
  9. package/src/components/affix/affix.meta.md +80 -0
  10. package/src/components/affix/affix.stories.tsx +57 -0
  11. package/src/components/affix/affix.tsx +97 -0
  12. package/src/components/alert/alert.meta.md +101 -0
  13. package/src/components/alert/alert.stories.tsx +93 -0
  14. package/src/components/alert/alert.tsx +132 -0
  15. package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
  16. package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
  17. package/src/components/alert-dialog/alert-dialog.tsx +136 -0
  18. package/src/components/anchor/anchor.meta.md +87 -0
  19. package/src/components/anchor/anchor.stories.tsx +74 -0
  20. package/src/components/anchor/anchor.tsx +130 -0
  21. package/src/components/app/app.meta.md +86 -0
  22. package/src/components/app/app.stories.tsx +62 -0
  23. package/src/components/app/app.tsx +58 -0
  24. package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
  25. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
  26. package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
  27. package/src/components/auto-complete/auto-complete.meta.md +102 -0
  28. package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
  29. package/src/components/auto-complete/auto-complete.tsx +205 -0
  30. package/src/components/avatar/avatar.meta.md +94 -0
  31. package/src/components/avatar/avatar.stories.tsx +80 -0
  32. package/src/components/avatar/avatar.tsx +126 -0
  33. package/src/components/badge/badge.meta.md +119 -0
  34. package/src/components/badge/badge.stories.tsx +153 -0
  35. package/src/components/badge/badge.tsx +210 -0
  36. package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
  37. package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
  38. package/src/components/breadcrumb/breadcrumb.tsx +122 -0
  39. package/src/components/button/button.meta.md +98 -0
  40. package/src/components/button/button.stories.tsx +235 -0
  41. package/src/components/button/button.tsx +160 -0
  42. package/src/components/button-group/button-group.meta.md +92 -0
  43. package/src/components/button-group/button-group.stories.tsx +90 -0
  44. package/src/components/button-group/button-group.tsx +75 -0
  45. package/src/components/calendar/calendar.meta.md +118 -0
  46. package/src/components/calendar/calendar.stories.tsx +68 -0
  47. package/src/components/calendar/calendar.tsx +107 -0
  48. package/src/components/card/card.meta.md +117 -0
  49. package/src/components/card/card.stories.tsx +112 -0
  50. package/src/components/card/card.tsx +222 -0
  51. package/src/components/carousel/carousel.meta.md +117 -0
  52. package/src/components/carousel/carousel.stories.tsx +84 -0
  53. package/src/components/carousel/carousel.tsx +224 -0
  54. package/src/components/cascader/cascader.meta.md +110 -0
  55. package/src/components/cascader/cascader.stories.tsx +108 -0
  56. package/src/components/cascader/cascader.tsx +198 -0
  57. package/src/components/checkbox/checkbox.meta.md +99 -0
  58. package/src/components/checkbox/checkbox.stories.tsx +130 -0
  59. package/src/components/checkbox/checkbox.tsx +125 -0
  60. package/src/components/collapsible/collapsible.meta.md +80 -0
  61. package/src/components/collapsible/collapsible.stories.tsx +35 -0
  62. package/src/components/collapsible/collapsible.tsx +18 -0
  63. package/src/components/color-picker/color-picker.meta.md +84 -0
  64. package/src/components/color-picker/color-picker.stories.tsx +80 -0
  65. package/src/components/color-picker/color-picker.tsx +160 -0
  66. package/src/components/combobox/combobox.meta.md +93 -0
  67. package/src/components/combobox/combobox.stories.tsx +55 -0
  68. package/src/components/combobox/combobox.tsx +130 -0
  69. package/src/components/command/command.meta.md +104 -0
  70. package/src/components/command/command.stories.tsx +59 -0
  71. package/src/components/command/command.tsx +147 -0
  72. package/src/components/context-menu/context-menu.meta.md +90 -0
  73. package/src/components/context-menu/context-menu.stories.tsx +46 -0
  74. package/src/components/context-menu/context-menu.tsx +191 -0
  75. package/src/components/data-table/data-table.meta.md +149 -0
  76. package/src/components/data-table/data-table.stories.tsx +125 -0
  77. package/src/components/data-table/data-table.tsx +185 -0
  78. package/src/components/date-picker/date-picker.meta.md +106 -0
  79. package/src/components/date-picker/date-picker.stories.tsx +58 -0
  80. package/src/components/date-picker/date-picker.tsx +156 -0
  81. package/src/components/descriptions/descriptions.meta.md +78 -0
  82. package/src/components/descriptions/descriptions.stories.tsx +60 -0
  83. package/src/components/descriptions/descriptions.tsx +129 -0
  84. package/src/components/dialog/dialog.meta.md +105 -0
  85. package/src/components/dialog/dialog.stories.tsx +93 -0
  86. package/src/components/dialog/dialog.tsx +128 -0
  87. package/src/components/drawer/drawer.meta.md +96 -0
  88. package/src/components/drawer/drawer.stories.tsx +54 -0
  89. package/src/components/drawer/drawer.tsx +114 -0
  90. package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
  91. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
  92. package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
  93. package/src/components/empty/empty.meta.md +81 -0
  94. package/src/components/empty/empty.stories.tsx +46 -0
  95. package/src/components/empty/empty.tsx +47 -0
  96. package/src/components/field/field.meta.md +116 -0
  97. package/src/components/field/field.stories.tsx +117 -0
  98. package/src/components/field/field.tsx +164 -0
  99. package/src/components/flex/flex.meta.md +94 -0
  100. package/src/components/flex/flex.stories.tsx +112 -0
  101. package/src/components/flex/flex.tsx +122 -0
  102. package/src/components/float-button/float-button.meta.md +87 -0
  103. package/src/components/float-button/float-button.stories.tsx +78 -0
  104. package/src/components/float-button/float-button.tsx +143 -0
  105. package/src/components/form/form.meta.md +131 -0
  106. package/src/components/form/form.stories.tsx +122 -0
  107. package/src/components/form/form.tsx +194 -0
  108. package/src/components/grid/grid.meta.md +87 -0
  109. package/src/components/grid/grid.stories.tsx +99 -0
  110. package/src/components/grid/grid.tsx +130 -0
  111. package/src/components/hover-card/hover-card.meta.md +92 -0
  112. package/src/components/hover-card/hover-card.stories.tsx +68 -0
  113. package/src/components/hover-card/hover-card.tsx +29 -0
  114. package/src/components/image/image.meta.md +94 -0
  115. package/src/components/image/image.stories.tsx +55 -0
  116. package/src/components/image/image.tsx +138 -0
  117. package/src/components/input/input.meta.md +109 -0
  118. package/src/components/input/input.stories.tsx +117 -0
  119. package/src/components/input/input.tsx +213 -0
  120. package/src/components/input-group/input-group.meta.md +92 -0
  121. package/src/components/input-group/input-group.stories.tsx +88 -0
  122. package/src/components/input-group/input-group.tsx +107 -0
  123. package/src/components/input-number/input-number.meta.md +91 -0
  124. package/src/components/input-number/input-number.stories.tsx +87 -0
  125. package/src/components/input-number/input-number.tsx +210 -0
  126. package/src/components/input-otp/input-otp.meta.md +105 -0
  127. package/src/components/input-otp/input-otp.stories.tsx +65 -0
  128. package/src/components/input-otp/input-otp.tsx +97 -0
  129. package/src/components/item/item.meta.md +116 -0
  130. package/src/components/item/item.stories.tsx +113 -0
  131. package/src/components/item/item.tsx +171 -0
  132. package/src/components/kbd/kbd.meta.md +85 -0
  133. package/src/components/kbd/kbd.stories.tsx +70 -0
  134. package/src/components/kbd/kbd.tsx +81 -0
  135. package/src/components/label/label.meta.md +91 -0
  136. package/src/components/label/label.stories.tsx +87 -0
  137. package/src/components/label/label.tsx +66 -0
  138. package/src/components/masonry/masonry.meta.md +85 -0
  139. package/src/components/masonry/masonry.stories.tsx +66 -0
  140. package/src/components/masonry/masonry.tsx +59 -0
  141. package/src/components/mentions/mentions.meta.md +89 -0
  142. package/src/components/mentions/mentions.stories.tsx +75 -0
  143. package/src/components/mentions/mentions.tsx +237 -0
  144. package/src/components/menubar/menubar.meta.md +100 -0
  145. package/src/components/menubar/menubar.stories.tsx +81 -0
  146. package/src/components/menubar/menubar.tsx +232 -0
  147. package/src/components/native-select/native-select.meta.md +88 -0
  148. package/src/components/native-select/native-select.stories.tsx +80 -0
  149. package/src/components/native-select/native-select.tsx +54 -0
  150. package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
  151. package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
  152. package/src/components/navigation-menu/navigation-menu.tsx +125 -0
  153. package/src/components/notification/notification.meta.md +91 -0
  154. package/src/components/notification/notification.stories.tsx +96 -0
  155. package/src/components/notification/notification.tsx +84 -0
  156. package/src/components/pagination/pagination.meta.md +127 -0
  157. package/src/components/pagination/pagination.stories.tsx +62 -0
  158. package/src/components/pagination/pagination.tsx +285 -0
  159. package/src/components/popconfirm/popconfirm.meta.md +109 -0
  160. package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
  161. package/src/components/popconfirm/popconfirm.tsx +134 -0
  162. package/src/components/popover/popover.meta.md +97 -0
  163. package/src/components/popover/popover.stories.tsx +82 -0
  164. package/src/components/popover/popover.tsx +55 -0
  165. package/src/components/progress/progress.meta.md +86 -0
  166. package/src/components/progress/progress.stories.tsx +75 -0
  167. package/src/components/progress/progress.tsx +195 -0
  168. package/src/components/radio-group/radio-group.meta.md +103 -0
  169. package/src/components/radio-group/radio-group.stories.tsx +77 -0
  170. package/src/components/radio-group/radio-group.tsx +78 -0
  171. package/src/components/rate/rate.meta.md +87 -0
  172. package/src/components/rate/rate.stories.tsx +81 -0
  173. package/src/components/rate/rate.tsx +153 -0
  174. package/src/components/resizable/resizable.meta.md +92 -0
  175. package/src/components/resizable/resizable.stories.tsx +104 -0
  176. package/src/components/resizable/resizable.tsx +56 -0
  177. package/src/components/result/result.meta.md +90 -0
  178. package/src/components/result/result.stories.tsx +71 -0
  179. package/src/components/result/result.tsx +91 -0
  180. package/src/components/scroll-area/scroll-area.meta.md +84 -0
  181. package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
  182. package/src/components/scroll-area/scroll-area.tsx +51 -0
  183. package/src/components/segmented/segmented.meta.md +103 -0
  184. package/src/components/segmented/segmented.stories.tsx +101 -0
  185. package/src/components/segmented/segmented.tsx +138 -0
  186. package/src/components/select/select.meta.md +110 -0
  187. package/src/components/select/select.stories.tsx +100 -0
  188. package/src/components/select/select.tsx +188 -0
  189. package/src/components/separator/separator.meta.md +74 -0
  190. package/src/components/separator/separator.stories.tsx +71 -0
  191. package/src/components/separator/separator.tsx +104 -0
  192. package/src/components/sheet/sheet.meta.md +97 -0
  193. package/src/components/sheet/sheet.stories.tsx +82 -0
  194. package/src/components/sheet/sheet.tsx +139 -0
  195. package/src/components/sidebar/sidebar.meta.md +131 -0
  196. package/src/components/sidebar/sidebar.stories.tsx +82 -0
  197. package/src/components/sidebar/sidebar.tsx +351 -0
  198. package/src/components/skeleton/skeleton.meta.md +95 -0
  199. package/src/components/skeleton/skeleton.stories.tsx +79 -0
  200. package/src/components/skeleton/skeleton.tsx +144 -0
  201. package/src/components/slider/slider.meta.md +94 -0
  202. package/src/components/slider/slider.stories.tsx +69 -0
  203. package/src/components/slider/slider.tsx +86 -0
  204. package/src/components/sonner/sonner.meta.md +96 -0
  205. package/src/components/sonner/sonner.stories.tsx +91 -0
  206. package/src/components/sonner/sonner.tsx +40 -0
  207. package/src/components/space/space.meta.md +94 -0
  208. package/src/components/space/space.stories.tsx +94 -0
  209. package/src/components/space/space.tsx +106 -0
  210. package/src/components/spinner/spinner.meta.md +76 -0
  211. package/src/components/spinner/spinner.stories.tsx +71 -0
  212. package/src/components/spinner/spinner.tsx +64 -0
  213. package/src/components/statistic/statistic.meta.md +99 -0
  214. package/src/components/statistic/statistic.stories.tsx +71 -0
  215. package/src/components/statistic/statistic.tsx +197 -0
  216. package/src/components/steps/steps.meta.md +102 -0
  217. package/src/components/steps/steps.stories.tsx +75 -0
  218. package/src/components/steps/steps.tsx +170 -0
  219. package/src/components/switch/switch.meta.md +92 -0
  220. package/src/components/switch/switch.stories.tsx +75 -0
  221. package/src/components/switch/switch.tsx +101 -0
  222. package/src/components/table/table.meta.md +95 -0
  223. package/src/components/table/table.stories.tsx +75 -0
  224. package/src/components/table/table.tsx +122 -0
  225. package/src/components/tabs/tabs.meta.md +98 -0
  226. package/src/components/tabs/tabs.stories.tsx +70 -0
  227. package/src/components/tabs/tabs.tsx +119 -0
  228. package/src/components/tag/tag.meta.md +94 -0
  229. package/src/components/tag/tag.stories.tsx +77 -0
  230. package/src/components/tag/tag.tsx +185 -0
  231. package/src/components/textarea/textarea.meta.md +83 -0
  232. package/src/components/textarea/textarea.stories.tsx +63 -0
  233. package/src/components/textarea/textarea.tsx +113 -0
  234. package/src/components/time-picker/time-picker.meta.md +83 -0
  235. package/src/components/time-picker/time-picker.stories.tsx +59 -0
  236. package/src/components/time-picker/time-picker.tsx +94 -0
  237. package/src/components/timeline/timeline.meta.md +102 -0
  238. package/src/components/timeline/timeline.stories.tsx +104 -0
  239. package/src/components/timeline/timeline.tsx +147 -0
  240. package/src/components/toggle/toggle.meta.md +88 -0
  241. package/src/components/toggle/toggle.stories.tsx +66 -0
  242. package/src/components/toggle/toggle.tsx +53 -0
  243. package/src/components/toggle-group/toggle-group.meta.md +90 -0
  244. package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
  245. package/src/components/toggle-group/toggle-group.tsx +78 -0
  246. package/src/components/tooltip/tooltip.meta.md +99 -0
  247. package/src/components/tooltip/tooltip.stories.tsx +71 -0
  248. package/src/components/tooltip/tooltip.tsx +93 -0
  249. package/src/components/tour/tour.meta.md +116 -0
  250. package/src/components/tour/tour.stories.tsx +66 -0
  251. package/src/components/tour/tour.tsx +242 -0
  252. package/src/components/transfer/transfer.meta.md +90 -0
  253. package/src/components/transfer/transfer.stories.tsx +68 -0
  254. package/src/components/transfer/transfer.tsx +251 -0
  255. package/src/components/tree/tree.meta.md +111 -0
  256. package/src/components/tree/tree.stories.tsx +109 -0
  257. package/src/components/tree/tree.tsx +367 -0
  258. package/src/components/tree-select/tree-select.meta.md +100 -0
  259. package/src/components/tree-select/tree-select.stories.tsx +80 -0
  260. package/src/components/tree-select/tree-select.tsx +171 -0
  261. package/src/components/typography/typography.meta.md +102 -0
  262. package/src/components/typography/typography.stories.tsx +115 -0
  263. package/src/components/typography/typography.tsx +245 -0
  264. package/src/components/upload/upload.meta.md +111 -0
  265. package/src/components/upload/upload.stories.tsx +75 -0
  266. package/src/components/upload/upload.tsx +265 -0
  267. package/src/components/watermark/watermark.meta.md +95 -0
  268. package/src/components/watermark/watermark.stories.tsx +78 -0
  269. package/src/components/watermark/watermark.tsx +165 -0
  270. package/src/utils/cn.ts +6 -0
@@ -0,0 +1,171 @@
1
+ import * as React from 'react';
2
+ import { ChevronDown } from 'lucide-react';
3
+
4
+ import { cn } from '@/utils/cn';
5
+ import { Button } from '@/components/button/button';
6
+ import {
7
+ Popover,
8
+ PopoverContent,
9
+ PopoverTrigger,
10
+ } from '@/components/popover/popover';
11
+ import { Tree, type TreeNode } from '@/components/tree/tree';
12
+
13
+ export interface TreeSelectProps {
14
+ /** 树形数据(antd `treeData` 并集)。 */
15
+ data: TreeNode[];
16
+ /**
17
+ * 多选模式 — 启用后下拉内的 Tree 变为 checkable;value 是 key 数组。
18
+ * @default false
19
+ */
20
+ multiple?: boolean;
21
+ /**
22
+ * 受控 value:
23
+ * - `multiple=false`:`string`(单 key)/ `undefined`(未选)
24
+ * - `multiple=true`:`string[]`
25
+ */
26
+ value?: string | string[];
27
+ /** uncontrolled 初值。 */
28
+ defaultValue?: string | string[];
29
+ /** value 变化回调。 */
30
+ onChange?: (value: string | string[]) => void;
31
+ /**
32
+ * 默认展开全部节点(antd `treeDefaultExpandAll` 并集)。
33
+ * @default false
34
+ */
35
+ defaultExpandAll?: boolean;
36
+ /** 占位文本。 @default "请选择" */
37
+ placeholder?: string;
38
+ /** 整体禁用。 */
39
+ disabled?: boolean;
40
+ /** 触发器 className。 */
41
+ className?: string;
42
+ /**
43
+ * 触发器尺寸。
44
+ * @default "default"
45
+ */
46
+ size?: 'sm' | 'default' | 'lg';
47
+ }
48
+
49
+ function flattenLabelMap(nodes: TreeNode[]): Map<string, React.ReactNode> {
50
+ const map = new Map<string, React.ReactNode>();
51
+ const walk = (n: TreeNode) => {
52
+ map.set(n.key, n.title);
53
+ n.children?.forEach(walk);
54
+ };
55
+ nodes.forEach(walk);
56
+ return map;
57
+ }
58
+
59
+ /**
60
+ * 树形下拉选择 — antd 独有补足。**等价 antd `TreeSelect`**。
61
+ * 把 `Tree` 嵌入 Popover,提供单选(selectable)或多选(checkable)两种模式 — 组织架构 / 分类树形选择常用。
62
+ */
63
+ const TreeSelect = React.forwardRef<HTMLButtonElement, TreeSelectProps>(
64
+ (
65
+ {
66
+ data,
67
+ multiple = false,
68
+ value,
69
+ defaultValue,
70
+ onChange,
71
+ defaultExpandAll = false,
72
+ placeholder = '请选择',
73
+ disabled = false,
74
+ className,
75
+ size = 'default',
76
+ },
77
+ ref,
78
+ ) => {
79
+ const isControlled = value !== undefined;
80
+ const [internal, setInternal] = React.useState<string | string[]>(
81
+ defaultValue ?? (multiple ? [] : ''),
82
+ );
83
+ const current = isControlled ? value! : internal;
84
+
85
+ const [open, setOpen] = React.useState(false);
86
+
87
+ const labelMap = React.useMemo(() => flattenLabelMap(data), [data]);
88
+
89
+ const display = React.useMemo(() => {
90
+ if (multiple) {
91
+ const arr = (current as string[]) ?? [];
92
+ if (arr.length === 0) return '';
93
+ if (arr.length <= 3) return arr.map((k) => labelMap.get(k) ?? k);
94
+ return `已选 ${arr.length} 项`;
95
+ }
96
+ const k = current as string;
97
+ return k ? labelMap.get(k) : '';
98
+ }, [current, labelMap, multiple]);
99
+
100
+ const renderDisplay = () => {
101
+ if (!display) return placeholder;
102
+ if (Array.isArray(display)) {
103
+ return (
104
+ <span className="flex flex-wrap gap-1">
105
+ {display.map((d, i) => (
106
+ <span
107
+ key={i}
108
+ className="inline-flex rounded-sm bg-muted px-1.5 py-0.5 text-xs"
109
+ >
110
+ {d}
111
+ </span>
112
+ ))}
113
+ </span>
114
+ );
115
+ }
116
+ return <span className="truncate">{display}</span>;
117
+ };
118
+
119
+ return (
120
+ <Popover open={open} onOpenChange={setOpen}>
121
+ <PopoverTrigger asChild>
122
+ <Button
123
+ ref={ref}
124
+ type="button"
125
+ variant="outline"
126
+ size={size}
127
+ disabled={disabled}
128
+ className={cn(
129
+ 'min-w-[200px] justify-between font-normal',
130
+ !display && 'text-muted-foreground',
131
+ className,
132
+ )}
133
+ >
134
+ <span className="min-w-0 flex-1 text-left">{renderDisplay()}</span>
135
+ <ChevronDown className="ml-2 size-4 shrink-0 opacity-50" />
136
+ </Button>
137
+ </PopoverTrigger>
138
+ <PopoverContent className="w-72 p-2" align="start">
139
+ {multiple ? (
140
+ <Tree
141
+ data={data}
142
+ checkable
143
+ selectable={false}
144
+ defaultExpandAll={defaultExpandAll}
145
+ checkedKeys={current as string[]}
146
+ onCheck={(next) => {
147
+ if (!isControlled) setInternal(next);
148
+ onChange?.(next);
149
+ }}
150
+ />
151
+ ) : (
152
+ <Tree
153
+ data={data}
154
+ defaultExpandAll={defaultExpandAll}
155
+ selectedKeys={current ? [current as string] : []}
156
+ onSelect={(next) => {
157
+ const v = next[0] ?? '';
158
+ if (!isControlled) setInternal(v);
159
+ onChange?.(v);
160
+ setOpen(false);
161
+ }}
162
+ />
163
+ )}
164
+ </PopoverContent>
165
+ </Popover>
166
+ );
167
+ },
168
+ );
169
+ TreeSelect.displayName = 'TreeSelect';
170
+
171
+ export { TreeSelect };
@@ -0,0 +1,102 @@
1
+ ---
2
+ id: typography
3
+ name: Typography
4
+ type: component
5
+ category: data-display
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Typography
11
+
12
+ 排版组件 — `Prose`(富文本容器)+ shadcn 风格 `Title / Paragraph / Text / Link` + antd `Text` 的 `type / strong / delete / disabled / code / ellipsis / copyable` 并集。
13
+
14
+ > Prose 不依赖 `@tailwindcss/typography` 插件,样式手写并对齐 OpenTrek tokens。
15
+
16
+ ## When to use
17
+
18
+ - **Prose**:Markdown / CMS 富文本渲染容器(自动套排版样式)
19
+ - **Title**:页面 / 区块标题(`level={1..5}` 自动映射 `<h1>~<h5>`)
20
+ - **Paragraph**:正文段落(行高 / 字号 / 颜色统一)
21
+ - **Text**:行内文本带语义色 / 强调 / 复制
22
+ - **Link**:语义化链接(类型 + 默认下划线 hover)
23
+
24
+ ## When NOT to use
25
+
26
+ - 富文本编辑(可编辑) → 用 Tiptap / Lexical 等编辑器
27
+ - 单一颜色文本 → 直接 `<span className="text-...">`,无需包装
28
+ - 多行 ellipsis(2 行 / 3 行) → 自行 `line-clamp-N`(本组件 ellipsis 仅单行)
29
+
30
+ <!-- auto:props:begin -->
31
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
32
+ | --- | --- | --- | --- | --- |
33
+ | `type` | `TextType` | `"default"` | – | 语义色(antd `type` 并集)。 |
34
+ | `delete` | `boolean` | `false` | – | 删除线。 |
35
+ | `disabled` | `boolean` | `false` | – | 不可用(灰色 + 不可选)。 |
36
+ | `strong` | `boolean` | `false` | – | 加粗。 |
37
+ | `code` | `boolean` | `false` | – | 行内代码样式(`code`)。 |
38
+ | `ellipsis` | `boolean` | `false` | – | 单行省略(antd `ellipsis` 简化版 — 只支持 boolean,行数 / suffix 留 v0.x)。 |
39
+ | `copyable` | `boolean \| { text: string; tooltips?: [string, string] }` | `false` | – | 可复制(antd `copyable` 并集)— 末尾追加复制图标按钮。 传对象时可指定要复制的文本(默认是 children 的纯文本);传 `true` 用 children。 |
40
+ <!-- auto:props:end -->
41
+
42
+ <!-- auto:deps:begin -->
43
+ ### 同库依赖
44
+
45
+ > `teamix-evo ui add typography` 时,以下 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 lucide-react@^0.460.0
57
+ ```
58
+ <!-- auto:deps:end -->
59
+
60
+ > 子组件:`Prose` / `Title`(level 1~5)/ `Paragraph` / `Text`(type / strong / delete / disabled / code / ellipsis / copyable)/ `Link`。各自的 props 详见 [`typography.tsx`](./typography.tsx)。
61
+
62
+ ## AI 生成纪律
63
+
64
+ - **`Title level` 决定标签**:不要用 `<h2>` 配 `level={1}`,语义会冲突
65
+ - **`Text type` 优先于 className 颜色**:语义色让暗色模式自动适配
66
+ - **`copyable` 仅当 children 是 string**:富节点请显式传 `copyable={{ text: '...' }}`
67
+ - **`ellipsis` 单行**:多行省略请直接 `className="line-clamp-2"`
68
+ - **Prose 内部不要嵌套 Prose**:嵌套排版会双倍间距
69
+
70
+ ## Examples
71
+
72
+ ```tsx
73
+ import { Prose, Title, Paragraph, Text, Link } from '@/components/ui/typography';
74
+
75
+ // 标题层级
76
+ <Title level={1}>页面标题</Title>
77
+ <Title level={2}>章节标题</Title>
78
+
79
+ // 段落
80
+ <Paragraph>正文段落,统一字号 / 行高 / 颜色。</Paragraph>
81
+
82
+ // 行内文本
83
+ <Text type="success" strong>成功</Text>
84
+ <Text type="danger" delete>已废弃</Text>
85
+ <Text disabled>不可用</Text>
86
+ <Text code>const x = 1</Text>
87
+ <Text ellipsis>这是一段非常长的文字...被截断</Text>
88
+ <Text copyable>可复制的文本</Text>
89
+ <Text copyable={{ text: 'lyca@teamix.dev' }}>邮箱(复制不同的内容)</Text>
90
+
91
+ // 链接
92
+ <Link href="/docs">文档</Link>
93
+ <Link href="/danger" type="danger">危险链接</Link>
94
+
95
+ // Prose 容器(渲染 Markdown 等富文本)
96
+ <Prose>
97
+ <h1>Markdown 标题</h1>
98
+ <p>段落...</p>
99
+ <ul><li>列表项</li></ul>
100
+ <pre><code>console.log('hi')</code></pre>
101
+ </Prose>
102
+ ```
@@ -0,0 +1,115 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Prose, Title, Paragraph, Text, Link } from './typography';
3
+
4
+ const meta: Meta<typeof Text> = {
5
+ title: '数据展示 · Data Display/Typography',
6
+ component: Text,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ '排版组件 — Prose 富文本容器 + Title/Paragraph/Text/Link,合并 antd 的 type/strong/delete/disabled/code/ellipsis/copyable 能力。OpenTrek tokens 适配,无 mock。',
13
+ },
14
+ },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof Text>;
20
+
21
+ export const Titles: Story = {
22
+ parameters: { controls: { disable: true } },
23
+ render: () => (
24
+ <div className="flex flex-col gap-2">
25
+ {([1, 2, 3, 4, 5] as const).map((l) => (
26
+ <Title key={l} level={l}>
27
+ Level {l} 标题
28
+ </Title>
29
+ ))}
30
+ </div>
31
+ ),
32
+ };
33
+
34
+ export const TextTypes: Story = {
35
+ parameters: { controls: { disable: true } },
36
+ render: () => (
37
+ <div className="flex flex-col gap-2 text-sm">
38
+ <Text>默认</Text>
39
+ <Text type="secondary">次要</Text>
40
+ <Text type="success">成功</Text>
41
+ <Text type="warning">警告</Text>
42
+ <Text type="danger">危险</Text>
43
+ <Text strong>加粗</Text>
44
+ <Text delete>已废弃</Text>
45
+ <Text disabled>不可用</Text>
46
+ <Text code>const x = 1</Text>
47
+ </div>
48
+ ),
49
+ };
50
+
51
+ export const Copyable: Story = {
52
+ parameters: { controls: { disable: true } },
53
+ render: () => (
54
+ <div className="flex flex-col gap-2 text-sm">
55
+ <Text copyable>这段文字可复制</Text>
56
+ <Text copyable={{ text: 'lyca@teamix.dev' }}>邮箱(复制实际地址)</Text>
57
+ </div>
58
+ ),
59
+ };
60
+
61
+ export const Ellipsis: Story = {
62
+ parameters: { controls: { disable: true } },
63
+ render: () => (
64
+ <div className="w-48">
65
+ <Text ellipsis>
66
+ 这是一段非常非常非常长的文字内容,在窄容器内会被自动截断成单行。
67
+ </Text>
68
+ </div>
69
+ ),
70
+ };
71
+
72
+ export const Links: Story = {
73
+ parameters: { controls: { disable: true } },
74
+ render: () => (
75
+ <div className="flex flex-col gap-2 text-sm">
76
+ <Link href="#docs" onClick={(e) => e.preventDefault()}>
77
+ 文档
78
+ </Link>
79
+ <Link
80
+ href="#delete"
81
+ type="danger"
82
+ onClick={(e) => e.preventDefault()}
83
+ >
84
+ 危险操作链接
85
+ </Link>
86
+ </div>
87
+ ),
88
+ };
89
+
90
+ export const ProseDemo: Story = {
91
+ parameters: { controls: { disable: true } },
92
+ render: () => (
93
+ <Prose>
94
+ <h1>Prose 容器</h1>
95
+ <p>
96
+ Prose 给原始 HTML(Markdown 渲染产物 / CMS 富文本)套上一致排版样式 —
97
+ 标题字号、段落行高、列表样式、代码块、引用。
98
+ </p>
99
+ <h2>子标题</h2>
100
+ <p>
101
+ 访问 <a href="#">链接</a> 查看更多。<strong>强调</strong>{' '}
102
+ <em>斜体</em> <code>inline-code</code>。
103
+ </p>
104
+ <ul>
105
+ <li>列表项 1</li>
106
+ <li>列表项 2</li>
107
+ <li>列表项 3</li>
108
+ </ul>
109
+ <blockquote>这是一段引用文字。</blockquote>
110
+ <pre>
111
+ <code>{`function hello() {\n return 'world';\n}`}</code>
112
+ </pre>
113
+ </Prose>
114
+ ),
115
+ };
@@ -0,0 +1,245 @@
1
+ import * as React from 'react';
2
+ import { Check, Copy } from 'lucide-react';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ // ─── Prose container ─────────────────────────────────────────────────────────
7
+
8
+ export interface ProseProps extends React.HTMLAttributes<HTMLDivElement> {}
9
+
10
+ /**
11
+ * Prose 容器 — 给原始 HTML(Markdown 渲染产物 / CMS 富文本)套上一致排版样式。
12
+ * 内部自动应用 Tailwind typography 风格的边距 / 标题字号 / 列表 / 链接 / 代码 / 引用。
13
+ *
14
+ * 注意:本组件刻意**不依赖** `@tailwindcss/typography` 插件 — 改用手写 selector
15
+ * 链让样式可控、与 design tokens 对齐。
16
+ */
17
+ const Prose = React.forwardRef<HTMLDivElement, ProseProps>(
18
+ ({ className, ...props }, ref) => (
19
+ <div
20
+ ref={ref}
21
+ className={cn(
22
+ 'max-w-prose text-sm text-foreground',
23
+ // headings
24
+ '[&_h1]:mt-8 [&_h1]:mb-4 [&_h1]:text-3xl [&_h1]:font-bold [&_h1]:tracking-tight',
25
+ '[&_h2]:mt-6 [&_h2]:mb-3 [&_h2]:text-2xl [&_h2]:font-semibold',
26
+ '[&_h3]:mt-5 [&_h3]:mb-2 [&_h3]:text-xl [&_h3]:font-semibold',
27
+ '[&_h4]:mt-4 [&_h4]:mb-2 [&_h4]:text-lg [&_h4]:font-medium',
28
+ // paragraphs / lists
29
+ '[&_p]:my-3 [&_p]:leading-7',
30
+ '[&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-6',
31
+ '[&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-6',
32
+ '[&_li]:my-1',
33
+ // inline
34
+ '[&_a]:text-primary [&_a]:underline-offset-4 hover:[&_a]:underline',
35
+ '[&_strong]:font-semibold',
36
+ '[&_em]:italic',
37
+ // code
38
+ '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.875em]',
39
+ '[&_pre]:my-4 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-4',
40
+ '[&_pre_code]:bg-transparent [&_pre_code]:p-0',
41
+ // blockquote / hr
42
+ '[&_blockquote]:my-4 [&_blockquote]:border-l-4 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground',
43
+ '[&_hr]:my-6 [&_hr]:border-border',
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ ),
49
+ );
50
+ Prose.displayName = 'Prose';
51
+
52
+ // ─── Title ───────────────────────────────────────────────────────────────────
53
+
54
+ const titleSizes = {
55
+ 1: 'text-3xl font-bold tracking-tight',
56
+ 2: 'text-2xl font-semibold tracking-tight',
57
+ 3: 'text-xl font-semibold',
58
+ 4: 'text-lg font-medium',
59
+ 5: 'text-base font-medium',
60
+ } as const;
61
+
62
+ export interface TitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
63
+ /**
64
+ * 层级(1~5),决定字号 / 粗细;同时映射为 `<h1>~<h5>` 标签。
65
+ * @default 1
66
+ */
67
+ level?: 1 | 2 | 3 | 4 | 5;
68
+ }
69
+
70
+ const Title = React.forwardRef<HTMLHeadingElement, TitleProps>(
71
+ ({ level = 1, className, ...props }, ref) => {
72
+ const Comp = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
73
+ return (
74
+ <Comp
75
+ ref={ref}
76
+ className={cn(titleSizes[level], className)}
77
+ {...props}
78
+ />
79
+ );
80
+ },
81
+ );
82
+ Title.displayName = 'Title';
83
+
84
+ // ─── Paragraph ───────────────────────────────────────────────────────────────
85
+
86
+ export interface ParagraphProps
87
+ extends React.HTMLAttributes<HTMLParagraphElement> {}
88
+
89
+ const Paragraph = React.forwardRef<HTMLParagraphElement, ParagraphProps>(
90
+ ({ className, ...props }, ref) => (
91
+ <p
92
+ ref={ref}
93
+ className={cn('text-sm leading-7 text-foreground', className)}
94
+ {...props}
95
+ />
96
+ ),
97
+ );
98
+ Paragraph.displayName = 'Paragraph';
99
+
100
+ // ─── Text(行内文本,支持 antd ellipsis / copyable / type)──────────────────
101
+
102
+ export type TextType = 'default' | 'secondary' | 'success' | 'warning' | 'danger';
103
+
104
+ export interface TextProps extends React.HTMLAttributes<HTMLSpanElement> {
105
+ /**
106
+ * 语义色(antd `type` 并集)。
107
+ * @default "default"
108
+ */
109
+ type?: TextType;
110
+ /**
111
+ * 删除线。
112
+ * @default false
113
+ */
114
+ delete?: boolean;
115
+ /**
116
+ * 不可用(灰色 + 不可选)。
117
+ * @default false
118
+ */
119
+ disabled?: boolean;
120
+ /**
121
+ * 加粗。
122
+ * @default false
123
+ */
124
+ strong?: boolean;
125
+ /**
126
+ * 行内代码样式(`code`)。
127
+ * @default false
128
+ */
129
+ code?: boolean;
130
+ /**
131
+ * 单行省略(antd `ellipsis` 简化版 — 只支持 boolean,行数 / suffix 留 v0.x)。
132
+ * @default false
133
+ */
134
+ ellipsis?: boolean;
135
+ /**
136
+ * 可复制(antd `copyable` 并集)— 末尾追加复制图标按钮。
137
+ * 传对象时可指定要复制的文本(默认是 children 的纯文本);传 `true` 用 children。
138
+ * @default false
139
+ */
140
+ copyable?: boolean | { text: string; tooltips?: [string, string] };
141
+ }
142
+
143
+ const typeColor: Record<TextType, string> = {
144
+ default: 'text-foreground',
145
+ secondary: 'text-muted-foreground',
146
+ success: 'text-emerald-600',
147
+ warning: 'text-amber-600',
148
+ danger: 'text-destructive',
149
+ };
150
+
151
+ const Text = React.forwardRef<HTMLSpanElement, TextProps>(
152
+ (
153
+ {
154
+ type = 'default',
155
+ delete: del = false,
156
+ disabled = false,
157
+ strong = false,
158
+ code = false,
159
+ ellipsis = false,
160
+ copyable = false,
161
+ className,
162
+ children,
163
+ ...props
164
+ },
165
+ ref,
166
+ ) => {
167
+ const [copied, setCopied] = React.useState(false);
168
+ const handleCopy = () => {
169
+ const text =
170
+ typeof copyable === 'object' && copyable.text
171
+ ? copyable.text
172
+ : typeof children === 'string'
173
+ ? children
174
+ : '';
175
+ void navigator.clipboard?.writeText(text).then(() => {
176
+ setCopied(true);
177
+ window.setTimeout(() => setCopied(false), 1500);
178
+ });
179
+ };
180
+
181
+ const cls = cn(
182
+ typeColor[type],
183
+ strong && 'font-semibold',
184
+ del && 'line-through',
185
+ disabled && 'cursor-not-allowed select-none opacity-50',
186
+ ellipsis && 'inline-block max-w-full truncate align-bottom',
187
+ code &&
188
+ 'rounded bg-muted px-1.5 py-0.5 font-mono text-[0.875em]',
189
+ className,
190
+ );
191
+
192
+ if (copyable) {
193
+ return (
194
+ <span className="inline-flex items-center gap-1">
195
+ <span ref={ref} className={cls} {...props}>
196
+ {children}
197
+ </span>
198
+ <button
199
+ type="button"
200
+ onClick={handleCopy}
201
+ aria-label={copied ? '已复制' : '复制'}
202
+ className="rounded-sm p-0.5 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
203
+ >
204
+ {copied ? (
205
+ <Check className="size-3.5 text-emerald-600" />
206
+ ) : (
207
+ <Copy className="size-3.5" />
208
+ )}
209
+ </button>
210
+ </span>
211
+ );
212
+ }
213
+
214
+ return (
215
+ <span ref={ref} className={cls} {...props}>
216
+ {children}
217
+ </span>
218
+ );
219
+ },
220
+ );
221
+ Text.displayName = 'Text';
222
+
223
+ // ─── Link(antd 风格,默认下划线 hover)────────────────────────────────────
224
+
225
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
226
+ /** 语义色(同 Text 的 type)。 @default "default" */
227
+ type?: TextType;
228
+ }
229
+
230
+ const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
231
+ ({ className, type = 'default', ...props }, ref) => (
232
+ <a
233
+ ref={ref}
234
+ className={cn(
235
+ 'underline-offset-4 transition-colors hover:underline',
236
+ type === 'default' ? 'text-primary' : typeColor[type],
237
+ className,
238
+ )}
239
+ {...props}
240
+ />
241
+ ),
242
+ );
243
+ Link.displayName = 'Link';
244
+
245
+ export { Prose, Title, Paragraph, Text, Link };