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

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

Shop Hero Editorial

Hero магазина в журнальном стиле: крупный serif-заголовок, курсивное слово, главное фото с подписью и бегущая строка.

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

Autumn / Winter 26N° 04

Quiet objects for everyday rituals

Clothing and homeware made slowly, in small runs, by workshops we know by name. Designed to be used for years — and to look better for it.

Wool overcoat, in Rust — €320

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

Скопируйте и вставьте этот код в ваш React-компонент:

tsx
import { ShopHeroEditorial } from 'react-component-kit-ui/sections';

<ShopHeroEditorial title="Quiet *objects* for everyday rituals" primary={{ label: 'Shop now', href: '/shop/' }} />

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

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

ShopHeroEditorial.tsx
'use client';

import ProductMedia from '../ecommerce/ProductMedia';
import './ShopHeroEditorial.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 Cta { label: string; href: string }

/**
 * ShopHeroEditorial — Magazine-style shop hero: oversized serif headline with an italic accent
 * word, issue/season meta, two CTAs, a tall feature image with caption and a running ticker.
 *
 * @param {string} kicker - Small label above the title. Default 'Autumn / Winter 26'.
 * @param {string} title - Headline (wrap a word in *asterisks* to italicise it). Default 'Quiet *objects* for everyday rituals'.
 * @param {string} text - Supporting paragraph.
 * @param {{label: string, href: string}} primary - Main CTA.
 * @param {{label: string, href: string}} secondary - Secondary CTA (optional).
 * @param {{icon?: string, hue?: number, image?: string, alt?: string, caption?: string, href?: string}} media - Feature visual.
 * @param {string[]} ticker - Words for the running ticker (empty hides it).
 * @param {string} issue - Small index label, e.g. "N° 04".
 * @param {string} variant - Layout (split|stacked). Default 'split'.
 * @param {string} size - Vertical rhythm (sm|md|lg). Default 'md'.
 * @param {string} className - Extra class.
 *
 * Accessibility: one h1; ticker is decorative (aria-hidden) and stops with reduced motion.
 */
