@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,75 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Progress, ProgressCircle } from './progress';
3
+
4
+ const meta: Meta<typeof Progress> = {
5
+ title: '基础原语 · Foundation/Progress',
6
+ component: Progress,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ '进度条 — 展示一个任务的完成进度,访问友好的状态反馈。Radix Progress 实现 + antd Progress 的能力并集:除默认线形(`Progress`)外还提供环形 `ProgressCircle`,支持 `status`(normal / success / warning / exception)、`size`、`showInfo` 以及任意 `format` 提示。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
13
+ },
14
+ },
15
+ },
16
+ argTypes: {
17
+ value: { control: { type: 'range', min: 0, max: 100 } },
18
+ status: {
19
+ control: 'select',
20
+ options: ['normal', 'success', 'warning', 'exception'],
21
+ },
22
+ showInfo: { control: 'boolean' },
23
+ size: { control: 'inline-radio', options: ['sm', 'default', 'lg'] },
24
+ },
25
+ args: { value: 42, status: 'normal', showInfo: true, size: 'default' },
26
+ decorators: [
27
+ (Story) => (
28
+ <div className="w-80">
29
+ <Story />
30
+ </div>
31
+ ),
32
+ ],
33
+ };
34
+
35
+ export default meta;
36
+ type Story = StoryObj<typeof Progress>;
37
+
38
+ export const Playground: Story = {};
39
+
40
+ export const Statuses: Story = {
41
+ parameters: { controls: { disable: true } },
42
+ render: () => (
43
+ <div className="flex flex-col gap-3">
44
+ <Progress value={20} showInfo />
45
+ <Progress value={66} status="warning" showInfo />
46
+ <Progress value={100} status="success" showInfo />
47
+ <Progress value={45} status="exception" showInfo />
48
+ </div>
49
+ ),
50
+ };
51
+
52
+ export const Sizes: Story = {
53
+ parameters: { controls: { disable: true } },
54
+ render: () => (
55
+ <div className="flex flex-col gap-3">
56
+ <Progress value={42} size="sm" showInfo />
57
+ <Progress value={42} size="default" showInfo />
58
+ <Progress value={42} size="lg" showInfo />
59
+ </div>
60
+ ),
61
+ };
62
+
63
+ export const Circle: StoryObj<typeof ProgressCircle> = {
64
+ parameters: { controls: { disable: true } },
65
+ render: () => (
66
+ <div className="flex items-center gap-6">
67
+ <ProgressCircle value={20} />
68
+ <ProgressCircle value={66} status="warning" />
69
+ <ProgressCircle value={100} status="success" />
70
+ <ProgressCircle value={42} status="exception" />
71
+ <ProgressCircle value={75} size={120} />
72
+ <ProgressCircle value={75} size={40} showInfo={false} />
73
+ </div>
74
+ ),
75
+ };
@@ -0,0 +1,195 @@
1
+ import * as React from 'react';
2
+ import * as ProgressPrimitive from '@radix-ui/react-progress';
3
+
4
+ import { cn } from '@/utils/cn';
5
+
6
+ export type ProgressStatus = 'normal' | 'success' | 'warning' | 'exception';
7
+
8
+ const statusBarColor: Record<ProgressStatus, string> = {
9
+ normal: 'bg-primary',
10
+ success: 'bg-emerald-500',
11
+ warning: 'bg-amber-500',
12
+ exception: 'bg-destructive',
13
+ };
14
+
15
+ const statusTextColor: Record<ProgressStatus, string> = {
16
+ normal: 'text-foreground',
17
+ success: 'text-emerald-600',
18
+ warning: 'text-amber-600',
19
+ exception: 'text-destructive',
20
+ };
21
+
22
+ export interface ProgressProps
23
+ extends Omit<
24
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>,
25
+ 'value'
26
+ > {
27
+ /**
28
+ * 当前百分比(0~100)。
29
+ * @default 0
30
+ */
31
+ value?: number;
32
+ /**
33
+ * 状态色,自动优先级:`exception > warning > success > normal`。
34
+ * @default "normal"
35
+ */
36
+ status?: ProgressStatus;
37
+ /**
38
+ * 是否在右侧显示百分比文字(antd showInfo 并集)。
39
+ * @default false
40
+ */
41
+ showInfo?: boolean;
42
+ /**
43
+ * 进度条尺寸。
44
+ * @default "default"
45
+ */
46
+ size?: 'sm' | 'default' | 'lg';
47
+ }
48
+
49
+ const Progress = React.forwardRef<
50
+ React.ElementRef<typeof ProgressPrimitive.Root>,
51
+ ProgressProps
52
+ >(
53
+ (
54
+ {
55
+ className,
56
+ value = 0,
57
+ status = 'normal',
58
+ showInfo = false,
59
+ size = 'default',
60
+ ...props
61
+ },
62
+ ref,
63
+ ) => {
64
+ const h = size === 'sm' ? 'h-1.5' : size === 'lg' ? 'h-3' : 'h-2';
65
+ return (
66
+ <div className={cn('flex items-center gap-3', className)}>
67
+ <ProgressPrimitive.Root
68
+ ref={ref}
69
+ value={value}
70
+ className={cn(
71
+ 'relative w-full overflow-hidden rounded-full bg-secondary',
72
+ h,
73
+ )}
74
+ {...props}
75
+ >
76
+ <ProgressPrimitive.Indicator
77
+ className={cn(
78
+ 'size-full flex-1 transition-all',
79
+ statusBarColor[status],
80
+ )}
81
+ style={{ transform: `translateX(-${100 - Math.min(100, Math.max(0, value))}%)` }}
82
+ />
83
+ </ProgressPrimitive.Root>
84
+ {showInfo ? (
85
+ <span
86
+ className={cn(
87
+ 'shrink-0 text-xs font-medium tabular-nums',
88
+ statusTextColor[status],
89
+ )}
90
+ >
91
+ {Math.round(value)}%
92
+ </span>
93
+ ) : null}
94
+ </div>
95
+ );
96
+ },
97
+ );
98
+ Progress.displayName = 'Progress';
99
+
100
+ // ─── ProgressCircle(antd 并集)────────────────────────────────────────────
101
+
102
+ export interface ProgressCircleProps
103
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
104
+ /** 当前百分比(0~100)。 @default 0 */
105
+ value?: number;
106
+ /** 状态色。 @default "normal" */
107
+ status?: ProgressStatus;
108
+ /** 圆环直径(px)。 @default 80 */
109
+ size?: number;
110
+ /** 圆环描边宽度(px)。 @default 6 */
111
+ strokeWidth?: number;
112
+ /**
113
+ * 是否在圆心显示百分比文字。
114
+ * @default true
115
+ */
116
+ showInfo?: boolean;
117
+ }
118
+
119
+ const ProgressCircle = React.forwardRef<HTMLDivElement, ProgressCircleProps>(
120
+ (
121
+ {
122
+ className,
123
+ value = 0,
124
+ status = 'normal',
125
+ size = 80,
126
+ strokeWidth = 6,
127
+ showInfo = true,
128
+ ...props
129
+ },
130
+ ref,
131
+ ) => {
132
+ const radius = (size - strokeWidth) / 2;
133
+ const circumference = 2 * Math.PI * radius;
134
+ const offset = circumference * (1 - Math.min(100, Math.max(0, value)) / 100);
135
+ return (
136
+ <div
137
+ ref={ref}
138
+ className={cn('relative inline-flex items-center justify-center', className)}
139
+ style={{ width: size, height: size }}
140
+ role="progressbar"
141
+ aria-valuenow={value}
142
+ aria-valuemin={0}
143
+ aria-valuemax={100}
144
+ {...props}
145
+ >
146
+ <svg
147
+ width={size}
148
+ height={size}
149
+ className="-rotate-90"
150
+ aria-hidden="true"
151
+ >
152
+ <circle
153
+ cx={size / 2}
154
+ cy={size / 2}
155
+ r={radius}
156
+ strokeWidth={strokeWidth}
157
+ className="fill-none stroke-secondary"
158
+ />
159
+ <circle
160
+ cx={size / 2}
161
+ cy={size / 2}
162
+ r={radius}
163
+ strokeWidth={strokeWidth}
164
+ strokeLinecap="round"
165
+ strokeDasharray={circumference}
166
+ strokeDashoffset={offset}
167
+ className={cn(
168
+ 'fill-none transition-all',
169
+ status === 'success'
170
+ ? 'stroke-emerald-500'
171
+ : status === 'warning'
172
+ ? 'stroke-amber-500'
173
+ : status === 'exception'
174
+ ? 'stroke-destructive'
175
+ : 'stroke-primary',
176
+ )}
177
+ />
178
+ </svg>
179
+ {showInfo ? (
180
+ <span
181
+ className={cn(
182
+ 'absolute text-sm font-semibold tabular-nums',
183
+ statusTextColor[status],
184
+ )}
185
+ >
186
+ {Math.round(value)}%
187
+ </span>
188
+ ) : null}
189
+ </div>
190
+ );
191
+ },
192
+ );
193
+ ProgressCircle.displayName = 'ProgressCircle';
194
+
195
+ export { Progress, ProgressCircle };
@@ -0,0 +1,103 @@
1
+ ---
2
+ id: radio-group
3
+ name: RadioGroup
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # RadioGroup
11
+
12
+ 单选组 — Radix RadioGroup,**两种渲染**:
13
+
14
+ | variant | 形态 | 适用 |
15
+ | --- | --- | --- |
16
+ | `default` | 圆点 + 文字(shadcn 风格) | 表单 / 设置面板 |
17
+ | `button` | 按钮组(antd Radio.Button 并集) | 视图切换 / 时间区间切换 / 紧凑工具栏 |
18
+
19
+ ## When to use
20
+
21
+ - 互斥单选(必选其一)
22
+ - 视图切换(列表 / 卡片 / 网格)
23
+ - 时间区间切换(今日 / 本周 / 本月)
24
+
25
+ ## When NOT to use
26
+
27
+ - 多选 → `Checkbox` / `CheckboxGroup`
28
+ - 选项数 > 5 → `Select`
29
+ - 可搜索 → `Combobox`(v0.x)
30
+
31
+ ## Props
32
+
33
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
34
+
35
+ <!-- auto:props:begin -->
36
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
37
+ | --- | --- | --- | --- | --- |
38
+ | `variant` | `'default' \| 'button'` | `"default"` | – | 渲染样式 — `default` 经典圆点;`button` 渲染为按钮组(antd `Radio.Button` 并集)。 |
39
+ <!-- auto:props:end -->
40
+
41
+ ## 依赖
42
+
43
+ > 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
44
+
45
+ <!-- auto:deps:begin -->
46
+ ### 同库依赖
47
+
48
+ > `teamix-evo ui add radio-group` 时,以下 entry 会被自动连带安装(无需手动 add)。
49
+
50
+ | Entry | 类型 | 描述 |
51
+ | --- | --- | --- |
52
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
53
+
54
+ ### npm 依赖
55
+
56
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
57
+
58
+ ```bash
59
+ pnpm add @radix-ui/react-radio-group@^1.2.0 lucide-react@^0.460.0
60
+ ```
61
+ <!-- auto:deps:end -->
62
+
63
+ > 子组件 `RadioGroupItem`(默认形态)/ `RadioGroupButton`(按钮形态)各自的 props 见 [`radio-group.tsx`](./radio-group.tsx)。
64
+
65
+ ## AI 生成纪律
66
+
67
+ - **形态由父级 variant 决定**:`default` 用 `RadioGroupItem`,`button` 用 `RadioGroupButton`,**不要混用**(默认形态用 Button 子项视觉怪异)
68
+ - **必须有 default 选中项**:用户进入页面看到全部未选会困惑;用 `defaultValue` 给一个合理初值
69
+ - **每项必关联 Label**:`<RadioGroupItem id="r1" value="a" /> + <Label htmlFor="r1">A</Label>` 或包裹模式
70
+ - **value 唯一稳定**:不要用 index,变化时 controlled diff 失败
71
+
72
+ ## Examples
73
+
74
+ ```tsx
75
+ import {
76
+ RadioGroup, RadioGroupItem, RadioGroupButton,
77
+ } from '@/components/ui/radio-group';
78
+ import { Label } from '@/components/ui/label';
79
+
80
+ // 默认形态
81
+ <RadioGroup defaultValue="comfortable" className="grid gap-2">
82
+ <div className="flex items-center gap-2">
83
+ <RadioGroupItem value="default" id="r1" />
84
+ <Label htmlFor="r1">Default</Label>
85
+ </div>
86
+ <div className="flex items-center gap-2">
87
+ <RadioGroupItem value="comfortable" id="r2" />
88
+ <Label htmlFor="r2">Comfortable</Label>
89
+ </div>
90
+ <div className="flex items-center gap-2">
91
+ <RadioGroupItem value="compact" id="r3" />
92
+ <Label htmlFor="r3">Compact</Label>
93
+ </div>
94
+ </RadioGroup>
95
+
96
+ // 按钮形态(antd Radio.Button)
97
+ <RadioGroup variant="button" defaultValue="month">
98
+ <RadioGroupButton value="day">日</RadioGroupButton>
99
+ <RadioGroupButton value="week">周</RadioGroupButton>
100
+ <RadioGroupButton value="month">月</RadioGroupButton>
101
+ <RadioGroupButton value="year">年</RadioGroupButton>
102
+ </RadioGroup>
103
+ ```
@@ -0,0 +1,77 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { RadioGroup, RadioGroupItem, RadioGroupButton } from './radio-group';
3
+
4
+ const meta: Meta<typeof RadioGroup> = {
5
+ title: '表单与输入 · Form/RadioGroup',
6
+ component: RadioGroup,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ '单选组 — 在一组互斥选项中选中唯一一个。Radix RadioGroup 实现 + antd Radio 的并集能力:同时提供默认的点选型 `RadioGroupItem` 与按钮型 `RadioGroupButton`(对齐 antd `Radio.Button`),通过 `variant` prop 切换。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
13
+ },
14
+ },
15
+ },
16
+ argTypes: {
17
+ variant: { control: 'inline-radio', options: ['default', 'button'] },
18
+ disabled: { control: 'boolean' },
19
+ },
20
+ args: { variant: 'default', defaultValue: 'comfortable' },
21
+ };
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof RadioGroup>;
25
+
26
+ export const Playground: Story = {
27
+ render: (args) => (
28
+ <RadioGroup {...args}>
29
+ <div className="flex items-center gap-2 text-sm">
30
+ <RadioGroupItem value="default" id="rg-a" />
31
+ <label htmlFor="rg-a">Default</label>
32
+ </div>
33
+ <div className="flex items-center gap-2 text-sm">
34
+ <RadioGroupItem value="comfortable" id="rg-b" />
35
+ <label htmlFor="rg-b">Comfortable</label>
36
+ </div>
37
+ <div className="flex items-center gap-2 text-sm">
38
+ <RadioGroupItem value="compact" id="rg-c" />
39
+ <label htmlFor="rg-c">Compact</label>
40
+ </div>
41
+ </RadioGroup>
42
+ ),
43
+ };
44
+
45
+ export const ButtonStyle: Story = {
46
+ parameters: { controls: { disable: true } },
47
+ render: () => (
48
+ <RadioGroup variant="button" defaultValue="month">
49
+ <RadioGroupButton value="day">日</RadioGroupButton>
50
+ <RadioGroupButton value="week">周</RadioGroupButton>
51
+ <RadioGroupButton value="month">月</RadioGroupButton>
52
+ <RadioGroupButton value="year">年</RadioGroupButton>
53
+ </RadioGroup>
54
+ ),
55
+ };
56
+
57
+ export const Disabled: Story = {
58
+ parameters: { controls: { disable: true } },
59
+ render: () => (
60
+ <div className="flex flex-col gap-6">
61
+ <RadioGroup defaultValue="a" disabled>
62
+ <div className="flex items-center gap-2 text-sm">
63
+ <RadioGroupItem value="a" id="rd-1" />
64
+ <label htmlFor="rd-1">禁用 - 选项 A</label>
65
+ </div>
66
+ <div className="flex items-center gap-2 text-sm">
67
+ <RadioGroupItem value="b" id="rd-2" />
68
+ <label htmlFor="rd-2">禁用 - 选项 B</label>
69
+ </div>
70
+ </RadioGroup>
71
+ <RadioGroup variant="button" defaultValue="b" disabled>
72
+ <RadioGroupButton value="a">A</RadioGroupButton>
73
+ <RadioGroupButton value="b">B</RadioGroupButton>
74
+ </RadioGroup>
75
+ </div>
76
+ ),
77
+ };
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
3
+ import { Circle } from 'lucide-react';
4
+
5
+ import { cn } from '@/utils/cn';
6
+
7
+ export interface RadioGroupProps
8
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> {
9
+ /**
10
+ * 渲染样式 — `default` 经典圆点;`button` 渲染为按钮组(antd `Radio.Button` 并集)。
11
+ * @default "default"
12
+ */
13
+ variant?: 'default' | 'button';
14
+ }
15
+
16
+ const RadioGroup = React.forwardRef<
17
+ React.ElementRef<typeof RadioGroupPrimitive.Root>,
18
+ RadioGroupProps
19
+ >(({ className, variant = 'default', ...props }, ref) => (
20
+ <RadioGroupPrimitive.Root
21
+ ref={ref}
22
+ data-variant={variant}
23
+ className={cn(
24
+ variant === 'button'
25
+ ? 'inline-flex divide-x divide-input overflow-hidden rounded-md border border-input shadow-sm'
26
+ : 'grid gap-2',
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ ));
32
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
33
+
34
+ export interface RadioGroupItemProps
35
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {}
36
+
37
+ const RadioGroupItem = React.forwardRef<
38
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
39
+ RadioGroupItemProps
40
+ >(({ className, ...props }, ref) => (
41
+ <RadioGroupPrimitive.Item
42
+ ref={ref}
43
+ className={cn(
44
+ 'aspect-square size-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
45
+ className,
46
+ )}
47
+ {...props}
48
+ >
49
+ <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
50
+ <Circle className="size-2.5 fill-primary" />
51
+ </RadioGroupPrimitive.Indicator>
52
+ </RadioGroupPrimitive.Item>
53
+ ));
54
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
55
+
56
+ // ─── RadioGroupButton(Button-style item, antd `Radio.Button` 并集)───────
57
+
58
+ export interface RadioGroupButtonProps
59
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {}
60
+
61
+ const RadioGroupButton = React.forwardRef<
62
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
63
+ RadioGroupButtonProps
64
+ >(({ className, children, ...props }, ref) => (
65
+ <RadioGroupPrimitive.Item
66
+ ref={ref}
67
+ className={cn(
68
+ 'inline-flex h-9 cursor-pointer items-center justify-center px-4 text-sm font-medium transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:hover:bg-primary/90',
69
+ className,
70
+ )}
71
+ {...props}
72
+ >
73
+ {children}
74
+ </RadioGroupPrimitive.Item>
75
+ ));
76
+ RadioGroupButton.displayName = 'RadioGroupButton';
77
+
78
+ export { RadioGroup, RadioGroupItem, RadioGroupButton };
@@ -0,0 +1,87 @@
1
+ ---
2
+ id: rate
3
+ name: Rate
4
+ type: component
5
+ category: form
6
+ since: 0.1.0
7
+ package: "@teamix-evo/ui"
8
+ ---
9
+
10
+ # Rate
11
+
12
+ 星级评分 — antd 独有补足。**等价 antd `Rate`**。支持半星(`allowHalf`)、再次点击清零(`allowClear`)、自定义图标(`character`)、只读展示(`disabled`)。
13
+
14
+ ## When to use
15
+
16
+ - 评论 / 评分场景(电商商品、酒店、餐厅)
17
+ - 用户偏好评分(满意度调查)
18
+ - 只读展示评分均值(`disabled` + `value`)
19
+
20
+ ## When NOT to use
21
+
22
+ - 二元选择(喜欢 / 不喜欢)→ `Toggle` / `Button` 配 icon
23
+ - 多档定量评分(0~100)→ `Slider`
24
+
25
+ <!-- auto:props:begin -->
26
+ | 名称 | 类型 | 默认值 | 必填 | 说明 |
27
+ | --- | --- | --- | --- | --- |
28
+ | `count` | `number` | `5` | – | 总星数(antd `count` 并集)。 |
29
+ | `value` | `number` | – | – | 受控值。 |
30
+ | `defaultValue` | `number` | – | – | uncontrolled 初值。 |
31
+ | `allowHalf` | `boolean` | `false` | – | 允许半星(antd `allowHalf` 并集) — 启用后单星可点击左半 / 右半。 |
32
+ | `allowClear` | `boolean` | `true` | – | 允许再次点击同值清零(antd `allowClear` 并集)。 |
33
+ | `disabled` | `boolean` | – | – | 禁用(只展示)。 |
34
+ | `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 尺寸(影响星图大小)。 |
35
+ | `character` | `React.ReactNode` | – | – | 自定义图标(antd `character` 并集) — 默认 `lucide-react` 的 `Star`。 |
36
+ | `onChange` | `(value: number) => void` | – | – | value 变化回调。 |
37
+ <!-- auto:props:end -->
38
+
39
+ <!-- auto:deps:begin -->
40
+ ### 同库依赖
41
+
42
+ > `teamix-evo ui add rate` 时,以下 entry 会被自动连带安装(无需手动 add)。
43
+
44
+ | Entry | 类型 | 描述 |
45
+ | --- | --- | --- |
46
+ | `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
47
+
48
+ ### npm 依赖
49
+
50
+ > 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
51
+
52
+ ```bash
53
+ pnpm add lucide-react@^0.460.0
54
+ ```
55
+ <!-- auto:deps:end -->
56
+
57
+ ## AI 生成纪律
58
+
59
+ - **`disabled` 即只读** — 配 `value` 显示评分均值;**不要**模拟"半禁用"或"局部可改"
60
+ - **`allowHalf` 仅前端显示用** — 业务层数据建议存为 number(允许 0.5 步长)
61
+ - **`allowClear` 默认 true** — 与 antd 一致;关闭后用户无法清空评分,只能改值
62
+ - **`character` 自定义图标**:必须是 `React.isValidElement` 节点,组件会在选中态注入 `fill="currentColor"`;**不要**传字符串
63
+ - **大评分场景**(`count > 7`)视觉拥挤,建议改用 `Slider` + 数字展示
64
+
65
+ ## Examples
66
+
67
+ ```tsx
68
+ import { Rate } from '@/components/ui/rate';
69
+ import { Heart } from 'lucide-react';
70
+ import * as React from 'react';
71
+
72
+ // 基础
73
+ <Rate defaultValue={3} />
74
+
75
+ // 半星
76
+ <Rate allowHalf defaultValue={3.5} />
77
+
78
+ // 受控
79
+ const [v, setV] = React.useState(0);
80
+ <Rate value={v} onChange={setV} allowHalf />
81
+
82
+ // 自定义图标
83
+ <Rate character={<Heart className="size-5" />} count={5} defaultValue={3} />
84
+
85
+ // 只读
86
+ <Rate disabled value={4.5} allowHalf />
87
+ ```
@@ -0,0 +1,81 @@
1
+ import * as React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Heart } from 'lucide-react';
4
+ import { Rate } from './rate';
5
+
6
+ const meta: Meta<typeof Rate> = {
7
+ title: '表单与输入 · Form/Rate',
8
+ component: Rate,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ '星级评分 — 评论 / 商品评分 / 偏好满意度。支持半星 + 再次点击清零 + 自定义图标 + 只读展示。等价 antd `Rate`。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
15
+ },
16
+ },
17
+ },
18
+ argTypes: {
19
+ count: { control: { type: 'number', min: 3, max: 10 } },
20
+ allowHalf: { control: 'boolean' },
21
+ allowClear: { control: 'boolean' },
22
+ disabled: { control: 'boolean' },
23
+ size: { control: 'inline-radio', options: ['sm', 'default', 'lg'] },
24
+ },
25
+ args: { count: 5, allowHalf: false, allowClear: true, disabled: false, size: 'default' },
26
+ };
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof Rate>;
30
+
31
+ export const Playground: Story = {
32
+ render: (args) => <Rate {...args} defaultValue={3} />,
33
+ };
34
+
35
+ export const HalfStar: Story = {
36
+ parameters: { controls: { disable: true } },
37
+ render: () => <Rate allowHalf defaultValue={3.5} />,
38
+ };
39
+
40
+ export const Controlled: Story = {
41
+ parameters: { controls: { disable: true } },
42
+ render: () => {
43
+ const [v, setV] = React.useState<number>(0);
44
+ return (
45
+ <div className="flex items-center gap-3">
46
+ <Rate value={v} onChange={setV} allowHalf />
47
+ <span className="text-sm text-muted-foreground tabular-nums">
48
+ {v.toFixed(1)} / 5
49
+ </span>
50
+ </div>
51
+ );
52
+ },
53
+ };
54
+
55
+ export const CustomIcon: Story = {
56
+ parameters: { controls: { disable: true } },
57
+ render: () => (
58
+ <Rate character={<Heart className="size-5" />} count={5} defaultValue={3} />
59
+ ),
60
+ };
61
+
62
+ export const ReadOnly: Story = {
63
+ parameters: { controls: { disable: true } },
64
+ render: () => (
65
+ <div className="flex items-center gap-2">
66
+ <Rate disabled value={4.5} allowHalf size="sm" />
67
+ <span className="text-sm text-muted-foreground">4.5 / 5 (1,287 评价)</span>
68
+ </div>
69
+ ),
70
+ };
71
+
72
+ export const Sizes: Story = {
73
+ parameters: { controls: { disable: true } },
74
+ render: () => (
75
+ <div className="flex items-center gap-6">
76
+ <Rate size="sm" defaultValue={3} />
77
+ <Rate size="default" defaultValue={3} />
78
+ <Rate size="lg" defaultValue={3} />
79
+ </div>
80
+ ),
81
+ };