feat: migrar de Lucide a Tabler Icons en componentes y documentación

This commit is contained in:
David Arranz 2026-10-16 14:16:32 +02:00
parent 94ddc5cf16
commit 53a58ee0de
17 changed files with 124 additions and 71 deletions

View File

@ -10,7 +10,7 @@
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"iconLibrary": "lucide", "iconLibrary": "tabler",
"aliases": { "aliases": {
"components": "#components", "components": "#components",
"ui": "@repo/shadcn-ui/components", "ui": "@repo/shadcn-ui/components",

View File

@ -56,7 +56,7 @@ contrato actual de instantáneas.
## Iconos de plantilla ## 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. 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.

View File

@ -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 para componer grupos, columnas responsive, filas compactas y selectores. Esta guía describe
los componentes que lo implementan y sus contratos. 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 ## Componentes actuales
- Button: volver a comprobar la conexión. - 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. Base UI proporciona interacción accesible y gestión del foco.
class-variance-authority define variantes del botón; cn combina clases de Tailwind. 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. 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). 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 ## 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 ## Consulta compacta de plantillas

View File

@ -122,7 +122,7 @@ presentación completa. No cambia el contrato HTTP, la persistencia ni la valida
## Iconos de plantillas ## 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 accesos, carpeta para carpetas compartidas, auriculares para asistencia, monitor para
sistemas operativos, capas para aplicaciones, escudo para certificados y archivo con sistemas operativos, capas para aplicaciones, escudo para certificados y archivo con
llave para licencias. Las plantillas globales y propias de compañía permiten elegir, llave para licencias. Las plantillas globales y propias de compañía permiten elegir,

View File

@ -109,6 +109,6 @@ test("las plantillas ofrecen selección visual traducida y conservan el icono al
); );
assert.ok(empty.includes(none)); assert.ok(empty.includes(none));
assert.ok(html.includes("lucide-laptop")); assert.ok(html.includes("tabler-icon-device-laptop"));
} }
}); });

View File

@ -91,15 +91,15 @@ test("iconos de recursos: catálogo y selector traducido conservan la selección
for (const icon of [ for (const icon of [
"mail", "mail",
"key-round", "key",
"folder", "folder",
"headphones", "headphones",
"monitor", "device-desktop",
"layers", "stack-2",
"shield-check", "shield-check",
"file-key", "file-certificate",
]) { ]) {
assert.ok(list.includes(`lucide-${icon}`)); assert.ok(list.includes(`tabler-icon-${icon}`));
} }
const empty = await render( const empty = await render(
language, 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(label));
assert.ok(html.includes('role="combobox"')); assert.ok(html.includes('role="combobox"'));
assert.ok(html.includes(mail)); assert.ok(html.includes(mail));
assert.ok(html.includes("lucide-mail")); assert.ok(html.includes("tabler-icon-mail"));
} }
} }
}); });

View File

