sigma-ui
Version:
CLI for SIGMA-UI components.
22 lines • 10.6 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 display: flex;\n width: 100%;\n max-width: 990px;\n flex-direction: column;\n margin: 0 auto;\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 padding: 0;\n margin: 0;\n gap: 20px;\n grid-template-columns: repeat(10, 1fr);\n list-style: none;\n}\n\n.card-lightbox__card {\n position: relative;\n overflow: hidden;\n height: 420px;\n box-sizing: border-box;\n padding: 0;\n grid-column: span 4;\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 position: absolute;\n z-index: 1;\n background: linear-gradient(160deg, rgb(0 0 0 / 95%) -8%, rgb(0 0 0 / 0%) 30%);\n content: '';\n inset: 0;\n pointer-events: none;\n}\n\n.theme-dark .card-lightbox__image-overlay {\n position: absolute;\n z-index: 1;\n background: linear-gradient(160deg, rgb(255 255 255 / 95%) -8%, rgb(255 255 255 / 0%) 30%);\n content: '';\n inset: 0;\n pointer-events: none;\n}\n\n.card-lightbox__expanded-container {\n position: fixed;\n z-index: 51;\n top: 0;\n right: 0;\n left: 0;\n display: flex;\n overflow: hidden;\n width: 100%;\n height: 100%;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 0;\n pointer-events: none;\n}\n\n.card-lightbox__card-content,\n.card-lightbox__expanded-content {\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 100%;\n border-radius: 20px;\n margin: 0 auto;\n background: hsl(var(--card));\n pointer-events: auto;\n user-select: none;\n}\n\n.card-lightbox__expanded-content {\n overflow: hidden;\n width: unset;\n max-width: 700px;\n height: 70dvh;\n overflow-y: auto;\n pointer-events: auto;\n}\n\n.card-lightbox__card-image-container,\n.card-lightbox__expanded-image-container {\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 420px;\n}\n\n.card-lightbox__card-image,\n.card-lightbox__expanded-image {\n position: absolute;\n z-index: 1;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n transform: none;\n}\n\n.card-lightbox__card-title-container {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n max-width: 300px;\n padding: 20px;\n text-align: left;\n}\n\n.card-lightbox__expanded-title-container {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n padding: 20px;\n}\n\n.card-lightbox__expanded-close-button {\n position: absolute;\n z-index: 2;\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 margin: 8px 0;\n color: hsl(0deg 0% 100%);\n font-size: 20px;\n font-weight: 600;\n}\n\n.card-lightbox__card-category,\n.card-lightbox__expanded-category {\n padding: 4px 8px;\n border-radius: 12px;\n backdrop-filter: blur(8px);\n background: hsl(0deg 0% 100% / 12%);\n color: hsl(0deg 0% 100%);\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(240deg 10% 3.9% / 20%);\n}\n\n.card-lightbox__expanded-overlay {\n position: fixed;\n z-index: 50;\n backdrop-filter: blur(12px);\n background: rgb(0 0 0 / 20%);\n inset: 0;\n will-change: opacity;\n}\n\n.card-lightbox__expanded-body {\n width: 100vw;\n max-width: 700px;\n padding: 35px;\n color: hsl(var(--primary));\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(240deg 10% 3.9% / 90%);\n}\n\n.theme-dark svg {\n stroke: hsl(240deg 10% 3.9% / 90%);\n}\n\n.theme-light svg {\n stroke: hsl(0deg 0% 100%);\n}\n\n@media only screen and (width <= 990px) {\n .card-lightbox__expanded-container {\n padding: 0;\n }\n\n .card-lightbox__expanded-content {\n overflow: hidden;\n width: 100vw;\n max-width: 100vw;\n height: 100dvh;\n border-radius: 0;\n overflow-y: auto;\n pointer-events: auto;\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-right: 10px;\n padding-left: 10px;\n }\n\n .card-lightbox__grid {\n gap: 10px;\n grid-template-columns: 1fr;\n }\n\n .card-lightbox__card {\n width: 100%;\n height: 280px;\n grid-column: auto / auto;\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 position: absolute;\n width: 100%;\n height: 100%;\n inset: 0;\n object-fit: cover;\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"
}