mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
5.3 KiB
JSON
1 line
5.3 KiB
JSON
{"name":"Folder","title":"Folder","description":"Interactive folder opens to reveal nested content smooth motion.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div :style=\"{ transform: `scale(${props.size})` }\" :class=\"props.class\">\n <div :class=\"folderClass\" :style=\"folderStyle\" @click=\"handleClick\">\n <div\n class=\"relative w-[100px] h-[80px] rounded-tl-0 rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px]\"\n :style=\"{ backgroundColor: folderBackColor }\"\n >\n <span\n class=\"absolute z-0 bottom-[98%] left-0 w-[30px] h-[10px] rounded-tl-[5px] rounded-tr-[5px] rounded-bl-0 rounded-br-0\"\n :style=\"{ backgroundColor: folderBackColor }\"\n ></span>\n <div v-for=\"(item, i) in papers\" :key=\"i\" :class=\"getPaperClasses(i)\" :style=\"getPaperStyle(i)\">\n <slot :name=\"`item-${i + 1}`\" :item=\"item\" :index=\"i\" :isOpen=\"open\">\n {{ item }}\n </slot>\n </div>\n <div :class=\"frontClass\" :style=\"{ backgroundColor: props.color }\"></div>\n <div :class=\"rightClass\" :style=\"{ backgroundColor: props.color }\"></div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue';\n\ninterface Props {\n color?: string;\n size?: number;\n items?: (string | null)[];\n class?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n color: '#27FF64',\n size: 1,\n items: () => [],\n class: ''\n});\n\nconst darkenColor = (hex: string, percent: number): string => {\n let color = hex.startsWith('#') ? hex.slice(1) : hex;\n if (color.length === 3) {\n color = color\n .split('')\n .map(c => c + c)\n .join('');\n }\n const num = parseInt(color, 16);\n let r = (num >> 16) & 0xff;\n let g = (num >> 8) & 0xff;\n let b = num & 0xff;\n r = Math.max(0, Math.min(255, Math.floor(r * (1 - percent))));\n g = Math.max(0, Math.min(255, Math.floor(g * (1 - percent))));\n b = Math.max(0, Math.min(255, Math.floor(b * (1 - percent))));\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n};\n\nconst open = ref(false);\nconst maxItems = 3;\n\nconst papers = computed(() => {\n const result = props.items.slice(0, maxItems);\n while (result.length < maxItems) {\n result.push(null);\n }\n return result;\n});\n\nconst folderBackColor = computed(() => darkenColor(props.color, 0.08));\nconst paper1 = computed(() => darkenColor('#ffffff', 0.1));\nconst paper2 = computed(() => darkenColor('#ffffff', 0.05));\nconst paper3 = computed(() => '#ffffff');\n\nconst folderStyle = computed(() => ({\n transform: open.value ? 'translateY(-8px)' : undefined\n}));\n\nconst folderClass = computed(() =>\n `group relative transition-all duration-200 ease-in cursor-pointer ${\n !open.value ? 'hover:-translate-y-2' : ''\n }`.trim()\n);\n\nconst getPaperClasses = (index: number) => {\n let sizeClasses = '';\n if (index === 0) sizeClasses = 'w-[70%] h-[80%]';\n if (index === 1) sizeClasses = open.value ? 'w-[80%] h-[80%]' : 'w-[80%] h-[70%]';\n if (index === 2) sizeClasses = open.value ? 'w-[90%] h-[80%]' : 'w-[90%] h-[60%]';\n\n return `absolute z-20 bottom-[10%] left-1/2 transition-all duration-300 ease-in-out overflow-hidden ${\n !open.value ? 'transform -translate-x-1/2 translate-y-[10%] group-hover:translate-y-0' : 'hover:scale-110'\n } ${sizeClasses}`.trim();\n};\n\nconst getOpenTransform = (index: number) => {\n if (index === 0) return 'translate(-120%, -70%) rotate(-15deg)';\n if (index === 1) return 'translate(10%, -70%) rotate(15deg)';\n if (index === 2) return 'translate(-50%, -100%) rotate(5deg)';\n return '';\n};\n\nconst getPaperStyle = (index: number) => {\n const backgroundColor = index === 0 ? paper1.value : index === 1 ? paper2.value : paper3.value;\n const baseStyle = {\n backgroundColor,\n borderRadius: '10px',\n transition: 'all 0.3s ease-in-out'\n };\n\n if (open.value) {\n const transformStyle = getOpenTransform(index);\n return {\n ...baseStyle,\n transform: transformStyle\n };\n }\n\n return baseStyle;\n};\n\nconst frontClass = computed(() => {\n const baseClasses = 'absolute z-30 w-full h-full origin-bottom transition-all duration-300 ease-in-out';\n const borderRadius = 'rounded-tl-[5px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px]';\n\n if (open.value) {\n return `${baseClasses} ${borderRadius} [transform:skew(15deg)_scaleY(0.6)]`;\n } else {\n return `${baseClasses} ${borderRadius} group-hover:[transform:skew(15deg)_scaleY(0.6)]`;\n }\n});\n\nconst rightClass = computed(() => {\n const baseClasses = 'absolute z-30 w-full h-full origin-bottom transition-all duration-300 ease-in-out';\n const borderRadius = 'rounded-tl-[5px] rounded-tr-[10px] rounded-br-[10px] rounded-bl-[10px]';\n\n if (open.value) {\n return `${baseClasses} ${borderRadius} [transform:skew(-15deg)_scaleY(0.6)]`;\n } else {\n return `${baseClasses} ${borderRadius} group-hover:[transform:skew(-15deg)_scaleY(0.6)]`;\n }\n});\n\nconst handleClick = () => {\n open.value = !open.value;\n};\n</script>\n","path":"Folder/Folder.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Components"]} |