JSPM

@jamx-framework/ui

1.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 14
  • Score
    100M100P100Q62757F
  • License MIT

JAMX Framework — UI Component Library

Package Exports

  • @jamx-framework/ui

Readme

@jamx-framework/ui

Descripción

Biblioteca de componentes UI para JAMX Framework. Proporciona un conjunto de componentes básicos (layout, tipografía, interactivos, feedback) y tokens de diseño (colores, espaciado, tipografía) para construir interfaces de usuario de forma consistente y type-safe. Todos los componentes están diseñados para funcionar con el renderer SSR de JAMX y son completamente personalizables.

Cómo funciona

La biblioteca implementa componentes funcionales que retornan elementos JSX usando el runtime de @jamx-framework/renderer. Cada componente acepta props tipadas y aplica estilos mediante tokens o estilos en línea. Los tokens proporcionan valores consistentes para colores, espaciado y tipografía que pueden ser personalizados.

Componentes principales

Layout

  • Box: Contenedor genérico con padding, margin, display, etc.
  • Stack: Contenedor con layout vertical/horizontal y gap
  • Grid: Grid system con columnas y gap

Typography

  • Text: Texto con variantes (body, caption, etc.)
  • Heading: Encabezados h1-h6 con tokens de tamaño

Interactive

  • Button: Botón con variantes (primary, secondary, etc.)
  • Link: Enlace con estilos consistentes
  • Input: Campo de texto con validación visual

Feedback

  • Alert: Mensajes de alerta (info, success, warning, error)
  • Badge: Etiquetas y contadores

Tokens

  • colors: Paleta de colores (primario, neutros, semánticos)
  • spacing: Espaciado (margins, paddings)
  • typography: Tamaños, pesos, familias de fuente

Uso básico

Instalación

pnpm add @jamx-framework/ui

Importar componentes

import { Box, Stack, Heading, Text, Button, Alert } from "@jamx-framework/ui";
import { jsx } from "@jamx-framework/renderer";

Ejemplo: Página simple

// src/pages/index.page.tsx
import { jsx, type RenderContext } from "@jamx-framework/renderer";
import { Heading, Text, Button, Stack } from "@jamx-framework/ui";

export default {
  render(_ctx: RenderContext) {
    return jsx("div", {
      class: "container",
      children: [
        jsx(Heading, { level: 1, children: "Bienvenido a JAMX" }),
        jsx(Text, {
          variant: "body",
          children: "Esta es una aplicación de ejemplo",
        }),
        jsx(Stack, {
          direction: "row",
          gap: "16px",
          children: [
            jsx(Button, { variant: "primary", children: "Empezar" }),
            jsx(Button, { variant: "secondary", children: "Aprender más" }),
          ],
        }),
      ],
    });
  },
};

Ejemplo: Formulario

import { jsx } from "@jamx-framework/renderer";
import { Box, Stack, Input, Button, Alert } from "@jamx-framework/ui";

export default {
  render(ctx) {
    return jsx("form", {
      onSubmit: handleSubmit,
      children: [
        jsx(Input, {
          name: "email",
          type: "email",
          placeholder: "Correo electrónico",
          required: true,
        }),
        jsx(Input, {
          name: "password",
          type: "password",
          placeholder: "Contraseña",
          required: true,
        }),
        jsx(Button, {
          type: "submit",
          variant: "primary",
          children: "Iniciar sesión",
        }),
        jsx(Alert, { variant: "error", children: "Credenciales inválidas" }),
      ],
    });
  },
};

Ejemplo: Card component

import { jsx } from "@jamx-framework/renderer";
import { Box, Heading, Text, Button } from "@jamx-framework/ui";

function Card(props: { title: string; children: string }) {
  return jsx(Box, {
    as: "article",
    padding: "24px",
    style: { border: "1px solid #e5e7eb", borderRadius: "8px" },
    children: [
      jsx(Heading, { level: 2, children: props.title }),
      jsx(Text, { variant: "body", children: props.children }),
      jsx(Button, { variant: "primary", children: "Acción" }),
    ],
  });
}

Usar tokens

import { colors, spacing, typography } from "@jamx-framework/ui";

// Colores
const primaryColor = colors.primary[500]; // #3b82f6
const successColor = colors.success[500]; // #22c55e

// Espaciado
const padding = spacing[4]; // 16px
const margin = spacing[8]; // 32px

