vue-clean-tabs 1.0.0 → 1.1.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 +104 -129
- package/dist/index.esm.js +85 -80
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +67 -67
- package/src/components/ConfigurableSimpleTabs.vue +85 -37
- package/src/index.ts +19 -18
- package/src/types.ts +181 -180
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vue-clean-tabs",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A lightweight and highly configurable Vue 3 tabs component with clean design and flexible customization options",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.esm.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist",
|
|
10
|
-
"src/components/ConfigurableSimpleTabs.vue",
|
|
11
|
-
"src/types.ts",
|
|
12
|
-
"src/index.ts"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "vite build",
|
|
16
|
-
"build:lib": "vite build --config vite.lib.config.ts",
|
|
17
|
-
"dev": "vite",
|
|
18
|
-
"preview": "vite preview",
|
|
19
|
-
"type-check": "vue-tsc --noEmit",
|
|
20
|
-
"prepublishOnly": "npm run build:lib"
|
|
21
|
-
},
|
|
22
|
-
"exports": {
|
|
23
|
-
".": {
|
|
24
|
-
"import": "./dist/index.esm.js",
|
|
25
|
-
"require": "./dist/index.js",
|
|
26
|
-
"types": "./dist/index.d.ts"
|
|
27
|
-
},
|
|
28
|
-
"./dist/style.css": "./dist/style.css"
|
|
29
|
-
},
|
|
30
|
-
"keywords": [
|
|
31
|
-
"vue",
|
|
32
|
-
"vue3",
|
|
33
|
-
"tabs",
|
|
34
|
-
"simple",
|
|
35
|
-
"lightweight",
|
|
36
|
-
"component",
|
|
37
|
-
"typescript",
|
|
38
|
-
"configurable",
|
|
39
|
-
"navigation",
|
|
40
|
-
"ui",
|
|
41
|
-
"customizable",
|
|
42
|
-
"clean",
|
|
43
|
-
"minimal",
|
|
44
|
-
"responsive"
|
|
45
|
-
],
|
|
46
|
-
"author": "SkillHarbor",
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "https://github.com/skillharbor/vue-clean-tabs.git"
|
|
51
|
-
},
|
|
52
|
-
"bugs": {
|
|
53
|
-
"url": "https://github.com/skillharbor/vue-clean-tabs/issues"
|
|
54
|
-
},
|
|
55
|
-
"homepage": "https://github.com/skillharbor/vue-clean-tabs#readme",
|
|
56
|
-
"peerDependencies": {
|
|
57
|
-
"vue": "^3.3.0"
|
|
58
|
-
},
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@types/node": "^20.5.2",
|
|
61
|
-
"@vitejs/plugin-vue": "^4.2.3",
|
|
62
|
-
"typescript": "~5.1.6",
|
|
63
|
-
"vite": "^4.4.5",
|
|
64
|
-
"vue": "^3.3.4",
|
|
65
|
-
"vue-tsc": "^1.8.5"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-clean-tabs",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A lightweight and highly configurable Vue 3 tabs component with clean design and flexible customization options",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src/components/ConfigurableSimpleTabs.vue",
|
|
11
|
+
"src/types.ts",
|
|
12
|
+
"src/index.ts"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "vite build",
|
|
16
|
+
"build:lib": "vite build --config vite.lib.config.ts",
|
|
17
|
+
"dev": "vite",
|
|
18
|
+
"preview": "vite preview",
|
|
19
|
+
"type-check": "vue-tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run build:lib"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./dist/index.esm.js",
|
|
25
|
+
"require": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./dist/style.css": "./dist/style.css"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"vue",
|
|
32
|
+
"vue3",
|
|
33
|
+
"tabs",
|
|
34
|
+
"simple",
|
|
35
|
+
"lightweight",
|
|
36
|
+
"component",
|
|
37
|
+
"typescript",
|
|
38
|
+
"configurable",
|
|
39
|
+
"navigation",
|
|
40
|
+
"ui",
|
|
41
|
+
"customizable",
|
|
42
|
+
"clean",
|
|
43
|
+
"minimal",
|
|
44
|
+
"responsive"
|
|
45
|
+
],
|
|
46
|
+
"author": "SkillHarbor",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/skillharbor/vue-clean-tabs.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/skillharbor/vue-clean-tabs/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/skillharbor/vue-clean-tabs#readme",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"vue": "^3.3.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^20.5.2",
|
|
61
|
+
"@vitejs/plugin-vue": "^4.2.3",
|
|
62
|
+
"typescript": "~5.1.6",
|
|
63
|
+
"vite": "^4.4.5",
|
|
64
|
+
"vue": "^3.3.4",
|
|
65
|
+
"vue-tsc": "^1.8.5"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
>
|
|
46
46
|
<button
|
|
47
47
|
class="tab-button"
|
|
48
|
+
:class="{
|
|
49
|
+
'tab-button-active': activeTab === tab.id,
|
|
50
|
+
'tab-button-disabled': tab.disabled,
|
|
51
|
+
}"
|
|
48
52
|
:disabled="tab.disabled"
|
|
49
53
|
:style="{
|
|
50
54
|
position: 'relative',
|
|
@@ -59,12 +63,13 @@
|
|
|
59
63
|
: 'none',
|
|
60
64
|
padding: computedStyleConfig.padding,
|
|
61
65
|
fontSize: computedStyleConfig.fontSize,
|
|
62
|
-
fontWeight: computedStyleConfig.fontWeight,
|
|
66
|
+
fontWeight: activeTab === tab.id ? '500' : computedStyleConfig.fontWeight,
|
|
63
67
|
fontFamily: computedTheme.fontFamily,
|
|
64
68
|
color: getTabTextColor(tab),
|
|
65
69
|
opacity: tab.disabled ? 0.5 : 1,
|
|
66
70
|
borderRadius: computedStyleConfig.borderRadius || '0',
|
|
67
71
|
whiteSpace: 'nowrap',
|
|
72
|
+
lineHeight: 1.25,
|
|
68
73
|
}"
|
|
69
74
|
@click="handleTabClick(tab, $event)"
|
|
70
75
|
@mouseenter="handleTabHover(tab)"
|
|
@@ -81,10 +86,10 @@
|
|
|
81
86
|
}"
|
|
82
87
|
v-html="tab.icon"
|
|
83
88
|
/>
|
|
84
|
-
|
|
89
|
+
|
|
85
90
|
<!-- 标签文本 -->
|
|
86
91
|
<span class="tab-text">{{ tab.name }}</span>
|
|
87
|
-
|
|
92
|
+
|
|
88
93
|
<!-- 激活指示器 -->
|
|
89
94
|
<span
|
|
90
95
|
v-if="activeTab === tab.id"
|
|
@@ -104,8 +109,8 @@
|
|
|
104
109
|
</template>
|
|
105
110
|
|
|
106
111
|
<script setup lang="ts">
|
|
107
|
-
import { ref, computed, onMounted, onUnmounted, nextTick } from
|
|
108
|
-
import type { SimpleTabsProps, SimpleTabsEvents, TabItem } from
|
|
112
|
+
import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
|
|
113
|
+
import type { SimpleTabsProps, SimpleTabsEvents, TabItem } from "../types";
|
|
109
114
|
import {
|
|
110
115
|
defaultTheme,
|
|
111
116
|
sizeConfigs,
|
|
@@ -114,11 +119,11 @@ import {
|
|
|
114
119
|
mergeConfig,
|
|
115
120
|
getResponsiveStyle,
|
|
116
121
|
getIndicatorStyle,
|
|
117
|
-
} from
|
|
122
|
+
} from "../types";
|
|
118
123
|
|
|
119
124
|
// Props定义
|
|
120
125
|
const props = withDefaults(defineProps<SimpleTabsProps>(), {
|
|
121
|
-
size:
|
|
126
|
+
size: "medium",
|
|
122
127
|
block: false,
|
|
123
128
|
centered: false,
|
|
124
129
|
scrollable: false,
|
|
@@ -128,28 +133,26 @@ const props = withDefaults(defineProps<SimpleTabsProps>(), {
|
|
|
128
133
|
const emit = defineEmits<SimpleTabsEvents>();
|
|
129
134
|
|
|
130
135
|
// 响应式状态
|
|
131
|
-
const activeTab = ref(props.defaultActive || props.tabs[0]?.id ||
|
|
136
|
+
const activeTab = ref(props.defaultActive || props.tabs[0]?.id || "");
|
|
132
137
|
const isMobile = ref(false);
|
|
133
138
|
const hoveredTab = ref<string | null>(null);
|
|
134
139
|
|
|
135
140
|
// 计算属性
|
|
136
|
-
const computedTheme = computed(() =>
|
|
137
|
-
mergeConfig(defaultTheme, props.theme)
|
|
138
|
-
);
|
|
141
|
+
const computedTheme = computed(() => mergeConfig(defaultTheme, props.theme));
|
|
139
142
|
|
|
140
|
-
const computedAnimation = computed(() =>
|
|
143
|
+
const computedAnimation = computed(() =>
|
|
141
144
|
mergeConfig(defaultAnimation, props.animation)
|
|
142
145
|
);
|
|
143
146
|
|
|
144
|
-
const computedResponsive = computed(() =>
|
|
147
|
+
const computedResponsive = computed(() =>
|
|
145
148
|
mergeConfig(defaultResponsive, props.responsive)
|
|
146
149
|
);
|
|
147
150
|
|
|
148
|
-
const baseStyleConfig = computed(() =>
|
|
151
|
+
const baseStyleConfig = computed(() =>
|
|
149
152
|
mergeConfig(sizeConfigs[props.size], props.style)
|
|
150
153
|
);
|
|
151
154
|
|
|
152
|
-
const computedStyleConfig = computed(() =>
|
|
155
|
+
const computedStyleConfig = computed(() =>
|
|
153
156
|
getResponsiveStyle(
|
|
154
157
|
isMobile.value && computedResponsive.value.enabled,
|
|
155
158
|
baseStyleConfig.value,
|
|
@@ -160,7 +163,8 @@ const computedStyleConfig = computed(() =>
|
|
|
160
163
|
// 工具方法
|
|
161
164
|
const checkMobile = () => {
|
|
162
165
|
if (computedResponsive.value.enabled) {
|
|
163
|
-
isMobile.value =
|
|
166
|
+
isMobile.value =
|
|
167
|
+
window.innerWidth <= computedResponsive.value.mobileBreakpoint;
|
|
164
168
|
}
|
|
165
169
|
};
|
|
166
170
|
|
|
@@ -168,15 +172,15 @@ const getTabTextColor = (tab: TabItem): string => {
|
|
|
168
172
|
if (tab.disabled) {
|
|
169
173
|
return computedTheme.value.inactiveTextColor;
|
|
170
174
|
}
|
|
171
|
-
|
|
175
|
+
|
|
172
176
|
if (activeTab.value === tab.id) {
|
|
173
177
|
return computedTheme.value.activeTextColor;
|
|
174
178
|
}
|
|
175
|
-
|
|
179
|
+
|
|
176
180
|
if (hoveredTab.value === tab.id) {
|
|
177
181
|
return computedTheme.value.hoverTextColor;
|
|
178
182
|
}
|
|
179
|
-
|
|
183
|
+
|
|
180
184
|
return computedTheme.value.inactiveTextColor;
|
|
181
185
|
};
|
|
182
186
|
|
|
@@ -191,14 +195,14 @@ const getIndicatorStyleComputed = () => {
|
|
|
191
195
|
// 事件处理
|
|
192
196
|
const handleTabClick = (tab: TabItem, event: Event) => {
|
|
193
197
|
if (tab.disabled) return;
|
|
194
|
-
|
|
198
|
+
|
|
195
199
|
activeTab.value = tab.id;
|
|
196
|
-
emit(
|
|
197
|
-
emit(
|
|
198
|
-
|
|
200
|
+
emit("tab-change", tab.id, tab);
|
|
201
|
+
emit("tab-click", tab.id, tab, event);
|
|
202
|
+
|
|
199
203
|
// 路由跳转逻辑(如果需要)
|
|
200
204
|
if (tab.route) {
|
|
201
|
-
console.log(
|
|
205
|
+
console.log("Navigate to:", tab.route);
|
|
202
206
|
// 这里可以集成 vue-router
|
|
203
207
|
}
|
|
204
208
|
};
|
|
@@ -206,13 +210,13 @@ const handleTabClick = (tab: TabItem, event: Event) => {
|
|
|
206
210
|
const handleTabHover = (tab: TabItem) => {
|
|
207
211
|
if (tab.disabled) return;
|
|
208
212
|
hoveredTab.value = tab.id;
|
|
209
|
-
emit(
|
|
213
|
+
emit("tab-hover", tab.id, tab);
|
|
210
214
|
};
|
|
211
215
|
|
|
212
216
|
const handleTabLeave = (tab: TabItem) => {
|
|
213
217
|
if (tab.disabled) return;
|
|
214
218
|
hoveredTab.value = null;
|
|
215
|
-
emit(
|
|
219
|
+
emit("tab-leave", tab.id, tab);
|
|
216
220
|
};
|
|
217
221
|
|
|
218
222
|
// 窗口尺寸变化监听
|
|
@@ -223,32 +227,32 @@ const handleResize = () => {
|
|
|
223
227
|
// 生命周期
|
|
224
228
|
onMounted(() => {
|
|
225
229
|
checkMobile();
|
|
226
|
-
window.addEventListener(
|
|
230
|
+
window.addEventListener("resize", handleResize);
|
|
227
231
|
});
|
|
228
232
|
|
|
229
233
|
onUnmounted(() => {
|
|
230
|
-
window.removeEventListener(
|
|
234
|
+
window.removeEventListener("resize", handleResize);
|
|
231
235
|
});
|
|
232
236
|
|
|
233
237
|
// 暴露给父组件
|
|
234
238
|
defineExpose({
|
|
235
239
|
activeTab,
|
|
236
240
|
setActiveTab: (tabId: string) => {
|
|
237
|
-
const tab = props.tabs.find(t => t.id === tabId);
|
|
241
|
+
const tab = props.tabs.find((t) => t.id === tabId);
|
|
238
242
|
if (tab && !tab.disabled) {
|
|
239
243
|
activeTab.value = tabId;
|
|
240
|
-
emit(
|
|
244
|
+
emit("tab-change", tabId, tab);
|
|
241
245
|
}
|
|
242
246
|
},
|
|
243
247
|
getActiveTab: () => {
|
|
244
|
-
return props.tabs.find(t => t.id === activeTab.value);
|
|
248
|
+
return props.tabs.find((t) => t.id === activeTab.value);
|
|
245
249
|
},
|
|
246
250
|
scrollToTab: (tabId: string) => {
|
|
247
251
|
if (!props.scrollable) return;
|
|
248
252
|
nextTick(() => {
|
|
249
253
|
const tabElement = document.querySelector(`[data-tab-id="${tabId}"]`);
|
|
250
254
|
if (tabElement) {
|
|
251
|
-
tabElement.scrollIntoView({ behavior:
|
|
255
|
+
tabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
|
|
252
256
|
}
|
|
253
257
|
});
|
|
254
258
|
},
|
|
@@ -283,6 +287,20 @@ defineExpose({
|
|
|
283
287
|
outline: none;
|
|
284
288
|
user-select: none;
|
|
285
289
|
-webkit-tap-highlight-color: transparent;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
text-decoration: none;
|
|
294
|
+
background: transparent;
|
|
295
|
+
border: 0px;
|
|
296
|
+
font-size: 16px;
|
|
297
|
+
font-weight: 400;
|
|
298
|
+
line-height: 1.25;
|
|
299
|
+
padding: 14px 12px;
|
|
300
|
+
position: relative;
|
|
301
|
+
color: v-bind("computedTheme.inactiveTextColor");
|
|
302
|
+
font-family: v-bind("computedTheme.fontFamily");
|
|
303
|
+
transition: color 0.2s ease;
|
|
286
304
|
}
|
|
287
305
|
|
|
288
306
|
.tab-button:focus {
|
|
@@ -290,10 +308,23 @@ defineExpose({
|
|
|
290
308
|
}
|
|
291
309
|
|
|
292
310
|
.tab-button:focus-visible {
|
|
293
|
-
outline: 2px solid v-bind(
|
|
311
|
+
outline: 2px solid v-bind("computedTheme.indicatorColor");
|
|
294
312
|
outline-offset: 2px;
|
|
295
313
|
}
|
|
296
314
|
|
|
315
|
+
.tab-button-active {
|
|
316
|
+
color: v-bind("computedTheme.activeTextColor");
|
|
317
|
+
font-weight: 500;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.tab-button-active::before {
|
|
321
|
+
position: absolute;
|
|
322
|
+
background: v-bind("computedTheme.indicatorColor");
|
|
323
|
+
height: 2px;
|
|
324
|
+
inset: auto 12px 0px;
|
|
325
|
+
content: "";
|
|
326
|
+
}
|
|
327
|
+
|
|
297
328
|
/* 移动端优化 */
|
|
298
329
|
.simple-tabs-container.is-mobile .tab-button {
|
|
299
330
|
-webkit-touch-callout: none;
|
|
@@ -311,6 +342,14 @@ defineExpose({
|
|
|
311
342
|
|
|
312
343
|
/* 响应式样式 */
|
|
313
344
|
@media (max-width: 768px) {
|
|
345
|
+
.simple-tabs-container.is-mobile .tab-button {
|
|
346
|
+
padding: 14px 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.simple-tabs-container.is-mobile .tab-button-active::before {
|
|
350
|
+
inset: auto 0px 0px;
|
|
351
|
+
}
|
|
352
|
+
|
|
314
353
|
.simple-tabs-container.is-mobile .tab-indicator {
|
|
315
354
|
left: 0 !important;
|
|
316
355
|
right: 0 !important;
|
|
@@ -324,7 +363,7 @@ defineExpose({
|
|
|
324
363
|
|
|
325
364
|
.tabs-scrollable::before,
|
|
326
365
|
.tabs-scrollable::after {
|
|
327
|
-
content:
|
|
366
|
+
content: "";
|
|
328
367
|
position: absolute;
|
|
329
368
|
top: 0;
|
|
330
369
|
bottom: 0;
|
|
@@ -335,12 +374,20 @@ defineExpose({
|
|
|
335
374
|
|
|
336
375
|
.tabs-scrollable::before {
|
|
337
376
|
left: 0;
|
|
338
|
-
background: linear-gradient(
|
|
377
|
+
background: linear-gradient(
|
|
378
|
+
to right,
|
|
379
|
+
v-bind("computedTheme.backgroundColor"),
|
|
380
|
+
transparent
|
|
381
|
+
);
|
|
339
382
|
}
|
|
340
383
|
|
|
341
384
|
.tabs-scrollable::after {
|
|
342
385
|
right: 0;
|
|
343
|
-
background: linear-gradient(
|
|
386
|
+
background: linear-gradient(
|
|
387
|
+
to left,
|
|
388
|
+
v-bind("computedTheme.backgroundColor"),
|
|
389
|
+
transparent
|
|
390
|
+
);
|
|
344
391
|
}
|
|
345
392
|
|
|
346
393
|
/* 不同尺寸的样式 */
|
|
@@ -390,7 +437,7 @@ defineExpose({
|
|
|
390
437
|
.tab-button {
|
|
391
438
|
border: 1px solid currentColor;
|
|
392
439
|
}
|
|
393
|
-
|
|
440
|
+
|
|
394
441
|
.tab-indicator {
|
|
395
442
|
border: 2px solid currentColor;
|
|
396
443
|
}
|
|
@@ -404,3 +451,4 @@ defineExpose({
|
|
|
404
451
|
}
|
|
405
452
|
}
|
|
406
453
|
</style>
|
|
454
|
+
|
package/src/index.ts
CHANGED
|
@@ -3,28 +3,29 @@ export { default as ConfigurableSimpleTabs } from './components/ConfigurableSimp
|
|
|
3
3
|
|
|
4
4
|
// 导出类型定义
|
|
5
5
|
export type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
TabItem,
|
|
7
|
+
TabSize,
|
|
8
|
+
IndicatorStyle,
|
|
9
|
+
SimpleTabsTheme,
|
|
10
|
+
StyleConfig,
|
|
11
|
+
AnimationConfig,
|
|
12
|
+
ResponsiveConfig,
|
|
13
|
+
SimpleTabsProps,
|
|
14
|
+
SimpleTabsEvents,
|
|
15
15
|
} from './types';
|
|
16
16
|
|
|
17
17
|
// 导出默认配置和工具函数
|
|
18
|
-
export {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
export {
|
|
19
|
+
defaultTheme,
|
|
20
|
+
sizeConfigs,
|
|
21
|
+
defaultAnimation,
|
|
22
|
+
defaultResponsive,
|
|
23
|
+
indicatorStyles,
|
|
24
|
+
getIndicatorStyle,
|
|
25
|
+
mergeConfig,
|
|
26
|
+
getResponsiveStyle,
|
|
27
27
|
} from './types';
|
|
28
28
|
|
|
29
29
|
// 默认导出
|
|
30
30
|
export { default } from './components/ConfigurableSimpleTabs.vue';
|
|
31
|
+
|