Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added some more GSAP animations to components #6

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function RootLayout({
<html lang="en">
<body className={`${inter_tight.className} flex flex-col min-h-screen`}>
<Navbar links={links} />
<div className="content my-6">{children}</div>
<main className="content my-6">{children}</main>
<Footer />
</body>
</html>
Expand Down
24 changes: 6 additions & 18 deletions app/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import path from "path";
import Link from "next/link";
import { dateComparator } from "@/app/utils";
import { PostMetadata } from "@/components/post";
import { parse } from "yaml";
import matter from "gray-matter";

interface RawPostMetadata {
title: string;
date: string;
editDate?: string;
author: string;
keywords: string[];
description: string;
slug: string;
Expand All @@ -29,21 +28,11 @@ async function getPosts() {
const raw_metadata: RawPostMetadata[] = await Promise.all(
folders.map(async ({fullpath, slug}) => {
const filename = `${fullpath}/page.mdx`;
const main = await readFile(filename);
const content = main.toString().split("\n");

let index = 0;
if (content[index++] !== "---") {
console.error(`Invalid MDX file "${filename}": no frontmatter found`);
} else {
while (index < content.length && content[index] != "---") {
++index;
}
return {
slug,
...parse(content.slice(1, index).join("\n"))
};
}
const contents = {
...matter(await readFile(filename)).data,
slug
} as RawPostMetadata;
return contents;
})
);

Expand All @@ -68,7 +57,6 @@ function PostInfo({
title,
date,
editDate,
author,
slug,
description,
}: PostMetadata) {
Expand Down
75 changes: 64 additions & 11 deletions components/dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from "react";
import { useState, useRef } from "react";
import Icon from "./icon";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";

interface DropdownProps {
icon: string;
Expand All @@ -9,23 +11,74 @@ interface DropdownProps {

export default function Dropdown({ icon, title, children }: DropdownProps) {
const [contentVisible, setContentVisibility] = useState(false);
const [visibleAnim, setVisibleAnim] = useState<gsap.core.Timeline | null>(
null
);

const dropdown = useRef<HTMLDivElement>(null);
const content = useRef<HTMLDivElement>(null);
const arrow = useRef<HTMLDivElement>(null);

const { contextSafe } = useGSAP(
() => {
let tl = gsap.timeline({ paused: true });
tl.fromTo(
content.current,
{
yPercent: -100,
opacity: 0,
},
{
yPercent: 0,
duration: 1,
opacity: 1,
onStart: () => setContentVisibility(true),
onReverseComplete: () => setContentVisibility(false),
ease: "power3.out",
}
);
tl.to(
arrow.current,
{
rotate: 180,
duration: 1,
ease: "power3.out",
},
0
);
setVisibleAnim(tl);
},
{ scope: dropdown }
);

const toggle = contextSafe(() => {
if (contentVisible) {
visibleAnim?.timeScale(2).reverse();
} else {
visibleAnim?.play();
}
});

return (
<div>
<div ref={dropdown}>
<button
type="button"
onClick={() => setContentVisibility(!contentVisible)}
className="hover:text-blue-500 transition"
onClick={toggle}
className="hover:text-blue-500 flex flex-row items-center gap-2"
>
<div className="flex flex-row items-center gap-2">
<Icon name={contentVisible ? "caret_up" : "caret_down"} />
<h1>{title}</h1>
<Icon name={icon} />
<div ref={arrow}>
<Icon name="caret_up" />
</div>
<h1>{title}</h1>
<Icon name={icon} />
</button>
{contentVisible ? (
<div className="flex flex-col ml-6 my-2 items-left">{children}</div>
) : null}
<div className="overflow-hidden">
<div ref={content}>
{contentVisible && (
<div className="flex flex-col ml-6 my-2 items-left">{children}</div>
)}
</div>
</div>
</div>
);
}
172 changes: 114 additions & 58 deletions components/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { useRef, useState } from "react";
import Icon from "./icon";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";

const DELAY_FACTOR = 0.2;

interface NavbarItemProps {
title: string;
href: string;
active: boolean;
}

export interface NavbarItem {
interface NavbarItemDropdownProps {
title: string;
href: string;
active: boolean;
timeline: gsap.core.Timeline | null;
index: number;
}

interface NavbarProps {
links: NavbarItem[];
export interface NavbarItem {
title: string;
href: string;
}

interface NavbarSubtypeProps {
interface NavbarProps {
links: NavbarItem[];
pathname: string;
}

function NavbarRowItem({ title, href, active }: NavbarItemProps) {
Expand All @@ -37,48 +44,37 @@ function NavbarRowItem({ title, href, active }: NavbarItemProps) {
);
}

function NavbarDropdownItem({ title, href, active }: NavbarItemProps) {
return (
<Link
className={`block w-full ${
active ? "font-bold" : "font-normal text-gray-600 dark:text-gray-400"
} hover:text-blue-500 py-2 pl-3 transition flex flew-row items-center gap-2`}
href={href}
>
{active ? <Icon name="arrow-right-sharp" /> : <></>} {title}
</Link>
);
}
function NavbarDropdownItem({
title,
href,
active,
timeline,
index,
}: NavbarItemDropdownProps) {
const linkRef = useRef<HTMLAnchorElement>(null);

function NavbarRow({ links, pathname }: NavbarSubtypeProps) {
return (
<ul className="flex-row sm:flex hidden divide-x-2 divide-gray-400 text-gray-600 dark:text-gray-400">
{links.map((item) => (
<li key={item.title} className="px-3 my-1">
<NavbarRowItem
title={item.title}
href={item.href}
active={pathname === item.href}
/>
</li>
))}
</ul>
);
}
useGSAP(() => {
timeline &&
timeline.fromTo(
linkRef.current,
{ opacity: 0, xPercent: -10 },
{ opacity: 1, duration: 0.75, xPercent: 0 },
index * DELAY_FACTOR
);
});

function NavbarDropdown({ links, pathname }: NavbarSubtypeProps) {
return (
<ul className="flex-col sm:hidden visible mt-6 divide-y-2 divide-gray-400 border-y-2 text-gray-600 dark:text-gray-400">
{links.map((item) => (
<li key={item.title}>
<NavbarDropdownItem
title={item.title}
href={item.href}
active={pathname === item.href}
/>
</li>
))}
</ul>
<div className="flex flex-row items-center">
<Link
ref={linkRef}
className={`block w-full ${
active ? "font-bold" : "font-normal text-gray-600 dark:text-gray-400"
} hover:text-blue-500 transition flex flew-row items-center gap-2 py-1 opacity-0`}
href={href}
>
<Icon name="arrow-right-sharp" /> {title}
</Link>
</div>
);
}

Expand All @@ -96,23 +92,83 @@ function Logo() {
export default function Navbar({ links }: NavbarProps) {
const pathname = usePathname();
const [dropdownActive, setDropdownActive] = useState(false);
const [dropAnim, setDropAnim] = useState<gsap.core.Timeline | null>(null);

const navbar = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);

const { contextSafe } = useGSAP(
() => {
let tl = gsap.timeline({ paused: true });
tl.fromTo(
dropdownRef.current,
{ opacity: 0 },
{
opacity: 1,
duration: links.length * DELAY_FACTOR,
onStart: () => setDropdownActive(true),
onReverseComplete: () => setDropdownActive(false),
}
);
setDropAnim(tl);
},
{ scope: navbar }
);
const toggle = contextSafe(() => {
if (dropdownActive) {
dropAnim?.timeScale(2).reverse();
} else {
dropAnim?.play();
}
});

const row = (
<ul className="flex-row sm:flex hidden divide-x-2 divide-gray-400 text-gray-600 dark:text-gray-400">
{links.map((item) => (
<li key={item.title} className="px-3">
<NavbarRowItem
title={item.title}
href={item.href}
active={pathname === item.href}
/>
</li>
))}
</ul>
);

const dropdown = (
<div ref={dropdownRef}>
{dropdownActive ? (
<div className="mt-6 border-y-2 py-2 sm:hidden visible">
<h4 className="mb-1">Navigation</h4>
<ul className="flex-col text-gray-600 dark:text-gray-400">
{links.map((item, index) => (
<li key={item.title}>
<NavbarDropdownItem
title={item.title}
href={item.href}
active={pathname === item.href}
timeline={dropAnim!}
index={index}
/>
</li>
))}
</ul>
</div>
) : null}
</div>
);

return (
<div className="px-10 pt-10">
<nav className={`flex flex-row items-center justify-between`}>
<nav ref={navbar} className="px-10 pt-10">
<div className="flex flex-row items-center justify-between">
<Logo />
<NavbarRow links={links} pathname={pathname} />
<button
type="button"
className="sm:hidden visible"
onClick={() => setDropdownActive(!dropdownActive)}
>
{row}
<button type="button" className="sm:hidden visible" onClick={toggle}>
<Icon name="menu" />
</button>
</nav>
{dropdownActive ? (
<NavbarDropdown links={links} pathname={pathname} />
) : null}
</div>
</div>
{dropdown}
</nav>
);
}
1 change: 0 additions & 1 deletion components/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export interface PostMetadata {
title: string;
date: Date;
editDate?: Date;
author: string;
slug: string;
keywords: string[];
description: string;
Expand Down
7 changes: 7 additions & 0 deletions mdx-components.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MDXComponents } from "mdx/types";
import Link from "next/link";
import { PostHeader, Callout, BlockEquation, Example, Definition, ImageWithCaption } from "@/components/post";

export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
Expand Down Expand Up @@ -39,6 +40,12 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
{children}
</blockquote>
),
PostHeader,
Callout,
BlockEquation,
Example,
Definition,
ImageWithCaption,
...components,
};
}
Loading
Loading