Feedback y estado
Cómo se le cuenta al usuario que algo está pasando: cargando, avanzando, vacío o terminado.
El spinner suelto.
import { Loader } from '@openfactu/ui';
<Loader /><Loader size="lg" label="Cargando facturas…" /><Loader variant="white" /><Loader overlay label="Guardando…" />overlay lo pinta sobre una capa que cubre al contenedor: sirve para bloquear una
tarjeta mientras se guarda, sin desmontar lo que hay debajo.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
size |
'sm' | 'md' | 'lg' | 'xl' |
'md' |
Tamaño |
variant |
'primary' | 'white' | 'neutral' |
'primary' |
Color |
label |
string |
— | Texto bajo el spinner |
overlay |
boolean |
— | Capa que cubre al contenedor |
| …resto | HTMLAttributes<HTMLDivElement> |
— | className… |
GlobalLoader
Sección titulada «GlobalLoader»La pantalla de carga de toda la aplicación.
import { GlobalLoader } from '@openfactu/ui';
<GlobalLoader isLoading={arrancando} message="Cargando tu empresa…" />| Prop | Tipo | Descripción |
|---|---|---|
isLoading |
boolean |
Si se muestra |
message |
string |
Texto bajo el indicador |
Skeleton
Sección titulada «Skeleton»La barra fantasma. Sirve suelta o en cualquiera de las cuatro composiciones que trae hechas.
import { Skeleton } from '@openfactu/ui';
<Skeleton /> {/* una línea de texto */}<Skeleton lines={3} /> {/* tres líneas; la última al 65 % */}<Skeleton variant="rect" height={120} /><Skeleton variant="circle" width={32} height={32} /><Skeleton animation="shimmer" delayMs={80} />delayMs retrasa la animación, que es como se escalonan varias barras para que no
parpadeen todas a la vez.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
variant |
'text' | 'rect' | 'circle' |
'text' |
text añade alto de línea; circle fuerza radio completo |
width / height |
number | string |
— | Dimensiones |
lines |
number |
— | Solo en text: líneas apiladas |
lastLineWidth |
number | string |
'65%' |
Ancho de la última línea |
gap |
number | string |
8 |
Separación entre líneas |
radius |
'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full' | string |
— | Redondez |
animation |
'pulse' | 'shimmer' | 'none' |
'pulse' |
Animación |
delayMs |
number |
— | Retardo, para escalonar varias barras |
count |
number |
1 |
Obsoleta: usa lines |
Composiciones
Sección titulada «Composiciones»import { SkeletonCard, SkeletonList, SkeletonTable, SkeletonPage, DashboardSkeleton } from '@openfactu/ui';
<SkeletonCard showAvatar showFooter /><SkeletonList rows={8} showActions /><SkeletonTable rows={10} columns={5} density="compact" /><SkeletonPage header kpis={4} columns={12} blocks={3} /><DashboardSkeleton />DashboardSkeleton no lleva props: es un SkeletonPage con cuatro KPIs y cuatro
bloques, centrado, tal como está el panel de inicio.
SkeletonCard
Sección titulada «SkeletonCard»| Prop | Tipo | Por defecto |
|---|---|---|
count |
number |
1 |
lines |
number |
2 |
showHeader |
boolean |
true |
showAvatar |
boolean |
false |
showFooter |
boolean |
false |
animation |
SkeletonAnimation |
— |
SkeletonList
Sección titulada «SkeletonList»| Prop | Tipo | Por defecto |
|---|---|---|
rows |
number |
5 |
showAvatar |
boolean |
true |
showSubtitle |
boolean |
true |
showMeta |
boolean |
true |
showActions |
boolean |
false |
density |
'compact' | 'normal' | 'comfy' |
'normal' |
variant |
'plain' | 'divided' | 'bordered' |
'divided' |
SkeletonTable
Sección titulada «SkeletonTable»| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
rows |
number |
6 |
Filas fantasma |
columns |
number | Array<{ width?, align? }> |
4 |
Número de columnas, o especificación por columna |
density |
'compact' | 'normal' | 'comfy' |
'normal' |
Densidad |
showHeader |
boolean |
true |
Cabecera falsa. Table lo pone en false: usa la real |
SkeletonPage
Sección titulada «SkeletonPage»| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
header |
boolean |
true |
Título y subtítulo arriba |
kpis |
number |
0 |
Tarjetas KPI |
columns |
1 | 2 | 12 |
1 |
Distribución del cuerpo |
blocks |
number |
2 |
Bloques de contenido |
Progress
Sección titulada «Progress»Barra de progreso horizontal.
import { Progress } from '@openfactu/ui';
<Progress value={62} /><Progress value={62} label="Subiendo adjuntos" showValue /><Progress value={95} variant="warning" size="sm" /><Progress value={100} variant="success" label="Importación terminada" showValue />| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
value |
number |
— | 0–100; se recorta por dentro |
variant |
'accent' | 'success' | 'warning' | 'danger' |
'accent' |
Color |
size |
'sm' | 'md' |
'md' |
Grosor |
label |
string |
— | Etiqueta encima |
showValue |
boolean |
false |
Porcentaje a la derecha del label |
className |
string |
— | Clases |
El anillo de progreso, para consumos y cuotas. Es SVG propio: no arrastra ninguna librería de gráficos.
import { Ring } from '@openfactu/ui';
<Ring value={72} label /><Ring value={1840} max={2000} size={64} thickness={6} label="92 %" ariaLabel="Almacenamiento usado" /><Ring value={95} tone="auto" label />| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
value |
number |
— | Valor |
max |
number |
100 |
Máximo |
size |
number |
40 |
Diámetro en píxeles |
thickness |
number |
4 |
Grosor del anillo |
tone |
'accent' | 'success' | 'warning' | 'danger' | 'auto' |
'accent' |
Color, o umbral automático |
label |
ReactNode | true |
— | Contenido central; true muestra el porcentaje |
ariaLabel |
string |
— | Descripción accesible del valor |
className |
string |
— | Clases |
EmptyState
Sección titulada «EmptyState»El estado vacío con icono, título, pista y acción.
import { EmptyState, Button } from '@openfactu/ui';import { Inbox, Plus } from 'lucide-react';
<EmptyState icon={<Inbox className="h-8 w-8" />} title="Todavía no hay facturas" hint="Cuando emitas la primera aparecerá aquí." action={ <Button variant="accent"> <Plus className="h-3.5 w-3.5" /> Nueva factura </Button> }/>List lo acepta directamente en su prop emptyState, pasándole estas mismas props sin
montar el componente.
| Prop | Tipo | Descripción |
|---|---|---|
title |
string |
Título |
icon |
ReactNode |
Icono |
hint |
string |
Texto de ayuda |
action |
ReactNode |
Normalmente un <Button> |
className |
string |
Clases |
Los avisos. Se usan a través de useToast, con el ToastProvider montado una vez en
la raíz de la aplicación.
import { ToastProvider } from '@openfactu/ui';
<ToastProvider position="bottom-right" max={3}> <App /></ToastProvider>import { useToast } from '@openfactu/ui';
function BotonGuardar() { const toast = useToast();
const guardar = async () => { try { await guardarFactura(); toast.success('Factura guardada'); } catch (e) { toast.error('No se pudo guardar'); } };
return <Button onClick={guardar}>Guardar</Button>;}Seguir una promesa
Sección titulada «Seguir una promesa»Lo más cómodo para una operación con espera: enseña «cargando» y al terminar lo cambia por el mensaje de éxito o de error en el mismo aviso, sin apilar otro.
toast.promise(guardarFactura(), { loading: 'Guardando…', success: 'Factura guardada', error: (e) => `No se pudo guardar: ${e.message}`,});Acciones y avisos persistentes
Sección titulada «Acciones y avisos persistentes»toast.success('Factura eliminada', { action: { label: 'Deshacer', onClick: restaurar },});
toast.warning('Hay cambios sin guardar', { duration: Infinity });
const id = toast.loading('Sincronizando con la AEAT…');// …más tardetoast.update(id, { type: 'success', message: 'Sincronizado' });duration: 0 o Infinity dejan el aviso fijo hasta que se cierre a mano. Los de tipo
loading ya no se cierran solos.
Formas y animaciones
Sección titulada «Formas y animaciones»toast.info('Aviso discreto', { variant: 'outline' });toast.error('Esto tiene que cantar', { variant: 'solid', animation: 'lift' });| Variante | Aspecto |
|---|---|
accent |
Tarjeta neutra con banda de color a la izquierda. El de siempre |
soft |
Fondo teñido del color de estado, para avisos que deben cantar más |
solid |
Relleno del color de estado. El más llamativo |
outline |
Solo contorno. El más discreto |
Animaciones: slide, fade, scale y lift. La salida es la misma transición al
revés.
useToast()
Sección titulada «useToast()»| Método | Firma | Descripción |
|---|---|---|
success |
(message, options?) => string |
Aviso de éxito |
error |
(message, options?) => string |
Aviso de error |
info |
(message, options?) => string |
Aviso informativo |
warning |
(message, options?) => string |
Aviso de advertencia |
loading |
(message, options?) => string |
Con spinner; no se cierra solo |
show |
(message, options & { type? }) => string |
Control total |
dismiss |
(id?) => void |
Cierra uno, o todos si no se indica |
update |
(id, patch) => void |
Actualiza un aviso ya visible |
promise |
(promise, { loading, success, error }, options?) => Promise<T> |
Sigue una promesa |
Todos devuelven el id del aviso, que es lo que necesitas para update y dismiss.
ToastOptions
Sección titulada «ToastOptions»| Prop | Tipo | Descripción |
|---|---|---|
title |
ReactNode |
Encabezado sobre el mensaje |
variant |
ToastVariant |
Forma. Por defecto la del proveedor |
animation |
ToastAnimation |
Entrada y salida. Por defecto la del proveedor |
duration |
number |
Milisegundos. 0 o Infinity lo dejan fijo |
action |
{ label, onClick } |
Botón a la derecha |
dismissible |
boolean |
Se puede cerrar a mano |
id |
string |
Identificador propio; repetirlo sustituye el aviso |
ToastProvider
Sección titulada «ToastProvider»| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
children |
ReactNode |
— | La aplicación |
position |
ToastPosition |
'top-right' |
Esquina donde se apilan |
max |
number |
4 |
Cuántos se ven a la vez; el resto espera turno |
duration |
number |
5000 |
Duración por defecto en milisegundos |
variant |
ToastVariant |
'accent' |
Forma por defecto |
animation |
ToastAnimation |
'slide' |
Animación por defecto |
swipeToDismiss |
boolean |
true |
Descartar deslizando con el dedo |
container |
HTMLElement | null |
document.body |
Dónde se monta el portal |
Posiciones: top-right, top-left, top-center, bottom-right, bottom-left y
bottom-center.
El temporizador se detiene mientras el ratón está encima de la pila, así que un aviso no desaparece justo cuando ibas a pulsar su acción.
Transition
Sección titulada «Transition»Monta y desmonta un nodo con transición de entrada y de salida, esperando a que termine antes de quitarlo del árbol.
import { Transition } from '@openfactu/ui';
<Transition show={abierto} from="opacity-0 -translate-y-2" to="opacity-100 translate-y-0"> <div className="rounded border p-4">Panel de detalle</div></Transition>
<Transition show={visible} from="opacity-0 scale-95" to="opacity-100 scale-100" duration={150}> <Aviso /></Transition>Con unmountOnExit={false} el contenido se queda montado y solo se oculta, que es lo
que quieres si dentro hay estado que no debe perderse.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
show |
boolean |
— | true anima la entrada; false, la salida y desmonta |
children |
ReactNode |
— | Contenido |
from |
string |
'opacity-0' |
Clases del estado oculto |
to |
string |
'opacity-100' |
Clases del estado visible |
duration |
number |
200 |
Duración en milisegundos |
easing |
CSSProperties['transitionTimingFunction'] |
'ease-out' |
Curva |
unmountOnExit |
boolean |
true |
Si false, queda montado y solo se oculta |
className |
string |
— | Clases |