Skip to content

Commit

Permalink
Navbar (#47)
Browse files Browse the repository at this point in the history
* feat(Header): accept Page links and show active route

* feat(customPage): pass header links if exists

* chore: include shadcn/ui button

* chore: include shadcn/ui scroll-area

* chore: include shadcn/ui sheet

* feat: adjust sheet for JSConf Menu

* feat: include Mobile Navbar
  • Loading branch information
joseglego authored Jun 6, 2024
1 parent 174e463 commit 5bd52fe
Show file tree
Hide file tree
Showing 9 changed files with 866 additions and 11 deletions.
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

0 comments on commit 5bd52fe

Please sign in to comment.