Skip to content

Instantly share code, notes, and snippets.

@ORESoftware
Last active May 30, 2025 16:32
Show Gist options
  • Save ORESoftware/322f76b1dfeca1191995a526d3f4d0d1 to your computer and use it in GitHub Desktop.
Save ORESoftware/322f76b1dfeca1191995a526d3f4d0d1 to your computer and use it in GitHub Desktop.
file example
// source file path: ./src/components/nav/coach-header.tsx
'use client';
import React, { useEffect, useRef, useState } from 'react';
import Link from 'next/link';
import {
ChevronDownIcon,
ChevronUpIcon,
MagnifyingGlassIcon,
PersonIcon,
} from '@radix-ui/react-icons';
import MobileNavDropdown from '@/components/nav/mobile-dropdown-menu';
import NavSearch, { NavSearchBarHandle } from '../search/nav-search';
import UserInfoButton from './nav-utils/user-info-button';
export default function CoachHeader() {
const [showSearch, setShowSearch] = useState(false);
const [showMoreDropdown, setShowMoreDropdown] = useState(false);
const moreDropdownRef = useRef<HTMLDivElement>(null);
const navSearchRef = useRef<NavSearchBarHandle>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (moreDropdownRef.current && !moreDropdownRef.current.contains(event.target as Node)) {
setShowMoreDropdown(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const handleHeaderSearchButtonClick = () => {
navSearchRef.current?.triggerSubmit();
};
return (
<>
{/* Main header container: relative positioning for the search overlay context */}
<header className='relative top-0 z-50 flex h-[60px] w-full items-center justify-between gap-2 border-b bg-[#03796b] px-4 py-2 text-white sm:px-8 lg:h-[80px] lg:gap-0'>
{!showSearch ? (
// Normal header layout
<>
{/* Desktop Navigation and other elements */}
<div className='hidden flex-grow items-center justify-between text-sm font-medium md:flex'>
{/* Left-aligned group: Coaching Icon and Main Navigation Links */}
<div className='flex items-center gap-6'>
<Link href='/u/coach'>
<div className='flex items-center font-semibold'>
<PersonIcon className='size-6' />
<span className='sr-only md:not-sr-only'>Coaching</span>
</div>
</Link>
<Link href='/u/coach'>Home</Link>
<Link href='/u/coach/dashboard'>Dashboard</Link>
<Link href='/u/coach/profile'>Profile</Link>
<Link href='/u/coach/clients'>Clients</Link>
<Link href='/u/coach/schedule'>Schedule</Link>
<Link href='/u/coach/events'>Events</Link>
</div>
{/* Right-aligned group (within the main nav area): Search and More... */}
<div className='flex items-center gap-6'>
<div
className='flex cursor-pointer items-center gap-1'
onClick={() => setShowSearch(true)}>
<MagnifyingGlassIcon className='size-4' />
<span>Search</span>
</div>
<div className='relative' ref={moreDropdownRef}>
<div
className='flex cursor-pointer items-center gap-1'
onClick={() => setShowMoreDropdown(!showMoreDropdown)}>
<span>More...</span>
{showMoreDropdown ? (
<ChevronUpIcon className='size-4' />
) : (
<ChevronDownIcon className='size-4' />
)}
</div>
{showMoreDropdown && (
<div className='absolute right-0 top-full z-10 mt-2 w-32 rounded-md border bg-white py-1 text-black shadow-lg'>
<Link href='/u/coach/item1' className='block px-4 py-2 hover:bg-gray-100'>
Item 1
</Link>
<Link href='/u/coach/item2' className='block px-4 py-2 hover:bg-gray-100'>
Item 2
</Link>
<Link href='/u/coach/item3' className='block px-4 py-2 hover:bg-gray-100'>
Item 3
</Link>
</div>
)}
</div>
</div>
</div>
{/* Mobile Dropdown - visible on smaller screens */}
{/* This div takes full width on mobile, pushing UserInfoButton to a new line if not handled by parent flex */}
<div className='flex-grow md:hidden'>
{' '}
{/* Use flex-grow to allow MobileNavDropdown to take space */}
<MobileNavDropdown navType='coach' />
</div>
{/* User Info Button - consistently placed to the far right */}
{/* ml-auto on this div will push it to the right of other sibling elements in the flex container */}
<div className='ml-auto flex-shrink-0'>
<UserInfoButton loggedInUser={null!} />
</div>
</>
) : (
// Search mode layout - Full overlay
<div className='absolute inset-0 z-[51] flex h-full w-full items-center justify-between gap-2 bg-[#03796b] px-4 py-2 sm:px-8'>
<div className='flex flex-1 items-center gap-2'>
<NavSearch ref={navSearchRef} />
<button
onClick={handleHeaderSearchButtonClick}
className='flex flex-shrink-0 items-center gap-1.5 rounded-md border border-white/40 bg-teal-500 px-3 py-1.5 text-sm text-white transition-colors hover:bg-teal-600 focus:outline-none focus:ring-2 focus:ring-white focus:ring-opacity-75'
title='Submit Search'>
<MagnifyingGlassIcon className='h-4 w-4' />
Search
</button>
<button
onClick={() => setShowSearch(false)}
className='flex-shrink-0 rounded-md border border-white/30 bg-pink-500 px-3 py-1.5 text-sm text-white transition-colors hover:bg-pink-600 focus:outline-none focus:ring-2 focus:ring-white focus:ring-opacity-75'
title='Close Search Bar'>
Close
</button>
</div>
<div className='ml-2 flex-shrink-0'>
<UserInfoButton loggedInUser={null!} />
</div>
</div>
)}
</header>
</>
);
}
// source file path: ./src/components/nav/mobile-dropdown-menu.tsx
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion';
import {
FiCalendar,
FiChevronDown,
FiClock,
FiGrid,
FiHome,
FiSettings,
FiStar,
FiUser,
FiUsers,
} from 'react-icons/fi';
interface MobileNavDropdownProps {
navType: 'admin' | 'coach' | 'client' | 'intake' | 'user'; // Accepting a string prop for nav type
}
const adminNav = [
{ href: '/u/admin', label: 'Home', icon: FiHome },
{ href: '/u/admin/audit', label: 'Audit', icon: FiGrid },
{ href: '/u/admin/dashboard', label: 'Dashboard', icon: FiGrid },
{ href: '/u/admin/users', label: 'Users', icon: FiUsers },
{ href: '/u/admin/sessions', label: 'Sessions', icon: FiCalendar },
{ href: '/u/admin/skills', label: 'Skills', icon: FiStar },
{ href: '/u/admin/blogging', label: 'Blog Mgmt', icon: FiStar },
// { href: '/u/admin/settings', label: 'Settings', icon: FiSettings },
];
const coachNav = [
{ href: '/u/coach', label: 'Home', icon: FiHome },
{ href: '/u/coach/dashboard', label: 'Dashboard', icon: FiGrid },
{ href: '/u/coach/profile', label: 'Profile', icon: FiUser },
{ href: '/u/coach/clients', label: 'Clients', icon: FiUsers },
{ href: '/u/coach/schedule', label: 'Schedule', icon: FiCalendar },
{ href: '/u/coach/events', label: 'Events', icon: FiStar },
];
const clientNav = [
{ href: '/u/client/sessions', label: 'Sessions', icon: FiCalendar },
{ href: '/u/client/schedule', label: 'Schedule', icon: FiClock },
{ href: '/u/client/profile', label: 'Profile', icon: FiUser },
];
const intakeNav = [
{ href: '/u/intake/appointments', label: 'Intake Appointments', icon: FiCalendar },
{ href: '/u/intake/users', label: 'Users', icon: FiUsers },
{ href: '/u/intake/skills', label: 'Skills', icon: FiStar },
{ href: '/u/intake/availability', label: 'Availability', icon: FiClock },
];
const userNav = [
{ href: '/u/intake/appointments', label: 'Intake Appointments', icon: FiCalendar },
{ href: '/u/intake/users', label: 'Users', icon: FiUsers },
{ href: '/u/intake/skills', label: 'Skills', icon: FiStar },
{ href: '/u/intake/availability', label: 'Availability', icon: FiClock },
];
export default function MobileNavDropdown({ navType }: MobileNavDropdownProps) {
const [open, setOpen] = useState(false);
// Choose which nav to render based on the navType prop
const navItems =
navType === 'admin'
? adminNav
: navType === 'coach'
? coachNav
: navType === 'client'
? clientNav
: navType === 'user'
? userNav
: intakeNav;
return (
<div className='relative md:hidden'>
<button
onClick={() => setOpen(!open)}
className='text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75'>
<FiChevronDown className={`text-2xl transition-transform ${open ? 'rotate-180' : ''}`} />
</button>
{open && (
<motion.div
className='absolute right-0 z-50 mt-2 w-56 overflow-hidden rounded-md bg-white text-gray-800 shadow-lg'
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.2, ease: 'easeInOut' }}>
<ul className='py-2'>
{navItems.map((item) => {
const Icon = item.icon; // Dynamically use the icon component
return (
<li key={item.href}>
<Link
href={item.href}
onClick={() => setOpen(false)}
className='flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1'>
<Icon className='h-5 w-5 text-primary-500' />
{item.label}
</Link>
</li>
);
})}
<li className='my-1'>
<div className='h-px bg-gradient-to-r from-transparent via-gray-200 to-transparent' />
</li>
<li>
<Link
href='/u/user/settings'
onClick={() => setOpen(false)}
className='flex items-center gap-3 px-4 py-2 text-sm hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1'>
<FiSettings className='h-5 w-5 text-gray-500' />
Settings
</Link>
</li>
</ul>
</motion.div>
)}
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment