From 8ab37f70591bfdb379aeab43bf89c9b3fa56f4ca Mon Sep 17 00:00:00 2001 From: Tom Elliott Date: Sat, 31 May 2025 16:48:43 +1200 Subject: [PATCH] basic scrolling --- payload-types.ts | 106 ++++++++++- .../components/Home/Hero/01-intro.tsx | 10 +- .../components/Home/Hero/02-data.tsx | 99 +++++++--- .../components/Home/ScrollingNumbers.tsx | 8 +- src/app/(website)/components/SmoothScroll.tsx | 20 +- src/app/(website)/hooks/useWindow.ts | 22 +++ src/app/(website)/page.tsx | 10 +- src/globals/Home/Hero.ts | 177 ++++++++++-------- 8 files changed, 332 insertions(+), 120 deletions(-) create mode 100644 src/app/(website)/hooks/useWindow.ts diff --git a/payload-types.ts b/payload-types.ts index 41f960a..08585cc 100644 --- a/payload-types.ts +++ b/payload-types.ts @@ -587,12 +587,98 @@ export interface HomeHero { }; [k: string]: unknown; }; - heroItems?: - | { - name: string; - id?: string | null; - }[] - | null; + heroItems: { + heroDataDesign: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + heroDataCollection: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + heroDataAnalysis: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + heroDataVisualisation: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + heroTraining: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + heroDataSovereignty: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + }; + }; }; /** * This title will be used for SEO purposes, and displayed in the browser tab. @@ -658,8 +744,12 @@ export interface HomeHeroSelect { heroItems?: | T | { - name?: T; - id?: T; + heroDataDesign?: T; + heroDataCollection?: T; + heroDataAnalysis?: T; + heroDataVisualisation?: T; + heroTraining?: T; + heroDataSovereignty?: T; }; }; metaTitle?: T; diff --git a/src/app/(website)/components/Home/Hero/01-intro.tsx b/src/app/(website)/components/Home/Hero/01-intro.tsx index 09e7381..f25be00 100644 --- a/src/app/(website)/components/Home/Hero/01-intro.tsx +++ b/src/app/(website)/components/Home/Hero/01-intro.tsx @@ -1,7 +1,7 @@ "use client"; import { RichText } from "@payloadcms/richtext-lexical/react"; -import { motion, MotionValue, useScroll, useTransform } from "motion/react"; +import { motion, useScroll, useTransform } from "motion/react"; import { useRef, useEffect, useState, useCallback } from "react"; import * as d3 from "d3"; @@ -54,7 +54,7 @@ export default function HeroIntro({ }, [windowSize, beamSize]); return ( -
@@ -70,7 +70,7 @@ export default function HeroIntro({
-
+
-
+ ); } diff --git a/src/app/(website)/components/Home/Hero/02-data.tsx b/src/app/(website)/components/Home/Hero/02-data.tsx index 9e3ab0e..9ba5a33 100644 --- a/src/app/(website)/components/Home/Hero/02-data.tsx +++ b/src/app/(website)/components/Home/Hero/02-data.tsx @@ -1,37 +1,88 @@ "use client"; -import { useScroll } from "motion/react"; -import { useRef } from "react"; +import { + cubicBezier, + easeIn, + easeInOut, + easeOut, + motion, + useScroll, + useTransform, +} from "motion/react"; +import { useEffect, useRef, useState } from "react"; +import type { HomeHero } from "@payload-types"; +import { RichText } from "@payloadcms/richtext-lexical/react"; +import { easeLinear } from "d3"; +import useWindow from "@/app/(website)/hooks/useWindow"; + +const heroMap = { + heroDataDesign: "Data Design", + heroDataCollection: "Data Collection", + heroDataAnalysis: "Data Analysis", + heroDataVisualisation: "Data Visualisation", + heroTraining: "Training", + heroDataSovereignty: "Data Sovereignty", +} as const; + +type HeroItems = HomeHero["heroGroup"]["heroItems"]; +type HeroItem = HeroItems[keyof HeroItems]; + +export default function HeroData({ + items, +}: { + items: HomeHero["heroGroup"]["heroItems"]; +}) { + const itemKeys: (keyof typeof items)[] = Object.keys(items) as any; + const itemArray = itemKeys.map((k) => ({ key: k, ...items[k] })); + + return ( +
+ {itemArray.map((item) => ( + + ))} +
+ ); +} + +const Item = ({ title, item }: { title: string; item: HeroItem }) => { + const { height } = useWindow(); -export default function HeroData() { const containerRef = useRef(null); - const scrollYProgress = useScroll({ + const { scrollYProgress } = useScroll({ target: containerRef, - offset: ["start end", "end end"], + offset: ["start end", "end start"], }); + + const yp = 0.7; + const yoffset = useTransform( + scrollYProgress, + [0.2, 0.8], + [-height * yp, height * yp] + ); + const opacity = useTransform(scrollYProgress, [0.3, 0.5, 0.7], [0, 1, 0]); + return (
-
-
- IMAGE -
-
-
Data design
-

- We design research projects that are efficient, effective and - tailored to your information needs, and can advise on, supervise, or - review projects — including through a Māori research lens. -

-
+
+ IMAGE +
+
+ +
{title}
+ + + +
-
-
-
-
-
); -} +}; diff --git a/src/app/(website)/components/Home/ScrollingNumbers.tsx b/src/app/(website)/components/Home/ScrollingNumbers.tsx index 686f15b..c341084 100644 --- a/src/app/(website)/components/Home/ScrollingNumbers.tsx +++ b/src/app/(website)/components/Home/ScrollingNumbers.tsx @@ -5,12 +5,12 @@ import { letters } from "./letters"; export default function ScrollingNumbers({ numbers }: { numbers: string[][] }) { return ( -
-
+
+
{numbers.map((col, i) => ( ))} -
+
); @@ -34,7 +34,7 @@ const NumberCol = ({ col }: { col: string[] }) => { className="flex-1 flex flex-col items-center" > {col.map((num, j) => ( -
+
{num}
))} diff --git a/src/app/(website)/components/SmoothScroll.tsx b/src/app/(website)/components/SmoothScroll.tsx index b2dd60b..d67b340 100644 --- a/src/app/(website)/components/SmoothScroll.tsx +++ b/src/app/(website)/components/SmoothScroll.tsx @@ -2,8 +2,15 @@ import { ReactNode, useEffect } from "react"; import Lenis from "lenis"; +import Snap from "lenis/snap"; -export default function ({ children }: { children: ReactNode }) { +export default function ({ + children, + snapAt, +}: { + children: ReactNode; + snapAt?: string[]; +}) { useEffect(() => { const lenis = new Lenis(); @@ -14,6 +21,17 @@ export default function ({ children }: { children: ReactNode }) { } requestAnimationFrame(raf); + + // const snap = new Snap(lenis, { + // type: "proximity", + // velocityThreshold: 1, + // debounce: 0, + // }); + // snapAt?.forEach((id) => { + // const el = document.querySelector(id); + // if (!el) return; + // snap.addElement(el, { align: ["center"] }); + // }); }, []); return <>{children}; } diff --git a/src/app/(website)/hooks/useWindow.ts b/src/app/(website)/hooks/useWindow.ts new file mode 100644 index 0000000..1f27627 --- /dev/null +++ b/src/app/(website)/hooks/useWindow.ts @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; + +export default function useWindow() { + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (!window) return; + setWidth(window.innerWidth); + setHeight(window.innerHeight); + + window.addEventListener("resize", () => { + setWidth(window.innerWidth); + setHeight(window.innerHeight); + }); + }, []); + + return { + width, + height, + }; +} diff --git a/src/app/(website)/page.tsx b/src/app/(website)/page.tsx index 42413cd..7718d3c 100644 --- a/src/app/(website)/page.tsx +++ b/src/app/(website)/page.tsx @@ -6,8 +6,8 @@ import HeroIntro from "./components/Home/Hero/01-intro"; import SmoothScroll from "./components/SmoothScroll"; import HeroData from "./components/Home/Hero/02-data"; -const randomNumbers = Array.from({ length: 50 }).map((i) => - Array.from({ length: 200 }).map( +const randomNumbers = Array.from({ length: 10 }).map((i) => + Array.from({ length: 20 }).map( (j) => letters[Math.floor(Math.random() * letters.length)] ) ); @@ -19,10 +19,10 @@ export default async function Home() { }); return ( - +
- + {/* */}

