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

@misael703/elalba-ui

v0.3.3

Published

UI Kit React + TypeScript multi-marca, optimizado para Next.js. Tokens, componentes accesibles y branding configurable en runtime.

Readme

@misael703/elalba-ui

UI Kit React + TypeScript multi-marca, optimizado para Next.js (App Router).

Incluye tokens (colores, tipografía, espacio, radii, sombras, motion) y componentes accesibles para producto, admin, POS, dashboards y e-commerce. Diseñado para reusarse en múltiples marcas: la identidad visual (colores, fuentes, logos, defaults de marca) se configura en runtime, sin tocar el código del kit.

Brand-neutral por diseño: el primer consumer de este kit es El Alba / Patio Constructor, pero el paquete sirve a cualquier marca vía configureBrand(). Si necesitas un paquete completamente independiente con otra cara, ver la sección Forking / Rebrand.


Instalación

El paquete está publicado en npm público (registry.npmjs.org) bajo licencia MIT. No requiere autenticación.

npm install @misael703/elalba-ui
# pares
npm install react react-dom

Actualizar a una nueva versión

npm outdated @misael703/elalba-ui          # ver si hay versión nueva
npm update @misael703/elalba-ui            # sube hasta donde el rango permite
npm install @misael703/elalba-ui@latest    # fuerza la última (ignora rango)
npm install @misael703/[email protected]     # fija una versión específica

⚠️ Mientras el paquete esté en 0.x.x la API se considera inestable — cualquier minor puede traer breaking changes. Lee las release notes o el CHANGELOG antes de subir de minor o major.

Uso en Next.js (App Router)

1) Importa los estilos en app/layout.tsx (una sola vez):

import '@misael703/elalba-ui/styles.css';
import { ToastProvider } from '@misael703/elalba-ui';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="es">
      <body>
        <ToastProvider>{children}</ToastProvider>
      </body>
    </html>
  );
}

2) Usa los componentes:

import {
  Button, Card, CardBody, CardHeader,
  Input, FormField, Badge, Alert,
  Modal, Drawer, Tabs, TabList, Tab, TabPanel,
  Stepper, useToast,
} from '@misael703/elalba-ui';

export function NewOrder() {
  const { push } = useToast();
  return (
    <Card>
      <CardHeader>Nuevo pedido <Badge variant="success">Activo</Badge></CardHeader>
      <CardBody>
        {/* FormField genera automáticamente el id del input y enlaza
            label/aria-describedby con hint y error. */}
        <FormField label="SKU" required hint="Ej. ELT-12-AC">
          <Input placeholder="SKU del producto" />
        </FormField>
        <Button onClick={() => push({ title: 'Guardado', variant: 'success' })}>
          Guardar
        </Button>
      </CardBody>
    </Card>
  );
}

Componentes

| Categoría | Componentes | |---|---| | Acción | Button, Menu (dropdown accesible), Toggle, ToggleGroup | | Forms | Input, Textarea, Select, Checkbox (con indeterminate y invalid), Radio, Switch (con role="switch"), Label, FormField (auto-id + aria-describedby), NumberInput, Slider, MoneyInput (CLP), PhoneInput, TimePicker, TagInput, RadioGroup, CheckboxGroup, InputOTP | | Pickers | Combobox, MultiCombobox, DatePicker, DateRangePicker (con presets), FileUpload | | Command | CommandPalette + useCommandPalette({ hotkey: 'mod+k' }) | | Display | Card, Badge, Alert, Skeleton, Spinner, Kpi, EmptyState, Avatar, AvatarGroup, Stat, Progress, ProgressCircle | | Overlay | Modal, Drawer (focus-trap + ESC + backdrop + body scroll lock + portal a document.body), Popover, HoverCard, ContextMenu | | Layout | Tabs, Table, Tooltip, Stepper, Accordion, Breadcrumbs, Pagination, AppShell (con tema default/brand), PageHeader, Menubar, NavigationMenu, Resizable, Carousel | | Data | DataTable (sort + selección + skeleton + empty + error + stickyHeader + mobileLayout="cards" + ariaLabel + rowLabel), TablePagination | | Primitives | AspectRatio, Collapsible, ScrollArea, Separator, Slot | | Charts | LineChart, AreaChart, BarChart, DonutChart, Sparkline (wrappers de Recharts; pasar recharts={Recharts}) | | Feedback | ToastProvider + useToast() (con pausa al hover/focus) | | Hooks | useCommandPalette() |

