Medidor de LED skeuomórfico
Segmentos que se encienden de verde a ámbar y rojo, con marca de pico y pantalla digital; para volumen, batería o carga.
Nivel
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useEffect, useRef, useState, type CSSProperties } from "react";
/** Medidor de LED skeuomórfico: segmentos que se encienden de verde a ámbar y rojo, con vidrio, tornillos y marca de pico. Sirve para volumen, batería o carga. React + TypeScript, sin dependencias. */
export type ProgresoLedProps = {
value?: number;
segments?: number;
label?: string;
simulate?: boolean;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcprl-root { box-sizing: border-box; display: inline-grid; gap: 14px; max-width: 100%; padding: 18px 22px 16px; border-radius: 14px; background: linear-gradient(180deg, #34363c, #202226); color: #c9ccd4; font: 700 11px/1 "Helvetica Neue", Arial, ui-sans-serif, sans-serif; letter-spacing: .16em; text-transform: uppercase; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), inset 0 -2px 6px rgba(0, 0, 0, .5), 0 14px 22px -10px rgba(0, 0, 0, .7); }
.gcprl-root *, .gcprl-root *::before, .gcprl-root *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) { .gcprl-root * { transition-duration: .01ms !important; } }
.gcprl-head { display: flex; justify-content: space-between; align-items: center; gap: 28px; }
.gcprl-val { padding: 4px 8px; border-radius: 4px; background: #0b140d; color: #7dffa1; font: 600 14px/1 ui-monospace, "SF Mono", Menlo, monospace; text-shadow: 0 0 6px rgba(125, 255, 161, .6); box-shadow: inset 0 1px 3px rgba(0, 0, 0, .9); font-variant-numeric: tabular-nums; }
.gcprl-bar { display: flex; gap: 4px; padding: 8px; border-radius: 8px; background: #0d0e11; box-shadow: inset 0 2px 6px rgba(0, 0, 0, .9), 0 1px 0 rgba(255, 255, 255, .12); }
.gcprl-seg { flex: 1; min-width: 10px; height: 34px; border-radius: 3px; background: color-mix(in srgb, var(--c) 18%, #14151a); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05); transition: background .12s, box-shadow .12s; }
.gcprl-seg[data-on="true"] { background: radial-gradient(circle at 50% 30%, color-mix(in srgb, var(--c) 60%, #fff), var(--c) 70%); box-shadow: 0 0 10px 1px color-mix(in srgb, var(--c) 70%, transparent), inset 0 1px 0 rgba(255, 255, 255, .5); }
.gcprl-seg[data-peak="true"] { background: color-mix(in srgb, var(--c) 55%, #14151a); }`;
export function ProgresoLed({ value, segments = 16, label = "Nivel", simulate = true, className = "", style }: ProgresoLedProps) {
const [auto, setAuto] = useState(55);
const [peak, setPeak] = useState(0);
useEffect(() => {
if (value !== undefined || !simulate || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const id = window.setInterval(() => !document.hidden && setAuto((v) => Math.max(8, Math.min(100, v + (Math.random() - 0.5) * 46))), 260);
return () => window.clearInterval(id);
}, [value, simulate]);
const v = Math.round(Math.max(0, Math.min(100, value ?? auto)));
const lit = Math.round((v / 100) * segments);
const litRef = useRef(lit);
useEffect(() => {
litRef.current = lit;
}, [lit]);
useEffect(() => {
const id = window.setInterval(() => setPeak((p) => (litRef.current >= p ? litRef.current : Math.max(litRef.current, p - 1))), 260);
return () => window.clearInterval(id);
}, []);
const color = (i: number) => (i / segments > 0.85 ? "#ff4d3a" : i / segments > 0.65 ? "#ffb82e" : "#38e07b");
return (
<div className={`gcprl-root ${className}`} style={style}>
<style>{CSS}</style>
<div className="gcprl-head">
<span>{label}</span>
<span className="gcprl-val" aria-hidden="true">{String(v).padStart(3, "0")}</span>
</div>
<div className="gcprl-bar" role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-valuenow={v} aria-label={label}>
{Array.from({ length: segments }, (_, i) => (
<span key={i} className="gcprl-seg" style={{ "--c": color(i) } as CSSProperties} data-on={i < lit} data-peak={i === peak - 1 && peak > lit} />
))}
</div>
</div>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/progreso-ledEjemplo de uso de Medidor de LED skeuomórfico
Después de copiar progreso-led.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { ProgresoLed } from "./progreso-led";
export default function Ejemplo() {
return <ProgresoLed label="Ejemplo" simulate />;
}Propiedades de ProgresoLed
ProgresoLed acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
value? | number | Opcional. |
segments? | number | Opcional. |
label? | string | Opcional. |
simulate? | boolean | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Accesibilidad y rendimiento
El archivo tiene 60 líneas (4 KB) y no necesita @keyframes. Esto es lo que hace Medidor de LED skeuomórfico 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".