Moodinary/UI

WeatherSprite

Escena chica para cada estado del clima del producto: soleado, parcial, nublado, tormenta y niebla.

Los umbrales son los del producto: promedio desde 4,2 soleado, desde 3,5 parcial, desde 2,8 nublado y por debajo tormenta. Con menos de 8 pulsos en los últimos 7 días el estado es niebla. weatherFor(promedio, pulsos) hace esa cuenta.

Instalación

npm
npm i @moodinary/sprites
shadcn
npx shadcn@latest add https://design.moodinary.com/r/sprites-weather.json
import
import { WeatherSprite } from "@moodinary/sprites/react";
import { weatherFor } from "@moodinary/sprites";

Ejemplos

Los cinco estados

Soleado
Parcial
Nublado
Tormenta
Niebla
Código
weather-states.tsx
import { WEATHER_LABEL, type Weather } from "@moodinary/sprites";
import { WeatherSprite } from "@moodinary/sprites/react";

const states: Weather[] = [
	"soleado",
	"parcial",
	"nublado",
	"tormenta",
	"niebla",
];

export default function WeatherStates() {
	return (
		<div className="grid w-full grid-cols-2 gap-3 sm:grid-cols-5">
			{states.map((w) => (
				<figure className="m-0" key={w}>
					<div className="h-20 overflow-hidden rounded-[8px] border border-line">
						<WeatherSprite weather={w} />
					</div>
					<figcaption className="mt-1.5 text-[12px] text-ink-3">
						{WEATHER_LABEL[w]}
					</figcaption>
				</figure>
			))}
		</div>
	);
}

De promedio a clima

Mové los controles. Con pocos pulsos manda la niebla, sin importar el promedio.

Parcial
Código
weather-for.tsx
import { WEATHER_LABEL, weatherFor } from "@moodinary/sprites";
import { WeatherSprite } from "@moodinary/sprites/react";
import { useState } from "react";

export default function WeatherFor() {
	const [avg, setAvg] = useState(3.7);
	const [n, setN] = useState(14);
	const w = weatherFor(avg, n);
	return (
		<div className="flex w-full flex-wrap items-center gap-6">
			<div className="h-24 w-40 overflow-hidden rounded-[8px] border border-line">
				<WeatherSprite weather={w} />
			</div>
			<div className="grid gap-3 text-[13px] text-ink-2">
				<label className="flex items-center gap-3">
					Promedio{" "}
					<input
						aria-label="Promedio"
						max={5}
						min={1}
						onChange={(e) => setAvg(Number(e.target.value))}
						step={0.1}
						type="range"
						value={avg}
					/>{" "}
					<code className="font-mono text-ink">
						{avg.toFixed(1).replace(".", ",")}
					</code>
				</label>
				<label className="flex items-center gap-3">
					Pulsos en 7 días{" "}
					<input
						aria-label="Pulsos en 7 días"
						max={30}
						min={0}
						onChange={(e) => setN(Number(e.target.value))}
						type="range"
						value={n}
					/>{" "}
					<code className="font-mono text-ink">{n}</code>
				</label>
				<div className="font-medium text-ink">{WEATHER_LABEL[w]}</div>
			</div>
		</div>
	);
}

Props

WeatherSprite

PropTipoDefaultDescripción
weather"soleado" | "parcial" | "nublado" | "tormenta" | "niebla"-Estado a dibujar.
pixelnumber3Píxeles CSS por píxel del dibujo. Más alto, más grueso.
stillbooleanfalseDibuja un solo cuadro. Pasa solo con prefers-reduced-motion.
className / style-Van al canvas. El canvas ocupa el 100% de su contenedor; dale alto al contenedor.

weatherFor

PropTipoDefaultDescripción
avgnumber-Promedio de 1 a 5.
weekPulsesnumber-Pulsos de los últimos 7 días. Sin este dato no hay niebla.

Cuándo sí y cuándo no

Hacé
  • Acompañá siempre la escena con la palabra del estado: Soleado, Parcial, Nublado, Tormenta o Niebla.
  • Usá weatherFor en lugar de copiar los umbrales.
Evitá
  • No uses niebla para decir ánimo bajo. Niebla significa pocos datos.
  • No inventes estados nuevos.

Accesibilidad

  • Con prefers-reduced-motion: reduce dibuja un cuadro quieto y no anima.
  • Fuera de pantalla se pausa (IntersectionObserver), así no gasta batería.
  • El canvas es aria-hidden: es decoración. Si transmite algo, decilo también en texto.