From bb5109c6d54c8287d1c5f776a3c00c31fccc084a Mon Sep 17 00:00:00 2001 From: Ando Date: Wed, 23 Oct 2024 15:35:56 +0300 Subject: [PATCH 1/2] feat: create hero section --- package.json | 1 + pnpm-lock.yaml | 24 +++ src/components/{ => common}/footer.tsx | 0 src/components/{ => common}/header.tsx | 0 src/components/sections/hero.tsx | 60 ++++++++ src/components/ui/animated-gradient-text.tsx | 26 ++++ src/components/ui/animated-grid-pattern.tsx | 150 +++++++++++++++++++ src/components/ui/blur-fade.tsx | 67 +++++++++ src/pages/index.tsx | 7 +- tailwind.config.ts | 108 +++++++------ 10 files changed, 391 insertions(+), 52 deletions(-) rename src/components/{ => common}/footer.tsx (100%) rename src/components/{ => common}/header.tsx (100%) create mode 100644 src/components/sections/hero.tsx create mode 100644 src/components/ui/animated-gradient-text.tsx create mode 100644 src/components/ui/animated-grid-pattern.tsx create mode 100644 src/components/ui/blur-fade.tsx diff --git a/package.json b/package.json index 47cab74..cbd973d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "framer-motion": "^11.11.9", "lucide-react": "^0.453.0", "next": "14.2.3", "next-themes": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f591cef..e8e52d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + framer-motion: + specifier: ^11.11.9 + version: 11.11.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.453.0 version: 0.453.0(react@18.3.1) @@ -1620,6 +1623,20 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + framer-motion@11.11.9: + resolution: {integrity: sha512-XpdZseuCrZehdHGuW22zZt3SF5g6AHJHJi7JwQIigOznW4Jg1n0oGPMJQheMaKLC+0rp5gxUKMRYI6ytd3q4RQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4992,6 +5009,13 @@ snapshots: format@0.2.2: {} + framer-motion@11.11.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + tslib: 2.8.0 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + fs.realpath@1.0.0: {} fsevents@2.3.3: diff --git a/src/components/footer.tsx b/src/components/common/footer.tsx similarity index 100% rename from src/components/footer.tsx rename to src/components/common/footer.tsx diff --git a/src/components/header.tsx b/src/components/common/header.tsx similarity index 100% rename from src/components/header.tsx rename to src/components/common/header.tsx diff --git a/src/components/sections/hero.tsx b/src/components/sections/hero.tsx new file mode 100644 index 0000000..02adc20 --- /dev/null +++ b/src/components/sections/hero.tsx @@ -0,0 +1,60 @@ +'use client' + +import { ChevronRight, Github } from 'lucide-react' +// import Link from 'next/link' + +import { Button } from '@/components/ui/button' + +import AnimatedGridPattern from '@/components/ui/animated-grid-pattern' +import BlurFade from '@/components/ui/blur-fade' + +import { cn } from '@/lib/utils' + +export const Hero = () => { + return ( +
+
+
+ +
+

Explore our documentations

+
+
+ +
+ +
+ + +
+
+
+
+
+ + +
+ ) +} diff --git a/src/components/ui/animated-gradient-text.tsx b/src/components/ui/animated-gradient-text.tsx new file mode 100644 index 0000000..19b0e62 --- /dev/null +++ b/src/components/ui/animated-gradient-text.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from "react"; + +import { cn } from "@/lib/utils"; + +export default function AnimatedGradientText({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( +
+
+ + {children} +
+ ); +} diff --git a/src/components/ui/animated-grid-pattern.tsx b/src/components/ui/animated-grid-pattern.tsx new file mode 100644 index 0000000..b74dc0c --- /dev/null +++ b/src/components/ui/animated-grid-pattern.tsx @@ -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 ( + + ); +} + +export default GridPattern; diff --git a/src/components/ui/blur-fade.tsx b/src/components/ui/blur-fade.tsx new file mode 100644 index 0000000..44e897e --- /dev/null +++ b/src/components/ui/blur-fade.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { useRef } from "react"; +import { + AnimatePresence, + motion, + useInView, + UseInViewOptions, + Variants, +} from "framer-motion"; + +type MarginType = UseInViewOptions["margin"]; + +interface BlurFadeProps { + children: React.ReactNode; + className?: string; + variant?: { + hidden: { y: number }; + visible: { y: number }; + }; + duration?: number; + delay?: number; + yOffset?: number; + inView?: boolean; + inViewMargin?: MarginType; + blur?: string; +} + +export default function BlurFade({ + children, + className, + variant, + duration = 0.4, + delay = 0, + yOffset = 6, + inView = false, + inViewMargin = "-50px", + blur = "6px", +}: BlurFadeProps) { + const ref = useRef(null); + const inViewResult = useInView(ref, { once: true, margin: inViewMargin }); + const isInView = !inView || inViewResult; + const defaultVariants: Variants = { + hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` }, + visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, + }; + const combinedVariants = variant || defaultVariants; + return ( + + + {children} + + + ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b749b78..68f34de 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,15 +3,16 @@ const inter = Inter({ subsets: ['latin'] }) import { ThemeProvider } from '@/components/theme-provider' -import { Header } from '@/components/header' -import { Footer } from '@/components/footer' +import { Header } from '@/components/common/header' +import { Footer } from '@/components/common/footer' +import { Hero } from '@/components/sections/hero' export default function Home() { return (
-

Hello docs

+
diff --git a/tailwind.config.ts b/tailwind.config.ts index 26f413b..7659b8c 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,55 +9,65 @@ const config: Config = { "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { - colors: { - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - chart: { - "1": "hsl(var(--chart-1))", - "2": "hsl(var(--chart-2))", - "3": "hsl(var(--chart-3))", - "4": "hsl(var(--chart-4))", - "5": "hsl(var(--chart-5))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - }, + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + animation: { + gradient: 'gradient 8s linear infinite' + }, + keyframes: { + gradient: { + to: { + backgroundPosition: 'var(--bg-size) 0' + } + } + } + } }, plugins: [tailwindcssAnimate], }; From eb8251be9c7b7677c41f20af3971272cafd65639 Mon Sep 17 00:00:00 2001 From: Ando Date: Wed, 23 Oct 2024 15:41:49 +0300 Subject: [PATCH 2/2] fix: eslint disable on animated grid pattern ui component --- src/components/ui/animated-gradient-text.tsx | 16 +-- src/components/ui/animated-grid-pattern.tsx | 113 +++++++++---------- src/components/ui/blur-fade.tsx | 62 +++++----- src/pages/docs/_meta.ts | 1 + src/pages/docs/index.mdx | 1 + 5 files changed, 91 insertions(+), 102 deletions(-) create mode 100644 src/pages/docs/index.mdx diff --git a/src/components/ui/animated-gradient-text.tsx b/src/components/ui/animated-gradient-text.tsx index 19b0e62..e6655a9 100644 --- a/src/components/ui/animated-gradient-text.tsx +++ b/src/components/ui/animated-gradient-text.tsx @@ -1,19 +1,19 @@ -import { ReactNode } from "react"; +import { ReactNode } from 'react' -import { cn } from "@/lib/utils"; +import { cn } from '@/lib/utils' export default function AnimatedGradientText({ children, - className, + className }: { - children: ReactNode; - className?: string; + children: ReactNode + className?: string }) { return (
- ); + ) } diff --git a/src/components/ui/animated-grid-pattern.tsx b/src/components/ui/animated-grid-pattern.tsx index b74dc0c..a8310e8 100644 --- a/src/components/ui/animated-grid-pattern.tsx +++ b/src/components/ui/animated-grid-pattern.tsx @@ -1,21 +1,24 @@ -"use client"; +/* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +'use client' -import { useEffect, useId, useRef, useState } from "react"; -import { motion } from "framer-motion"; +import { useEffect, useId, useRef, useState } from 'react' +import { motion } from 'framer-motion' -import { cn } from "@/lib/utils"; +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; + width?: number + height?: number + x?: number + y?: number + strokeDasharray?: any + numSquares?: number + className?: string + maxOpacity?: number + duration?: number + repeatDelay?: number } export function GridPattern({ @@ -31,24 +34,24 @@ export function GridPattern({ 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)); + 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), - ]; + 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(), - })); + pos: getPos() + })) } // Function to update a single square's position @@ -58,70 +61,60 @@ export function GridPattern({ sq.id === id ? { ...sq, - pos: getPos(), + pos: getPos() } - : sq, - ), - ); - }; + : sq + ) + ) + } // Update squares to animate in useEffect(() => { if (dimensions.width && dimensions.height) { - setSquares(generateSquares(numSquares)); + setSquares(generateSquares(numSquares)) } - }, [dimensions, numSquares]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dimensions, numSquares]) // Resize observer to update container dimensions useEffect(() => { const resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) { + for (const entry of entries) { setDimensions({ width: entry.contentRect.width, - height: entry.contentRect.height, - }); + height: entry.contentRect.height + }) } - }); + }) if (containerRef.current) { - resizeObserver.observe(containerRef.current); + resizeObserver.observe(containerRef.current) } return () => { if (containerRef.current) { - resizeObserver.unobserve(containerRef.current); + resizeObserver.unobserve(containerRef.current) } - }; - }, [containerRef]); + } + }, [containerRef]) return (