1 звезда на GitHub = 1 новый компонент.

Поставить звезду
E-commerce

Shop Category Hero

Шапка каталога: хлебные крошки, крупное название категории с количеством, описание, подкатегории и изображения.

categoryheaderecommercecatalog
Живое превью

Clothing24

Overshirts, knitwear and outerwear cut from linen, merino and organic cotton. Designed to be layered, washed and worn for a decade.

Использование

Скопируйте и вставьте этот код на вашу страницу Astro:

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

<ShopCategoryHero title="Clothing" count={24} links={[{ label: 'All', href: '/shop/', active: true }, { label: 'Knitwear', href: '/shop/knit/' }]} />
--- import { ShopCategoryHero } from 'astro-component-kit/sections'; --- <ShopCategoryHero title="Clothing" count={24} links={[{ label: 'All', href: '/shop/', active: true }, { label: 'Knitwear', href: '/shop/knit/' }]} />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/sections/ShopCategoryHero.astro:

astro
---
/**
 * ShopCategoryHero — Catalogue page header: breadcrumb, oversized category title with item
 * count, intro text, sub-category links and a strip of three visuals.
 *
 * @param {Array<{label: string, href?: string}>} breadcrumb - Trail; the last item is the current page.
 * @param {string} title - Category name. Default 'Clothing'.
 * @param {number} count - Number of products (superscript). Optional.
 * @param {string} text - Intro paragraph.
 * @param {Array<{label: string, href: string, active?: boolean}>} links - Sub-category links.
 * @param {Array<{icon?: string, hue?: number, image?: string}>} visuals - Up to three images (empty hides the strip).
 * @param {string} variant - Layout (split|centered). Default 'split'.
 * @param {string} size - Vertical rhythm (sm|md|lg). Default 'md'.
 * @param {string} className - Extra class.
 *
 * Accessibility: breadcrumb nav with aria-current; one h1; visuals are decorative.
 */
import ProductMedia from '../ecommerce/ProductMedia.astro';

interface Props {
  breadcrumb?: Array<{ label: string; href?: string }>;
  title?: string;
  count?: number;
  text?: string;
  links?: Array<{ label: string; href: string; active?: boolean }>;
  visuals?: Array<{ icon?: string; hue?: number; image?: string }>;
  variant?: 'split' | 'centered';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  breadcrumb = [{ label: 'Home', href: '#' }, { label: 'Shop', href: '#' }, { label: 'Clothing' }],
  title = 'Clothing',
  count = 24,
  text = 'Overshirts, knitwear and outerwear cut from linen, merino and organic cotton. Designed to be layered, washed and worn for a decade.',
  links = [
    { label: 'All', href: '#', active: true },
    { label: 'Overshirts', href: '#' },
    { label: 'Knitwear', href: '#' },
    { label: 'Outerwear', href: '#' },
  ],
  visuals = [{ icon: 'shirt', hue: 30 }, { icon: 'jacket', hue: 22 }, { icon: 'sneaker', hue: 12 }],
  variant = 'split',
  size = 'md',
  className = '',
} = Astro.props;
---

<header class:list={['sch', `sch--${variant}`, `sch--${size}`, className]}>
  <nav class="sch__crumbs" aria-label="Breadcrumb">
    <ol>
      {breadcrumb.map((b, i) => (
        <li>{b.href && i < breadcrumb.length - 1 ? <a href={b.href}>{b.label}</a> : <span aria-current="page">{b.label}</span>}</li>
      ))}
    </ol>
  </nav>
  <div class="sch__main">
    <h1 class="sch__title">{title}{count !== undefined && <sup>{count}</sup>}</h1>
    <div class="sch__side">
      {text && <p class="sch__text">{text}</p>}
      {links.length > 0 && (
        <ul class="sch__links">
          {links.map((l) => <li><a href={l.href} aria-current={l.active ? 'page' : undefined}>{l.label}</a></li>)}
        </ul>
      )}
    </div>
  </div>
  {visuals.length > 0 && (
    <div class="sch__strip" aria-hidden="true">
      {visuals.slice(0, 3).map((v) => <ProductMedia image={v.image} icon={v.icon} hue={v.hue} ratio="16 / 9" variant="soft" size="md" />)}
    </div>
  )}
