Ir al contenido
Evaluar mi proyecto
Componentes · Progreso y cargaDisponible

Anillo de progreso claymorfismo

Anillo grueso de plastilina con una bolita que recorre el arco y la cifra al centro.

Código completo

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

progreso-arcilla.tsx56 líneas
Descargar
"use client";

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

/** Progreso claymorfismo: anillo grueso de plastilina con una bolita que recorre el arco y cifra central. Colores configurables. React + TypeScript, sin dependencias. */
export type ProgresoArcillaProps = {
  value?: number;
  label?: string;
  color?: string;
  simulate?: boolean;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcpra-root { box-sizing: border-box; display: inline-grid; justify-items: center; gap: 14px; padding: 26px 30px 22px; border-radius: 36px; background: #fff0e6; color: #40284d; font: 800 15px/1.2 ui-rounded, "SF Pro Rounded", "Nunito", ui-sans-serif, system-ui, sans-serif; box-shadow: inset -8px -10px 18px rgba(214, 150, 100, .3), inset 8px 10px 18px rgba(255, 255, 255, .9), 14px 18px 28px -10px rgba(190, 110, 60, .4); }
.gcpra-root *, .gcpra-root *::before, .gcpra-root *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) { .gcpra-root * { transition-duration: .01ms !important; } }
.gcpra-ring { position: relative; width: 180px; height: 180px; }
.gcpra-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; }
.gcpra-ring circle { fill: none; stroke-width: 24; stroke-linecap: round; }
.gcpra-ring .t { stroke: #f3d7c4; filter: drop-shadow(inset 0 0 0 #000); }
.gcpra-ring .v { stroke: var(--c); transition: stroke-dashoffset .6s cubic-bezier(.34, 1.3, .64, 1); filter: drop-shadow(4px 6px 5px color-mix(in srgb, var(--c) 55%, #4a1a30)); }
.gcpra-ball { position: absolute; left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px; border-radius: 50%; background: #fff; box-shadow: inset -4px -5px 8px rgba(190, 120, 80, .45), inset 4px 5px 8px #fff, 3px 6px 10px rgba(60, 30, 80, .5), 0 0 0 3px color-mix(in srgb, var(--c) 70%, #fff); transition: transform .6s cubic-bezier(.34, 1.3, .64, 1); }
.gcpra-num { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; font: 900 40px/1 ui-rounded, "SF Pro Rounded", "Nunito", ui-sans-serif, sans-serif; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
.gcpra-num small { font-size: 13px; font-weight: 700; letter-spacing: 0; opacity: .7; }`;

export function ProgresoArcilla({ value, label = "Meta del mes", color = "#ff9a7a", simulate = true, className = "", style }: ProgresoArcillaProps) {
  const [auto, setAuto] = useState(64);
  useEffect(() => {
    if (value !== undefined || !simulate || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const id = window.setInterval(() => !document.hidden && setAuto((v) => (v >= 100 ? 12 : Math.min(100, v + 7 + Math.random() * 9))), 1100);
    return () => window.clearInterval(id);
  }, [value, simulate]);
  const v = Math.round(Math.max(0, Math.min(100, value ?? auto)));
  const R = 70;
  const C = 2 * Math.PI * R;
  const angle = (v / 100) * 360 - 90;
  return (
    <div className={`gcpra-root ${className}`} style={{ "--c": color, ...style } as CSSProperties}>
      <style>{CSS}</style>
      <div className="gcpra-ring" role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-valuenow={v} aria-label={label}>
        <svg viewBox="0 0 180 180" aria-hidden="true">
          <circle className="t" cx="90" cy="90" r={R} />
          <circle className="v" cx="90" cy="90" r={R} strokeDasharray={C} strokeDashoffset={C * (1 - v / 100)} />
        </svg>
        <span className="gcpra-ball" aria-hidden="true" style={{ transform: `translate(${Math.cos((angle * Math.PI) / 180) * R}px, ${Math.sin((angle * Math.PI) / 180) * R}px) rotate(0deg)` }} />
        <span className="gcpra-num" aria-hidden="true">
          {v}%
          <small>{label}</small>
        </span>
      </div>
    </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-arcilla

Ejemplo de uso de Anillo de progreso claymorfismo

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

ejemplo.tsx5 líneas
import { ProgresoArcilla } from "./progreso-arcilla";

export default function Ejemplo() {
  return <ProgresoArcilla label="Ejemplo" simulate />;
}

Propiedades de ProgresoArcilla

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

PropiedadTipoDescripción
value?numberOpcional.
label?stringOpcional.
color?stringOpcional.
simulate?booleanOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 56 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Anillo de progreso claymorfismo 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.
  • Calcula tonos con color-mix(), así que un solo color base alcanza.
  • 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)