1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Secciones de Página headernavbarmega-menuecommerceeditorial

Shop Header Editorial

Cabecera editorial de tienda: línea de aviso, logotipo centrado, mega menú con destacado, cambio de tema y contador de la bolsa en vivo.

Vista Previa en Vivo

Uso

Copia y pega este código directamente en tu página de Astro:

astro
---
import { ShopHeaderEditorial } from 'astro-component-kit/sections';
---

<ShopHeaderEditorial brand="Forma" bagHref="/bag/" links={[
  { label: 'Shop', href: '/shop/', columns: [{ title: 'Clothing', links: [{ label: 'Knitwear', href: '/shop/?category=knit' }] }] },
  { label: 'Journal', href: '/journal/' },
]} />
--- import { ShopHeaderEditorial } from 'astro-component-kit/sections'; --- <ShopHeaderEditorial brand="Forma" bagHref="/bag/" links={[ { label: 'Shop', href: '/shop/', columns: [{ title: 'Clothing', links: [{ label: 'Knitwear', href: '/shop/?category=knit' }] }] }, { label: 'Journal', href: '/journal/' }, ]} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/lib/ShopHeaderEditorial.astro:

astro
---
/**
 * ShopHeaderEditorial — Editorial store header: thin announcement line, left menu, centred
 * wordmark, right utilities (search, theme toggle, bag count) and a full-width mega menu.
 *
 * @param {string} brand - Wordmark text. Default 'Forma'.
 * @param {string} brandHref - Wordmark link. Default '/'.
 * @param {string} announcement - Top line text (empty hides it).
 * @param {Array} links - `{ label, href, columns?: { title, links: {label, href}[] }[], feature?: { title, text, href, icon?, hue? } }`.
 * @param {string} searchHref - Search page URL (empty hides the icon). Default '#'.
 * @param {string} bagHref - Bag page URL. When empty, the bag button opens a CartDrawer instead.
 * @param {string} cartKey - Cart storage key for the live count. Default 'ack-cart'.
 * @param {boolean} themeToggle - Show a light/dark toggle (html[data-theme]). Default true.
 * @param {string} currentHref - Marks the active link.
 * @param {boolean} sticky - Stick to the top on scroll. Default true.
 * @param {string} variant - Style (paper|transparent|inverted). Default 'paper'.
 * @param {string} size - Height (sm|md|lg). Default 'md'.
 * @param {string} id - Base id. Default 'shop-header'.
 * @param {string} className - Extra class.
 *
 * Accessibility: mega menus are disclosure buttons (aria-expanded) that close on Escape/outside
 * click; the mobile menu is a disclosure too; the bag link label includes the live count.
 */
import ProductMedia from '../ecommerce/ProductMedia.astro';

interface Col { title: string; links: Array<{ label: string; href: string }> }
interface Feature { title: string; text?: string; href: string; icon?: string; hue?: number }
interface NavLink { label: string; href: string; columns?: Col[]; feature?: Feature }

interface Props {
  brand?: string;
  brandHref?: string;
  announcement?: string;
  links?: NavLink[];
  searchHref?: string;
  bagHref?: string;
  cartKey?: string;
  themeToggle?: boolean;
  currentHref?: string;
  sticky?: boolean;
  variant?: 'paper' | 'transparent' | 'inverted';
  size?: 'sm' | 'md' | 'lg';
  id?: string;
  className?: string;
}

