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

feat/navigation bar #226

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
130 changes: 119 additions & 11 deletions app/[locale]/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import { Button, HamburgerButton } from '~/components/buttons';
import { CtrlLink } from '~/components/link';
import LocaleSwitcher from '~/components/locale-switcher';
import MaybeLink from '~/components/maybe-link';
import {
NavigationMenu,
NavigationMenuCustomListItem,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
navigationMenuTriggerStyle,
} from '~/components/ui';
import { getTranslations } from '~/i18n/translations';
import { cn } from '~/lib/utils';
import { getServerAuthSession } from '~/server/auth';
Expand All @@ -22,11 +30,84 @@ export default async function Header({ locale }: { locale: string }) {
const isMacOS = agent.os.name === 'Mac OS';

const items = [
{ label: text.institute, href: 'institute' },
{ label: text.academics, href: 'academics' },
{
label: text.institute,
href: 'institute',
listItems: [
{
title: 'Institute Profile',
href: '/institute/profile',
description:
'Get to know our institute’s vision, mission, and values.',
},
{
title: 'Administration',
href: '/institute/administration',
description: 'Meet the leadership team guiding our institution.',
},
{
title: 'Sections',
href: '/institute/sections',
description:
'Explore the various sections that support campus life and academics.',
},
{
title: 'Campus Infrastructure',
href: '/institute/campus-infra',
description:
'Discover the state-of-the-art facilities and infrastructure on campus.',
},
],
},
{
label: text.academics,
href: 'academics',
listItems: [
{
title: 'Departments',
href: '/academics/departments',
description:
'Explore the diverse academic departments and their offerings.',
},
{
title: 'Programmes',
href: '/academics/programmes',
description:
'Discover our range of undergraduate and postgraduate programmes.',
},
{
title: 'Courses & Curricula',
href: '/academics/curricula',
description: 'Browse through the detailed list of courses available.',
},
{
title: 'Convocation',
href: '/academics/convocation',
description: 'Get information on upcoming convocation ceremonies.',
},
{
title: 'Awards',
href: '/academics/awards',
description:
'Recognizing excellence in academics, research, and beyond.',
},
{
title: 'Scholarship',
href: '/academics/scholarships',
description:
'Learn about scholarships, eligibility, and application details.',
},
{
title: 'Academic Notifications',
href: '/academics/notifications',
description:
'Stay updated with the latest academic announcements and deadlines.',
},
],
},
{ label: text.faculty, href: 'faculty-and-staff' },
{ label: text.placement, href: 'training-and-placement' },
{ label: text.alumni, href: 'alumni' },
// { label: text.alumni, href: 'alumni' },
{ label: text.activities, href: 'student-activities' },
];

Expand All @@ -48,16 +129,43 @@ export default async function Header({ locale }: { locale: string }) {
src="assets/nitlogo.png"
/>
</Link>
<NavigationMenu>
<NavigationMenuList
className={cn('hidden grow lg:flex', 'gap-4 xl:gap-5 2xl:gap-6')}
>
{items.map(({ label, href, listItems }, index) => (
<NavigationMenuCustomListItem
key={index}
triggerName={label}
locale={locale}
listItems={listItems}
href={href}
imageDetails={{
src: `${href}/image01.jpg`,
alt: label,
href: href,
}}
/>
))}

<ol className={cn('hidden grow lg:flex', 'gap-4 xl:gap-5 2xl:gap-6')}>
{items.map(({ label, href }, index) => (
<li className="my-auto min-h-fit" key={index}>
<Link href={`/${locale}/${href}`} prefetch>
{label}
{/* <NavigationMenuCustomListItem
triggerName={items[0].label}
imageDetails={{
src: 'https://s3-alpha-sig.figma.com/img/054e/19b7/43c945f2ee30e43f797f944b1c02fe2e?Expires=1728259200&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=TjH6LvDVUoTwEvUvc02-8DAwccrJ9YRqDqTy5h-0O0cYSVWG03it8-zr5OSBcjGVuu5TMnV7ZlnEiM3CIHQ3mQAy7Z3~Bv2sbCL8plMbE0GDxzmjpaVkKPAfgbMpuyofWABnyQjH4cda6qWEzeBuGEw~KQfFxVuAA-wHYTA6GL~B776fRbfdfzNxtSqucrIEqfGG1nUMFEdxvTLMPCqXTjErPikIs2rDXtAZ3K3U4suPFFqLRyBQ9H0B3DAGDzxZ64CIVLkAaE~ALCRy1BBUDyXrU24E1~BeTobiCoR0q1WcnBOPMKpnUb0c2qTyaDz8BxMe9hMMI9vGnv4fxdqGBA__',
alt: items[0].label,
href: items[0].href,
}}
listItems={components}
/> */}
{/* <NavigationMenuItem>
<Link href="/docs" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Documentation
</NavigationMenuLink>
</Link>
</li>
))}
</ol>
</NavigationMenuItem> */}
</NavigationMenuList>
</NavigationMenu>

<ol className="inline-flex h-10 gap-2">
<li className="flex h-full rounded-xl border border-neutral-500 bg-neutral-50">
Expand Down
1 change: 1 addition & 0 deletions components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './card';
export * from './command';
export * from './dialog';
export * from './label';
export * from './navigation-menu';
export * from './radio-group';
export * from './scroll-area';
export * from './table';
Expand Down
220 changes: 220 additions & 0 deletions components/ui/navigation-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { RxChevronDown } from 'react-icons/rx';
import Link from 'next/link';
import Image from 'next/image';

import { cn } from '~/lib/utils';

const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
'relative z-10 flex max-w-max flex-1 items-center justify-center',
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;

const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
'group flex flex-1 list-none items-center justify-center space-x-1',
className
)}
{...props}
/>
));
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;

const NavigationMenuItem = NavigationMenuPrimitive.Item;

const navigationMenuTriggerStyle = cva(
'hover:text-gray-900 focus:bg-gray-100 focus:text-gray-900 group flex max-w-fit rounded-md bg-background transition-colors hover:bg-neutral-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-neutral-100/50 data-[state=open]:bg-neutral-100/50'
);

const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), 'group', className)}
{...props}
>
<p className="w-fit min-w-0 text-pretty text-left text-base">{children}</p>
<RxChevronDown
className="relative top-[1px] my-auto ml-1 w-5 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
));
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;

