From 53a58ee0de266ab50bf0de1e89044fc073f15750 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 16 Oct 2026 14:16:32 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20migrar=20de=20Lucide=20a=20Tabler=20Ico?= =?UTF-8?q?ns=20en=20componentes=20y=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/components.json | 2 +- docs/campos-y-plantillas-de-activos.md | 2 +- docs/componentes-de-interfaz.md | 30 +++++- docs/recursos-campos-y-plantillas.md | 2 +- .../src/web/catalog/shared/catalog.test.tsx | 2 +- .../src/web/catalog/shared/catalog.test.tsx | 12 +-- .../web/resources/shared/resources.test.tsx | 6 +- packages/rdx-ui/package.json | 4 +- .../rdx-ui/src/components/context-help.tsx | 3 +- .../src/components/entity-list-controls.tsx | 2 +- .../rdx-ui/src/components/reorder-handle.tsx | 3 +- .../src/components/select-with-action.tsx | 3 +- .../components/template-section-editor.tsx | 3 +- .../components/template-section-fields.tsx | 2 +- .../components/template-sections-editor.tsx | 2 +- packages/rdx-ui/src/lib/icons.ts | 96 ++++++++++--------- pnpm-lock.yaml | 21 +++- 17 files changed, 124 insertions(+), 71 deletions(-) diff --git a/apps/web/components.json b/apps/web/components.json index 8b761f3..be3bbc5 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -10,7 +10,7 @@ "cssVariables": true, "prefix": "" }, - "iconLibrary": "lucide", + "iconLibrary": "tabler", "aliases": { "components": "#components", "ui": "@repo/shadcn-ui/components", diff --git a/docs/campos-y-plantillas-de-activos.md b/docs/campos-y-plantillas-de-activos.md index e52d286..c4267dd 100644 --- a/docs/campos-y-plantillas-de-activos.md +++ b/docs/campos-y-plantillas-de-activos.md @@ -56,7 +56,7 @@ contrato actual de instantáneas. ## Iconos de plantilla -Cada plantilla global o propia de compañía admite un icono opcional de la biblioteca Lucide existente. El formulario incluye un selector visual con nombres traducidos, selección marcada y opción «Sin icono». Las copias conservan el icono; las plantillas personalizadas permiten cambiarlo y retirarlo. +Cada plantilla global o propia de compañía admite un icono opcional de la biblioteca Tabler Icons compartida. El formulario incluye un selector visual con nombres traducidos, selección marcada y opción «Sin icono». Las copias conservan el icono; las plantillas personalizadas permiten cambiarlo y retirarlo. Las doce plantillas de serie incluyen iconos adecuados a cada equipo. Editar o retirar un icono conserva identidades, campos y disponibilidad; el arranque no repone los iconos retirados por el usuario. diff --git a/docs/componentes-de-interfaz.md b/docs/componentes-de-interfaz.md index 48c08cb..3d121c2 100644 --- a/docs/componentes-de-interfaz.md +++ b/docs/componentes-de-interfaz.md @@ -13,6 +13,31 @@ El [patrón de diseño de cuadros de diálogo](patron-de-dialogos.md) es la fuen para componer grupos, columnas responsive, filas compactas y selectores. Esta guía describe los componentes que lo implementan y sus contratos. +## Iconografía de la aplicación + +El código propio utiliza `@tabler/icons-react`, con versión exacta en `@repo/rdx-ui` y +licencia MIT. La dependencia sustituye a Lucide en la biblioteca propia y amplía el +catálogo disponible sin cargar iconos desde servicios externos. Las importaciones de +la aplicación y de sus componentes compartidos pasan por `@repo/rdx-ui/lib/icons` +(o `../lib/icons` dentro del paquete). No importar las bibliotecas de iconos directamente +en módulos ni duplicar catálogos locales para acciones comunes. + +`lib/icons.ts` exporta nombres semánticos estables, asociados a los componentes de +contorno de Tabler. El tamaño se controla con las clases existentes y el color con +`currentColor`; el trazo predeterminado de Tabler es 2. Para ajustar su grosor se utiliza +la prop `stroke`. Los iconos decorativos mantienen `aria-hidden`; las acciones de icono +conservan su nombre accesible traducido. Importar únicamente los iconos necesarios. + +Las claves persistidas de las plantillas de activos y recursos conservan su contrato: +`laptop`, `desktop`, `mail`, `file-key`, etc. La correspondencia visual cambia a Tabler, +sin migraciones de datos ni cambios en DTO, historial o preferencias del usuario. +Los selectores de plantillas usan la misma correspondencia que las fichas y listados. + +`apps/web/components.json` declara Tabler para la generación desde la aplicación. +`packages/shadcn-ui` conserva su implementación vendor, configuración y dependencia de +Lucide para sus iconos internos; no modificar el código de terceros para migrar los +iconos propios. Consultar la [API oficial de Tabler para React](https://docs.tabler.io/icons/libraries/react). + ## Componentes actuales - Button: volver a comprobar la conexión. @@ -116,7 +141,8 @@ No añadir todo el catálogo ni crear envoltorios sin una necesidad concreta. Base UI proporciona interacción accesible y gestión del foco. class-variance-authority define variantes del botón; cn combina clases de Tailwind. -lucide-react aporta los iconos del selector y tw-animate-css sus transiciones. +`@tabler/icons-react` aporta los iconos del código propio y tw-animate-css sus transiciones. +El código vendor de shadcn/ui conserva sus dependencias e iconos internos del registro. shadcn es una herramienta de desarrollo; su hoja de estilos común se incorpora al compilar. Guías oficiales: [monorepos](https://ui.shadcn.com/docs/monorepo) e [instalación manual](https://ui.shadcn.com/docs/installation/manual). @@ -382,7 +408,7 @@ Los módulos conectan este componente mediante `DeleteRecordDialog`, separado de ## IconPicker -`@repo/rdx-ui/components/icon-picker` presenta un desplegable compacto mediante `Select`, `SelectTrigger`, `SelectValue` y `SelectItem` de shadcn/ui (Base UI). Muestra el icono seleccionado y su etiqueta traducida; cada opción combina icono y texto, incluida «Sin icono». Recibe opciones (valor, etiqueta e icono), valor actual, callback, estado deshabilitado y descripción opcional enlazada mediante `aria-describedby`. Su anchura máxima en escritorio es 16 rem y se adapta al espacio disponible en móvil. La etiqueta se asocia al control y los iconos se ocultan del nombre accesible. Mantiene teclado, foco y desplazamiento del desplegable mediante la primitiva existente. No conoce entidades ni endpoints. Las plantillas de activos y recursos reutilizan el mismo componente y aportan sus iconos Lucide, traducciones y validación de claves. +`@repo/rdx-ui/components/icon-picker` presenta un desplegable compacto mediante `Select`, `SelectTrigger`, `SelectValue` y `SelectItem` de shadcn/ui (Base UI). Muestra el icono seleccionado y su etiqueta traducida; cada opción combina icono y texto, incluida «Sin icono». Recibe opciones (valor, etiqueta e icono), valor actual, callback, estado deshabilitado y descripción opcional enlazada mediante `aria-describedby`. Su anchura máxima en escritorio es 16 rem y se adapta al espacio disponible en móvil. La etiqueta se asocia al control y los iconos se ocultan del nombre accesible. Mantiene teclado, foco y desplazamiento del desplegable mediante la primitiva existente. No conoce entidades ni endpoints. Las plantillas de activos y recursos reutilizan el mismo componente y aportan sus iconos Tabler, traducciones y validación de claves. ## Consulta compacta de plantillas diff --git a/docs/recursos-campos-y-plantillas.md b/docs/recursos-campos-y-plantillas.md index ab2c882..2052f30 100644 --- a/docs/recursos-campos-y-plantillas.md +++ b/docs/recursos-campos-y-plantillas.md @@ -122,7 +122,7 @@ presentación completa. No cambia el contrato HTTP, la persistencia ni la valida ## Iconos de plantillas -Las ocho plantillas de serie incluyen un icono Lucide: correo para buzones, llave para +Las ocho plantillas de serie incluyen un icono Tabler: correo para buzones, llave para accesos, carpeta para carpetas compartidas, auriculares para asistencia, monitor para sistemas operativos, capas para aplicaciones, escudo para certificados y archivo con llave para licencias. Las plantillas globales y propias de compañía permiten elegir, diff --git a/modules/assets/src/web/catalog/shared/catalog.test.tsx b/modules/assets/src/web/catalog/shared/catalog.test.tsx index cde3ece..3074a54 100644 --- a/modules/assets/src/web/catalog/shared/catalog.test.tsx +++ b/modules/assets/src/web/catalog/shared/catalog.test.tsx @@ -109,6 +109,6 @@ test("las plantillas ofrecen selección visual traducida y conservan el icono al ); assert.ok(empty.includes(none)); - assert.ok(html.includes("lucide-laptop")); + assert.ok(html.includes("tabler-icon-device-laptop")); } }); diff --git a/modules/resources/src/web/catalog/shared/catalog.test.tsx b/modules/resources/src/web/catalog/shared/catalog.test.tsx index e061a76..84e9419 100644 --- a/modules/resources/src/web/catalog/shared/catalog.test.tsx +++ b/modules/resources/src/web/catalog/shared/catalog.test.tsx @@ -91,15 +91,15 @@ test("iconos de recursos: catálogo y selector traducido conservan la selección for (const icon of [ "mail", - "key-round", + "key", "folder", "headphones", - "monitor", - "layers", + "device-desktop", + "stack-2", "shield-check", - "file-key", + "file-certificate", ]) { - assert.ok(list.includes(`lucide-${icon}`)); + assert.ok(list.includes(`tabler-icon-${icon}`)); } const empty = await render( language, @@ -121,7 +121,7 @@ test("iconos de recursos: catálogo y selector traducido conservan la selección assert.ok(html.includes(label)); assert.ok(html.includes('role="combobox"')); assert.ok(html.includes(mail)); - assert.ok(html.includes("lucide-mail")); + assert.ok(html.includes("tabler-icon-mail")); } } }); diff --git a/modules/resources/src/web/resources/shared/resources.test.tsx b/modules/resources/src/web/resources/shared/resources.test.tsx index ab712e0..985ef56 100644 --- a/modules/resources/src/web/resources/shared/resources.test.tsx +++ b/modules/resources/src/web/resources/shared/resources.test.tsx @@ -496,7 +496,7 @@ test("recursos: inventario y panel usan el icono vigente de la plantilla sin cam catalog, }); - assert.ok(list.includes("lucide-mail")); + assert.ok(list.includes("tabler-icon-mail")); const panel = await render( "es", ); - assert.ok(panel.includes("lucide-mail")); + assert.ok(panel.includes("tabler-icon-mail")); const withoutIcon = { ...template, values: { ...template.values, icon: undefined } }; const cleared = await render("es", , { resources: [resource], catalog: { items: [withoutIcon] }, }); - assert.equal(cleared.includes("lucide-mail"), false); + assert.equal(cleared.includes("tabler-icon-mail"), false); assert.equal(resource.specification.templateName, source.specification.templateName); assert.deepEqual(resource.history, source.history); }); diff --git a/packages/rdx-ui/package.json b/packages/rdx-ui/package.json index a0a2645..d2e7df2 100644 --- a/packages/rdx-ui/package.json +++ b/packages/rdx-ui/package.json @@ -16,10 +16,10 @@ "dependencies": { "@base-ui/react": "1.8.0", "@repo/shadcn-ui": "workspace:*", + "@tabler/icons-react": "3.49.0", "@tanstack/react-table": "8.21.3", "class-variance-authority": "0.7.1", - "cn": "0.3.0", - "lucide-react": "1.8.0" + "cn": "0.3.0" }, "devDependencies": { "@repo/typescript-config": "workspace:*", diff --git a/packages/rdx-ui/src/components/context-help.tsx b/packages/rdx-ui/src/components/context-help.tsx index 0c69571..c067a00 100644 --- a/packages/rdx-ui/src/components/context-help.tsx +++ b/packages/rdx-ui/src/components/context-help.tsx @@ -1,6 +1,7 @@ -import { ChevronDownIcon, InfoIcon } from "lucide-react"; import type { ReactNode } from "react"; +import { ChevronDownIcon, InfoIcon } from "../lib/icons"; + export function ContextHelp({ title, description, diff --git a/packages/rdx-ui/src/components/entity-list-controls.tsx b/packages/rdx-ui/src/components/entity-list-controls.tsx index d2fb54b..e26c16e 100644 --- a/packages/rdx-ui/src/components/entity-list-controls.tsx +++ b/packages/rdx-ui/src/components/entity-list-controls.tsx @@ -1,9 +1,9 @@ import { Menu } from "@base-ui/react/menu"; import { Button } from "@repo/shadcn-ui/components/button"; -import { EllipsisIcon } from "lucide-react"; import type { ReactNode } from "react"; import { ENTITY_PAGE_SIZES, entityListLabels, entityListPages } from "../lib/entity-list"; +import { EllipsisIcon } from "../lib/icons"; import { SelectWithAction } from "./select-with-action"; diff --git a/packages/rdx-ui/src/components/reorder-handle.tsx b/packages/rdx-ui/src/components/reorder-handle.tsx index b09f3e6..05d866a 100644 --- a/packages/rdx-ui/src/components/reorder-handle.tsx +++ b/packages/rdx-ui/src/components/reorder-handle.tsx @@ -1,7 +1,8 @@ import { Button } from "@repo/shadcn-ui/components/button"; -import { GripVerticalIcon } from "lucide-react"; import type { ComponentProps } from "react"; +import { GripVerticalIcon } from "../lib/icons"; + /** Asa con nombre traducido; el consumidor aporta los eventos de arrastre y teclado. */ export function ReorderHandle({ label, diff --git a/packages/rdx-ui/src/components/select-with-action.tsx b/packages/rdx-ui/src/components/select-with-action.tsx index 02f2975..dd6662d 100644 --- a/packages/rdx-ui/src/components/select-with-action.tsx +++ b/packages/rdx-ui/src/components/select-with-action.tsx @@ -6,7 +6,8 @@ import { SelectTrigger, SelectValue, } from "@repo/shadcn-ui/components/select"; -import { PlusIcon } from "lucide-react"; + +import { PlusIcon } from "../lib/icons"; import { ButtonGroup } from "./button-group"; diff --git a/packages/rdx-ui/src/components/template-section-editor.tsx b/packages/rdx-ui/src/components/template-section-editor.tsx index 77fad12..5a8691f 100644 --- a/packages/rdx-ui/src/components/template-section-editor.tsx +++ b/packages/rdx-ui/src/components/template-section-editor.tsx @@ -6,11 +6,12 @@ import { } from "@repo/shadcn-ui/components/collapsible"; import { Input } from "@repo/shadcn-ui/components/input"; import { Textarea } from "@repo/shadcn-ui/components/textarea"; -import { ArrowDownIcon, ArrowUpIcon, ChevronRightIcon, Trash2Icon } from "lucide-react"; import type { ReactNode } from "react"; import { useId, useState } from "react"; import { flushSync } from "react-dom"; +import { ArrowDownIcon, ArrowUpIcon, ChevronRightIcon, Trash2Icon } from "../lib/icons"; + import type { FieldSection } from "./field-sections"; import { FormLabel } from "./form-label"; import type { SectionEditorLabels } from "./template-sections-editor"; diff --git a/packages/rdx-ui/src/components/template-section-fields.tsx b/packages/rdx-ui/src/components/template-section-fields.tsx index fd70e55..f398cb3 100644 --- a/packages/rdx-ui/src/components/template-section-fields.tsx +++ b/packages/rdx-ui/src/components/template-section-fields.tsx @@ -1,8 +1,8 @@ import { Button } from "@repo/shadcn-ui/components/button"; -import { ArrowDownIcon, ArrowUpIcon, PlusIcon, Trash2Icon } from "lucide-react"; import { useId } from "react"; import { useDragReorder } from "../hooks/use-drag-reorder"; +import { ArrowDownIcon, ArrowUpIcon, PlusIcon, Trash2Icon } from "../lib/icons"; import type { FieldSection } from "./field-sections"; import { FormLabel } from "./form-label"; diff --git a/packages/rdx-ui/src/components/template-sections-editor.tsx b/packages/rdx-ui/src/components/template-sections-editor.tsx index 1493f35..b12d26d 100644 --- a/packages/rdx-ui/src/components/template-sections-editor.tsx +++ b/packages/rdx-ui/src/components/template-sections-editor.tsx @@ -1,8 +1,8 @@ import { Button } from "@repo/shadcn-ui/components/button"; -import { PlusIcon } from "lucide-react"; import { useId } from "react"; import { useDragReorder } from "../hooks/use-drag-reorder"; +import { PlusIcon } from "../lib/icons"; import { removeTemplateSection } from "../lib/template-sections"; import type { FieldSection } from "./field-sections"; diff --git a/packages/rdx-ui/src/lib/icons.ts b/packages/rdx-ui/src/lib/icons.ts index d5f5bb7..db6bf4b 100644 --- a/packages/rdx-ui/src/lib/icons.ts +++ b/packages/rdx-ui/src/lib/icons.ts @@ -1,45 +1,53 @@ +/** Catálogo visual propio: nombres semánticos estables y componentes SVG de Tabler. */ export { - BatteryChargingIcon, - BellIcon, - BoxIcon, - BriefcaseIcon, - Building2Icon, - CameraIcon, - ChevronDownIcon, - ChevronLeftIcon, - ChevronRightIcon, - ClockIcon, - DatabaseIcon, - FileKeyIcon, - FolderIcon, - HardDriveIcon, - HeadphonesIcon, - HomeIcon, - KeyRoundIcon, - KeyboardIcon, - LaptopIcon, - LayersIcon, - LayoutTemplateIcon, - MailIcon, - MapPinIcon, - MonitorCogIcon, - MonitorIcon, - MouseIcon, - NetworkIcon, - PhoneIcon, - PrinterIcon, - RouterIcon, - SearchIcon, - ServerIcon, - SettingsIcon, - ShieldCheckIcon, - SmartphoneIcon, - TabletIcon, - TicketIcon, - TriangleAlertIcon, - UserRoundXIcon, - UsersIcon, - WifiIcon, - WrenchIcon, - XIcon, -} from "lucide-react"; + IconAlertTriangle as TriangleAlertIcon, + IconArrowDown as ArrowDownIcon, + IconArrowUp as ArrowUpIcon, + IconBatteryCharging as BatteryChargingIcon, + IconBell as BellIcon, + IconBox as BoxIcon, + IconBriefcase as BriefcaseIcon, + IconBuildings as Building2Icon, + IconCamera as CameraIcon, + IconChevronDown as ChevronDownIcon, + IconChevronLeft as ChevronLeftIcon, + IconChevronRight as ChevronRightIcon, + IconClock as ClockIcon, + IconDatabase as DatabaseIcon, + IconDeviceDesktop as MonitorIcon, + IconDeviceDesktopCog as MonitorCogIcon, + IconDeviceLaptop as LaptopIcon, + IconDeviceMobile as SmartphoneIcon, + IconDeviceTablet as TabletIcon, + IconDots as EllipsisIcon, + IconFileCertificate as FileKeyIcon, + IconFolder as FolderIcon, + IconGripVertical as GripVerticalIcon, + IconHeadphones as HeadphonesIcon, + IconHome as HomeIcon, + IconInfoCircle as InfoIcon, + IconKey as KeyRoundIcon, + IconKeyboard as KeyboardIcon, + IconLayoutDashboard as LayoutTemplateIcon, + IconMail as MailIcon, + IconMapPin as MapPinIcon, + IconMouse as MouseIcon, + IconPhone as PhoneIcon, + IconPlus as PlusIcon, + IconPrinter as PrinterIcon, + IconRouter as RouterIcon, + IconSearch as SearchIcon, + IconServer as ServerIcon, + IconServer2 as HardDriveIcon, + IconSettings as SettingsIcon, + IconShieldCheck as ShieldCheckIcon, + IconSitemap as NetworkIcon, + IconStack2 as LayersIcon, + IconTicket as TicketIcon, + IconTool as WrenchIcon, + IconTrash as Trash2Icon, + IconUserX as UserRoundXIcon, + IconUsers as UsersIcon, + IconWifi as WifiIcon, + IconX as XIcon, +} from "@tabler/icons-react"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae12a58..d087979 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -493,6 +493,9 @@ importers: '@repo/shadcn-ui': specifier: workspace:* version: link:../shadcn-ui + '@tabler/icons-react': + specifier: 3.49.0 + version: 3.49.0(react@19.2.5) '@tanstack/react-table': specifier: 8.21.3 version: 8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -502,9 +505,6 @@ importers: cn: specifier: 0.3.0 version: 0.3.0 - lucide-react: - specifier: 1.8.0 - version: 1.8.0(react@19.2.5) devDependencies: '@repo/typescript-config': specifier: workspace:* @@ -1733,6 +1733,14 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 + '@tabler/icons-react@3.49.0': + resolution: {integrity: sha512-Um59pm5RVNUaQjFUtG0r4oYJbZuBS3tGugAA2tgBg0WufMk34Eio3nsO30RUkLuL2giYPQUNdY9HvqltLLYyNA==} + peerDependencies: + react: '>= 16' + + '@tabler/icons@3.49.0': + resolution: {integrity: sha512-l+GnSkFETx0U26icxv1VUFnxxiTqXfCcVRiRAnyMwibupM2S2L/hSaZ9gmSgz01rkrsH/93NvLsxykqdanc6lw==} + '@tailwindcss/node@4.2.2': resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} @@ -5120,6 +5128,13 @@ snapshots: estraverse: 5.3.0 picomatch: 4.0.7 + '@tabler/icons-react@3.49.0(react@19.2.5)': + dependencies: + '@tabler/icons': 3.49.0 + react: 19.2.5 + + '@tabler/icons@3.49.0': {} + '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5