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/package.json CHANGED
@@ -1,67 +1,67 @@
1
- {
2
- "name": "vue-clean-tabs",
3
- "version": "1.0.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
- }
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 'vue';
108
- import type { SimpleTabsProps, SimpleTabsEvents, TabItem } from '../types';
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 '../types';
122
+ } from "../types";
118
123
 
119
124
  // Props定义
120
125
  const props = withDefaults(defineProps<SimpleTabsProps>(), {
121
- size: 'medium',
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 = window.innerWidth <= computedResponsive.value.mobileBreakpoint;
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('tab-change', tab.id, tab);
197
- emit('tab-click', tab.id, tab, event);
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('Navigate to:', tab.route);
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('tab-hover', tab.id, tab);
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('tab-leave', tab.id, tab);
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('resize', handleResize);
230
+ window.addEventListener("resize", handleResize);
227
231
  });
228
232
 
229
233
  onUnmounted(() => {
230
- window.removeEventListener('resize', handleResize);
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('tab-change', tabId, tab);
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: 'smooth', inline: 'center' });
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('computedTheme.indicatorColor');
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(to right, v-bind('computedTheme.backgroundColor'), transparent);
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(to left, v-bind('computedTheme.backgroundColor'), transparent);
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
- TabItem,
7
- TabSize,
8
- IndicatorStyle,
9
- SimpleTabsTheme,
10
- StyleConfig,
11
- AnimationConfig,
12
- ResponsiveConfig,
13
- SimpleTabsProps,
14
- SimpleTabsEvents,
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
- defaultTheme,
20
- sizeConfigs,
21
- defaultAnimation,
22
- defaultResponsive,
23
- indicatorStyles,
24
- getIndicatorStyle,
25
- mergeConfig,
26
- getResponsiveStyle,
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
+