Primeros pasos
Hay tres caminos, de menos a más trabajo: una aplicación nueva con create, un proyecto que ya existe con init, o montarlo a mano. Los dos primeros hacen lo mismo que el tercero, y doctor comprueba el resultado en cualquiera de los tres.
Qué hace falta
| Qué | Versión |
|---|---|
| React y React DOM | ^18.3.0 || ^19.0.0 |
| Node.js | >=20.19 |
| Next (opcional) | ^14.0.0 || ^15.0.0 || ^16.0.0 |
| react-hook-form (opcional) | ^7.54.0 |
Y nada más: el paquete tiene 0 dependencias al ejecutarse. react-hook-form solo hace falta para @amset/ui/form, y next para @amset/ui/next.
Se instala del registro de paquetes del GitLab de AMSET: la versión pública en npm está prevista y todavía no está publicada. Una línea en el .npmrc del proyecto dice de dónde sale el paquete, y el token de lectura va en el tuyo:
# .npmrc del repositorio (se commitea)@amset:registry=https://gitlab.amset.tech/api/v4/packages/npm/ # ~/.npmrc, el tuyo, fuera del repositorio (nunca se commitea)//gitlab.amset.tech/api/v4/packages/npm/:_authToken=<token con read_package_registry>- Un 401 no quiere decir «ese paquete no existe»: quiere decir que el token no vale. El registro contesta 401 a todo lo que llega sin credenciales.
- Un nombre mal escrito no da error: lo que este registro no tiene lo reenvía a npm público, así que
@amset/iuinstalaría el paquete de un desconocido.
Una aplicación nueva
npx amset-ui create mi-app # Next con App Routernpx amset-ui create mi-panel --template vite # ViteSale con TypeScript estricto, ESLint, el armazón con su menú, una página por fichero, tu tema y el proveedor ya montados. Sin Tailwind.
Un proyecto que ya existe
npx amset-ui init --brand "#2F9E44" --installnpx amset-ui doctorinit pone la línea del registro, la hoja base en el orden de capas correcto, tu tema y el proveedor con el enrutador de Next, sin pisar nada tuyo. doctor comprueba que todo está en su sitio y sale con 1 si algo falla.
A mano
npm install @amset/uiNo hay que importar ninguna hoja de estilos: cada componente trae la suya, y el empaquetador se lleva solo la de lo que usas. Las de la biblioteca van en la capa @layer amset-ui, así que cualquier CSS tuyo sin capa les gana sin !important. Si además importas la hoja entera (@amset/ui/styles.css), pagas las reglas dos veces: es una cosa o la otra.
El tema va en data-theme="light" o "dark" sobre <html>; sin el atributo manda el sistema operativo, y al imprimir siempre sale el claro. La densidad, en data-density. Los dos van sin prefijo: si tu proyecto ya usa data-theme con otros valores, la biblioteca no aplica ningún tema.
Sin proveedor, la biblioteca habla en castellano (es-ES). En una aplicación en otro idioma, AmsetProvider con su locale: textos, fechas, números y moneda.
Next con App Router
// app/layout.tsx (componente de servidor)import { AmsetProvider, INITIAL_THEME_SCRIPT } from '@amset/ui'import { NextRouterProvider } from '@amset/ui/next' export default function Layout({ children }: { readonly children: React.ReactNode }) { return ( <html lang="es" suppressHydrationWarning> <head> <script dangerouslySetInnerHTML={{ __html: INITIAL_THEME_SCRIPT }} /> </head> <body> <AmsetProvider locale="es-ES"> <NextRouterProvider>{children}</NextRouterProvider> </AmsetProvider> </body> </html> )}- Los enlaces. Sin un enrutador,
LinkyButtonLinknavegan recargando la página entera, sin ningún error.NextRouterProviderlos lleva porrouter.pushy los precarga al pasar el ratón o el foco, comonext/link. - Servidor y cliente. Los componentes sin interacción no llevan
'use client'; los demás, lo más abajo posible. Ninguno lee «hoy» ni «ahora» mientras se pinta, así que el HTML del servidor y el del navegador coinciden al hidratar. - El tema sin parpadeo:
INITIAL_THEME_SCRIPTen el<head>aplica el tema guardado antes del primer pintado. - SEO:
@amset/ui/seoes una subruta de servidor, sin dependencias:pageMetadata(elMetadatade Next),JsonLdycheckSeo.
Next con Pages Router
// next.config.tsexport default { transpilePackages: ['@amset/ui'] } // pages/_app.tsximport { AmsetProvider } from '@amset/ui'import { NextPagesRouterProvider } from '@amset/ui/next/pages' export default function App({ Component, pageProps }) { return ( <AmsetProvider locale="es-ES"> <NextPagesRouterProvider> <Component {...pageProps} /> </NextPagesRouterProvider> </AmsetProvider> )}Con el Pages Router hace falta transpilePackages: ['@amset/ui']: sin él, Next carga el paquete con Node, que no sabe qué hacer con los .css que importa cada componente, y el build falla. El App Router, Vite y webpack no necesitan nada.
Vite (y cualquier otro enrutador)
// src/main.tsx, con React Routerimport { AmsetProvider, RouterProvider } from '@amset/ui'import { useNavigate } from 'react-router' function Enrutado({ children }: { readonly children: React.ReactNode }) { const navigate = useNavigate() return <RouterProvider navigate={(href) => { void navigate(href) }}>{children}</RouterProvider>}RouterProvider recibe la función con la que navega tu enrutador. Con React Router es navigate; con otro, la que use. Sin él, los enlaces recargan la página.
React 18 y 19
Funciona con React ^18.3.0 || ^19.0.0, y las dos versiones se prueban: además de la batería con React 19, la CI pinta cada ficha con React 18 de verdad y falla con cualquier excepción o aviso de React.
La única diferencia que se nota es el ref: en React 18 un componente de función no lo recibe como prop. Los tres que lo exponen en su tipo (Button, IconButton y FloatingButton) van envueltos para que llegue igual en las dos; en el resto, el elemento de dentro se pide con inputRef o similar.
Con Tailwind
Con Tailwind 4, o sin Tailwind, no hay que hacer nada. Tailwind 3 no emite capas nativas: su preflight y sus utilidades le ganan a cualquier capa, sea cual sea el orden de los imports, y el botón se queda sin fondo y el campo con el borde de @tailwindcss/forms. Se arregla poniendo el preflight por debajo de la biblioteca y las utilidades por encima, compilando Tailwind en dos ficheros:
/* globals.css, con Tailwind 3 */@layer tw-base, amset-ui, tw-utils; @import './tailwind-base.css' layer(tw-base); /* solo @tailwind base */@import './tailwind-utils.css' layer(tw-utils); /* @tailwind components y utilities */Idiomas
Los textos de la biblioteca (etiquetas accesibles, mensajes, avisos del lector de pantalla) vienen en castellano y en inglés. Cualquier otro idioma se carga desde el proveedor como un JSON de "Componente.clave": "texto"; la plantilla con todas las claves es @amset/ui/locales/en.json.
import { AmsetProvider } from '@amset/ui' const catalogs = { fr: () => import('./i18n/fr.json'), // solo se descarga si el idioma es «fr» 'pt-BR': () => import('./i18n/pt-BR.json'),} <AmsetProvider locale={locale} catalogs={catalogs}>{children}</AmsetProvider>Se cargan bajo demanda: con siete idiomas configurados, el navegador descarga uno. Para pintarlo traducido en el servidor, loadMessages(locale, catalogs) lo resuelve antes y se pasa como messages.
Pruebas con Jest
Jest necesita un moduleNameMapper que convierta los .css en un módulo vacío, porque cada componente importa su hoja. Vitest ya lo hace solo.
// jest.config.jsexport default { moduleNameMapper: { '\\.css$': '<rootDir>/test/css-vacio.js' },}