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

Navbar #47

Merged
merged 7 commits into from
Jun 6, 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
471 changes: 469 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"node": ">=18.17.0"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-slot": "^1.0.2",
"@sanity/image-url": "^1.0.2",
"@sanity/vision": "^3.36.3",
"@tsparticles/engine": "^3.2.2",
Expand Down
9 changes: 6 additions & 3 deletions src/app/[slug]/client-page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Page as PageType } from "@/api/types";
import type { Link, Page as PageType } from "@/api/types";
import { urlForImage } from "@/sanity/lib/image";
import type { Image } from "sanity";

Expand All @@ -12,11 +12,14 @@ export const generateMetadata = () => getMetaData({});
export const generateViewport = getViewports;

export default function Page({ page }: { page: PageType }) {
const hasNavbar = page?.navbar;
const navbarLinks = (
page?.navbar?.links ? page?.navbar?.links?.filter(Boolean) : []
) as Link[];
const hasFooter = page?.footer;

return (
<>
{hasNavbar ? <Header /> : null}
{navbarLinks.length ? <Header links={navbarLinks} /> : null}
<div className="md:min-h-[calc(100vh_-_390px)]">
{page.sections?.map((section, idx) => {
if (section?.__typename == "Hero") {
Expand Down
31 changes: 25 additions & 6 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import type { Link as LinkType } from "@/api/types";

import { Logo } from "@/components/Icons/Logo";
import { SocialLink } from "@/components/SocialLink/SocialLink";
import { links } from "@/lib/data";
import { links as socialLinks } from "@/lib/data";
import { theme } from "@/lib/theme";
import { cn } from "@/lib/utils";

import { HeaderLink } from "./HeaderLink";
import { MobileNav } from "./MobileNav";

export function Header({ links }: { links?: LinkType[] }) {
const pathname = usePathname();

export function Header() {
return (
<header className="container relative z-10 mx-auto flex h-20 max-w-[1136px] items-center justify-between p-4 text-jsconf-yellow">
<Link href="/">
<Logo color={theme?.colors?.jsconfYellow} size="36" />
</Link>
<div className="flex items-center gap-6">
{links.map((link) => (
<SocialLink key={link.id} link={link} />
))}
<div
className={cn("hidden items-center md:flex", links ? "gap-2" : "gap-4")}
>
{links
? links.map((link) => (
<HeaderLink
key={link.url}
isActive={pathname == link.url}
{...link}
/>
))
: socialLinks.map((link) => <SocialLink key={link.id} link={link} />)}
</div>
{links?.length ? <MobileNav links={links} activePath={pathname} /> : null}
</header>
);
}
36 changes: 36 additions & 0 deletions src/components/Header/HeaderLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Link from "next/link";
import type { Link as LinkType } from "@/api/types";
import { LogIn } from "lucide-react";

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

type HeaderLinkProps = LinkType & { isActive: boolean };

export function HeaderLink({
url,
target,
text,
icon,
style,
isActive,
}: HeaderLinkProps) {
const Comp = url?.startsWith("/") && target === "_self" ? Link : "a";

return (
<Comp
href={url ?? ""}
target={target ?? ""}
className={cn(
"flex gap-2 font-barlow font-medium text-white rounded-full p-4 py-2 hover:text-jsconf-yellow",
style === "button"
? "border rounded-md border-jsconf-yellow text-white hover:bg-[#F0E04060] hover:text-black"
: "",
isActive ? "underline underline-offset-8 text-jsconf-yellow" : "",
)}
rel={target == "_blank" ? "noreferrer" : ""}
>
{text}
{icon === "external" ? <LogIn /> : null}
</Comp>
);
}
74 changes: 74 additions & 0 deletions src/components/Header/MobileNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use client";

import { useState } from "react";
import Link from "next/link";
import type { Link as LinkType } from "@/api/types";
import { LogIn, Menu } from "lucide-react";

import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Logo } from "@/components/Icons/Logo";
import { theme } from "@/lib/theme";
import { cn } from "@/lib/utils";

export function MobileNav({
links,
activePath,
}: {
links: LinkType[];
activePath?: string;
}) {
const [open, setOpen] = useState(false);

const closeNav = () => setOpen(false);
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
variant="ghost"
className="px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
>
<Menu className="size-6" />
<span className="sr-only">Toggle Menu</span>
</Button>
</SheetTrigger>
<SheetContent side="top" className="p-4 pb-0" icon="menu">
<Link href="/" className="flex items-center" onClick={closeNav}>
<Logo color={theme?.colors?.jsconfYellow} size="36" />
</Link>
<ScrollArea className="my-4 px-6 pb-10">
<div className="flex flex-col space-y-2">
<div className="flex flex-col space-y-3 pt-6">
{links.map((link) => {
const Comp =
link.url?.startsWith("/") && link?.target === "_self"
? Link
: "a";
return (
<Comp
key={`mobile-${link.text}`}
href={link.url ?? ""}
target={link.target ?? ""}
className={cn(
"flex font-barlow justify-center gap-2 p-4 py-2",
link.style === "button"
? "border rounded-md border-jsconf-yellow text-white hover:bg-[#F0E04060] hover:text-black"
: "",
activePath == link.url
? "underline underline-offset-8 text-jsconf-yellow"
: "",
)}
>
{link.text}
{link.icon === "external" ? <LogIn /> : null}
</Comp>
);
})}
</div>
</div>
</ScrollArea>
</SheetContent>
</Sheet>
);
}
56 changes: 56 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

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

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";

export { Button, buttonVariants };
48 changes: 48 additions & 0 deletions src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";

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

const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;

const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;

export { ScrollArea, ScrollBar };
Loading
Loading