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
- dev-develop
- v1.31.0
- v1.30.1
- v1.30.0
- v1.29.0
- v1.28.0
- v1.27.1
- v1.27.0
- v1.26.0
- v1.25.0
- v1.24.0
- v1.23.0
- v1.22.0
- v1.21.0
- v1.20.0
- v1.19.0
- v1.18.0
- v1.17.0
- v1.16.0
- v1.15.0
- v1.14.1
- v1.14.0
- v1.13.0
- v1.12.2
- v1.12.1
- v1.12.0
- v1.11.0
- v1.10.0
- v1.9.1
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.1
- v1.6.0
- v1.5.0
- v1.4.1
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.0
- v0.1.0
- dev-main
- dev-refactor
This package is auto-updated.
Last update: 2026-11-12 10:00:28 UTC
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::widget |
title, description, href, href-label, span (1–12), empty; slots actions y contenido |
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, eyebrow, icon, color; slots eyebrow y 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 |
x-lnx-ui::sam-trigger |
label, shortcut, count (inicial); escucha sam-badge-count; emite sam-open |
x-lnx-ui::sam-ambient-alerts |
alerts (bloque del panel Sam: revisar, descartar, silenciar, ocultar) |
x-lnx-ui::sam-panel |
open y close (expresiones Alpine), title, subtitle, label, dock, preview; slots actions, context, footer y contenido |
x-lnx-ui::sam-message |
role (assistant/user), name, copy, citations, sources, time, markdown (por defecto true para el asistente, false para el usuario) |
x-lnx-ui::sam-signals |
observations, actions, observationLimit, actionLimit; bloque compartido por sam-brief y sam-insight |
x-lnx-ui::sam-context-chip |
label, source, icon |
x-lnx-ui::sam-suggestions |
items (array), label; emite sam-suggestion |
x-lnx-ui::sam-empty-state |
title, body, icon; slot para acciones o sugerencias |
x-lnx-ui::sam-notice |
variant (secondary, success, warning, danger), icon |
x-lnx-ui::sam-typing |
label |
x-lnx-ui::sam-proposal-card |
source, id, title, summary, changes, status, expiresAt, recordUrl, canConfirm, risk (reversible/irreversible/bulk, por defecto irreversible), requiresPhrase (por defecto, solo reversible la omite), canUndo, undoExpiresAt; emite sam-confirm y sam-undo |
Los componentes de Sam toman el color del tema del anfitrión a través de los tokens accent (--color-accent, --color-accent-content, --color-accent-foreground). Una app derivada debe definirlos —el kit lo hace en resources/css/app.css— o los acentos caerán al valor por defecto de Tailwind. Los colores semánticos de la tarjeta de propuesta (green, amber, zinc, red) no dependen del tema.
Los componentes de Sam son presentacionales y no dependen de Livewire. El host conecta el estado y los turnos escuchando eventos DOM: sam-open (abrir el panel), sam-confirm ({ source, id, phrase } para confirmar un borrador; phrase viaja vacía cuando la propuesta es reversible y el módulo declaró el undo) y sam-suggestion ({ text } para rellenar la entrada). Tras confirmar, el host emite sam-phrase-consumed para limpiar el campo de frase. La frase nunca forma parte del HTML persistido.
La tarjeta muestra el riesgo de la propuesta y lo usa para decidir la confirmación: reversible se confirma con un clic, mientras irreversible y bulk piden la frase de un solo uso. Cuando el borrador ya se confirmó y su módulo conserva un undo vigente (canUndo y, si existe, undoExpiresAt), la tarjeta pinta un botón «Deshacer» de un solo uso que emite sam-undo ({ source, id }); el host vuelve a validar propiedad, ventana y frescura al ejecutarlo.
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. El eyebrow opcional añade una línea pequeña y semibold sobre el título —útil para una fecha o un contexto breve— y acepta texto o el slot eyebrow cuando hace falta HTML, por ejemplo <time datetime="…">. 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>
El eyebrow se puede pasar como texto o como slot cuando el contenido necesita semántica propia:
<x-lnx-ui::page-header :title="__('Dashboard')"> <x-slot:eyebrow> <time datetime="2026-11-02">{{ __('Monday, November 2, 2026') }}</time> </x-slot:eyebrow> </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.