integrasoft-shared-ui
v0.3.4
Published
Librería de componentes UI en Vue 3 + TypeScript para estandarizar estilos entre los proyectos de Integrasoft.
Maintainers
Readme
integrasoft-shared-ui
Librería de componentes UI en Vue 3 + TypeScript, pensada para estandarizar los estilos y componentes repetidos entre los proyectos frontend de Integrasoft (formularios, modales, tablas de búsqueda, paginación, etc.), en vez de reimplementarlos en cada proyecto.
Publicada en npm: integrasoft-shared-ui
Índice
- Instalación
- Configuración inicial
- Diseño y tokens (theming)
- Componentes
- Catálogo visual (Storybook)
- Desarrollo local
- Publicar una nueva versión
Instalación
npm install integrasoft-shared-uiDependencias necesarias en el proyecto consumidor
La librería no incluye Vue ni FontAwesome dentro de su bundle (son peerDependencies): cada proyecto usa su propia versión ya instalada.
npm install vue@^3.5.0 @fortawesome/vue-fontawesome@^3.2.0 @fortawesome/fontawesome-svg-coreSi tu proyecto no usa iconos de FontAwesome en ningún componente, puedes omitir esas dos dependencias — están marcadas como opcionales (
peerDependenciesMeta).
Configuración inicial
1. Importa los estilos globales una sola vez, normalmente en tu main.ts:
import { createApp } from "vue";
import "integrasoft-shared-ui/style.css";
import App from "./App.vue";
createApp(App).mount("#app");2. Importa los componentes que necesites directamente donde los uses (recomendado, mejor tree-shaking):
<script setup lang="ts">
import { IsuButton } from "integrasoft-shared-ui";
</script>
<template>
<IsuButton label="Guardar" variant="primary" />
</template>Alternativa: registrar todos los componentes de golpe como plugin global (menos recomendado, pero disponible):
import IntegrasoftSharedUI from "integrasoft-shared-ui";
createApp(App).use(IntegrasoftSharedUI).mount("#app");Diseño y tokens (theming)
Todos los componentes usan CSS variables (--isu-*) en vez de colores fijos, para que cada proyecto pueda ajustar la marca sin tocar el código de los componentes. Sobreescribe las que necesites en tu CSS global, después de importar integrasoft-shared-ui/style.css:
:root {
--isu-color-primary: #135995;
--isu-color-primary-hover: #1a6bb0;
--isu-color-danger: #dc2626;
--isu-font-family: "Poppins", Arial, Helvetica, sans-serif;
/* ...ver la lista completa en src/styles/tokens.css del repositorio */
}También incluye un tema oscuro opcional: agrega data-theme="dark" en <html> o <body> para activarlo.
Sobre la tipografía (Poppins)
--isu-font-family solo le dice a los componentes qué fuente usar — no la carga por sí sola. Si Poppins no está disponible en el navegador, cae al siguiente de la lista (Arial/Helvetica/sans-serif del sistema), que se ve distinto en cada máquina/SO. Cada proyecto consumidor debe cargar la fuente él mismo, por ejemplo:
- Google Fonts (requiere acceso a internet desde el navegador del usuario final):
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet" /> - Autohospedada (recomendado si tus apps corren en una red interna sin salida a internet garantizada): descarga los archivos
.woff2de Poppins y decláralos con@font-faceen el CSS global de tu proyecto.
Componentes
IsuButton
<IsuButton label="Guardar" variant="primary" size="md" @click="onGuardar" />
<IsuButton label="Eliminar" variant="danger" :icon="faTrash" icon-position="left" />
<IsuButton label="Continuar" :icon="faArrowRight" icon-position="right" />
<!-- Botón cuadrado de solo ícono, con tooltip (toolbars, columna de acciones de una rejilla) -->
<IsuButton square variant="secondary" :icon="faRotate" tooltip="Consultar" @click="consultar" />
<IsuButton square variant="danger" :icon="faTrash" tooltip="Eliminar" @click="eliminar" />
<!-- Variante "outline": transparente + borde gris, fondo gris claro al hacer hover -->
<IsuButton variant="outline" label="Cancelar" @click="cancelar" />
<IsuButton variant="outline" square :icon="faTrash" icon-color="#dc2626" tooltip="Eliminar" @click="eliminar" />
<!-- Variante "default": gris neutro con hover, es la variante usada cuando no se especifica `variant` -->
<IsuButton label="Ver detalle" @click="verDetalle" />
<IsuButton variant="default" label="Ver detalle" @click="verDetalle" />
<!-- Colores custom: borderColor/textColor, independientes de la variante -->
<IsuButton label="Pendiente" variant="outline" border-color="#f59e0b" text-color="#f59e0b" />
<!-- Si hay ícono y no se pasa iconColor, el ícono hereda textColor -->
<IsuButton label="Pendiente" variant="outline" :icon="faTriangleExclamation" text-color="#f59e0b" border-color="#f59e0b" />
<!-- Botones predeterminados de la empresa: label/ícono/variante/colores ya definidos -->
<IsuButton preset="guardar" @click="guardar" />
<IsuButton preset="atras" @click="volver" />
<IsuButton preset="nuevo" @click="crear" />
<IsuButton preset="eliminar" @click="eliminar" />
<IsuButton preset="editar" @click="editar" />
<IsuButton preset="descargar" @click="descargar" />
<IsuButton preset="buscar" @click="buscar" />
<!-- Cualquier prop pasada explícitamente tiene prioridad sobre el preset -->
<IsuButton preset="guardar" label="Guardar cambios" variant="secondary" />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| label | string | — | Texto del botón. Si no se pasa, se usa el slot por defecto (o el label del preset, si hay uno). |
| variant | "primary" \| "secondary" \| "danger" \| "disabled" \| "outline" \| "default" | "default" | Estilo visual. "outline": fondo transparente, borde gris, fondo gris claro al hacer hover. "default": gris neutro con hover, mismo set de props que el resto de variantes. |
| size | "sm" \| "md" \| "lg" | "md" | Tamaño (padding + tipografía). En modo square, define el lado del cuadrado (28px / igual al alto de los inputs / 40px). |
| disabled | boolean | false | Deshabilita el botón (atributo nativo: bloquea clicks y aplica el estilo atenuado). |
| type | "button" \| "submit" \| "reset" | "button" | Atributo type nativo. |
| icon | IconDefinition (FontAwesome) | — | Ícono opcional. |
| iconPosition | "left" \| "right" | "left" | Posición del ícono respecto al texto (ignorado si square es true). |
| iconColor | string | — | Color CSS del ícono (ej. "#dc2626" o "var(--isu-color-primary)"), sin importar la variante. Si no se pasa, usa textColor (si está definida), o el color de texto normal de la variante actual. |
| borderColor | string | — | Color CSS del borde del botón (ej. "#f59e0b" o "var(--isu-color-warning)"), sin importar la variante. |
| textColor | string | — | Color CSS del texto del botón, sin importar la variante. También se usa como color del ícono si iconColor no está definida. |
| square | boolean | false | Botón cuadrado de solo ícono (ignora label y el slot por defecto). El tamaño md queda exactamente del alto de un input, para alinearse con IsuInput/IsuInputWithAddon. |
| tooltip | string | — | Texto mostrado en un tooltip propio (estilizado con los tokens de la librería) al pasar el mouse o enfocar el botón. Textos largos hacen wrap automáticamente en varias líneas (máximo ~220px de ancho) en vez de desbordarse en una sola línea. |
| tooltipPosition | "top" \| "bottom" \| "left" \| "right" | "top" | Lado del botón donde aparece el tooltip. |
| preset | "guardar" \| "atras" \| "nuevo" \| "eliminar" \| "editar" \| "descargar" \| "buscar" | — | Botón predeterminado de la empresa: aplica automáticamente label, icon, iconPosition, variant y colores/tooltip por defecto (ver tabla abajo). Cualquier otra prop pasada explícitamente (label, variant, icon, iconColor, tooltip, etc.) tiene prioridad sobre el valor del preset. |
Presets disponibles:
| preset | Variante | Ícono | Label | Tooltip | Notas |
|---|---|---|---|---|---|
| guardar | primary | disquete, izquierda | "Guardar" | "Guardar" | — |
| atras | default | flecha izquierda | "Atrás" | "Volver" | — |
| nuevo | default | más, izquierda | "Nuevo" | "Nuevo" | — |
| eliminar | outline, square | basura (solo ícono) | — | "Eliminar" | Ícono y borde en rojo (--isu-color-danger) |
| editar | outline, square | lápiz (solo ícono) | — | "Editar" | Ícono y borde en azul (--isu-color-primary) |
| descargar | secondary | descarga, izquierda | "Descargar" | "Descargar" | — |
| buscar | secondary | lupa, izquierda | "Buscar" | "Buscar" | — |
⚠️
variant="disabled"solo aplica el estilo visual gris de deshabilitado — el botón sigue siendo clickeable. Si además quieres que no reaccione a clicks, agrega también la propdisabled.
Emits: click(event: MouseEvent)
IsuInput
Input de texto estandarizado con label, validación y filtros de escritura.
<IsuInput v-model="fecha" type="date" label="Fecha del documento" required min="2026-01-01" />
<IsuInput v-model="correo" type="email" label="Correo" placeholder="[email protected]" />
<IsuInput v-model="nombre" filter="text" label="Solo texto (sin números)" />
<IsuInput v-model="telefono" filter="number" label="Solo número (sin letras)" />
<IsuInput v-model="valorUnitario" filter="money" label="Valor unitario" align="right" />
<IsuInput v-model="unidad" label="Unidad funcional" readonly required />
<!-- Tooltip propio: aparece tras mantener el cursor encima del input durante tooltipDelay ms -->
<IsuInput
v-model="unidad"
label="Unidad funcional"
readonly
tooltip="Este campo se completa automáticamente desde el módulo de presupuesto"
:tooltip-delay="800"
tooltip-position="right"
/>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | string | — | Valor del input (v-model). |
| type | "text" \| "number" \| "date" \| "email" \| "password" \| "tel" | "text" | Tipo nativo de HTML. |
| filter | "none" \| "text" \| "number" \| "money" | "none" | Restringe lo que se puede escribir, independiente del type. "text": solo letras/espacios. "number": solo dígitos. "money": solo dígitos, formateados en vivo con separador de miles (es-CO) y el signo $ al inicio (ej. "$ 1.000.000"). Se aplica al escribir, al pegar, y también al valor inicial recibido por v-model (aunque llegue como dígitos sin formatear). |
| label | string | — | Etiqueta encima del input. |
| required | boolean | false | Muestra asterisco rojo junto al label. |
| disabled / readonly | boolean | false | Estados nativos. |
| error | string | — | Mensaje de error externo (prioridad sobre la validación automática de email). |
| emailErrorMessage | string | "Correo electrónico no válido" | Mensaje mostrado cuando type="email" y el valor no tiene estructura de correo válida (se valida en tiempo real, mientras se escribe). |
| reserveErrorSpace | boolean | true | Reserva el espacio del mensaje de error para evitar saltos de layout. |
| align | "left" \| "right" | "left" | Alineación del texto (usar "right" para campos monetarios). |
| min / max / maxlength | — | — | Atributos nativos (fechas, longitud, etc.). |
| tooltip | string | — | Texto de un tooltip propio (mismo estilo visual que el de IsuButton), mostrado tras mantener el cursor sobre el input durante tooltipDelay ms. Si el cursor sale antes de que se cumpla el delay, no llega a mostrarse. |
| tooltipDelay | number | 500 | Milisegundos de espera con el cursor encima antes de mostrar el tooltip. |
| tooltipPosition | "top" \| "bottom" \| "left" \| "right" | "top" | Lado del input donde aparece el tooltip. |
Slots: addon — contenido renderizado al lado del input (mismo alto), sin afectar la posición del label ni del mensaje de error. Es lo que usa IsuInputWithAddon por dentro para sus botones de acción; también se puede usar directamente en IsuInput.
Emits: update:modelValue(value: string), focus(event), blur(event)
IsuTextarea
Área de texto multilínea con el mismo label/asterisco/validación que IsuInput. El ancho lo define el contenedor y no cambia; solo el alto es ajustable (arrastrando la esquina inferior derecha), gracias a resize: vertical por defecto.
<IsuTextarea v-model="observaciones" label="Observaciones" placeholder="Escribe aquí..." />
<!-- Con contador de caracteres y límites -->
<IsuTextarea
v-model="justificacion"
label="Justificación"
required
:minlength="20"
:maxlength="300"
show-char-count
/>
<!-- Tamaño fijo, sin permitir redimensionar -->
<IsuTextarea v-model="descripcion" label="Descripción" resize="none" :rows="4" />
<!-- Alternativa a resize: crece solo según el contenido, sin tirador ni scroll interno -->
<IsuTextarea v-model="descripcion" label="Descripción" auto-grow />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | string | — | Valor del textarea (v-model). |
| label | string | — | Etiqueta encima del campo. |
| required | boolean | false | Muestra asterisco rojo junto al label. |
| disabled / readonly | boolean | false | Estados nativos. |
| error | string | — | Mensaje de error externo (prioridad sobre la validación automática de minlength). |
| reserveErrorSpace | boolean | true | Igual que en IsuInput. |
| minlength | number | — | Si se define, valida en vivo mientras se escribe y muestra "Mínimo N caracteres" cuando el campo tiene contenido pero no alcanza el mínimo. |
| maxlength | number | — | Atributo nativo: el navegador bloquea escribir/pegar más caracteres. También se usa como total del contador (actual/maxlength) si showCharCount está activo. |
| rows | number | 3 | Alto inicial en líneas visibles. |
| resize | "vertical" \| "none" | "vertical" | "vertical": el usuario puede arrastrar para cambiar el alto (el ancho siempre queda fijo por el contenedor). "none": tamaño fijo, sin permitir redimensionar. Se ignora si autoGrow está activo. |
| autoGrow | boolean | false | Alternativa a resize: el alto se ajusta solo al contenido a medida que se escribe, sin scroll interno. Al activarlo, el usuario ya no puede arrastrar el alto a mano (equivale a forzar resize: "none"). |
| showCharCount | boolean | false | Muestra el contador de caracteres debajo, a la derecha del mensaje de error. |
Emits: update:modelValue(value: string), focus(event), blur(event)
IsuModal
<IsuButton label="Abrir" @click="visible = true" />
<IsuModal v-model="visible" title="Editar registro">
Contenido del modal.
<template #footer="{ close }">
<IsuButton variant="secondary" label="Cancelar" @click="close" />
<IsuButton variant="primary" label="Guardar" @click="close" />
</template>
</IsuModal>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | boolean | — | Visibilidad (v-model). |
| title | string | — | Título del header. |
| icon | IconDefinition | — | Ícono junto al título. |
| variant | "default" \| "danger" | "default" | Variante visual del header (danger para confirmaciones destructivas). |
| size | "sm" \| "md" \| "lg" \| "xl" | "md" | Ancho máximo predefinido. |
| maxWidth | string | — | Ancho máximo custom (ej. "820px"), tiene prioridad sobre size. |
| closeOnOverlayClick | boolean | true | Cerrar al hacer click fuera del modal. |
| closeOnEsc | boolean | true | Cerrar con la tecla Esc. |
| showCloseButton | boolean | true | Muestra el botón de cerrar. |
| closeIcon | IconDefinition | — | Ícono del botón de cerrar (ej. faXmark). Si no se pasa, se muestra "✕" como texto. |
| teleportTarget | string | "body" | Destino del <Teleport>. |
Slots: default (body), header (reemplaza título+ícono, recibe { close }), footer (recibe { close }).
Emits: update:modelValue(value: boolean), close()
IsuPagination
<!-- Selector fijo: aplica al instante -->
<IsuPagination v-model="page" v-model:page-size="pageSize" :total-items="230" :page-size-options="[8, 16, 32]" />
<!-- Input libre + botón "Mostrar" -->
<IsuPagination v-model="page" v-model:page-size="pageSize" :total-items="500" :sibling-count="4" />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | number | — | Página actual, 1-indexed (v-model). |
| pageSize | number | — | Registros por página (v-model:page-size). |
| totalItems | number | — | Total de registros. |
| pageSizeOptions | number[] | — | Si se pasa, renderiza un <select> que aplica al instante. Si se omite, renderiza un input numérico libre + botón "Mostrar". |
| minPageSize / maxPageSize | number | 5 / 50 | Límites del input libre. |
| siblingCount | number | 2 | Páginas visibles a cada lado de la actual. |
| showPageSize | boolean | true | Oculta el control de tamaño de página. |
El input numérico libre aplica el cambio al presionar Enter (además del botón "Mostrar"), y no muestra las flechas de incremento/decremento nativas del navegador.
Emits: update:modelValue(page: number), update:pageSize(size: number)
IsuBadge
<IsuBadge label="Provisional" variant="warning" />
<IsuBadge label="Firme" variant="success" />
<IsuBadge label="Anulado" variant="danger" size="md" :dot="false" />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| label | string | — | Texto del badge. |
| variant | "success" \| "warning" \| "info" \| "danger" \| "neutral" | "neutral" | Color (fondo pastel + texto contrastante). |
| size | "sm" \| "md" | "sm" | Tamaño. |
| dot | boolean | true | Muestra el punto de color junto al texto. |
IsuInputWithAddon
IsuInput con uno o varios botones de acción adjuntos (buscar, refrescar, etc.) — internamente envuelve IsuInput y expone exactamente las mismas props (label, filtros, validación de email, alineación, error, etc.), así que el campo de búsqueda tiene las mismas opciones que cualquier IsuInput. Los botones son IsuButton en modo square, con soporte de tooltip.
<IsuInputWithAddon
v-model="nit"
label="CC / NIT"
required
placeholder="Buscar por NIT o nombre..."
filter="number"
:actions="[
{ icon: faRotate, onClick: consultar, tooltip: 'Consultar' },
{ icon: faMagnifyingGlass, onClick: abrirModal, tooltip: 'Buscar' },
]"
/>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | string | — | Valor del input (v-model). |
| type | "text" \| "number" \| "date" \| "email" \| "password" \| "tel" | "text" | Igual que en IsuInput. |
| filter | "none" \| "text" \| "number" \| "money" | "none" | Igual que en IsuInput (incluye "money" para valores monetarios). |
| label | string | — | Etiqueta encima del input (igual estilo que IsuInput). |
| placeholder | string | — | Igual que un input nativo. |
| required | boolean | false | Muestra asterisco rojo junto al label. |
| disabled / readonly | boolean | false | Estados nativos. |
| error | string | — | Mensaje de error externo (igual que en IsuInput): borde rojo + mensaje debajo del input y los botones. |
| emailErrorMessage | string | "Correo electrónico no válido" | Igual que en IsuInput (solo aplica con type="email"). |
| reserveErrorSpace | boolean | true | Igual que en IsuInput. |
| align | "left" \| "right" | "left" | Igual que en IsuInput (usar "right" para campos monetarios). |
| min / max / maxlength / name | — | — | Igual que en IsuInput. |
| tooltip / tooltipDelay / tooltipPosition | — | — / 500 / "top" | Igual que en IsuInput: tooltip propio del campo, mostrado tras mantener el cursor encima. |
| actions | InputAddonAction[] | [] | Botones adjuntos: { icon, onClick, disabled?, variant?: ButtonVariant, tooltip?: string, ariaLabel? }. variant usa las mismas opciones que IsuButton ("primary" \| "secondary" \| "danger" \| "disabled" \| "outline" \| "default", default "primary"). |
Emits: update:modelValue(value: string), focus(event), blur(event)
IsuAutocomplete
Lista flotante de sugerencias, genérica sobre cualquier tipo de dato (usar junto a un input propio o a IsuInputWithAddon, dentro de un contenedor con position: relative).
El contenedor
position: relativedebe envolver solo el input y elIsuAutocomplete. Si agregás ahí otro contenido que aparece condicionalmente (p. ej. un mensaje de "seleccionado"), le suma altura al contenedor y el dropdown se desplaza hacia abajo la próxima vez que se abra.
<div style="position: relative">
<input v-model="query" />
<IsuAutocomplete :items="sugerencias" @select="seleccionar">
<template #default="{ item }">
<span>{{ item.nit }}</span>
<span>{{ item.nombre }}</span>
</template>
</IsuAutocomplete>
</div>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| items | T[] | — | Lista de sugerencias a mostrar (ya filtrada por quien la use). No se renderiza nada si está vacía. |
Al seleccionar un ítem (@select), el componente se oculta automáticamente aunque items no quede vacío, y solo vuelve a mostrarse cuando items cambia de nuevo (p. ej. el usuario sigue escribiendo).
Slots: default({ item, index }) — contenido de cada sugerencia, 100% libre.
Emits: select(item: T)
IsuSearchPickerModal
Combina IsuModal + tabla + IsuPagination para modales de búsqueda con selección de fila (reemplaza los típicos "buscar tercero / buscar producto / buscar unidad funcional").
<IsuSearchPickerModal
v-model="visible"
title="Buscar tercero"
:icon="faSearch"
:items="resultados"
:loading="cargando"
search-placeholder="Buscar por NIT o nombre..."
@search="onBuscar"
@select="onSeleccionar"
>
<template #columns>
<th>NIT</th>
<th>Nombre</th>
</template>
<template #row="{ item }">
<td>{{ item.nit }}</td>
<td>{{ item.nombre }}</td>
</template>
</IsuSearchPickerModal>El componente no filtra los datos por sí mismo: solo maneja el input de búsqueda y la paginación. Escucha el evento search y actualiza tú la prop items (filtrando en memoria o llamando a tu API).
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | boolean | — | Visibilidad (v-model). |
| title / icon / size | — | size: "xl" | Igual que en IsuModal. |
| items | T[] | — | Resultados actuales a mostrar (ya buscados/filtrados por ti). |
| loading | boolean | false | Muestra un spinner en vez de la tabla. |
| searchPlaceholder | string | "Buscar..." | Placeholder del input de búsqueda. |
| emptyMessage | string | — | Mensaje cuando no hay resultados (por defecto varía si hay o no una búsqueda escrita). |
| pageSize | number | 8 | Tamaño de página inicial. |
| pageSizeOptions | number[] | — | Igual que en IsuPagination. |
| siblingCount | number | 4 | Igual que en IsuPagination. |
Slots: columns (<th> del header), row({ item }) (<td> de cada fila), empty (mensaje vacío custom), footer({ close }) (por defecto muestra un botón "Cancelar").
Emits: update:modelValue(value: boolean), search(query: string), select(item: T)
IsuDataGrid
Tabla estandarizada con paginación integrada, para usar directamente en una vista (listados, grillas de detalle) — mismo estándar visual que IsuSearchPickerModal, pero sin el modal. Las columnas se definen como datos (título, alineación, formato) y el componente las renderiza automáticamente; para columnas especiales (badges, botones de acción, combinaciones de campos) hay un slot de escape por columna. Opcionalmente puede incluir su propio buscador interno (showSearch) que filtra en memoria los items ya cargados.
<script setup lang="ts">
import { ref } from "vue";
import type { DataGridColumn } from "integrasoft-shared-ui";
const seleccionado = ref<number | null>(null);
const columnas: DataGridColumn[] = [
{ key: "consecutivo", label: "Consecutivo" },
{ key: "tercero", label: "Tercero" },
{ key: "estado", label: "Estado", align: "center" }, // se llena con el slot cell-estado
{
key: "valor",
label: "Valor",
align: "right", // el contenido queda a la derecha...
alignTitle: "center", // ...pero el título queda centrado
format: "currency",
cellStyle: (item) => (item.valor > 5000000 ? { color: "var(--isu-color-danger)", fontWeight: "700" } : {}),
},
];
</script>
<template>
<!-- Listado con fila clickeable, estado resaltado, una columna con badge y buscador interno -->
<IsuDataGrid
:items="documentosFiltrados"
:columns="columnas"
show-search
search-placeholder="Buscar por consecutivo o tercero..."
:row-class="(doc) => ({ 'isu-data-grid__row--selected': doc.id === seleccionado })"
@row-click="(doc) => (seleccionado = doc.id)"
>
<template #cell-estado="{ item }">
<IsuBadge :label="item.estadoLabel" :variant="item.estadoVariant" />
</template>
</IsuDataGrid>
</template><!-- Grilla de detalle sin paginación, con columnas combinadas (accessor) y una columna de acciones -->
<script setup lang="ts">
const columnasDetalle: DataGridColumn[] = [
{ key: "combo", label: "Línea - Producto", accessor: (d) => `${d.linea} - ${d.producto}` },
{ key: "cantidad", label: "Cantidad", align: "right", format: "number" },
{ key: "valorUnitario", label: "Valor unitario", align: "right", format: "currency" },
{ key: "iva", label: "% Iva", align: "center", format: "percentage" },
{ key: "acciones", label: "Acciones", align: "center" },
];
</script>
<template>
<IsuDataGrid :items="detalles" :columns="columnasDetalle" :show-pagination="false" :clickable-rows="false">
<template #cell-acciones="{ index }">
<IsuButton size="sm" variant="secondary" :icon="faPen" @click="editar(index)" />
<IsuButton size="sm" variant="danger" :icon="faTrash" @click="eliminar(index)" />
</template>
</IsuDataGrid>
</template>El componente pagina internamente: recibe la lista completa (ya filtrada/buscada por ti) y solo muestra la porción de la página actual — igual que IsuSearchPickerModal, no reimplementes el "slice" en cada vista.
Definición de columna (DataGridColumn):
| Campo | Tipo | Default | Descripción |
|---|---|---|---|
| key | string | — | Se usa para leer item[key] (salvo que definas accessor) y para el slot de escape cell-<key>. |
| label | string | — | Título de la columna (header). |
| align | "left" \| "center" \| "right" | "left" | Alineación horizontal del contenido de la celda. |
| alignTitle | "left" \| "center" \| "right" | usa align | Alineación horizontal del título (header), independiente de align. Útil para, por ejemplo, centrar el título mientras el contenido queda a la derecha. |
| valign | "top" \| "middle" \| "bottom" | "middle" | Alineación vertical del contenido de la celda. |
| format | "text" \| "number" \| "currency" \| "percentage" | "text" | "currency": antepone $ y formatea con separador de miles y 2 decimales ($ 1.000.000,00). "percentage": agrega % al final. "number"/"text": el valor tal cual. |
| width | string | — | Ancho fijo de la columna, en cualquier unidad CSS (ej. "160px"). Tiene prioridad sobre widthPercent. |
| widthPercent | number | — | Ancho como porcentaje del total de la tabla (ej. 20 = 20%). Útil para repartir el ancho entre columnas sin calcular píxeles. Se ignora si defines width. |
| accessor | (item) => unknown | — | Función para combinar/transformar varios campos en una sola columna. Si no se define, se usa item[key] directamente. |
| cellStyle | Record<string, string \| number> \| (item) => Record<string, string \| number> | — | Estilos CSS extra para el contenido de la celda (no el título). Acepta un objeto fijo o una función por item — por ejemplo, para resaltar en rojo un valor que excede un límite. |
| field | DataGridColumnField | — | Si se define, la celda renderiza un IsuInput editable (bindeado a item[key]) en vez de texto/slot — ver abajo. |
Columnas editables (field), para grillas de detalle tipo "cantidad autorizada" o valores que el usuario corrige en línea:
<script setup lang="ts">
const columnas: DataGridColumn[] = [
{ key: "producto", label: "Producto" },
{
key: "cantAutorizada",
label: "Cant. autorizada",
align: "right",
field: { type: "number", filter: "number", placeholder: "0" },
},
{
key: "valorUnitario",
label: "Valor unitario",
align: "right",
field: { filter: "money" }, // "$ 1.000.000" formateado en vivo
},
];
</script>
<template>
<IsuDataGrid :items="detalles" :columns="columnas" @cell-change="(item, column, value) => recalcular(item)" />
</template>El componente muta item[key] directamente (los objetos de items deben ser reactivos, ej. definidos con ref/reactive) y además emite cell-change para que puedas reaccionar (recalcular totales, validar, etc.).
| Campo de DataGridColumnField | Tipo | Descripción |
|---|---|---|
| type | InputType de IsuInput | Tipo nativo del input ("text", "number", "date", etc.). |
| filter | InputFilter de IsuInput | Incluye "money" para valores monetarios editables en línea. |
| placeholder | string | Placeholder del input. |
| min / max | string \| number | Igual que en IsuInput. |
| disabled | boolean \| (item) => boolean | Deshabilita el input, fijo o condicionado por fila. |
Props del componente:
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| items | T[] | — | Lista completa ya filtrada (el grid pagina internamente). Al reemplazar el array (ej. tras filtrar) vuelve automáticamente a la página 1. |
| columns | DataGridColumn[] | — | Definición de columnas (ver arriba). |
| bordered | boolean | true | Muestra bordes verticales entre columnas. |
| rowClass | (item: T, index: number) => string \| string[] \| Record<string, boolean> | — | Clase(s) extra por fila. Usa isu-data-grid__row--selected o isu-data-grid__row--editing para los estados ya estilizados, o cualquier clase propia. |
| clickableRows | boolean | true | Si es true, las filas muestran cursor pointer y emiten row-click. |
| loading | boolean | false | Muestra un spinner en vez de la tabla. |
| emptyMessage | string | "No hay registros para mostrar." | Mensaje cuando items está vacío (o usa el slot empty). |
| showPagination | boolean | true | Oculta la paginación y muestra todos los items sin recortar (útil para grillas de detalle pequeñas). |
| pageSize | number | 8 | Tamaño de página inicial. |
| pageSizeOptions | number[] | — | Igual que en IsuPagination (selector fijo vs. input libre + "Mostrar"). |
| minPageSize | number | 1 | Límite inferior del input libre de tamaño de página (se reenvía a IsuPagination; no modifica los defaults propios de IsuPagination cuando se usa fuera de IsuDataGrid). |
| maxPageSize | number | 100 | Límite superior del input libre de tamaño de página (se reenvía a IsuPagination). |
| siblingCount | number | 2 | Igual que en IsuPagination. |
| showSearch | boolean | false | Muestra un IsuInput de búsqueda (con label "Buscar") arriba de la rejilla, que filtra en memoria los items ya cargados (no dispara ninguna petición externa). |
| searchPlaceholder | string | "Buscar..." | Placeholder del input de búsqueda (solo aplica si showSearch es true). |
El componente muestra una barra superior de color azul (mismo estilo que IsuSearchPickerModal) con el formato "Contar: {mostrados}/{total}": {mostrados} es la cantidad de filas realmente renderizadas en la página actual y {total} es la cantidad de items que coinciden con la búsqueda activa (si showSearch está desactivado, {total} es simplemente el total de items). Se oculta automáticamente cuando no hay resultados que mostrar.
Cuando showSearch está activo, la búsqueda compara el texto visible de todas las columnas (incluyendo columnas con accessor y formatos como currency/percentage, ya formateados), sin distinguir mayúsculas/minúsculas, y vuelve a la página 1 en cada cambio. Si la búsqueda no encuentra coincidencias se muestra "No se encontraron resultados.", distinto del emptyMessage que se usa cuando items está vacío desde el inicio. Limitación: el contenido que solo se renderiza a través de un slot cell-<key> (por ejemplo un IsuBadge custom) no participa en la búsqueda, porque esta se basa en el valor/formato de la columna, no en el HTML del slot.
Slots: cell-<key>({ item, index, value }) (uno por cada key de columna sin field — sustituye el valor formateado por contenido custom), empty (mensaje vacío custom).
Emits: row-click(item: T, index: number), cell-change(item: T, column: DataGridColumn, value: string) (columnas editables).
IsuSwitch
Interruptor on/off, con label y texto de estado ("Sí"/"No" por defecto) junto al switch.
<IsuSwitch v-model="tieneMedidor" label="Tiene medidor" />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | boolean | — | Estado del switch (v-model). |
| label | string | — | Etiqueta encima del switch. |
| required | boolean | false | Muestra asterisco rojo junto al label. |
| disabled | boolean | false | Deshabilita el switch. |
| onLabel | string | "Sí" | Texto mostrado junto al switch cuando está activado. |
| offLabel | string | "No" | Texto mostrado junto al switch cuando está desactivado. |
| showStateLabel | boolean | true | Oculta el texto de estado si es false. |
Emits: update:modelValue(value: boolean)
IsuDropdown
Selector con el mismo estilo visual que IsuInput (label, borde, foco, error), con modo de selección única o múltiple. En modo múltiple, cada opción muestra un checkbox y el panel permanece abierto entre selecciones.
<script setup lang="ts">
import { ref } from "vue";
import type { DropdownOption } from "integrasoft-shared-ui";
const estado = ref<string | number | null>(null);
const estados = ref<(string | number)[]>([]);
const opciones: DropdownOption[] = [
{ value: "prov", label: "Provisional" },
{ value: "firme", label: "Firme" },
{ value: "anulado", label: "Anulado", disabled: true },
];
</script>
<template>
<!-- Selección única -->
<IsuDropdown v-model="estado" :options="opciones" label="Estado" required />
<!-- Selección múltiple, con checkbox por opción -->
<IsuDropdown v-model="estados" :options="opciones" multiple label="Estados" />
</template>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| modelValue | string \| number \| (string \| number)[] \| null | — | Valor seleccionado (v-model). En modo multiple es un arreglo; en modo simple, un valor único o null. |
| options | DropdownOption[] | — | { value: string \| number, label: string, disabled?: boolean }. |
| multiple | boolean | false | Activa selección múltiple con checkbox por opción; el panel no se cierra al seleccionar. |
| label | string | — | Etiqueta encima del control. |
| placeholder | string | "Seleccione..." | Texto mostrado cuando no hay nada seleccionado. |
| required | boolean | false | Muestra asterisco rojo junto al label. |
| disabled | boolean | false | Deshabilita el control (no abre el panel). |
| error | string | — | Mensaje de error, con borde rojo en el control. |
| reserveErrorSpace | boolean | true | Reserva el espacio del mensaje de error para evitar saltos de layout. |
En modo multiple, si hay 3 o más opciones seleccionadas el control muestra "N seleccionados" en vez de listar todas las etiquetas (con 1 o 2 seleccionadas, las junta con ", "). El panel se cierra automáticamente al hacer clic fuera del componente.
Emits: update:modelValue(value: string | number | (string | number)[] | null)
IsuPageHeader
Encabezado de página estándar: ícono + título, con una ruta de navegación (breadcrumb) debajo. Todos los segmentos de la ruta son clickeables excepto el último, que se muestra como la página activa (en color primario, sin interacción). El componente no navega por sí mismo — emite breadcrumb-click con el ítem clickeado y su índice, y cada proyecto decide cómo navegar (vue-router, window.location, etc.), igual que search en IsuSearchPickerModal o row-click en IsuDataGrid.
<script setup lang="ts">
import { faClipboard } from "@fortawesome/free-solid-svg-icons";
import type { PageHeaderBreadcrumbItem } from "integrasoft-shared-ui";
import { useRouter } from "vue-router";
const router = useRouter();
const ruta: PageHeaderBreadcrumbItem[] = [
{ label: "ERP.go", to: "/" },
{ label: "Servicios públicos", to: "/servicios-publicos" },
{ label: "Procesos", to: "/servicios-publicos/procesos" },
{ label: "Confirmar orden de trabajo" }, // último = página activa, no lleva "to"
];
function onBreadcrumbClick(item: PageHeaderBreadcrumbItem) {
if (item.to) router.push(item.to as string);
}
</script>
<template>
<IsuPageHeader :icon="faClipboard" title="Órdenes de trabajo" :items="ruta" @breadcrumb-click="onBreadcrumbClick" />
</template>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| icon | IconDefinition (FontAwesome) | — | Ícono junto al título. |
| title | string | — | Título de la página. |
| items | PageHeaderBreadcrumbItem[] | — | Segmentos de la ruta, en orden: { label: string, to?: unknown }. to es un valor libre (string, objeto de ruta, etc.) que se devuelve tal cual en breadcrumb-click — el componente no lo interpreta. El último ítem del arreglo siempre se renderiza como la página activa, sin importar si tiene to. |
Emits: breadcrumb-click(item: PageHeaderBreadcrumbItem, index: number) — solo se emite al hacer clic en un segmento que no sea el último.
IsuTabs
Barra de pestañas para navegar entre secciones de una página. La pestaña activa se resalta (texto y borde inferior en color primario) y no es clickeable; el resto son clickeables. Igual que IsuPageHeader, el componente no navega por sí mismo — emite tab-click con el ítem y su índice, y cada proyecto decide cómo cambiar de página.
<script setup lang="ts">
import { faFileEdit, faList } from "@fortawesome/free-solid-svg-icons";
import type { TabItem } from "integrasoft-shared-ui";
import { useRouter } from "vue-router";
const router = useRouter();
const pestanias: TabItem[] = [
{ label: "Lista de documentos", icon: faList, active: true, to: "/documentos" },
{ label: "Elaboración de documentos", icon: faFileEdit, to: "/documentos/nuevo" },
];
function onTabClick(item: TabItem) {
if (item.to) router.push(item.to as string);
}
</script>
<template>
<IsuTabs :items="pestanias" @tab-click="onTabClick" />
</template>| Prop | Tipo | Default | Descripción |
|---|---|---|---|
| items | TabItem[] | — | Pestañas a mostrar: { label: string, icon?: IconDefinition, active?: boolean, to?: unknown }. active: true resalta la pestaña y la vuelve no clickeable. to es un valor libre (string, objeto de ruta, etc.) que se devuelve tal cual en tab-click — el componente no lo interpreta. |
Emits: tab-click(item: TabItem, index: number) — solo se emite al hacer clic en una pestaña que no tenga active: true.
Catálogo visual (Storybook)
El repositorio incluye Storybook con ejemplos interactivos de cada componente:
npm run storybookAbre http://localhost:6006.
Desarrollo local
npm install
npm run dev # playground local (src/App.vue) para probar componentes mientras se desarrollan
npm run test # tests unitarios (Vitest)
npm run build # genera dist/ (ESM + CJS + tipos + CSS)
npm run storybook # catálogo visual interactivoPublicar una nueva versión
npm run build
npm version patch # o minor / major según el cambio
npm publishPublicar requiere autenticación con
npm logino un token en la variable de entornoNPM_TOKEN(ver.npmrc). Si tu cuenta tiene 2FA, necesitas un Granular Access Token con "Bypass 2FA" habilitado, o pasar--otp=123456anpm publish.