// Tipografía
const fontSize = typography.fontSizes.lg; // 18px
const fontWeight = typography.fontWeights.semibold; // 600

API Reference

Componentes

Box

interface BoxProps extends BaseProps {
  as?: string; // elemento HTML (default: 'div')
  padding?: string; // padding (ej: '16px', 'spacing[4]')
  margin?: string; // margin
  display?: "block" | "flex" | "grid" | "inline" | "inline-block" | "none";
  width?: string; // ancho (ej: '100%', '300px')
  height?: string; // alto
}

Contenedor genérico. Renderiza cualquier elemento HTML con estilos aplicados.

Ejemplo:

jsx(Box, {
  as: "section",
  padding: spacing[6],
  display: "flex",
  children: "Contenido",
});

Stack

interface StackProps extends BaseProps {
  direction?: "row" | "column"; // dirección del layout
  gap?: string; // espacio entre hijos
  align?: "start" | "center" | "end" | "stretch";
  justify?: "start" | "center" | "end" | "between" | "around";
  wrap?: boolean; // permitir wrap (solo row)
}

Contenedor con layout flex y gap automático.

Ejemplo:

jsx(Stack, {
  direction: "row",
  gap: spacing[4],
  align: "center",
  children: [item1, item2, item3],
});

Grid

interface GridProps extends BaseProps {
  columns?: number | string; // número de columnas o 'auto-fit'
  gap?: string; // espacio entre celdas
}

Grid system basado en CSS Grid.

Ejemplo:

jsx(Grid, {
  columns: 3,
  gap: spacing[4],
  children: [item1, item2, item3, item4, item5, item6],
});

Text

interface TextProps extends BaseProps {
  variant?: "body" | "caption" | "label" | "helper";
  size?: "sm" | "md" | "lg"; // override de tamaño
  weight?: "normal" | "medium" | "semibold" | "bold";
  color?: string; // color personalizado
}

Componente de texto con variantes predefinidas.

Ejemplo:

jsx(Text, { variant: "body", children: "Texto normal" });
jsx(Text, { variant: "caption", children: "Texto pequeño" });

Heading

interface HeadingProps extends BaseProps {
  level: 1 | 2 | 3 | 4 | 5 | 6; // nivel del encabezado (h1-h6)
  size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"; // override
  weight?: "normal" | "medium" | "semibold" | "bold";
}

Encabezados con jerarquía semántica.

Ejemplo:

jsx(Heading, { level: 1, children: "Título principal" });
jsx(Heading, { level: 2, size: "lg", children: "Subtítulo" });

Button

interface ButtonProps extends BaseProps {
  variant?: "primary" | "secondary" | "ghost" | "danger";
  size?: "sm" | "md" | "lg";
  type?: "button" | "submit" | "reset";
  disabled?: boolean;
  loading?: boolean; // mostrar spinner
  fullWidth?: boolean; // ancho 100%
}

Botón con estilos consistentes.

Ejemplo:

jsx(Button, {
  variant: "primary",
  size: "md",
  onClick: handleClick,
  children: "Guardar",
});
interface LinkProps extends BaseProps {
  href: string;
  external?: boolean; // abrir en nueva pestaña
  underline?: boolean; // subrayado
}

Enlace con estilos de link.

Ejemplo:

jsx(Link, {
  href: "/about",
  children: "Acerca de",
});

jsx(Link, {
  href: "https://example.com",
  external: true,
  children: "Ejemplo externo",
});

Input

interface InputProps extends BaseProps {
  type?: "text" | "email" | "password" | "number" | "tel" | "url";
  name?: string;
  placeholder?: string;
  value?: string;
  defaultValue?: string;
  disabled?: boolean;
  required?: boolean;
  error?: boolean; // estado de error
  helperText?: string; // texto de ayuda
}

Campo de texto con validación visual.

Ejemplo:

jsx(Input, {
  name: "email",
  type: "email",
  placeholder: "Correo electrónico",
  required: true,
  error: !isValid,
  helperText: isValid ? "" : "Correo inválido",
});

Alert

interface AlertProps extends BaseProps {
  variant?: "info" | "success" | "warning" | "error";
  title?: string; // título opcional
  dismissible?: boolean; // mostrar botón cerrar
  onDismiss?: () => void;
}

