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
63 changes: 42 additions & 21 deletions components/navbar/dropdown-navbar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,61 @@ import {
NavigationMenuLink,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import { NavLinkGroup } from "./nav-types";
import { NavLink, NavLinkGroup } from "./nav-types";

interface DropdownNavbarItemProps {
group: NavLinkGroup;
}

function LinkItem({ href, label, icon }: NavLink) {
return (
<li key={href}>
<NavigationMenuLink asChild>
<Link
href={href}
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="flex items-center space-x-2">
{icon}
<div className="text-sm font-medium leading-snug">{label}</div>
</div>
</Link>
</NavigationMenuLink>
</li>
);
}

export function DropdownNavbarItem({
group: { label, links },
group: { label, links, sections },
}: DropdownNavbarItemProps) {
return (
<NavigationMenuItem>
<NavigationMenuTrigger className="h-9 px-3 py-1">
{label}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{links.map(({ href, label, icon }) => (
<li key={href}>
<NavigationMenuLink asChild>
<Link
href={href}
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
>
<div className="flex items-center space-x-2">
{icon}
<div className="text-sm font-medium leading-none">
{label}
</div>
</div>
</Link>
</NavigationMenuLink>
</li>
))}
</ul>
{sections ? (
<div className="flex w-max max-w-[600px] gap-4 p-4">
{sections.map((section) => (
<div key={section.heading} className="flex-1">
<h4 className="mb-2 px-3 text-sm font-semibold text-muted-foreground">
{section.heading}
</h4>
<ul className="grid gap-1">
{section.links.map((link) => (
<LinkItem key={link.href} {...link} />
))}
</ul>
</div>
))}
</div>
) : (
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{links?.map((link) => (
<LinkItem key={link.href} {...link} />
))}
</ul>
)}
</NavigationMenuContent>
</NavigationMenuItem>
);
Expand Down
47 changes: 32 additions & 15 deletions components/navbar/mobile-dropdown-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@ import Link from "next/link";
import { ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import { SheetClose } from "@/components/ui/sheet";
import { NavLinkGroup } from "./nav-types";
import { NavLink, NavLinkGroup } from "./nav-types";

interface MobileDropdownItemProps {
group: NavLinkGroup;
isExpanded: boolean;
onToggle: () => void;
}

function MobileLinkItem({ href, label, icon }: NavLink) {
return (
<SheetClose asChild key={href}>
<Link href={href}>
<Button
variant="ghost"
className="w-full justify-start h-10 text-sm pl-6"
>
<span className="mr-3">{icon}</span>
{label}
</Button>
</Link>
</SheetClose>
);
}

export function MobileDropdownItem({
group: { label, links },
group: { label, links, sections },
isExpanded,
onToggle,
}: MobileDropdownItemProps) {
Expand All @@ -33,19 +49,20 @@ export function MobileDropdownItem({
</Button>
{isExpanded && (
<div className="space-y-1 pl-4">
{links.map(({ href, label, icon }) => (
<SheetClose asChild key={href}>
<Link href={href}>
<Button
variant="ghost"
className="w-full justify-start h-10 text-sm pl-6"
>
<span className="mr-3">{icon}</span>
{label}
</Button>
</Link>
</SheetClose>
))}
{sections
? sections.map((section) => (
<div key={section.heading} className="space-y-1">
<div className="px-6 py-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{section.heading}
</div>
{section.links.map((link) => (
<MobileLinkItem key={link.href} {...link} />
))}
</div>
))
: links?.map((link) => (
<MobileLinkItem key={link.href} {...link} />
))}
</div>
)}
</div>
Expand Down
8 changes: 7 additions & 1 deletion components/navbar/nav-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ export type NavLink = {
icon?: ReactElement;
};

export type NavLinkSection = {
heading: string;
links: NavLink[];
};

export type NavLinkGroup = {
label: string;
links: NavLink[];
links?: NavLink[];
sections?: NavLinkSection[];
};
24 changes: 18 additions & 6 deletions components/navbar/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { useCurrentUser } from "@/hooks/useCurrentUser";
import { getCompetitions } from "@/lib/db_actions/competitions";
import { Competition } from "@/types/db_types";
import { getCompetitionStatusFromObject } from "@/lib/competition-status";
import { NavLink, NavLinkGroup } from "./nav-types";
import { NavLink, NavLinkGroup, NavLinkSection } from "./nav-types";
import { DropdownNavbarItem } from "./dropdown-navbar-item";
import { MobileDropdownItem } from "./mobile-dropdown-item";

Expand Down Expand Up @@ -78,13 +78,25 @@ export default function NavBar() {

// Add competitions section if there are any competitions
if (competitions.length > 0) {
const privateComps = competitions.filter((c) => c.is_private);
const publicComps = competitions.filter((c) => !c.is_private);
const toNavLink = (competition: Competition): NavLink => ({
href: `/competitions/${competition.id}`,
label: competition.name,
icon: <Medal size={16} />,
});

const sections: NavLinkSection[] = [];
if (privateComps.length > 0) {
sections.push({ heading: "Private", links: privateComps.map(toNavLink) });
}
if (publicComps.length > 0) {
sections.push({ heading: "Public", links: publicComps.map(toNavLink) });
}

links.push({
label: "Competitions",
links: competitions.map((competition) => ({
href: `/competitions/${competition.id}`,
label: competition.name,
icon: <Medal size={16} />,
})),
sections,
});
}

Expand Down
Loading