@manohub/ui 0.6.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 +141 -0
- package/dist/components/avatar/index.d.ts +283 -0
- package/dist/components/avatar/index.js +167 -0
- package/dist/components/badge/index.d.ts +132 -0
- package/dist/components/badge/index.js +99 -0
- package/dist/components/button/index.d.ts +578 -0
- package/dist/components/button/index.js +191 -0
- package/dist/components/capsule/index.d.ts +161 -0
- package/dist/components/capsule/index.js +101 -0
- package/dist/components/card/index.d.ts +456 -0
- package/dist/components/card/index.js +231 -0
- package/dist/components/checkbox/index.d.ts +331 -0
- package/dist/components/checkbox/index.js +165 -0
- package/dist/components/code/index.d.ts +134 -0
- package/dist/components/code/index.js +117 -0
- package/dist/components/code-editor/index.d.ts +163 -0
- package/dist/components/code-editor/index.js +98 -0
- package/dist/components/collapse/index.d.ts +125 -0
- package/dist/components/collapse/index.js +95 -0
- package/dist/components/control-size.d.ts +8 -0
- package/dist/components/dialog/index.d.ts +224 -0
- package/dist/components/dialog/index.js +194 -0
- package/dist/components/divider/index.d.ts +64 -0
- package/dist/components/divider/index.js +62 -0
- package/dist/components/drawer/index.d.ts +226 -0
- package/dist/components/drawer/index.js +190 -0
- package/dist/components/filter/index.d.ts +391 -0
- package/dist/components/filter/index.js +203 -0
- package/dist/components/form/index.d.ts +704 -0
- package/dist/components/form/index.js +329 -0
- package/dist/components/input/index.d.ts +980 -0
- package/dist/components/input/index.js +334 -0
- package/dist/components/layout/index.d.ts +162 -0
- package/dist/components/layout/index.js +100 -0
- package/dist/components/list-view/index.d.ts +563 -0
- package/dist/components/list-view/index.js +221 -0
- package/dist/components/loading/index.d.ts +77 -0
- package/dist/components/loading/index.js +40 -0
- package/dist/components/nav/index.d.ts +293 -0
- package/dist/components/nav/index.js +164 -0
- package/dist/components/notice/index.d.ts +137 -0
- package/dist/components/notice/index.js +101 -0
- package/dist/components/notification/index.d.ts +118 -0
- package/dist/components/notification/index.js +99 -0
- package/dist/components/page/index.d.ts +276 -0
- package/dist/components/page/index.js +351 -0
- package/dist/components/page/split-resize.d.ts +42 -0
- package/dist/components/page/split-resize.js +36 -0
- package/dist/components/pagination/index.d.ts +171 -0
- package/dist/components/pagination/index.js +226 -0
- package/dist/components/panel/index.d.ts +401 -0
- package/dist/components/panel/index.js +193 -0
- package/dist/components/part-padding.d.ts +27 -0
- package/dist/components/part-padding.js +23 -0
- package/dist/components/query-state/index.d.ts +207 -0
- package/dist/components/query-state/index.js +181 -0
- package/dist/components/radio/index.d.ts +473 -0
- package/dist/components/radio/index.js +250 -0
- package/dist/components/search/index.d.ts +167 -0
- package/dist/components/search/index.js +105 -0
- package/dist/components/select/index.d.ts +252 -0
- package/dist/components/select/index.js +281 -0
- package/dist/components/skeleton/index.d.ts +88 -0
- package/dist/components/skeleton/index.js +56 -0
- package/dist/components/steps/index.d.ts +199 -0
- package/dist/components/steps/index.js +117 -0
- package/dist/components/switch/index.d.ts +150 -0
- package/dist/components/switch/index.js +103 -0
- package/dist/components/table/index.d.ts +351 -0
- package/dist/components/table/index.js +286 -0
- package/dist/components/tabs/index.d.ts +179 -0
- package/dist/components/tabs/index.js +153 -0
- package/dist/components/tabset/index.d.ts +105 -0
- package/dist/components/tabset/index.js +68 -0
- package/dist/components/tag/index.d.ts +203 -0
- package/dist/components/tag/index.js +144 -0
- package/dist/components/toast/index.d.ts +271 -0
- package/dist/components/toast/index.js +116 -0
- package/dist/components/tooltip/index.d.ts +126 -0
- package/dist/components/tooltip/index.js +97 -0
- package/dist/components/tree/index.d.ts +317 -0
- package/dist/components/tree/index.js +251 -0
- package/dist/components/upload/index.d.ts +384 -0
- package/dist/components/upload/index.js +172 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +102 -0
- package/dist/services/base.d.ts +21 -0
- package/dist/services/base.js +42 -0
- package/dist/services/dialog.d.ts +41 -0
- package/dist/services/dialog.js +83 -0
- package/dist/services/index.d.ts +12 -0
- package/dist/services/loading.d.ts +21 -0
- package/dist/services/loading.js +41 -0
- package/dist/services/toast.d.ts +40 -0
- package/dist/services/toast.js +74 -0
- package/dist/styles/components/avatar.css +128 -0
- package/dist/styles/components/avatar.tokens.css +24 -0
- package/dist/styles/components/badge.css +237 -0
- package/dist/styles/components/button.css +454 -0
- package/dist/styles/components/button.tokens.css +56 -0
- package/dist/styles/components/capsule.css +120 -0
- package/dist/styles/components/capsule.tokens.css +27 -0
- package/dist/styles/components/card.css +192 -0
- package/dist/styles/components/checkbox.css +145 -0
- package/dist/styles/components/checkbox.tokens.css +26 -0
- package/dist/styles/components/code-editor.css +91 -0
- package/dist/styles/components/code-editor.tokens.css +24 -0
- package/dist/styles/components/code.css +153 -0
- package/dist/styles/components/code.tokens.css +29 -0
- package/dist/styles/components/collapse.css +62 -0
- package/dist/styles/components/collapse.tokens.css +20 -0
- package/dist/styles/components/dialog.css +106 -0
- package/dist/styles/components/dialog.tokens.css +24 -0
- package/dist/styles/components/divider.css +71 -0
- package/dist/styles/components/divider.tokens.css +21 -0
- package/dist/styles/components/drawer.css +143 -0
- package/dist/styles/components/drawer.tokens.css +22 -0
- package/dist/styles/components/filter.css +41 -0
- package/dist/styles/components/filter.tokens.css +14 -0
- package/dist/styles/components/form.css +220 -0
- package/dist/styles/components/form.tokens.css +15 -0
- package/dist/styles/components/input.css +445 -0
- package/dist/styles/components/input.tokens.css +41 -0
- package/dist/styles/components/layout.css +198 -0
- package/dist/styles/components/list-view.css +244 -0
- package/dist/styles/components/loading.css +76 -0
- package/dist/styles/components/loading.tokens.css +23 -0
- package/dist/styles/components/nav.css +48 -0
- package/dist/styles/components/notice.css +153 -0
- package/dist/styles/components/notice.tokens.css +17 -0
- package/dist/styles/components/notification.css +159 -0
- package/dist/styles/components/notification.tokens.css +37 -0
- package/dist/styles/components/page.css +473 -0
- package/dist/styles/components/pagination.css +297 -0
- package/dist/styles/components/pagination.tokens.css +32 -0
- package/dist/styles/components/panel.css +77 -0
- package/dist/styles/components/query-state.css +88 -0
- package/dist/styles/components/radio.css +271 -0
- package/dist/styles/components/radio.tokens.css +26 -0
- package/dist/styles/components/search.css +30 -0
- package/dist/styles/components/select.css +242 -0
- package/dist/styles/components/select.tokens.css +33 -0
- package/dist/styles/components/skeleton.css +60 -0
- package/dist/styles/components/skeleton.tokens.css +27 -0
- package/dist/styles/components/steps.css +157 -0
- package/dist/styles/components/steps.tokens.css +42 -0
- package/dist/styles/components/switch.css +165 -0
- package/dist/styles/components/switch.tokens.css +26 -0
- package/dist/styles/components/table.css +439 -0
- package/dist/styles/components/table.tokens.css +41 -0
- package/dist/styles/components/tabs.css +234 -0
- package/dist/styles/components/tabs.tokens.css +30 -0
- package/dist/styles/components/tag.css +200 -0
- package/dist/styles/components/tag.tokens.css +30 -0
- package/dist/styles/components/toast.css +150 -0
- package/dist/styles/components/toast.tokens.css +26 -0
- package/dist/styles/components/tooltip.css +89 -0
- package/dist/styles/components/tooltip.tokens.css +25 -0
- package/dist/styles/components/tree.css +139 -0
- package/dist/styles/components/tree.tokens.css +20 -0
- package/dist/styles/components/upload.css +150 -0
- package/dist/styles/components/upload.tokens.css +27 -0
- package/dist/styles/index.css +149 -0
- package/dist/styles/operations/collapse.css +84 -0
- package/dist/styles/operations/popup.css +189 -0
- package/dist/styles/operations/tabset.css +57 -0
- package/dist/utils/state-illustration.d.ts +31 -0
- package/dist/utils/state-illustration.js +156 -0
- package/package.json +62 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { type PropType, type SlotsType, type VNodeChild } from 'vue';
|
|
2
|
+
import type { ControlSize } from '../control-size';
|
|
3
|
+
export interface TabsOption {
|
|
4
|
+
/** 项文字 */
|
|
5
|
+
label: string;
|
|
6
|
+
/** 项值(受控值就是它) */
|
|
7
|
+
value: string | number;
|
|
8
|
+
/** 禁用项:不可选 */
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** 单项可关(`closable` 打开时用它例外掉某些项) */
|
|
11
|
+
closable?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type TabsValue = string | number;
|
|
14
|
+
/** 页签形态:`line` 下划线指示条 / `card` 选项卡(与内容区连成一体) */
|
|
15
|
+
export type TabsType = 'line' | 'card';
|
|
16
|
+
/**
|
|
17
|
+
* 页签条(**自绘**;下划线或选项卡两种形态 + 三档尺寸 + 居中 + 可关闭 + 可新增 + 「更多」收纳)。
|
|
18
|
+
*
|
|
19
|
+
* ## 与 `Tabset` / `Capsule` 的分工(三者都能"切换",判据在**可见形态**)
|
|
20
|
+
*
|
|
21
|
+
* - 本件:**页签条**(`line` 下划线 / `card` 选项卡,页面级分区切换)—— 就是这一族的皮肤;
|
|
22
|
+
* - `Capsule`:胶囊/分段按钮(块状选中底),用于筛选项那种"按钮感"切换;
|
|
23
|
+
* - `Tabset`:**骨架**(把某组互斥项与面板显隐接起来),它不含皮肤 —— 把本件塞进它的 `selector` 插槽即可。
|
|
24
|
+
*
|
|
25
|
+
* ## 指示条为什么不是一个滑块
|
|
26
|
+
*
|
|
27
|
+
* 每个项的指示条是它自己的伪元素、常驻但未选时 `opacity:0; scale:.75` —— "滑过去"的观感来自这两个属性的
|
|
28
|
+
* 过渡。做成一条共享滑块要 JS 量位置,且首帧会跳。**别在页签区加轨线**(会扣走区高 1px,规范明确警告)。
|
|
29
|
+
* (`card` 档不画指示条:选中由底色与顶边表达。)
|
|
30
|
+
*
|
|
31
|
+
* ## 超过一屏的页签怎么放
|
|
32
|
+
*
|
|
33
|
+
* 给 `more` 插槽即出「更多 ▾」页签 + 操作层浮层(原生 popover,零 JS 开合);哪些页签收进"更多"由调用方
|
|
34
|
+
* 决定 —— 收多久、收哪些是业务判断,不是皮肤能定的。
|
|
35
|
+
*
|
|
36
|
+
* @example 基本
|
|
37
|
+
* <Tabs :options="[{label:'全部',value:'all'},{label:'进行中',value:'doing'}]" v-model:value="current" />
|
|
38
|
+
*
|
|
39
|
+
* @example 选项卡形态 + 可关闭可新增
|
|
40
|
+
* <Tabs type="card" closable addable :options="tabs" v-model:value="current"
|
|
41
|
+
* :on-close="remove" :on-add="add" />
|
|
42
|
+
*/
|
|
43
|
+
export declare const Tabs: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
44
|
+
options: {
|
|
45
|
+
type: PropType<TabsOption[]>;
|
|
46
|
+
required: true;
|
|
47
|
+
};
|
|
48
|
+
/** 受控选中值;不传即非受控(由 `defaultValue` 起) */
|
|
49
|
+
value: {
|
|
50
|
+
type: PropType<TabsValue | undefined>;
|
|
51
|
+
default: undefined;
|
|
52
|
+
};
|
|
53
|
+
/** 非受控初始选中值,缺省第一项 */
|
|
54
|
+
defaultValue: {
|
|
55
|
+
type: PropType<TabsValue | undefined>;
|
|
56
|
+
default: undefined;
|
|
57
|
+
};
|
|
58
|
+
/** 选中变化回调(受控与非受控都会调) */
|
|
59
|
+
onChange: {
|
|
60
|
+
type: PropType<(value: TabsValue) => void>;
|
|
61
|
+
default: undefined;
|
|
62
|
+
};
|
|
63
|
+
/** 形态:`line` 下划线 / `card` 选项卡 */
|
|
64
|
+
type: {
|
|
65
|
+
type: PropType<TabsType>;
|
|
66
|
+
default: string;
|
|
67
|
+
};
|
|
68
|
+
/** 整体居中(页签少、要撑满整行时用) */
|
|
69
|
+
centered: {
|
|
70
|
+
type: BooleanConstructor;
|
|
71
|
+
default: boolean;
|
|
72
|
+
};
|
|
73
|
+
/** 尺寸档(24 / 32 / 40,只覆盖令牌) */
|
|
74
|
+
size: {
|
|
75
|
+
type: PropType<ControlSize>;
|
|
76
|
+
default: string;
|
|
77
|
+
};
|
|
78
|
+
/** 每项带关闭位(单项可用 `option.closable` 覆写) */
|
|
79
|
+
closable: {
|
|
80
|
+
type: BooleanConstructor;
|
|
81
|
+
default: boolean;
|
|
82
|
+
};
|
|
83
|
+
/** 尾部「新增」按钮 */
|
|
84
|
+
addable: {
|
|
85
|
+
type: BooleanConstructor;
|
|
86
|
+
default: boolean;
|
|
87
|
+
};
|
|
88
|
+
/** 「更多」页签的文字 */
|
|
89
|
+
moreLabel: {
|
|
90
|
+
type: StringConstructor;
|
|
91
|
+
default: string;
|
|
92
|
+
};
|
|
93
|
+
/** 关闭某项(只回调,**不从 `options` 里删** —— 数据归调用方) */
|
|
94
|
+
onClose: {
|
|
95
|
+
type: PropType<(value: TabsValue) => void>;
|
|
96
|
+
default: undefined;
|
|
97
|
+
};
|
|
98
|
+
/** 点新增(只回调) */
|
|
99
|
+
onAdd: {
|
|
100
|
+
type: PropType<() => void>;
|
|
101
|
+
default: undefined;
|
|
102
|
+
};
|
|
103
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
104
|
+
options: {
|
|
105
|
+
type: PropType<TabsOption[]>;
|
|
106
|
+
required: true;
|
|
107
|
+
};
|
|
108
|
+
/** 受控选中值;不传即非受控(由 `defaultValue` 起) */
|
|
109
|
+
value: {
|
|
110
|
+
type: PropType<TabsValue | undefined>;
|
|
111
|
+
default: undefined;
|
|
112
|
+
};
|
|
113
|
+
/** 非受控初始选中值,缺省第一项 */
|
|
114
|
+
defaultValue: {
|
|
115
|
+
type: PropType<TabsValue | undefined>;
|
|
116
|
+
default: undefined;
|
|
117
|
+
};
|
|
118
|
+
/** 选中变化回调(受控与非受控都会调) */
|
|
119
|
+
onChange: {
|
|
120
|
+
type: PropType<(value: TabsValue) => void>;
|
|
121
|
+
default: undefined;
|
|
122
|
+
};
|
|
123
|
+
/** 形态:`line` 下划线 / `card` 选项卡 */
|
|
124
|
+
type: {
|
|
125
|
+
type: PropType<TabsType>;
|
|
126
|
+
default: string;
|
|
127
|
+
};
|
|
128
|
+
/** 整体居中(页签少、要撑满整行时用) */
|
|
129
|
+
centered: {
|
|
130
|
+
type: BooleanConstructor;
|
|
131
|
+
default: boolean;
|
|
132
|
+
};
|
|
133
|
+
/** 尺寸档(24 / 32 / 40,只覆盖令牌) */
|
|
134
|
+
size: {
|
|
135
|
+
type: PropType<ControlSize>;
|
|
136
|
+
default: string;
|
|
137
|
+
};
|
|
138
|
+
/** 每项带关闭位(单项可用 `option.closable` 覆写) */
|
|
139
|
+
closable: {
|
|
140
|
+
type: BooleanConstructor;
|
|
141
|
+
default: boolean;
|
|
142
|
+
};
|
|
143
|
+
/** 尾部「新增」按钮 */
|
|
144
|
+
addable: {
|
|
145
|
+
type: BooleanConstructor;
|
|
146
|
+
default: boolean;
|
|
147
|
+
};
|
|
148
|
+
/** 「更多」页签的文字 */
|
|
149
|
+
moreLabel: {
|
|
150
|
+
type: StringConstructor;
|
|
151
|
+
default: string;
|
|
152
|
+
};
|
|
153
|
+
/** 关闭某项(只回调,**不从 `options` 里删** —— 数据归调用方) */
|
|
154
|
+
onClose: {
|
|
155
|
+
type: PropType<(value: TabsValue) => void>;
|
|
156
|
+
default: undefined;
|
|
157
|
+
};
|
|
158
|
+
/** 点新增(只回调) */
|
|
159
|
+
onAdd: {
|
|
160
|
+
type: PropType<() => void>;
|
|
161
|
+
default: undefined;
|
|
162
|
+
};
|
|
163
|
+
}>> & Readonly<{}>, {
|
|
164
|
+
size: ControlSize;
|
|
165
|
+
value: TabsValue | undefined;
|
|
166
|
+
type: TabsType;
|
|
167
|
+
onChange: (value: TabsValue) => void;
|
|
168
|
+
onClose: (value: TabsValue) => void;
|
|
169
|
+
defaultValue: TabsValue | undefined;
|
|
170
|
+
closable: boolean;
|
|
171
|
+
centered: boolean;
|
|
172
|
+
addable: boolean;
|
|
173
|
+
moreLabel: string;
|
|
174
|
+
onAdd: () => void;
|
|
175
|
+
}, SlotsType<{
|
|
176
|
+
more?: (context: {
|
|
177
|
+
options: TabsOption[];
|
|
178
|
+
}) => VNodeChild;
|
|
179
|
+
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, createVNode, Fragment, mergeProps } from "vue";
|
|
2
|
+
import { Icon } from "@manohub/icon";
|
|
3
|
+
const Tabs = /* @__PURE__ */ defineComponent({
|
|
4
|
+
name: "Tabs",
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
props: {
|
|
7
|
+
options: {
|
|
8
|
+
type: Array,
|
|
9
|
+
required: true
|
|
10
|
+
},
|
|
11
|
+
/** 受控选中值;不传即非受控(由 `defaultValue` 起) */
|
|
12
|
+
value: {
|
|
13
|
+
type: [String, Number],
|
|
14
|
+
default: void 0
|
|
15
|
+
},
|
|
16
|
+
/** 非受控初始选中值,缺省第一项 */
|
|
17
|
+
defaultValue: {
|
|
18
|
+
type: [String, Number],
|
|
19
|
+
default: void 0
|
|
20
|
+
},
|
|
21
|
+
/** 选中变化回调(受控与非受控都会调) */
|
|
22
|
+
onChange: {
|
|
23
|
+
type: Function,
|
|
24
|
+
default: void 0
|
|
25
|
+
},
|
|
26
|
+
/** 形态:`line` 下划线 / `card` 选项卡 */
|
|
27
|
+
type: {
|
|
28
|
+
type: String,
|
|
29
|
+
default: "line"
|
|
30
|
+
},
|
|
31
|
+
/** 整体居中(页签少、要撑满整行时用) */
|
|
32
|
+
centered: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
default: false
|
|
35
|
+
},
|
|
36
|
+
/** 尺寸档(24 / 32 / 40,只覆盖令牌) */
|
|
37
|
+
size: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: "md"
|
|
40
|
+
},
|
|
41
|
+
/** 每项带关闭位(单项可用 `option.closable` 覆写) */
|
|
42
|
+
closable: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: false
|
|
45
|
+
},
|
|
46
|
+
/** 尾部「新增」按钮 */
|
|
47
|
+
addable: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: false
|
|
50
|
+
},
|
|
51
|
+
/** 「更多」页签的文字 */
|
|
52
|
+
moreLabel: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: "更多"
|
|
55
|
+
},
|
|
56
|
+
/** 关闭某项(只回调,**不从 `options` 里删** —— 数据归调用方) */
|
|
57
|
+
onClose: {
|
|
58
|
+
type: Function,
|
|
59
|
+
default: void 0
|
|
60
|
+
},
|
|
61
|
+
/** 点新增(只回调) */
|
|
62
|
+
onAdd: {
|
|
63
|
+
type: Function,
|
|
64
|
+
default: void 0
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
slots: Object,
|
|
68
|
+
setup(props, {
|
|
69
|
+
slots,
|
|
70
|
+
attrs
|
|
71
|
+
}) {
|
|
72
|
+
const innerValue = ref(props.value ?? props.defaultValue ?? props.options[0]?.value);
|
|
73
|
+
const current = computed(() => props.value ?? innerValue.value);
|
|
74
|
+
const moreId = `mh-tabs-${Math.random().toString(36).slice(2, 10)}`;
|
|
75
|
+
const select = (value) => {
|
|
76
|
+
const target = props.options.find((option) => option.value === value);
|
|
77
|
+
if (target?.disabled) return;
|
|
78
|
+
if (props.value === void 0) innerValue.value = value;
|
|
79
|
+
props.onChange?.(value);
|
|
80
|
+
};
|
|
81
|
+
const isClosable = (option) => option.closable ?? props.closable;
|
|
82
|
+
return () => createVNode("nav", {
|
|
83
|
+
"class": [
|
|
84
|
+
"mh-tabs",
|
|
85
|
+
/* `mh-popup-host` 是操作层展开转向的**宿主标记**(popup.css 靠它 :has 反推面板开合):
|
|
86
|
+
没有它,「更多」的 caret 展开时不会翻向 —— 与 Select / Tooltip 同一分工。 */
|
|
87
|
+
"mh-popup-host",
|
|
88
|
+
props.type === "card" ? "mh-tabs--card" : "",
|
|
89
|
+
props.centered ? "mh-tabs--centered" : "",
|
|
90
|
+
props.size === "md" ? "" : `mh-tabs--${props.size}`,
|
|
91
|
+
attrs.class
|
|
92
|
+
].filter(Boolean).join(" "),
|
|
93
|
+
"style": attrs.style,
|
|
94
|
+
"role": "tablist"
|
|
95
|
+
}, [props.options.map((option) => {
|
|
96
|
+
const active = option.value === current.value;
|
|
97
|
+
return createVNode("button", {
|
|
98
|
+
"key": option.value,
|
|
99
|
+
"type": "button",
|
|
100
|
+
"role": "tab",
|
|
101
|
+
"class": ["mh-tabs__tab", active ? "is-selected" : "", option.disabled ? "is-disabled" : ""].filter(Boolean).join(" "),
|
|
102
|
+
"data-state": active ? "active" : "inactive",
|
|
103
|
+
"aria-selected": active,
|
|
104
|
+
"disabled": option.disabled,
|
|
105
|
+
"onClick": () => select(option.value)
|
|
106
|
+
}, [option.label, isClosable(option) && !option.disabled ? (
|
|
107
|
+
/* 关闭位不是 `<button>`:页签自己是按钮,按钮里嵌按钮是非法 HTML。
|
|
108
|
+
用 `role="button"` + `stopPropagation` 表达同样的语义与行为。 */
|
|
109
|
+
createVNode("span", {
|
|
110
|
+
"class": "mh-tabs__close",
|
|
111
|
+
"role": "button",
|
|
112
|
+
"tabindex": -1,
|
|
113
|
+
"aria-label": `关闭${option.label}`,
|
|
114
|
+
"onClick": (event) => {
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
props.onClose?.(option.value);
|
|
118
|
+
}
|
|
119
|
+
}, [createVNode(Icon, {
|
|
120
|
+
"name": "close"
|
|
121
|
+
}, null)])
|
|
122
|
+
) : null]);
|
|
123
|
+
}), props.addable ? createVNode("button", {
|
|
124
|
+
"type": "button",
|
|
125
|
+
"class": "mh-tabs__add",
|
|
126
|
+
"aria-label": "新增页签",
|
|
127
|
+
"onClick": () => props.onAdd?.()
|
|
128
|
+
}, [createVNode(Icon, {
|
|
129
|
+
"name": "plus"
|
|
130
|
+
}, null)]) : null, slots.more ? createVNode(Fragment, null, [createVNode("button", mergeProps({
|
|
131
|
+
"type": "button",
|
|
132
|
+
"class": "mh-tabs__tab mh-tabs__tab--more",
|
|
133
|
+
"data-state": "inactive"
|
|
134
|
+
}, {
|
|
135
|
+
popovertarget: moreId
|
|
136
|
+
}), [props.moreLabel, createVNode(Icon, {
|
|
137
|
+
"name": "chevron-down",
|
|
138
|
+
"class": "mh-popup-caret"
|
|
139
|
+
}, null)]), createVNode("div", mergeProps({
|
|
140
|
+
"id": moreId,
|
|
141
|
+
"class": "mh-popup",
|
|
142
|
+
"data-side": "bottom",
|
|
143
|
+
"data-align": "end"
|
|
144
|
+
}, {
|
|
145
|
+
popover: "auto"
|
|
146
|
+
}), [slots.more({
|
|
147
|
+
options: props.options
|
|
148
|
+
})])]) : null]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
export {
|
|
152
|
+
Tabs
|
|
153
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type PropType, type SlotsType, type VNodeChild } from 'vue';
|
|
2
|
+
export interface TabsetOption {
|
|
3
|
+
/** 项文字 */
|
|
4
|
+
label: string;
|
|
5
|
+
/** 项值(受控值就是它) */
|
|
6
|
+
value: string | number;
|
|
7
|
+
/** 禁用项:不可选(原生 radio 路径下方向键会跳过) */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface TabsetProps {
|
|
11
|
+
/** 轨道项(顺序即「第 n 项 ↔ 第 n 块面板」的档位表) */
|
|
12
|
+
options: TabsetOption[];
|
|
13
|
+
/** 受控选中值;不传即非受控(由 `defaultValue` 起) */
|
|
14
|
+
value?: string | number;
|
|
15
|
+
/** 非受控初始选中值,缺省第一项 */
|
|
16
|
+
defaultValue?: string | number;
|
|
17
|
+
/** 选中变化回调(受控与非受控都会调) */
|
|
18
|
+
onChange?: (value: string | number) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 面板切换(**操作层骨架 + 组件路径显隐**)。
|
|
22
|
+
*
|
|
23
|
+
* ## 它是什么
|
|
24
|
+
* 把「一组互斥选项」与「按顺序排列的面板」接起来:第 n 个选项对应第 n 块面板。
|
|
25
|
+
* 骨架(根、面板组、档位表)在操作层 `styles/operations/tabset.css`,本件负责:
|
|
26
|
+
* 受控值、面板包装与显隐、把 `select` 交给选择控件。
|
|
27
|
+
*
|
|
28
|
+
* ## 两条显隐路径(结论一致,不冲突)
|
|
29
|
+
* - **原生路径**(推荐):选择控件用**原生 radio**(如 `Capsule`)→ 操作层的档位表
|
|
30
|
+
* `:has(input:nth-of-type(n):checked)` 直接显隐,**零 JS**;
|
|
31
|
+
* - **组件路径**:控件不是 radio 时,本件按受控值给非选中面板下发 `hidden`。
|
|
32
|
+
* 两条路径都由同一个值驱动,故同时成立不会互相打架。
|
|
33
|
+
*
|
|
34
|
+
* ## 与皮肤零互指
|
|
35
|
+
* 本件不认识任何选择控件的类名 —— 轨道由 `selector` 插槽提供(作用域参数 `{ value, options, select }`),
|
|
36
|
+
* 放胶囊、分段按钮、单选卡片都行;面板内容由默认插槽按 `options` 顺序给出。
|
|
37
|
+
*
|
|
38
|
+
* @example 胶囊 + 面板(`Capsule` 提供原生 radio,故走零 JS 路径)
|
|
39
|
+
* <Tabset :options="tabs" v-model:value="current">
|
|
40
|
+
* {{
|
|
41
|
+
* selector: ({ value, select }) => <Capsule :options="tabs" modelValue={value} onChange={select} />,
|
|
42
|
+
* default: () => [
|
|
43
|
+
* <Card key="all">全部:15 条</Card>,
|
|
44
|
+
* <Card key="doing">进行中:3 条</Card>,
|
|
45
|
+
* ],
|
|
46
|
+
* }}
|
|
47
|
+
* </Tabset>
|
|
48
|
+
*/
|
|
49
|
+
export declare const Tabset: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
50
|
+
options: {
|
|
51
|
+
type: PropType<TabsetOption[]>;
|
|
52
|
+
required: true;
|
|
53
|
+
};
|
|
54
|
+
value: {
|
|
55
|
+
type: PropType<string | number | undefined>;
|
|
56
|
+
default: undefined;
|
|
57
|
+
};
|
|
58
|
+
defaultValue: {
|
|
59
|
+
type: PropType<string | number | undefined>;
|
|
60
|
+
default: undefined;
|
|
61
|
+
};
|
|
62
|
+
onChange: {
|
|
63
|
+
type: PropType<(value: string | number) => void>;
|
|
64
|
+
default: undefined;
|
|
65
|
+
};
|
|
66
|
+
/** 惰性档:只有激活的面板才渲染(切走即卸载),用于面板很重(图表 / 大表格)的场景 */
|
|
67
|
+
lazy: {
|
|
68
|
+
type: BooleanConstructor;
|
|
69
|
+
default: boolean;
|
|
70
|
+
};
|
|
71
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
72
|
+
options: {
|
|
73
|
+
type: PropType<TabsetOption[]>;
|
|
74
|
+
required: true;
|
|
75
|
+
};
|
|
76
|
+
value: {
|
|
77
|
+
type: PropType<string | number | undefined>;
|
|
78
|
+
default: undefined;
|
|
79
|
+
};
|
|
80
|
+
defaultValue: {
|
|
81
|
+
type: PropType<string | number | undefined>;
|
|
82
|
+
default: undefined;
|
|
83
|
+
};
|
|
84
|
+
onChange: {
|
|
85
|
+
type: PropType<(value: string | number) => void>;
|
|
86
|
+
default: undefined;
|
|
87
|
+
};
|
|
88
|
+
/** 惰性档:只有激活的面板才渲染(切走即卸载),用于面板很重(图表 / 大表格)的场景 */
|
|
89
|
+
lazy: {
|
|
90
|
+
type: BooleanConstructor;
|
|
91
|
+
default: boolean;
|
|
92
|
+
};
|
|
93
|
+
}>> & Readonly<{}>, {
|
|
94
|
+
value: string | number | undefined;
|
|
95
|
+
onChange: (value: string | number) => void;
|
|
96
|
+
defaultValue: string | number | undefined;
|
|
97
|
+
lazy: boolean;
|
|
98
|
+
}, SlotsType<{
|
|
99
|
+
default?: () => VNodeChild;
|
|
100
|
+
selector?: (context: {
|
|
101
|
+
value: string | number | undefined;
|
|
102
|
+
options: TabsetOption[];
|
|
103
|
+
select: (value: string | number) => void;
|
|
104
|
+
}) => VNodeChild;
|
|
105
|
+
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, createVNode } from "vue";
|
|
2
|
+
const Tabset = /* @__PURE__ */ defineComponent({
|
|
3
|
+
name: "Tabset",
|
|
4
|
+
inheritAttrs: false,
|
|
5
|
+
props: {
|
|
6
|
+
options: {
|
|
7
|
+
type: Array,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
value: {
|
|
11
|
+
type: [String, Number],
|
|
12
|
+
default: void 0
|
|
13
|
+
},
|
|
14
|
+
defaultValue: {
|
|
15
|
+
type: [String, Number],
|
|
16
|
+
default: void 0
|
|
17
|
+
},
|
|
18
|
+
onChange: {
|
|
19
|
+
type: Function,
|
|
20
|
+
default: void 0
|
|
21
|
+
},
|
|
22
|
+
/** 惰性档:只有激活的面板才渲染(切走即卸载),用于面板很重(图表 / 大表格)的场景 */
|
|
23
|
+
lazy: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
slots: Object,
|
|
29
|
+
setup(props, {
|
|
30
|
+
slots,
|
|
31
|
+
attrs
|
|
32
|
+
}) {
|
|
33
|
+
const innerValue = ref(props.value ?? props.defaultValue ?? props.options[0]?.value);
|
|
34
|
+
const current = computed(() => props.value ?? innerValue.value);
|
|
35
|
+
const select = (value) => {
|
|
36
|
+
const target = props.options.find((option) => option.value === value);
|
|
37
|
+
if (target?.disabled) return;
|
|
38
|
+
if (props.value === void 0) innerValue.value = value;
|
|
39
|
+
props.onChange?.(value);
|
|
40
|
+
};
|
|
41
|
+
const panels = () => {
|
|
42
|
+
const children = slots.default?.();
|
|
43
|
+
const list = Array.isArray(children) ? children : children ? [children] : [];
|
|
44
|
+
return list.map((child, index) => {
|
|
45
|
+
const option = props.options[index];
|
|
46
|
+
const active = option !== void 0 && option.value === current.value;
|
|
47
|
+
if (props.lazy && !active) return null;
|
|
48
|
+
return createVNode("div", {
|
|
49
|
+
"class": "mh-tabset-panel",
|
|
50
|
+
"hidden": !active
|
|
51
|
+
}, [child]);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
return () => createVNode("div", {
|
|
55
|
+
"class": ["mh-tabset", attrs.class].filter(Boolean).join(" "),
|
|
56
|
+
"style": attrs.style
|
|
57
|
+
}, [slots.selector?.({
|
|
58
|
+
value: current.value,
|
|
59
|
+
options: props.options,
|
|
60
|
+
select
|
|
61
|
+
}), createVNode("div", {
|
|
62
|
+
"class": "mh-tabset-panels"
|
|
63
|
+
}, [panels()])]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
export {
|
|
67
|
+
Tabset
|
|
68
|
+
};
|