Gráfico de barras seleccionable
Barras con escala numérica, meses y selección por teclado o toque; valores anunciados y callback del dato elegido.
Actividad por mes
Datos de muestra · proyectos
Abr56 proyectos
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useState, type CSSProperties } from "react";
/** Gráfico de barras seleccionable. React + TypeScript, sin dependencias. Personaliza con variables --gczb-*. */
export type BarDatum = { label: string; value: number };
export type DatosBarrasProps = {
title?: string;
data?: BarDatum[];
unit?: string;
onSelect?: (datum: BarDatum) => void;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gczb-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gczb-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gczb-root *, .gczb-root *::before, .gczb-root *::after { box-sizing: border-box; }
.gczb-root button, .gczb-root input, .gczb-root textarea, .gczb-root select { font: inherit; }
.gczb-root button, .gczb-root a { -webkit-tap-highlight-color: transparent; }
.gczb-root :focus-visible { outline: 2px solid var(--gczb-accent, #25724b); outline-offset: 4px; }
.gczb-root ::selection { background: var(--gczb-accent, #25724b); color: #fff; }
.gczb-root button:disabled { opacity: .5; cursor: not-allowed; }
.gczb-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gczb-root *, .gczb-root *::before, .gczb-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gczb-root { width:520px; background:var(--gczb-bg,#fff); padding:26px; border-radius:12px; }
.gczb-root h3 { font-size:18px; font-weight:600; margin:0 0 6px; }
.gczb-root>p { color:#61705f; font-size:11px; margin:0 0 26px; }
.gczb-chart { display:flex; gap:14px; height:200px; }
.gczb-axis { width:30px; height:170px; display:flex; flex-direction:column; justify-content:space-between; font-size:10px; color:#61705f; font-variant-numeric:tabular-nums; }
.gczb-bars { display:flex; flex:1; gap:10px; min-width:0; border-bottom:1px solid #ced8c9; }
.gczb-bars button { flex:1; min-width:0; display:flex; flex-direction:column; gap:10px; align-items:center; padding:0 0 8px; border:0; background:transparent; color:#52664e; cursor:pointer; font-size:11px; }
.gczb-track { display:flex; align-items:flex-end; height:170px; width:100%; position:relative; }
.gczb-track i { display:block; height:100%; width:100%; background:#b0c4a7; border-radius:4px 4px 0 0; transform-origin:bottom; transition:background .2s,transform .4s cubic-bezier(.16,1,.3,1); }
.gczb-bars button[aria-pressed="true"] i { background:var(--gczb-accent,#345d3c); }
.gczb-bars button:hover i { background:#678b5b; }
.gczb-root .gczb-detail { margin:20px 0 0; display:flex; justify-content:space-between; gap:15px; padding-top:16px; font-size:12px; color:#34513b; }
.gczb-detail span { font-variant-numeric:tabular-nums; }`;
export function DatosBarras({
title = "Actividad por mes",
data = [
{ label: "Ene", value: 24 },
{ label: "Feb", value: 38 },
{ label: "Mar", value: 31 },
{ label: "Abr", value: 56 },
{ label: "May", value: 43 },
{ label: "Jun", value: 68 },
],
unit = "proyectos",
onSelect,
className = "",
style,
}: DatosBarrasProps) {
const [selected, setSelected] = useState(3);
const max = Math.max(1, ...data.map((d) => d.value));
const index = Math.min(selected, Math.max(0, data.length - 1));
const current = data[index];
return (
<section className={`gczb-root ${className}`} style={style}>
<style>{CSS}</style>
<h3>{title}</h3>
<p>Datos de muestra · {unit}</p>
{data.length ? (
<>
<div className="gczb-chart">
<div className="gczb-axis" aria-hidden="true">
<span>{max}</span>
<span>{Math.round(max / 2)}</span>
<span>0</span>
</div>
<div className="gczb-bars">
{data.map((datum, i) => (
<button
type="button"
key={i}
aria-label={`${datum.label}: ${datum.value} ${unit}`}
aria-pressed={index === i}
onClick={() => {
setSelected(i);
onSelect?.(datum);
}}
>
<span className="gczb-track" aria-hidden="true">
<i
style={{
transform: `scaleY(${Math.max(0, datum.value) / max})`,
}}
/>
</span>
<span>{datum.label}</span>
</button>
))}
</div>
</div>
<p className="gczb-detail" role="status">
<strong>{current.label}</strong>
<span>
{current.value.toLocaleString("es-MX")} {unit}
</span>
</p>
</>
) : (
<p>No hay datos para mostrar.</p>
)}
</section>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/datos-barrasEjemplo de uso de Gráfico de barras seleccionable
Después de copiar datos-barras.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { DatosBarras } from "./datos-barras";
export default function Ejemplo() {
return <DatosBarras title="Ejemplo" />;
}Propiedades de DatosBarras
DatosBarras acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
title? | string | Opcional. |
data? | BarDatum[] | Opcional. |
unit? | string | Opcional. |
onSelect? | (datum: BarDatum) => void | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Gráfico de barras seleccionable con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gczb-fg | #202421 |
--gczb-accent | #25724b |
--gczb-bg | #fff |
Accesibilidad y rendimiento
El archivo tiene 109 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Gráfico de barras seleccionable por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Declara roles ARIA para lectores de pantalla: status.
- Es un componente de cliente de Next.js: incluye la directiva "use client".