@teamix-evo/ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,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
|
+
};
|