# Extender Componentes
Aprende cómo crear componentes personalizados que envuelven y extienden los componentes de Dynamic Framework.
# Composición sobre Herencia
Dynamic Framework favorece la composición sobre la herencia. En lugar de extender clases, envuelve los componentes para agregar funcionalidad:
// ✗ Evitar: herencia
class MyButton extends DButton { }
// ✓ Preferir: composición
import { DButton } from '@dynamic-framework/ui-react';
function MyButton(props: ButtonProps) {
return <DButton {...props} />;
}
# Componentes Wrapper
Crea componentes wrapper para agregar funcionalidad como analytics, props por defecto o estilos personalizados.
# Wrapper Básico
// src/components/PrimaryButton.tsx
import { DButton, type DButtonProps } from '@dynamic-framework/ui-react';
interface PrimaryButtonProps extends Omit<DButtonProps, 'variant'> {
trackingLabel?: string;
}
export function PrimaryButton({
onClick,
trackingLabel,
...props
}: PrimaryButtonProps) {
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
if (trackingLabel) {
// Tu lógica de analytics
console.log('Track:', trackingLabel);
}
onClick?.(e);
};
return (
<DButton
variant="primary"
onClick={handleClick}
{...props}
/>
);
}
# Uso
<PrimaryButton
text="Enviar"
trackingLabel="submit-form"
onClick={() => handleSubmit()}
/>
# Componentes Compuestos
Crea componentes modulares usando el patrón de componentes compuestos de Dynamic.
# Ejemplo de Tarjeta de Producto
// src/components/ProductCard.tsx
import { DCard, DCurrencyText, DButton } from '@dynamic-framework/ui-react';
import type { ReactNode } from 'react';
interface Product {
id: string;
name: string;
description: string;
price: number;
}
interface ProductCardProps {
product: Product;
onSelect?: (id: string) => void;
}
export function ProductCard({ product, onSelect }: ProductCardProps) {
return (
<DCard>
<DCard.Header>
<h5 className="mb-0">{product.name}</h5>
</DCard.Header>
<DCard.Body>
<p className="text-muted">{product.description}</p>
<DCurrencyText value={product.price} />
</DCard.Body>
<DCard.Footer>
<DButton
text="Seleccionar"
variant="primary"
onClick={() => onSelect?.(product.id)}
/>
</DCard.Footer>
</DCard>
);
}
# Creando tu Propio Componente Compuesto
// src/components/InfoCard/index.tsx
import { DCard } from '@dynamic-framework/ui-react';
import type { ReactNode } from 'react';
interface InfoCardProps {
children: ReactNode;
className?: string;
}
interface InfoCardTitleProps {
children: ReactNode;
}
interface InfoCardContentProps {
children: ReactNode;
}
function InfoCard({ children, className }: InfoCardProps) {
return (
<DCard className={className}>
{children}
</DCard>
);
}
function InfoCardTitle({ children }: InfoCardTitleProps) {
return (
<DCard.Header>
<h5 className="mb-0">{children}</h5>
</DCard.Header>
);
}
function InfoCardContent({ children }: InfoCardContentProps) {
return <DCard.Body>{children}</DCard.Body>;
}
// Adjuntar sub-componentes
InfoCard.Title = InfoCardTitle;
InfoCard.Content = InfoCardContent;
export { InfoCard };
# Uso
<InfoCard>
<InfoCard.Title>Resumen de Cuenta</InfoCard.Title>
<InfoCard.Content>
<p>Detalles de tu cuenta aquí</p>
</InfoCard.Content>
</InfoCard>
# Hooks Personalizados
Crea hooks para encapsular lógica reutilizable.
# useToggle
// src/hooks/useToggle.ts
import { useState, useCallback } from 'react';
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue((v) => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
# Uso con Modal
import { DModal, DButton } from '@dynamic-framework/ui-react';
import { useToggle } from '@/hooks/useToggle';
function MyComponent() {
const modal = useToggle();
return (
<>
<DButton text="Abrir Modal" onClick={modal.setTrue} />
<DModal isOpen={modal.value} onClose={modal.setFalse}>
<DModal.Header>
<h5>Título</h5>
</DModal.Header>
<DModal.Body>
<p>Contenido del modal</p>
</DModal.Body>
</DModal>
</>
);
}
# Testing de Componentes
Usa Vitest y React Testing Library para probar tus componentes extendidos.
# Configuración
npm install -D vitest @testing-library/react @testing-library/jest-dom
# Ejemplo de Test
// src/components/__tests__/PrimaryButton.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { PrimaryButton } from '../PrimaryButton';
describe('PrimaryButton', () => {
it('renderiza con texto', () => {
render(<PrimaryButton text="Hazme clic" />);
expect(screen.getByText('Hazme clic')).toBeInTheDocument();
});
it('llama onClick cuando se hace clic', () => {
const handleClick = vi.fn();
render(<PrimaryButton text="Clic" onClick={handleClick} />);
fireEvent.click(screen.getByText('Clic'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('registra tracking label cuando se proporciona', () => {
const consoleSpy = vi.spyOn(console, 'log');
render(
<PrimaryButton text="Rastrear" trackingLabel="test-button" />
);
fireEvent.click(screen.getByText('Rastrear'));
expect(consoleSpy).toHaveBeenCalledWith('Track:', 'test-button');
});
});
# Mejores Prácticas
- Preservar la API original - Tu wrapper debe aceptar todas las props que acepta el componente original
- Usar TypeScript - Extiende los tipos de props del componente para type safety
- Mantenerlo simple - Solo envuelve cuando necesites agregar valor real
- Testear tus componentes - Asegúrate de que los wrappers se comporten correctamente
- Documentar props personalizadas - Deja claro qué agrega tu wrapper