</header>

<style>
  .sch { container-type: inline-size; --py: clamp(1.5rem, 4vw, 3rem); padding-block: var(--py); color: var(--c-text-1, #fff); }
  .sch--sm { --py: 1rem; }
  .sch--lg { --py: clamp(2.5rem, 6vw, 5rem); }
  .sch__crumbs ol { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 var(--sp-6, 1.5rem); padding: 0; list-style: none; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
  .sch__crumbs li + li::before { content: '/'; margin-right: 0.5rem; opacity: 0.5; }
  .sch__crumbs a { color: inherit; text-decoration: none; }
  .sch__crumbs a:hover { color: var(--c-text-1, #fff); }
  .sch__crumbs [aria-current] { color: var(--c-text-1, #fff); }
  .sch__main { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr); gap: clamp(1rem, 4vw, 4rem); align-items: end; }
  .sch--centered .sch__main { grid-template-columns: 1fr; text-align: center; justify-items: center; }
  .sch__title { margin: 0; font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: clamp(3.5rem, 12vw, 10rem); font-weight: 400; letter-spacing: -0.055em; line-height: 0.85; }
  .sch__title sup { margin-left: 0.15em; font-family: var(--font-sans, system-ui); font-size: 0.14em; font-weight: 600; letter-spacing: 0; vertical-align: super; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
  .sch__text { margin: 0 0 var(--sp-4, 1rem); max-width: 30rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
  .sch__links { display: flex; flex-wrap: wrap; gap: 0 var(--sp-5, 1.25rem); margin: 0; padding: 0; list-style: none; }
  .sch--centered .sch__links { justify-content: center; }
  .sch__links a { display: inline-flex; align-items: center; min-height: 40px; font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; border-bottom: 1px solid transparent; }
  .sch__links a:hover, .sch__links a[aria-current] { color: var(--c-text-1, #fff); border-bottom-color: currentColor; }
  .sch a:focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }
  .sch__strip { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--sp-3, 0.75rem); margin-top: clamp(1.5rem, 4vw, 3rem); }
  .sch__strip :global(.p-media) { height: 100%; border-radius: var(--r-sm, 2px); }

  @container (max-width: 760px) {
    .sch__main { grid-template-columns: 1fr; }
    .sch__strip { grid-template-columns: 1fr 1fr; }
    .sch__strip > :global(:first-child) { grid-column: 1 / -1; }
  }
</style>
--- /** * ShopCategoryHero — Catalogue page header: breadcrumb, oversized category title with item * count, intro text, sub-category links and a strip of three visuals. * * @param {Array<{label: string, href?: string}>} breadcrumb - Trail; the last item is the current page. * @param {string} title - Category name. Default 'Clothing'. * @param {number} count - Number of products (superscript). Optional. * @param {string} text - Intro paragraph. * @param {Array<{label: string, href: string, active?: boolean}>} links - Sub-category links. * @param {Array<{icon?: string, hue?: number, image?: string}>} visuals - Up to three images (empty hides the strip). * @param {string} variant - Layout (split|centered). Default 'split'. * @param {string} size - Vertical rhythm (sm|md|lg). Default 'md'. * @param {string} className - Extra class. * * Accessibility: breadcrumb nav with aria-current; one h1; visuals are decorative. */ import ProductMedia from '../ecommerce/ProductMedia.astro'; interface Props { breadcrumb?: Array<{ label: string; href?: string }>; title?: string; count?: number; text?: string; links?: Array<{ label: string; href: string; active?: boolean }>; visuals?: Array<{ icon?: string; hue?: number; image?: string }>; variant?: 'split' | 'centered'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { breadcrumb = [{ label: 'Home', href: '#' }, { label: 'Shop', href: '#' }, { label: 'Clothing' }], title = 'Clothing', count = 24, text = 'Overshirts, knitwear and outerwear cut from linen, merino and organic cotton. Designed to be layered, washed and worn for a decade.', links = [ { label: 'All', href: '#', active: true }, { label: 'Overshirts', href: '#' }, { label: 'Knitwear', href: '#' }, { label: 'Outerwear', href: '#' }, ], visuals = [{ icon: 'shirt', hue: 30 }, { icon: 'jacket', hue: 22 }, { icon: 'sneaker', hue: 12 }], variant = 'split', size = 'md', className = '', } = Astro.props; --- <header class:list={['sch', `sch--${variant}`, `sch--${size}`, className]}> <nav class="sch__crumbs" aria-label="Breadcrumb"> <ol> {breadcrumb.map((b, i) => ( <li>{b.href && i < breadcrumb.length - 1 ? <a href={b.href}>{b.label}</a> : <span aria-current="page">{b.label}</span>}</li> ))} </ol> </nav> <div class="sch__main"> <h1 class="sch__title">{title}{count !== undefined && <sup>{count}</sup>}</h1> <div class="sch__side"> {text && <p class="sch__text">{text}</p>} {links.length > 0 && ( <ul class="sch__links"> {links.map((l) => <li><a href={l.href} aria-current={l.active ? 'page' : undefined}>{l.label}</a></li>)} </ul> )} </div> </div> {visuals.length > 0 && ( <div class="sch__strip" aria-hidden="true"> {visuals.slice(0, 3).map((v) => <ProductMedia image={v.image} icon={v.icon} hue={v.hue} ratio="16 / 9" variant="soft" size="md" />)} </div> )} </header> <style> .sch { container-type: inline-size; --py: clamp(1.5rem, 4vw, 3rem); padding-block: var(--py); color: var(--c-text-1, #fff); } .sch--sm { --py: 1rem; } .sch--lg { --py: clamp(2.5rem, 6vw, 5rem); } .sch__crumbs ol { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 var(--sp-6, 1.5rem); padding: 0; list-style: none; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); } .sch__crumbs li + li::before { content: '/'; margin-right: 0.5rem; opacity: 0.5; } .sch__crumbs a { color: inherit; text-decoration: none; } .sch__crumbs a:hover { color: var(--c-text-1, #fff); } .sch__crumbs [aria-current] { color: var(--c-text-1, #fff); } .sch__main { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr); gap: clamp(1rem, 4vw, 4rem); align-items: end; } .sch--centered .sch__main { grid-template-columns: 1fr; text-align: center; justify-items: center; } .sch__title { margin: 0; font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: clamp(3.5rem, 12vw, 10rem); font-weight: 400; letter-spacing: -0.055em; line-height: 0.85; } .sch__title sup { margin-left: 0.15em; font-family: var(--font-sans, system-ui); font-size: 0.14em; font-weight: 600; letter-spacing: 0; vertical-align: super; color: var(--shop-accent, var(--c-primary-light, #d4a017)); } .sch__text { margin: 0 0 var(--sp-4, 1rem); max-width: 30rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); } .sch__links { display: flex; flex-wrap: wrap; gap: 0 var(--sp-5, 1.25rem); margin: 0; padding: 0; list-style: none; } .sch--centered .sch__links { justify-content: center; } .sch__links a { display: inline-flex; align-items: center; min-height: 40px; font-size: 0.9rem; color: var(--c-text-2, #a3a3a3); text-decoration: none; border-bottom: 1px solid transparent; } .sch__links a:hover, .sch__links a[aria-current] { color: var(--c-text-1, #fff); border-bottom-color: currentColor; } .sch a:focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; } .sch__strip { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--sp-3, 0.75rem); margin-top: clamp(1.5rem, 4vw, 3rem); } .sch__strip :global(.p-media) { height: 100%; border-radius: var(--r-sm, 2px); } @container (max-width: 760px) { .sch__main { grid-template-columns: 1fr; } .sch__strip { grid-template-columns: 1fr 1fr; } .sch__strip > :global(:first-child) { grid-column: 1 / -1; } } </style>