Skip to content

Commit

Permalink
hello magic🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
suvanbanerjee committed Aug 1, 2024
1 parent 5ddde64 commit e5820e1
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 100 deletions.
1 change: 1 addition & 0 deletions app/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
}
}


19 changes: 19 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/css/style.css",
"baseColor": "slate",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"magicui": "@/components/magicui"
}
}
28 changes: 27 additions & 1 deletion components/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
'use client'

import Image from 'next/image'
import Logo from '@/public/images/logo.png'
import React from 'react'
import React, { useRef } from "react";
import Link from 'next/link'
import Release from './release'
import { cn } from "@/lib/utils";
import DotPattern from "@/components/magicui/dot-pattern";
import type { ConfettiRef } from "@/components/magicui/confetti";
import Confetti from "@/components/magicui/confetti";


export default function Hero() {
const confettiRef = useRef<ConfettiRef>(null);
return (
<section className="relative md:h-screen flex justify-center items-center">
<div className="max-w-6xl mx-auto px-4 sm:px-6 ">
Expand All @@ -22,10 +30,28 @@ export default function Hero() {
</div>
</div>
<Release />
<Confetti
ref={confettiRef}
particleCount={100}
className="absolute left-0 top-0 z-0 w-full h-full"
onLoad={() => {
confettiRef.current?.fire({});
}}
/>
</div>
</div>
</div>
</div>
<DotPattern
width={20}
height={20}
cx={1}
cy={1}
cr={1}
className={cn(
"[mask-image:linear-gradient(to_bottom_right,white,transparent,transparent)] ",
)}
/>
</section>
)
}
150 changes: 150 additions & 0 deletions components/magicui/animated-grid-pattern.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"use client";

import { useEffect, useId, useRef, useState } from "react";
import { motion } from "framer-motion";

import { cn } from "@/lib/utils";

interface GridPatternProps {
width?: number;
height?: number;
x?: number;
y?: number;
strokeDasharray?: any;
numSquares?: number;
className?: string;
maxOpacity?: number;
duration?: number;
repeatDelay?: number;
}

export function GridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
numSquares = 50,
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: GridPatternProps) {
const id = useId();
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [squares, setSquares] = useState(() => generateSquares(numSquares));

function getPos() {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
];
}

// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}));
}

// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq,
),
);
};

// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares));
}
}, [dimensions, numSquares]);

// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
});
}
});

if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}

return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);

return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
fill="currentColor"
strokeWidth="0"
/>
))}
</svg>
</svg>
);
}

export default GridPattern;
117 changes: 117 additions & 0 deletions components/magicui/confetti.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { ReactNode } from "react";
import React, {
createContext,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from "react";
import confetti from "canvas-confetti";
import type {
GlobalOptions as ConfettiGlobalOptions,
CreateTypes as ConfettiInstance,
Options as ConfettiOptions,
} from "canvas-confetti";

type Api = {
fire: (options?: ConfettiOptions) => void;
};

type Props = React.ComponentPropsWithRef<"canvas"> & {
options?: ConfettiOptions;
globalOptions?: ConfettiGlobalOptions;
manualstart?: boolean;
children?: ReactNode;
};

export type ConfettiRef = Api | null;

const ConfettiContext = createContext<Api>({} as Api);

const Confetti = forwardRef<ConfettiRef, Props>((props, ref) => {
const {
options,
globalOptions = { resize: true, useWorker: true },
manualstart = false,
children,
...rest
} = props;
const instanceRef = useRef<ConfettiInstance | null>(null); // confetti instance

const canvasRef = useCallback(
(node: HTMLCanvasElement) => {
if (node !== null) {
if (instanceRef.current) return; // if not already created
instanceRef.current = confetti.create(node, {
...globalOptions,
resize: true,
});
} else {
if (instanceRef.current) {
instanceRef.current.reset();
instanceRef.current = null;
}
}
},
[globalOptions],
);

const fire = useCallback(
(opts = {}) => instanceRef.current?.({ ...options, ...opts }),
[options],
);

const api = useMemo(
() => ({
fire,
}),
[fire],
);

useImperativeHandle(ref, () => api, [api]);

useEffect(() => {
if (!manualstart) {
fire();
}
}, [manualstart, fire]);

return (
<ConfettiContext.Provider value={api}>
<canvas ref={canvasRef} {...rest} />
{children}
</ConfettiContext.Provider>
);
});

interface ConfettiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
options?: ConfettiOptions & ConfettiGlobalOptions & { canvas?: HTMLCanvasElement };
children?: React.ReactNode;
}

function ConfettiButton({ options, children, ...props }: ConfettiButtonProps) {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
confetti({
...options,
origin: {
x: x / window.innerWidth,
y: y / window.innerHeight,
},
});
};

return (
<button onClick={handleClick} {...props}>
{children}
</button>
);
}

export { Confetti, ConfettiButton };

export default Confetti;
Loading

0 comments on commit e5820e1

Please sign in to comment.