fix ChromaGrid errors

This commit is contained in:
David Haz
2025-07-18 11:33:13 +03:00
parent 63d194ddd9
commit 992d451064
3 changed files with 118 additions and 124 deletions

View File

@@ -5,7 +5,7 @@ export const chromaGrid: CodeObject = {
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ChromaGrid`, cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ChromaGrid`,
installation: `npm install gsap`, installation: `npm install gsap`,
usage: `<template> usage: `<template>
<div style="height: 600px; position: relative"> <div class="w-[600px] relative">
<ChromaGrid <ChromaGrid
:items="items" :items="items"
:radius="300" :radius="300"

View File

@@ -1,25 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue';
import gsap from 'gsap' import gsap from 'gsap';
interface CardItem { interface CardItem {
image: string image: string;
title: string title: string;
subtitle: string subtitle: string;
handle?: string handle?: string;
borderColor?: string borderColor?: string;
gradient?: string gradient?: string;
url?: string url?: string;
location?: string location?: string;
} }
interface GridMotionProps { interface GridMotionProps {
items?: CardItem[] items?: CardItem[];
className?: string className?: string;
radius?: number radius?: number;
damping?: number damping?: number;
fadeOut?: number fadeOut?: number;
ease?: string ease?: string;
} }
const props = withDefaults(defineProps<GridMotionProps>(), { const props = withDefaults(defineProps<GridMotionProps>(), {
@@ -28,85 +28,85 @@ const props = withDefaults(defineProps<GridMotionProps>(), {
radius: 300, radius: 300,
damping: 0.45, damping: 0.45,
fadeOut: 0.6, fadeOut: 0.6,
ease: 'power3.out', ease: 'power3.out'
}) });
const rootRef = ref<HTMLElement | null>(null) const rootRef = ref<HTMLElement | null>(null);
const fadeRef = ref<HTMLElement | null>(null) const fadeRef = ref<HTMLElement | null>(null);
const setX = ref<any>(null) const setX = ref<((value: number | string) => void) | null>(null);
const setY = ref<any>(null) const setY = ref<((value: number | string) => void) | null>(null);
const pos = ref({ x: 0, y: 0 }) const pos = ref({ x: 0, y: 0 });
const demo: CardItem[] = [ const demo: CardItem[] = [
{ {
image: "https://i.pravatar.cc/300?img=8", image: 'https://i.pravatar.cc/300?img=8',
title: "Alex Rivera", title: 'Alex Rivera',
subtitle: "Full Stack Developer", subtitle: 'Full Stack Developer',
handle: "@alexrivera", handle: '@alexrivera',
borderColor: "#4F46E5", borderColor: '#4F46E5',
gradient: "linear-gradient(145deg,#4F46E5,#000)", gradient: 'linear-gradient(145deg,#4F46E5,#000)',
url: "https://github.com/", url: 'https://github.com/'
}, },
{ {
image: "https://i.pravatar.cc/300?img=11", image: 'https://i.pravatar.cc/300?img=11',
title: "Jordan Chen", title: 'Jordan Chen',
subtitle: "DevOps Engineer", subtitle: 'DevOps Engineer',
handle: "@jordanchen", handle: '@jordanchen',
borderColor: "#10B981", borderColor: '#10B981',
gradient: "linear-gradient(210deg,#10B981,#000)", gradient: 'linear-gradient(210deg,#10B981,#000)',
url: "https://linkedin.com/in/", url: 'https://linkedin.com/in/'
}, },
{ {
image: "https://i.pravatar.cc/300?img=3", image: 'https://i.pravatar.cc/300?img=3',
title: "Morgan Blake", title: 'Morgan Blake',
subtitle: "UI/UX Designer", subtitle: 'UI/UX Designer',
handle: "@morganblake", handle: '@morganblake',
borderColor: "#F59E0B", borderColor: '#F59E0B',
gradient: "linear-gradient(165deg,#F59E0B,#000)", gradient: 'linear-gradient(165deg,#F59E0B,#000)',
url: "https://dribbble.com/", url: 'https://dribbble.com/'
}, },
{ {
image: "https://i.pravatar.cc/300?img=16", image: 'https://i.pravatar.cc/300?img=16',
title: "Casey Park", title: 'Casey Park',
subtitle: "Data Scientist", subtitle: 'Data Scientist',
handle: "@caseypark", handle: '@caseypark',
borderColor: "#EF4444", borderColor: '#EF4444',
gradient: "linear-gradient(195deg,#EF4444,#000)", gradient: 'linear-gradient(195deg,#EF4444,#000)',
url: "https://kaggle.com/", url: 'https://kaggle.com/'
}, },
{ {
image: "https://i.pravatar.cc/300?img=25", image: 'https://i.pravatar.cc/300?img=25',
title: "Sam Kim", title: 'Sam Kim',
subtitle: "Mobile Developer", subtitle: 'Mobile Developer',
handle: "@thesamkim", handle: '@thesamkim',
borderColor: "#8B5CF6", borderColor: '#8B5CF6',
gradient: "linear-gradient(225deg,#8B5CF6,#000)", gradient: 'linear-gradient(225deg,#8B5CF6,#000)',
url: "https://github.com/", url: 'https://github.com/'
}, },
{ {
image: "https://i.pravatar.cc/300?img=60", image: 'https://i.pravatar.cc/300?img=60',
title: "Tyler Rodriguez", title: 'Tyler Rodriguez',
subtitle: "Cloud Architect", subtitle: 'Cloud Architect',
handle: "@tylerrod", handle: '@tylerrod',
borderColor: "#06B6D4", borderColor: '#06B6D4',
gradient: "linear-gradient(135deg,#06B6D4,#000)", gradient: 'linear-gradient(135deg,#06B6D4,#000)',
url: "https://aws.amazon.com/", url: 'https://aws.amazon.com/'
}, }
] ];
const data = computed(() => (props.items.length ? props.items : demo)) const data = computed(() => (props.items.length ? props.items : demo));
onMounted(() => { onMounted(() => {
const el = rootRef.value const el = rootRef.value;
if (!el) return if (!el) return;
setX.value = gsap.quickSetter(el, '--x', 'px') setX.value = gsap.quickSetter(el, '--x', 'px') as (value: number | string) => void;
setY.value = gsap.quickSetter(el, '--y', 'px') setY.value = gsap.quickSetter(el, '--y', 'px') as (value: number | string) => void;
const { width, height } = el.getBoundingClientRect() const { width, height } = el.getBoundingClientRect();
pos.value = { x: width / 2, y: height / 2 } pos.value = { x: width / 2, y: height / 2 };
setX.value(pos.value.x) setX.value?.(pos.value.x);
setY.value(pos.value.y) setY.value?.(pos.value.y);
}) });
const moveTo = (x: number, y: number) => { const moveTo = (x: number, y: number) => {
gsap.to(pos.value, { gsap.to(pos.value, {
@@ -115,38 +115,38 @@ const moveTo = (x: number, y: number) => {
duration: props.damping, duration: props.damping,
ease: props.ease, ease: props.ease,
onUpdate: () => { onUpdate: () => {
setX.value?.(pos.value.x) setX.value?.(pos.value.x);
setY.value?.(pos.value.y) setY.value?.(pos.value.y);
}, },
overwrite: true, overwrite: true
}) });
} };
const handleMove = (e: PointerEvent) => { const handleMove = (e: PointerEvent) => {
const r = rootRef.value?.getBoundingClientRect() const r = rootRef.value?.getBoundingClientRect();
if (!r) return if (!r) return;
moveTo(e.clientX - r.left, e.clientY - r.top) moveTo(e.clientX - r.left, e.clientY - r.top);
gsap.to(fadeRef.value, { opacity: 0, duration: 0.25, overwrite: true }) gsap.to(fadeRef.value, { opacity: 0, duration: 0.25, overwrite: true });
} };
const handleLeave = () => { const handleLeave = () => {
gsap.to(fadeRef.value, { gsap.to(fadeRef.value, {
opacity: 1, opacity: 1,
duration: props.fadeOut, duration: props.fadeOut,
overwrite: true, overwrite: true
}) });
} };
const handleCardClick = (url?: string) => { const handleCardClick = (url?: string) => {
if (url) window.open(url, "_blank", "noopener,noreferrer") if (url) window.open(url, '_blank', 'noopener,noreferrer');
} };
const handleCardMove = (e: MouseEvent) => { const handleCardMove = (e: MouseEvent) => {
const c = e.currentTarget as HTMLElement const c = e.currentTarget as HTMLElement;
const rect = c.getBoundingClientRect() const rect = c.getBoundingClientRect();
c.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`) c.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`);
c.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`) c.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`);
} };
const spotlightStyle = { const spotlightStyle = {
backdropFilter: 'grayscale(1) brightness(0.78)', backdropFilter: 'grayscale(1) brightness(0.78)',
@@ -155,8 +155,8 @@ const spotlightStyle = {
maskImage: maskImage:
'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)', 'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)',
WebkitMaskImage: WebkitMaskImage:
'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)', 'radial-gradient(circle var(--r) at var(--x) var(--y),transparent 0%,transparent 15%,rgba(0,0,0,0.10) 30%,rgba(0,0,0,0.22)45%,rgba(0,0,0,0.35)60%,rgba(0,0,0,0.50)75%,rgba(0,0,0,0.68)88%,white 100%)'
} };
const fadeStyle = { const fadeStyle = {
...spotlightStyle, ...spotlightStyle,
@@ -164,8 +164,8 @@ const fadeStyle = {
'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)', 'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)',
WebkitMaskImage: WebkitMaskImage:
'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)', 'radial-gradient(circle var(--r) at var(--x) var(--y),white 0%,white 15%,rgba(255,255,255,0.90)30%,rgba(255,255,255,0.78)45%,rgba(255,255,255,0.65)60%,rgba(255,255,255,0.50)75%,rgba(255,255,255,0.32)88%,transparent 100%)',
opacity: 1, opacity: 1
} };
</script> </script>
<template> <template>
@@ -175,7 +175,7 @@ const fadeStyle = {
:style="{ :style="{
'--r': `${props.radius}px`, '--r': `${props.radius}px`,
'--x': '50%', '--x': '50%',
'--y': '50%', '--y': '50%'
}" }"
@pointermove="handleMove" @pointermove="handleMove"
@pointerleave="handleLeave" @pointerleave="handleLeave"
@@ -187,7 +187,7 @@ const fadeStyle = {
:style="{ :style="{
'--card-border': c.borderColor || 'transparent', '--card-border': c.borderColor || 'transparent',
background: c.gradient, background: c.gradient,
'--spotlight-color': 'rgba(255,255,255,0.3)', '--spotlight-color': 'rgba(255,255,255,0.3)'
}" }"
@mousemove="handleCardMove" @mousemove="handleCardMove"
@click="() => handleCardClick(c.url)" @click="() => handleCardClick(c.url)"
@@ -195,16 +195,12 @@ const fadeStyle = {
<div <div
class="absolute inset-0 pointer-events-none transition-opacity duration-500 z-20 opacity-0 group-hover:opacity-100" class="absolute inset-0 pointer-events-none transition-opacity duration-500 z-20 opacity-0 group-hover:opacity-100"
:style="{ :style="{
background: 'radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 70%)', background:
'radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 70%)'
}" }"
/> />
<div class="relative z-10 flex-1 p-[10px] box-border"> <div class="relative z-10 flex-1 p-[10px] box-border">
<img <img :src="c.image" :alt="c.title" loading="lazy" class="w-full h-full object-cover rounded-[10px]" />
:src="c.image"
:alt="c.title"
loading="lazy"
class="w-full h-full object-cover rounded-[10px]"
/>
</div> </div>
<footer class="relative z-10 p-3 text-white font-sans grid grid-cols-[1fr_auto] gap-x-3 gap-y-1"> <footer class="relative z-10 p-3 text-white font-sans grid grid-cols-[1fr_auto] gap-x-3 gap-y-1">
<h3 class="m-0 text-[1.05rem] font-semibold">{{ c.title }}</h3> <h3 class="m-0 text-[1.05rem] font-semibold">{{ c.title }}</h3>
@@ -222,4 +218,3 @@ const fadeStyle = {
/> />
</div> </div>
</template> </template>

View File

@@ -19,55 +19,54 @@
</TabbedLayout> </TabbedLayout>
</template> </template>
<script setup> <script setup lang="ts">
import CliInstallation from '../../components/code/CliInstallation.vue'; import CliInstallation from '../../components/code/CliInstallation.vue';
import CodeExample from '../../components/code/CodeExample.vue'; import CodeExample from '../../components/code/CodeExample.vue';
import Dependencies from '../../components/code/Dependencies.vue'; import Dependencies from '../../components/code/Dependencies.vue';
import PropTable from '../../components/common/PropTable.vue'; import PropTable from '../../components/common/PropTable.vue';
import TabbedLayout from '../../components/common/TabbedLayout.vue'; import TabbedLayout from '../../components/common/TabbedLayout.vue';
import { chromaGrid } from '../../constants/code/Components/chromaGridCode';
import { chromaGrid } from '../../constants/code/Components/chromaGridCode' import ChromaGrid from '../../content/Components/ChromaGrid/ChromaGrid.vue';
import ChromaGrid from '../../content/Components/ChromaGrid/ChromaGrid.vue'
const propData = [ const propData = [
{ {
name: 'items', name: 'items',
type: 'Array', type: 'Array',
default: 'Demo []', default: 'Demo []',
description: 'Array of ChromaItem objects to display in the grid', description: 'Array of ChromaItem objects to display in the grid'
}, },
{ {
name: 'className', name: 'className',
type: 'string', type: 'string',
default: "''", default: "''",
description: 'Additional CSS classes to apply to the grid container', description: 'Additional CSS classes to apply to the grid container'
}, },
{ {
name: 'radius', name: 'radius',
type: 'number', type: 'number',
default: '300', default: '300',
description: 'Size of the spotlight effect in pixels', description: 'Size of the spotlight effect in pixels'
}, },
{ {
name: 'damping', name: 'damping',
type: 'number', type: 'number',
default: '0.45', default: '0.45',
description: 'Cursor follow animation duration in seconds', description: 'Cursor follow animation duration in seconds'
}, },
{ {
name: 'fadeOut', name: 'fadeOut',
type: 'number', type: 'number',
default: '0.6', default: '0.6',
description: 'Fade-out animation duration in seconds when mouse leaves', description: 'Fade-out animation duration in seconds when mouse leaves'
}, },
{ {
name: 'ease', name: 'ease',
type: 'string', type: 'string',
default: "'power3.out'", default: "'power3.out'",
description: 'GSAP easing function for animations', description: 'GSAP easing function for animations'
}, }
] ];
</script> </script>
<style scoped> <style scoped>