Skip to content

Commit

Permalink
Merge pull request #6 from skunichetty/posts
Browse files Browse the repository at this point in the history
Added some more GSAP animations to components
  • Loading branch information
skunichetty authored Jul 9, 2024
2 parents 17a608e + 99ae597 commit 4f83059
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 89 deletions.
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

0 comments on commit 4f83059

Please sign in to comment.