Ir al contenido
Evaluar mi proyecto
Componentes · Datos y visualizaciónDisponible

Embudo de conversión de vidrio

Etapas centradas con ancho proporcional, porcentaje de paso a la siguiente y detalle al enfocar. Glassmorfismo.

Del primer clic al proyecto

  1. ↓ 25% pasa a la siguiente etapa
  2. ↓ 40% pasa a la siguiente etapa
  3. ↓ 33% pasa a la siguiente etapa
  4. ↓ 23% pasa a la siguiente etapa

Apunta a una etapa para ver su peso sobre el total.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

datos-embudo.tsx65 líneas
Descargar
"use client";

import { useState, type CSSProperties } from "react";

/** Embudo de conversión en vidrio esmerilado: cada etapa es una barra centrada cuyo ancho es proporcional al valor, con porcentaje respecto a la etapa anterior y detalle al enfocar. Datos de muestra. React + TypeScript, sin dependencias. */
export type DatosEmbudoEtapa = { label: string; value: number };

export type DatosEmbudoProps = {
  stages?: DatosEmbudoEtapa[];
  title?: string;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcdem-root { box-sizing: border-box; width: 100%; max-width: 520px; padding: 22px; border-radius: 24px; background: rgba(255, 255, 255, .14); -webkit-backdrop-filter: blur(20px) saturate(1.7); backdrop-filter: blur(20px) saturate(1.7); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3), inset 0 1px 0 rgba(255, 255, 255, .55), 0 28px 44px -22px rgba(10, 10, 60, .6); color: #fff; font: 500 14px/1.3 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; text-shadow: 0 1px 2px rgba(10, 10, 60, .25); }
.gcdem-root *, .gcdem-root *::before, .gcdem-root *::after { box-sizing: border-box; }
.gcdem-root button { font: inherit; }
.gcdem-root :focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .gcdem-root * { transition-duration: .01ms !important; animation: none !important; } }
.gcdem-title { margin: 0 0 16px; font-size: 16px; font-weight: 700; }
.gcdem-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.gcdem-row { display: grid; gap: 4px; justify-items: center; }
.gcdem-bar { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: var(--w); min-width: min(100%, 230px); white-space: nowrap; height: 46px; padding: 0 16px; border: 0; border-radius: 12px; background: linear-gradient(180deg, rgba(255, 255, 255, .38), rgba(255, 255, 255, .18)); color: #fff; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .4), inset 0 1px 0 rgba(255, 255, 255, .7); cursor: pointer; text-shadow: 0 1px 2px rgba(10, 10, 60, .35); transition: transform .3s cubic-bezier(.16, 1, .3, 1), background .2s; animation: gcdem-in .7s cubic-bezier(.16, 1, .3, 1) backwards; }
.gcdem-bar:hover, .gcdem-bar[data-on="true"] { transform: scaleX(1.03); background: linear-gradient(180deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .26)); }
.gcdem-bar b { font-variant-numeric: tabular-nums; }
.gcdem-conv { font-size: 12px; opacity: .85; font-variant-numeric: tabular-nums; }
.gcdem-detail { min-height: 20px; margin: 14px 0 0; font-size: 13px; opacity: .9; }
@keyframes gcdem-in { from { opacity: 0; transform: scaleX(.5); } }`;

const DEFAULT: DatosEmbudoEtapa[] = [
  { label: "Visitas", value: 12400 },
  { label: "Diagnósticos iniciados", value: 3100 },
  { label: "Diagnósticos enviados", value: 1240 },
  { label: "Llamadas", value: 410 },
  { label: "Proyectos", value: 96 },
];

export function DatosEmbudo({ stages = DEFAULT, title = "Del primer clic al proyecto", className = "", style }: DatosEmbudoProps) {
  const [on, setOn] = useState<number | null>(null);
  const top = stages[0].value;
  const n = new Intl.NumberFormat("es-MX");
  return (
    <section className={`gcdem-root ${className}`} style={style} aria-label={title}>
      <style>{CSS}</style>
      <h3 className="gcdem-title">{title}</h3>
      <ol className="gcdem-list">
        {stages.map((stage, i) => {
          const prev = i ? stages[i - 1].value : null;
          return (
            <li key={stage.label} className="gcdem-row">
              {prev ? <span className="gcdem-conv">↓ {Math.round((stage.value / prev) * 100)}% pasa a la siguiente etapa</span> : null}
              <button className="gcdem-bar" type="button" data-on={on === i} style={{ "--w": `${Math.max(24, (stage.value / top) * 100)}%`, animationDelay: `${i * 0.08}s` } as CSSProperties} onPointerEnter={() => setOn(i)} onPointerLeave={() => setOn(null)} onFocus={() => setOn(i)} onBlur={() => setOn(null)}>
                <span>{stage.label}</span>
                <b>{n.format(stage.value)}</b>
              </button>
            </li>
          );
        })}
      </ol>
      <p className="gcdem-detail" aria-live="polite">{on !== null ? `${stages[on].label}: ${Math.round((stages[on].value / top) * 1000) / 10}% del total de visitas.` : "Apunta a una etapa para ver su peso sobre el total."}</p>
    </section>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/datos-embudo

Ejemplo de uso de Embudo de conversión de vidrio

Después de copiar datos-embudo.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { DatosEmbudo } from "./datos-embudo";

export default function Ejemplo() {
  return <DatosEmbudo title="Ejemplo" />;
}

Propiedades de DatosEmbudo

DatosEmbudo acepta 4 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
stages?DatosEmbudoEtapa[]Opcional.
title?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 65 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Embudo de conversión de vidrio 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).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • Usa backdrop-filter para el efecto de vidrio; en navegadores sin soporte el panel se ve sin desenfoque.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)