1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
E-commerceecommerceshopnavbarheadermenucart

Store Header

Barra de navegación de tienda con desplegables de categorías, buscador, cuenta y carrito con contador en vivo; menú móvil incluido.

Vista Previa en Vivo

Uso

Copia y pega este código directamente en tu componente React:

tsx
import { StoreHeader } from 'react-component-kit-ui/ecommerce';

<StoreHeader brand="Nova Store" links={[
    { label: 'Shop', href: '/shop/', children: [
      { label: 'Audio', href: '/shop/?category=audio', description: 'Headphones & speakers' },
      { label: 'Bags', href: '/shop/?category=bags' },
    ] },
    { label: 'Sale', href: '/shop/?sort=price-asc' },
  ]} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/StoreHeader.tsx + StoreHeader.css:

StoreHeader.tsx
'use client';

import { useEffect, useRef } from 'react';
import { createCart } from './cart-store';
import './StoreHeader.css';

/** class:list → className */
function cx(...args: unknown[]): string {
  const out: string[] = [];
  const walk = (v: unknown) => {
    if (!v) return;
    if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
    else if (Array.isArray(v)) v.forEach(walk);
    else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
  };
  args.forEach(walk);
  return out.join(' ');
}

interface ChildLink { label: string; href: string; description?: string }

interface NavLink { label: string; href: string; children?: ChildLink[] }

/**
 * StoreHeader — Shop navigation bar: brand, menu with category dropdowns, search,
 * account link and a cart button whose badge follows the cart store live.
 *
 * @param {string} brand - Store name. Default 'Nova Store'.
 * @param {string} brandHref - Brand link. Default '/'.
 * @param {Array} links - Menu items `{ label, href, children?: { label, href, description? }[] }`.
 * @param {string} searchAction - Search form action URL. Default '#'.
 * @param {string} searchPlaceholder - Search placeholder. Default 'Search products'.
 * @param {string} accountHref - Account link (hidden when empty). Default '#'.
 * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
 * @param {string} cartLabel - Accessible label for the cart button. Default 'Cart'.
 * @param {string} currentHref - Marks the matching menu link with aria-current.
 * @param {boolean} sticky - Stick to the top on scroll. Default true.
 * @param {string} variant - Style (glass|solid|transparent). Default 'glass'.
 * @param {string} size - Height (sm|md|lg). Default 'md'.
 * @param {boolean} themeToggle - Show a light/dark toggle (sets html[data-theme], remembered in localStorage). Default true.
 * @param {string} id - Base id for inner elements (unique per page). Default 'store-header'.
 * @param {string} className - Extra class.
 *
 * Accessibility: dropdowns are disclosure buttons (aria-expanded) closed with Escape or outside click;
 * the mobile menu is a disclosure too; the cart badge count is part of the button label.
 * Clicking the cart button dispatches `ack:cart-open`, which CartDrawer listens to.
 */
export interface StoreHeaderProps {
  brand?: string;
  brandHref?: string;
  links?: NavLink[];
  searchAction?: string;
  searchPlaceholder?: string;
  accountHref?: string;
  cartKey?: string;
  cartLabel?: string;
  currentHref?: string;
  sticky?: boolean;
  variant?: 'glass' | 'solid' | 'transparent';
  size?: 'sm' | 'md' | 'lg';
  themeToggle?: boolean;
  id?: string;
  className?: string;
}

