Skip to content
Navigation

Navigation

SiteHeader

Fixed portfolio header with rolling link text and a bubble fill on hover.

npx bezel-add add site-header

packages/ui/src/navigation/SiteHeader.tsx · 110 lines

"use client";

type NavLink = { label: string; href: string };

type Props = {
  logo?: string;
  logoHref?: string;
  navLinks?: NavLink[];
  ctaLabel?: string;
  ctaHref?: string;
  visible?: boolean;
};

export function SiteHeader({
  logo = "YASH",
  logoHref = "/",
  navLinks = [],
  ctaLabel = "GET IN TOUCH",
  ctaHref = "#contact",
  visible = true,
}: Props) {
  return (
    <>
      <style>{`
        .yui-site-header {
          display: flex; align-items: center; justify-content: space-between;
          position: fixed; top: 0; left: 0; width: 100%;
          padding: 2rem 5rem; z-index: 1000;
          transform: translateY(-100%);
          transition: transform 0.8s cubic-bezier(0.25,1,0.5,1);
        }
        .yui-site-header.visible { transform: translateY(0); }
        .yui-site-header-logo { font-size: 2rem; letter-spacing: -0.1rem; font-weight: 500; color: #fff; text-decoration: none; }
        .yui-site-header-nav { display: flex; list-style: none; gap: 1.5rem; margin: 0; padding: 0; }
        .yui-nav-item {
          position: relative; overflow: hidden;
          font-size: 1rem; font-weight: 500;
          border-radius: 999px; cursor: pointer;
        }
        .yui-nav-link {
          position: relative; display: inline-flex; flex-direction: column;
          overflow: hidden; line-height: 1; padding: 0.5rem 1.2rem;
          text-decoration: none;
        }
        .yui-nav-link .link-outer { display: block; color: #fff; transition: transform 0.5s cubic-bezier(0.25,1,0.5,1); }
        .yui-nav-link .link-inner {
          position: absolute; top: 0; left: 0; width: 100%; height: 100%;
          display: flex; align-items: center; justify-content: center;
          color: #000; transform: translateY(100%);
          transition: transform 0.5s cubic-bezier(0.25,1,0.5,1);
        }
        .yui-nav-item:hover .link-outer { transform: translateY(-100%); }
        .yui-nav-item:hover .link-inner { transform: translateY(0); }
        /* Outline goes on the item, not the link: the item clips its children */
        .yui-nav-item:has(:focus-visible) { outline: 2px solid #fff; outline-offset: 2px; }
        .yui-nav-bubble {
          position: absolute; inset: 0; overflow: hidden; border-radius: 999px; pointer-events: none;
        }
        .yui-nav-bubble::before {
          content: ''; position: absolute; width: 150%; height: 150%;
          background: #fff; border-radius: 50%;
          top: 100%; left: -25%; transform: translateY(0);
          transition: top 0.4s cubic-bezier(0.25,1,0.5,1);
        }
        .yui-nav-item:hover .yui-nav-bubble::before { top: -25%; }
        .yui-site-cta {
          position: relative; overflow: hidden;
          display: inline-flex; align-items: center;
          padding: 0.5rem 1.4rem; border-radius: 999px;
          border: 1px solid rgba(255,255,255,0.3);
          font-size: 0.9rem; font-weight: 500; color: #fff;
          text-decoration: none; cursor: pointer;
        }
        .yui-site-cta .link-outer { position: relative; z-index: 1; transition: color 0.3s; }
        .yui-site-cta-bubble {
          position: absolute; inset: 0; overflow: hidden; border-radius: 999px;
        }
        .yui-site-cta-bubble::before {
          content: ''; position: absolute; width: 150%; height: 150%;
          background: #fff; border-radius: 50%;
          top: 100%; left: -25%;
          transition: top 0.4s cubic-bezier(0.25,1,0.5,1);
        }
        .yui-site-cta:hover .yui-site-cta-bubble::before { top: -25%; }
        .yui-site-cta:hover .link-outer { color: #000; }
        .yui-site-cta:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
      `}</style>
      <header className={`yui-site-header${visible ? " visible" : ""}`}>
        <a href={logoHref} className="yui-site-header-logo">{logo}</a>
        {navLinks.length > 0 && (
          <ul className="yui-site-header-nav">
            {navLinks.map((link) => (
              <li key={link.href} className="yui-nav-item">
                <div className="yui-nav-bubble" />
                <a href={link.href} className="yui-nav-link">
                  <span className="link-outer">{link.label}</span>
                  <span className="link-inner">{link.label}</span>
                </a>
              </li>
            ))}
          </ul>
        )}
        <a href={ctaHref} className="yui-site-cta">
          <div className="yui-site-cta-bubble" />
          <span className="link-outer">{ctaLabel}</span>
        </a>
      </header>
    </>
  );
}