export interface ShopHeroEditorialProps {
  kicker?: string;
  title?: string;
  text?: string;
  primary?: Cta;
  secondary?: Cta;
  media?: { icon?: string; hue?: number; image?: string; alt?: string; caption?: string; href?: string };
  ticker?: string[];
  issue?: string;
  variant?: 'split' | 'stacked';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function ShopHeroEditorial({
  kicker = 'Autumn / Winter 26',
  title = 'Quiet *objects* for everyday rituals',
  text = 'Clothing and homeware made slowly, in small runs, by workshops we know by name. Designed to be used for years — and to look better for it.',
  primary = { label: 'Shop the collection', href: '#' },
  secondary = { label: 'Read the journal', href: '#' },
  media = { icon: 'jacket', hue: 22, caption: 'Wool overcoat, in Rust — €320', href: '#' },
  ticker = ['Made in small runs', 'Natural fibres', 'Repair for life', 'Carbon-neutral shipping', 'Free returns'],
  issue = 'N° 04',
  variant = 'split',
  size = 'md',
  className = '',
}: ShopHeroEditorialProps) {
  const parts = title.split(/\*(.+?)\*/g);
  const MediaTag = media.href ? 'a' : 'div';
  return (
    <>
      <section className={cx(['sh-ed', `sh-ed--${variant}`, `sh-ed--${size}`, className])} data-rk-cb8b08="">
        <div className="sh-ed__inner" data-rk-cb8b08="">
          <div className="sh-ed__copy" data-rk-cb8b08="">
            <p className="sh-ed__meta" data-rk-cb8b08=""><span data-rk-cb8b08="">{kicker}</span>{issue && <span data-rk-cb8b08="">{issue}</span>}</p>
            <h1 className="sh-ed__title" data-rk-cb8b08="">{parts.map((p, i) => (i % 2 ? <em data-rk-cb8b08="">{p}</em> : p))}</h1>
            <div className="sh-ed__foot" data-rk-cb8b08="">
              {text && <p className="sh-ed__text" data-rk-cb8b08="">{text}</p>}
              <div className="sh-ed__ctas" data-rk-cb8b08="">
                {primary && <a className="sh-ed__btn sh-ed__btn--solid" href={primary.href} data-rk-cb8b08="">{primary.label}<span aria-hidden="true" data-rk-cb8b08="">→</span></a>}
                {secondary && <a className="sh-ed__btn" href={secondary.href} data-rk-cb8b08="">{secondary.label}</a>}
              </div>
            </div>
          </div>
          <MediaTag className="sh-ed__media" href={media.href} data-rk-cb8b08="">
            <ProductMedia image={media.image} alt={media.alt ?? ''} icon={media.icon} hue={media.hue} ratio="4 / 5" variant="soft" size="lg" />
            {media.caption && <span className="sh-ed__caption" data-rk-cb8b08="">{media.caption}</span>}
          </MediaTag>
        </div>
        {ticker.length > 0 && (
          <div className="sh-ed__ticker" aria-hidden="true" data-rk-cb8b08="">
            <div className="sh-ed__track" data-rk-cb8b08="">
              {[0, 1].map(() => ticker.map((w, __i: number) => <span key={__i} data-rk-cb8b08="">{w}<i data-rk-cb8b08="">✦</i></span>))}
            </div>
          </div>
        )}
      </section>
    </>
  );
}

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

.sh-ed:where([data-rk-cb8b08]) { container-type: inline-size; --py: clamp(2.5rem, 6vw, 5rem); color: var(--c-text-1, #fff); overflow: hidden; }
.sh-ed--sm:where([data-rk-cb8b08]) { --py: clamp(1.5rem, 4vw, 3rem); }
.sh-ed--lg:where([data-rk-cb8b08]) { --py: clamp(3.5rem, 8vw, 7rem); }
.sh-ed__inner:where([data-rk-cb8b08]) {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
  max-width: 1440px;
  margin: 0 auto;
  padding: var(--py) clamp(1rem, 3vw, 2.5rem);
}
.sh-ed--stacked:where([data-rk-cb8b08]) .sh-ed__inner:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
.sh-ed__meta:where([data-rk-cb8b08]) { display: flex; justify-content: space-between; gap: var(--sp-4, 1rem); margin: 0 0 var(--sp-6, 1.5rem); padding-bottom: var(--sp-3, 0.75rem); border-bottom: 1px solid currentColor; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; }
.sh-ed__title:where([data-rk-cb8b08]) {
  margin: 0;
  font-family: var(--font-display, Georgia, 'Times New Roman', serif);
  font-size: clamp(3rem, 8.5vw, 8.5rem);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: -0.045em;
  text-wrap: balance;
}
.sh-ed__title:where([data-rk-cb8b08]) em:where([data-rk-cb8b08]) { font-style: italic; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
.sh-ed__foot:where([data-rk-cb8b08]) { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-6, 1.5rem); align-items: end; margin-top: var(--sp-8, 2rem); }
.sh-ed__text:where([data-rk-cb8b08]) { margin: 0; max-width: 30rem; font-size: 1rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
.sh-ed__ctas:where([data-rk-cb8b08]) { display: flex; flex-wrap: wrap; gap: var(--sp-2, 0.5rem); }
.sh-ed__btn:where([data-rk-cb8b08]) {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 52px;
  padding: 0 1.4rem;
  border: 1px solid currentColor;
  border-radius: var(--r-sm, 2px);
  color: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.sh-ed__btn:hover:where([data-rk-cb8b08]) { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); }
.sh-ed__btn--solid:where([data-rk-cb8b08]) { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); border-color: var(--c-text-1, #fff); }
.sh-ed__btn--solid:hover:where([data-rk-cb8b08]) { background: var(--shop-accent, var(--c-primary-light, #d4a017)); border-color: var(--shop-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); }
.sh-ed__btn:focus-visible:where([data-rk-cb8b08]), .sh-ed__media:focus-visible:where([data-rk-cb8b08]) { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }

.sh-ed__media:where([data-rk-cb8b08]) { position: relative; display: block; color: inherit; text-decoration: none; border-radius: var(--r-sm, 2px); overflow: hidden; }
.sh-ed__media:where([data-rk-cb8b08]) .p-media { transition: transform 1.2s cubic-bezier(0.16, 1, 0.3, 1); }
.sh-ed__media:hover:where([data-rk-cb8b08]) .p-media { transform: scale(1.03); }
.sh-ed__caption:where([data-rk-cb8b08]) { position: absolute; left: 0; right: 0; bottom: 0; padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem); background: linear-gradient(transparent, rgb(var(--paper-rgb, 5 5 5) / 0.85)); font-size: 0.8rem; letter-spacing: 0.02em; }

.sh-ed__ticker:where([data-rk-cb8b08]) { border-block: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); overflow: hidden; }
.sh-ed__track:where([data-rk-cb8b08]) { display: flex; width: max-content; animation: shop-hero-editorial-sh-ed-run 40s linear infinite; }
.sh-ed__track:where([data-rk-cb8b08]) span:where([data-rk-cb8b08]) { display: inline-flex; align-items: center; gap: 1.5rem; padding: 0.9rem 0 0.9rem 1.5rem; font-family: var(--font-display, Georgia, serif); font-size: 1.15rem; font-style: italic; white-space: nowrap; }
.sh-ed__track:where([data-rk-cb8b08]) i:where([data-rk-cb8b08]) { font-style: normal; font-size: 0.7rem; color: var(--shop-accent, var(--c-primary-light, #d4a017)); }
@keyframes shop-hero-editorial-sh-ed-run { to { transform: translateX(-50%); } }

@container (max-width: 900px) {
  .sh-ed__inner:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
  .sh-ed__foot:where([data-rk-cb8b08]) { grid-template-columns: 1fr; }
  .sh-ed__media:where([data-rk-cb8b08]) { max-width: 520px; }
}
@media (prefers-reduced-motion: reduce) {
  .sh-ed__track:where([data-rk-cb8b08]) { animation: none; }
  .sh-ed__media:where([data-rk-cb8b08]) .p-media { transition: none; }
}