{titleGroup.title}

@@ -31,7 +31,7 @@ export default async function Home() { title={heroGroup.heroTitle} desc={heroGroup.heroDescription} /> - +
); diff --git a/src/globals/Home/Hero.ts b/src/globals/Home/Hero.ts index cbde355..af60d9d 100644 --- a/src/globals/Home/Hero.ts +++ b/src/globals/Home/Hero.ts @@ -1,83 +1,114 @@ import { GlobalConfig } from "payload"; export const HomeHero: GlobalConfig = { - slug: 'homeHero', - label: 'Hero', - fields: [ + slug: "homeHero", + label: "Hero", + fields: [ + { + name: "titleGroup", + label: "Landing page", + type: "group", + fields: [ { - name: 'titleGroup', - label: 'Landing page', - type: 'group', - fields: [ - { - name: 'title', - label: 'Title', - type: 'text', - required: true, - defaultValue: 'Analytics, research, and data visualisation that make a difference' - }, - ], + name: "title", + label: "Title", + type: "text", + required: true, + defaultValue: + "Analytics, research, and data visualisation that make a difference", + }, + ], + }, + { + name: "heroGroup", + label: "Hero", + type: "group", + fields: [ + { + name: "heroTitle", + label: "Hero Title", + type: "text", + required: true, + defaultValue: "We help people tell stories with data", }, { - name: 'heroGroup', - label: 'Hero', - type: 'group', - fields: [ - { - name: 'heroTitle', - label: 'Hero Title', - type: 'text', - required: true, - defaultValue: 'We help people tell stories with data' - }, - { - name: 'heroDescription', - label: 'Hero Description', - type: 'richText', - required: true, - }, - { - name: 'heroItems', - label: 'Items', - type: 'array', - fields: [ - { - name: 'name', - label: 'Name', - type: 'text', - required: true, - } - ], - defaultValue: ["Data design", "Data collection", "Data analysis", "Data visualisation", "Training", "Data sovereignty"].map((item) => ({ - name: item, - })), - }, - ] + name: "heroDescription", + label: "Hero Description", + type: "richText", + required: true, }, { - name: 'metaTitle', - label: 'Meta Title', - type: 'text', - defaultValue: 'iNZight Analytics Ltd', - required: true, - admin: { - position: 'sidebar', - description: 'This title will be used for SEO purposes, and displayed in the browser tab.', - } + name: "heroItems", + label: "Items", + type: "group", + fields: [ + { + name: "heroDataDesign", + label: "Data Design", + type: "richText", + required: true, + }, + { + name: "heroDataCollection", + label: "Data Collection", + type: "richText", + required: true, + }, + { + name: "heroDataAnalysis", + label: "Data Analysis", + type: "richText", + required: true, + }, + { + name: "heroDataVisualisation", + label: "Data Visualisation", + type: "richText", + required: true, + }, + { + name: "heroTraining", + label: "Training", + type: "richText", + required: true, + }, + { + name: "heroDataSovereignty", + label: "Data Sovereignty", + type: "richText", + required: true, + }, + ], }, - { - name: 'metaDescription', - label: 'Meta Description', - type: 'textarea', - required: true, - defaultValue: 'iNZight Analytics Ltd is a New Zealand-based company that provides data analysis and visualisation services.', - admin: { - position: 'sidebar', - description: 'This description will be used for SEO purposes (e.g., shown in search results and on social media cards).', - } - }, - ], - admin: { - group: 'Home page' - } + ], + }, + { + name: "metaTitle", + label: "Meta Title", + type: "text", + defaultValue: "iNZight Analytics Ltd", + required: true, + admin: { + position: "sidebar", + description: + "This title will be used for SEO purposes, and displayed in the browser tab.", + }, + }, + { + name: "metaDescription", + label: "Meta Description", + type: "textarea", + required: true, + defaultValue: + "iNZight Analytics Ltd is a New Zealand-based company that provides data analysis and visualisation services.", + admin: { + position: "sidebar", + description: + "This description will be used for SEO purposes (e.g., shown in search results and on social media cards).", + }, + }, + ], + admin: { + group: "Home page", + }, };