@ -496,7 +496,7 @@ test("recursos: inventario y panel usan el icono vigente de la plantilla sin cam
catalog, catalog,
}); });
assert.ok(list.includes("lucide-mail")); assert.ok(list.includes("tabler-icon-mail"));
const panel = await render( const panel = await render(
"es", "es",
<ResourceSelectionPanel <ResourceSelectionPanel
@ -511,14 +511,14 @@ test("recursos: inventario y panel usan el icono vigente de la plantilla sin cam
/> />
); );
assert.ok(panel.includes("lucide-mail")); assert.ok(panel.includes("tabler-icon-mail"));
const withoutIcon = { ...template, values: { ...template.values, icon: undefined } }; const withoutIcon = { ...template, values: { ...template.values, icon: undefined } };
const cleared = await render("es", <ResourcesPage companyId={companyId} />, { const cleared = await render("es", <ResourcesPage companyId={companyId} />, {
resources: [resource], resources: [resource],
catalog: { items: [withoutIcon] }, 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.equal(resource.specification.templateName, source.specification.templateName);
assert.deepEqual(resource.history, source.history); assert.deepEqual(resource.history, source.history);
}); });

View File

@ -16,10 +16,10 @@
"dependencies": { "dependencies": {
"@base-ui/react": "1.8.0", "@base-ui/react": "1.8.0",
"@repo/shadcn-ui": "workspace:*", "@repo/shadcn-ui": "workspace:*",
"@tabler/icons-react": "3.49.0",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "8.21.3",
"class-variance-authority": "0.7.1", "class-variance-authority": "0.7.1",
"cn": "0.3.0", "cn": "0.3.0"
"lucide-react": "1.8.0"
}, },
"devDependencies": { "devDependencies": {
"@repo/typescript-config": "workspace:*", "@repo/typescript-config": "workspace:*",

View File

@ -1,6 +1,7 @@
import { ChevronDownIcon, InfoIcon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ChevronDownIcon, InfoIcon } from "../lib/icons";
export function ContextHelp({ export function ContextHelp({
title, title,
description, description,

View File

@ -1,9 +1,9 @@
import { Menu } from "@base-ui/react/menu"; import { Menu } from "@base-ui/react/menu";
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { EllipsisIcon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ENTITY_PAGE_SIZES, entityListLabels, entityListPages } from "../lib/entity-list"; import { ENTITY_PAGE_SIZES, entityListLabels, entityListPages } from "../lib/entity-list";
import { EllipsisIcon } from "../lib/icons";
import { SelectWithAction } from "./select-with-action"; import { SelectWithAction } from "./select-with-action";

View File

@ -1,7 +1,8 @@
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { GripVerticalIcon } from "lucide-react";
import type { ComponentProps } from "react"; import type { ComponentProps } from "react";
import { GripVerticalIcon } from "../lib/icons";
/** Asa con nombre traducido; el consumidor aporta los eventos de arrastre y teclado. */ /** Asa con nombre traducido; el consumidor aporta los eventos de arrastre y teclado. */
export function ReorderHandle({ export function ReorderHandle({
label, label,

View File

@ -6,7 +6,8 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@repo/shadcn-ui/components/select"; } from "@repo/shadcn-ui/components/select";
import { PlusIcon } from "lucide-react";
import { PlusIcon } from "../lib/icons";
import { ButtonGroup } from "./button-group"; import { ButtonGroup } from "./button-group";

View File

@ -6,11 +6,12 @@ import {
} from "@repo/shadcn-ui/components/collapsible"; } from "@repo/shadcn-ui/components/collapsible";
import { Input } from "@repo/shadcn-ui/components/input"; import { Input } from "@repo/shadcn-ui/components/input";
import { Textarea } from "@repo/shadcn-ui/components/textarea"; import { Textarea } from "@repo/shadcn-ui/components/textarea";
import { ArrowDownIcon, ArrowUpIcon, ChevronRightIcon, Trash2Icon } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useId, useState } from "react"; import { useId, useState } from "react";
import { flushSync } from "react-dom"; import { flushSync } from "react-dom";
import { ArrowDownIcon, ArrowUpIcon, ChevronRightIcon, Trash2Icon } from "../lib/icons";
import type { FieldSection } from "./field-sections"; import type { FieldSection } from "./field-sections";
import { FormLabel } from "./form-label"; import { FormLabel } from "./form-label";
import type { SectionEditorLabels } from "./template-sections-editor"; import type { SectionEditorLabels } from "./template-sections-editor";

View File

@ -1,8 +1,8 @@
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { ArrowDownIcon, ArrowUpIcon, PlusIcon, Trash2Icon } from "lucide-react";
import { useId } from "react"; import { useId } from "react";
import { useDragReorder } from "../hooks/use-drag-reorder"; import { useDragReorder } from "../hooks/use-drag-reorder";
import { ArrowDownIcon, ArrowUpIcon, PlusIcon, Trash2Icon } from "../lib/icons";
import type { FieldSection } from "./field-sections"; import type { FieldSection } from "./field-sections";
import { FormLabel } from "./form-label"; import { FormLabel } from "./form-label";

View File

@ -1,8 +1,8 @@
import { Button } from "@repo/shadcn-ui/components/button"; import { Button } from "@repo/shadcn-ui/components/button";
import { PlusIcon } from "lucide-react";
import { useId } from "react"; import { useId } from "react";
import { useDragReorder } from "../hooks/use-drag-reorder"; import { useDragReorder } from "../hooks/use-drag-reorder";
import { PlusIcon } from "../lib/icons";
import { removeTemplateSection } from "../lib/template-sections"; import { removeTemplateSection } from "../lib/template-sections";
import type { FieldSection } from "./field-sections"; import type { FieldSection } from "./field-sections";

View File

@ -1,45 +1,53 @@
/** Catálogo visual propio: nombres semánticos estables y componentes SVG de Tabler. */
export { export {
BatteryChargingIcon, IconAlertTriangle as TriangleAlertIcon,
BellIcon, IconArrowDown as ArrowDownIcon,
BoxIcon, IconArrowUp as ArrowUpIcon,
BriefcaseIcon, IconBatteryCharging as BatteryChargingIcon,
Building2Icon, IconBell as BellIcon,
CameraIcon, IconBox as BoxIcon,
ChevronDownIcon, IconBriefcase as BriefcaseIcon,
ChevronLeftIcon, IconBuildings as Building2Icon,
ChevronRightIcon, IconCamera as CameraIcon,
ClockIcon, IconChevronDown as ChevronDownIcon,
DatabaseIcon, IconChevronLeft as ChevronLeftIcon,
FileKeyIcon, IconChevronRight as ChevronRightIcon,
FolderIcon, IconClock as ClockIcon,
HardDriveIcon, IconDatabase as DatabaseIcon,
HeadphonesIcon, IconDeviceDesktop as MonitorIcon,
HomeIcon, IconDeviceDesktopCog as MonitorCogIcon,
KeyRoundIcon, IconDeviceLaptop as LaptopIcon,
KeyboardIcon, IconDeviceMobile as SmartphoneIcon,
LaptopIcon, IconDeviceTablet as TabletIcon,
LayersIcon, IconDots as EllipsisIcon,
LayoutTemplateIcon, IconFileCertificate as FileKeyIcon,
MailIcon, IconFolder as FolderIcon,
MapPinIcon, IconGripVertical as GripVerticalIcon,
MonitorCogIcon, IconHeadphones as HeadphonesIcon,
MonitorIcon, IconHome as HomeIcon,
MouseIcon, IconInfoCircle as InfoIcon,
NetworkIcon, IconKey as KeyRoundIcon,
PhoneIcon, IconKeyboard as KeyboardIcon,
PrinterIcon, IconLayoutDashboard as LayoutTemplateIcon,
RouterIcon, IconMail as MailIcon,
SearchIcon, IconMapPin as MapPinIcon,
ServerIcon, IconMouse as MouseIcon,
SettingsIcon, IconPhone as PhoneIcon,
ShieldCheckIcon, IconPlus as PlusIcon,
SmartphoneIcon, IconPrinter as PrinterIcon,
TabletIcon, IconRouter as RouterIcon,
TicketIcon, IconSearch as SearchIcon,
TriangleAlertIcon, IconServer as ServerIcon,
UserRoundXIcon, IconServer2 as HardDriveIcon,
UsersIcon, IconSettings as SettingsIcon,
WifiIcon, IconShieldCheck as ShieldCheckIcon,
WrenchIcon, IconSitemap as NetworkIcon,
XIcon, IconStack2 as LayersIcon,
} from "lucide-react"; 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";

View File

@ -493,6 +493,9 @@ importers:
'@repo/shadcn-ui': '@repo/shadcn-ui':
specifier: workspace:* specifier: workspace:*
version: link:../shadcn-ui version: link:../shadcn-ui
'@tabler/icons-react':
specifier: 3.49.0
version: 3.49.0(react@19.2.5)
'@tanstack/react-table': '@tanstack/react-table':
specifier: 8.21.3 specifier: 8.21.3
version: 8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) version: 8.21.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@ -502,9 +505,6 @@ importers:
cn: cn:
specifier: 0.3.0 specifier: 0.3.0
version: 0.3.0 version: 0.3.0
lucide-react:
specifier: 1.8.0
version: 1.8.0(react@19.2.5)
devDependencies: devDependencies:
'@repo/typescript-config': '@repo/typescript-config':
specifier: workspace:* specifier: workspace:*
@ -1733,6 +1733,14 @@ packages:
peerDependencies: peerDependencies:
eslint: ^9.0.0 || ^10.0.0 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': '@tailwindcss/node@4.2.2':
resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
@ -5120,6 +5128,13 @@ snapshots:
estraverse: 5.3.0 estraverse: 5.3.0
picomatch: 4.0.7 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': '@tailwindcss/node@4.2.2':
dependencies: dependencies:
'@jridgewell/remapping': 2.3.5 '@jridgewell/remapping': 2.3.5