const {
  brand = 'Forma',
  brandHref = '/',
  announcement = 'Complimentary shipping over €150 · Free returns within 30 days',
  links = [
    {
      label: 'Shop',
      href: '#',
      columns: [
        { title: 'Clothing', links: [{ label: 'Overshirts', href: '#' }, { label: 'Knitwear', href: '#' }, { label: 'Outerwear', href: '#' }] },
        { title: 'Objects', links: [{ label: 'Ceramics', href: '#' }, { label: 'Lighting', href: '#' }, { label: 'Plants', href: '#' }] },
        { title: 'Accessories', links: [{ label: 'Bags', href: '#' }, { label: 'Eyewear', href: '#' }, { label: 'Watches', href: '#' }] },
      ],
      feature: { title: 'The Autumn edit', text: 'Twelve pieces in warm, quiet tones.', href: '#', icon: 'jacket', hue: 22 },
    },
    { label: 'New in', href: '#' },
    { label: 'Journal', href: '#' },
  ],
  searchHref = '#',
  bagHref = '#',
  cartKey = 'ack-cart',
  themeToggle = true,
  currentHref,
  sticky = true,
  variant = 'paper',
  size = 'md',
  id = 'shop-header',
  className = '',
} = Astro.props;
---

<header class:list={['she', `she--${variant}`, `she--${size}`, { 'she--sticky': sticky }, className]} data-shop-header data-key={cartKey}>
  {announcement && <p class="she__announce">{announcement}</p>}
  <div class="she__bar">
    <div class="she__left">
      <button class="she__icon she__burger" type="button" aria-expanded="false" aria-controls={`${id}-nav`} aria-label="Menu" data-burger>
        <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M3 8h18M3 16h18"/></svg>
      </button>
      <nav class="she__nav" id={`${id}-nav`} aria-label="Main" data-nav>
        <ul>
          {links.map((link, i) => (
            <li>
              {link.columns?.length ? (
                <>
                  <button class="she__link she__link--desk" type="button" aria-expanded="false" aria-controls={`${id}-mega-${i}`} data-mega-toggle>{link.label}</button>
                  <a class="she__link she__link--mob" href={link.href}>{link.label}</a>
                  <ul class="she__sub">
                    {link.columns.flatMap((c) => c.links).map((l) => <li><a href={l.href}>{l.label}</a></li>)}
                  </ul>
                </>
              ) : (
                <a class="she__link" href={link.href} aria-current={currentHref === link.href ? 'page' : undefined}>{link.label}</a>
              )}
            </li>
          ))}
        </ul>
      </nav>
    </div>

    <a class="she__brand" href={brandHref}>{brand}</a>

    <div class="she__right">
      {searchHref && (
        <a class="she__icon" href={searchHref} aria-label="Search">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
        </a>
      )}
      {themeToggle && (
        <button class="she__icon" type="button" aria-label="Switch theme" data-theme-btn>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z" fill="currentColor"/></svg>
        </button>
      )}
      {bagHref ? (
        <a class="she__bag" href={bagHref} aria-label="Bag" data-bag-link>
          Bag <span class="she__count" data-count>0</span>
        </a>
      ) : (
        <button class="she__bag" type="button" aria-label="Bag" data-bag-link data-cart-open data-cart-key={cartKey}>
          Bag <span class="she__count" data-count>0</span>
        </button>
      )}
    </div>
  </div>

  {links.map((link, i) => link.columns?.length ? (
    <div class="she__mega" id={`${id}-mega-${i}`} hidden data-mega>
      <div class="she__mega-inner">
        {link.columns.map((col) => (
          <div class="she__col">
            <p class="she__col-title">{col.title}</p>
            <ul>{col.links.map((l) => <li><a href={l.href}>{l.label}</a></li>)}</ul>
          </div>
        ))}
        <a class="she__col she__col--all" href={link.href}>View everything <span aria-hidden="true">→</span></a>
        {link.feature && (
          <a class="she__feature" href={link.feature.href}>
            <ProductMedia icon={link.feature.icon} hue={link.feature.hue} ratio="4 / 3" variant="soft" size="lg" />
            <span class="she__feature-title">{link.feature.title}</span>
            {link.feature.text && <span class="she__feature-text">{link.feature.text}</span>}
          </a>
        )}
      </div>
    </div>
  ) : null)}
</header>