Todos los componentes son type-safe, exponen forwardRef cuando aplica y aceptan className para extender estilos.

AppShell + Next.js Link

import Link from 'next/link';
import { AppShell } from '@misael703/elalba-ui';

<AppShell
  brand={<img src="/logo.svg" alt="El Alba" height={28} />}
  sections={[{
    label: 'Operación',
    items: [
      { id: 'home', label: 'Inicio', href: '/', active: true },
      { id: 'pedidos', label: 'Pedidos', href: '/pedidos', badge: 12 },
    ],
  }]}
  linkAs={(item, content, className) => (
    <Link href={item.href!} className={className} aria-current={item.active ? 'page' : undefined}>
      {content}
    </Link>
  )}
  topbar={<input className="input" placeholder="Buscar…" />}
>
  {/* page content */}
</AppShell>

DataTable con miles de filas (virtualization)

El kit no incluye virtualización built-in — para datasets grandes (>200 filas), envuelve el <DataTable> con react-window o @tanstack/react-virtual. Patrón base:

'use client';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
import { DataTable } from '@misael703/elalba-ui';

function VirtualTable({ rows, columns, rowKey }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 48,
    overscan: 10,
  });

  const visibleRows = virtualizer.getVirtualItems().map((vi) => rows[vi.index]);

  return (
    <div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        <DataTable
          columns={columns}
          rows={visibleRows}
          rowKey={rowKey}
          stickyHeader
        />
      </div>
    </div>
  );
}

Notas:

  • El kit pasa stickyHeader para que el thead se mantenga visible durante el scroll.
  • El <div> exterior con height fija es el contenedor de scroll que el virtualizer mide.
  • Si necesitás keyboard navigation entre filas virtuales, agregalo en el wrapper — es responsabilidad del consumer porque depende del UX deseado (Enter para abrir vs. expandir, etc.).

Para casos donde el sort/filter pasa por el server, mantené <DataTable rows={pageData}> con paginación normal y <TablePagination>. Virtualization rinde solo cuando todo el dataset cabe en memoria.

Charts (Recharts opcional)

@misael703/elalba-ui no incluye Recharts: lo recibe por prop. Instala recharts en tu app y pásalo:

'use client';
import * as Recharts from 'recharts';
import { LineChart } from '@misael703/elalba-ui';

<LineChart
  recharts={Recharts}
  data={data}
  categoryKey="mes"
  series={[{ key: 'ventas', label: 'Ventas' }]}
/>

Iconos

@misael703/elalba-ui exporta un set de íconos SVG (24×24, currentColor, stroke 1.75) listos para usar:

import { Search, ShoppingCart, ChevronRight } from '@misael703/elalba-ui';

<Button iconLeft={<ShoppingCart />}>Ver carro</Button>
<ChevronRight size={16} />

Heredan color del padre y aceptan size, strokeWidth, className y title (a11y).

Fuentes (opcional)

Si no usás next/font, podés cargar Integral CF + Metropolis empaquetadas con el kit:

// app/layout.tsx
import "@misael703/elalba-ui/fonts.css";
import "@misael703/elalba-ui/styles.css";

Tokens (CSS variables)

Los tokens viven en :root. La paleta de marca y los tokens semánticos están disponibles para overrides en tu app.

/* Brand */
--color-brand-orange: #ff671d;
--color-brand-blue:   #002f87;

