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.
- package/README.md +450 -0
- package/dist/index.esm.js +262 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +67 -0
- package/src/components/ConfigurableSimpleTabs.vue +406 -0
- package/src/index.ts +30 -0
- package/src/types.ts +250 -0
|
@@ -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';
|