<script>
  import { createCart } from '../ecommerce/cart-store';

  document.querySelectorAll<HTMLElement>('[data-shop-header]:not([data-bound])').forEach((header) => {
    header.dataset.bound = '';
    const toggles = Array.from(header.querySelectorAll<HTMLButtonElement>('[data-mega-toggle]'));
    const panelOf = (b: HTMLButtonElement) => document.getElementById(b.getAttribute('aria-controls') || '');
    const closeAll = (except?: HTMLButtonElement) => toggles.forEach((b) => {
      if (b === except) return;
      b.setAttribute('aria-expanded', 'false');
      const p = panelOf(b);
      if (p) p.hidden = true;
    });
    toggles.forEach((b) => b.addEventListener('click', () => {
      const open = b.getAttribute('aria-expanded') !== 'true';
      closeAll(b);
      b.setAttribute('aria-expanded', String(open));
      const p = panelOf(b);
      if (p) p.hidden = !open;
    }));
    document.addEventListener('click', (e) => { if (!header.contains(e.target as Node)) closeAll(); });
    document.addEventListener('keydown', (e) => {
      if (e.key !== 'Escape') return;
      const open = toggles.find((b) => b.getAttribute('aria-expanded') === 'true');
      closeAll();
      open?.focus();
      const burger = header.querySelector<HTMLButtonElement>('[data-burger]');
      if (burger?.getAttribute('aria-expanded') === 'true') { burger.click(); burger.focus(); }
    });

    const burger = header.querySelector<HTMLButtonElement>('[data-burger]');
    const nav = header.querySelector<HTMLElement>('[data-nav]');
    burger?.addEventListener('click', () => {
      const open = burger.getAttribute('aria-expanded') !== 'true';
      burger.setAttribute('aria-expanded', String(open));
      nav?.classList.toggle('is-open', open);
    });

    header.querySelector('[data-theme-btn]')?.addEventListener('click', () => {
      const root = document.documentElement;
      const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
      root.dataset.theme = next;
      try { window.localStorage.setItem('ack-theme', next); } catch { /* ignore */ }
    });

    const bag = header.querySelector<HTMLElement>('[data-bag-link]');
    const count = header.querySelector<HTMLElement>('[data-count]');
    if (bag && count) {
      const cart = createCart(header.dataset.key);
      const render = () => {
        const n = cart.count();
        count.textContent = String(n);
        bag.setAttribute('aria-label', `Bag, ${n} ${n === 1 ? 'item' : 'items'}`);
      };
      render();
      cart.subscribe(() => {
        render();
        count.classList.remove('is-bump');
        void count.offsetWidth;
        count.classList.add('is-bump');
      });
      if (bag.tagName === 'BUTTON') {
        bag.addEventListener('click', () => document.dispatchEvent(new CustomEvent('ack:cart-open', { detail: { key: cart.key, trigger: bag } })));
      }
    }
  });
</script>