Mensaje de alerta con variantes semánticas.

Ejemplo:

jsx(Alert, {
  variant: "success",
  title: "Éxito",
  children: "Operación completada",
});

Badge

interface BadgeProps extends BaseProps {
  variant?: "default" | "primary" | "success" | "warning" | "error";
  size?: "sm" | "md" | "lg";
  count?: number; // para contadores
  dot?: boolean; // punto de notificación
}

Etiqueta o contador.

Ejemplo:

jsx(Badge, { variant: "primary", children: "Nuevo" });
jsx(Badge, { count: 5, children: "Notificaciones" });

Tokens

colors

export const colors = {
  // Paleta primaria (configurable)
  primary: {
    50: "#eff6ff",
    100: "#dbeafe",
    200: "#bfdbfe",
    300: "#93c5fd",
    400: "#60a5fa",
    500: "#3b82f6", // primario por defecto
    600: "#2563eb",
    700: "#1d4ed8",
    800: "#1e40af",
    900: "#1e3a8a",
  },

  // Colores semánticos
  success: {
    /* ... */
  },
  warning: {
    /* ... */
  },
  error: {
    /* ... */
  },
  info: {
    /* ... */
  },

  // Escala de grises
  gray: {
    50: "#f9fafb",
    100: "#f3f4f6",
    200: "#e5e7eb",
    300: "#d1d5db",
    400: "#9ca3af",
    500: "#6b7280",
    600: "#4b5563",
    700: "#374151",
    800: "#1f2937",
    900: "#111827",
  },
};

spacing

export const spacing = {
  0: "0px",
  1: "4px",
  2: "8px",
  3: "12px",
  4: "16px",
  5: "20px",
  6: "24px",
  8: "32px",
  10: "40px",
  12: "48px",
  16: "64px",
  24: "96px",
  32: "128px",
};

typography

export const typography = {
  fontSizes: {
    xs: "12px",
    sm: "14px",
    md: "16px",
    lg: "18px",
    xl: "20px",
    "2xl": "24px",
    "3xl": "30px",
    "4xl": "36px",
    "5xl": "48px",
    "6xl": "60px",
  },

  fontWeights: {
    normal: 400,
    medium: 500,
    semibold: 600,
    bold: 700,
  },

  lineHeights: {
    tight: 1.25,
    normal: 1.5,
    relaxed: 1.75,
  },

  fontFamilies: {
    sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    mono: 'ui-monospace, SFMono-Regular, "SF Mono", monospace',
  },
};

Utilidades

cx

function cx(...classNames: (string | undefined | null | false)[]): string;

Combina classNames, filtrando falsy values.

Ejemplo:

const className = cx("base-class", condition && "conditional", customClass);

sp

function sp(token: SpacingToken): string;

Helper para obtener valores de spacing.

Ejemplo:

import { sp } from "@jamx-framework/ui";

jsx(Box, { padding: sp(4) }); // '16px'

Tipos

BaseProps

interface BaseProps {
  class?: string; // clase CSS adicional
  className?: string; // alias de class
  style?: Record<string, string>; // estilos en línea
  testId?: string; // para testing (data-testid)
  id?: string; // ID HTML
  [key: string]: unknown; // otras props pasan al elemento
}

Props base que todos los componentes aceptan.

ColorVariant

type ColorVariant =
  | "primary"
  | "secondary"
  | "success"
  | "warning"
  | "error"
  | "info";

Size

type Size = "sm" | "md" | "lg" | "xl";

Personalización

Override de tokens

Puedes crear tus propios tokens:

// theme.ts
import { createTheme } from "@jamx-framework/ui";

export const myTheme = createTheme({
  colors: {
    primary: {
      500: "#ff5722", // naranja personalizado
    },
  },
  spacing: {
    4: "20px", // spacing personalizado
  },
  typography: {
    fontSizes: {
      lg: "20px",
    },
  },
});

Estilos personalizados

Todos los componentes aceptan style y className:

jsx(Button, {
  variant: "primary",
  style: { borderRadius: "9999px" },
  className: "my-custom-button",
  children: "Botón",
});

CSS Modules

Puedes usar CSS modules con los componentes:

import styles from "./MyComponent.module.css";

jsx(Box, {
  class: styles.container,
  children: jsx(Button, { class: styles.button, children: "Click" }),
});

Ejemplos completos

