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

Поставить звезду
Секции страниц featureszig-zagalternatingsplit

Feature Alternating Rows

Зигзагообразные блоки, чередующие текст и CSS-иллюстрации (график, код, токены).

Живое превью

How the kit helps your team

Insights

Dashboards your stakeholders actually read

Drop-in SVG charts with tooltips, legends and table fallbacks. No chart library to configure.

  • 45 chart types
  • Colorblind-safe palette
  • Keyboard-accessible marks

Developer experience

Typed props, sensible defaults

Every component ships a TypeScript Props interface and renders beautifully with zero configuration.

  • Strict TypeScript
  • JSDoc on every prop
  • Copy-paste friendly

Design system

One token file to re-theme everything

Swap colors, radii and spacing in global.css and every section updates consistently.

  • CSS custom properties
  • Dark-first
  • 8pt spacing grid

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

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

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

<FeatureAlternatingRows
  rows={[
    { eyebrow: 'Insights', title: 'Dashboards people read', text: '…', bullets: ['45 charts'], visual: 'chart' },
    { eyebrow: 'DX', title: 'Typed props', text: '…', bullets: ['Strict TS'], visual: 'code' },
  ]}
/>
--- import { FeatureAlternatingRows } from 'astro-component-kit/sections'; --- <FeatureAlternatingRows rows={[ { eyebrow: 'Insights', title: 'Dashboards people read', text: '…', bullets: ['45 charts'], visual: 'chart' }, { eyebrow: 'DX', title: 'Typed props', text: '…', bullets: ['Strict TS'], visual: 'code' }, ]} />

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

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

astro
---
/**
 * FeatureAlternatingRows — Zig-zag feature section: text and a visual alternate
 * sides on each row. Visuals are pure-CSS illustrations (no images to load).
 *
 * @param {string} title - Section heading (visually hidden if empty-styled).
 * @param {Array<{eyebrow: string, title: string, text: string, bullets: string[], visual: 'chart'|'code'|'stack'}>} rows - Feature rows.
 * @param {boolean} startReversed - Put the visual on the left in the first row.
 * @param {string} variant - Visual frame (glass|outline|plain).
 * @param {string} size - Vertical rhythm (sm|md|lg).
 * @param {string} className - Extra CSS class.
 */
interface Row { eyebrow: string; title: string; text: string; bullets: string[]; visual: 'chart' | 'code' | 'stack' }
interface Props {
  title?: string;
  rows?: Row[];
  startReversed?: boolean;
  variant?: 'glass' | 'outline' | 'plain';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  title = 'How the kit helps your team',
  rows = [
    {
      eyebrow: 'Insights',
      title: 'Dashboards your stakeholders actually read',
      text: 'Drop-in SVG charts with tooltips, legends and table fallbacks. No chart library to configure.',
      bullets: ['45 chart types', 'Colorblind-safe palette', 'Keyboard-accessible marks'],
      visual: 'chart',
    },
    {
      eyebrow: 'Developer experience',
      title: 'Typed props, sensible defaults',
      text: 'Every component ships a TypeScript Props interface and renders beautifully with zero configuration.',
      bullets: ['Strict TypeScript', 'JSDoc on every prop', 'Copy-paste friendly'],
      visual: 'code',
    },
    {
      eyebrow: 'Design system',
      title: 'One token file to re-theme everything',
      text: 'Swap colors, radii and spacing in global.css and every section updates consistently.',
      bullets: ['CSS custom properties', 'Dark-first', '8pt spacing grid'],
      visual: 'stack',
    },
  ],
  startReversed = false,
  variant = 'glass',
  size = 'md',
  className = '',
} = Astro.props;

const id = `far-${Math.random().toString(36).slice(2, 8)}`;
const bars = [38, 62, 48, 80, 66, 92, 74];
---