<style>
  .she {
    container-type: inline-size;
    --she-h: 72px;
    position: relative;
    z-index: 50;
    color: var(--c-text-1, #fff);
    background: var(--c-bg, #050505);
    border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
  }
  .she--sm { --she-h: 60px; }
  .she--lg { --she-h: 88px; }
  .she--sticky { position: sticky; top: 0; }
  .she--transparent { background: transparent; border-bottom-color: transparent; }
  .she--inverted { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); }

  .she__announce {
    margin: 0;
    padding: 0.5rem var(--sp-4, 1rem);
    border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
    font-size: 0.72rem;
    letter-spacing: 0.12em;
    text-align: center;
    text-transform: uppercase;
    color: var(--c-text-2, #a3a3a3);
  }

  .she__bar {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--sp-4, 1rem);
    max-width: 1440px;
    height: var(--she-h);
    margin: 0 auto;
    padding: 0 clamp(1rem, 3vw, 2.5rem);
  }
  .she__left, .she__right { display: flex; align-items: center; gap: var(--sp-1, 0.25rem); }
  .she__right { justify-content: flex-end; }
  .she__brand {
    font-family: var(--font-display, Georgia, 'Times New Roman', serif);
    font-size: clamp(1.6rem, 3vw, 2.1rem);
    font-weight: 400;
    letter-spacing: -0.03em;
    color: inherit;
    text-decoration: none;
  }
  .she__brand::after { content: '°'; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }

  .she__nav ul { display: flex; gap: var(--sp-1, 0.25rem); margin: 0; padding: 0; list-style: none; }
  .she__link {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.75rem;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
  }
  .she__link::after {
    content: '';
    position: absolute;
    left: 0.75rem;
    right: 0.75rem;
    bottom: 10px;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.3s ease;
  }
  .she__link:hover::after, .she__link[aria-expanded='true']::after, .she__link[aria-current='page']::after { transform: scaleX(1); }

  .she__icon, .she__bag {
    display: inline-grid;
    place-items: center;
    min-width: 44px;
    height: 44px;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
    text-decoration: none;
  }
  .she__bag { display: inline-flex; align-items: center; gap: 6px; padding: 0 0.5rem; font: inherit; font-size: 0.82rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; }
  .she__count {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--c-text-1, #fff);
    color: var(--c-bg, #050505);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0;
  }
  .she--inverted .she__count { background: var(--c-bg, #050505); color: var(--c-text-1, #fff); }
  .she__count.is-bump { animation: she-bump 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); }
  @keyframes she-bump { 40% { transform: scale(1.3); background: var(--shop-accent, var(--c-primary-light, #d4a017)); } }
  .she :is(a, button):focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
  .she__burger { display: none; }
  .she__link--mob, .she__sub { display: none !important; }

  .she__mega {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    background: var(--c-bg, #050505);
    border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
    box-shadow: 0 40px 60px -40px rgba(0, 0, 0, 0.5);
    animation: she-drop 0.25s ease-out;
  }
  .she__mega[hidden] { display: none; }
  .she__mega-inner {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(0, 0.8fr) minmax(0, 1.4fr);
    gap: var(--sp-8, 2rem);
    max-width: 1440px;
    margin: 0 auto;
    padding: var(--sp-8, 2rem) clamp(1rem, 3vw, 2.5rem) var(--sp-10, 2.5rem);
  }
  .she__col-title { margin: 0 0 var(--sp-3, 0.75rem); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
  .she__col ul { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
  .she__col a { display: inline-flex; min-height: 36px; align-items: center; font-family: var(--font-display, Georgia, serif); font-size: 1.15rem; color: var(--c-text-1, #fff); text-decoration: none; }
  .she__col a:hover { color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
  .she__col--all { align-self: end; font-family: inherit !important; font-size: 0.85rem !important; text-decoration: underline !important; text-underline-offset: 4px; }
  .she__feature { display: grid; gap: 6px; color: inherit; text-decoration: none; }
  .she__feature :global(.p-media) { border-radius: var(--r-sm, 2px); }
  .she__feature-title { font-family: var(--font-display, Georgia, serif); font-size: 1.2rem; color: var(--c-text-1, #fff); }
  .she__feature-text { font-size: 0.85rem; color: var(--c-text-2, #a3a3a3); }
  @keyframes she-drop { from { opacity: 0; transform: translateY(-6px); } }

  @container (max-width: 900px) {
    .she__burger { display: inline-grid; }
    .she__nav {
      position: absolute;
      left: 0;
      right: 0;
      top: 100%;
      display: none;
      padding: var(--sp-4, 1rem) clamp(1rem, 3vw, 2.5rem) var(--sp-6, 1.5rem);
      background: var(--c-bg, #050505);
      border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
    }
    .she__nav.is-open { display: block; }
    .she__nav ul { flex-direction: column; }
    .she__link { width: 100%; min-height: 52px; padding: 0; font-family: var(--font-display, Georgia, serif); font-size: 1.6rem; letter-spacing: -0.01em; text-transform: none; }
    .she__link::after { display: none; }
    .she__mega-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .she__feature { grid-column: 1 / -1; }
    .she__right [aria-label='Search'] { display: none; }
    .she__link--desk, .she__mega { display: none !important; }
    .she__link--mob { display: inline-flex !important; }
    .she__sub { display: flex !important; flex-direction: row !important; flex-wrap: wrap; gap: 0 1rem !important; padding-bottom: var(--sp-3, 0.75rem) !important; }
    .she__sub a { display: inline-flex; min-height: 40px; align-items: center; font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; }
  }
  @container (max-width: 480px) {
    .she__bag { font-size: 0; gap: 0; }
    .she__count { font-size: 0.7rem; }
  }
  @media (prefers-reduced-motion: reduce) {
    .she__mega, .she__count.is-bump { animation: none; }
    .she__link::after { transition: none; }
  }
</style>
--- /** * ShopHeaderEditorial — Editorial store header: thin announcement line, left menu, centred * wordmark, right utilities (search, theme toggle, bag count) and a full-width mega menu. * * @param {string} brand - Wordmark text. Default 'Forma'. * @param {string} brandHref - Wordmark link. Default '/'. * @param {string} announcement - Top line text (empty hides it). * @param {Array} links - `{ label, href, columns?: { title, links: {label, href}[] }[], feature?: { title, text, href, icon?, hue? } }`. * @param {string} searchHref - Search page URL (empty hides the icon). Default '#'. * @param {string} bagHref - Bag page URL. When empty, the bag button opens a CartDrawer instead. * @param {string} cartKey - Cart storage key for the live count. Default 'ack-cart'. * @param {boolean} themeToggle - Show a light/dark toggle (html[data-theme]). Default true. * @param {string} currentHref - Marks the active link. * @param {boolean} sticky - Stick to the top on scroll. Default true. * @param {string} variant - Style (paper|transparent|inverted). Default 'paper'. * @param {string} size - Height (sm|md|lg). Default 'md'. * @param {string} id - Base id. Default 'shop-header'. * @param {string} className - Extra class. * * Accessibility: mega menus are disclosure buttons (aria-expanded) that close on Escape/outside * click; the mobile menu is a disclosure too; the bag link label includes the live count. */ import ProductMedia from '../ecommerce/ProductMedia.astro'; interface Col { title: string; links: Array<{ label: string; href: string }> } interface Feature { title: string; text?: string; href: string; icon?: string; hue?: number } interface NavLink { label: string; href: string; columns?: Col[]; feature?: Feature } interface Props { brand?: string; brandHref?: string; announcement?: string; links?: NavLink[]; searchHref?: string; bagHref?: string; cartKey?: string; themeToggle?: boolean; currentHref?: string; sticky?: boolean; variant?: 'paper' | 'transparent' | 'inverted'; size?: 'sm' | 'md' | 'lg'; id?: string; className?: string; } const { brand = 'Forma', brandHref = '/', announcement = 'Complimentary shipping over €150 · Free returns within 30 days', links = [ { label: 'Shop', href: '#', columns: [ { title: 'Clothing', links: [{ label: 'Overshirts', href: '#' }, { label: 'Knitwear', href: '#' }, { label: 'Outerwear', href: '#' }] }, { title: 'Objects', links: [{ label: 'Ceramics', href: '#' }, { label: 'Lighting', href: '#' }, { label: 'Plants', href: '#' }] }, { title: 'Accessories', links: [{ label: 'Bags', href: '#' }, { label: 'Eyewear', href: '#' }, { label: 'Watches', href: '#' }] }, ], feature: { title: 'The Autumn edit', text: 'Twelve pieces in warm, quiet tones.', href: '#', icon: 'jacket', hue: 22 }, }, { label: 'New in', href: '#' }, { label: 'Journal', href: '#' }, ], searchHref = '#', bagHref = '#', cartKey = 'ack-cart', themeToggle = true, currentHref, sticky = true, variant = 'paper', size = 'md', id = 'shop-header', className = '', } = Astro.props; --- <header class:list={['she', `she--${variant}`, `she--${size}`, { 'she--sticky': sticky }, className]} data-shop-header data-key={cartKey}> {announcement && <p class="she__announce">{announcement}</p>} <div class="she__bar"> <div class="she__left"> <button class="she__icon she__burger" type="button" aria-expanded="false" aria-controls={`${id}-nav`} aria-label="Menu" data-burger> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M3 8h18M3 16h18"/></svg> </button> <nav class="she__nav" id={`${id}-nav`} aria-label="Main" data-nav> <ul> {links.map((link, i) => ( <li> {link.columns?.length ? ( <> <button class="she__link she__link--desk" type="button" aria-expanded="false" aria-controls={`${id}-mega-${i}`} data-mega-toggle>{link.label}</button> <a class="she__link she__link--mob" href={link.href}>{link.label}</a> <ul class="she__sub"> {link.columns.flatMap((c) => c.links).map((l) => <li><a href={l.href}>{l.label}</a></li>)} </ul> </> ) : ( <a class="she__link" href={link.href} aria-current={currentHref === link.href ? 'page' : undefined}>{link.label}</a> )} </li> ))} </ul> </nav> </div> <a class="she__brand" href={brandHref}>{brand}</a> <div class="she__right"> {searchHref && ( <a class="she__icon" href={searchHref} aria-label="Search"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg> </a> )} {themeToggle && ( <button class="she__icon" type="button" aria-label="Switch theme" data-theme-btn> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z" fill="currentColor"/></svg> </button> )} {bagHref ? ( <a class="she__bag" href={bagHref} aria-label="Bag" data-bag-link> Bag <span class="she__count" data-count>0</span> </a> ) : ( <button class="she__bag" type="button" aria-label="Bag" data-bag-link data-cart-open data-cart-key={cartKey}> Bag <span class="she__count" data-count>0</span> </button> )} </div> </div> {links.map((link, i) => link.columns?.length ? ( <div class="she__mega" id={`${id}-mega-${i}`} hidden data-mega> <div class="she__mega-inner"> {link.columns.map((col) => ( <div class="she__col"> <p class="she__col-title">{col.title}</p> <ul>{col.links.map((l) => <li><a href={l.href}>{l.label}</a></li>)}</ul> </div> ))} <a class="she__col she__col--all" href={link.href}>View everything <span aria-hidden="true">→</span></a> {link.feature && ( <a class="she__feature" href={link.feature.href}> <ProductMedia icon={link.feature.icon} hue={link.feature.hue} ratio="4 / 3" variant="soft" size="lg" /> <span class="she__feature-title">{link.feature.title}</span> {link.feature.text && <span class="she__feature-text">{link.feature.text}</span>} </a> )} </div> </div> ) : null)} </header> <script> import { createCart } from '../ecommerce/cart-store'; document.querySelectorAll<HTMLElement>('[data-shop-header]:not([data-bound])').forEach((header) => { header.dataset.bound = ''; const toggles = Array.from(header.querySelectorAll<HTMLButtonElement>('[data-mega-toggle]')); const panelOf = (b: HTMLButtonElement) => document.getElementById(b.getAttribute('aria-controls') || ''); const closeAll = (except?: HTMLButtonElement) => toggles.forEach((b) => { if (b === except) return; b.setAttribute('aria-expanded', 'false'); const p = panelOf(b); if (p) p.hidden = true; }); toggles.forEach((b) => b.addEventListener('click', () => { const open = b.getAttribute('aria-expanded') !== 'true'; closeAll(b); b.setAttribute('aria-expanded', String(open)); const p = panelOf(b); if (p) p.hidden = !open; })); document.addEventListener('click', (e) => { if (!header.contains(e.target as Node)) closeAll(); }); document.addEventListener('keydown', (e) => { if (e.key !== 'Escape') return; const open = toggles.find((b) => b.getAttribute('aria-expanded') === 'true'); closeAll(); open?.focus(); const burger = header.querySelector<HTMLButtonElement>('[data-burger]'); if (burger?.getAttribute('aria-expanded') === 'true') { burger.click(); burger.focus(); } }); const burger = header.querySelector<HTMLButtonElement>('[data-burger]'); const nav = header.querySelector<HTMLElement>('[data-nav]'); burger?.addEventListener('click', () => { const open = burger.getAttribute('aria-expanded') !== 'true'; burger.setAttribute('aria-expanded', String(open)); nav?.classList.toggle('is-open', open); }); header.querySelector('[data-theme-btn]')?.addEventListener('click', () => { const root = document.documentElement; const next = root.dataset.theme === 'dark' ? 'light' : 'dark'; root.dataset.theme = next; try { window.localStorage.setItem('ack-theme', next); } catch { /* ignore */ } }); const bag = header.querySelector<HTMLElement>('[data-bag-link]'); const count = header.querySelector<HTMLElement>('[data-count]'); if (bag && count) { const cart = createCart(header.dataset.key); const render = () => { const n = cart.count(); count.textContent = String(n); bag.setAttribute('aria-label', `Bag, ${n} ${n === 1 ? 'item' : 'items'}`); }; render(); cart.subscribe(() => { render(); count.classList.remove('is-bump'); void count.offsetWidth; count.classList.add('is-bump'); }); if (bag.tagName === 'BUTTON') { bag.addEventListener('click', () => document.dispatchEvent(new CustomEvent('ack:cart-open', { detail: { key: cart.key, trigger: bag } }))); } } }); </script> <style> .she { container-type: inline-size; --she-h: 72px; position: relative; z-index: 50; color: var(--c-text-1, #fff); background: var(--c-bg, #050505); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); } .she--sm { --she-h: 60px; } .she--lg { --she-h: 88px; } .she--sticky { position: sticky; top: 0; } .she--transparent { background: transparent; border-bottom-color: transparent; } .she--inverted { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); } .she__announce { margin: 0; padding: 0.5rem var(--sp-4, 1rem); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); font-size: 0.72rem; letter-spacing: 0.12em; text-align: center; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); } .she__bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--sp-4, 1rem); max-width: 1440px; height: var(--she-h); margin: 0 auto; padding: 0 clamp(1rem, 3vw, 2.5rem); } .she__left, .she__right { display: flex; align-items: center; gap: var(--sp-1, 0.25rem); } .she__right { justify-content: flex-end; } .she__brand { font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 400; letter-spacing: -0.03em; color: inherit; text-decoration: none; } .she__brand::after { content: '°'; color: var(--shop-accent, var(--c-primary-light, #d4a017)); } .she__nav ul { display: flex; gap: var(--sp-1, 0.25rem); margin: 0; padding: 0; list-style: none; } .she__link { position: relative; display: inline-flex; align-items: center; min-height: 44px; padding: 0 0.75rem; border: 0; background: none; color: inherit; font: inherit; font-size: 0.82rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none; cursor: pointer; } .she__link::after { content: ''; position: absolute; left: 0.75rem; right: 0.75rem; bottom: 10px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease; } .she__link:hover::after, .she__link[aria-expanded='true']::after, .she__link[aria-current='page']::after { transform: scaleX(1); } .she__icon, .she__bag { display: inline-grid; place-items: center; min-width: 44px; height: 44px; border: 0; background: none; color: inherit; cursor: pointer; text-decoration: none; } .she__bag { display: inline-flex; align-items: center; gap: 6px; padding: 0 0.5rem; font: inherit; font-size: 0.82rem; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; } .she__count { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px; background: var(--c-text-1, #fff); color: var(--c-bg, #050505); font-size: 0.7rem; font-weight: 700; letter-spacing: 0; } .she--inverted .she__count { background: var(--c-bg, #050505); color: var(--c-text-1, #fff); } .she__count.is-bump { animation: she-bump 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes she-bump { 40% { transform: scale(1.3); background: var(--shop-accent, var(--c-primary-light, #d4a017)); } } .she :is(a, button):focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; } .she__burger { display: none; } .she__link--mob, .she__sub { display: none !important; } .she__mega { position: absolute; left: 0; right: 0; top: 100%; background: var(--c-bg, #050505); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); box-shadow: 0 40px 60px -40px rgba(0, 0, 0, 0.5); animation: she-drop 0.25s ease-out; } .she__mega[hidden] { display: none; } .she__mega-inner { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(0, 0.8fr) minmax(0, 1.4fr); gap: var(--sp-8, 2rem); max-width: 1440px; margin: 0 auto; padding: var(--sp-8, 2rem) clamp(1rem, 3vw, 2.5rem) var(--sp-10, 2.5rem); } .she__col-title { margin: 0 0 var(--sp-3, 0.75rem); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); } .she__col ul { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; } .she__col a { display: inline-flex; min-height: 36px; align-items: center; font-family: var(--font-display, Georgia, serif); font-size: 1.15rem; color: var(--c-text-1, #fff); text-decoration: none; } .she__col a:hover { color: var(--shop-accent, var(--c-primary-light, #d4a017)); } .she__col--all { align-self: end; font-family: inherit !important; font-size: 0.85rem !important; text-decoration: underline !important; text-underline-offset: 4px; } .she__feature { display: grid; gap: 6px; color: inherit; text-decoration: none; } .she__feature :global(.p-media) { border-radius: var(--r-sm, 2px); } .she__feature-title { font-family: var(--font-display, Georgia, serif); font-size: 1.2rem; color: var(--c-text-1, #fff); } .she__feature-text { font-size: 0.85rem; color: var(--c-text-2, #a3a3a3); } @keyframes she-drop { from { opacity: 0; transform: translateY(-6px); } } @container (max-width: 900px) { .she__burger { display: inline-grid; } .she__nav { position: absolute; left: 0; right: 0; top: 100%; display: none; padding: var(--sp-4, 1rem) clamp(1rem, 3vw, 2.5rem) var(--sp-6, 1.5rem); background: var(--c-bg, #050505); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); } .she__nav.is-open { display: block; } .she__nav ul { flex-direction: column; } .she__link { width: 100%; min-height: 52px; padding: 0; font-family: var(--font-display, Georgia, serif); font-size: 1.6rem; letter-spacing: -0.01em; text-transform: none; } .she__link::after { display: none; } .she__mega-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); } .she__feature { grid-column: 1 / -1; } .she__right [aria-label='Search'] { display: none; } .she__link--desk, .she__mega { display: none !important; } .she__link--mob { display: inline-flex !important; } .she__sub { display: flex !important; flex-direction: row !important; flex-wrap: wrap; gap: 0 1rem !important; padding-bottom: var(--sp-3, 0.75rem) !important; } .she__sub a { display: inline-flex; min-height: 40px; align-items: center; font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; } } @container (max-width: 480px) { .she__bag { font-size: 0; gap: 0; } .she__count { font-size: 0.7rem; } } @media (prefers-reduced-motion: reduce) { .she__mega, .she__count.is-bump { animation: none; } .she__link::after { transition: none; } } </style>

Información Rápida

Categoría
Secciones de Página
Nombre de archivo
ShopHeaderEditorial.astro
Dependencias
Ninguna — Astro nativo + CSS puro
Etiquetas
headernavbarmega-menuecommerceeditorial