Layout con Stack y Grid

import { jsx } from "@jamx-framework/renderer";
import { Box, Stack, Grid, Heading, Text, Card } from "@jamx-framework/ui";

export default {
  render(ctx) {
    return jsx(Box, {
      as: "main",
      padding: spacing[6],
      children: [
        jsx(Heading, { level: 1, children: "Dashboard" }),

        // Grid de cards
        jsx(Grid, {
          columns: { sm: 1, md: 2, lg: 3 },
          gap: spacing[4],
          children: [
            jsx(Card, { title: "Ventas", children: "$12,345" }),
            jsx(Card, { title: "Usuarios", children: "1,234" }),
            jsx(Card, { title: "Pedidos", children: "567" }),
          ],
        }),

        // Stack vertical
        jsx(Stack, {
          direction: "column",
          gap: spacing[4],
          children: [
            jsx(Heading, { level: 2, children: "Actividad reciente" }),
            jsx(Text, { variant: "body", children: "No hay actividad" }),
          ],
        }),
      ],
    });
  },
};

Formulario con validación

import { jsx, useState } from "@jamx-framework/renderer";
import { Box, Stack, Input, Button, Alert } from "@jamx-framework/ui";

export default {
  async render(ctx) {
    return jsx(Box, {
      as: "form",
      onSubmit: handleSubmit,
      children: [
        jsx(Input, {
          name: "email",
          type: "email",
          placeholder: "Correo electrónico",
          value: email,
          onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
            setEmail(e.target.value),
          error: !!error,
          helperText: error,
        }),
        jsx(Button, {
          type: "submit",
          variant: "primary",
          disabled: !email,
          children: "Enviar",
        }),
      ],
    });
  },
};

Componente Card reutilizable

import { jsx } from "@jamx-framework/renderer";
import { Box, Stack, Heading, Text, Button } from "@jamx-framework/ui";

interface CardProps {
  title: string;
  description: string;
  actionLabel?: string;
  onAction?: () => void;
}

function Card({ title, description, actionLabel, onAction }: CardProps) {
  return jsx(Box, {
    as: "article",
    padding: spacing[6],
    style: {
      border: `1px solid ${colors.gray[200]}`,
      borderRadius: "8px",
      boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
    },
    children: [
      jsx(Heading, { level: 3, children: title }),
      jsx(Text, {
        variant: "body",
        style: { marginTop: spacing[3] },
        children: description,
      }),
      actionLabel &&
        jsx(Button, {
          variant: "primary",
          onClick: onAction,
          style: { marginTop: spacing[4] },
          children: actionLabel,
        }),
    ],
  });
}

Alertas con dismiss

import { jsx, useState } from "@jamx-framework/renderer";
import { Alert, Button, Stack } from "@jamx-framework/ui";

export default {
  render(ctx) {
    const [showSuccess, setShowSuccess] = useState(true);

    return jsx(Stack, {
      direction: "column",
      gap: spacing[3],
      children: [
        showSuccess &&
          jsx(Alert, {
            variant: "success",
            title: "Éxito",
            dismissible: true,
            onDismiss: () => setShowSuccess(false),
            children: "Cambios guardados correctamente",
          }),

        jsx(Button, {
          variant: "primary",
          onClick: () => setShowSuccess(true),
          children: "Mostrar alerta",
        }),
      ],
    });
  },
};

Badge con contador

import { jsx } from "@jamx-framework/renderer";
import { Badge, Button, Stack } from "@jamx-framework/ui";

export default {
  render(ctx) {
    return jsx(Stack, {
      direction: "row",
      gap: spacing[4],
      children: [
        jsx(Button, { variant: "primary", children: "Inbox" }),
        jsx(Badge, { count: 5, children: "Notificaciones" }),
        jsx(Badge, { variant: "success", children: "Activo" }),
        jsx(Badge, { variant: "error", children: "Error" }),
      ],
    });
  },
};

Diseño y tokens

Sistema de diseño

La biblioteca sigue un sistema de diseño basado en tokens:

  • Colores: Escala de 50-900 + colores semánticos (success, warning, error, info)
  • Espaciado: Escala de 0-32 (0px a 128px)
  • Tipografía: Tamaños xs-6xl, pesos normal/semibold/bold, line-heights

Customización global

