@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,153 @@
1
+ import * as React from 'react';
2
+ import { Star } from 'lucide-react';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ export interface RateProps
7
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
8
+ /**
9
+ * 总星数(antd `count` 并集)。
10
+ * @default 5
11
+ */
12
+ count?: number;
13
+ /** 受控值。 */
14
+ value?: number;
15
+ /** uncontrolled 初值。 */
16
+ defaultValue?: number;
17
+ /**
18
+ * 允许半星(antd `allowHalf` 并集) — 启用后单星可点击左半 / 右半。
19
+ * @default false
20
+ */
21
+ allowHalf?: boolean;
22
+ /**
23
+ * 允许再次点击同值清零(antd `allowClear` 并集)。
24
+ * @default true
25
+ */
26
+ allowClear?: boolean;
27
+ /** 禁用(只展示)。 */
28
+ disabled?: boolean;
29
+ /**
30
+ * 尺寸(影响星图大小)。
31
+ * @default "default"
32
+ */
33
+ size?: 'sm' | 'default' | 'lg';
34
+ /**
35
+ * 自定义图标(antd `character` 并集) — 默认 `lucide-react` 的 `Star`。
36
+ */
37
+ character?: React.ReactNode;
38
+ /** value 变化回调。 */
39
+ onChange?: (value: number) => void;
40
+ }
41
+
42
+ const sizeMap = {
43
+ sm: 'size-4',
44
+ default: 'size-5',
45
+ lg: 'size-7',
46
+ } as const;
47
+
48
+ const Rate = React.forwardRef<HTMLDivElement, RateProps>(
49
+ (
50
+ {
51
+ count = 5,
52
+ value,
53
+ defaultValue,
54
+ allowHalf = false,
55
+ allowClear = true,
56
+ disabled = false,
57
+ size = 'default',
58
+ character,
59
+ onChange,
60
+ className,
61
+ ...props
62
+ },
63
+ ref,
64
+ ) => {
65
+ const isControlled = value !== undefined;
66
+ const [internal, setInternal] = React.useState<number>(defaultValue ?? 0);
67
+ const current = isControlled ? value! : internal;
68
+ const [hover, setHover] = React.useState<number | null>(null);
69
+
70
+ const active = hover ?? current;
71
+
72
+ const set = (next: number) => {
73
+ const final = allowClear && next === current ? 0 : next;
74
+ if (!isControlled) setInternal(final);
75
+ onChange?.(final);
76
+ };
77
+
78
+ const Icon = character ?? <Star className={cn(sizeMap[size])} />;
79
+
80
+ return (
81
+ <div
82
+ ref={ref}
83
+ role="radiogroup"
84
+ aria-disabled={disabled}
85
+ className={cn(
86
+ 'inline-flex items-center gap-1',
87
+ disabled && 'cursor-not-allowed opacity-50',
88
+ className,
89
+ )}
90
+ onMouseLeave={() => !disabled && setHover(null)}
91
+ {...props}
92
+ >
93
+ {Array.from({ length: count }).map((_, i) => {
94
+ const fullValue = i + 1;
95
+ const halfValue = i + 0.5;
96
+ const filledFull = active >= fullValue;
97
+ const filledHalf = !filledFull && allowHalf && active >= halfValue;
98
+
99
+ return (
100
+ <span
101
+ key={i}
102
+ role="radio"
103
+ aria-checked={current >= fullValue}
104
+ className="relative inline-flex"
105
+ >
106
+ {allowHalf && !disabled ? (
107
+ <>
108
+ <span
109
+ onMouseEnter={() => setHover(halfValue)}
110
+ onClick={() => set(halfValue)}
111
+ className="absolute left-0 top-0 z-10 size-1/2 cursor-pointer"
112
+ aria-hidden="true"
113
+ />
114
+ <span
115
+ onMouseEnter={() => setHover(fullValue)}
116
+ onClick={() => set(fullValue)}
117
+ className="absolute right-0 top-0 z-10 size-1/2 cursor-pointer"
118
+ aria-hidden="true"
119
+ />
120
+ </>
121
+ ) : !disabled ? (
122
+ <span
123
+ onMouseEnter={() => setHover(fullValue)}
124
+ onClick={() => set(fullValue)}
125
+ className="absolute inset-0 z-10 cursor-pointer"
126
+ aria-hidden="true"
127
+ />
128
+ ) : null}
129
+
130
+ {filledFull ? (
131
+ <span className="text-amber-400">{React.isValidElement(Icon) ? React.cloneElement(Icon as React.ReactElement<{ fill?: string }>, { fill: 'currentColor' }) : Icon}</span>
132
+ ) : filledHalf ? (
133
+ <span className="relative inline-block text-amber-400">
134
+ <span className="absolute inset-0 overflow-hidden" style={{ clipPath: 'inset(0 50% 0 0)' }}>
135
+ {React.isValidElement(Icon)
136
+ ? React.cloneElement(Icon as React.ReactElement<{ fill?: string }>, { fill: 'currentColor' })
137
+ : Icon}
138
+ </span>
139
+ <span className="text-muted-foreground/40">{Icon}</span>
140
+ </span>
141
+ ) : (
142
+ <span className="text-muted-foreground/40">{Icon}</span>
143
+ )}
144
+ </span>
145
+ );
146
+ })}
147
+ </div>
148
+ );
149
+ },
150
+ );
151
+ Rate.displayName = 'Rate';
152
+
153
+ export { Rate };
@@ -0,0 +1,92 @@
1
+ ---
2
+ id: resizable
3
+ name: Resizable
4
+ type: component
5
+ category: layout
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Resizable
11
+
12
+ 可拖拽分栏 — 基于 [`react-resizable-panels`](https://react-resizable-panels.vercel.app/),桌面应用 / 编辑器风格(VS Code、Figma、Slack)。
13
+ 对应 antd `Splitter`(v5.16+)的核心场景。
14
+
15
+ ## When to use
16
+
17
+ - 编辑器双栏 / 三栏布局(目录 / 编辑区 / 预览)
18
+ - 可调宽度的侧边栏 + 主区
19
+ - 上下分割面板(终端 / 输出)
20
+ - 嵌套面板(网格化布局)
21
+
22
+ ## When NOT to use
23
+
24
+ - 固定宽度的 Sidebar → `Sidebar`(支持折叠 / icon 模式)
25
+ - 临时分栏 → `Sheet` / `Drawer`
26
+ - 卡片网格 → CSS Grid
27
+
28
+ <!-- auto:props:begin -->
29
+ _(组件无 `<Name>Props` interface — props 详见 [`resizable.tsx`](./resizable.tsx))_
30
+ <!-- auto:props:end -->
31
+
32
+ <!-- auto:deps:begin -->
33
+ ### 同库依赖
34
+
35
+ > `teamix-evo ui add resizable` 时,以下 entry 会被自动连带安装(无需手动 add)。
36
+
37
+ | Entry | 类型 | 描述 |
38
+ | --- | --- | --- |
39
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
40
+
41
+ ### npm 依赖
42
+
43
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
44
+
45
+ ```bash
46
+ pnpm add react-resizable-panels@^2.0.0 lucide-react@^0.460.0
47
+ ```
48
+ <!-- auto:deps:end -->
49
+
50
+ > 子组件:`ResizablePanelGroup`(`direction="horizontal" | "vertical"`)/ `ResizablePanel`(`defaultSize / minSize / maxSize` 百分比)/ `ResizableHandle`(分隔条,`withHandle` 显示握把)。
51
+
52
+ ## AI 生成纪律
53
+
54
+ - **`PanelGroup` 必给容器固定尺寸**:height / width 不能 0(否则面板无可分配空间)
55
+ - **`defaultSize` 总和应为 100**:N 个 panel 的默认大小相加 = 100
56
+ - **`minSize` 防止面板被拖到 0**:推荐每个 panel 最小 10~15(百分比)
57
+ - **`withHandle` 提升可发现性**:握把图标让用户立刻意识到可拖拽
58
+ - **嵌套**:嵌套 `PanelGroup` 时内层 `direction` 与外层不同,做网格化布局
59
+
60
+ ## Examples
61
+
62
+ ```tsx
63
+ import {
64
+ ResizablePanelGroup, ResizablePanel, ResizableHandle,
65
+ } from '@/components/ui/resizable';
66
+
67
+ // 横向双栏
68
+ <ResizablePanelGroup direction="horizontal" className="min-h-[300px] rounded-lg border">
69
+ <ResizablePanel defaultSize={30} minSize={20}>
70
+ <div className="flex h-full items-center justify-center p-6">侧栏</div>
71
+ </ResizablePanel>
72
+ <ResizableHandle withHandle />
73
+ <ResizablePanel defaultSize={70}>
74
+ <div className="flex h-full items-center justify-center p-6">主区</div>
75
+ </ResizablePanel>
76
+ </ResizablePanelGroup>
77
+
78
+ // 编辑器三栏(嵌套)
79
+ <ResizablePanelGroup direction="horizontal" className="min-h-[400px]">
80
+ <ResizablePanel defaultSize={20}>目录</ResizablePanel>
81
+ <ResizableHandle />
82
+ <ResizablePanel defaultSize={55}>
83
+ <ResizablePanelGroup direction="vertical">
84
+ <ResizablePanel defaultSize={70}>编辑区</ResizablePanel>
85
+ <ResizableHandle />
86
+ <ResizablePanel defaultSize={30}>终端</ResizablePanel>
87
+ </ResizablePanelGroup>
88
+ </ResizablePanel>
89
+ <ResizableHandle />
90
+ <ResizablePanel defaultSize={25}>预览</ResizablePanel>
91
+ </ResizablePanelGroup>
92
+ ```
@@ -0,0 +1,104 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import {
3
+ ResizablePanelGroup,
4
+ ResizablePanel,
5
+ ResizableHandle,
6
+ } from './resizable';
7
+
8
+ const meta: Meta<typeof ResizablePanelGroup> = {
9
+ title: '布局与容器 · Layout/Resizable',
10
+ component: ResizablePanelGroup,
11
+ tags: ['autodocs'],
12
+ parameters: {
13
+ docs: {
14
+ description: {
15
+ component:
16
+ '可拖拽分栏 — 编辑器 / 桌面应用风格(VS Code、Figma)。基于 react-resizable-panels,支持横/纵向、嵌套、min/max 限制、握把可视化。OpenTrek tokens 适配。',
17
+ },
18
+ },
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof ResizablePanelGroup>;
24
+
25
+ export const Horizontal: Story = {
26
+ render: () => (
27
+ <ResizablePanelGroup
28
+ direction="horizontal"
29
+ className="min-h-[300px] w-[600px] rounded-lg border"
30
+ >
31
+ <ResizablePanel defaultSize={30} minSize={20}>
32
+ <div className="flex h-full items-center justify-center p-6">
33
+ <span className="text-sm">侧栏(30%)</span>
34
+ </div>
35
+ </ResizablePanel>
36
+ <ResizableHandle withHandle />
37
+ <ResizablePanel defaultSize={70}>
38
+ <div className="flex h-full items-center justify-center p-6">
39
+ <span className="text-sm">主区(70%)</span>
40
+ </div>
41
+ </ResizablePanel>
42
+ </ResizablePanelGroup>
43
+ ),
44
+ };
45
+
46
+ export const Vertical: Story = {
47
+ parameters: { controls: { disable: true } },
48
+ render: () => (
49
+ <ResizablePanelGroup
50
+ direction="vertical"
51
+ className="min-h-[400px] w-[400px] rounded-lg border"
52
+ >
53
+ <ResizablePanel defaultSize={70}>
54
+ <div className="flex h-full items-center justify-center p-6 text-sm">
55
+ 上(70%)
56
+ </div>
57
+ </ResizablePanel>
58
+ <ResizableHandle />
59
+ <ResizablePanel defaultSize={30}>
60
+ <div className="flex h-full items-center justify-center p-6 text-sm">
61
+ 下(30%)
62
+ </div>
63
+ </ResizablePanel>
64
+ </ResizablePanelGroup>
65
+ ),
66
+ };
67
+
68
+ export const Nested: Story = {
69
+ parameters: { controls: { disable: true } },
70
+ render: () => (
71
+ <ResizablePanelGroup
72
+ direction="horizontal"
73
+ className="min-h-[400px] w-[700px] rounded-lg border"
74
+ >
75
+ <ResizablePanel defaultSize={20} minSize={15}>
76
+ <div className="flex h-full items-center justify-center p-4 text-sm">
77
+ 目录
78
+ </div>
79
+ </ResizablePanel>
80
+ <ResizableHandle withHandle />
81
+ <ResizablePanel defaultSize={55}>
82
+ <ResizablePanelGroup direction="vertical">
83
+ <ResizablePanel defaultSize={70}>
84
+ <div className="flex h-full items-center justify-center p-4 text-sm">
85
+ 编辑区
86
+ </div>
87
+ </ResizablePanel>
88
+ <ResizableHandle />
89
+ <ResizablePanel defaultSize={30}>
90
+ <div className="flex h-full items-center justify-center p-4 text-sm">
91
+ 终端
92
+ </div>
93
+ </ResizablePanel>
94
+ </ResizablePanelGroup>
95
+ </ResizablePanel>
96
+ <ResizableHandle withHandle />
97
+ <ResizablePanel defaultSize={25}>
98
+ <div className="flex h-full items-center justify-center p-4 text-sm">
99
+ 预览
100
+ </div>
101
+ </ResizablePanel>
102
+ </ResizablePanelGroup>
103
+ ),
104
+ };
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ import { GripVertical } from 'lucide-react';
3
+ import * as ResizablePrimitive from 'react-resizable-panels';
4
+
5
+ import { cn } from '@/utils/cn';
6
+
7
+ export interface ResizablePanelGroupProps
8
+ extends React.ComponentProps<typeof ResizablePrimitive.PanelGroup> {}
9
+
10
+ const ResizablePanelGroup = ({
11
+ className,
12
+ ...props
13
+ }: ResizablePanelGroupProps) => (
14
+ <ResizablePrimitive.PanelGroup
15
+ className={cn(
16
+ 'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ ResizablePanelGroup.displayName = 'ResizablePanelGroup';
23
+
24
+ const ResizablePanel = ResizablePrimitive.Panel;
25
+
26
+ export interface ResizableHandleProps
27
+ extends React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> {
28
+ /**
29
+ * 是否在 handle 上显示握把图标(`<GripVertical />`),提升可发现性。
30
+ * @default false
31
+ */
32
+ withHandle?: boolean;
33
+ }
34
+
35
+ const ResizableHandle = ({
36
+ withHandle = false,
37
+ className,
38
+ ...props
39
+ }: ResizableHandleProps) => (
40
+ <ResizablePrimitive.PanelResizeHandle
41
+ className={cn(
42
+ 'relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90',
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ {withHandle ? (
48
+ <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
49
+ <GripVertical className="size-2.5" />
50
+ </div>
51
+ ) : null}
52
+ </ResizablePrimitive.PanelResizeHandle>
53
+ );
54
+ ResizableHandle.displayName = 'ResizableHandle';
55
+
56
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
@@ -0,0 +1,90 @@
1
+ ---
2
+ id: result
3
+ name: Result
4
+ type: component
5
+ category: feedback
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Result
11
+
12
+ 结果页 — antd 独有补足。**整页级反馈**:操作成功、失败、404、403、500 等场景的居中状态页(图标 + 标题 + 副标题 + 操作区)。
13
+
14
+ ## When to use
15
+
16
+ - 操作完成的成功页(订单提交、支付完成)
17
+ - 错误页(500 / 404 / 403)
18
+ - 表单提交失败的反馈页
19
+ - 空数据 + 引导操作(也可考虑 `Empty`)
20
+
21
+ ## When NOT to use
22
+
23
+ - 行内提示 → `Alert`
24
+ - 短暂消息 → `Sonner`
25
+ - 阻断式确认 → `AlertDialog`
26
+
27
+ <!-- auto:props:begin -->
28
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
29
+ | --- | --- | --- | --- | --- |
30
+ | `status` | `ResultStatus` | `"info"` | – | 状态类型 — 决定默认图标和配色;`404 / 403 / 500` 显示大字号错误码代替图标。 |
31
+ | `icon` | `React.ReactNode` | – | – | 自定义图标(覆盖默认)。 |
32
+ | `title` | `React.ReactNode` | – | – | 标题。 |
33
+ | `subTitle` | `React.ReactNode` | – | – | 副标题(描述)。 |
34
+ | `extra` | `React.ReactNode` | – | – | 操作区(放 Button)。 |
35
+ <!-- auto:props:end -->
36
+
37
+ <!-- auto:deps:begin -->
38
+ ### 同库依赖
39
+
40
+ > `teamix-evo ui add result` 时,以下 entry 会被自动连带安装(无需手动 add)。
41
+
42
+ | Entry | 类型 | 描述 |
43
+ | --- | --- | --- |
44
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
45
+
46
+ ### npm 依赖
47
+
48
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
49
+
50
+ ```bash
51
+ pnpm add lucide-react@^0.460.0
52
+ ```
53
+ <!-- auto:deps:end -->
54
+
55
+ ## AI 生成纪律
56
+
57
+ - **`status` 决定默认图标 + 配色**:用语义,不要随便覆盖
58
+ - **`extra` 必有主 / 次操作**:错误页放"重试 + 返回",成功页放"查看详情 + 完成"
59
+ - **大字号错误码**(`404 / 403 / 500`)自动渲染,无需自行加 icon
60
+ - **不要嵌套 Result**:Result 是页面级组件
61
+ - **subTitle 简短**:< 1 段,长说明用单独的内容区
62
+
63
+ ## Examples
64
+
65
+ ```tsx
66
+ import { Result } from '@/components/ui/result';
67
+ import { Button } from '@/components/ui/button';
68
+
69
+ // 成功
70
+ <Result
71
+ status="success"
72
+ title="提交成功"
73
+ subTitle="订单已生成,我们将尽快处理。"
74
+ extra={[
75
+ <Button key="view">查看订单</Button>,
76
+ <Button key="back" variant="outline">返回首页</Button>,
77
+ ]}
78
+ />
79
+
80
+ // 错误页
81
+ <Result
82
+ status="500"
83
+ title="服务异常"
84
+ subTitle="抱歉,系统出错。请稍后重试或联系管理员。"
85
+ extra={<Button>返回首页</Button>}
86
+ />
87
+
88
+ // 404
89
+ <Result status="404" title="页面不存在" extra={<Button>返回首页</Button>} />
90
+ ```
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Result } from './result';
3
+ import { Button } from '@/components/button/button';
4
+
5
+ const meta: Meta<typeof Result> = {
6
+ title: '反馈与浮层 · Feedback/Result',
7
+ component: Result,
8
+ tags: ['autodocs'],
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ '结果页 — 整页级反馈(成功 / 失败 / 404 / 403 / 500)。图标 + 标题 + 副标题 + 操作区,自动配色。OpenTrek tokens 适配,等价 antd Result。',
14
+ },
15
+ },
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+ type Story = StoryObj<typeof Result>;
21
+
22
+ export const Success: Story = {
23
+ render: () => (
24
+ <Result
25
+ status="success"
26
+ title="提交成功"
27
+ subTitle="订单已生成,我们将尽快处理并通过邮件通知你。"
28
+ extra={
29
+ <>
30
+ <Button>查看订单</Button>
31
+ <Button variant="outline">返回首页</Button>
32
+ </>
33
+ }
34
+ />
35
+ ),
36
+ };
37
+
38
+ export const Error500: Story = {
39
+ parameters: { controls: { disable: true } },
40
+ render: () => (
41
+ <Result
42
+ status="500"
43
+ title="服务异常"
44
+ subTitle="抱歉,系统出错了。请稍后重试或联系管理员。"
45
+ extra={<Button>返回首页</Button>}
46
+ />
47
+ ),
48
+ };
49
+
50
+ export const NotFound: Story = {
51
+ parameters: { controls: { disable: true } },
52
+ render: () => (
53
+ <Result
54
+ status="404"
55
+ title="页面不存在"
56
+ subTitle="你访问的页面已被移除或不存在。"
57
+ extra={<Button>返回首页</Button>}
58
+ />
59
+ ),
60
+ };
61
+
62
+ export const Forbidden: Story = {
63
+ parameters: { controls: { disable: true } },
64
+ render: () => (
65
+ <Result
66
+ status="403"
67
+ title="无权访问"
68
+ subTitle="联系管理员申请访问权限。"
69
+ />
70
+ ),
71
+ };
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import {
3
+ AlertCircle,
4
+ CheckCircle2,
5
+ Info,
6
+ XCircle,
7
+ } from 'lucide-react';
8
+
9
+ import { cn } from '@/utils/cn';
10
+
11
+ export type ResultStatus = 'success' | 'error' | 'info' | 'warning' | '404' | '403' | '500';
12
+
13
+ const iconMap = {
14
+ success: { Icon: CheckCircle2, color: 'text-emerald-500' },
15
+ error: { Icon: XCircle, color: 'text-destructive' },
16
+ info: { Icon: Info, color: 'text-blue-500' },
17
+ warning: { Icon: AlertCircle, color: 'text-amber-500' },
18
+ '404': { Icon: AlertCircle, color: 'text-muted-foreground' },
19
+ '403': { Icon: AlertCircle, color: 'text-muted-foreground' },
20
+ '500': { Icon: AlertCircle, color: 'text-destructive' },
21
+ } as const;
22
+
23
+ const codeText = {
24
+ '404': '404',
25
+ '403': '403',
26
+ '500': '500',
27
+ } as const;
28
+
29
+ export interface ResultProps
30
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
31
+ /**
32
+ * 状态类型 — 决定默认图标和配色;`404 / 403 / 500` 显示大字号错误码代替图标。
33
+ * @default "info"
34
+ */
35
+ status?: ResultStatus;
36
+ /** 自定义图标(覆盖默认)。 */
37
+ icon?: React.ReactNode;
38
+ /** 标题。 */
39
+ title?: React.ReactNode;
40
+ /** 副标题(描述)。 */
41
+ subTitle?: React.ReactNode;
42
+ /** 操作区(放 Button)。 */
43
+ extra?: React.ReactNode;
44
+ }
45
+
46
+ const Result = React.forwardRef<HTMLDivElement, ResultProps>(
47
+ (
48
+ { status = 'info', icon, title, subTitle, extra, className, children, ...props },
49
+ ref,
50
+ ) => {
51
+ const { Icon, color } = iconMap[status];
52
+ const isCode = status === '404' || status === '403' || status === '500';
53
+ return (
54
+ <div
55
+ ref={ref}
56
+ className={cn(
57
+ 'flex flex-col items-center gap-4 px-6 py-12 text-center',
58
+ className,
59
+ )}
60
+ {...props}
61
+ >
62
+ {icon ? (
63
+ <div className="text-6xl">{icon}</div>
64
+ ) : isCode ? (
65
+ <div className={cn('text-7xl font-bold', color)}>
66
+ {codeText[status as '404' | '403' | '500']}
67
+ </div>
68
+ ) : (
69
+ <Icon className={cn('size-16', color)} aria-hidden="true" />
70
+ )}
71
+ {title ? (
72
+ <h2 className="text-2xl font-semibold">{title}</h2>
73
+ ) : null}
74
+ {subTitle ? (
75
+ <p className="max-w-prose text-sm text-muted-foreground">
76
+ {subTitle}
77
+ </p>
78
+ ) : null}
79
+ {children ? <div className="w-full">{children}</div> : null}
80
+ {extra ? (
81
+ <div className="flex flex-wrap items-center justify-center gap-2">
82
+ {extra}
83
+ </div>
84
+ ) : null}
85
+ </div>
86
+ );
87
+ },
88
+ );
89
+ Result.displayName = 'Result';
90
+
91
+ export { Result };