vue-clean-tabs 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/types.ts","../src/components/ConfigurableSimpleTabs.vue"],"sourcesContent":["// 简洁标签页组件的类型定义\r\n\r\n// 标签项接口\r\nexport interface TabItem {\r\n id: string;\r\n name: string;\r\n disabled?: boolean; // 是否禁用\r\n route?: string; // 可选路由地址\r\n icon?: string; // 可选图标\r\n}\r\n\r\n// 尺寸类型\r\nexport type TabSize = 'small' | 'medium' | 'large';\r\n\r\n// 指示器样式类型\r\nexport type IndicatorStyle = 'line' | 'dot' | 'pill' | 'underline';\r\n\r\n// 主题配置接口\r\nexport interface SimpleTabsTheme {\r\n // 激活状态文字颜色\r\n activeTextColor: string;\r\n // 非激活状态文字颜色\r\n inactiveTextColor: string;\r\n // 悬浮状态文字颜色\r\n hoverTextColor: string;\r\n // 背景色\r\n backgroundColor: string;\r\n // 激活指示器颜色\r\n indicatorColor: string;\r\n // 字体系列\r\n fontFamily: string;\r\n // 边框颜色(如果需要)\r\n borderColor?: string;\r\n // 阴影(如果需要)\r\n boxShadow?: string;\r\n}\r\n\r\n// 样式配置接口\r\nexport interface StyleConfig {\r\n // 内边距\r\n padding: string;\r\n // 字体大小\r\n fontSize: string;\r\n // 字体粗细\r\n fontWeight: string;\r\n // 指示器高度\r\n indicatorHeight: string;\r\n // 指示器样式\r\n indicatorStyle: IndicatorStyle;\r\n // 圆角大小\r\n borderRadius?: string;\r\n // 标签间距\r\n gap?: string;\r\n}\r\n\r\n// 动画配置接口\r\nexport interface AnimationConfig {\r\n // 过渡时长\r\n transitionDuration: string;\r\n // 缓动函数\r\n easing: string;\r\n // 是否启用动画\r\n enabled: boolean;\r\n // 指示器动画时长\r\n indicatorTransition?: string;\r\n}\r\n\r\n// 响应式配置接口\r\nexport interface ResponsiveConfig {\r\n // 移动端断点\r\n mobileBreakpoint: number;\r\n // 移动端样式\r\n mobileStyle?: Partial<StyleConfig>;\r\n // 是否启用响应式\r\n enabled: boolean;\r\n}\r\n\r\n// 主组件Props接口\r\nexport interface SimpleTabsProps {\r\n // 标签页数据\r\n tabs: TabItem[];\r\n \r\n // 默认激活的标签ID\r\n defaultActive?: string;\r\n \r\n // 尺寸\r\n size?: TabSize;\r\n \r\n // 主题配置\r\n theme?: Partial<SimpleTabsTheme>;\r\n \r\n // 样式配置\r\n style?: Partial<StyleConfig>;\r\n \r\n // 动画配置\r\n animation?: Partial<AnimationConfig>;\r\n \r\n // 响应式配置\r\n responsive?: Partial<ResponsiveConfig>;\r\n \r\n // 自定义类名\r\n className?: string;\r\n \r\n // 自定义内联样式\r\n customStyle?: Record<string, any>;\r\n \r\n // 是否显示为块级元素\r\n block?: boolean;\r\n \r\n // 居中对齐\r\n centered?: boolean;\r\n \r\n // 是否可滚动(当标签过多时)\r\n scrollable?: boolean;\r\n}\r\n\r\n// 事件接口\r\nexport interface SimpleTabsEvents {\r\n 'tab-change': [tabId: string, tab: TabItem];\r\n 'tab-click': [tabId: string, tab: TabItem, event: Event];\r\n 'tab-hover': [tabId: string, tab: TabItem];\r\n 'tab-leave': [tabId: string, tab: TabItem];\r\n}\r\n\r\n// 默认主题\r\nexport const defaultTheme: SimpleTabsTheme = {\r\n activeTextColor: 'rgb(20, 23, 26)',\r\n inactiveTextColor: 'rgb(76, 82, 89)',\r\n hoverTextColor: 'rgb(55, 65, 81)',\r\n backgroundColor: '#ffffff',\r\n indicatorColor: 'rgb(20, 23, 26)',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"SF Pro Display\", \"Inter\", sans-serif',\r\n};\r\n\r\n// 尺寸配置\r\nexport const sizeConfigs: Record<TabSize, StyleConfig> = {\r\n small: {\r\n padding: '8px 10px',\r\n fontSize: '14px',\r\n fontWeight: '500',\r\n indicatorHeight: '2px',\r\n indicatorStyle: 'line',\r\n gap: '16px',\r\n },\r\n medium: {\r\n padding: '14px 12px',\r\n fontSize: '16px',\r\n fontWeight: '600',\r\n indicatorHeight: '2px',\r\n indicatorStyle: 'line',\r\n gap: '20px',\r\n },\r\n large: {\r\n padding: '16px 16px',\r\n fontSize: '18px',\r\n fontWeight: '700',\r\n indicatorHeight: '3px',\r\n indicatorStyle: 'line',\r\n gap: '24px',\r\n },\r\n};\r\n\r\n// 默认动画配置\r\nexport const defaultAnimation: AnimationConfig = {\r\n transitionDuration: '0.2s',\r\n easing: 'ease',\r\n enabled: true,\r\n indicatorTransition: '0.3s ease',\r\n};\r\n\r\n// 默认响应式配置\r\nexport const defaultResponsive: ResponsiveConfig = {\r\n mobileBreakpoint: 768,\r\n enabled: true,\r\n mobileStyle: {\r\n padding: '14px 0',\r\n gap: '16px',\r\n },\r\n};\r\n\r\n// 指示器样式映射\r\nexport const indicatorStyles: Record<IndicatorStyle, (color: string, height: string) => Record<string, string>> = {\r\n line: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '0',\r\n left: '12px',\r\n right: '12px',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '0',\r\n }),\r\n \r\n underline: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '0',\r\n left: '0',\r\n right: '0',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '0',\r\n }),\r\n \r\n dot: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '4px',\r\n left: '50%',\r\n transform: 'translateX(-50%)',\r\n width: height,\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '50%',\r\n }),\r\n \r\n pill: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '4px',\r\n left: '8px',\r\n right: '8px',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: height,\r\n }),\r\n};\r\n\r\n// 工具函数:获取指示器样式\r\nexport const getIndicatorStyle = (\r\n style: IndicatorStyle, \r\n color: string, \r\n height: string\r\n): Record<string, string> => {\r\n return indicatorStyles[style](color, height);\r\n};\r\n\r\n// 工具函数:合并配置\r\nexport const mergeConfig = <T extends Record<string, any>>(\r\n defaultConfig: T,\r\n userConfig?: Partial<T>\r\n): T => {\r\n return { ...defaultConfig, ...userConfig };\r\n};\r\n\r\n// 工具函数:获取响应式样式\r\nexport const getResponsiveStyle = (\r\n isMobile: boolean,\r\n baseStyle: StyleConfig,\r\n mobileStyle?: Partial<StyleConfig>\r\n): StyleConfig => {\r\n if (!isMobile || !mobileStyle) return baseStyle;\r\n return { ...baseStyle, ...mobileStyle };\r\n};\r\n","<template>\r\n <div\r\n class=\"simple-tabs-container\"\r\n :class=\"[\r\n `size-${props.size}`,\r\n {\r\n 'tabs-block': props.block,\r\n 'tabs-centered': props.centered,\r\n 'tabs-scrollable': props.scrollable,\r\n 'is-mobile': isMobile,\r\n },\r\n props.className,\r\n ]\"\r\n :style=\"{\r\n backgroundColor: computedTheme.backgroundColor,\r\n boxShadow: computedTheme.boxShadow,\r\n flexShrink: 0,\r\n ...props.customStyle,\r\n }\"\r\n >\r\n <nav>\r\n <ul\r\n class=\"tabs-list\"\r\n :class=\"{\r\n 'flex-wrap': props.scrollable && !isMobile,\r\n 'overflow-x-auto': props.scrollable && isMobile,\r\n }\"\r\n :style=\"{\r\n display: 'flex',\r\n gap: computedStyleConfig.gap,\r\n justifyContent: props.centered ? 'center' : 'flex-start',\r\n margin: 0,\r\n padding: 0,\r\n listStyle: 'none',\r\n }\"\r\n >\r\n <li\r\n v-for=\"tab in props.tabs\"\r\n :key=\"tab.id\"\r\n class=\"tab-item\"\r\n :class=\"{\r\n 'tab-active': activeTab === tab.id,\r\n 'tab-disabled': tab.disabled,\r\n }\"\r\n >\r\n <button\r\n class=\"tab-button\"\r\n :disabled=\"tab.disabled\"\r\n :style=\"{\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center',\r\n background: 'none',\r\n border: 'none',\r\n cursor: tab.disabled ? 'not-allowed' : 'pointer',\r\n textDecoration: 'none',\r\n transition: computedAnimation.enabled\r\n ? `color ${computedAnimation.transitionDuration} ${computedAnimation.easing}`\r\n : 'none',\r\n padding: computedStyleConfig.padding,\r\n fontSize: computedStyleConfig.fontSize,\r\n fontWeight: computedStyleConfig.fontWeight,\r\n fontFamily: computedTheme.fontFamily,\r\n color: getTabTextColor(tab),\r\n opacity: tab.disabled ? 0.5 : 1,\r\n borderRadius: computedStyleConfig.borderRadius || '0',\r\n whiteSpace: 'nowrap',\r\n }\"\r\n @click=\"handleTabClick(tab, $event)\"\r\n @mouseenter=\"handleTabHover(tab)\"\r\n @mouseleave=\"handleTabLeave(tab)\"\r\n >\r\n <!-- 图标 -->\r\n <span\r\n v-if=\"tab.icon\"\r\n class=\"tab-icon\"\r\n :style=\"{\r\n marginRight: '8px',\r\n fontSize: '1em',\r\n lineHeight: 1,\r\n }\"\r\n v-html=\"tab.icon\"\r\n />\r\n \r\n <!-- 标签文本 -->\r\n <span class=\"tab-text\">{{ tab.name }}</span>\r\n \r\n <!-- 激活指示器 -->\r\n <span\r\n v-if=\"activeTab === tab.id\"\r\n class=\"tab-indicator\"\r\n :style=\"{\r\n ...getIndicatorStyleComputed(),\r\n transition: computedAnimation.enabled\r\n ? `all ${computedAnimation.indicatorTransition || computedAnimation.transitionDuration} ${computedAnimation.easing}`\r\n : 'none',\r\n }\"\r\n />\r\n </button>\r\n </li>\r\n </ul>\r\n </nav>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';\r\nimport type { SimpleTabsProps, SimpleTabsEvents, TabItem } from '../types';\r\nimport {\r\n defaultTheme,\r\n sizeConfigs,\r\n defaultAnimation,\r\n defaultResponsive,\r\n mergeConfig,\r\n getResponsiveStyle,\r\n getIndicatorStyle,\r\n} from '../types';\r\n\r\n// Props定义\r\nconst props = withDefaults(defineProps<SimpleTabsProps>(), {\r\n size: 'medium',\r\n block: false,\r\n centered: false,\r\n scrollable: false,\r\n});\r\n\r\n// Events定义\r\nconst emit = defineEmits<SimpleTabsEvents>();\r\n\r\n// 响应式状态\r\nconst activeTab = ref(props.defaultActive || props.tabs[0]?.id || '');\r\nconst isMobile = ref(false);\r\nconst hoveredTab = ref<string | null>(null);\r\n\r\n// 计算属性\r\nconst computedTheme = computed(() => \r\n mergeConfig(defaultTheme, props.theme)\r\n);\r\n\r\nconst computedAnimation = computed(() => \r\n mergeConfig(defaultAnimation, props.animation)\r\n);\r\n\r\nconst computedResponsive = computed(() => \r\n mergeConfig(defaultResponsive, props.responsive)\r\n);\r\n\r\nconst baseStyleConfig = computed(() => \r\n mergeConfig(sizeConfigs[props.size], props.style)\r\n);\r\n\r\nconst computedStyleConfig = computed(() => \r\n getResponsiveStyle(\r\n isMobile.value && computedResponsive.value.enabled,\r\n baseStyleConfig.value,\r\n computedResponsive.value.mobileStyle\r\n )\r\n);\r\n\r\n// 工具方法\r\nconst checkMobile = () => {\r\n if (computedResponsive.value.enabled) {\r\n isMobile.value = window.innerWidth <= computedResponsive.value.mobileBreakpoint;\r\n }\r\n};\r\n\r\nconst getTabTextColor = (tab: TabItem): string => {\r\n if (tab.disabled) {\r\n return computedTheme.value.inactiveTextColor;\r\n }\r\n \r\n if (activeTab.value === tab.id) {\r\n return computedTheme.value.activeTextColor;\r\n }\r\n \r\n if (hoveredTab.value === tab.id) {\r\n return computedTheme.value.hoverTextColor;\r\n }\r\n \r\n return computedTheme.value.inactiveTextColor;\r\n};\r\n\r\nconst getIndicatorStyleComputed = () => {\r\n return getIndicatorStyle(\r\n computedStyleConfig.value.indicatorStyle,\r\n computedTheme.value.indicatorColor,\r\n computedStyleConfig.value.indicatorHeight\r\n );\r\n};\r\n\r\n// 事件处理\r\nconst handleTabClick = (tab: TabItem, event: Event) => {\r\n if (tab.disabled) return;\r\n \r\n activeTab.value = tab.id;\r\n emit('tab-change', tab.id, tab);\r\n emit('tab-click', tab.id, tab, event);\r\n \r\n // 路由跳转逻辑(如果需要)\r\n if (tab.route) {\r\n console.log('Navigate to:', tab.route);\r\n // 这里可以集成 vue-router\r\n }\r\n};\r\n\r\nconst handleTabHover = (tab: TabItem) => {\r\n if (tab.disabled) return;\r\n hoveredTab.value = tab.id;\r\n emit('tab-hover', tab.id, tab);\r\n};\r\n\r\nconst handleTabLeave = (tab: TabItem) => {\r\n if (tab.disabled) return;\r\n hoveredTab.value = null;\r\n emit('tab-leave', tab.id, tab);\r\n};\r\n\r\n// 窗口尺寸变化监听\r\nconst handleResize = () => {\r\n checkMobile();\r\n};\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n checkMobile();\r\n window.addEventListener('resize', handleResize);\r\n});\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener('resize', handleResize);\r\n});\r\n\r\n// 暴露给父组件\r\ndefineExpose({\r\n activeTab,\r\n setActiveTab: (tabId: string) => {\r\n const tab = props.tabs.find(t => t.id === tabId);\r\n if (tab && !tab.disabled) {\r\n activeTab.value = tabId;\r\n emit('tab-change', tabId, tab);\r\n }\r\n },\r\n getActiveTab: () => {\r\n return props.tabs.find(t => t.id === activeTab.value);\r\n },\r\n scrollToTab: (tabId: string) => {\r\n if (!props.scrollable) return;\r\n nextTick(() => {\r\n const tabElement = document.querySelector(`[data-tab-id=\"${tabId}\"]`);\r\n if (tabElement) {\r\n tabElement.scrollIntoView({ behavior: 'smooth', inline: 'center' });\r\n }\r\n });\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.simple-tabs-container {\r\n width: 100%;\r\n}\r\n\r\n.simple-tabs-container.tabs-block {\r\n display: block;\r\n}\r\n\r\n.simple-tabs-container.tabs-scrollable .tabs-list {\r\n overflow-x: auto;\r\n scrollbar-width: none; /* Firefox */\r\n -ms-overflow-style: none; /* IE and Edge */\r\n}\r\n\r\n.simple-tabs-container.tabs-scrollable .tabs-list::-webkit-scrollbar {\r\n display: none; /* Chrome, Safari, Opera */\r\n}\r\n\r\n.tab-item {\r\n flex-shrink: 0;\r\n position: relative;\r\n}\r\n\r\n.tab-button {\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.tab-button:focus {\r\n outline: none;\r\n}\r\n\r\n.tab-button:focus-visible {\r\n outline: 2px solid v-bind('computedTheme.indicatorColor');\r\n outline-offset: 2px;\r\n}\r\n\r\n/* 移动端优化 */\r\n.simple-tabs-container.is-mobile .tab-button {\r\n -webkit-touch-callout: none;\r\n -webkit-user-select: none;\r\n -khtml-user-select: none;\r\n -moz-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n/* 禁用状态样式 */\r\n.tab-disabled .tab-button {\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 响应式样式 */\r\n@media (max-width: 768px) {\r\n .simple-tabs-container.is-mobile .tab-indicator {\r\n left: 0 !important;\r\n right: 0 !important;\r\n }\r\n}\r\n\r\n/* 滚动优化 */\r\n.tabs-scrollable {\r\n position: relative;\r\n}\r\n\r\n.tabs-scrollable::before,\r\n.tabs-scrollable::after {\r\n content: '';\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n width: 20px;\r\n pointer-events: none;\r\n z-index: 1;\r\n}\r\n\r\n.tabs-scrollable::before {\r\n left: 0;\r\n background: linear-gradient(to right, v-bind('computedTheme.backgroundColor'), transparent);\r\n}\r\n\r\n.tabs-scrollable::after {\r\n right: 0;\r\n background: linear-gradient(to left, v-bind('computedTheme.backgroundColor'), transparent);\r\n}\r\n\r\n/* 不同尺寸的样式 */\r\n.size-small .tab-button {\r\n min-height: 32px;\r\n}\r\n\r\n.size-medium .tab-button {\r\n min-height: 40px;\r\n}\r\n\r\n.size-large .tab-button {\r\n min-height: 48px;\r\n}\r\n\r\n/* 居中对齐样式 */\r\n.tabs-centered .tabs-list {\r\n justify-content: center;\r\n}\r\n\r\n/* 图标样式 */\r\n.tab-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.tab-icon svg {\r\n width: 1em;\r\n height: 1em;\r\n fill: currentColor;\r\n}\r\n\r\n/* 指示器样式基类 */\r\n.tab-indicator {\r\n pointer-events: none;\r\n}\r\n\r\n/* 动画性能优化 */\r\n.tab-button,\r\n.tab-indicator {\r\n will-change: color, background-color, transform;\r\n}\r\n\r\n/* 高对比度模式支持 */\r\n@media (prefers-contrast: high) {\r\n .tab-button {\r\n border: 1px solid currentColor;\r\n }\r\n \r\n .tab-indicator {\r\n border: 2px solid currentColor;\r\n }\r\n}\r\n\r\n/* 减少动画模式支持 */\r\n@media (prefers-reduced-motion: reduce) {\r\n .tab-button,\r\n .tab-indicator {\r\n transition: none !important;\r\n }\r\n}\r\n</style>\r\n"],"names":["defaultTheme","sizeConfigs","defaultAnimation","defaultResponsive","indicatorStyles","color","height","getIndicatorStyle","style","mergeConfig","defaultConfig","userConfig","getResponsiveStyle","isMobile","baseStyle","mobileStyle","props","__props","emit","__emit","activeTab","ref","_a","hoveredTab","computedTheme","computed","computedAnimation","computedResponsive","baseStyleConfig","computedStyleConfig","checkMobile","getTabTextColor","tab","getIndicatorStyleComputed","handleTabClick","event","handleTabHover","handleTabLeave","handleResize","onMounted","onUnmounted","__expose","tabId","t","nextTick","tabElement","_createElementBlock","_normalizeStyle","_createElementVNode","_openBlock","_Fragment","_renderList","$event","_hoisted_3","_toDisplayString"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(l,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(l=typeof globalThis<"u"?globalThis:l||self,e(l.VueSimpleTabs={},l.Vue))})(this,function(l,e){"use strict";const h={activeTextColor:"rgb(20, 23, 26)",inactiveTextColor:"rgb(76, 82, 89)",hoverTextColor:"rgb(55, 65, 81)",backgroundColor:"#ffffff",indicatorColor:"rgb(20, 23, 26)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", "SF Pro Display", "Inter", sans-serif'},x={small:{padding:"8px 10px",fontSize:"14px",fontWeight:"500",indicatorHeight:"2px",indicatorStyle:"line",gap:"16px"},medium:{padding:"14px 12px",fontSize:"16px",fontWeight:"600",indicatorHeight:"2px",indicatorStyle:"line",gap:"20px"},large:{padding:"16px 16px",fontSize:"18px",fontWeight:"700",indicatorHeight:"3px",indicatorStyle:"line",gap:"24px"}},S={transitionDuration:"0.2s",easing:"ease",enabled:!0,indicatorTransition:"0.3s ease"},C={mobileBreakpoint:768,enabled:!0,mobileStyle:{padding:"14px 0",gap:"16px"}},k={line:(i,n)=>({position:"absolute",bottom:"0",left:"12px",right:"12px",height:n,backgroundColor:i,borderRadius:"0"}),underline:(i,n)=>({position:"absolute",bottom:"0",left:"0",right:"0",height:n,backgroundColor:i,borderRadius:"0"}),dot:(i,n)=>({position:"absolute",bottom:"4px",left:"50%",transform:"translateX(-50%)",width:n,height:n,backgroundColor:i,borderRadius:"50%"}),pill:(i,n)=>({position:"absolute",bottom:"4px",left:"8px",right:"8px",height:n,backgroundColor:i,borderRadius:n})},T=(i,n,s)=>k[i](n,s),m=(i,n)=>({...i,...n}),_=(i,n,s)=>!i||!s?n:{...n,...s},E=["disabled","onClick","onMouseenter","onMouseleave"],M=["innerHTML"],V={class:"tab-text"},N=e.defineComponent({__name:"ConfigurableSimpleTabs",props:{tabs:{},defaultActive:{},size:{default:"medium"},theme:{},style:{},animation:{},responsive:{},className:{},customStyle:{},block:{type:Boolean,default:!1},centered:{type:Boolean,default:!1},scrollable:{type:Boolean,default:!1}},emits:["tab-change","tab-click","tab-hover","tab-leave"],setup(i,{expose:n,emit:s}){var R;e.useCssVars(t=>({"4e65487a":r.value.indicatorColor,"25a898f4":r.value.backgroundColor}));const o=i,c=s,u=e.ref(o.defaultActive||((R=o.tabs[0])==null?void 0:R.id)||""),b=e.ref(!1),v=e.ref(null),r=e.computed(()=>m(h,o.theme)),p=e.computed(()=>m(S,o.animation)),g=e.computed(()=>m(C,o.responsive)),H=e.computed(()=>m(x[o.size],o.style)),f=e.computed(()=>_(b.value&&g.value.enabled,H.value,g.value.mobileStyle)),B=()=>{g.value.enabled&&(b.value=window.innerWidth<=g.value.mobileBreakpoint)},$=t=>t.disabled?r.value.inactiveTextColor:u.value===t.id?r.value.activeTextColor:v.value===t.id?r.value.hoverTextColor:r.value.inactiveTextColor,A=()=>T(f.value.indicatorStyle,r.value.indicatorColor,f.value.indicatorHeight),D=(t,d)=>{t.disabled||(u.value=t.id,c("tab-change",t.id,t),c("tab-click",t.id,t,d),t.route&&console.log("Navigate to:",t.route))},F=t=>{t.disabled||(v.value=t.id,c("tab-hover",t.id,t))},I=t=>{t.disabled||(v.value=null,c("tab-leave",t.id,t))},w=()=>{B()};return e.onMounted(()=>{B(),window.addEventListener("resize",w)}),e.onUnmounted(()=>{window.removeEventListener("resize",w)}),n({activeTab:u,setActiveTab:t=>{const d=o.tabs.find(a=>a.id===t);d&&!d.disabled&&(u.value=t,c("tab-change",t,d))},getActiveTab:()=>o.tabs.find(t=>t.id===u.value),scrollToTab:t=>{o.scrollable&&e.nextTick(()=>{const d=document.querySelector(`[data-tab-id="${t}"]`);d&&d.scrollIntoView({behavior:"smooth",inline:"center"})})}}),(t,d)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["simple-tabs-container",[`size-${o.size}`,{"tabs-block":o.block,"tabs-centered":o.centered,"tabs-scrollable":o.scrollable,"is-mobile":b.value},o.className]]),style:e.normalizeStyle({backgroundColor:r.value.backgroundColor,boxShadow:r.value.boxShadow,flexShrink:0,...o.customStyle})},[e.createElementVNode("nav",null,[e.createElementVNode("ul",{class:e.normalizeClass(["tabs-list",{"flex-wrap":o.scrollable&&!b.value,"overflow-x-auto":o.scrollable&&b.value}]),style:e.normalizeStyle({display:"flex",gap:f.value.gap,justifyContent:o.centered?"center":"flex-start",margin:0,padding:0,listStyle:"none"})},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(o.tabs,a=>(e.openBlock(),e.createElementBlock("li",{key:a.id,class:e.normalizeClass(["tab-item",{"tab-active":u.value===a.id,"tab-disabled":a.disabled}])},[e.createElementVNode("button",{class:"tab-button",disabled:a.disabled,style:e.normalizeStyle({position:"relative",display:"flex",alignItems:"center",background:"none",border:"none",cursor:a.disabled?"not-allowed":"pointer",textDecoration:"none",transition:p.value.enabled?`color ${p.value.transitionDuration} ${p.value.easing}`:"none",padding:f.value.padding,fontSize:f.value.fontSize,fontWeight:f.value.fontWeight,fontFamily:r.value.fontFamily,color:$(a),opacity:a.disabled?.5:1,borderRadius:f.value.borderRadius||"0",whiteSpace:"nowrap"}),onClick:y=>D(a,y),onMouseenter:y=>F(a),onMouseleave:y=>I(a)},[a.icon?(e.openBlock(),e.createElementBlock("span",{key:0,class:"tab-icon",style:{marginRight:"8px",fontSize:"1em",lineHeight:1},innerHTML:a.icon},null,8,M)):e.createCommentVNode("",!0),e.createElementVNode("span",V,e.toDisplayString(a.name),1),u.value===a.id?(e.openBlock(),e.createElementBlock("span",{key:1,class:"tab-indicator",style:e.normalizeStyle({...A(),transition:p.value.enabled?`all ${p.value.indicatorTransition||p.value.transitionDuration} ${p.value.easing}`:"none"})},null,4)):e.createCommentVNode("",!0)],44,E)],2))),128))],6)])],6))}}),L="",z=((i,n)=>{const s=i.__vccOpts||i;for(const[o,c]of n)s[o]=c;return s})(N,[["__scopeId","data-v-166844ee"]]);l.ConfigurableSimpleTabs=z,l.default=z,l.defaultAnimation=S,l.defaultResponsive=C,l.defaultTheme=h,l.getIndicatorStyle=T,l.getResponsiveStyle=_,l.indicatorStyles=k,l.mergeConfig=m,l.sizeConfigs=x,Object.defineProperties(l,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/types.ts","../src/components/ConfigurableSimpleTabs.vue"],"sourcesContent":["// 简洁标签页组件的类型定义\r\n\r\n// 标签项接口\r\nexport interface TabItem {\r\n id: string;\r\n name: string;\r\n disabled?: boolean; // 是否禁用\r\n route?: string; // 可选路由地址\r\n icon?: string; // 可选图标\r\n}\r\n\r\n// 尺寸类型\r\nexport type TabSize = 'small' | 'medium' | 'large';\r\n\r\n// 指示器样式类型\r\nexport type IndicatorStyle = 'line' | 'dot' | 'pill' | 'underline';\r\n\r\n// 主题配置接口\r\nexport interface SimpleTabsTheme {\r\n // 激活状态文字颜色\r\n activeTextColor: string;\r\n // 非激活状态文字颜色\r\n inactiveTextColor: string;\r\n // 悬浮状态文字颜色\r\n hoverTextColor: string;\r\n // 背景色\r\n backgroundColor: string;\r\n // 激活指示器颜色\r\n indicatorColor: string;\r\n // 字体系列\r\n fontFamily: string;\r\n // 边框颜色(如果需要)\r\n borderColor?: string;\r\n // 阴影(如果需要)\r\n boxShadow?: string;\r\n}\r\n\r\n// 样式配置接口\r\nexport interface StyleConfig {\r\n // 内边距\r\n padding: string;\r\n // 字体大小\r\n fontSize: string;\r\n // 字体粗细\r\n fontWeight: string;\r\n // 指示器高度\r\n indicatorHeight: string;\r\n // 指示器样式\r\n indicatorStyle: IndicatorStyle;\r\n // 圆角大小\r\n borderRadius?: string;\r\n // 标签间距\r\n gap?: string;\r\n}\r\n\r\n// 动画配置接口\r\nexport interface AnimationConfig {\r\n // 过渡时长\r\n transitionDuration: string;\r\n // 缓动函数\r\n easing: string;\r\n // 是否启用动画\r\n enabled: boolean;\r\n // 指示器动画时长\r\n indicatorTransition?: string;\r\n}\r\n\r\n// 响应式配置接口\r\nexport interface ResponsiveConfig {\r\n // 移动端断点\r\n mobileBreakpoint: number;\r\n // 移动端样式\r\n mobileStyle?: Partial<StyleConfig>;\r\n // 是否启用响应式\r\n enabled: boolean;\r\n}\r\n\r\n// 主组件Props接口\r\nexport interface SimpleTabsProps {\r\n // 标签页数据\r\n tabs: TabItem[];\r\n \r\n // 默认激活的标签ID\r\n defaultActive?: string;\r\n \r\n // 尺寸\r\n size?: TabSize;\r\n \r\n // 主题配置\r\n theme?: Partial<SimpleTabsTheme>;\r\n \r\n // 样式配置\r\n style?: Partial<StyleConfig>;\r\n \r\n // 动画配置\r\n animation?: Partial<AnimationConfig>;\r\n \r\n // 响应式配置\r\n responsive?: Partial<ResponsiveConfig>;\r\n \r\n // 自定义类名\r\n className?: string;\r\n \r\n // 自定义内联样式\r\n customStyle?: Record<string, any>;\r\n \r\n // 是否显示为块级元素\r\n block?: boolean;\r\n \r\n // 居中对齐\r\n centered?: boolean;\r\n \r\n // 是否可滚动(当标签过多时)\r\n scrollable?: boolean;\r\n}\r\n\r\n// 事件接口\r\nexport interface SimpleTabsEvents {\r\n 'tab-change': [tabId: string, tab: TabItem];\r\n 'tab-click': [tabId: string, tab: TabItem, event: Event];\r\n 'tab-hover': [tabId: string, tab: TabItem];\r\n 'tab-leave': [tabId: string, tab: TabItem];\r\n}\r\n\r\n// 默认主题\r\nexport const defaultTheme: SimpleTabsTheme = {\r\n activeTextColor: 'rgb(20, 23, 26)',\r\n inactiveTextColor: 'rgb(76, 82, 89)',\r\n hoverTextColor: 'rgb(55, 65, 81)',\r\n backgroundColor: '#ffffff',\r\n indicatorColor: 'rgb(20, 23, 26)',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"SF Pro Display\", \"Inter\", sans-serif',\r\n};\r\n\r\n// 尺寸配置\r\nexport const sizeConfigs: Record<TabSize, StyleConfig> = {\r\n small: {\r\n padding: '8px 10px',\r\n fontSize: '14px',\r\n fontWeight: '500',\r\n indicatorHeight: '2px',\r\n indicatorStyle: 'line',\r\n gap: '16px',\r\n },\r\n medium: {\r\n padding: '14px 12px',\r\n fontSize: '16px',\r\n fontWeight: '600',\r\n indicatorHeight: '2px',\r\n indicatorStyle: 'line',\r\n gap: '20px',\r\n },\r\n large: {\r\n padding: '16px 16px',\r\n fontSize: '18px',\r\n fontWeight: '700',\r\n indicatorHeight: '3px',\r\n indicatorStyle: 'line',\r\n gap: '24px',\r\n },\r\n};\r\n\r\n// 默认动画配置\r\nexport const defaultAnimation: AnimationConfig = {\r\n transitionDuration: '0.2s',\r\n easing: 'ease',\r\n enabled: true,\r\n indicatorTransition: '0.3s ease',\r\n};\r\n\r\n// 默认响应式配置\r\nexport const defaultResponsive: ResponsiveConfig = {\r\n mobileBreakpoint: 768,\r\n enabled: true,\r\n mobileStyle: {\r\n padding: '14px 0',\r\n gap: '16px',\r\n },\r\n};\r\n\r\n// 指示器样式映射\r\nexport const indicatorStyles: Record<IndicatorStyle, (color: string, height: string) => Record<string, string>> = {\r\n line: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '0',\r\n left: '12px',\r\n right: '12px',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '0',\r\n }),\r\n \r\n underline: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '0',\r\n left: '0',\r\n right: '0',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '0',\r\n }),\r\n \r\n dot: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '4px',\r\n left: '50%',\r\n transform: 'translateX(-50%)',\r\n width: height,\r\n height,\r\n backgroundColor: color,\r\n borderRadius: '50%',\r\n }),\r\n \r\n pill: (color: string, height: string) => ({\r\n position: 'absolute',\r\n bottom: '4px',\r\n left: '8px',\r\n right: '8px',\r\n height,\r\n backgroundColor: color,\r\n borderRadius: height,\r\n }),\r\n};\r\n\r\n// 工具函数:获取指示器样式\r\nexport const getIndicatorStyle = (\r\n style: IndicatorStyle, \r\n color: string, \r\n height: string\r\n): Record<string, string> => {\r\n return indicatorStyles[style](color, height);\r\n};\r\n\r\n// 工具函数:合并配置\r\nexport const mergeConfig = <T extends Record<string, any>>(\r\n defaultConfig: T,\r\n userConfig?: Partial<T>\r\n): T => {\r\n return { ...defaultConfig, ...userConfig };\r\n};\r\n\r\n// 工具函数:获取响应式样式\r\nexport const getResponsiveStyle = (\r\n isMobile: boolean,\r\n baseStyle: StyleConfig,\r\n mobileStyle?: Partial<StyleConfig>\r\n): StyleConfig => {\r\n if (!isMobile || !mobileStyle) return baseStyle;\r\n return { ...baseStyle, ...mobileStyle };\r\n};\r\n","<template>\r\n <div\r\n class=\"simple-tabs-container\"\r\n :class=\"[\r\n `size-${props.size}`,\r\n {\r\n 'tabs-block': props.block,\r\n 'tabs-centered': props.centered,\r\n 'tabs-scrollable': props.scrollable,\r\n 'is-mobile': isMobile,\r\n },\r\n props.className,\r\n ]\"\r\n :style=\"{\r\n backgroundColor: computedTheme.backgroundColor,\r\n boxShadow: computedTheme.boxShadow,\r\n flexShrink: 0,\r\n ...props.customStyle,\r\n }\"\r\n >\r\n <nav>\r\n <ul\r\n class=\"tabs-list\"\r\n :class=\"{\r\n 'flex-wrap': props.scrollable && !isMobile,\r\n 'overflow-x-auto': props.scrollable && isMobile,\r\n }\"\r\n :style=\"{\r\n display: 'flex',\r\n gap: computedStyleConfig.gap,\r\n justifyContent: props.centered ? 'center' : 'flex-start',\r\n margin: 0,\r\n padding: 0,\r\n listStyle: 'none',\r\n }\"\r\n >\r\n <li\r\n v-for=\"tab in props.tabs\"\r\n :key=\"tab.id\"\r\n class=\"tab-item\"\r\n :class=\"{\r\n 'tab-active': activeTab === tab.id,\r\n 'tab-disabled': tab.disabled,\r\n }\"\r\n >\r\n <button\r\n class=\"tab-button\"\r\n :disabled=\"tab.disabled\"\r\n :style=\"{\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center',\r\n background: 'none',\r\n border: 'none',\r\n cursor: tab.disabled ? 'not-allowed' : 'pointer',\r\n textDecoration: 'none',\r\n transition: computedAnimation.enabled\r\n ? `color ${computedAnimation.transitionDuration} ${computedAnimation.easing}`\r\n : 'none',\r\n padding: computedStyleConfig.padding,\r\n fontSize: computedStyleConfig.fontSize,\r\n fontWeight: computedStyleConfig.fontWeight,\r\n fontFamily: computedTheme.fontFamily,\r\n color: getTabTextColor(tab),\r\n opacity: tab.disabled ? 0.5 : 1,\r\n borderRadius: computedStyleConfig.borderRadius || '0',\r\n whiteSpace: 'nowrap',\r\n }\"\r\n @click=\"handleTabClick(tab, $event)\"\r\n @mouseenter=\"handleTabHover(tab)\"\r\n @mouseleave=\"handleTabLeave(tab)\"\r\n >\r\n <!-- 图标 -->\r\n <span\r\n v-if=\"tab.icon\"\r\n class=\"tab-icon\"\r\n :style=\"{\r\n marginRight: '8px',\r\n fontSize: '1em',\r\n lineHeight: 1,\r\n }\"\r\n v-html=\"tab.icon\"\r\n />\r\n \r\n <!-- 标签文本 -->\r\n <span class=\"tab-text\">{{ tab.name }}</span>\r\n \r\n <!-- 激活指示器 -->\r\n <span\r\n v-if=\"activeTab === tab.id\"\r\n class=\"tab-indicator\"\r\n :style=\"{\r\n ...getIndicatorStyleComputed(),\r\n transition: computedAnimation.enabled\r\n ? `all ${computedAnimation.indicatorTransition || computedAnimation.transitionDuration} ${computedAnimation.easing}`\r\n : 'none',\r\n }\"\r\n />\r\n </button>\r\n </li>\r\n </ul>\r\n </nav>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';\r\nimport type { SimpleTabsProps, SimpleTabsEvents, TabItem } from '../types';\r\nimport {\r\n defaultTheme,\r\n sizeConfigs,\r\n defaultAnimation,\r\n defaultResponsive,\r\n mergeConfig,\r\n getResponsiveStyle,\r\n getIndicatorStyle,\r\n} from '../types';\r\n\r\n// Props定义\r\nconst props = withDefaults(defineProps<SimpleTabsProps>(), {\r\n size: 'medium',\r\n block: false,\r\n centered: false,\r\n scrollable: false,\r\n});\r\n\r\n// Events定义\r\nconst emit = defineEmits<SimpleTabsEvents>();\r\n\r\n// 响应式状态\r\nconst activeTab = ref(props.defaultActive || props.tabs[0]?.id || '');\r\nconst isMobile = ref(false);\r\nconst hoveredTab = ref<string | null>(null);\r\n\r\n// 计算属性\r\nconst computedTheme = computed(() => \r\n mergeConfig(defaultTheme, props.theme)\r\n);\r\n\r\nconst computedAnimation = computed(() => \r\n mergeConfig(defaultAnimation, props.animation)\r\n);\r\n\r\nconst computedResponsive = computed(() => \r\n mergeConfig(defaultResponsive, props.responsive)\r\n);\r\n\r\nconst baseStyleConfig = computed(() => \r\n mergeConfig(sizeConfigs[props.size], props.style)\r\n);\r\n\r\nconst computedStyleConfig = computed(() => \r\n getResponsiveStyle(\r\n isMobile.value && computedResponsive.value.enabled,\r\n baseStyleConfig.value,\r\n computedResponsive.value.mobileStyle\r\n )\r\n);\r\n\r\n// 工具方法\r\nconst checkMobile = () => {\r\n if (computedResponsive.value.enabled) {\r\n isMobile.value = window.innerWidth <= computedResponsive.value.mobileBreakpoint;\r\n }\r\n};\r\n\r\nconst getTabTextColor = (tab: TabItem): string => {\r\n if (tab.disabled) {\r\n return computedTheme.value.inactiveTextColor;\r\n }\r\n \r\n if (activeTab.value === tab.id) {\r\n return computedTheme.value.activeTextColor;\r\n }\r\n \r\n if (hoveredTab.value === tab.id) {\r\n return computedTheme.value.hoverTextColor;\r\n }\r\n \r\n return computedTheme.value.inactiveTextColor;\r\n};\r\n\r\nconst getIndicatorStyleComputed = () => {\r\n return getIndicatorStyle(\r\n computedStyleConfig.value.indicatorStyle,\r\n computedTheme.value.indicatorColor,\r\n computedStyleConfig.value.indicatorHeight\r\n );\r\n};\r\n\r\n// 事件处理\r\nconst handleTabClick = (tab: TabItem, event: Event) => {\r\n if (tab.disabled) return;\r\n \r\n activeTab.value = tab.id;\r\n emit('tab-change', tab.id, tab);\r\n emit('tab-click', tab.id, tab, event);\r\n \r\n // 路由跳转逻辑(如果需要)\r\n if (tab.route) {\r\n console.log('Navigate to:', tab.route);\r\n // 这里可以集成 vue-router\r\n }\r\n};\r\n\r\nconst handleTabHover = (tab: TabItem) => {\r\n if (tab.disabled) return;\r\n hoveredTab.value = tab.id;\r\n emit('tab-hover', tab.id, tab);\r\n};\r\n\r\nconst handleTabLeave = (tab: TabItem) => {\r\n if (tab.disabled) return;\r\n hoveredTab.value = null;\r\n emit('tab-leave', tab.id, tab);\r\n};\r\n\r\n// 窗口尺寸变化监听\r\nconst handleResize = () => {\r\n checkMobile();\r\n};\r\n\r\n// 生命周期\r\nonMounted(() => {\r\n checkMobile();\r\n window.addEventListener('resize', handleResize);\r\n});\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener('resize', handleResize);\r\n});\r\n\r\n// 暴露给父组件\r\ndefineExpose({\r\n activeTab,\r\n setActiveTab: (tabId: string) => {\r\n const tab = props.tabs.find(t => t.id === tabId);\r\n if (tab && !tab.disabled) {\r\n activeTab.value = tabId;\r\n emit('tab-change', tabId, tab);\r\n }\r\n },\r\n getActiveTab: () => {\r\n return props.tabs.find(t => t.id === activeTab.value);\r\n },\r\n scrollToTab: (tabId: string) => {\r\n if (!props.scrollable) return;\r\n nextTick(() => {\r\n const tabElement = document.querySelector(`[data-tab-id=\"${tabId}\"]`);\r\n if (tabElement) {\r\n tabElement.scrollIntoView({ behavior: 'smooth', inline: 'center' });\r\n }\r\n });\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.simple-tabs-container {\r\n width: 100%;\r\n}\r\n\r\n.simple-tabs-container.tabs-block {\r\n display: block;\r\n}\r\n\r\n.simple-tabs-container.tabs-scrollable .tabs-list {\r\n overflow-x: auto;\r\n scrollbar-width: none; /* Firefox */\r\n -ms-overflow-style: none; /* IE and Edge */\r\n}\r\n\r\n.simple-tabs-container.tabs-scrollable .tabs-list::-webkit-scrollbar {\r\n display: none; /* Chrome, Safari, Opera */\r\n}\r\n\r\n.tab-item {\r\n flex-shrink: 0;\r\n position: relative;\r\n}\r\n\r\n.tab-button {\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.tab-button:focus {\r\n outline: none;\r\n}\r\n\r\n.tab-button:focus-visible {\r\n outline: 2px solid v-bind('computedTheme.indicatorColor');\r\n outline-offset: 2px;\r\n}\r\n\r\n/* 移动端优化 */\r\n.simple-tabs-container.is-mobile .tab-button {\r\n -webkit-touch-callout: none;\r\n -webkit-user-select: none;\r\n -khtml-user-select: none;\r\n -moz-user-select: none;\r\n -ms-user-select: none;\r\n user-select: none;\r\n}\r\n\r\n/* 禁用状态样式 */\r\n.tab-disabled .tab-button {\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 响应式样式 */\r\n@media (max-width: 768px) {\r\n .simple-tabs-container.is-mobile .tab-indicator {\r\n left: 0 !important;\r\n right: 0 !important;\r\n }\r\n}\r\n\r\n/* 滚动优化 */\r\n.tabs-scrollable {\r\n position: relative;\r\n}\r\n\r\n.tabs-scrollable::before,\r\n.tabs-scrollable::after {\r\n content: '';\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n width: 20px;\r\n pointer-events: none;\r\n z-index: 1;\r\n}\r\n\r\n.tabs-scrollable::before {\r\n left: 0;\r\n background: linear-gradient(to right, v-bind('computedTheme.backgroundColor'), transparent);\r\n}\r\n\r\n.tabs-scrollable::after {\r\n right: 0;\r\n background: linear-gradient(to left, v-bind('computedTheme.backgroundColor'), transparent);\r\n}\r\n\r\n/* 不同尺寸的样式 */\r\n.size-small .tab-button {\r\n min-height: 32px;\r\n}\r\n\r\n.size-medium .tab-button {\r\n min-height: 40px;\r\n}\r\n\r\n.size-large .tab-button {\r\n min-height: 48px;\r\n}\r\n\r\n/* 居中对齐样式 */\r\n.tabs-centered .tabs-list {\r\n justify-content: center;\r\n}\r\n\r\n/* 图标样式 */\r\n.tab-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.tab-icon svg {\r\n width: 1em;\r\n height: 1em;\r\n fill: currentColor;\r\n}\r\n\r\n/* 指示器样式基类 */\r\n.tab-indicator {\r\n pointer-events: none;\r\n}\r\n\r\n/* 动画性能优化 */\r\n.tab-button,\r\n.tab-indicator {\r\n will-change: color, background-color, transform;\r\n}\r\n\r\n/* 高对比度模式支持 */\r\n@media (prefers-contrast: high) {\r\n .tab-button {\r\n border: 1px solid currentColor;\r\n }\r\n \r\n .tab-indicator {\r\n border: 2px solid currentColor;\r\n }\r\n}\r\n\r\n/* 减少动画模式支持 */\r\n@media (prefers-reduced-motion: reduce) {\r\n .tab-button,\r\n .tab-indicator {\r\n transition: none !important;\r\n }\r\n}\r\n</style>\r\n"],"names":["defaultTheme","sizeConfigs","defaultAnimation","defaultResponsive","indicatorStyles","color","height","getIndicatorStyle","style","mergeConfig","defaultConfig","userConfig","getResponsiveStyle","isMobile","baseStyle","mobileStyle","props","__props","emit","__emit","activeTab","ref","_a","hoveredTab","computedTheme","computed","computedAnimation","computedResponsive","baseStyleConfig","computedStyleConfig","checkMobile","getTabTextColor","tab","getIndicatorStyleComputed","handleTabClick","event","handleTabHover","handleTabLeave","handleResize","onMounted","onUnmounted","__expose","tabId","t","nextTick","tabElement","_createElementBlock","_normalizeStyle","_createElementVNode","_openBlock","_Fragment","_renderList","$event","_hoisted_3","_toDisplayString"],"mappings":"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"}
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .simple-tabs-container[data-v-166844ee]{width:100%}.simple-tabs-container.tabs-block[data-v-166844ee]{display:block}.simple-tabs-container.tabs-scrollable .tabs-list[data-v-166844ee]{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}.simple-tabs-container.tabs-scrollable .tabs-list[data-v-166844ee]::-webkit-scrollbar{display:none}.tab-item[data-v-166844ee]{flex-shrink:0;position:relative}.tab-button[data-v-166844ee]{outline:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.tab-button[data-v-166844ee]:focus{outline:none}.tab-button[data-v-166844ee]:focus-visible{outline:2px solid var(--4e65487a);outline-offset:2px}.simple-tabs-container.is-mobile .tab-button[data-v-166844ee]{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tab-disabled .tab-button[data-v-166844ee]{cursor:not-allowed}@media (max-width: 768px){.simple-tabs-container.is-mobile .tab-indicator[data-v-166844ee]{left:0!important;right:0!important}}.tabs-scrollable[data-v-166844ee]{position:relative}.tabs-scrollable[data-v-166844ee]:before,.tabs-scrollable[data-v-166844ee]:after{content:"";position:absolute;top:0;bottom:0;width:20px;pointer-events:none;z-index:1}.tabs-scrollable[data-v-166844ee]:before{left:0;background:linear-gradient(to right,var(--25a898f4),transparent)}.tabs-scrollable[data-v-166844ee]:after{right:0;background:linear-gradient(to left,var(--25a898f4),transparent)}.size-small .tab-button[data-v-166844ee]{min-height:32px}.size-medium .tab-button[data-v-166844ee]{min-height:40px}.size-large .tab-button[data-v-166844ee]{min-height:48px}.tabs-centered .tabs-list[data-v-166844ee]{justify-content:center}.tab-icon[data-v-166844ee]{display:inline-flex;align-items:center;justify-content:center}.tab-icon svg[data-v-166844ee]{width:1em;height:1em;fill:currentColor}.tab-indicator[data-v-166844ee]{pointer-events:none}.tab-button[data-v-166844ee],.tab-indicator[data-v-166844ee]{will-change:color,background-color,transform}@media (prefers-contrast: high){.tab-button[data-v-166844ee]{border:1px solid currentColor}.tab-indicator[data-v-166844ee]{border:2px solid currentColor}}@media (prefers-reduced-motion: reduce){.tab-button[data-v-166844ee],.tab-indicator[data-v-166844ee]{transition:none!important}}
package/package.json ADDED
@@ -0,0 +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
+ }
@@ -0,0 +1,406 @@
1
+ <template>
2
+ <div
3
+ class="simple-tabs-container"
4
+ :class="[
5
+ `size-${props.size}`,
6
+ {
7
+ 'tabs-block': props.block,
8
+ 'tabs-centered': props.centered,
9
+ 'tabs-scrollable': props.scrollable,
10
+ 'is-mobile': isMobile,
11
+ },
12
+ props.className,
13
+ ]"
14
+ :style="{
15
+ backgroundColor: computedTheme.backgroundColor,
16
+ boxShadow: computedTheme.boxShadow,
17
+ flexShrink: 0,
18
+ ...props.customStyle,
19
+ }"
20
+ >
21
+ <nav>
22
+ <ul
23
+ class="tabs-list"
24
+ :class="{
25
+ 'flex-wrap': props.scrollable && !isMobile,
26
+ 'overflow-x-auto': props.scrollable && isMobile,
27
+ }"
28
+ :style="{
29
+ display: 'flex',
30
+ gap: computedStyleConfig.gap,
31
+ justifyContent: props.centered ? 'center' : 'flex-start',
32
+ margin: 0,
33
+ padding: 0,
34
+ listStyle: 'none',
35
+ }"
36
+ >
37
+ <li
38
+ v-for="tab in props.tabs"
39
+ :key="tab.id"
40
+ class="tab-item"
41
+ :class="{
42
+ 'tab-active': activeTab === tab.id,
43
+ 'tab-disabled': tab.disabled,
44
+ }"
45
+ >
46
+ <button
47
+ class="tab-button"
48
+ :disabled="tab.disabled"
49
+ :style="{
50
+ position: 'relative',
51
+ display: 'flex',
52
+ alignItems: 'center',
53
+ background: 'none',
54
+ border: 'none',
55
+ cursor: tab.disabled ? 'not-allowed' : 'pointer',
56
+ textDecoration: 'none',
57
+ transition: computedAnimation.enabled
58
+ ? `color ${computedAnimation.transitionDuration} ${computedAnimation.easing}`
59
+ : 'none',
60
+ padding: computedStyleConfig.padding,
61
+ fontSize: computedStyleConfig.fontSize,
62
+ fontWeight: computedStyleConfig.fontWeight,
63
+ fontFamily: computedTheme.fontFamily,
64
+ color: getTabTextColor(tab),
65
+ opacity: tab.disabled ? 0.5 : 1,
66
+ borderRadius: computedStyleConfig.borderRadius || '0',
67
+ whiteSpace: 'nowrap',
68
+ }"
69
+ @click="handleTabClick(tab, $event)"
70
+ @mouseenter="handleTabHover(tab)"
71
+ @mouseleave="handleTabLeave(tab)"
72
+ >
73
+ <!-- 图标 -->
74
+ <span
75
+ v-if="tab.icon"
76
+ class="tab-icon"
77
+ :style="{
78
+ marginRight: '8px',
79
+ fontSize: '1em',
80
+ lineHeight: 1,
81
+ }"
82
+ v-html="tab.icon"
83
+ />
84
+
85
+ <!-- 标签文本 -->
86
+ <span class="tab-text">{{ tab.name }}</span>
87
+
88
+ <!-- 激活指示器 -->
89
+ <span
90
+ v-if="activeTab === tab.id"
91
+ class="tab-indicator"
92
+ :style="{
93
+ ...getIndicatorStyleComputed(),
94
+ transition: computedAnimation.enabled
95
+ ? `all ${computedAnimation.indicatorTransition || computedAnimation.transitionDuration} ${computedAnimation.easing}`
96
+ : 'none',
97
+ }"
98
+ />
99
+ </button>
100
+ </li>
101
+ </ul>
102
+ </nav>
103
+ </div>
104
+ </template>
105
+
106
+ <script setup lang="ts">
107
+ import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
108
+ import type { SimpleTabsProps, SimpleTabsEvents, TabItem } from '../types';
109
+ import {
110
+ defaultTheme,
111
+ sizeConfigs,
112
+ defaultAnimation,
113
+ defaultResponsive,
114
+ mergeConfig,
115
+ getResponsiveStyle,
116
+ getIndicatorStyle,
117
+ } from '../types';
118
+
119
+ // Props定义
120
+ const props = withDefaults(defineProps<SimpleTabsProps>(), {
121
+ size: 'medium',
122
+ block: false,
123
+ centered: false,
124
+ scrollable: false,
125
+ });
126
+
127
+ // Events定义
128
+ const emit = defineEmits<SimpleTabsEvents>();
129
+
130
+ // 响应式状态
131
+ const activeTab = ref(props.defaultActive || props.tabs[0]?.id || '');
132
+ const isMobile = ref(false);
133
+ const hoveredTab = ref<string | null>(null);
134
+
135
+ // 计算属性
136
+ const computedTheme = computed(() =>
137
+ mergeConfig(defaultTheme, props.theme)
138
+ );
139
+
140
+ const computedAnimation = computed(() =>
141
+ mergeConfig(defaultAnimation, props.animation)
142
+ );
143
+
144
+ const computedResponsive = computed(() =>
145
+ mergeConfig(defaultResponsive, props.responsive)
146
+ );
147
+
148
+ const baseStyleConfig = computed(() =>
149
+ mergeConfig(sizeConfigs[props.size], props.style)
150
+ );
151
+
152
+ const computedStyleConfig = computed(() =>
153
+ getResponsiveStyle(
154
+ isMobile.value && computedResponsive.value.enabled,
155
+ baseStyleConfig.value,
156
+ computedResponsive.value.mobileStyle
157
+ )
158
+ );
159
+
160
+ // 工具方法
161
+ const checkMobile = () => {
162
+ if (computedResponsive.value.enabled) {
163
+ isMobile.value = window.innerWidth <= computedResponsive.value.mobileBreakpoint;
164
+ }
165
+ };
166
+
167
+ const getTabTextColor = (tab: TabItem): string => {
168
+ if (tab.disabled) {
169
+ return computedTheme.value.inactiveTextColor;
170
+ }
171
+
172
+ if (activeTab.value === tab.id) {
173
+ return computedTheme.value.activeTextColor;
174
+ }
175
+
176
+ if (hoveredTab.value === tab.id) {
177
+ return computedTheme.value.hoverTextColor;
178
+ }
179
+
180
+ return computedTheme.value.inactiveTextColor;
181
+ };
182
+
183
+ const getIndicatorStyleComputed = () => {
184
+ return getIndicatorStyle(
185
+ computedStyleConfig.value.indicatorStyle,
186
+ computedTheme.value.indicatorColor,
187
+ computedStyleConfig.value.indicatorHeight
188
+ );
189
+ };
190
+
191
+ // 事件处理
192
+ const handleTabClick = (tab: TabItem, event: Event) => {
193
+ if (tab.disabled) return;
194
+
195
+ activeTab.value = tab.id;
196
+ emit('tab-change', tab.id, tab);
197
+ emit('tab-click', tab.id, tab, event);
198
+
199
+ // 路由跳转逻辑(如果需要)
200
+ if (tab.route) {
201
+ console.log('Navigate to:', tab.route);
202
+ // 这里可以集成 vue-router
203
+ }
204
+ };
205
+
206
+ const handleTabHover = (tab: TabItem) => {
207
+ if (tab.disabled) return;
208
+ hoveredTab.value = tab.id;
209
+ emit('tab-hover', tab.id, tab);
210
+ };
211
+
212
+ const handleTabLeave = (tab: TabItem) => {
213
+ if (tab.disabled) return;
214
+ hoveredTab.value = null;
215
+ emit('tab-leave', tab.id, tab);
216
+ };
217
+
218
+ // 窗口尺寸变化监听
219
+ const handleResize = () => {
220
+ checkMobile();
221
+ };
222
+
223
+ // 生命周期
224
+ onMounted(() => {
225
+ checkMobile();
226
+ window.addEventListener('resize', handleResize);
227
+ });
228
+
229
+ onUnmounted(() => {
230
+ window.removeEventListener('resize', handleResize);
231
+ });
232
+
233
+ // 暴露给父组件
234
+ defineExpose({
235
+ activeTab,
236
+ setActiveTab: (tabId: string) => {
237
+ const tab = props.tabs.find(t => t.id === tabId);
238
+ if (tab && !tab.disabled) {
239
+ activeTab.value = tabId;
240
+ emit('tab-change', tabId, tab);
241
+ }
242
+ },
243
+ getActiveTab: () => {
244
+ return props.tabs.find(t => t.id === activeTab.value);
245
+ },
246
+ scrollToTab: (tabId: string) => {
247
+ if (!props.scrollable) return;
248
+ nextTick(() => {
249
+ const tabElement = document.querySelector(`[data-tab-id="${tabId}"]`);
250
+ if (tabElement) {
251
+ tabElement.scrollIntoView({ behavior: 'smooth', inline: 'center' });
252
+ }
253
+ });
254
+ },
255
+ });
256
+ </script>
257
+
258
+ <style scoped>
259
+ .simple-tabs-container {
260
+ width: 100%;
261
+ }
262
+
263
+ .simple-tabs-container.tabs-block {
264
+ display: block;
265
+ }
266
+
267
+ .simple-tabs-container.tabs-scrollable .tabs-list {
268
+ overflow-x: auto;
269
+ scrollbar-width: none; /* Firefox */
270
+ -ms-overflow-style: none; /* IE and Edge */
271
+ }
272
+
273
+ .simple-tabs-container.tabs-scrollable .tabs-list::-webkit-scrollbar {
274
+ display: none; /* Chrome, Safari, Opera */
275
+ }
276
+
277
+ .tab-item {
278
+ flex-shrink: 0;
279
+ position: relative;
280
+ }
281
+
282
+ .tab-button {
283
+ outline: none;
284
+ user-select: none;
285
+ -webkit-tap-highlight-color: transparent;
286
+ }
287
+
288
+ .tab-button:focus {
289
+ outline: none;
290
+ }
291
+
292
+ .tab-button:focus-visible {
293
+ outline: 2px solid v-bind('computedTheme.indicatorColor');
294
+ outline-offset: 2px;
295
+ }
296
+
297
+ /* 移动端优化 */
298
+ .simple-tabs-container.is-mobile .tab-button {
299
+ -webkit-touch-callout: none;
300
+ -webkit-user-select: none;
301
+ -khtml-user-select: none;
302
+ -moz-user-select: none;
303
+ -ms-user-select: none;
304
+ user-select: none;
305
+ }
306
+
307
+ /* 禁用状态样式 */
308
+ .tab-disabled .tab-button {
309
+ cursor: not-allowed;
310
+ }
311
+
312
+ /* 响应式样式 */
313
+ @media (max-width: 768px) {
314
+ .simple-tabs-container.is-mobile .tab-indicator {
315
+ left: 0 !important;
316
+ right: 0 !important;
317
+ }
318
+ }
319
+
320
+ /* 滚动优化 */
321
+ .tabs-scrollable {
322
+ position: relative;
323
+ }
324
+
325
+ .tabs-scrollable::before,
326
+ .tabs-scrollable::after {
327
+ content: '';
328
+ position: absolute;
329
+ top: 0;
330
+ bottom: 0;
331
+ width: 20px;
332
+ pointer-events: none;
333
+ z-index: 1;
334
+ }
335
+
336
+ .tabs-scrollable::before {
337
+ left: 0;
338
+ background: linear-gradient(to right, v-bind('computedTheme.backgroundColor'), transparent);
339
+ }
340
+
341
+ .tabs-scrollable::after {
342
+ right: 0;
343
+ background: linear-gradient(to left, v-bind('computedTheme.backgroundColor'), transparent);
344
+ }
345
+
346
+ /* 不同尺寸的样式 */
347
+ .size-small .tab-button {
348
+ min-height: 32px;
349
+ }
350
+
351
+ .size-medium .tab-button {
352
+ min-height: 40px;
353
+ }
354
+
355
+ .size-large .tab-button {
356
+ min-height: 48px;
357
+ }
358
+
359
+ /* 居中对齐样式 */
360
+ .tabs-centered .tabs-list {
361
+ justify-content: center;
362
+ }
363
+
364
+ /* 图标样式 */
365
+ .tab-icon {
366
+ display: inline-flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ }
370
+
371
+ .tab-icon svg {
372
+ width: 1em;
373
+ height: 1em;
374
+ fill: currentColor;
375
+ }
376
+
377
+ /* 指示器样式基类 */
378
+ .tab-indicator {
379
+ pointer-events: none;
380
+ }
381
+
382
+ /* 动画性能优化 */
383
+ .tab-button,
384
+ .tab-indicator {
385
+ will-change: color, background-color, transform;
386
+ }
387
+
388
+ /* 高对比度模式支持 */
389
+ @media (prefers-contrast: high) {
390
+ .tab-button {
391
+ border: 1px solid currentColor;
392
+ }
393
+
394
+ .tab-indicator {
395
+ border: 2px solid currentColor;
396
+ }
397
+ }
398
+
399
+ /* 减少动画模式支持 */
400
+ @media (prefers-reduced-motion: reduce) {
401
+ .tab-button,
402
+ .tab-indicator {
403
+ transition: none !important;
404
+ }
405
+ }
406
+ </style>
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ // 主要组件导出
2
+ export { default as ConfigurableSimpleTabs } from './components/ConfigurableSimpleTabs.vue';
3
+
4
+ // 导出类型定义
5
+ export type {
6
+ TabItem,
7
+ TabSize,
8
+ IndicatorStyle,
9
+ SimpleTabsTheme,
10
+ StyleConfig,
11
+ AnimationConfig,
12
+ ResponsiveConfig,
13
+ SimpleTabsProps,
14
+ SimpleTabsEvents,
15
+ } from './types';
16
+
17
+ // 导出默认配置和工具函数
18
+ export {
19
+ defaultTheme,
20
+ sizeConfigs,
21
+ defaultAnimation,
22
+ defaultResponsive,
23
+ indicatorStyles,
24
+ getIndicatorStyle,
25
+ mergeConfig,
26
+ getResponsiveStyle,
27
+ } from './types';
28
+
29
+ // 默认导出
30
+ export { default } from './components/ConfigurableSimpleTabs.vue';