Para cambiar el tema global, puedes:

  1. Modificar los archivos de tokens (si tienes acceso al código fuente)
  2. Usar CSS variables y sobreescribir en tu hoja de estilos:
:root {
  --color-primary-500: #ff5722;
  --spacing-4: 20px;
  --font-size-lg: 20px;
}
  1. Crear un wrapper de componentes con tus estilos por defecto:
function MyButton(props: ButtonProps) {
  return jsx(Button, {
    ...props,
    style: { borderRadius: "9999px", ...props.style },
  });
}

Testing

Tests unitarios

import { describe, it, expect } from "vitest";
import { render } from "@jamx-framework/testing";
import { Button, Alert } from "@jamx-framework/ui";

describe("Button", () => {
  it("should render with primary variant", () => {
    const html = render(jsx(Button, { variant: "primary", children: "Click" }));
    expect(html).toContain('class="btn btn-primary"');
  });

  it("should be disabled when disabled prop", () => {
    const html = render(jsx(Button, { disabled: true, children: "Click" }));
    expect(html).toContain("disabled");
  });
});

Tests de integración

import { createTestServer } from "@jamx-framework/testing";
import { Button } from "@jamx-framework/ui";

// Testear que los componentes se renderizan correctamente en el servidor

Accesibilidad

Los componentes están diseñados con accesibilidad en mente:

  • Box: Usa as para cambiar elemento semántico (section, article, etc.)
  • Button: Usa <button> nativo con type correcto
  • Link: Usa <a> con href
  • Input: Usa <input> con label implícito (placeholder) o explícito
  • Heading: Jerarquía h1-h6 semántica
  • Alert: Usa role="alert" automáticamente

Ejemplo accesible

jsx(Box, { as: "main" }, [
  jsx(Heading, { level: 1 }, "Título principal"),
  jsx(Input, {
    name: "email",
    type: "email",
    placeholder: "Correo electrónico",
    "aria-label": "Correo electrónico",
    required: true,
  }),
  jsx(Button, { type: "submit" }, "Enviar"),
]);

Limitaciones

Sin CSS framework integrado

  • No incluye Tailwind, CSS Modules, o styled-components
  • Los estilos son en línea o className manual
  • Se recomienda usar con un sistema de estilos externo

Componentes básicos

  • Solo incluye componentes fundamentales
  • No tiene componentes complejos (tables, modals, dropdowns, etc.)
  • Para componentes avanzados, extender o usar otra biblioteca

Sin JavaScript interactivo

  • Los componentes son estáticos (no tienen estado interno)
  • Para interactividad, usar hooks de JAMX o estado externo

Renderer dependency

  • Depende de @jamx-framework/renderer
  • No funciona con React, Vue, etc.

Buenas prácticas

1. Usar tokens en lugar de valores hardcodeados

// ✅ Bien
jsx(Box, { padding: spacing[4] });

// ❌ No
jsx(Box, { padding: "16px" });

2. Componer componentes

// ✅ Bien: crear componentes compuestos
function UserCard({ user }) {
  return jsx(Box, {
    as: "article",
    children: [
      jsx(Heading, { level: 3, children: user.name }),
      jsx(Text, { variant: "body", children: user.email }),
      jsx(Button, { variant: "primary", children: "Ver perfil" }),
    ],
  });
}

// ❌ No: repetir estructura

3. Usar variantes semánticas

// ✅ Bien: usar variantes apropiadas
jsx(Alert, { variant: "error", children: "Error crítico" });
jsx(Button, { variant: "primary", children: "Guardar" });

// ❌ No: inventar variantes
jsx(Alert, { variant: "red", children: "Error" });

4. Proporcionar fallbacks

// ✅ Bien: manejar datos undefined
jsx(Text, { variant: "body", children: user?.name ?? "Anónimo" });

// ❌ No: asumir datos
jsx(Text, { variant: "body", children: user.name });

5. Testear con testId

jsx(Button, {
  testId: "login-submit",
  children: "Iniciar sesión",
});

// En tests
const button = screen.getByTestId("login-submit");
expect(button).toBeInTheDocument();

Integración con otros paquetes

Con @jamx-framework/renderer

import { jsx } from "@jamx-framework/renderer";
import { Box, Text } from "@jamx-framework/ui";

const page = {
  render(ctx) {
    return jsx(Box, {
      padding: "16px",
      children: [jsx(Text, { children: "Hello" })],
    });
  },
};

