@teamix-evo/ui 0.1.1 → 0.3.0
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/README.md +184 -184
- package/manifest.json +680 -492
- package/package.json +20 -10
- package/src/components/accordion/accordion.meta.md +5 -4
- package/src/components/accordion/accordion.stories.tsx +14 -9
- package/src/components/accordion/accordion.tsx +104 -8
- package/src/components/affix/affix.meta.md +20 -2
- package/src/components/affix/affix.stories.tsx +102 -25
- package/src/components/affix/affix.tsx +79 -9
- package/src/components/alert/alert.meta.md +44 -13
- package/src/components/alert/alert.stories.tsx +66 -21
- package/src/components/alert/alert.tsx +81 -34
- package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
- package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
- package/src/components/alert-dialog/alert-dialog.tsx +60 -13
- package/src/components/anchor/anchor.meta.md +8 -3
- package/src/components/anchor/anchor.stories.tsx +3 -3
- package/src/components/anchor/anchor.tsx +2 -2
- package/src/components/app/app.meta.md +9 -4
- package/src/components/app/app.stories.tsx +9 -7
- package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
- package/src/components/auto-complete/auto-complete.meta.md +14 -6
- package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
- package/src/components/auto-complete/auto-complete.tsx +119 -71
- package/src/components/avatar/avatar.meta.md +6 -7
- package/src/components/avatar/avatar.stories.tsx +21 -3
- package/src/components/avatar/avatar.tsx +24 -23
- package/src/components/badge/badge.meta.md +10 -9
- package/src/components/badge/badge.stories.tsx +2 -2
- package/src/components/badge/badge.tsx +9 -15
- package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
- package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
- package/src/components/breadcrumb/breadcrumb.tsx +22 -8
- package/src/components/button/button.meta.md +258 -21
- package/src/components/button/button.stories.tsx +549 -41
- package/src/components/button/button.tsx +335 -33
- package/src/components/button/demo/as-child.tsx +24 -0
- package/src/components/button/demo/basic.tsx +8 -0
- package/src/components/button/demo/block.tsx +16 -0
- package/src/components/button/demo/loading.tsx +19 -0
- package/src/components/button/demo/shapes.tsx +18 -0
- package/src/components/button/demo/sizes.tsx +19 -0
- package/src/components/button/demo/variants.tsx +19 -0
- package/src/components/button/demo/with-icon.tsx +20 -0
- package/src/components/calendar/calendar.meta.md +13 -3
- package/src/components/calendar/calendar.stories.tsx +6 -6
- package/src/components/calendar/calendar.tsx +73 -8
- package/src/components/card/card.meta.md +27 -5
- package/src/components/card/card.stories.tsx +42 -3
- package/src/components/card/card.tsx +146 -63
- package/src/components/carousel/carousel.meta.md +4 -3
- package/src/components/carousel/carousel.stories.tsx +11 -6
- package/src/components/cascader/cascader.meta.md +47 -17
- package/src/components/cascader/cascader.stories.tsx +22 -10
- package/src/components/cascader/cascader.tsx +428 -85
- package/src/components/checkbox/checkbox.meta.md +75 -7
- package/src/components/checkbox/checkbox.stories.tsx +161 -3
- package/src/components/checkbox/checkbox.tsx +77 -9
- package/src/components/collapsible/collapsible.meta.md +14 -6
- package/src/components/collapsible/collapsible.stories.tsx +10 -2
- package/src/components/collapsible/collapsible.tsx +93 -6
- package/src/components/color-picker/color-picker.meta.md +12 -7
- package/src/components/color-picker/color-picker.stories.tsx +86 -7
- package/src/components/color-picker/color-picker.tsx +20 -9
- package/src/components/command/command.meta.md +29 -13
- package/src/components/command/command.stories.tsx +4 -4
- package/src/components/command/command.tsx +19 -8
- package/src/components/context-menu/context-menu.meta.md +11 -8
- package/src/components/context-menu/context-menu.stories.tsx +11 -3
- package/src/components/context-menu/context-menu.tsx +21 -8
- package/src/components/data-table/data-table.meta.md +6 -5
- package/src/components/data-table/data-table.stories.tsx +13 -6
- package/src/components/data-table/data-table.tsx +2 -2
- package/src/components/date-picker/date-picker.meta.md +88 -19
- package/src/components/date-picker/date-picker.stories.tsx +55 -5
- package/src/components/date-picker/date-picker.tsx +1489 -91
- package/src/components/descriptions/descriptions.meta.md +10 -5
- package/src/components/descriptions/descriptions.stories.tsx +3 -3
- package/src/components/descriptions/descriptions.tsx +22 -14
- package/src/components/dialog/dialog.meta.md +76 -13
- package/src/components/dialog/dialog.stories.tsx +182 -20
- package/src/components/dialog/dialog.tsx +67 -15
- package/src/components/dialog/imperative.tsx +252 -0
- package/src/components/drawer/drawer.meta.md +33 -34
- package/src/components/drawer/drawer.stories.tsx +29 -12
- package/src/components/drawer/drawer.tsx +22 -113
- package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
- package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
- package/src/components/ellipsis/ellipsis.meta.md +87 -0
- package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
- package/src/components/ellipsis/ellipsis.tsx +153 -0
- package/src/components/empty/empty.meta.md +9 -4
- package/src/components/empty/empty.stories.tsx +4 -4
- package/src/components/empty/empty.tsx +10 -3
- package/src/components/field/field.meta.md +47 -9
- package/src/components/field/field.stories.tsx +385 -5
- package/src/components/field/field.tsx +263 -35
- package/src/components/filter-bar/filter-bar.meta.md +92 -0
- package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
- package/src/components/filter-bar/filter-bar.tsx +568 -0
- package/src/components/flex/flex.meta.md +54 -6
- package/src/components/flex/flex.stories.tsx +107 -20
- package/src/components/flex/flex.tsx +27 -4
- package/src/components/float-button/float-button.meta.md +8 -3
- package/src/components/float-button/float-button.stories.tsx +9 -7
- package/src/components/float-button/float-button.tsx +1 -1
- package/src/components/form/form.meta.md +39 -17
- package/src/components/form/form.stories.tsx +350 -3
- package/src/components/form/form.tsx +101 -35
- package/src/components/grid/grid.meta.md +7 -2
- package/src/components/grid/grid.stories.tsx +6 -4
- package/src/components/hover-card/hover-card.meta.md +20 -9
- package/src/components/hover-card/hover-card.stories.tsx +34 -5
- package/src/components/hover-card/hover-card.tsx +51 -13
- package/src/components/icon/DEVELOPMENT.md +809 -0
- package/src/components/icon/icon.meta.md +170 -0
- package/src/components/icon/icon.stories.tsx +344 -0
- package/src/components/icon/icon.tsx +248 -0
- package/src/components/image/image.meta.md +9 -4
- package/src/components/image/image.stories.tsx +3 -3
- package/src/components/image/image.tsx +6 -4
- package/src/components/input/demo/basic.tsx +12 -0
- package/src/components/input/demo/clearable.tsx +21 -0
- package/src/components/input/demo/show-count.tsx +18 -0
- package/src/components/input/demo/sizes.tsx +15 -0
- package/src/components/input/input.meta.md +39 -33
- package/src/components/input/input.stories.tsx +62 -35
- package/src/components/input/input.tsx +97 -98
- package/src/components/input-group/input-group.meta.md +54 -22
- package/src/components/input-group/input-group.stories.tsx +49 -16
- package/src/components/input-group/input-group.tsx +44 -8
- package/src/components/input-number/input-number.meta.md +64 -7
- package/src/components/input-number/input-number.stories.tsx +46 -8
- package/src/components/input-number/input-number.tsx +99 -26
- package/src/components/input-otp/input-otp.meta.md +4 -3
- package/src/components/input-otp/input-otp.stories.tsx +3 -3
- package/src/components/input-otp/input-otp.tsx +1 -1
- package/src/components/item/item.meta.md +8 -3
- package/src/components/item/item.stories.tsx +8 -5
- package/src/components/item/item.tsx +7 -6
- package/src/components/kbd/kbd.meta.md +13 -4
- package/src/components/kbd/kbd.stories.tsx +4 -4
- package/src/components/kbd/kbd.tsx +10 -5
- package/src/components/label/label.meta.md +18 -10
- package/src/components/label/label.stories.tsx +64 -6
- package/src/components/label/label.tsx +91 -19
- package/src/components/masonry/masonry.meta.md +8 -3
- package/src/components/masonry/masonry.stories.tsx +7 -5
- package/src/components/masonry/masonry.tsx +1 -0
- package/src/components/mentions/mentions.meta.md +36 -6
- package/src/components/mentions/mentions.stories.tsx +120 -6
- package/src/components/mentions/mentions.tsx +11 -5
- package/src/components/menubar/menubar.meta.md +30 -12
- package/src/components/menubar/menubar.stories.tsx +62 -2
- package/src/components/menubar/menubar.tsx +9 -9
- package/src/components/native-select/native-select.meta.md +8 -3
- package/src/components/native-select/native-select.stories.tsx +8 -5
- package/src/components/native-select/native-select.tsx +1 -1
- package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
- package/src/components/navigation-menu/navigation-menu.tsx +8 -4
- package/src/components/notification/notification.meta.md +52 -10
- package/src/components/notification/notification.stories.tsx +11 -9
- package/src/components/notification/notification.tsx +36 -21
- package/src/components/page-header/DEVELOPMENT.md +842 -0
- package/src/components/page-header/page-header.meta.md +208 -0
- package/src/components/page-header/page-header.stories.tsx +421 -0
- package/src/components/page-header/page-header.tsx +281 -0
- package/src/components/pagination/pagination.meta.md +140 -37
- package/src/components/pagination/pagination.stories.tsx +232 -10
- package/src/components/pagination/pagination.tsx +355 -63
- package/src/components/popconfirm/popconfirm.meta.md +9 -4
- package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
- package/src/components/popconfirm/popconfirm.tsx +2 -2
- package/src/components/popover/popover.meta.md +62 -5
- package/src/components/popover/popover.stories.tsx +83 -7
- package/src/components/popover/popover.tsx +77 -28
- package/src/components/progress/progress.meta.md +38 -6
- package/src/components/progress/progress.stories.tsx +3 -3
- package/src/components/progress/progress.tsx +24 -16
- package/src/components/radio-group/radio-group.meta.md +79 -7
- package/src/components/radio-group/radio-group.stories.tsx +39 -3
- package/src/components/radio-group/radio-group.tsx +149 -18
- package/src/components/rate/rate.meta.md +35 -4
- package/src/components/rate/rate.stories.tsx +13 -5
- package/src/components/rate/rate.tsx +37 -10
- package/src/components/resizable/resizable.meta.md +7 -4
- package/src/components/resizable/resizable.stories.tsx +6 -6
- package/src/components/resizable/resizable.tsx +1 -1
- package/src/components/result/result.meta.md +7 -2
- package/src/components/result/result.stories.tsx +4 -8
- package/src/components/result/result.tsx +24 -15
- package/src/components/scroll-area/scroll-area.meta.md +4 -3
- package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
- package/src/components/scroll-area/scroll-area.tsx +3 -3
- package/src/components/segmented/segmented.meta.md +7 -4
- package/src/components/segmented/segmented.stories.tsx +37 -8
- package/src/components/segmented/segmented.tsx +15 -7
- package/src/components/select/select.meta.md +197 -52
- package/src/components/select/select.stories.tsx +238 -63
- package/src/components/select/select.tsx +718 -171
- package/src/components/separator/separator.meta.md +4 -3
- package/src/components/separator/separator.stories.tsx +3 -3
- package/src/components/separator/separator.tsx +3 -7
- package/src/components/sheet/sheet.meta.md +32 -16
- package/src/components/sheet/sheet.stories.tsx +116 -10
- package/src/components/sheet/sheet.tsx +116 -29
- package/src/components/sidebar/sidebar.meta.md +37 -18
- package/src/components/sidebar/sidebar.stories.tsx +701 -29
- package/src/components/sidebar/sidebar.tsx +615 -142
- package/src/components/skeleton/skeleton.meta.md +4 -5
- package/src/components/skeleton/skeleton.stories.tsx +4 -4
- package/src/components/skeleton/skeleton.tsx +7 -7
- package/src/components/slider/slider.meta.md +57 -5
- package/src/components/slider/slider.stories.tsx +58 -6
- package/src/components/slider/slider.tsx +154 -13
- package/src/components/sonner/sonner.meta.md +58 -7
- package/src/components/sonner/sonner.stories.tsx +78 -5
- package/src/components/sonner/sonner.tsx +137 -8
- package/src/components/spinner/spinner.meta.md +62 -13
- package/src/components/spinner/spinner.stories.tsx +66 -14
- package/src/components/spinner/spinner.tsx +111 -9
- package/src/components/statistic/statistic.meta.md +7 -2
- package/src/components/statistic/statistic.stories.tsx +3 -7
- package/src/components/statistic/statistic.tsx +5 -6
- package/src/components/steps/steps.meta.md +18 -4
- package/src/components/steps/steps.stories.tsx +43 -3
- package/src/components/steps/steps.tsx +15 -12
- package/src/components/switch/switch.meta.md +51 -5
- package/src/components/switch/switch.stories.tsx +6 -6
- package/src/components/switch/switch.tsx +109 -41
- package/src/components/table/table.meta.md +17 -6
- package/src/components/table/table.stories.tsx +10 -5
- package/src/components/table/table.tsx +4 -4
- package/src/components/tabs/tabs.meta.md +38 -25
- package/src/components/tabs/tabs.stories.tsx +111 -25
- package/src/components/tabs/tabs.tsx +125 -54
- package/src/components/tag/tag.meta.md +105 -40
- package/src/components/tag/tag.stories.tsx +189 -16
- package/src/components/tag/tag.tsx +222 -21
- package/src/components/textarea/textarea.meta.md +35 -19
- package/src/components/textarea/textarea.stories.tsx +32 -6
- package/src/components/textarea/textarea.tsx +33 -9
- package/src/components/time-picker/time-picker.meta.md +124 -32
- package/src/components/time-picker/time-picker.stories.tsx +85 -15
- package/src/components/time-picker/time-picker.tsx +913 -61
- package/src/components/timeline/timeline.meta.md +14 -6
- package/src/components/timeline/timeline.stories.tsx +37 -7
- package/src/components/timeline/timeline.tsx +35 -14
- package/src/components/toggle/toggle.meta.md +5 -4
- package/src/components/toggle/toggle.stories.tsx +4 -4
- package/src/components/toggle/toggle.tsx +4 -3
- package/src/components/toggle-group/toggle-group.meta.md +5 -4
- package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
- package/src/components/toggle-group/toggle-group.tsx +2 -2
- package/src/components/tooltip/tooltip.meta.md +55 -5
- package/src/components/tooltip/tooltip.stories.tsx +42 -5
- package/src/components/tooltip/tooltip.tsx +81 -21
- package/src/components/tour/tour.meta.md +9 -4
- package/src/components/tour/tour.stories.tsx +3 -3
- package/src/components/tour/tour.tsx +4 -4
- package/src/components/transfer/transfer.meta.md +11 -6
- package/src/components/transfer/transfer.stories.tsx +4 -8
- package/src/components/transfer/transfer.tsx +28 -21
- package/src/components/tree/tree.meta.md +63 -5
- package/src/components/tree/tree.stories.tsx +31 -12
- package/src/components/tree/tree.tsx +9 -8
- package/src/components/tree-select/tree-select.meta.md +59 -8
- package/src/components/tree-select/tree-select.stories.tsx +3 -3
- package/src/components/tree-select/tree-select.tsx +42 -7
- package/src/components/typography/typography.meta.md +61 -14
- package/src/components/typography/typography.stories.tsx +12 -11
- package/src/components/typography/typography.tsx +43 -28
- package/src/components/upload/upload.meta.md +49 -4
- package/src/components/upload/upload.stories.tsx +72 -12
- package/src/components/upload/upload.tsx +170 -37
- package/src/components/watermark/watermark.meta.md +7 -2
- package/src/components/watermark/watermark.stories.tsx +101 -9
- package/src/components/watermark/watermark.tsx +1 -0
- package/src/hooks/use-breakpoint.ts +117 -0
- package/src/hooks/use-debounce-callback.ts +52 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/stories/theme-tokens.stories.tsx +747 -0
- package/src/utils/trigger-input.ts +53 -0
- package/src/components/button-group/button-group.meta.md +0 -92
- package/src/components/button-group/button-group.stories.tsx +0 -90
- package/src/components/button-group/button-group.tsx +0 -75
- package/src/components/combobox/combobox.meta.md +0 -93
- package/src/components/combobox/combobox.stories.tsx +0 -55
- package/src/components/combobox/combobox.tsx +0 -130
- package/src/components/space/space.meta.md +0 -94
- package/src/components/space/space.stories.tsx +0 -94
- package/src/components/space/space.tsx +0 -106
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: skeleton
|
|
3
3
|
name: Skeleton
|
|
4
|
+
displayName: 骨架屏
|
|
4
5
|
type: component
|
|
5
|
-
category:
|
|
6
|
+
category: feedback
|
|
6
7
|
since: 0.1.0
|
|
7
|
-
package:
|
|
8
|
+
package: '@teamix-evo/ui'
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
# Skeleton
|
|
11
|
+
# Skeleton 骨架屏
|
|
11
12
|
|
|
12
13
|
骨架屏占位 — shadcn 单元素 pulse + antd 子组件家族(`SkeletonAvatar / SkeletonImage / SkeletonButton / SkeletonInput / SkeletonParagraph`)。
|
|
13
14
|
|
|
@@ -56,8 +57,6 @@ pnpm add class-variance-authority@^0.7.0
|
|
|
56
57
|
```
|
|
57
58
|
<!-- auto:deps:end -->
|
|
58
59
|
|
|
59
|
-
> 子组件 `SkeletonAvatar / SkeletonImage / SkeletonButton / SkeletonInput / SkeletonParagraph` 各自的 Props 可在 [`skeleton.tsx`](./skeleton.tsx) 查看。
|
|
60
|
-
|
|
61
60
|
## AI 生成纪律
|
|
62
61
|
|
|
63
62
|
- **形状要与最终内容匹配**:文本用 `shape="line"`,头像用 `<SkeletonAvatar />`,图片用 `<SkeletonImage />`,**不要**全用矩形糊弄
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import {
|
|
3
3
|
Skeleton,
|
|
4
4
|
SkeletonAvatar,
|
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
} from './skeleton';
|
|
10
10
|
|
|
11
11
|
const meta: Meta<typeof Skeleton> = {
|
|
12
|
-
title: '
|
|
12
|
+
title: '反馈 · Feedback/Skeleton',
|
|
13
13
|
component: Skeleton,
|
|
14
14
|
tags: ['autodocs'],
|
|
15
15
|
parameters: {
|
|
16
16
|
docs: {
|
|
17
17
|
description: {
|
|
18
18
|
component:
|
|
19
|
-
'骨架屏 — 在内容加载过程中呈现占位图形,减少加载期间的视觉跳动。shadcn `Skeleton` 原子 + antd
|
|
19
|
+
'骨架屏 — 在内容加载过程中呈现占位图形,减少加载期间的视觉跳动。shadcn `Skeleton` 原子 + antd 能力并集:支持 `shape: rect | circle | line`,同时提供预设组合 `SkeletonAvatar` / `SkeletonImage` / `SkeletonButton` / `SkeletonInput` / `SkeletonParagraph`。',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
},
|
|
@@ -58,7 +58,7 @@ export const Family: Story = {
|
|
|
58
58
|
export const Card: Story = {
|
|
59
59
|
parameters: { controls: { disable: true } },
|
|
60
60
|
render: () => (
|
|
61
|
-
<div className="w-80 rounded-lg border p-4">
|
|
61
|
+
<div className="w-80 rounded-lg border border-border p-4">
|
|
62
62
|
<SkeletonImage />
|
|
63
63
|
<SkeletonParagraph rows={3} className="mt-3" />
|
|
64
64
|
</div>
|
|
@@ -43,11 +43,12 @@ Skeleton.displayName = 'Skeleton';
|
|
|
43
43
|
|
|
44
44
|
export interface SkeletonAvatarProps extends Omit<SkeletonProps, 'shape'> {
|
|
45
45
|
/** 尺寸:小 32 / 默认 40 / 大 64。 @default "default" */
|
|
46
|
-
size?: 'sm' | 'default' | 'lg';
|
|
46
|
+
size?: 'sm' | 'md' | 'default' | 'lg';
|
|
47
47
|
}
|
|
48
48
|
const SkeletonAvatar = React.forwardRef<HTMLDivElement, SkeletonAvatarProps>(
|
|
49
|
-
({ size = '
|
|
50
|
-
const dim =
|
|
49
|
+
({ size = 'md', className, ...props }, ref) => {
|
|
50
|
+
const dim =
|
|
51
|
+
size === 'sm' ? 'size-8' : size === 'lg' ? 'size-16' : 'size-10';
|
|
51
52
|
return (
|
|
52
53
|
<Skeleton
|
|
53
54
|
ref={ref}
|
|
@@ -74,10 +75,10 @@ const SkeletonImage = React.forwardRef<HTMLDivElement, SkeletonImageProps>(
|
|
|
74
75
|
SkeletonImage.displayName = 'SkeletonImage';
|
|
75
76
|
|
|
76
77
|
export interface SkeletonButtonProps extends Omit<SkeletonProps, 'shape'> {
|
|
77
|
-
size?: 'sm' | 'default' | 'lg';
|
|
78
|
+
size?: 'sm' | 'md' | 'default' | 'lg';
|
|
78
79
|
}
|
|
79
80
|
const SkeletonButton = React.forwardRef<HTMLDivElement, SkeletonButtonProps>(
|
|
80
|
-
({ size = '
|
|
81
|
+
({ size = 'md', className, ...props }, ref) => {
|
|
81
82
|
const h = size === 'sm' ? 'h-8' : size === 'lg' ? 'h-10' : 'h-9';
|
|
82
83
|
return (
|
|
83
84
|
<Skeleton
|
|
@@ -104,8 +105,7 @@ const SkeletonInput = React.forwardRef<HTMLDivElement, SkeletonInputProps>(
|
|
|
104
105
|
);
|
|
105
106
|
SkeletonInput.displayName = 'SkeletonInput';
|
|
106
107
|
|
|
107
|
-
export interface SkeletonParagraphProps
|
|
108
|
-
extends Omit<SkeletonProps, 'shape'> {
|
|
108
|
+
export interface SkeletonParagraphProps extends Omit<SkeletonProps, 'shape'> {
|
|
109
109
|
/**
|
|
110
110
|
* 段落行数。最后一行宽度自动收窄至 60%,模拟自然段尾。
|
|
111
111
|
* @default 3
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: slider
|
|
3
3
|
name: Slider
|
|
4
|
+
displayName: 滑块
|
|
4
5
|
type: component
|
|
5
|
-
category:
|
|
6
|
+
category: data-entry
|
|
6
7
|
since: 0.1.0
|
|
7
8
|
package: '@teamix-evo/ui'
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
# Slider
|
|
11
|
+
# Slider 滑块
|
|
11
12
|
|
|
12
13
|
滑块 — Radix Slider + antd 的 `marks` 刻度标签。**单 / 双滑块由 `value`/`defaultValue` 数组长度决定**(传 `[lo, hi]` 自动变范围滑块)。
|
|
13
14
|
|
|
@@ -30,7 +31,21 @@ package: '@teamix-evo/ui'
|
|
|
30
31
|
<!-- auto:props:begin -->
|
|
31
32
|
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
32
33
|
| --- | --- | --- | --- | --- |
|
|
33
|
-
| `
|
|
34
|
+
| `value` | `number[]` | – | – | 受控 value — **必为数组**,单滑块 `[v]`,范围滑块 `[lo, hi]`(单/双由数组长度决定)。 与 `defaultValue` 二选一。 |
|
|
35
|
+
| `defaultValue` | `number[]` | – | – | 非受控初始值。同样数组形态(单/双由长度决定)。 |
|
|
36
|
+
| `onValueChange` | `(value: number[]) => void` | – | – | 滑块拖动 / 步进时触发(实时,频率高)。 业务表单层用 `onValueCommit` 拿到最终值更省心。 |
|
|
37
|
+
| `onValueCommit` | `(value: number[]) => void` | – | – | 拖动结束(mouse up / key up)时触发一次,适合"提交"语义。 |
|
|
38
|
+
| `min` | `number` | `0` | – | 最小值。 |
|
|
39
|
+
| `max` | `number` | `100` | – | 最大值。 |
|
|
40
|
+
| `step` | `number` | `1` | – | 步进单位(键盘 ←→ / 拖动吸附)。 |
|
|
41
|
+
| `disabled` | `boolean` | `false` | – | 禁用整个 Slider(灰化 + 不响应交互)。 |
|
|
42
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 排列方向(键盘导航方向跟随)。 |
|
|
43
|
+
| `inverted` | `boolean` | `false` | – | 反向滚动(从右往左 / 从下往上)。 |
|
|
44
|
+
| `name` | `string` | – | – | 表单字段名(原生 `<input name>`)。 |
|
|
45
|
+
| `marks` | `Record<number, React.ReactNode>` | – | – | 刻度标记(antd `marks` 并集)。键为 0~max 的数值,值为标签文本。 渲染时小标记点 + 文字标签出现在轨道下方(默认)或上方(`marksPosition="above"`)。 |
|
|
46
|
+
| `marksPosition` | `'above' \| 'below'` | `"below"` | – | 刻度文字位置(cloud-design `marksPosition` 并集) — `"below"` 在轨道下方,`"above"` 在轨道上方。 |
|
|
47
|
+
| `tooltipVisible` | `boolean` | `false` | – | 是否常驻显示当前值的 tooltip(cloud-design `tooltipVisible` 并集) — `true` 时所有 thumb 都会持续显示数值气泡;`false` 时仅在 hover / drag 时显示(默认)。 |
|
|
48
|
+
| `tipRender` | `(value: number) => React.ReactNode` | – | – | 自定义 tooltip 渲染函数(cloud-design `tipRender` 并集)。 不传时直接渲染数值。 |
|
|
34
49
|
<!-- auto:props:end -->
|
|
35
50
|
|
|
36
51
|
## 依赖
|
|
@@ -66,8 +81,10 @@ pnpm add @radix-ui/react-slider@^1.2.0
|
|
|
66
81
|
- **`step` 跨度的可访问性**:键盘 Page Up/Down 步进 = step × 10,设计 step 时考虑这一点
|
|
67
82
|
- **marks 的"圆点"和"文字"必须分两层渲染,不要打包成一个 box**:
|
|
68
83
|
- **圆点**:`absolute top-1/2 -translate-x-1/2 -translate-y-1/2` 居中压在 track 中线上,渲染在 `<SliderPrimitive.Root>` 内、`<Track>` 之外(放进 Track 内会被 `overflow-hidden` 裁掉或被 `Range` 高亮覆盖)
|
|
69
|
-
- **文字**:独立放在父容器 `bottom-0` 处,通过父容器 `pb-6` 留出 ~14px 间距;不要用 `mt-1` 把文字直接挂在圆点下方,会出现"点和文字粘在一起"的视觉 bug
|
|
84
|
+
- **文字**:独立放在父容器 `bottom-0`(默认)或 `top-0`(`marksPosition="above"`)处,通过父容器 `pb-6` / `pt-6` 留出 ~14px 间距;不要用 `mt-1` 把文字直接挂在圆点下方,会出现"点和文字粘在一起"的视觉 bug
|
|
70
85
|
- **`<Thumb>` 必须加 `relative z-10`**:让滑块视觉层级高于 marks 圆点,避免拖动时圆点突兀露在 thumb 之上
|
|
86
|
+
- **`tooltipVisible` 为展示能力**:常驻气泡不要氪加在表单表中使用(气泡会遮住上方行);需要 `tipRender` 定制显示内容时请传函数而非 ReactNode
|
|
87
|
+
- **`marksPosition="above"` 与拖拽 tooltip 会重叠**:同时开启 `marksPosition="above"` 和 `tooltipVisible=true` 时,拖拽气泡会与上方 marks 文字压在一起 — 建议二者选一
|
|
71
88
|
|
|
72
89
|
## Examples
|
|
73
90
|
|
|
@@ -80,7 +97,7 @@ import { Slider } from '@/components/ui/slider';
|
|
|
80
97
|
// 范围滑块
|
|
81
98
|
<Slider defaultValue={[20, 80]} max={100} step={1} />
|
|
82
99
|
|
|
83
|
-
// 带刻度标签
|
|
100
|
+
// 带刻度标签(下方)
|
|
84
101
|
<Slider
|
|
85
102
|
defaultValue={[40]}
|
|
86
103
|
max={100}
|
|
@@ -88,7 +105,42 @@ import { Slider } from '@/components/ui/slider';
|
|
|
88
105
|
marks={{ 0: '0°C', 20: '20', 40: '40', 60: '60', 80: '80', 100: '100°C' }}
|
|
89
106
|
/>
|
|
90
107
|
|
|
108
|
+
// 刻度上方
|
|
109
|
+
<Slider defaultValue={[40]} marksPosition="above" marks={{ 0: '0', 50: '50', 100: '100' }} />
|
|
110
|
+
|
|
111
|
+
// 常驻数值气泡
|
|
112
|
+
<Slider defaultValue={[42]} tooltipVisible />
|
|
113
|
+
|
|
114
|
+
// 自定义 tooltip
|
|
115
|
+
<Slider defaultValue={[1500]} max={5000} tooltipVisible tipRender={(v) => `¥${v}`} />
|
|
116
|
+
|
|
91
117
|
// 受控
|
|
92
118
|
const [v, setV] = React.useState([50]);
|
|
93
119
|
<Slider value={v} onValueChange={setV} max={100} />
|
|
94
120
|
```
|
|
121
|
+
|
|
122
|
+
## Range 形态 — 旧库 API → 新库映射
|
|
123
|
+
|
|
124
|
+
> 旧库(Teamix 1.0 / `@alifd/next.Range`)→ 新库 `Slider`(Radix `@radix-ui/react-slider` 底层 + antd `marks` 并集)。新库**单/双滑块由数据形态决定** — 不再有独立的 `slider="single|double"` prop。
|
|
125
|
+
|
|
126
|
+
| 旧库 API | 新库 API | 备注 |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| 组件名 `Range` | `Slider` | 对齐 shadcn / Radix 命名 |
|
|
129
|
+
| `slider="single"` | `defaultValue={[v]}` | 数据决定 — 数组长度 = 1 即单滑块 |
|
|
130
|
+
| `slider="double"` | `defaultValue={[lo, hi]}` | 数组长度 = 2 即范围滑块 |
|
|
131
|
+
| `onChange(number \| [n,n])` | `onValueChange(number[])` | 始终数组,Radix 命名 |
|
|
132
|
+
| `onProcess` 实时触发 | `onValueChange` | 同一回调,实时触发(Radix 内部) |
|
|
133
|
+
| `reverse` | `inverted`(透传 Radix Root) | Radix 命名 |
|
|
134
|
+
| `marks={5}` 数字密度 | 不修复 — 仅支持 `Record<number, ReactNode>` | 业务自构造 marks 对象 |
|
|
135
|
+
| `marks=true` boolean | 不修复 — 同上 | 业务自构造 |
|
|
136
|
+
| `marks=[5, 50, 100]` array | 不修复 — 同上 | 业务自构造 |
|
|
137
|
+
| `tooltipVisible` | `tooltipVisible` | 同名 |
|
|
138
|
+
| `tipRender` | `tipRender` | 同名 |
|
|
139
|
+
| `fixedWidth` 固定宽度拖动 | 不修复 | 极少使用,P3 |
|
|
140
|
+
| `pure` 纯渲染 | 不修复 | `React.memo` 由消费方按需包裹 |
|
|
141
|
+
|
|
142
|
+
### 不修复 / 后续工序清单
|
|
143
|
+
|
|
144
|
+
- **`slider` prop**:不修复 — 数组形态本身已是更直观的 API
|
|
145
|
+
- **`marks` 多类型签名**:仅保留 Object 形式,boolean / number / Array 由业务侧自行构造 marks 对象
|
|
146
|
+
- **`fixedWidth`** / **`pure`**:报告 §5 已列入"保留新库优势",**不修复**
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { Slider } from './slider';
|
|
3
3
|
|
|
4
4
|
const meta: Meta<typeof Slider> = {
|
|
5
|
-
title: '
|
|
5
|
+
title: '数据录入 · Data Entry/Slider',
|
|
6
6
|
component: Slider,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
8
|
parameters: {
|
|
9
9
|
docs: {
|
|
10
10
|
description: {
|
|
11
11
|
component:
|
|
12
|
-
'滑块 — 在给定范围内选取一个数值或一段区间。Radix Slider 实现 + antd
|
|
12
|
+
'滑块 — 在给定范围内选取一个数值或一段区间。Radix Slider 实现 + antd `marks` 刻度 ∪ cloud-design `marksPosition` / `tooltipVisible` / `tipRender` 并集能力:`value` / `defaultValue` 为数组,长度为 1 时为单滑块、为 2 时自动切换为区间滑块;可配 `marks` 渲染轨道下方(默认)或上方(`marksPosition="above"`)的刻度点与标签,`tooltipVisible` 控制数值气泡是否常驻。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/tokens`,无 mock。',
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
15
|
},
|
|
@@ -18,11 +18,20 @@ const meta: Meta<typeof Slider> = {
|
|
|
18
18
|
max: { control: 'number' },
|
|
19
19
|
step: { control: 'number' },
|
|
20
20
|
disabled: { control: 'boolean' },
|
|
21
|
+
tooltipVisible: { control: 'boolean' },
|
|
22
|
+
marksPosition: { control: 'inline-radio', options: ['below', 'above'] },
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
defaultValue: [42],
|
|
26
|
+
min: 0,
|
|
27
|
+
max: 100,
|
|
28
|
+
step: 1,
|
|
29
|
+
tooltipVisible: false,
|
|
30
|
+
marksPosition: 'below',
|
|
21
31
|
},
|
|
22
|
-
args: { defaultValue: [42], min: 0, max: 100, step: 1 },
|
|
23
32
|
decorators: [
|
|
24
33
|
(Story) => (
|
|
25
|
-
<div className="w-80">
|
|
34
|
+
<div className="w-80 px-2">
|
|
26
35
|
<Story />
|
|
27
36
|
</div>
|
|
28
37
|
),
|
|
@@ -39,7 +48,7 @@ export const Range: Story = {
|
|
|
39
48
|
render: () => <Slider defaultValue={[20, 80]} max={100} />,
|
|
40
49
|
};
|
|
41
50
|
|
|
42
|
-
export const
|
|
51
|
+
export const WithMarksBelow: Story = {
|
|
43
52
|
parameters: { controls: { disable: true } },
|
|
44
53
|
render: () => (
|
|
45
54
|
<Slider
|
|
@@ -58,6 +67,49 @@ export const WithMarks: Story = {
|
|
|
58
67
|
),
|
|
59
68
|
};
|
|
60
69
|
|
|
70
|
+
export const WithMarksAbove: Story = {
|
|
71
|
+
parameters: { controls: { disable: true } },
|
|
72
|
+
render: () => (
|
|
73
|
+
<Slider
|
|
74
|
+
defaultValue={[40]}
|
|
75
|
+
max={100}
|
|
76
|
+
step={20}
|
|
77
|
+
marksPosition="above"
|
|
78
|
+
marks={{
|
|
79
|
+
0: '0',
|
|
80
|
+
20: '20',
|
|
81
|
+
40: '40',
|
|
82
|
+
60: '60',
|
|
83
|
+
80: '80',
|
|
84
|
+
100: '100',
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const TooltipAlwaysVisible: Story = {
|
|
91
|
+
parameters: { controls: { disable: true } },
|
|
92
|
+
render: () => (
|
|
93
|
+
<div className="flex flex-col gap-10">
|
|
94
|
+
<Slider defaultValue={[42]} tooltipVisible />
|
|
95
|
+
<Slider defaultValue={[20, 80]} tooltipVisible />
|
|
96
|
+
</div>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const TooltipWithRender: Story = {
|
|
101
|
+
parameters: { controls: { disable: true } },
|
|
102
|
+
render: () => (
|
|
103
|
+
<Slider
|
|
104
|
+
defaultValue={[1500]}
|
|
105
|
+
max={5000}
|
|
106
|
+
step={100}
|
|
107
|
+
tooltipVisible
|
|
108
|
+
tipRender={(v) => `¥${v.toLocaleString()}`}
|
|
109
|
+
/>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
|
|
61
113
|
export const Disabled: Story = {
|
|
62
114
|
parameters: { controls: { disable: true } },
|
|
63
115
|
render: () => (
|
|
@@ -4,12 +4,89 @@ import * as SliderPrimitive from '@radix-ui/react-slider';
|
|
|
4
4
|
import { cn } from '@/utils/cn';
|
|
5
5
|
|
|
6
6
|
export interface SliderProps
|
|
7
|
-
extends
|
|
7
|
+
extends Omit<
|
|
8
|
+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>,
|
|
9
|
+
| 'value'
|
|
10
|
+
| 'defaultValue'
|
|
11
|
+
| 'onValueChange'
|
|
12
|
+
| 'onValueCommit'
|
|
13
|
+
| 'min'
|
|
14
|
+
| 'max'
|
|
15
|
+
| 'step'
|
|
16
|
+
| 'disabled'
|
|
17
|
+
| 'orientation'
|
|
18
|
+
| 'inverted'
|
|
19
|
+
| 'name'
|
|
20
|
+
> {
|
|
21
|
+
/**
|
|
22
|
+
* 受控 value — **必为数组**,单滑块 `[v]`,范围滑块 `[lo, hi]`(单/双由数组长度决定)。
|
|
23
|
+
* 与 `defaultValue` 二选一。
|
|
24
|
+
*/
|
|
25
|
+
value?: number[];
|
|
26
|
+
/** 非受控初始值。同样数组形态(单/双由长度决定)。 */
|
|
27
|
+
defaultValue?: number[];
|
|
28
|
+
/**
|
|
29
|
+
* 滑块拖动 / 步进时触发(实时,频率高)。
|
|
30
|
+
* 业务表单层用 `onValueCommit` 拿到最终值更省心。
|
|
31
|
+
*/
|
|
32
|
+
onValueChange?: (value: number[]) => void;
|
|
33
|
+
/**
|
|
34
|
+
* 拖动结束(mouse up / key up)时触发一次,适合"提交"语义。
|
|
35
|
+
*/
|
|
36
|
+
onValueCommit?: (value: number[]) => void;
|
|
37
|
+
/**
|
|
38
|
+
* 最小值。
|
|
39
|
+
* @default 0
|
|
40
|
+
*/
|
|
41
|
+
min?: number;
|
|
42
|
+
/**
|
|
43
|
+
* 最大值。
|
|
44
|
+
* @default 100
|
|
45
|
+
*/
|
|
46
|
+
max?: number;
|
|
47
|
+
/**
|
|
48
|
+
* 步进单位(键盘 ←→ / 拖动吸附)。
|
|
49
|
+
* @default 1
|
|
50
|
+
*/
|
|
51
|
+
step?: number;
|
|
52
|
+
/**
|
|
53
|
+
* 禁用整个 Slider(灰化 + 不响应交互)。
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 排列方向(键盘导航方向跟随)。
|
|
59
|
+
* @default "horizontal"
|
|
60
|
+
*/
|
|
61
|
+
orientation?: 'horizontal' | 'vertical';
|
|
62
|
+
/**
|
|
63
|
+
* 反向滚动(从右往左 / 从下往上)。
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
inverted?: boolean;
|
|
67
|
+
/** 表单字段名(原生 `<input name>`)。 */
|
|
68
|
+
name?: string;
|
|
8
69
|
/**
|
|
9
70
|
* 刻度标记(antd `marks` 并集)。键为 0~max 的数值,值为标签文本。
|
|
10
|
-
* 渲染时小标记点 +
|
|
71
|
+
* 渲染时小标记点 + 文字标签出现在轨道下方(默认)或上方(`marksPosition="above"`)。
|
|
11
72
|
*/
|
|
12
73
|
marks?: Record<number, React.ReactNode>;
|
|
74
|
+
/**
|
|
75
|
+
* 刻度文字位置(cloud-design `marksPosition` 并集) — `"below"` 在轨道下方,`"above"` 在轨道上方。
|
|
76
|
+
* @default "below"
|
|
77
|
+
*/
|
|
78
|
+
marksPosition?: 'above' | 'below';
|
|
79
|
+
/**
|
|
80
|
+
* 是否常驻显示当前值的 tooltip(cloud-design `tooltipVisible` 并集) —
|
|
81
|
+
* `true` 时所有 thumb 都会持续显示数值气泡;`false` 时仅在 hover / drag 时显示(默认)。
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
84
|
+
tooltipVisible?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* 自定义 tooltip 渲染函数(cloud-design `tipRender` 并集)。
|
|
87
|
+
* 不传时直接渲染数值。
|
|
88
|
+
*/
|
|
89
|
+
tipRender?: (value: number) => React.ReactNode;
|
|
13
90
|
}
|
|
14
91
|
|
|
15
92
|
const Slider = React.forwardRef<
|
|
@@ -17,19 +94,51 @@ const Slider = React.forwardRef<
|
|
|
17
94
|
SliderProps
|
|
18
95
|
>(
|
|
19
96
|
(
|
|
20
|
-
{
|
|
97
|
+
{
|
|
98
|
+
className,
|
|
99
|
+
marks,
|
|
100
|
+
marksPosition = 'below',
|
|
101
|
+
tooltipVisible = false,
|
|
102
|
+
tipRender,
|
|
103
|
+
value,
|
|
104
|
+
defaultValue,
|
|
105
|
+
min = 0,
|
|
106
|
+
max = 100,
|
|
107
|
+
onValueChange,
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
21
110
|
ref,
|
|
22
111
|
) => {
|
|
23
|
-
//
|
|
24
|
-
const
|
|
112
|
+
// 单 / 双滑块由 value/defaultValue 数组长度决定
|
|
113
|
+
const initial = (value ?? defaultValue ?? [0]) as number[];
|
|
114
|
+
const [internal, setInternal] = React.useState<number[]>(initial);
|
|
115
|
+
const isControlled = value !== undefined;
|
|
116
|
+
const current = isControlled ? (value as number[]) : internal;
|
|
117
|
+
|
|
118
|
+
const handleValueChange = (next: number[]) => {
|
|
119
|
+
if (!isControlled) setInternal(next);
|
|
120
|
+
onValueChange?.(next);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const thumbCount = current.length;
|
|
124
|
+
const showAboveMarks = marks && marksPosition === 'above';
|
|
125
|
+
const showBelowMarks = marks && marksPosition === 'below';
|
|
126
|
+
|
|
25
127
|
return (
|
|
26
|
-
<div
|
|
128
|
+
<div
|
|
129
|
+
className={cn(
|
|
130
|
+
'relative w-full',
|
|
131
|
+
showBelowMarks && 'pb-6',
|
|
132
|
+
showAboveMarks && 'pt-6',
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
27
135
|
<SliderPrimitive.Root
|
|
28
136
|
ref={ref}
|
|
29
137
|
value={value}
|
|
30
138
|
defaultValue={defaultValue}
|
|
31
139
|
min={min}
|
|
32
140
|
max={max}
|
|
141
|
+
onValueChange={handleValueChange}
|
|
33
142
|
className={cn(
|
|
34
143
|
'relative flex w-full touch-none select-none items-center',
|
|
35
144
|
className,
|
|
@@ -53,15 +162,47 @@ const Slider = React.forwardRef<
|
|
|
53
162
|
);
|
|
54
163
|
})
|
|
55
164
|
: null}
|
|
56
|
-
{Array.from({ length: thumbCount }).map((_, i) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
165
|
+
{Array.from({ length: thumbCount }).map((_, i) => {
|
|
166
|
+
const v = current[i] ?? 0;
|
|
167
|
+
const display = tipRender ? tipRender(v) : v;
|
|
168
|
+
return (
|
|
169
|
+
<SliderPrimitive.Thumb
|
|
170
|
+
key={i}
|
|
171
|
+
className="group relative z-10 block size-4 rounded-full border-2 border-primary bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
172
|
+
>
|
|
173
|
+
{tooltipVisible ? (
|
|
174
|
+
<span
|
|
175
|
+
className={cn(
|
|
176
|
+
// 常驻气泡:thumb 上方 ~8px,带轻微小三角效果(用 box-shadow 做箭头有点冗余,简化为圆角矩形)
|
|
177
|
+
'pointer-events-none absolute -top-8 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-foreground px-2 py-0.5 text-xs font-medium text-background shadow-md',
|
|
178
|
+
)}
|
|
179
|
+
aria-hidden
|
|
180
|
+
>
|
|
181
|
+
{display}
|
|
182
|
+
</span>
|
|
183
|
+
) : (
|
|
184
|
+
<span
|
|
185
|
+
className={cn(
|
|
186
|
+
// 仅 hover / focus / active 时显示
|
|
187
|
+
'pointer-events-none absolute -top-8 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-foreground px-2 py-0.5 text-xs font-medium text-background opacity-0 shadow-md transition-opacity',
|
|
188
|
+
'group-hover:opacity-100 group-focus-visible:opacity-100 group-data-[state=active]:opacity-100',
|
|
189
|
+
)}
|
|
190
|
+
aria-hidden
|
|
191
|
+
>
|
|
192
|
+
{display}
|
|
193
|
+
</span>
|
|
194
|
+
)}
|
|
195
|
+
</SliderPrimitive.Thumb>
|
|
196
|
+
);
|
|
197
|
+
})}
|
|
62
198
|
</SliderPrimitive.Root>
|
|
63
199
|
{marks ? (
|
|
64
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
className={cn(
|
|
202
|
+
'pointer-events-none absolute inset-x-0 h-4',
|
|
203
|
+
marksPosition === 'above' ? 'top-0' : 'bottom-0',
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
65
206
|
{Object.entries(marks).map(([k, label]) => {
|
|
66
207
|
const num = Number(k);
|
|
67
208
|
const pct = ((num - min) / (max - min)) * 100;
|
|
@@ -4,14 +4,15 @@ name: Sonner
|
|
|
4
4
|
type: component
|
|
5
5
|
category: feedback
|
|
6
6
|
since: 0.1.0
|
|
7
|
-
package:
|
|
7
|
+
package: '@teamix-evo/ui'
|
|
8
|
+
displayName: 轻提示
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
# Sonner
|
|
11
|
+
# Sonner 轻提示
|
|
11
12
|
|
|
12
13
|
Toast 通知 — 基于 [`sonner`](https://sonner.emilkowal.ski/),已**同时覆盖** antd `message`(行内简短)+ `notification`(标题 + 描述 + 行动)的并集。
|
|
13
14
|
|
|
14
|
-
**应用根挂 `<Toaster />` 一次**,在任意地方 `import { toast } from '@/components/ui/sonner'`
|
|
15
|
+
**应用根挂 `<Toaster />` 一次**,在任意地方 `import { toast } from '@/components/ui/sonner'` 触发。视觉走 OpenTrek tokens —— **无边框、纯 shadow 分界、面性(filled)状态图标**;`success` / `error` / `warning` / `info` / `loading` 五种状态各有对应配色与实心图标。
|
|
15
16
|
|
|
16
17
|
## When to use
|
|
17
18
|
|
|
@@ -31,7 +32,16 @@ Toast 通知 — 基于 [`sonner`](https://sonner.emilkowal.ski/),已**同时覆
|
|
|
31
32
|
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `<Toaster />` 的 props。`toast()` 函数式 API 见下方 Examples。
|
|
32
33
|
|
|
33
34
|
<!-- auto:props:begin -->
|
|
34
|
-
|
|
35
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
36
|
+
| --- | --- | --- | --- | --- |
|
|
37
|
+
| `position` | `SonnerProps['position']` | `"top-right"` | – | Toast 弹出位置(对齐 antd `message.config.placement`)。 |
|
|
38
|
+
| `duration` | `number` | `4000` | – | 自动消失时长(ms)— 0 表示常驻不自动关闭。 |
|
|
39
|
+
| `visibleToasts` | `number` | `3` | – | 同屏最大可见 toast 数(超出旧的自动收起)。 |
|
|
40
|
+
| `expand` | `boolean` | `false` | – | 鼠标 hover 时是否展开堆叠的 toast(否则保持折叠堆叠状)。 |
|
|
41
|
+
| `richColors` | `boolean` | `false` | – | 启用 sonner 自带的"丰富配色"(更高对比度的状态色背景); 关闭则走我们的视觉(对齐 OpenTrek tokens)。 |
|
|
42
|
+
| `closeButton` | `boolean` | `false` | – | 每张 toast 是否显示关闭按钮(右上角 ×)。 |
|
|
43
|
+
| `theme` | `SonnerProps['theme']` | – | – | 主题 — `'light'` / `'dark'` / `'system'`(跟随 OS 偏好)。 通常无需手传,组件会跟随 `<html>` 上的 `.dark` class 自动切换。 |
|
|
44
|
+
| `dir` | `SonnerProps['dir']` | `"auto"` | – | 文字方向 — `'ltr'` / `'rtl'` / `'auto'`。 |
|
|
35
45
|
<!-- auto:props:end -->
|
|
36
46
|
|
|
37
47
|
## 依赖
|
|
@@ -41,14 +51,18 @@ _(no props)_
|
|
|
41
51
|
<!-- auto:deps:begin -->
|
|
42
52
|
### 同库依赖
|
|
43
53
|
|
|
44
|
-
|
|
54
|
+
> `teamix-evo ui add sonner` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
55
|
+
|
|
56
|
+
| Entry | 类型 | 描述 |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
45
59
|
|
|
46
60
|
### npm 依赖
|
|
47
61
|
|
|
48
62
|
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
|
-
pnpm add sonner@^1.5.0
|
|
65
|
+
pnpm add sonner@^1.5.0 lucide-react@^0.460.0
|
|
52
66
|
```
|
|
53
67
|
<!-- auto:deps:end -->
|
|
54
68
|
|
|
@@ -61,6 +75,8 @@ pnpm add sonner@^1.5.0
|
|
|
61
75
|
- **错误用 `toast.error()`**:语义化方法比 `toast({ type: 'error' })` 优先
|
|
62
76
|
- **长任务用 `toast.promise()`**:自动显示 loading → success / error,避免手写状态切换
|
|
63
77
|
- **Action 按钮用一个就够**:不要塞多个,过载
|
|
78
|
+
- **不要覆盖 `icons` / `richColors`**:本组件已用面性 lucide 图标 + OpenTrek tokens 处理 5 种状态配色,自行传 `richColors` 会与既有样式冲突;如需自定义图标,通过传入的 `toastOptions.classNames.icon` / `icons` 在外层精细覆盖
|
|
79
|
+
- **禁用边框样式**:卡片默认 `border-0 + shadow-lg`,不要再加 `border` —— 与轻提示克制美学冲突
|
|
64
80
|
|
|
65
81
|
## Examples
|
|
66
82
|
|
|
@@ -68,7 +84,7 @@ pnpm add sonner@^1.5.0
|
|
|
68
84
|
import { Toaster, toast } from '@/components/ui/sonner';
|
|
69
85
|
|
|
70
86
|
// 应用根
|
|
71
|
-
<Toaster position="top-right" richColors
|
|
87
|
+
<Toaster position="top-right" richColors />;
|
|
72
88
|
|
|
73
89
|
// 简单消息(等价 antd message)
|
|
74
90
|
toast('已复制到剪贴板');
|
|
@@ -94,3 +110,38 @@ toast('文件已删除', {
|
|
|
94
110
|
action: { label: '撤销', onClick: () => undo() },
|
|
95
111
|
});
|
|
96
112
|
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Message 形态 — 旧库 API → 新映射
|
|
117
|
+
|
|
118
|
+
> 旧库 `Message`(hybridcloud,含 show/success/error/confirm 等命令式) → 新库 `toast()`(命令式) + `Alert`(内联常驻)。
|
|
119
|
+
> 新库用 [sonner](https://sonner.emilkowal.ski/) 三方库,已自定义视觉(OpenTrek tokens + 面性图标)。
|
|
120
|
+
|
|
121
|
+
### 命名 & 结构映射
|
|
122
|
+
|
|
123
|
+
| 旧库 | 新库 | 说明 |
|
|
124
|
+
| ------------------------------ | ----------------------------------------------- | ------------------------------- |
|
|
125
|
+
| `Message shape='toast'` | `toast()` | 命令式轻提示 |
|
|
126
|
+
| `Message shape='inline'` | `Alert` 组件 | 内联常驻提示(独立 entry) |
|
|
127
|
+
| `Message.success(msg)` | `toast.success(msg)` | 直接映射 |
|
|
128
|
+
| `Message.error(msg)` | `toast.error(msg)` | 直接映射 |
|
|
129
|
+
| `Message.warning(msg)` | `toast.warning(msg)` | 直接映射 |
|
|
130
|
+
| `Message.notice(msg)` | `toast.info(msg)` | notice → info |
|
|
131
|
+
| `Message.loading(msg)` | `toast.loading(msg)` | 直接映射 |
|
|
132
|
+
| `Message.hide()` | `toast.dismiss()` | 全局关闭 |
|
|
133
|
+
| `Message.config({ duration })` | `<Toaster duration={...} />` | 声明式配置 |
|
|
134
|
+
| `closeable` | `closeButton`(Toaster prop) | 拼写变更 |
|
|
135
|
+
| `type='help'` | `toast.info()` | 映射到 info |
|
|
136
|
+
| Promise 自动切换 | `toast.promise(p, { loading, success, error })` | 新增能力(P0) |
|
|
137
|
+
| `title + content` | `toast.success(title, { description })` | title 第一参,description 第二参 |
|
|
138
|
+
| `action` | `toast(msg, { action: { label, onClick } })` | 行动按钮 |
|
|
139
|
+
|
|
140
|
+
### 迁移 FAQ
|
|
141
|
+
|
|
142
|
+
| 问题 | 回答 |
|
|
143
|
+
| -------------------------- | -------------------------------------------------- |
|
|
144
|
+
| 内联提示怎么做? | 用 `Alert` 组件(非 toast),常驻在页面上 |
|
|
145
|
+
| Toaster 放哪? | 应用根组件放一次即可,如 `layout.tsx` |
|
|
146
|
+
| 怎么控制位置? | `<Toaster position="top-right" />` |
|
|
147
|
+
| Message.hide(id) 关闭指定? | `const id = toast.loading(...); toast.dismiss(id)` |
|