<div class="cq-feature-alternating-rows-cq">
<section class={`far far--${variant} far--${size} ${className}`} aria-labelledby={id}>
  <h2 id={id} class="far__sr">{title}</h2>
  <div class="far__inner">
    {rows.map((r, i) => {
      const reversed = startReversed ? i % 2 === 0 : i % 2 === 1;
      return (
        <article class={`far__row ${reversed ? 'far__row--rev' : ''}`}>
          <div class="far__copy">
            <p class="far__eyebrow">{r.eyebrow}</p>
            <h3 class="far__title">{r.title}</h3>
            <p class="far__text">{r.text}</p>
            <ul class="far__bullets">
              {r.bullets.map((b) => (
                <li>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
                  {b}
                </li>
              ))}
            </ul>
          </div>
          <div class="far__visual" aria-hidden="true">
            {r.visual === 'chart' && (
              <div class="far__chart">
                {bars.map((h, bi) => <span style={`--h:${h}%; --d:${bi * 80}ms`}></span>)}
              </div>
            )}
            {r.visual === 'code' && (
              <pre class="far__code"><code><span class="k">interface</span> <span class="t">Props</span> {'{'}
  title?: <span class="t">string</span>;
  variant?: <span class="s">'glass'</span> | <span class="s">'solid'</span>;
  size?: <span class="s">'sm'</span> | <span class="s">'md'</span> | <span class="s">'lg'</span>;
{'}'}

<span class="c">// Sensible defaults, zero config</span>
&lt;<span class="t">LineChart</span> /&gt;</code></pre>
            )}
            {r.visual === 'stack' && (
              <div class="far__stack">
                <span style="--c:var(--chart-1, #3987e5)">--chart-1</span>
                <span style="--c:var(--c-primary-light, #d4a017)">--c-primary</span>
                <span style="--c:var(--chart-3, #199e70)">--chart-pos</span>
                <span style="--c:var(--chart-7, #9085e9)">--r-lg: 20px</span>
              </div>
            )}
          </div>
        </article>
      );
    })}
  </div>
