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