npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jamx-framework/ui

v1.0.1

Published

JAMX Framework — UI Component Library

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 } from '@jamx-framework/renderer';
import { Box, Stack, Heading, Text, Button } from '@jamx-framework/ui';

export default {
  render(ctx) {
    return jsx('div', { class: 'container' }, [
      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' }, [
        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 }, [
      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, Stack, 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' },
  }, [
    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',
});

Link

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] }, [
      jsx(Heading, { level: 1, children: 'Dashboard' }),

      // Grid de cards
      jsx(Grid, {
        columns: { sm: 1, md: 2, lg: 3 },
        gap: spacing[4],
      }, [
        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] }, [
        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) {
    const [email, setEmail] = useState('');
    const [error, setError] = useState('');

    const handleSubmit = async () => {
      if (!email.includes('@')) {
        setError('Email inválido');
        return;
      }
      await submitForm({ email });
    };

    return jsx(Box, { as: 'form', onSubmit: handleSubmit }, [
      jsx(Input, {
        name: 'email',
        type: 'email',
        placeholder: 'Correo electrónico',
        value: email,
        onChange: (e) => 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)',
    },
  }, [
    jsx(Heading, { level: 3, children: title }),
    jsx(Text, { variant: 'body', children: description, style: { marginTop: spacing[3] } }),
    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] }, [
      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] }, [
      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' }, [
    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' }, [
      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] },
      }, [
        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],
      }, [
        jsx(Box, { as: 'div', padding: spacing[4], style: { background: colors.gray[50] } }, [
          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] } }, [
          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' },
      }),
    ]);
  },
};

Comparación con otras bibliotecas

| Característica | JAMX UI | Material-UI | Chakra UI | Tailwind CSS | |----------------|---------|-------------|-----------|-------------| | Framework | JAMX | React | React | Agnostico | | Componentes | Básicos | Completo | Completo | Ninguno | | Tokens | Sí | Sí | Sí | No | | SSR | Sí | Sí | Sí | Sí | | TypeScript | Nativo | Nativo | Nativo | No | | Tamaño | Pequeño | Grande | Medio | Variable |

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.