</section>
</div>
<style>
  .cq-feature-alternating-rows-cq { container-type: inline-size; }
  .far {
    --far-py: 5.5rem;
    padding: var(--far-py) var(--sp-6, 1.5rem);
    background: var(--c-bg, #050505);
  }
  .far--sm { --far-py: 3.5rem; }
  .far--lg { --far-py: 7.5rem; }
  .far__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .far__inner { max-width: 1120px; margin: 0 auto; display: grid; gap: clamp(4rem, 8vw, 7rem); }
  .far__row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; }
  .far__row--rev .far__copy { order: 2; }
  .far__eyebrow { margin: 0 0 0.75rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-primary-light, #d4a017); }
  .far__title { margin: 0 0 1rem; font-size: clamp(1.6rem, 3vw, 2.25rem); font-weight: 750; letter-spacing: -0.025em; line-height: 1.15; color: var(--c-text-1, #fff); }
  .far__text { margin: 0 0 1.5rem; font-size: 1.0625rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
  .far__bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
  .far__bullets li { display: flex; align-items: center; gap: 0.6rem; color: var(--c-text-1, #fff); font-size: 0.9375rem; }
  .far__bullets svg { color: var(--chart-pos, #34c38f); flex-shrink: 0; }

  .far__visual {
    aspect-ratio: 4 / 3;
    border-radius: var(--r-xl, 24px);
    display: grid;
    place-items: center;
    padding: 2rem;
    overflow: hidden;
  }
  .far--glass .far__visual {
    background: radial-gradient(circle at 30% 20%, rgb(var(--ink-rgb, 255 255 255) / 0.06), transparent 60%), rgb(var(--ink-rgb, 255 255 255) / 0.02);
    border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07));
  }
  .far--outline .far__visual { border: 1px dashed rgb(var(--ink-rgb, 255 255 255) / 0.14); }
  .far__chart { display: flex; align-items: flex-end; gap: 10px; width: 100%; height: 70%; }
  .far__chart span {
    flex: 1;
    height: var(--h);
    border-radius: 6px 6px 0 0;
    background: linear-gradient(to top, rgba(57, 135, 229, 0.35), var(--chart-1, #3987e5));
    transform-origin: bottom;
    animation: far-grow 0.8s cubic-bezier(0.4, 0, 0.2, 1) both;
    animation-delay: var(--d);
  }
  @keyframes far-grow { from { transform: scaleY(0); } }
  .far__code {
    margin: 0;
    width: 100%;
    padding: 1.5rem;
    border-radius: var(--r-md, 12px);
    background: #0b0b0d;
    border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
    font-family: var(--font-mono, monospace);
    font-size: 0.8125rem;
    line-height: 1.75;
    color: var(--c-text-2, #c4c4c4);
    overflow-x: auto;
  }
  .far__code .k { color: #c792ea; }
  .far__code .t { color: #82aaff; }
  .far__code .s { color: #c3e88d; }
  .far__code .c { color: #6b7280; }
  .far__stack { display: grid; gap: 0.75rem; width: 100%; max-width: 320px; }
  .far__stack span {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.85rem 1rem;
    border-radius: var(--r-md, 12px);
    background: rgb(var(--ink-rgb, 255 255 255) / 0.03);
    border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.07);
    font-family: var(--font-mono, monospace);
    font-size: 0.8125rem;
    color: var(--c-text-1, #fff);
  }
  .far__stack span::before { content: ''; width: 20px; height: 20px; border-radius: 6px; background: var(--c); }
  .far__stack span:nth-child(2) { margin-left: 1.5rem; }
  .far__stack span:nth-child(3) { margin-left: 3rem; }
  @container (max-width: 820px) {
    .far__row { grid-template-columns: 1fr; }
    .far__row--rev .far__copy { order: 0; }
  }
  @media (prefers-reduced-motion: reduce) { .far__chart span { animation: none; } }
</style>
--- /** * FeatureAlternatingRows — Zig-zag feature section: text and a visual alternate * sides on each row. Visuals are pure-CSS illustrations (no images to load). * * @param {string} title - Section heading (visually hidden if empty-styled). * @param {Array<{eyebrow: string, title: string, text: string, bullets: string[], visual: 'chart'|'code'|'stack'}>} rows - Feature rows. * @param {boolean} startReversed - Put the visual on the left in the first row. * @param {string} variant - Visual frame (glass|outline|plain). * @param {string} size - Vertical rhythm (sm|md|lg). * @param {string} className - Extra CSS class. */ interface Row { eyebrow: string; title: string; text: string; bullets: string[]; visual: 'chart' | 'code' | 'stack' } interface Props { title?: string; rows?: Row[]; startReversed?: boolean; variant?: 'glass' | 'outline' | 'plain'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'How the kit helps your team', rows = [ { eyebrow: 'Insights', title: 'Dashboards your stakeholders actually read', text: 'Drop-in SVG charts with tooltips, legends and table fallbacks. No chart library to configure.', bullets: ['45 chart types', 'Colorblind-safe palette', 'Keyboard-accessible marks'], visual: 'chart', }, { eyebrow: 'Developer experience', title: 'Typed props, sensible defaults', text: 'Every component ships a TypeScript Props interface and renders beautifully with zero configuration.', bullets: ['Strict TypeScript', 'JSDoc on every prop', 'Copy-paste friendly'], visual: 'code', }, { eyebrow: 'Design system', title: 'One token file to re-theme everything', text: 'Swap colors, radii and spacing in global.css and every section updates consistently.', bullets: ['CSS custom properties', 'Dark-first', '8pt spacing grid'], visual: 'stack', }, ], startReversed = false, variant = 'glass', size = 'md', className = '', } = Astro.props; const id = `far-${Math.random().toString(36).slice(2, 8)}`; const bars = [38, 62, 48, 80, 66, 92, 74]; --- <div class="cq-feature-alternating-rows-cq"> <section class={`far far--${variant} far--${size} ${className}`} aria-labelledby={id}> <h2 id={id} class="far__sr">{title}</h2> <div class="far__inner"> {rows.map((r, i) => { const reversed = startReversed ? i % 2 === 0 : i % 2 === 1; return ( <article class={`far__row ${reversed ? 'far__row--rev' : ''}`}> <div class="far__copy"> <p class="far__eyebrow">{r.eyebrow}</p> <h3 class="far__title">{r.title}</h3> <p class="far__text">{r.text}</p> <ul class="far__bullets"> {r.bullets.map((b) => ( <li> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg> {b} </li> ))} </ul> </div> <div class="far__visual" aria-hidden="true"> {r.visual === 'chart' && ( <div class="far__chart"> {bars.map((h, bi) => <span style={`--h:${h}%; --d:${bi * 80}ms`}></span>)} </div> )} {r.visual === 'code' && ( <pre class="far__code"><code><span class="k">interface</span> <span class="t">Props</span> {'{'} title?: <span class="t">string</span>; variant?: <span class="s">'glass'</span> | <span class="s">'solid'</span>; size?: <span class="s">'sm'</span> | <span class="s">'md'</span> | <span class="s">'lg'</span>; {'}'} <span class="c">// Sensible defaults, zero config</span> &lt;<span class="t">LineChart</span> /&gt;</code></pre> )} {r.visual === 'stack' && ( <div class="far__stack"> <span style="--c:var(--chart-1, #3987e5)">--chart-1</span> <span style="--c:var(--c-primary-light, #d4a017)">--c-primary</span> <span style="--c:var(--chart-3, #199e70)">--chart-pos</span> <span style="--c:var(--chart-7, #9085e9)">--r-lg: 20px</span> </div> )} </div> </article> ); })} </div> </section> </div> <style> .cq-feature-alternating-rows-cq { container-type: inline-size; } .far { --far-py: 5.5rem; padding: var(--far-py) var(--sp-6, 1.5rem); background: var(--c-bg, #050505); } .far--sm { --far-py: 3.5rem; } .far--lg { --far-py: 7.5rem; } .far__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } .far__inner { max-width: 1120px; margin: 0 auto; display: grid; gap: clamp(4rem, 8vw, 7rem); } .far__row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; } .far__row--rev .far__copy { order: 2; } .far__eyebrow { margin: 0 0 0.75rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-primary-light, #d4a017); } .far__title { margin: 0 0 1rem; font-size: clamp(1.6rem, 3vw, 2.25rem); font-weight: 750; letter-spacing: -0.025em; line-height: 1.15; color: var(--c-text-1, #fff); } .far__text { margin: 0 0 1.5rem; font-size: 1.0625rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); } .far__bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; } .far__bullets li { display: flex; align-items: center; gap: 0.6rem; color: var(--c-text-1, #fff); font-size: 0.9375rem; } .far__bullets svg { color: var(--chart-pos, #34c38f); flex-shrink: 0; } .far__visual { aspect-ratio: 4 / 3; border-radius: var(--r-xl, 24px); display: grid; place-items: center; padding: 2rem; overflow: hidden; } .far--glass .far__visual { background: radial-gradient(circle at 30% 20%, rgb(var(--ink-rgb, 255 255 255) / 0.06), transparent 60%), rgb(var(--ink-rgb, 255 255 255) / 0.02); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); } .far--outline .far__visual { border: 1px dashed rgb(var(--ink-rgb, 255 255 255) / 0.14); } .far__chart { display: flex; align-items: flex-end; gap: 10px; width: 100%; height: 70%; } .far__chart span { flex: 1; height: var(--h); border-radius: 6px 6px 0 0; background: linear-gradient(to top, rgba(57, 135, 229, 0.35), var(--chart-1, #3987e5)); transform-origin: bottom; animation: far-grow 0.8s cubic-bezier(0.4, 0, 0.2, 1) both; animation-delay: var(--d); } @keyframes far-grow { from { transform: scaleY(0); } } .far__code { margin: 0; width: 100%; padding: 1.5rem; border-radius: var(--r-md, 12px); background: #0b0b0d; border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06); font-family: var(--font-mono, monospace); font-size: 0.8125rem; line-height: 1.75; color: var(--c-text-2, #c4c4c4); overflow-x: auto; } .far__code .k { color: #c792ea; } .far__code .t { color: #82aaff; } .far__code .s { color: #c3e88d; } .far__code .c { color: #6b7280; } .far__stack { display: grid; gap: 0.75rem; width: 100%; max-width: 320px; } .far__stack span { display: flex; align-items: center; gap: 0.75rem; padding: 0.85rem 1rem; border-radius: var(--r-md, 12px); background: rgb(var(--ink-rgb, 255 255 255) / 0.03); border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.07); font-family: var(--font-mono, monospace); font-size: 0.8125rem; color: var(--c-text-1, #fff); } .far__stack span::before { content: ''; width: 20px; height: 20px; border-radius: 6px; background: var(--c); } .far__stack span:nth-child(2) { margin-left: 1.5rem; } .far__stack span:nth-child(3) { margin-left: 3rem; } @container (max-width: 820px) { .far__row { grid-template-columns: 1fr; } .far__row--rev .far__copy { order: 0; } } @media (prefers-reduced-motion: reduce) { .far__chart span { animation: none; } } </style>

Краткая информация

Категория
Секции страниц
Имя файла
FeatureAlternatingRows.astro
Зависимости
Нет — чистый Astro + CSS
Теги
featureszig-zagalternatingsplit