/* Semánticos */
--bg-canvas, --bg-surface, --bg-subtle, --bg-inverse
--fg-default, --fg-muted, --fg-subtle
--border-default, --border-strong, --border-focus
--color-success, --color-warning, --color-danger, --color-info

/* Type */
--font-display, --font-body, --font-mono
--text-xs … --text-display

/* Espacio */
--space-1 … --space-16 (4px–64px)

/* Radii / sombras / motion */
--radius-sm … --radius-2xl
--shadow-sm … --shadow-xl
--duration-fast / --duration-base / --duration-slow
--ease-standard / --ease-emphasized

Úsalos directamente en tu CSS:

.my-card { background: var(--bg-surface); color: var(--fg-default); }

¿Solo querés los tokens (sin los componentes)? Importá únicamente tokens.css:

import '@misael703/elalba-ui/tokens.css';

Superficies invertidas (zonas con bg oscuro)

El kit resetea color en <p>, <h1><h6>, anchors y .caption para mantener consistencia tipográfica. Cuando metés esos elementos dentro de un footer / hero / sidebar oscuro, el color heredado del kit gana sobre el color del padre (especificidad). Para invertir el subtree completo en una línea, usá .surface-inverse:

<footer class="surface-inverse surface-inverse--brand">
  <h3 class="h3">Footer en navy</h3>
  <p>Texto blanco automáticamente — herencia vía CSS vars.</p>
  <p class="caption">Captions caen a blanco translúcido.</p>
  <p><a href="#">Anchors</a> usan el naranja de marca en hover.</p>
</footer>

Variantes:

  • .surface-inverse solo (sin bg) — para cuando vos pintás el fondo.
  • .surface-inverse--brand — bg var(--color-brand-blue).
  • .surface-inverse--dark — bg var(--color-blue-900).
  • [data-tone="inverse"] — atributo equivalente, sin agregar clases.

El mecanismo es re-scope de tokens (--fg-default, --fg-muted, --fg-subtle, --border-default, --fg-link*) en el subtree. Cualquier componente del kit que resuelva esos vars adentro hereda los valores claros sin tocar el componente. Cuidado: componentes con bg propio (Card, Modal, Button, Input) no se invierten — la utilidad apunta al texto y bordes del contenedor, no a re-skinear cada componente anidado.


Build local

npm install
npm run build           # emite dist/ con .mjs, .cjs, .d.ts y styles.css
npm test                # Vitest + Testing Library
npm run storybook       # http://localhost:6006
npm run build-storybook # genera storybook-static/ para deploy

Para consumir sin publicar (desarrollo iterativo entre repos): npm install file:../ui_kit.

Storybook

Cada componente tiene un *.stories.tsx con variantes interactivas y autodocs.

Tests

npm test corre Vitest + Testing Library en jsdom. Cobertura actual: 249 tests sobre todos los componentes públicos, incluyendo regresiones de a11y (FormField wiring, indeterminate, hover-pause, focus rings). Agregar tests es trivial — copia uno existente como referencia.


Releases & CI

Las publicaciones a npm están automatizadas con un workflow de GitHub Actions (.github/workflows/publish.yml) que se dispara al crear un release.

Autenticación: el workflow usa npm Trusted Publishing (OIDC) — no hay NPM_TOKEN ni secrets estáticos. npm confía directamente en GitHub Actions vía OpenID Connect, y cada publish se firma con --provenance para que el tarball quede criptográficamente atado al commit + workflow que lo produjo.

Configurado en: npmjs.com/package/@misael703/elalba-ui/access → Trusted Publisher → Misael703/elalba-ui workflow publish.yml.

Sacar una nueva versión

npm version patch     # o minor / major — bumpea package.json y crea tag
git push && git push --tags
gh release create vX.Y.Z --title "vX.Y.Z" --notes "..."