Con @jamx-framework/server

import { JamxServer } from "@jamx-framework/server";
import { Box, Heading } from "@jamx-framework/ui";

const server = await JamxServer.create();

server.use(async (req, res) => {
  const html = render(
    jsx(Box, {}, jsx(Heading, { level: 1, children: "Hello" })),
  );
  res.send(html);
});

Con @jamx-framework/testing

import { render } from "@jamx-framework/testing";
import { Button } from "@jamx-framework/ui";

test("renders button", () => {
  const html = render(jsx(Button, { children: "Click" }));
  expect(html).toContain("Click");
});

Roadmap futuro

  • Componentes adicionales: Select, Checkbox, Radio, Modal, Dropdown, Table
  • Soporte para dark mode con tokens de color
  • Animaciones y transiciones
  • Accesibilidad mejorada (ARIA, focus management)
  • Internacionalización (i18n) integrada
  • Soporte para CSS-in-JS (emotion, styled-components)
  • Componentes de formulario completos (Form, Field, FieldGroup)
  • Data display: List, Card, Avatar, AvatarGroup
  • Navigation: Tabs, Breadcrumb, Pagination
  • Layout: Divider, Spacer, Container

Contribución

Para añadir un nuevo componente:

  1. Crear archivo en src/components/<category>/<ComponentName>.ts
  2. Definir interface de props que extienda BaseProps
  3. Implementar componente como función que retorna JamxElement
  4. Exportar desde src/index.ts
  5. Añadir tests en tests/unit/components/
  6. Documentar en este README

Archivos importantes

  • src/index.ts - Punto de entrada
  • src/components/ - Componentes organizados por categoría
  • src/tokens/ - Tokens de diseño
  • src/types.ts - Tipos compartidos
  • tests/unit/components/ - Tests de componentes

Dependencias

  • @jamx-framework/renderer - Para jsx y tipos
  • @types/node - Tipos de Node.js
  • vitest - Testing

Scripts del paquete

  • pnpm build - Compila TypeScript
  • pnpm dev - Watch mode
  • pnpm test - Tests unitarios
  • pnpm test:watch - Tests en watch
  • pnpm type-check - Verificar tipos
  • pnpm clean - Limpiar build

Ejemplo completo de aplicación

// src/pages/dashboard.page.tsx
import { jsx } from "@jamx-framework/renderer";
import {
  Box,
  Stack,
  Grid,
  Heading,
  Text,
  Button,
  Alert,
  Badge,
  Input,
} from "@jamx-framework/ui";
import { colors, spacing, typography } from "@jamx-framework/ui";

export default {
  render(ctx) {
    return jsx(Box, { as: "div", padding: spacing[6] }, [
      // Header
      jsx(Stack, {
        direction: "row",
        justify: "between",
        align: "center",
        style: { marginBottom: spacing[8] },
        children: [
          jsx(Heading, { level: 1, children: "Dashboard" }),
          jsx(Button, { variant: "primary", children: "Nuevo" }),
        ],
      }),

      // Stats grid
      jsx(Grid, {
        columns: { sm: 1, md: 2, lg: 4 },
        gap: spacing[4],
        children: [
          jsx(Box, {
            as: "div",
            padding: spacing[4],
            style: { background: colors.gray[50] },
            children: [
              jsx(Text, { variant: "caption", children: "Usuarios" }),
              jsx(Heading, { level: 2, children: "1,234" }),
            ],
          }),
          jsx(Box, {
            as: "div",
            padding: spacing[4],
            style: { background: colors.gray[50] },
            children: [
              jsx(Text, { variant: "caption", children: "Ingresos" }),
              jsx(Heading, { level: 2, children: "$12,345" }),
            ],
          }),
        ],
      }),

      // Alert
      jsx(Alert, {
        variant: "info",
        title: "Bienvenido",
        children: "Esta es tu dashboard personal",
      }),

      // Search
      jsx(Input, {
        placeholder: "Buscar...",
        style: { maxWidth: "300px" },
      }),
    ]);
  },
};

Conclusión

@jamx-framework/ui proporciona un conjunto minimalista pero poderoso de componentes y tokens para construir interfaces de usuario en JAMX. Su diseño modular y type-safe lo hace ideal para aplicaciones que necesitan consistencia visual sin el overhead de bibliotecas grandes.