export function StoreHeader({
  brand = 'Nova Store',
  brandHref = '/',
  links = [
    {
      label: 'Shop',
      href: '#',
      children: [
        { label: 'Audio', href: '#', description: 'Headphones & speakers' },
        { label: 'Wearables', href: '#', description: 'Watches & bands' },
        { label: 'Bags', href: '#', description: 'Backpacks & totes' },
        { label: 'Home', href: '#', description: 'Lamps, mugs & plants' },
      ],
    },
    { label: 'New in', href: '#' },
    { label: 'Sale', href: '#' },
    { label: 'Journal', href: '#' },
  ],
  searchAction = '#',
  searchPlaceholder = 'Search products',
  accountHref = '#',
  cartKey = 'ack-cart',
  cartLabel = 'Cart',
  currentHref,
  sticky = true,
  variant = 'glass',
  size = 'md',
  themeToggle = true,
  id = 'store-header',
  className = '',
}: StoreHeaderProps) {
  const uid = id;

  // Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
  const mounted = useRef(false);
  useEffect(() => {
    if (mounted.current) return;
    mounted.current = true;
    document.querySelectorAll<HTMLElement>('[data-store-header]:not([data-bound])').forEach((header) => {
        header.dataset.bound = '';
        const toggle = header.querySelector<HTMLButtonElement>('[data-menu-toggle]');
        const menu = header.querySelector<HTMLElement>('[data-menu]');
        const dropdownToggles = Array.from(header.querySelectorAll<HTMLButtonElement>('[data-dropdown-toggle]'));
    
        const closeDropdowns = (except?: HTMLButtonElement) => {
          dropdownToggles.forEach((btn) => {
            if (btn === except) return;
            btn.setAttribute('aria-expanded', 'false');
            const panel = document.getElementById(btn.getAttribute('aria-controls') || '');
            if (panel) panel.hidden = true;
          });
        };
    
        dropdownToggles.forEach((btn) => {
          btn.addEventListener('click', () => {
            const open = btn.getAttribute('aria-expanded') !== 'true';
            closeDropdowns(btn);
            btn.setAttribute('aria-expanded', String(open));
            const panel = document.getElementById(btn.getAttribute('aria-controls') || '');
            if (panel) panel.hidden = !open;
          });
        });
    
        toggle?.addEventListener('click', () => {
          const open = toggle.getAttribute('aria-expanded') !== 'true';
          toggle.setAttribute('aria-expanded', String(open));
          menu?.classList.toggle('is-open', open);
        });
    
        document.addEventListener('click', (e) => {
          if (!header.contains(e.target as Node)) closeDropdowns();
        });
        document.addEventListener('keydown', (e) => {
          if (e.key !== 'Escape') return;
          const openBtn = dropdownToggles.find((b) => b.getAttribute('aria-expanded') === 'true');
          closeDropdowns();
          openBtn?.focus();
          if (toggle?.getAttribute('aria-expanded') === 'true') {
            toggle.setAttribute('aria-expanded', 'false');
            menu?.classList.remove('is-open');
            toggle.focus();
          }
        });
    
        const themeBtn = header.querySelector<HTMLButtonElement>('[data-theme-toggle]');
        if (themeBtn) {
          const root = document.documentElement;
          const current = () => (root.dataset.theme === 'light' ? 'light' : 'dark');
          const label = () => themeBtn.setAttribute('aria-label', current() === 'light' ? 'Switch to dark theme' : 'Switch to light theme');
          label();
          themeBtn.addEventListener('click', () => {
            const next = current() === 'light' ? 'dark' : 'light';
            root.dataset.theme = next;
            try { window.localStorage.setItem('ack-theme', next); } catch { /* ignore */ }
            label();
            document.dispatchEvent(new CustomEvent('ack:theme', { detail: { theme: next } }));
          });
        }
    
        const cartBtn = header.querySelector<HTMLButtonElement>('[data-cart-open]');
        const countEl = header.querySelector<HTMLElement>('[data-cart-count]');
        if (cartBtn && countEl) {
          const cart = createCart(cartBtn.dataset.cartKey);
          const label = cartBtn.dataset.cartLabel || 'Cart';
          const render = () => {
            const n = cart.count();
            countEl.textContent = n > 99 ? '99+' : String(n);
            countEl.hidden = n === 0;
            cartBtn.setAttribute('aria-label', n ? `${label} (${n})` : label);
          };
          render();
          cart.subscribe((_items) => {
            render();
            countEl.classList.remove('is-bump');
            void countEl.offsetWidth;
            countEl.classList.add('is-bump');
          });
          cartBtn.addEventListener('click', () => {
            document.dispatchEvent(new CustomEvent('ack:cart-open', { detail: { key: cart.key, trigger: cartBtn } }));
          });
        }
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <header className={cx(['store-header', `store-header--${variant}`, `store-header--${size}`, { 'store-header--sticky': sticky }, className])} data-store-header data-rk-bf78a5="">
        <div className="store-header__inner" data-rk-bf78a5="">
          {links.length > 0 && (
          <button className="store-header__toggle" type="button" aria-expanded="false" aria-controls={`${uid}-nav`} aria-label="Menu" data-menu-toggle data-rk-bf78a5="">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true" data-rk-bf78a5=""><path className="l1" d="M4 7h16" data-rk-bf78a5="" /><path className="l2" d="M4 12h16" data-rk-bf78a5="" /><path className="l3" d="M4 17h16" data-rk-bf78a5="" /></svg>
          </button>
          )}
      
          <a className="store-header__brand" href={brandHref} data-rk-bf78a5="">
            <span className="store-header__logo" aria-hidden="true" data-rk-bf78a5="">
              <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" data-rk-bf78a5=""><path d="M12 2 3 7v10l9 5 9-5V7z" data-rk-bf78a5="" /><path d="m3 7 9 5 9-5M12 12v10" data-rk-bf78a5="" /></svg>
            </span>
            <span data-rk-bf78a5="">{brand}</span>
          </a>
      
          {links.length > 0 ? (
          <nav className="store-header__nav" id={`${uid}-nav`} aria-label="Main" data-menu data-rk-bf78a5="">
            <ul className="store-header__links" data-rk-bf78a5="">
              {links.map((link, i) => (
                <li key={i} className="store-header__item" data-rk-bf78a5="">
                  {link.children?.length ? (
                    <>
                      <button className="store-header__link store-header__link--parent" type="button" aria-expanded="false" aria-controls={`${uid}-dd-${i}`} data-dropdown-toggle data-rk-bf78a5="">
                        {link.label}
                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" data-rk-bf78a5=""><path d="m6 9 6 6 6-6" data-rk-bf78a5="" /></svg>
                      </button>
                      <div className="store-header__dropdown" id={`${uid}-dd-${i}`} hidden data-dropdown data-rk-bf78a5="">
                        <ul data-rk-bf78a5="">
                          {link.children.map((child, __i: number) => (
                            <li key={__i} data-rk-bf78a5="">
                              <a href={child.href} aria-current={currentHref === child.href ? 'page' : undefined} data-rk-bf78a5="">
                                <strong data-rk-bf78a5="">{child.label}</strong>
                                {child.description && <span data-rk-bf78a5="">{child.description}</span>}
                              </a>
                            </li>
                          ))}
                          <li className="store-header__dropdown-all" data-rk-bf78a5=""><a href={link.href} data-rk-bf78a5="">View all →</a></li>
                        </ul>
                      </div>
                    </>
                  ) : (
                    <a className="store-header__link" href={link.href} aria-current={currentHref === link.href ? 'page' : undefined} data-rk-bf78a5="">{link.label}</a>
                  )}
                </li>
              ))}
            </ul>
          </nav>
          ) : <div className="store-header__nav" data-rk-bf78a5="" />}
      
          <div className="store-header__actions" data-rk-bf78a5="">
            <form className="store-header__search" role="search" action={searchAction} data-rk-bf78a5="">
              <label className="sr-only" htmlFor={`${uid}-q`} data-rk-bf78a5="">{searchPlaceholder}</label>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" aria-hidden="true" data-rk-bf78a5=""><circle cx="11" cy="11" r="7" data-rk-bf78a5="" /><path d="m20 20-3.5-3.5" data-rk-bf78a5="" /></svg>
              <input id={`${uid}-q`} name="q" type="search" placeholder={searchPlaceholder} autoComplete="off" data-rk-bf78a5="" />
            </form>
            {themeToggle && (
              <button className="store-header__icon store-header__theme" type="button" aria-label="Switch to light theme" data-theme-toggle data-rk-bf78a5="">
                <svg className="i-sun" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true" data-rk-bf78a5=""><circle cx="12" cy="12" r="4" data-rk-bf78a5="" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" data-rk-bf78a5="" /></svg>
                <svg className="i-moon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" data-rk-bf78a5=""><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" data-rk-bf78a5="" /></svg>
              </button>
            )}
            {accountHref && (
              <a className="store-header__icon" href={accountHref} aria-label="Account" data-rk-bf78a5="">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true" data-rk-bf78a5=""><circle cx="12" cy="8" r="4" data-rk-bf78a5="" /><path d="M4 21a8 8 0 0 1 16 0" data-rk-bf78a5="" /></svg>
              </a>
            )}
            <button className="store-header__icon store-header__cart" type="button" data-cart-open data-cart-key={cartKey} data-cart-label={cartLabel} aria-label={cartLabel} data-rk-bf78a5="">
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" data-rk-bf78a5=""><path d="M6 7h12l-1 13H7z" data-rk-bf78a5="" /><path d="M9 7V6a3 3 0 0 1 6 0v1" data-rk-bf78a5="" /></svg>
              <span className="store-header__count" data-cart-count hidden aria-hidden="true" data-rk-bf78a5="">0</span>
            </button>
          </div>
        </div>
      </header>
    </>
  );
}

export default StoreHeader;
StoreHeader.css
/* StoreHeader — ported from astro-component-kit (ecommerce/StoreHeader.astro) */

.store-header:where([data-rk-bf78a5]) {
  container-type: inline-size;
  --sh-h: 68px;
  position: relative;
  z-index: 50;
  width: 100%;
  color: var(--c-text-1, #fff);
  border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
}
.store-header--sm:where([data-rk-bf78a5]) { --sh-h: 56px; }
.store-header--lg:where([data-rk-bf78a5]) { --sh-h: 80px; }
.store-header--sticky:where([data-rk-bf78a5]) { position: sticky; top: 0; }
.store-header--glass:where([data-rk-bf78a5]) { background: rgb(var(--paper-rgb, 5 5 5) / 0.72); backdrop-filter: blur(18px) saturate(1.4); }
.store-header--solid:where([data-rk-bf78a5]) { background: var(--c-bg-1, #0a0a0a); }
.store-header--transparent:where([data-rk-bf78a5]) { background: transparent; border-bottom-color: transparent; }

.store-header__inner:where([data-rk-bf78a5]) {
  display: flex;
  align-items: center;
  gap: var(--sp-6, 1.5rem);
  max-width: 1280px;
  height: var(--sh-h);
  margin: 0 auto;
  padding: 0 var(--sp-6, 1.5rem);
}

.store-header__brand:where([data-rk-bf78a5]) {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2, 0.5rem);
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.02em;
  color: var(--c-text-1, #fff);
  text-decoration: none;
  white-space: nowrap;
}
.store-header__logo:where([data-rk-bf78a5]) {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--c-primary, #b8860b), var(--c-primary-light, #d4a017));
  color: var(--c-on-primary, #050505);
}

.store-header__nav:where([data-rk-bf78a5]) { flex: 1; }
.store-header__links:where([data-rk-bf78a5]) { display: flex; align-items: center; gap: var(--sp-1, 0.25rem); margin: 0; padding: 0; list-style: none; }
.store-header__item:where([data-rk-bf78a5]) { position: relative; }
.store-header__link:where([data-rk-bf78a5]) {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 40px;
  padding: 0 var(--sp-3, 0.75rem);
  border: 0;
  border-radius: var(--r-full, 9999px);
  background: transparent;
  color: var(--c-text-2, #a3a3a3);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}
.store-header__link:hover:where([data-rk-bf78a5]),
.store-header__link[aria-expanded='true']:where([data-rk-bf78a5]),
.store-header__link[aria-current='page']:where([data-rk-bf78a5]) { color: var(--c-text-1, #fff); background: rgb(var(--ink-rgb, 255 255 255) / 0.05); }
.store-header__link:where([data-rk-bf78a5]) svg:where([data-rk-bf78a5]) { transition: transform 0.2s ease; }
.store-header__link[aria-expanded='true']:where([data-rk-bf78a5]) svg:where([data-rk-bf78a5]) { transform: rotate(180deg); }

.store-header__dropdown:where([data-rk-bf78a5]) {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 280px;
  padding: var(--sp-2, 0.5rem);
  border-radius: var(--r-md, 12px);
  border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
  background: rgb(var(--paper-rgb, 5 5 5) / 0.96);
  backdrop-filter: blur(18px);
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.8);
  animation: store-header-sh-pop 0.18s ease-out;
}
.store-header__dropdown:where([data-rk-bf78a5]) ul:where([data-rk-bf78a5]) { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.store-header__dropdown:where([data-rk-bf78a5]) a:where([data-rk-bf78a5]) {
  display: grid;
  gap: 2px;
  padding: var(--sp-2, 0.5rem) var(--sp-3, 0.75rem);
  border-radius: var(--r-sm, 8px);
  color: var(--c-text-1, #fff);
  text-decoration: none;
  font-size: 0.9rem;
}
.store-header__dropdown:where([data-rk-bf78a5]) a:where([data-rk-bf78a5]) span:where([data-rk-bf78a5]) { font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
.store-header__dropdown:where([data-rk-bf78a5]) a:hover:where([data-rk-bf78a5]) { background: rgb(var(--ink-rgb, 255 255 255) / 0.05); }
.store-header__dropdown-all:where([data-rk-bf78a5]) { border-top: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05)); margin-top: 4px; padding-top: 4px; }
.store-header__dropdown-all:where([data-rk-bf78a5]) a:where([data-rk-bf78a5]) { color: var(--c-primary-light, #d4a017); font-weight: 600; }
@keyframes store-header-sh-pop { from { opacity: 0; transform: translateY(-4px); } }

.store-header__actions:where([data-rk-bf78a5]) { display: flex; align-items: center; gap: var(--sp-2, 0.5rem); margin-left: auto; }
.store-header__search:where([data-rk-bf78a5]) {
  display: flex;
  align-items: center;
  gap: var(--sp-2, 0.5rem);
  height: 40px;
  padding: 0 var(--sp-3, 0.75rem);
  border-radius: var(--r-full, 9999px);
  border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
  background: rgb(var(--ink-rgb, 255 255 255) / 0.03);
  color: var(--c-text-2, #a3a3a3);
  transition: border-color 0.2s ease;
}
.store-header__search:focus-within:where([data-rk-bf78a5]) { border-color: var(--c-primary-light, #d4a017); }
.store-header__search:where([data-rk-bf78a5]) input:where([data-rk-bf78a5]) {
  width: 170px;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--c-text-1, #fff);
  font: inherit;
  font-size: 0.875rem;
}
.store-header__search:where([data-rk-bf78a5]) input:where([data-rk-bf78a5])::placeholder { color: var(--c-text-2, #a3a3a3); }

.store-header__icon:where([data-rk-bf78a5]),
.store-header__toggle:where([data-rk-bf78a5]) {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--r-full, 9999px);
  background: transparent;
  color: var(--c-text-1, #fff);
  cursor: pointer;
  transition: background 0.2s ease;
}
.store-header__icon:hover:where([data-rk-bf78a5]),
.store-header__toggle:hover:where([data-rk-bf78a5]) { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); color: var(--c-text-1, #fff); }
.store-header__toggle:where([data-rk-bf78a5]) { display: none; }
.store-header__theme:where([data-rk-bf78a5]) .i-moon:where([data-rk-bf78a5]) { display: none; }
[data-theme='light'] .store-header__theme:where([data-rk-bf78a5]) .i-sun:where([data-rk-bf78a5]) { display: none; }
[data-theme='light'] .store-header__theme:where([data-rk-bf78a5]) .i-moon:where([data-rk-bf78a5]) { display: block; }
.store-header:where([data-rk-bf78a5]) :is(a, button, input):focus-visible:where([data-rk-bf78a5]) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }

.store-header__count:where([data-rk-bf78a5]) {
  position: absolute;
  top: 4px;
  right: 2px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--r-full, 9999px);
  background: var(--c-primary-light, #d4a017);
  color: var(--c-on-primary, #050505);
  font-size: 0.68rem;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
}
.store-header__count[hidden]:where([data-rk-bf78a5]) { display: none; }
.store-header__count.is-bump:where([data-rk-bf78a5]) { animation: store-header-sh-bump 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes store-header-sh-bump { 40% { transform: scale(1.35); } }

.sr-only:where([data-rk-bf78a5]) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

@container (max-width: 960px) {
  .store-header__inner:where([data-rk-bf78a5]) { gap: var(--sp-2, 0.5rem); padding: 0 var(--sp-3, 0.75rem); }
  .store-header__toggle:where([data-rk-bf78a5]) { display: grid; }
  .store-header__search:where([data-rk-bf78a5]) { display: none; }
  .store-header__nav:where([data-rk-bf78a5]) {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: none;
    padding: var(--sp-3, 0.75rem);
    background: var(--c-bg-1, #0a0a0a);
    box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.9);
    border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
    max-height: calc(100dvh - var(--sh-h));
    overflow-y: auto;
  }
  .store-header__nav.is-open:where([data-rk-bf78a5]) { display: block; }
  .store-header__links:where([data-rk-bf78a5]) { flex-direction: column; align-items: stretch; }
  .store-header__link:where([data-rk-bf78a5]) { width: 100%; justify-content: space-between; min-height: 48px; border-radius: var(--r-sm, 8px); }
  .store-header__dropdown:where([data-rk-bf78a5]) { position: static; min-width: 0; box-shadow: none; background: rgb(var(--ink-rgb, 255 255 255) / 0.02); margin: 4px 0; }
  .store-header__toggle[aria-expanded='true']:where([data-rk-bf78a5]) .l1:where([data-rk-bf78a5]) { transform: translateY(5px) rotate(45deg); transform-origin: center; }
  .store-header__toggle[aria-expanded='true']:where([data-rk-bf78a5]) .l2:where([data-rk-bf78a5]) { opacity: 0; }
  .store-header__toggle[aria-expanded='true']:where([data-rk-bf78a5]) .l3:where([data-rk-bf78a5]) { transform: translateY(-5px) rotate(-45deg); transform-origin: center; }
  .store-header__toggle:where([data-rk-bf78a5]) path:where([data-rk-bf78a5]) { transition: transform 0.2s ease, opacity 0.2s ease; transform-box: fill-box; }
}

@media (prefers-reduced-motion: reduce) {
  .store-header__dropdown:where([data-rk-bf78a5]), .store-header__count.is-bump:where([data-rk-bf78a5]) { animation: none; }
}

Información Rápida

Categoría
E-commerce
Nombre de archivo
StoreHeader.tsx
Dependencias
Ninguna — React + CSS puro
Etiquetas
ecommerceshopnavbarheadermenucart