El workflow corre tests, build, y publica automáticamente. Sin OTP, sin tokens en tu máquina.

Publicar manualmente (caso excepcional)

Si necesitas publicar desde local (ej. CI caído):

npm publish --otp=<código de 2FA>

Requiere ~/.npmrc con tu token de npm y haber corrido npm login antes.


Forking / Rebrand

El kit está diseñado para reusarse en múltiples marcas. Hay 4 ejes que puedes ajustar de manera independiente.

1. Colores (sin tocar el kit)

Override los tokens desde tu app — el kit usa @layer así que tus reglas ganan automáticamente sin pelear con especificidad:

/* tu globals.css */
:root {
  --color-brand-blue: #6366f1;
  --color-brand-orange: #f59e0b;
  /* opcional: cambiar escalas completas si quieres */
}

2. Defaults de marca (currency, locale, name, logoBasePath)

El kit guarda solo lo que necesita para renderizar — identidad visual (name, logoBasePath) y formateo UI (currency, locale BCP 47 para Intl.NumberFormat / DateTimeFormat). No asume país.

Llama a configureBrand() una sola vez al arranque:

// app/layout.tsx (Next.js)
import { configureBrand } from '@misael703/elalba-ui';

configureBrand({
  name: 'Mi Marca',
  currency: 'USD',
  locale: 'en-US',
  logoBasePath: '/static/brand',
});

Después de configurar, <Logo>, <PriceDisplay>, <MoneyInput>, <CartDrawer> y <FreeShippingProgress> usan los nuevos defaults automáticamente. Las props siguen funcionando como override puntual.

Datos de país (regiones, prefijo telefónico, validaciones de RUT/SSN/etc.) los pasa el consumer como props o los modela en su <AddressForm fields={...}>. Ver "Idioma" (sección 5) y la story Commerce → AddressFormDemo para un ejemplo Chile-flavored.

3. Fuentes

Reemplaza los archivos en public/fonts/ y src/fonts/, y actualiza:

  • Las declaraciones @font-face en src/styles/index.css (líneas 9-37) y src/styles/fonts.css
  • Los tokens --font-display y --font-body en src/styles/index.css (~línea 117)

Todo el kit usa var(--font-display) y var(--font-body), no hace referencia directa a "Integral CF" ni "Metropolis" en componentes.

4. Logos

Reemplaza los archivos en public/assets/logos/ manteniendo el naming (logo-horizontal-light.svg, mark-dark.svg, etc.). Si usas otro path, configúralo:

configureBrand({ logoBasePath: '/static/mi-marca' });

5. Idioma (i18n)

Por defecto los strings del kit están en español ("Cerrar", "Sin datos", "Página anterior", etc.). Para una app en otro idioma, envuelve el árbol en un LocaleProvider con las claves que quieras traducir:

import { LocaleProvider } from '@misael703/elalba-ui';

<LocaleProvider
  messages={{
    'modal.close': 'Close',
    'table.empty': 'No data',
    'pagination.range': '{from}–{to} of {total}',
    'calendar.weekdays': ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  }}
>
  <App />
</LocaleProvider>

Cómo funciona:

  • El dict UiKitMessages está completamente tipado (TypeScript te autocompleta cada clave).
  • Las claves no provistas hacen fallback al default español (esMessages) — no hace falta declarar todo.
  • Templates con placeholders ("Eliminar {name}", "{n} sin leer", etc.) se resuelven con el helper format(tpl, vars) que también está exportado.

Sin LocaleProvider el kit funciona en español como siempre — el provider es opcional.

Resumen del esfuerzo

| Cambio | Esfuerzo | |---|---| | Colores | 5 min (override CSS) | | Defaults marca (name, currency, locale) | 5 min (configureBrand()) | | Fuente | 10 min (reemplazar archivos + tokens) | | Logos | 5 min (reemplazar archivos) | | Idioma | 15 min (envolver en LocaleProvider con un dict) |