A bar whose menus open across the page: four columns over a sign-up strip.

Last updated

Open block
Show code
---
/**
 * Four columns with footer: A bar whose menus open across the page: four columns over a sign-up strip.
 *
 * @block header-navigation
 * @order 13
 */
import { Book, ChartColumn, ChartPie, CircleCheck, CircleDollarSign, CirclePlay, DollarSign, FileCode, Flag, Folder, Funnel, HeartHandshake, MessageCircle, MessagesSquare, Monitor, Orbit, Sparkles, TrendingUp, Users } from '@lucide/astro'
import Navbar from '@/components/apogee/Navbar/Navbar.astro'
import NavLink from '@/components/apogee/NavLink/NavLink.astro'
import NavDropdown from '@/components/apogee/NavDropdown/NavDropdown.astro'
import Link from '@/components/apogee/Link/Link.astro'
import Button from '@/components/apogee/Button/Button.astro'
import FeaturedIcon from '@/components/apogee/FeaturedIcon/FeaturedIcon.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import type { Action } from '@/components/apogee/sections/_shared/spine.ts'
import type { NavGroup, NavigationContent } from '@/components/apogee/sections/_shared/navigation.ts'

// One icon set per column, cycling by position.
const icons = [
  [ChartPie, Monitor, Funnel, ChartColumn],
  [CircleCheck, HeartHandshake, CircleDollarSign, TrendingUp],
  [Book, Sparkles, CirclePlay, FileCode],
  [Flag, MessageCircle, Users, Folder],
]
const footerIcons = [DollarSign, CirclePlay, MessagesSquare]
const groups: NavGroup[] = [
  { heading: 'Products', items: [
    { label: 'Interactive reports', href: '#', description: 'Learn about your users.' },
    { label: 'Team dashboard', href: '#', description: 'Monitor your metrics.' },
    { label: 'Segmentation', href: '#', description: 'Surface hidden trends.' },
    { label: 'Group analytics', href: '#', description: 'Measure account health.' },
  ] },
  { heading: 'Use cases', items: [
    { label: 'Convert', href: '#', description: 'Read conversion rates, and raise them.' },
    { label: 'Engage', href: '#', description: 'Measure active use, and find what to improve.' },
    { label: 'Retain', href: '#', description: 'Find what keeps customers with you.' },
    { label: 'Grow', href: '#', description: 'Grow your user base faster.', badge: 'New' },
  ] },
  { heading: 'Resources', items: [
    { label: 'Blog', href: '#', description: 'The latest industry news and guides, written by our team.' },
    { label: 'Customer stories', href: '#', description: 'How teams use the product to grow faster.' },
    { label: 'Video tutorials', href: '#', description: 'Get up and running on new features with short guides.' },
    { label: 'Documentation', href: '#', description: 'In-depth articles on our tools and technologies.' },
  ] },
  { heading: 'Company', items: [
    { label: 'About us', href: '#', description: 'Our team and what we are working towards.' },
    { label: 'Press', href: '#', description: 'News, press releases and press resources.' },
    { label: 'Careers', href: '#', description: 'We are always looking for talented people. Join our remote team.', badge: "We're hiring" },
    { label: 'Legal', href: '#', description: 'Company details, licensing, terms and privacy.' },
  ] },
]

/** @composable */
interface Props extends NavigationContent {
  /** The bar's fill: the page, a grey band or the brand colour. */
  background?: 'page' | 'neutral-faded' | 'primary' | undefined
  /** `dark` sets the bar in the dark theme on a light page. */
  theme?: 'dark' | undefined
  /** Bold line in the strip under each menu. @category content */
  footerText?: string | null
  /** The link after `footerText`. @category content */
  footerAction?: Action | null
  /** Links at the end of the strip. @category content */
  footerLinks?: Action[] | null
}
const {
  brand = 'Example',
  links = [
    { label: 'Products', groups },
    { label: 'Services', groups },
    { label: 'Pricing', href: '#' },
    { label: 'Resources', groups },
    { label: 'About', href: '#' },
  ],
  primaryAction = { label: 'Sign up', href: '#' },
  secondaryAction = { label: 'Log in', href: '#' },
  footerText = 'Ready to get started?',
  footerAction = { label: 'Sign up for free', href: '#' },
  footerLinks = [
    { label: 'Pricing', href: '#' },
    { label: 'Watch demo', href: '#' },
    { label: 'Chat to sales', href: '#' },
  ],
  background,
  theme,
} = Astro.props
---

