Ir al contenido
Evaluar mi proyecto
Componentes · Progreso y cargaDisponible

Progreso de vidrio por etapas

Barra translúcida con destello en el relleno y etapas nombradas que se completan; glassmorfismo sobre color.

Subida22%
  1. Subida
  2. Revisión
  3. Procesado
  4. Listo

Código completo

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

progreso-vidrio.tsx60 líneas
Descargar
"use client";

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

/** Progreso de vidrio: barra translúcida con relleno que lleva un destello y paso a paso con etapas nombradas. Glassmorfismo sobre fondo de color. React + TypeScript, sin dependencias. */
export type ProgresoVidrioProps = {
  value?: number;
  steps?: string[];
  simulate?: boolean;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcprv-root { box-sizing: border-box; display: grid; gap: 16px; width: 100%; max-width: 420px; 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 26px 40px -20px rgba(10, 10, 60, .6); color: #fff; font: 600 14px/1.3 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; text-shadow: 0 1px 2px rgba(10, 10, 60, .25); }
.gcprv-root *, .gcprv-root *::before, .gcprv-root *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) { .gcprv-root * { animation: none !important; transition-duration: .01ms !important; } }
.gcprv-head { display: flex; justify-content: space-between; }
.gcprv-head span:last-child { font-variant-numeric: tabular-nums; }
.gcprv-track { position: relative; height: 14px; border-radius: 999px; background: rgba(255, 255, 255, .18); box-shadow: inset 0 1px 3px rgba(10, 10, 60, .35), 0 0 0 1px rgba(255, 255, 255, .25); overflow: hidden; }
.gcprv-fill { position: relative; height: 100%; border-radius: 999px; background: linear-gradient(180deg, rgba(255, 255, 255, .95), rgba(255, 255, 255, .7)); box-shadow: 0 0 0 1px rgba(255, 255, 255, .6), 0 2px 8px rgba(255, 255, 255, .5); transition: width .6s cubic-bezier(.16, 1, .3, 1); overflow: hidden; }
.gcprv-fill::after { content: ""; position: absolute; inset: 0; background: linear-gradient(100deg, transparent 30%, rgba(80, 100, 255, .35) 50%, transparent 70%); background-size: 220% 100%; animation: gcprv-shine 1.8s linear infinite; }
.gcprv-steps { display: grid; grid-template-columns: repeat(var(--n), 1fr); gap: 6px; margin: 0; padding: 0; list-style: none; font-size: 12px; }
.gcprv-steps li { display: grid; gap: 6px; justify-items: start; opacity: .6; transition: opacity .3s; }
.gcprv-steps li[data-s="done"], .gcprv-steps li[data-s="now"] { opacity: 1; }
.gcprv-steps i { width: 100%; height: 3px; border-radius: 2px; background: rgba(255, 255, 255, .35); }
.gcprv-steps li[data-s="done"] i, .gcprv-steps li[data-s="now"] i { background: #fff; }
@keyframes gcprv-shine { to { background-position: -220% 0; } }`;

export function ProgresoVidrio({ value, steps = ["Subida", "Revisión", "Procesado", "Listo"], simulate = true, className = "", style }: ProgresoVidrioProps) {
  const [auto, setAuto] = useState(22);
  useEffect(() => {
    if (value !== undefined || !simulate || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const id = window.setInterval(() => !document.hidden && setAuto((v) => (v >= 100 ? 6 : Math.min(100, v + 4 + Math.random() * 7))), 800);
    return () => window.clearInterval(id);
  }, [value, simulate]);
  const v = Math.round(Math.max(0, Math.min(100, value ?? auto)));
  const stage = Math.min(steps.length - 1, Math.floor((v / 100) * steps.length - 0.0001));
  return (
    <div className={`gcprv-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gcprv-head">
        <span>{v >= 100 ? "Completado" : steps[Math.max(0, stage)]}</span>
        <span>{v}%</span>
      </div>
      <div className="gcprv-track" role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-valuenow={v} aria-label="Progreso">
        <div className="gcprv-fill" style={{ width: `${v}%` }} />
      </div>
      <ol className="gcprv-steps" style={{ "--n": steps.length } as CSSProperties}>
        {steps.map((name, i) => (
          <li key={name} data-s={v >= 100 || i < stage ? "done" : i === stage ? "now" : "todo"}>
            <i />
            {name}
          </li>
        ))}
      </ol>
    </div>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

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

Ejemplo de uso de Progreso de vidrio por etapas

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

ejemplo.tsx5 líneas
import { ProgresoVidrio } from "./progreso-vidrio";

export default function Ejemplo() {
  return <ProgresoVidrio simulate />;
}

Propiedades de ProgresoVidrio

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

PropiedadTipoDescripción
value?numberOpcional.
steps?string[]Opcional.
simulate?booleanOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 60 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Progreso de vidrio por etapas por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Declara roles ARIA para lectores de pantalla: progressbar.
  • 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)