Skip to content
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
78 changes: 59 additions & 19 deletions apps/web/src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,72 @@
import { useState } from "react";
import { Link } from "@tanstack/react-router";
import { Menu } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetTrigger,
SheetTitle,
} from "@/components/ui/sheet";

const links = [
{ to: "/", label: "Home" },
{ to: "/feed", label: "Feed" },
{ to: "/bookmarks", label: "Bookmarks" },
{ to: "/dashboard", label: "Dashboard" },
] as const;

export default function Header() {
const links = [
{ to: "/", label: "Home" },
{ to: "/feed", label: "Feed" },
{ to: "/bookmarks", label: "Bookmarks" },
{ to: "/dashboard", label: "Dashboard" },
] as const;
const [open, setOpen] = useState(false);

return (
<header>
<div className="flex flex-row items-center justify-between px-6 py-4">
<div className="flex items-center justify-between px-6 py-4">
<Link to="/" className="text-xl font-bold">
Biviant
</Link>
<nav aria-label="Primary" className="flex gap-6">
{links.map(({ to, label }) => {
return (
<Link
key={to}
to={to}
className="text-sm font-medium hover:text-primary transition-colors"
>
{label}
</Link>
);
})}

{/* Desktop nav */}
<nav aria-label="Primary" className="hidden md:flex gap-6">
{links.map(({ to, label }) => (
<Link
key={to}
to={to}
className="text-sm font-medium hover:text-primary transition-colors"
>
{label}
</Link>
))}
</nav>

{/* Mobile burger */}
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
variant="ghost"
size="icon"
className="md:hidden"
aria-label="Open menu"
>
<Menu className="size-5" />
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-64">
<SheetTitle className="text-lg font-bold mb-6">Menu</SheetTitle>
<nav aria-label="Mobile" className="flex flex-col gap-4">
{links.map(({ to, label }) => (
<Link
key={to}
to={to}
className="text-base font-medium hover:text-primary transition-colors"
onClick={() => setOpen(false)}
>
{label}
</Link>
))}
</nav>
</SheetContent>
</Sheet>
</div>
<hr />
</header>
Expand Down
141 changes: 141 additions & 0 deletions apps/web/src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as SheetPrimitive } from "radix-ui"

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

function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}

function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}

function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}

function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}

function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}

function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className
)}
{...props}
>
{children}
{showCloseButton && (
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Content>
</SheetPortal>
)
}

function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
)
}

function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}

function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
{...props}
/>
)
}

function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}

export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}