<Navbar background={background} data-theme={theme}>
  <Link slot="brand" href="/" color="inherit" underline="none" class="inline-flex items-center gap-2 text-lg font-semibold"><FeaturedIcon theme="solid" color="primary" size="small"><Orbit /></FeaturedIcon>{brand}</Link>
  {links.map((l) => 'groups' in l ? (
  <NavDropdown label={l.label} fullWidth>
    <div class="mx-auto max-w-7xl px-gutter py-6 md:py-8">
        <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
          {l.groups.map((g, gi) => {
            const set = icons[gi % icons.length]!
            return (
          <div class="flex flex-col gap-2">
          {g.heading && <p class="px-3 text-sm font-semibold text-primary">{g.heading}</p>}
          <ul class="list-none p-0 flex flex-col gap-0.5">
            {g.items.map((i, n) => {
              const Icon = set[n % set.length]!
              return (
              <li>
                <Link href={i.href} color="inherit" underline="none" class="flex gap-3 rounded-lg p-3 transition-colors hover:bg-neutral-faded">
                  <FeaturedIcon theme="outline" color="neutral" size="medium"><Icon /></FeaturedIcon>
                  <span class="flex flex-col gap-1">
                    <span class="flex flex-wrap items-center gap-2 font-semibold">{i.label}{i.badge && <Badge size="small" color={i.badge === 'New' ? 'neutral' : 'positive'} variant="faded">{i.badge}</Badge>}</span>
                    {i.description && <span class="line-clamp-2 text-sm text-neutral-faded">{i.description}</span>}
                  </span>
                </Link>
              </li>
            )})}
          </ul>
        </div>
          )})}
        </div>
        {(footerText || footerAction || footerLinks) && <div class="flex flex-col gap-3 rounded-xl bg-neutral-faded px-4 py-3 sm:flex-row sm:items-center sm:justify-between mt-4 max-md:hidden">
          {(footerText || footerAction) && <p class="text-sm">{footerText && <span class="font-semibold">{footerText}</span>} {footerAction && <Link href={footerAction.href}>{footerAction.label}</Link>}</p>}
          {footerLinks && <ul class="flex list-none flex-wrap gap-x-6 gap-y-2 p-0 text-sm font-semibold max-sm:hidden">
            {footerLinks.map((f, n) => {
              const Icon = footerIcons[n % footerIcons.length]!
              return <li><Link href={f.href} color="neutral" underline="hover" class="inline-flex items-center gap-2"><Icon class="size-4" aria-hidden="true" />{f.label}</Link></li>
            })}
          </ul>}
        </div>}
      </div>
  </NavDropdown>
  ) : <NavLink href={l.href}>{l.label}</NavLink>)}
  {secondaryAction && <Button slot="actions" href={secondaryAction.href} variant="outline" color="neutral" size="small">{secondaryAction.label}</Button>}
  {primaryAction && <Button slot="actions" href={primaryAction.href} size="small">{primaryAction.label}</Button>}
</Navbar>

Add

npx @ava-ui/apogee add header-navigation/four-col-with-footer

This copies the block to src/components/apogee/blocks/header-navigation/ with everything it uses: Badge, Button, FeaturedIcon, Link, NavDropdown, NavLink, Navbar. Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.

Accessibility

  • Each menu is a button with a native popover: Esc or a click outside closes it and focus returns to the button. On phones the menus are rows that expand inside the menu. No JS is needed for either.