const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
className
)}
{...props}
/>
));
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;

const NavigationMenuLink = NavigationMenuPrimitive.Link;

const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center text-neutral-950 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-xl border border-neutral-200 bg-shade-light shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]',
className
)}
ref={ref}
{...props}
/>
</div>
));
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;

const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-neutral-200 shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName;

const NavigationMenuCustomListItem = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Item> & {
locale: string;
triggerName: string;
listItems?: {
title: string;
description: string;
href: string;
}[];
imageDetails?: {
src: string;
alt: string;
href: string;
};
href?: string;
}
>(({ imageDetails, listItems, triggerName, href, locale, ...props }, ref) => {
if (!listItems) {
return (
<NavigationMenuItem {...props} ref={ref}>
<Link href={`/${locale}/${href}`} legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
{triggerName}
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
);
}
const imageHeight = listItems.length > 4 ? 4 : listItems.length;
return (
<NavigationMenuItem {...props} ref={ref}>
<NavigationMenuTrigger>{triggerName}</NavigationMenuTrigger>
<NavigationMenuContent className="flex gap-4 p-6 xl:gap-6 2xl:gap-8">
{imageDetails && (
<Link href={imageDetails.href} passHref legacyBehavior>
<NavigationMenuLink
className="group relative flex select-none flex-col justify-end overflow-hidden rounded-xl no-underline outline-none"
style={{ minWidth: `${70 * imageHeight}px` }}
>
<Image
className="absolute inset-0 z-0 h-full w-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-125"
alt=""
src={imageDetails.src}
width={0}
height={0}
/>
<section className="relative z-30 flex h-full w-full flex-col justify-end rounded-xl bg-gradient-to-b from-primary-500/0 to-primary-500 p-2 focus:shadow-md">
<h5 className="!mb-0 origin-bottom-left text-shade-light transition-transform duration-500 ease-in-out group-hover:scale-150">
{imageDetails.alt + '→'}
</h5>
</section>
</NavigationMenuLink>
</Link>
)}
<ul
className={cn(
'grid grid-flow-col auto-rows-max gap-4 xl:gap-6 2xl:gap-8'
)}
style={{
gridTemplateRows: `repeat(${imageHeight}, minmax(0, 1fr))`,
}}
>
{listItems.map(({ title, description, href }, index) => (
<li key={index}>
<NavigationMenuLink asChild>
<Link
className={cn(
'group block w-56 select-none space-y-1 rounded-xl p-3 leading-none no-underline outline-none transition-colors transition-transform duration-500 ease-in-out hover:scale-110 hover:bg-neutral-50 focus:bg-neutral-50'
)}
href={`/${locale}/${href}`}
>
<h6 className="font-sans font-semibold leading-none text-shade-dark group-hover:text-primary-500 group-focus:text-primary-500">
{title}
</h6>
<p className="line-clamp-3 text-sm leading-snug text-neutral-700 group-hover:text-primary-500 group-focus:text-primary-500">
{description}
</p>
</Link>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
);
});
NavigationMenuCustomListItem.displayName = 'NavigationMenuCustomListItem';

export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuCustomListItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
};
Loading