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
1 estrella en GitHub = 1 componente nuevo.
Dar una estrella→Filas en zigzag que alternan texto e ilustraciones CSS (gráfico, código, tokens).
Insights
Drop-in SVG charts with tooltips, legends and table fallbacks. No chart library to configure.
Developer experience
Every component ships a TypeScript Props interface and renders beautifully with zero configuration.
interface Props {title?: string; variant?: 'glass' | 'solid'; size?: 'sm' | 'md' | 'lg';}// Sensible defaults, zero config<LineChart />Design system
Swap colors, radii and spacing in global.css and every section updates consistently.
Copia y pega este código directamente en tu componente React:
import { FeatureAlternatingRows } from 'react-component-kit-ui/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' },
]} />Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/sections/FeatureAlternatingRows.tsx + FeatureAlternatingRows.css:
'use client';
import type { CSSProperties } from 'react';
import './FeatureAlternatingRows.css';
/** "a: b; --c: d" → style object */
function st(css: string | undefined): CSSProperties {
const o: Record<string, string> = {};
for (const decl of String(css ?? '').split(';')) {
const i = decl.indexOf(':');
if (i < 0) continue;
const k = decl.slice(0, i).trim();
if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
}
return o as CSSProperties;
}
interface Row { eyebrow: string; title: string; text: string; bullets: string[]; visual: 'chart' | 'code' | 'stack' }
/**
* 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.
*/
export interface FeatureAlternatingRowsProps {
title?: string;
rows?: Row[];
startReversed?: boolean;
variant?: 'glass' | 'outline' | 'plain';
size?: 'sm' | 'md' | 'lg';
className?: string;
}
export function FeatureAlternatingRows({
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 = '',
}: FeatureAlternatingRowsProps) {
const id = `far-${Math.random().toString(36).slice(2, 8)}`;
const bars = [38, 62, 48, 80, 66, 92, 74];
return (
<>
<div className="cq-feature-alternating-rows-cq" data-rk-43b840="">
<section className={`far far--${variant} far--${size} ${className}`} aria-labelledby={id} data-rk-43b840="">
<h2 id={id} className="far__sr" data-rk-43b840="">{title}</h2>
<div className="far__inner" data-rk-43b840="">
{rows.map((r, i) => {
const reversed = startReversed ? i % 2 === 0 : i % 2 === 1;
return (
<article className={`far__row ${reversed ? 'far__row--rev' : ''}`} data-rk-43b840="">
<div className="far__copy" data-rk-43b840="">
<p className="far__eyebrow" data-rk-43b840="">{r.eyebrow}</p>
<h3 className="far__title" data-rk-43b840="">{r.title}</h3>
<p className="far__text" data-rk-43b840="">{r.text}</p>
<ul className="far__bullets" data-rk-43b840="">
{r.bullets.map((b, __i: number) => (
<li key={__i} data-rk-43b840="">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" data-rk-43b840=""><path d="M20 6 9 17l-5-5" data-rk-43b840="" /></svg>
{b}
</li>
))}
</ul>
</div>
<div className="far__visual" aria-hidden="true" data-rk-43b840="">
{r.visual === 'chart' && (
<div className="far__chart" data-rk-43b840="">
{bars.map((h, bi) => <span key={bi} style={st(`--h:${h}%; --d:${bi * 80}ms`)} data-rk-43b840=""></span>)}
</div>
)}
{r.visual === 'code' && (
<pre className="far__code" data-rk-43b840=""><code data-rk-43b840=""><span className="k" data-rk-43b840="">interface</span> <span className="t" data-rk-43b840="">Props</span> {'{'}
title?: <span className="t" data-rk-43b840="">string</span>;
variant?: <span className="s" data-rk-43b840="">'glass'</span> | <span className="s" data-rk-43b840="">'solid'</span>;
size?: <span className="s" data-rk-43b840="">'sm'</span> | <span className="s" data-rk-43b840="">'md'</span> | <span className="s" data-rk-43b840="">'lg'</span>;
{'}'}
<span className="c" data-rk-43b840="">// Sensible defaults, zero config</span>
<<span className="t" data-rk-43b840="">LineChart</span> /></code></pre>
)}
{r.visual === 'stack' && (
<div className="far__stack" data-rk-43b840="">
<span style={{ '--c': "var(--chart-1, #3987e5)" } as CSSProperties} data-rk-43b840="">--chart-1</span>
<span style={{ '--c': "var(--c-primary-light, #d4a017)" } as CSSProperties} data-rk-43b840="">--c-primary</span>
<span style={{ '--c': "var(--chart-3, #199e70)" } as CSSProperties} data-rk-43b840="">--chart-pos</span>
<span style={{ '--c': "var(--chart-7, #9085e9)" } as CSSProperties} data-rk-43b840="">--r-lg: 20px</span>
</div>
)}
</div>
</article>
);
})}
</div>
</section>
</div>
</>
);
}
export default FeatureAlternatingRows;/* FeatureAlternatingRows — ported from astro-component-kit (sections/FeatureAlternatingRows.astro) */
.cq-feature-alternating-rows-cq:where([data-rk-43b840]) { container-type: inline-size; }
.far:where([data-rk-43b840]) {
--far-py: 5.5rem;
padding: var(--far-py) var(--sp-6, 1.5rem);
background: var(--c-bg, #050505);
}
.far--sm:where([data-rk-43b840]) { --far-py: 3.5rem; }
.far--lg:where([data-rk-43b840]) { --far-py: 7.5rem; }
.far__sr:where([data-rk-43b840]) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.far__inner:where([data-rk-43b840]) { max-width: 1120px; margin: 0 auto; display: grid; gap: clamp(4rem, 8vw, 7rem); }
.far__row:where([data-rk-43b840]) { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; }
.far__row--rev:where([data-rk-43b840]) .far__copy:where([data-rk-43b840]) { order: 2; }
.far__eyebrow:where([data-rk-43b840]) { 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:where([data-rk-43b840]) { 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:where([data-rk-43b840]) { margin: 0 0 1.5rem; font-size: 1.0625rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
.far__bullets:where([data-rk-43b840]) { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
.far__bullets:where([data-rk-43b840]) li:where([data-rk-43b840]) { display: flex; align-items: center; gap: 0.6rem; color: var(--c-text-1, #fff); font-size: 0.9375rem; }
.far__bullets:where([data-rk-43b840]) svg:where([data-rk-43b840]) { color: var(--chart-pos, #34c38f); flex-shrink: 0; }
.far__visual:where([data-rk-43b840]) {
aspect-ratio: 4 / 3;
border-radius: var(--r-xl, 24px);
display: grid;
place-items: center;
padding: 2rem;
overflow: hidden;
}
.far--glass:where([data-rk-43b840]) .far__visual:where([data-rk-43b840]) {
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:where([data-rk-43b840]) .far__visual:where([data-rk-43b840]) { border: 1px dashed rgb(var(--ink-rgb, 255 255 255) / 0.14); }
.far__chart:where([data-rk-43b840]) { display: flex; align-items: flex-end; gap: 10px; width: 100%; height: 70%; }
.far__chart:where([data-rk-43b840]) span:where([data-rk-43b840]) {
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: feature-alternating-rows-far-grow 0.8s cubic-bezier(0.4, 0, 0.2, 1) both;
animation-delay: var(--d);
}
@keyframes feature-alternating-rows-far-grow { from { transform: scaleY(0); } }
.far__code:where([data-rk-43b840]) {
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:where([data-rk-43b840]) .k:where([data-rk-43b840]) { color: #c792ea; }
.far__code:where([data-rk-43b840]) .t:where([data-rk-43b840]) { color: #82aaff; }
.far__code:where([data-rk-43b840]) .s:where([data-rk-43b840]) { color: #c3e88d; }
.far__code:where([data-rk-43b840]) .c:where([data-rk-43b840]) { color: #6b7280; }
.far__stack:where([data-rk-43b840]) { display: grid; gap: 0.75rem; width: 100%; max-width: 320px; }
.far__stack:where([data-rk-43b840]) span:where([data-rk-43b840]) {
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:where([data-rk-43b840]) span:where([data-rk-43b840])::before { content: ''; width: 20px; height: 20px; border-radius: 6px; background: var(--c); }
.far__stack:where([data-rk-43b840]) span:nth-child(2):where([data-rk-43b840]) { margin-left: 1.5rem; }
.far__stack:where([data-rk-43b840]) span:nth-child(3):where([data-rk-43b840]) { margin-left: 3rem; }
@container (max-width: 820px) {
.far__row:where([data-rk-43b840]) { grid-template-columns: 1fr; }
.far__row--rev:where([data-rk-43b840]) .far__copy:where([data-rk-43b840]) { order: 0; }
}
@media (prefers-reduced-motion: reduce) { .far__chart:where([data-rk-43b840]) span:where([data-rk-43b840]) { animation: none; } }