gvf3n4ygn-test
Version:
CLI for SIGMA-UI components.
22 lines • 10.7 kB
JSON
{
"name": "card-lightbox",
"dependencies": [
"motion-v"
],
"registryDependencies": [],
"files": [
{
"name": "CardLightbox.vue",
"content": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref } from 'vue';\nimport { AnimatePresence, motion } from 'motion-v';\nimport type { Item } from './types';\nimport { XIcon } from 'lucide-vue-next';\n\ntype Props = {\n items: Item[];\n};\n\nconst props = defineProps<Props>();\n\nconst openId = ref<string | null>(null);\n\nonMounted(() => {\n document.addEventListener('keydown', handleEscKeydown);\n});\n\nonUnmounted(() => {\n document.removeEventListener('keydown', handleEscKeydown);\n});\n\nfunction getTheme(id: string) {\n return props.items.find(item => item.id === id)?.theme;\n}\n\nfunction open(id: string) {\n openId.value = id;\n}\n\nfunction close() {\n openId.value = null;\n}\n\nfunction handleEscKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n close();\n }\n}\n</script>\n\n<template>\n <div class=\"card-lightbox\">\n <ul class=\"card-lightbox__grid\">\n <motion.button\n v-for=\"card in props.items\"\n :key=\"card.id\"\n :data-open=\"openId === card.id\"\n :class=\"[\n 'card-lightbox__card',\n {\n 'theme-dark': getTheme(card.id) === 'dark',\n 'theme-light': getTheme(card.id) === 'light'\n }\n ]\"\n @click=\"() => open(card.id)\"\n >\n <motion.div\n class=\"card-lightbox__card-content animate-fade-in\"\n :layout-id=\"`card-container-${card.id}`\"\n >\n <motion.div\n class=\"card-lightbox__card-image-container\"\n :layout-id=\"`card-image-container-${card.id}`\"\n >\n <motion.img\n class=\"card-lightbox__card-image\"\n :src=\"card.image\"\n alt=\"\"\n />\n <div class=\"card-lightbox__image-overlay\" />\n </motion.div>\n <motion.div\n class=\"card-lightbox__card-title-container\"\n :layout-id=\"`title-container-${card.id}`\"\n layout=\"position\"\n >\n <span class=\"card-lightbox__card-category\">{{ card.category }}</span>\n <h2 class=\"card-lightbox__card-title\">\n {{ card.title }}\n </h2>\n </motion.div>\n </motion.div>\n </motion.button>\n </ul>\n <AnimatePresence>\n <motion.div\n v-if=\"openId\"\n :initial=\"{ opacity: 0 }\"\n :animate=\"{ opacity: 1 }\"\n :exit=\"{ opacity: 0 }\"\n :transition=\"{ duration: 0.2 }\"\n style=\"pointer-events: auto\"\n class=\"card-lightbox__expanded-overlay\"\n @click=\"close\"\n />\n </AnimatePresence>\n <AnimatePresence>\n <div\n v-if=\"openId\"\n :class=\"[\n 'card-lightbox__expanded-container',\n {\n 'theme-dark': getTheme(openId) === 'dark',\n 'theme-light': getTheme(openId) === 'light'\n }\n ]\"\n >\n <motion.div\n class=\"card-lightbox__expanded-content\"\n :layout-id=\"`card-container-${openId}`\"\n >\n <motion.div\n class=\"card-lightbox__expanded-image-container\"\n :layout-id=\"`card-image-container-${openId}`\"\n >\n <motion.img\n class=\"card-lightbox__expanded-image\"\n :src=\"props.items.find(item => item.id === openId)?.image\"\n alt=\"\"\n :layout-id=\"`card-image-${openId}`\"\n :initial=\"{ opacity: 0, filter: 'blur(12px)' }\"\n :animate=\"{ opacity: 1, filter: 'blur(0px)' }\"\n :exit=\"{ opacity: 0, filter: 'blur(12px)' }\"\n :transition=\"{ duration: 0.5 }\"\n />\n <div class=\"card-lightbox__image-overlay\" />\n <motion.div\n class=\"card-lightbox__expanded-title-container\"\n :layout-id=\"`title-container-${openId}`\"\n layout=\"position\"\n >\n <span class=\"card-lightbox__expanded-category\">{{ props.items.find(item => item.id === openId)?.category }}</span>\n <h2 class=\"card-lightbox__expanded-title\">\n {{ props.items.find(item => item.id === openId)?.title }}\n </h2>\n </motion.div>\n <motion.div\n class=\"card-lightbox__expanded-close-button\"\n @click=\"close\"\n >\n <XIcon />\n </motion.div>\n </motion.div>\n <motion.div class=\"card-lightbox__expanded-body\">\n <div v-html=\"props.items.find(item => item.id === openId)?.content\" />\n </motion.div>\n </motion.div>\n </div>\n </AnimatePresence>\n </div>\n</template>\n\n<style scoped>\n.card-lightbox {\n margin: 0 auto;\n width: 100%;\n max-width: 990px;\n display: flex;\n flex-direction: column;\n container-type: inline-size;\n}\n\n.card-lightbox,\n.card-lightbox * {\n box-sizing: border-box;\n}\n\n.card-lightbox__grid {\n display: grid;\n grid-template-columns: repeat(10, 1fr);\n gap: 20px;\n padding: 0;\n margin: 0;\n list-style: none;\n}\n\n.card-lightbox__card {\n position: relative;\n padding: 0px;\n height: 420px;\n grid-column: span 4;\n box-sizing: border-box;\n overflow: hidden;\n}\n\n.card-lightbox__card:focus-visible {\n border-radius: 20px;\n box-shadow:\n 0 0 0 2px hsl(var(--background)),\n 0 0 0 4px hsl(var(--ring));\n outline: none;\n}\n\n.card-lightbox__card:nth-child(4n + 1),\n.card-lightbox__card:nth-child(4n + 4) {\n grid-column: span 6;\n}\n\n.theme-light .card-lightbox__image-overlay {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(160deg, rgba(0, 0, 0, 0.95) -8%, rgba(0, 0, 0, 0) 30%);\n z-index: 1;\n pointer-events: none;\n}\n\n.theme-dark .card-lightbox__image-overlay {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(160deg, rgba(255, 255, 255, 0.95) -8%, rgba(255, 255, 255, 0) 30%);\n z-index: 1;\n pointer-events: none;\n}\n\n.card-lightbox__expanded-container {\n width: 100%;\n height: 100%;\n pointer-events: none;\n display: flex;\n flex-direction: column;\n top: 0;\n left: 0;\n right: 0;\n position: fixed;\n z-index: 51;\n overflow: hidden;\n padding: 40px 0;\n justify-content: center;\n align-items: center;\n}\n\n.card-lightbox__card-content,\n.card-lightbox__expanded-content {\n pointer-events: auto;\n user-select: none;\n position: relative;\n border-radius: 20px;\n background: hsl(var(--card));\n overflow: hidden;\n width: 100%;\n height: 100%;\n margin: 0 auto;\n}\n\n.card-lightbox__expanded-content {\n width: unset;\n max-width: 700px;\n overflow: hidden;\n pointer-events: auto;\n overflow-y: auto;\n height: 70dvh;\n}\n\n.card-lightbox__card-image-container,\n.card-lightbox__expanded-image-container {\n overflow: hidden;\n height: 420px;\n position: relative;\n width: 100%;\n}\n\n.card-lightbox__card-image,\n.card-lightbox__expanded-image {\n z-index: 1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n position: absolute;\n top: 0;\n left: 0;\n transform: none;\n}\n\n.card-lightbox__card-title-container {\n z-index: 2;\n position: absolute;\n top: 0px;\n left: 0px;\n max-width: 300px;\n padding: 20px;\n text-align: left;\n}\n\n.card-lightbox__expanded-title-container {\n z-index: 2;\n position: absolute;\n top: 0;\n left: 0;\n padding: 20px;\n}\n\n.card-lightbox__expanded-close-button {\n z-index: 2;\n position: absolute;\n top: 0;\n right: 0;\n padding: 20px;\n cursor: pointer;\n}\n\n.card-lightbox__card-title,\n.card-lightbox__expanded-title {\n color: hsl(0 0% 100%);\n margin: 8px 0;\n font-size: 20px;\n font-weight: 600;\n}\n\n.card-lightbox__card-category,\n.card-lightbox__expanded-category {\n color: hsl(0 0% 100%);\n background: hsl(0 0% 100% / 0.12);\n backdrop-filter: blur(8px);\n padding: 4px 8px;\n border-radius: 12px;\n font-size: 12px;\n text-transform: uppercase;\n}\n\n.theme-dark .card-lightbox__card-category,\n.theme-dark .card-lightbox__expanded-category {\n background: hsl(240 10% 3.9% / 0.2);\n}\n\n.card-lightbox__expanded-overlay {\n inset: 0;\n z-index: 50;\n position: fixed;\n backdrop-filter: blur(12px);\n background: rgba(0, 0, 0, 0.2);\n will-change: opacity;\n}\n\n.card-lightbox__expanded-body {\n padding: 35px;\n max-width: 700px;\n width: 100vw;\n}\n\n.theme-dark .card-lightbox__card-category,\n.theme-dark .card-lightbox__card-title,\n.theme-dark .card-lightbox__expanded-title-container,\n.theme-dark .card-lightbox__expanded-title,\n.theme-dark .card-lightbox__expanded-category {\n color: hsl(240 10% 3.9% / 0.9);\n}\n\n.theme-dark svg {\n stroke: hsl(240 10% 3.9% / 0.9);\n}\n\n.theme-light svg {\n stroke: hsl(0 0% 100%);\n}\n\n.card-lightbox__expanded-body {\n color: hsl(var(--primary));\n}\n\n@media only screen and (max-width: 990px) {\n .card-lightbox__expanded-container {\n padding: 0;\n }\n\n .card-lightbox__expanded-content {\n overflow: hidden;\n pointer-events: auto;\n overflow-y: auto;\n width: 100vw;\n max-width: 100vw;\n height: 100dvh;\n border-radius: 0;\n }\n\n .card-lightbox__card-image-container,\n .card-lightbox__expanded-image-container {\n height: 280px;\n }\n}\n\n@container (max-width: 600px) {\n #sandbox {\n align-items: stretch;\n }\n\n .card-lightbox {\n padding: 60px 20px;\n padding-left: 10px;\n padding-right: 10px;\n }\n\n .card-lightbox__grid {\n gap: 10px;\n grid-template-columns: 1fr;\n }\n\n .card-lightbox__card {\n grid-column: auto / auto;\n height: 280px;\n width: 100%;\n }\n\n .card-lightbox__card:nth-child(4n + 1),\n .card-lightbox__card:nth-child(4n + 4) {\n grid-column: auto / auto;\n }\n\n .card-lightbox__card-image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n position: absolute;\n inset: 0;\n }\n}\n</style>\n"
},
{
"name": "index.ts",
"content": "export { default as CardLightbox } from './CardLightbox.vue';\nexport * from './types';\n"
},
{
"name": "types.ts",
"content": "import type { Component } from 'vue';\n\nexport interface Item {\n id: string;\n category: string;\n title: string;\n content: string | Component;\n theme: 'light' | 'dark';\n image: string;\n}\n"
}
],
"type": "components:ui"
}