luinuxscl / lnx-ui
Componentes UI compartidos para aplicaciones derivadas del kit lnx (Blade anónimos, override-friendly).
Requires
- php: ^8.3
- illuminate/support: ^13.0
- illuminate/view: ^13.0
- livewire/flux: ^2.20
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^11.3
- pestphp/pest: ^5.3
- pestphp/pest-plugin-laravel: ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Componentes UI compartidos para las aplicaciones derivadas del kit lnx. Es un paquete Composer de Blade anónimos, pensado para tener una sola fuente de componentes entre proyectos y permitir que cada app los sobrescriba sin tocar el paquete.
Licencia MIT: parte del kit. Úsalo y modifícalo libremente.
Instalación
En una app lnx, añade el repositorio VCS público y requiere el paquete:
composer config repositories.lnx-ui vcs https://github.com/luinuxscl/lnx-ui.git composer require luinuxscl/lnx-ui
En desarrollo local del propio paquete puedes usar un repositorio path en lugar del VCS.
El LnxUiServiceProvider se autodescubre gracias a extra.laravel.providers: no hay que registrarlo a mano.
Tailwind 4
Las clases dentro de vendor/ no se detectan solas. Añade una línea a resources/css/app.css:
@source '../../vendor/luinuxscl/lnx-ui/resources/views';
Uso
Los componentes viven bajo el namespace lnx-ui:
<x-lnx-ui::trend-card label="Sales" value="2.968" change="+3.2%" :series="[10, 20, 30]" color="emerald" />
Sobrescribir un componente
loadViewsFrom registra primero resources/views/vendor/lnx-ui, así que una app puede reemplazar cualquier componente creando el mismo archivo dentro de resources/views/vendor/lnx-ui/components/:
resources/views/vendor/lnx-ui/components/trend-card.blade.php
Para publicar todas las vistas del paquete y editarlas:
php artisan vendor:publish --tag=lnx-ui-views
Galería de diseño
El paquete incluye la vista lnx-ui::design-gallery: el catálogo de componentes agrupados por tipo, cada uno con un nombre y un ancla estable (#<nombre>) para referenciarlo con la IA. Es contenido sin layout; la app lo envuelve en su propio layout y decide la ruta y el gating:
{{-- resources/views/design-gallery.blade.php --}} <x-layouts::app :title="__('Design gallery')" :breadcrumbs="[['label' => __('Design gallery')]]"> @include('lnx-ui::design-gallery') </x-layouts::app>
Las cadenas de la galería viven en el namespace lnx-ui::design-gallery (lang/{en,es}/design-gallery.php) y se pueden sobrescribir en lang/vendor/lnx-ui/{locale}/design-gallery.php.
Componentes disponibles
| Componente | Props |
|---|---|
x-lnx-ui::stat |
label, value, icon, color |
x-lnx-ui::stat-card |
label, value, icon, color (envuelve stat en flux:card) |
x-lnx-ui::statistics-panel |
title, updated-label y updated-icon (opcionales); slots de métricas, actions, description y empty |
x-lnx-ui::trend-card |
label, value, change, series (array), color |
x-lnx-ui::chart-card |
label, value; description, help, change, trend, change-style (text por defecto), series (array), chart (line/bar), labels (array), color |
x-lnx-ui::metric-card |
label, value, icon; description, help, change, trend, change-style (badge por defecto), color |
x-lnx-ui::tool-card |
title, description, icon, href, color, external |
x-lnx-ui::resource-card |
title, description, icon, eyebrow, href, external, color; slots badges, tags, meta, actions |
x-lnx-ui::properties-card |
title, description, items (array de label, value, href, icon), columns (1 o 2), empty; slots actions y contenido libre |
x-lnx-ui::timeline |
title, description, items (array de title, description, meta, time, datetime, icon, color), empty; slots actions, empty y contenido libre |
x-lnx-ui::placeholder-pattern |
id (opcional) |
x-lnx-ui::auth-header |
title, description |
x-lnx-ui::auth-session-status |
status |
x-lnx-ui::chip |
label, icon, color; el slot es el fallback |
x-lnx-ui::media-cell |
title, description, icon, image, href, color |
x-lnx-ui::summary-card |
label, value, caption, change, icon, color |
x-lnx-ui::page-header |
title, subtitle, icon, color; slot actions |
x-lnx-ui::data-table |
title (opcional), description, filters-label, paginate, table-class, container-class; slots filters, toolbar, actions y el contenido de la tabla |
x-lnx-ui::data-table-inline |
title (opcional), description, paginate, table-class, container-class; slots filters, filtersActions, toolbar, actions y el contenido de la tabla |
x-lnx-ui::mail-list-item |
sender, email, avatar, subject, preview, time, unread, starred, selected, badge (['label' => …, 'color' => …]), href, select-label, star-label; slots select, star |
x-lnx-ui::attachment-card |
name, size, icon, color, href; slot action |
x-lnx-ui::presence-list |
title (opcional), users (array de name, avatar, line, online) |
x-lnx-ui::mail-viewer |
sender, email, avatar, subject, date; slots actions, attachments, reply, footer y el cuerpo por defecto |
x-lnx-ui::rich-text-editor |
value, label, placeholder, disabled; slot actions; usa wire:model/x-model |
color acepta accent (predeterminado), violet, cyan, rose, emerald, blue y amber.
Los componentes de correo (mail-list-item, attachment-card, presence-list, mail-viewer) son presentacionales: el consumidor aporta los textos ya traducidos y los datos ya formateados. Sus etiquetas accesibles viven en el namespace lnx-ui::components (lang/{en,es}/components.php) y se pueden sobrescribir con select-label/star-label.
page-header es el encabezado estándar de una página: un solo <h1> con el título, un subtítulo opcional y un badge de icono opcional. Las acciones de la página van en el slot actions, alineadas a la derecha:
<x-lnx-ui::page-header :title="__('Users')" :subtitle="__('Manage accounts, roles and invitations.')" icon="users" color="blue" > <x-slot:actions> <flux:button variant="primary" icon="plus">{{ __('Invite user') }}</flux:button> </x-slot:actions> </x-lnx-ui::page-header>
data-table-inline es la variante compacta de data-table para barras de filtros: no muestra el rótulo de filtros y coloca el slot filtersActions (exportar, limpiar, etc.) alineado a la derecha dentro de la misma fila que filters, ajustándose a ancho completo en pantallas estrechas.
data-table renderiza el title solo si se entrega: sin título ni slot filters, desaparece la fila superior. container-class se reenvía al contenedor de flux:table (el que envuelve la tabla, el pie y la paginación), útil para acotar la altura y fijar el encabezado (por ejemplo container-class="max-h-[70vh] overflow-auto" junto con sticky en flux:table.columns). No lo uses para el ancho.
En pantallas estrechas no fuerces un ancho mínimo con table-class="min-w-[…]": crea un scroll horizontal sin barra visible que la tarjeta recorta y deja columnas inalcanzables. En su lugar, oculta columnas por breakpoint (max-sm:hidden, max-md:hidden, max-lg:hidden, max-xl:hidden) en la columna de flux:table.columns y en su celda, de modo que la tabla siempre quepa sin scroll horizontal. La galería (#data-table) es el ejemplo de referencia. Si de verdad necesitas un suelo de ancho, que sea responsivo y coherente con las columnas que ocultas.
En chart-card y metric-card, la variación (change) se colorea en verde o rojo según su signo; trend (up/down) lo sobrescribe. El contenedor asigna el ancho, normalmente col-span-12 md:col-span-6 xl:col-span-3.
En chart-card y metric-card, help añade un texto de ayuda inline bajo la descripción para explicar conceptos poco obvios. Cuando se entrega, la tarjeta se expone como grupo accesible (role="group") asociado a la etiqueta y a la ayuda con aria-labelledby/aria-describedby; sin help, el render es idéntico al anterior:
<x-lnx-ui::metric-card :label="__('Total sales')" value="24.67k" icon="shopping-bag" :help="__('Includes confirmed and pending records.')" />
Editor de texto enriquecido
x-lnx-ui::rich-text-editor es un editor TipTap (ProseMirror) con toolbar accesible para negrita, cursiva, h2/h3, listas, cita, enlaces y deshacer/rehacer. El HTML resultante es responsabilidad de la app: siempre debe sanearse en el servidor antes de guardarlo.
<x-lnx-ui::rich-text-editor wire:model="bodyHtml" :value="$bodyHtml" :label="__('Body')" :placeholder="__('Start writing…')" > <x-slot:actions> <button type="button" x-on:click="assist('improve')">{{ __('Improve') }}</button> </x-slot:actions> </x-lnx-ui::rich-text-editor>
El componente usa x-modelable="html", así que funciona con wire:model en Livewire y con x-model en Alpine. Para uso standalone, pasa el contenido inicial en value.
Dependencias JavaScript
El paquete solo distribuye el módulo ESM; las dependencias npm las instala la app consumidora:
npm install @tiptap/core @tiptap/starter-kit @tiptap/pm
Importa el módulo una sola vez en el bundle de la app:
// resources/js/app.js import '../../vendor/luinuxscl/lnx-ui/resources/js/rich-text-editor.js';
El módulo registra el componente Alpine lnxRichText en alpine:init. Las clases Tailwind de la vista ya se detectan con el @source del paquete.
Acciones de IA y eventos
El editor no implementa IA. La app dibuja sus acciones en el slot actions y llama a assist(action), que captura la selección y emite un evento Livewire:
lnx-rich-text:assist({ action, html }) — la app recibe la selección.lnx-rich-text:assist-complete({ html }) — la app devuelve el HTML saneado; el editor reemplaza la selección. Conhtml: nullsolo se reactiva el editor.lnx-rich-text:set-content({ html }) — reemplaza el contenido completo (por ejemplo, tras una acción de IA o al normalizar tras guardar).
Mientras espera la respuesta, el editor se bloquea para conservar el rango de selección. La sustitución es una sola transacción, así que se puede deshacer.
Versionado
El paquete sigue SemVer. Las apps consumidoras requieren ^1.0, de modo que las adiciones de componentes (minor) se reciben con composer update luinuxscl/lnx-ui sin tocar composer.json.
Desarrollo
composer install vendor/bin/pest vendor/bin/pint
Convención de extracción
Un componente se mueve a este paquete cuando es presentacional puro: no consulta la base de datos, no lee modelos del host (App\Models\*) y no depende de rutas ni servicios de la app. Los componentes acoplados al host (app-logo, user-avatar, desktop-user-menu, etc.) se quedan en la aplicación.