# Componentes de Dynamic Framework
Dynamic Framework ofrece una librería de componentes React especializados para la industria financiera, diseñados para cubrir las necesidades más comunes en aplicaciones bancarias y financieras.
# Catálogo de Componentes
Explora todos los componentes de forma interactiva en nuestro Storybook (opens new window).
# Componentes de Layout
- DBox: Contenedor con opciones de estilo flexibles
- DCard: Contenedor de tarjeta con sub-componentes Header, Body y Footer
- DLayout: Grilla de página con
DLayout.Panepara diseños responsivos - DCollapse: Sección colapsable con un encabezado que actúa como botón
# Componentes de Navegación
- DTabs: Navegación por pestañas con variantes
tabs,pills,underlineytoggle-button-group, panelesDTabs.TabyDTabs.Provider - DStepper: Navegación multi-paso (con variantes DStepperDesktop y DStepperMobile)
- DPaginator: Control de paginación para listados
# Componentes de Formulario
- DInput: Input de texto con estados de validación
- DInputSearch: Input de búsqueda con debounce integrado
- DInputCheck: Input de checkbox o radio
- DInputSwitch: Input de toggle switch
- DInputCounter: Contador numérico con incremento/decremento
- DInputCurrency: Input formateado para moneda
- DInputMask: Input con máscara para datos formateados
- DInputPassword: Input de contraseña con toggle de visibilidad
- DInputPhone: Input de número telefónico internacional
- DInputPin: Input de PIN/código con cajas de dígitos individuales
- DInputRange: Input de rango deslizante
- DInputSelect: Select nativo con opciones como objetos
- DSelect: Select avanzado con búsqueda (usa react-select)
- DDatePicker: Selector de fecha con calendario
- DPasswordStrengthMeter: Input de contraseña con indicador de fortaleza
- DBoxFile: Carga de archivos con drag-and-drop
# Componentes de Visualización de Datos
- DListGroup: Lista con
DListGroupItem, que puede ser texto, enlace o botón - DTimeline: Línea de tiempo para historial de eventos
- DCarousel: Carrusel/slider con DCarouselSlide
- DCurrencyText: Visualización de moneda formateada
- DProgress: Indicador de barra de progreso
- DSkeleton: Marcador de carga con
DSkeleton.Text,DSkeleton.BlockyDSkeleton.Circle
# Componentes de Retroalimentación
- DAlert: Mensajes de notificación al usuario
- DModal: Diálogo modal con Header, Body y Footer, abierto como portal
- DOffcanvas: Panel deslizante con Header, Body y Footer, abierto como portal
- DToast: Notificaciones toast temporales (con DToastContainer)
- DPopover: Popover contextual
- DTooltip: Tooltips al pasar el mouse
- DDropdown: Menú desplegable de acciones
# Componentes Visuales
- DAvatar: Visualización de avatar de usuario
- DBadge: Badges de estado/conteo
- DChip: Elemento compacto para etiquetas/filtros
- DButton: Botón de acción con estado de carga
- DButtonIcon: Botón solo con icono
- DIcon: Componente de icono (usa Lucide Icons)
# Componentes Financieros Especializados
- DCreditCard: Visualización de tarjeta de crédito/débito
- DVoucher: Comprobante de transacción con descarga y compartir
- DOtp: Formulario de código de un solo uso con cuenta regresiva
# Componentes de Estado y Utilidad
- DDataStateWrapper: Manejo declarativo de los estados loading, error y empty de una lista o de un recurso único (exporta también
EmptyState,ErrorStateyLoadingState) - DErrorBoundary: Límite de error con fallback configurable (basado en
react-error-boundary) - DConfirmModalContainer: Contenedor de los modales de confirmación de
useConfirmModal
# Uso de Componentes
Todos los componentes se importan desde @dynamic-framework/ui-react:
import {
DButton,
DCard,
DInput,
} from '@dynamic-framework/ui-react';
function MyComponent() {
return (
<DCard>
<DCard.Header>
<h5>Detalles de la Cuenta</h5>
</DCard.Header>
<DCard.Body>
<DInput
id="account-name"
label="Nombre de la Cuenta"
placeholder="Ingresa el nombre de la cuenta"
/>
<DButton color="primary">
Guardar Cambios
</DButton>
</DCard.Body>
</DCard>
);
}
# Props de Componentes
# Props Comunes
La mayoría de los componentes comparten estas props:
| Prop | Tipo | Descripción |
|---|---|---|
className | string | Clases CSS adicionales |
style | CSSProperties | Estilos en línea |
dataAttributes | DataAttributes | Objeto con claves data-* que se agregan como atributos al elemento raíz |
Los componentes de formulario aceptan además id, que conecta el label con el control.
# Variantes de Color
Los componentes que soportan colores usan la prop color:
<DButton color="primary">Primario</DButton>
<DButton color="secondary">Secundario</DButton>
<DButton color="success">Éxito</DButton>
<DButton color="danger">Peligro</DButton>
<DButton color="warning">Advertencia</DButton>
<DButton color="info">Info</DButton>
DAlert solo acepta los colores de estado: success, danger, warning e info.
# Variantes de Tamaño
Los componentes que soportan tamaños usan la prop size, con los valores sm y lg:
<DButton size="sm">Pequeño</DButton>
<DButton>Por defecto</DButton>
<DButton size="lg">Grande</DButton>
# Tamaños Responsivos
DButton y DBadge aceptan en size un objeto con un valor por breakpoint (xs, sm, md, lg, xl, xxl). Cada valor sigue siendo sm o lg. Se aplica el valor del mayor breakpoint definido cuyo ancho mínimo se cumple, así que un breakpoint sin valor hereda el del anterior: en el ejemplo, el botón mantiene lg en xl y xxl. El tamaño por defecto solo se usa cuando ningún breakpoint definido aplica, por ejemplo { md: 'lg' } en un viewport xs:
<DButton size={{ xs: 'sm', md: 'lg' }} text="Continuar" />
<DBadge size={{ xs: 'sm', lg: 'lg' }} color="success" text="Activo" />
Las props responsivas leen los breakpoints de las variables --bs-breakpoint-* de dynamic-ui.css. No necesitan DContextProvider, pero sí que el CSS esté cargado antes de renderizar: sin él, el objeto cae al valor por defecto y en desarrollo se muestra un aviso en consola.
# Variantes de Botón
DButton y DButtonIcon aceptan variant con los valores solid (por defecto), outline, link y soft. La variante soft genera la clase .btn-soft-{color}, con un fondo tenue:
<DButton variant="outline" color="primary" text="Cancelar" />
<DButton variant="soft" color="primary" text="Acción suave" />
<DButton variant="soft" color="danger" text="Eliminar" />
# Context Provider
DContextProvider configura idioma, moneda, iconos y portales para todos los componentes que envuelve. Sin provider, los componentes usan los valores por defecto de la librería, pero useDPortalContext y useConfirmModal sí lo necesitan.
| Prop | Tipo | Descripción |
|---|---|---|
language | string | Idioma disponible para tu aplicación en useDContext().language; ningún componente lo lee ni cambia el idioma de i18next (default 'en') |
currency | { symbol: string; precision: number; separator: string; decimal: string } | Formato de moneda que usan DCurrencyText, DInputCurrency y useFormatCurrency |
icon | { familyClass: string; familyPrefix: string; materialStyle: boolean } | Familia de iconos de fuente para los nombres que no resuelven ni el registro ni Lucide |
iconMap | IconMapProps | Iconos que usan los componentes internamente (cerrar, chevrons, estados de alerta, adornos de input) |
iconRegistry | Record<string, IconComponent> | Iconos propios por nombre (ver Iconos Personalizados) |
portalName | string | Id del nodo donde se montan los portales (default 'd-portal') |
availablePortals | Record<string, FC<PortalProps>> | Componentes que se pueden abrir con openPortal (ver Portales) |
breakpoints | BreakpointProps | Deprecada y sin efecto: los breakpoints se leen de las variables --bs-breakpoint-* del CSS |
import { DContextProvider } from '@dynamic-framework/ui-react';
function App() {
return (
<DContextProvider
language="es"
currency={{ symbol: '$', precision: 0, separator: '.', decimal: ',' }}
>
<TuApp />
</DContextProvider>
);
}
Ver la referencia en Storybook (opens new window).
# Portales
DModal y DOffcanvas no se muestran ni se ocultan con una prop: se renderizan como portales. Cada uno se registra con un nombre en availablePortals de DContextProvider y se abre con openPortal, del hook useDPortalContext:
- Define un tipo que asocie cada nombre de portal con los datos (
payload) que recibe. - Crea el componente del portal. Recibe
nameypayload(PortalProps) y renderiza unDModalo unDOffcanvascon la propname, que es obligatoria. - Registra el componente en
availablePortals. - Llama a
openPortal(nombre, payload)para abrirlo y aclosePortal()para cerrar el portal que está encima.
import {
DButton,
DContextProvider,
DModal,
DOffcanvas,
useDPortalContext,
type PortalProps,
} from '@dynamic-framework/ui-react';
type Portals = {
transferConfirm: { amount: string };
accountFilters: { accountId: string };
};
function TransferConfirmModal({ name, payload }: PortalProps<Portals['transferConfirm']>) {
const { closePortal } = useDPortalContext<Portals>();
return (
<DModal name={name} centered>
<DModal.Header showCloseButton onClose={closePortal}>
<h5 className="mb-0">Confirmar transferencia</h5>
</DModal.Header>
<DModal.Body>
<p>Se transferirán {payload.amount}.</p>
</DModal.Body>
<DModal.Footer>
<DButton text="Cancelar" variant="outline" onClick={closePortal} />
<DButton text="Confirmar" onClick={closePortal} />
</DModal.Footer>
</DModal>
);
}
function AccountFiltersPanel({ name, payload }: PortalProps<Portals['accountFilters']>) {
const { closePortal } = useDPortalContext<Portals>();
return (
<DOffcanvas
name={name}
openFrom={{ xs: 'bottom', md: 'end' }}
width={{ xs: '100%', md: '480px' }}
>
<DOffcanvas.Header showCloseButton onClose={closePortal}>
<h5 className="mb-0">Filtros de la cuenta {payload.accountId}</h5>
</DOffcanvas.Header>
<DOffcanvas.Body>
<p>Contenido de los filtros</p>
</DOffcanvas.Body>
</DOffcanvas>
);
}
const availablePortals = {
transferConfirm: TransferConfirmModal,
accountFilters: AccountFiltersPanel,
};
function AccountActions() {
const { openPortal } = useDPortalContext<Portals>();
return (
<>
<DButton
text="Transferir"
onClick={() => openPortal('transferConfirm', { amount: '$100.000' })}
/>
<DButton
text="Filtros"
variant="outline"
onClick={() => openPortal('accountFilters', { accountId: '001' })}
/>
</>
);
}
export function App() {
return (
<DContextProvider<Portals> availablePortals={availablePortals}>
<AccountActions />
</DContextProvider>
);
}
Comportamiento de los portales:
DContextProvidercrea el nodo#d-portal(o el deportalName) endocument.body.- Los portales se apilan:
openPortalagrega uno encima yclosePortalcierra el último.stackexpone los portales abiertos. - Escape y el clic en el fondo cierran el portal superior, salvo que tenga
staticBackdrop. openPortallanza un error si el nombre no está registrado enavailablePortals.DOffcanvasacepta enopenFrom(start,end,top,bottom) un objeto por breakpoint. Enwidthyheightacepta cualquier longitud CSS ('320px','50vw','100%') o un objeto por breakpoint.
Ver la referencia en Storybook: DModal (opens new window), DOffcanvas (opens new window) y useDPortalContext (opens new window).
# Layout
# DBox y DLayout
DBox es un contenedor con el estilo de superficie de Dynamic. DLayout es una grilla CSS: columns define el número de columnas, gap (de 0 a 30) el espacio, y gapSm a gapXxl lo ajustan por breakpoint. Cada DLayout.Pane ocupa cols columnas, con colsXs a colsXxl por breakpoint:
import { DBox, DLayout } from '@dynamic-framework/ui-react';
function AccountLayout() {
return (
<DLayout columns={12} gap={4}>
<DLayout.Pane cols={12} colsLg={4}>
<DBox className="p-4">Resumen</DBox>
</DLayout.Pane>
<DLayout.Pane cols={12} colsLg={8}>
<DBox className="p-4">Movimientos</DBox>
</DLayout.Pane>
</DLayout>
);
}
# DCollapse
DCollapse renderiza un encabezado clicable (Component, obligatoria) y un cuerpo (children) que se expande o se colapsa. Maneja su propio estado:
| Prop | Tipo | Descripción |
|---|---|---|
Component | ReactNode | Contenido del encabezado, dentro del botón que alterna el estado |
defaultCollapsed | boolean | Estado inicial (default true). Es reactiva: si cambia, el componente adopta el nuevo valor |
onChange | (collapsed: boolean) => void | Se dispara con el nuevo estado en cada clic |
iconOpen / iconClose | string | Iconos del encabezado colapsado y expandido |
import { DCollapse } from '@dynamic-framework/ui-react';
function AccountDetails() {
return (
<DCollapse Component={<span>Información adicional</span>}>
<p className="p-3">Este contenido puede colapsarse para ahorrar espacio.</p>
</DCollapse>
);
}
Para un acordeón donde solo una sección queda abierta, controla defaultCollapsed desde el padre y sincroniza con onChange:
import { useState } from 'react';
import { DCollapse } from '@dynamic-framework/ui-react';
function FAQ({ items }: { items: { question: string; answer: string }[] }) {
const [openIndex, setOpenIndex] = useState<number | null>(null);
return (
<div>
{items.map((item, index) => (
<DCollapse
key={item.question}
Component={<span>{item.question}</span>}
defaultCollapsed={openIndex !== index}
onChange={(collapsed) => setOpenIndex(collapsed ? null : index)}
>
<div className="p-3">{item.answer}</div>
</DCollapse>
))}
</div>
);
}
# Navegación con DTabs
DTabs recibe las pestañas en options ({ label, tab, disabled? }) y los paneles como hijos DTabs.Tab, cada uno con el tab al que corresponde:
| Prop | Tipo | Descripción |
|---|---|---|
options | DTabOption[] | Pestañas a mostrar |
variant | 'tabs' \| 'pills' \| 'underline' \| 'toggle-button-group' | Estilo de la barra (default 'underline') |
defaultSelected | string | Pestaña inicial; sin ella se selecciona la primera habilitada |
onChange | (option: DTabOption) => void | Se dispara al cambiar de pestaña |
vertical | boolean | Barra vertical |
ariaLabel / ariaLabelledBy | string | Nombre accesible de la lista de pestañas |
import { DTabs } from '@dynamic-framework/ui-react';
const options = [
{ label: 'Movimientos', tab: 'movements' },
{ label: 'Estados de cuenta', tab: 'statements' },
];
function AccountTabs() {
return (
<DTabs options={options} variant="pills" ariaLabel="Secciones de la cuenta">
<DTabs.Tab tab="movements">Lista de movimientos</DTabs.Tab>
<DTabs.Tab tab="statements">Estados de cuenta</DTabs.Tab>
</DTabs>
);
}
Cuando la barra y los paneles viven en lugares distintos del árbol, envuélvelos en DTabs.Provider, que guarda la pestaña seleccionada. Dentro del provider, defaultSelected de DTabs se ignora:
import { DTabs } from '@dynamic-framework/ui-react';
const options = [
{ label: 'Resumen', tab: 'summary' },
{ label: 'Detalle', tab: 'detail' },
];
function SplitTabs() {
return (
<DTabs.Provider defaultSelected="summary">
<header>
<DTabs options={options} ariaLabel="Vista" />
</header>
<main>
<DTabs.Tab tab="summary">Resumen</DTabs.Tab>
<DTabs.Tab tab="detail">Detalle</DTabs.Tab>
</main>
</DTabs.Provider>
);
}
# Formularios
# DInputSearch
Input de búsqueda con debounce integrado. Extiende DInput (omite onChange/defaultValue/type). Igual que DInput, su onChange entrega el string, no el evento.
Props propias:
| Prop | Tipo | Descripción |
|---|---|---|
debounceMs | number | Retardo del debounce (default 300) |
onChange | (value: string) => void | Se dispara tras el debounce, recibe el string |
onImmediateChange | (value: string) => void | Se dispara en cada tecla, sin debounce |
value | string | Modo controlado |
defaultValue | string | Modo no controlado |
placeholder | string | Texto placeholder (default 'Search...') |
import { useState } from 'react';
import { DInputSearch } from '@dynamic-framework/ui-react';
function AccountSearch({ onSearch }: { onSearch: (value: string) => void }) {
const [query, setQuery] = useState('');
return (
<>
{/* No controlado, con debounce por defecto (300ms) */}
<DInputSearch
id="account-search"
label="Buscar"
placeholder="Buscar cuentas..."
onChange={(value) => onSearch(value)}
onImmediateChange={(value) => setQuery(value)}
/>
{/* Controlado, con debounce personalizado */}
<DInputSearch
id="account-search-controlled"
label="Buscar"
value={query}
debounceMs={500}
onImmediateChange={setQuery}
onChange={onSearch}
/>
</>
);
}
# DSelect
Error Común
DSelect usa react-select internamente: su value es el objeto de opción completo y su onChange recibe ese objeto. Usar e.target.value NO funcionará.
import { useState } from 'react';
import { DSelect } from '@dynamic-framework/ui-react';
type CurrencyOption = { value: string; label: string };
// Las opciones deben ser objetos con value y label
const options: CurrencyOption[] = [
{ value: 'usd', label: 'Dólar Estadounidense' },
{ value: 'eur', label: 'Euro' },
{ value: 'gbp', label: 'Libra Esterlina' },
];
function CurrencySelect() {
// El estado guarda el objeto completo de la opción, no solo el value
const [selected, setSelected] = useState<CurrencyOption | null>(null);
return (
<DSelect<CurrencyOption>
id="currency"
label="Seleccionar Moneda"
options={options}
value={selected}
onChange={(option) => setSelected(option)}
/>
);
}
# Qué NO Hacer
// ❌ Incorrecto - Usando e.target.value (no funciona con DSelect)
onChange={(e) => setValue(e.target.value)}
// ❌ Incorrecto - Pasando solo el string del value
value={selectedValue} // Debe ser el objeto de opción completo
// ❌ Incorrecto - Opciones como strings simples
options={['USD', 'EUR', 'GBP']} // Deben ser objetos { value, label }
# DSelect vs DInputSelect
| Componente | Caso de Uso | value | onChange recibe |
|---|---|---|---|
DSelect | Select avanzado con búsqueda, multi-selección | Objeto de opción | El objeto de opción |
DInputSelect | Select nativo simple | string \| number (el value de la opción) | El objeto de opción |
import { useState } from 'react';
import { DInputSelect } from '@dynamic-framework/ui-react';
const accountTypes = [
{ value: 'checking', label: 'Cuenta corriente' },
{ value: 'savings', label: 'Cuenta de ahorro' },
];
function AccountTypeSelect() {
const [type, setType] = useState<string | number>('checking');
return (
<DInputSelect
id="account-type"
label="Tipo de cuenta"
options={accountTypes}
value={type}
onChange={(option) => setType(option.value)}
/>
);
}
Con opciones de otra forma, valueExtractor y labelExtractor indican qué campo usar como valor y como texto.
# DPasswordStrengthMeter
Input de contraseña con indicador de fortaleza. onChange entrega el string y enabledChecks elige las reglas ('uppercase', 'lowercase', 'number', 'specialChar'):
import { useState } from 'react';
import { DPasswordStrengthMeter } from '@dynamic-framework/ui-react';
function NewPassword() {
const [password, setPassword] = useState('');
return (
<DPasswordStrengthMeter
id="new-password"
label="Nueva contraseña"
value={password}
onChange={setPassword}
/>
);
}
# Estados de Carga y Error
# DDataStateWrapper
Muestra el estado de loading, error, empty o los datos, en ese orden de precedencia. data puede ser una lista (vacía cuando no tiene elementos) o un recurso único (vacío cuando es null o undefined); children recibe los datos con la misma forma. Exporta también los sub-componentes EmptyState, ErrorState y LoadingState para usarlos por separado.
Props:
| Prop | Tipo | Descripción |
|---|---|---|
isLoading | boolean | Estado de carga |
isError | boolean | Estado de error |
data | T[] \| undefined o T \| null \| undefined | Lista o recurso único |
children | (data: T[]) => ReactNode o (data: T) => ReactNode | Render con datos garantizados |
onRetry | () => void | Callback del botón de reintento |
messages | { loading?, empty?, error?, retry? } | Textos de los estados por defecto |
renderLoading / renderEmpty / renderError | ReactNode \| (() => ReactNode) | Reemplazo completo de cada estado |
import {
DDataStateWrapper,
DListGroup,
DListGroupItem,
} from '@dynamic-framework/ui-react';
type Movement = { id: string; title: string };
type MovementsQuery = {
data: Movement[] | undefined;
isLoading: boolean;
isError: boolean;
refetch: () => void;
};
function Movements({ query }: { query: MovementsQuery }) {
return (
<DDataStateWrapper
isLoading={query.isLoading}
isError={query.isError}
data={query.data}
onRetry={query.refetch}
messages={{
loading: 'Cargando movimientos...',
empty: 'No hay movimientos',
error: 'No pudimos cargar los movimientos',
retry: 'Reintentar',
}}
>
{(movements) => (
<DListGroup ariaLabel="Movimientos">
{movements.map((m) => <DListGroupItem key={m.id}>{m.title}</DListGroupItem>)}
</DListGroup>
)}
</DDataStateWrapper>
);
}
Con un recurso único, children recibe el objeto:
import { DDataStateWrapper, DCurrencyText } from '@dynamic-framework/ui-react';
type Account = { id: string; name: string; balance: number };
function AccountSummary({ account, isLoading, isError }: {
account: Account | null | undefined;
isLoading: boolean;
isError: boolean;
}) {
return (
<DDataStateWrapper isLoading={isLoading} isError={isError} data={account}>
{(acc) => (
<p>{acc.name}: <DCurrencyText value={acc.balance} /></p>
)}
</DDataStateWrapper>
);
}
Encaja con el patrón TanStack Query. Ver Integración con APIs.
# DSkeleton
Marcador de carga con formas que imitan el contenido. DSkeleton agrupa las formas DSkeleton.Text, DSkeleton.Block y DSkeleton.Circle, y con items repite la plantilla:
| Prop | Tipo | Descripción |
|---|---|---|
animation | 'glow' \| 'wave' \| 'none' | Animación de las formas; los skeletons anidados la heredan |
ariaLabel | string | Texto que anuncian los lectores de pantalla (default 'Loading...') |
color | string | Color del tema para las formas |
gap | string \| number | Espacio entre elementos (los números son píxeles) |
direction | 'vertical' \| 'horizontal' | Eje de los elementos (default 'vertical') |
items | number | Repeticiones de la plantilla (máximo 100) |
itemClassName | string | Clase del contenedor de cada repetición |
children | ReactNode \| ((index: number) => ReactNode) | Plantilla de cada elemento |
Las formas aceptan: DSkeleton.Text → lines, size ('xs' \| 'sm' \| 'lg'), widths, lastLineWidth y rounded; DSkeleton.Block → width, height y rounded; DSkeleton.Circle → size.
import { DSkeleton } from '@dynamic-framework/ui-react';
function MovementsSkeleton() {
return (
<DSkeleton items={3} gap={16} animation="wave" ariaLabel="Cargando movimientos">
<DSkeleton direction="horizontal" gap={12}>
<DSkeleton.Circle size={40} />
<DSkeleton.Text lines={2} lastLineWidth="60%" />
</DSkeleton>
</DSkeleton>
);
}
Ver la referencia en Storybook (opens new window).
# DErrorBoundary
Límite de error con fallback configurable, basado en react-error-boundary. La librería también exporta el hook useErrorBoundary, el tipo FallbackProps y la utilidad getErrorMessage.
Props (DErrorBoundaryProps):
| Prop | Tipo | Descripción |
|---|---|---|
name | string | Nombre identificador del boundary |
fallback | (props: FallbackProps) => ReactNode | Reemplaza el fallback por defecto |
messages | { error?: string; retry?: string } | Textos del fallback por defecto; se ignoran si pasas fallback |
onError | (error: unknown, info: ErrorInfo) => void | Callback al capturar un error |
onReset | () => void | Callback al reiniciar el boundary |
resetKeys | unknown[] | Reinicia el boundary cuando cambia alguno de estos valores |
children | ReactNode | Contenido protegido |
import type { ReactNode } from 'react';
import { DErrorBoundary } from '@dynamic-framework/ui-react';
function DashboardBoundary({ children }: { children: ReactNode }) {
return (
<DErrorBoundary
name="dashboard"
messages={{ error: 'No pudimos cargar el panel', retry: 'Reintentar' }}
onError={(error, info) => console.error(error, info.componentStack)}
>
{children}
</DErrorBoundary>
);
}
# Componentes Financieros
# DCreditCard
import { DCreditCard } from '@dynamic-framework/ui-react';
function CardPreview() {
return (
<DCreditCard
brand="visa"
name="Juana Pérez"
number="**** **** **** 1234"
/>
);
}
brand acepta 'visa' o 'mastercard'. isVertical, isChipVisible, holderText y logoImage ajustan la presentación.
# DVoucher
Comprobante de transacción con botones para descargarlo y compartirlo como imagen. title y message son obligatorias:
| Prop | Tipo | Descripción |
|---|---|---|
title | string | Título del comprobante |
message | string | Mensaje bajo el título |
icon | false \| null \| string \| Partial<DIconProps> | Nombre de Lucide, objeto de props de DIcon, o false/null para ocultarlo |
amount / amountDetails | string / ReactNode | Monto y su detalle |
downloadText / shareText / fileName | string | Textos de los botones y nombre del archivo |
hideActions | boolean | Oculta los botones |
onError | (err: Error) => void \| Promise<void> | Error al generar la imagen |
import { DVoucher } from '@dynamic-framework/ui-react';
function PaymentReceipt() {
return (
<DVoucher
icon={{ icon: 'CircleCheckBig', color: 'success' }}
title="Pago exitoso"
message="Tu pago se procesó correctamente"
amount="$125.00"
>
<p>Número de operación: 000123</p>
</DVoucher>
);
}
Los hooks useScreenshot, useScreenshotDownload y useScreenshotWebShare exponen la misma captura para componentes propios.
# DOtp
Formulario de código de un solo uso con cuenta regresiva para reenviar:
import { DOtp } from '@dynamic-framework/ui-react';
function VerifyCode({ onSubmit }: { onSubmit: () => Promise<void> }) {
return (
<DOtp
otpSize={6}
seconds={60}
action={onSubmit}
texts={{ title: 'Ingresa el código', submit: 'Verificar', resend: 'Reenviar' }}
/>
);
}
action se invoca al enviar y no recibe el código ingresado. Si necesitas leerlo, arma el formulario con el hook useOtp, que expone otp, setOtp, submit, isLoading, invalid y la cuenta regresiva.
# Menús y Líneas de Tiempo
# DDropdown
Menú de acciones. Cada acción lleva label (obligatoria), y opcionalmente icon, color, onClick, href o disabled. Una acción con isDivider se muestra como separador:
import { DDropdown } from '@dynamic-framework/ui-react';
function AccountMenu({ onEdit }: { onEdit: () => void }) {
return (
<DDropdown
actions={[
{ label: 'Editar', icon: 'Pencil', onClick: onEdit },
{ label: 'Eliminar', icon: 'Trash2', color: 'danger' },
{ label: 'separador', isDivider: true },
{ label: 'Ayuda', href: '/help' },
]}
/>
);
}
# DTimeline
import { DTimeline } from '@dynamic-framework/ui-react';
function PaymentHistory() {
return (
<DTimeline
items={[
{ title: 'Pago exitoso', time: '10:00', status: 'success' },
{ title: 'En proceso', time: '09:55', status: 'warning' },
{ title: 'Iniciado', time: '09:50' },
]}
/>
);
}
# DCarousel
iconArrowLeft e iconArrowRight aceptan las props de DIcon para personalizar las flechas:
import { DCarousel } from '@dynamic-framework/ui-react';
function Promotions() {
return (
<DCarousel
iconArrowLeft={{ icon: 'ArrowLeft', color: 'primary' }}
iconArrowRight={{ icon: 'ArrowRight', color: 'primary' }}
>
<DCarousel.Slide>Promoción 1</DCarousel.Slide>
<DCarousel.Slide>Promoción 2</DCarousel.Slide>
</DCarousel>
);
}
# Hooks Exportados por la Librería
Estos hooks se exportan desde @dynamic-framework/ui-react:
| Propósito | Hook | Retorna |
|---|---|---|
| Contexto | useDContext | La configuración de DContextProvider y setContext |
| Portales | useDPortalContext | { openPortal, closePortal, stack } |
| Confirmación | useConfirmModal | { open } (requiere DConfirmModalContainer) |
| Notificaciones | useDToast | { toast } (requiere DToastContainer) |
| Pestañas | useTabContext | { isSelected(tab) } dentro de DTabs |
| Errores | useErrorBoundary | { error, showBoundary, resetBoundary } dentro de DErrorBoundary |
| Moneda | useFormatCurrency | { format, values } |
| Moneda | useInputCurrency | Estado y handlers de un input de moneda |
| OTP | useOtp | { otp, setOtp, submit, invalid, error, isLoading, secondsLeft, restartCountdown } |
| OTP | useCountdown | { secondsLeft, restartCountdown } |
| Comprobantes | useScreenshot | { clipRef, takeBlob } |
| Comprobantes | useScreenshotDownload | { downloadRef, download } |
| Comprobantes | useScreenshotWebShare | { shareRef, share } |
| Responsivo | useMediaQuery | boolean para una media query |
| Responsivo | useMediaBreakpointUpXs, useMediaBreakpointUpSm, useMediaBreakpointUpMd, useMediaBreakpointUpLg, useMediaBreakpointUpXl, useMediaBreakpointUpXxl | boolean: el viewport está en ese breakpoint o uno mayor |
| Listas y estado | useItemSelection | Selección de elementos de una lista |
| Listas y estado | useStackState | [items, { push, pop, peek, clear, isEmpty, length }] |
| Utilidades de DOM | useDisableBodyScrollEffect | Bloquea el scroll del body mientras su argumento es true |
| Utilidades de DOM | useDisableInputWheel | { handleOnWheel } para que la rueda no cambie un input numérico |
| Utilidades de DOM | useProvidedRefOrCreate | La ref recibida o una nueva |
# Utilidades Adicionales
import {
getErrorMessage, // Extraer mensaje de error de errores desconocidos
formatCurrency, // Formatear un número como moneda fuera de un componente
} from '@dynamic-framework/ui-react';
Patrones de Implementación vs Exports de la Librería
Hooks como useApi, useAccounts, o useFormValidation mostrados en la documentación son patrones de implementación que creas en tu proyecto, NO exports de la librería. Ver Integración con APIs para ejemplos de patrones.
# useConfirmModal
Hook para abrir un modal de confirmación desde código. Requiere estar dentro de DContextProvider y montar una vez DConfirmModalContainer con nodeId igual al portalName del provider ('d-portal' por defecto).
Config (UseConfirmModalConfig):
| Campo | Tipo | Descripción |
|---|---|---|
title / message | string | Título y mensaje del modal |
confirmLabel / cancelLabel | string | Textos de los botones |
confirmColor | 'primary' \| 'danger' \| 'warning' | Color del botón de confirmación |
onConfirm | () => void \| Promise<void> | Callback de confirmación (requerido) |
onClose | () => void | Callback al cerrar |
critical | { code: string; codeLabel?: string; inputPlaceholder?: string } | Confirmación crítica que exige teclear un código exacto |
import {
DButton,
DConfirmModalContainer,
DContextProvider,
useConfirmModal,
} from '@dynamic-framework/ui-react';
function DeleteAccountButton({ onDelete }: { onDelete: () => Promise<void> }) {
const confirm = useConfirmModal({
title: 'Eliminar cuenta',
message: 'Esta acción no se puede deshacer.',
confirmColor: 'danger',
confirmLabel: 'Eliminar',
critical: { code: 'ELIMINAR CUENTA' }, // el usuario debe teclear esto
onConfirm: onDelete,
});
return <DButton color="danger" text="Eliminar" onClick={confirm.open} />;
}
export function App() {
return (
<DContextProvider>
<DeleteAccountButton onDelete={async () => {}} />
<DConfirmModalContainer nodeId="d-portal" />
</DContextProvider>
);
}
# Sistema de Iconos
Dynamic UI usa Lucide Icons (opens new window), una librería de iconos moderna y consistente con más de 1000 iconos. Usa el componente DIcon para renderizar iconos.
Crítico: Formato PascalCase Requerido
Los iconos deben usar formato PascalCase. Usar kebab-case mostrará un "?" como placeholder.
# Uso Básico
import { DIcon } from '@dynamic-framework/ui-react';
// ✅ Correcto - PascalCase
<DIcon icon="Check" />
<DIcon icon="AlertCircle" />
<DIcon icon="ArrowRight" />
<DIcon icon="CreditCard" />
// ❌ Incorrecto - kebab-case (mostrará "?")
<DIcon icon="check" />
<DIcon icon="alert-circle" />
# Tamaño de Iconos
Usa la prop size con valores CSS exactos:
<DIcon icon="Home" size="1rem" /> {/* 16px */}
<DIcon icon="Home" size="1.5rem" /> {/* 24px - por defecto */}
<DIcon icon="Home" size="2rem" /> {/* 32px */}
<DIcon icon="Home" size="48px" /> {/* 48px */}
# Tamaños Responsivos de Iconos
size también acepta un valor por breakpoint (xs, sm, md, lg, xl, xxl):
<DIcon
icon="Home"
size={{
xs: '1rem', // Móvil
md: '1.5rem', // Tablet
lg: '2rem', // Desktop
}}
/>
Como el resto de props responsivas, requiere dynamic-ui.css cargado, no DContextProvider. Por defecto el tamaño se resuelve al montar el icono; con useListenerSize el icono escucha los cambios de breakpoint y se actualiza al redimensionar, con un costo de rendimiento si hay muchos iconos.
# Colores de Iconos
Los iconos heredan su color de la propiedad CSS color del padre por defecto. Usa la prop color para colores del tema:
<DIcon icon="Check" color="success" />
<DIcon icon="X" color="danger" />
<DIcon icon="AlertCircle" color="warning" />
<DIcon icon="Info" color="info" />
<DIcon icon="Star" color="primary" />
Para colores personalizados, usa CSS:
<DIcon icon="Heart" style={{ color: '#e91e63' }} />
# Iconos Personalizados
Además de los iconos de Lucide, DIcon acepta iconos propios de dos formas:
1. Pasar un componente React directamente. La prop icon acepta string | IconComponent:
import { MiIconoSvg } from './icons';
<DIcon icon={MiIconoSvg} />
2. Registrar iconos por nombre en DContextProvider con la prop iconRegistry:
const iconRegistry = { NMChevron: MiIconoSvg };
<DContextProvider iconRegistry={iconRegistry}>
<DIcon icon="NMChevron" /> {/* resuelve desde el registry */}
<DIcon icon="Home" /> {/* fallback a Lucide */}
</DContextProvider>
DIcon resuelve el nombre en este orden: primero el iconRegistry, luego Lucide y por último la familia de iconos de fuente configurada en icon. El iconRegistry tiene prioridad sobre Lucide cuando el nombre coincide en ambos.
# Encontrar Iconos
Explora todos los iconos disponibles en lucide.dev/icons (opens new window). Cuando encuentres un icono:
- Nota el nombre del icono (ej.
arrow-right) - Convierte a PascalCase (ej.
ArrowRight) - Usa en DIcon:
<DIcon icon="ArrowRight" />
# Iconos por Categoría
# Navegación
Home, Menu, X, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, ArrowLeft, ArrowRight, ExternalLink, MoreHorizontal, MoreVertical
# Acciones
Plus, Minus, Check, X, Edit, Pencil, Trash2, Copy, Download, Upload, Share, Send, Save, RefreshCw
# Usuario y Cuenta
User, Users, UserPlus, UserMinus, UserCheck, LogIn, LogOut, Key, Lock, Unlock, Shield, Settings
# Comunicación
Mail, MessageSquare, MessageCircle, Phone, Bell, BellOff, AtSign
# Media
Image, Camera, Video, Play, Pause, Volume2, VolumeX, Mic, MicOff
# Archivos y Datos
File, FileText, Folder, FolderOpen, Archive, Clipboard, Database
# Financieros (Apps Bancarias)
CreditCard, Wallet, DollarSign, Banknote, PiggyBank, TrendingUp, TrendingDown, BarChart, PieChart, Receipt, Calculator
# Estado y Feedback
AlertCircle, AlertTriangle, Info, HelpCircle, CheckCircle, XCircle, Clock, Loader
# Varios
Search, Filter, Calendar, MapPin, Globe, Link, Eye, EyeOff, Star, Heart, ThumbsUp, ThumbsDown
# Iconos en Botones
Usa DButtonIcon para botones solo con icono. Su nombre accesible va en aria-label:
import { DButtonIcon } from '@dynamic-framework/ui-react';
<DButtonIcon icon="Pencil" aria-label="Editar elemento" />
<DButtonIcon icon="Trash2" aria-label="Eliminar elemento" color="danger" />
<DButtonIcon icon="Plus" aria-label="Agregar nuevo" variant="outline" />
Para botones con icono y texto, usa DButton con iconStart o iconEnd:
import { DButton } from '@dynamic-framework/ui-react';
<DButton iconStart="Plus">Agregar Cuenta</DButton>
<DButton iconEnd="ArrowRight">Continuar</DButton>
<DButton iconStart="Download" iconEnd="ChevronDown">Exportar</DButton>
# Accesibilidad
Los componentes de Dynamic Framework incorporan roles y atributos ARIA siguiendo los patrones de WAI-ARIA (opens new window). La librería no puede hacer tu aplicación accesible por sí sola: parte del trabajo depende de las props que pases y de cómo compongas los componentes.
# Lo que Proveen los Componentes
| Componente | Accesibilidad Incluida |
|---|---|
| DInput | Conecta el label con el input vía id; aria-invalid cuando es invalid |
| DButton | aria-busy durante la carga, loadingAriaLabel como nombre mientras carga, aria-disabled |
| DButtonIcon | Toma su nombre de aria-label; en desarrollo avisa si el botón queda sin nombre |
| DIcon | Oculto a los lectores de pantalla por defecto (aria-hidden). Con ariaLabel se expone como role="img" con ese nombre |
| DModal / DOffcanvas | role="dialog" y aria-modal="true". El nombre sale del título del Header (aria-labelledby) o de ariaLabel. El foco pasa al diálogo al abrir, Tab no sale del portal superior y Escape lo cierra salvo con staticBackdrop |
| DTabs | role="tablist", role="tab" con aria-selected y aria-controls; nombre con ariaLabel o ariaLabelledBy |
| DListGroup | <ul> por defecto. Los items enlace o botón se envuelven en un <li>. ariaLabel/ariaLabelledBy nombran la lista y ariaCurrent marca el item activo. En desarrollo avisa si un item <li> queda dentro de as="div" |
| DAlert | role="alert" por defecto. Con role="status" se anuncia sin interrumpir y con role="none" no es región viva |
| DSkeleton | Una región role="status" anuncia ariaLabel; las formas quedan ocultas a los lectores de pantalla |
| DSelect | Sin label ni aria-labelledby, usa un nombre genérico; ariaLabel lo reemplaza |
# Tu Responsabilidad
Debes proveer los props necesarios para accesibilidad completa:
- Inputs: provee
labelcon texto. Si el label no es solo texto (lleva un enlace o un icono), agregaaria-label(ariaLabelenDSelectyDInputSelect) - Botones de solo icono: provee
aria-labelenDButtonIcon - Iconos con significado propio: pasa
ariaLabelaDIcon; los decorativos no necesitan nada - Modales y paneles: incluye un título en el Header o pasa
ariaLabel. Al cerrar un portal el foco no vuelve al elemento que lo abrió: devuélvelo tú si el flujo lo necesita - Alertas: usa
role="status"para mensajes que no son críticos - Imágenes: siempre incluye texto
altdescriptivo
// ✅ Input accesible
<DInput id="search" label="Buscar cuentas" />
// ✅ Botón de solo icono accesible
<DButtonIcon icon="Trash2" aria-label="Eliminar elemento" />
// ✅ Icono con significado propio
<DIcon icon="AlertTriangle" ariaLabel="Saldo insuficiente" />
# Documentación y Soporte
Cada componente incluye documentación interactiva en nuestro Storybook:
- Documentación detallada de props y métodos
- Ejemplos de código interactivos
- Guías de mejores prácticas
Accede a la documentación completa en react.dynamicframework.dev (opens new window)