Apariencia
Catálogo de componentes
Los componentes de admin/src/components/ui son el kit de interfaz del panel. Un plugin los importa con @/components/ui/<Nombre>.vue. Esta página se genera del código: las props, los modelos, los eventos y los slots se leen de cada .vue en cada compilación del portal, y cada ejemplo se comprueba contra ellos (si un ejemplo usa una prop que el componente no declara, la compilación falla).
ts
import AppPage from "@/components/ui/AppPage.vue";
import UiButton from "@/components/ui/UiButton.vue";
import { t } from "@/i18n";Además de los componentes, components/ui ofrece types.ts (Column, Tone, Option, TabItem, MenuItem, FIELD_KEY), icons.ts (ICONS, IconName), focusTrap.ts (useFocusTrap) y dirtyClose.ts (useDirtyClose).
Tipos auxiliares:
ts
type Tone = "neutral" | "accent" | "success" | "danger" | "warning" | "info";
type Option<V> = { value: V; label: string; disabled?: boolean };
type Column = { key: string; label: string; align?: "left" | "right" | "center"; width?: string; mobile?: "title" | "hide" | "show"; nowrap?: boolean };
type TabItem = { id: string; label: string; count?: number | null };
type MenuItem = { label: string; icon?: IconName; tone?: "danger"; onSelect: () => void };Estructura de página
AppPage
Marco de toda pantalla: título, descripción, enlace de vuelta, acciones y cuerpo. Una página de plugin empieza siempre por él.
widthesdefault(1280 px),narrow(880 px, formularios de ajustes) owide.- Los slots
actionsytoolbarcolocan botones en la cabecera y filtros sobre el cuerpo.
Importación: import AppPage from "@/components/ui/AppPage.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
title * | string | ||
description | string | ||
back | { label: string; to: RouteLocationRaw } | ||
width | "default" | "narrow" | "wide" | Ancho: default (1280) o narrow (formularios de ajustes, 880). |
Slots
| Slot | Datos que recibe |
|---|---|
actions | — |
toolbar | — |
default | — |
Ejemplo
vue
<AppPage :title="t('Notas')" :description="t('Notas internas del local.')" width="narrow">
<template #actions><UiButton variant="primary" icon="plus">{{ t("Nueva nota") }}</UiButton></template>
<UiCard :title="t('Últimas notas')">…</UiCard>
</AppPage>Fuente: admin/src/components/ui/AppPage.vue.
UiCard
Panel con degradado suave y borde teñido con el acento. Contiene secciones, tablas o formularios.
flushquita el relleno (para tablas y listas que ocupan todo el ancho).toañade una flecha de enlace en la cabecera.
Importación: import UiCard from "@/components/ui/UiCard.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
title | string | |
subtitle | string | |
padded | boolean | true |
flush | boolean | |
to | RouteLocationRaw | null | null |
linkLabel | string |
Slots
| Slot | Datos que recibe |
|---|---|
header | — |
actions | — |
default | — |
footer | — |
Ejemplo
vue
<UiCard :title="t('Resumen')" :subtitle="t('Últimos 7 días')">
<UiDescList :columns="2">
<UiDesc :label="t('Notas')" :value="12" />
</UiDescList>
<template #footer><UiButton size="sm">{{ t("Ver todas") }}</UiButton></template>
</UiCard>Fuente: admin/src/components/ui/UiCard.vue.
UiSection
Subtítulo con descripción y acciones para dividir una página o una tarjeta en bloques.
Importación: import UiSection from "@/components/ui/UiSection.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
title * | string | |
description | string |
Slots
| Slot | Datos que recibe |
|---|---|
actions | — |
default | — |
Ejemplo
vue
<UiSection :title="t('Avisos')" :description="t('Quién recibe cada aviso.')">
<template #actions><UiButton size="sm" icon="plus">{{ t("Añadir") }}</UiButton></template>
…
</UiSection>Fuente: admin/src/components/ui/UiSection.vue.
UiToolbar
Fila única de filtros sobre el contenido que filtran: búsqueda y selectores a la izquierda, acciones en #side.
Importación: import UiToolbar from "@/components/ui/UiToolbar.vue".
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
side | — |
Ejemplo
vue
<UiToolbar>
<UiSearch v-model="q" />
<UiSelect v-model="state" :options="stateOptions" />
<template #side><UiButton icon="download">{{ t("Exportar") }}</UiButton></template>
</UiToolbar>Fuente: admin/src/components/ui/UiToolbar.vue.
Datos y listas
UiDesc
Par etiqueta-valor de una UiDescList. Sin valor pinta una raya; el slot por defecto admite contenido propio.
Importación: import UiDesc from "@/components/ui/UiDesc.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
label * | string | |
value | string | number | null | |
mono | boolean |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiDesc :label="t('Estado')"><UiBadge tone="success">{{ t("Activo") }}</UiBadge></UiDesc>Fuente: admin/src/components/ui/UiDesc.vue.
UiDescList
Lista de descripción en 1, 2 o 3 columnas para fichas de solo lectura. Sus hijos son UiDesc.
Importación: import UiDescList from "@/components/ui/UiDescList.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
columns | 1 | 2 | 3 | 2 |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiDescList :columns="3">
<UiDesc :label="t('Autor')" :value="n.author" />
<UiDesc :label="t('Clave')" :value="n.id" mono />
</UiDescList>Fuente: admin/src/components/ui/UiDescList.vue.
UiIcon
Icono del panel (trazo 1,75 sobre rejilla 24x24). El nombre es uno de los de icons.ts; ver el catálogo en la guía de diseño.
Importación: import UiIcon from "@/components/ui/UiIcon.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
name * | IconName | |
size | number | string | 18 |
Ejemplo
vue
<UiIcon name="thermometer" :size="18" />Fuente: admin/src/components/ui/UiIcon.vue.
UiLinkList
Lista de filas con icono, texto, valor, insignia y enlace: índices de ajustes y resúmenes. Va dentro de una UiCard flush.
Importación: import UiLinkList from "@/components/ui/UiLinkList.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
items * | Array<{ key: string; icon: IconName; label: string; description?: string; value?: string; badge?: { text: string; tone: Tone } | null; to?: RouteLocationRaw; }> |
Ejemplo
vue
<UiCard flush>
<UiLinkList :items="[{ key: 'a', icon: 'settings', label: t('Ajustes'), description: t('Opciones del módulo'), to: '/notas/ajustes' }]" />
</UiCard>Fuente: admin/src/components/ui/UiLinkList.vue.
UiList
Contenedor de filas separadas por líneas finas dentro de un panel. Su contenido son UiListItem.
Importación: import UiList from "@/components/ui/UiList.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
bare | boolean | |
tag | "ul" | "ol" | "div" | "ul" |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiList>
<UiListItem v-for="n in notes" :key="n.id" :title="n.title" :description="n.body" icon="message" />
</UiList>Fuente: admin/src/components/ui/UiList.vue.
UiListItem
Fila de UiList: asa opcional, icono o muestra de color, título y texto secundario, meta a la derecha y acciones.
toconvierte la zona principal en enlace;clickableen botón (emiteopen).- Las acciones se ven al pasar por encima o al enfocar la fila; en táctil, siempre.
Importación: import UiListItem from "@/components/ui/UiListItem.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
title | string | ||
description | string | null | ||
icon | IconName | ||
swatch | string | null | Color de la muestra (categorías, etiquetas) en lugar del icono. | |
meta | string | null | ||
to | RouteLocationRaw | ||
clickable | boolean | ||
handle | boolean | ||
chevron | boolean | ||
active | boolean | ||
muted | boolean | ||
actionsVisible | boolean | Acciones siempre visibles (no solo al pasar por encima). |
Eventos
| Evento | Argumentos |
|---|---|
open |
Slots
| Slot | Datos que recibe |
|---|---|
leading | — |
title | — |
description | — |
body | — |
meta | — |
actions | — |
Ejemplo
vue
<UiListItem :title="n.title" :description="n.body" icon="message" :meta="date(n.createdAt)" clickable @open="open(n)">
<template #actions><UiButton variant="ghost" size="sm" icon="trash" :label="t('Borrar')" /></template>
</UiListItem>Fuente: admin/src/components/ui/UiListItem.vue.
UiSortHandle
Asa y botones de orden que usan UiTable y UiSortableList por dentro. Solo se usa directamente al construir una lista ordenable propia.
Importación: import UiSortHandle from "@/components/ui/UiSortHandle.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
grip | boolean | true | Muestra el asa (clase ui-sortable__grip, la que agarra Sortable). |
moveButtons | boolean | true | |
disabled | boolean | false | |
canUp | boolean | true | |
canDown | boolean | true | |
describedby | string | Descripción compartida («Arrastra el asa o pulsa Alt + ↑/↓…»). |
Eventos
| Evento | Argumentos |
|---|---|
move | dir: "up" | "down" |
Ejemplo
vue
<UiSortHandle :can-up="index > 0" :can-down="index < last" @move="(dir) => move(index, dir)" />Fuente: admin/src/components/ui/UiSortHandle.vue.
UiSortableList
Lista ordenable por arrastre (SortableJS) con teclado (Alt + flechas) y botones subir/bajar. reorder es el momento de guardar.
Importación: import UiSortableList from "@/components/ui/UiSortableList.vue". Genérico: T.
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
modelValue * | T[] | ||
itemKey * | string | ((item: T) => string | number) | Campo (o función) que da la clave única de cada elemento. | |
handle | string | false | undefined | Selector del asa. Sin valor: asa ⋮⋮ integrada. false: toda la fila arrastra. |
disabled | boolean | false | |
group | string | Mismo nombre en varias listas = se puede arrastrar entre ellas. | |
moveButtons | boolean | true | Botones subir/bajar accesibles en cada fila. |
forceFallback | boolean | false | Para pantallas táctiles problemáticas: arrastre simulado en lugar del nativo. |
itemLabel | (item: T) => string | Nombre del elemento para los avisos de lectores de pantalla («Mesa 3 movido…»). | |
ariaLabel | string | ||
tag | string | "ul" | |
itemTag | string | "li" |
Eventos
| Evento | Argumentos |
|---|---|
update:modelValue | value: T[] |
reorder | ids: string[], ctx: { previous: T[]; next: T[] } |
change | change: SortableChange<T> |
Slots
| Slot | Datos que recibe |
|---|---|
item | item, index, move |
Ejemplo
vue
<UiSortableList v-model="items" item-key="id" @reorder="(ids) => save(ids)">
<template #item="{ item }">{{ item.name }}</template>
</UiSortableList>Fuente: admin/src/components/ui/UiSortableList.vue.
UiStat
Cifra con etiqueta y variación frente a un periodo con nombre. delta es la variación porcentual; lowerIsBetter invierte el color.
Importación: import UiStat from "@/components/ui/UiStat.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
label * | string | |
value * | string | |
delta | number | null | |
deltaLabel | string | |
hint | string | |
to | RouteLocationRaw | |
loading | boolean | |
lowerIsBetter | boolean | |
hero | boolean |
Slots
| Slot | Datos que recibe |
|---|---|
info | — |
Ejemplo
vue
<UiStat :label="t('Ventas')" value="1.240 €" :delta="8.5" :delta-label="t('vs. semana anterior')" />Fuente: admin/src/components/ui/UiStat.vue.
UiStats
Rejilla adaptable para varias UiStat. min fija el ancho mínimo de cada una.
Importación: import UiStats from "@/components/ui/UiStats.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
min | string | "190px" |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiStats min="200px">
<UiStat :label="t('Pedidos')" value="48" />
<UiStat :label="t('Ticket medio')" value="12,40 €" :hint="t('Hoy')" />
</UiStats>Fuente: admin/src/components/ui/UiStats.vue.
UiTable
Tabla única del panel: cabecera, filas, esqueleto de carga, estado vacío y «Cargar más». En móvil se convierte en tarjetas.
- Celdas personalizadas con el slot
#cell-<clave>="{ row }". selectableconv-model:selectedañade casillas y la barra#bulk;sortableañade asa de arrastre y emitereorder.- Columnas:
{ key, label, align?, width?, mobile?: 'title' | 'hide' | 'show', nowrap? }.
Importación: import UiTable from "@/components/ui/UiTable.vue". Genérico: T extends Record<string, any>.
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
columns * | Column[] | ||
rows * | T[] | ||
rowKey * | string | ((row: T) => string | number) | ||
loading | boolean | false | |
clickable | boolean | false | |
activeKey | string | number | null | null | |
hasMore | boolean | false | |
loadingMore | boolean | false | |
dense | boolean | false | |
bare | boolean | false | Sin marco (dentro de una tarjeta o un panel). |
selectable | boolean | false | Casilla por fila y barra de acciones masivas (v-model:selected). |
sortable | boolean | false | Asa de arrastre por fila + reordenar con teclado. |
sortDisabled | boolean | false | |
moveButtons | boolean | false | Botones subir/bajar junto al asa (útiles en tablet). |
rowLabel | (row: T) => string | Nombre de la fila para los avisos de lectores de pantalla. |
v-model
| Modelo | Tipo |
|---|---|
v-model:selected | Array<string | number> |
Eventos
| Evento | Argumentos |
|---|---|
rowClick | row: T |
loadMore | |
reorder | ids: string[], ctx: { previous: T[]; next: T[] } |
change | change: SortableChange<T> |
Slots
| Slot | Datos que recibe |
|---|---|
bulk | count, clear |
cell-<clave> | row |
empty | — |
footer | — |
Ejemplo
vue
<UiTable :columns="[{ key: 'title', label: t('Título'), mobile: 'title' }, { key: 'status', label: t('Estado') }]" :rows="rows" row-key="id" :loading="loading" clickable @row-click="open">
<template #cell-status="{ row }"><StatusBadge :of="STATUS" :value="row.status" /></template>
<template #empty><UiEmpty :title="t('Sin notas')" /></template>
</UiTable>Fuente: admin/src/components/ui/UiTable.vue.
UiThumb
Miniatura única (productos, portadas): si la imagen falta o no carga, muestra un bloque neutro.
Importación: import UiThumb from "@/components/ui/UiThumb.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
src * | string | null | undefined | |
size | number | 36 |
ratio | number | 1 |
fallbackColor | string | null |
Ejemplo
vue
<UiThumb :src="product.imageUrl" :size="40" />Fuente: admin/src/components/ui/UiThumb.vue.
Formularios
UiCheckbox
Casilla con estado intermedio opcional (indeterminate). Sin texto visible, exige aria-label.
Importación: import UiCheckbox from "@/components/ui/UiCheckbox.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
indeterminate | boolean | |
ariaLabel | string | |
disabled | boolean |
v-model
| Modelo | Tipo |
|---|---|
v-model | boolean |
Ejemplo
vue
<UiCheckbox v-model="form.accepted" :aria-label="t('Acepto')" />Fuente: admin/src/components/ui/UiCheckbox.vue.
UiChoiceChips
Selección múltiple con fichas (días, canales, alérgenos). El modelo conserva el orden de options, no el de los clics.
Importación: import UiChoiceChips from "@/components/ui/UiChoiceChips.vue". Genérico: V extends string | number.
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
options * | Option<V>[] | |
ariaLabel | string | |
disabled | boolean | |
invalid | boolean |
v-model
| Modelo | Tipo |
|---|---|
v-model | V[] |
Ejemplo
vue
<UiChoiceChips v-model="form.days" :options="[{ value: 1, label: t('Lun') }, { value: 2, label: t('Mar') }]" />Fuente: admin/src/components/ui/UiChoiceChips.vue.
UiField
Envoltorio de un control de formulario: etiqueta enlazada al control, ayuda, error y marca de obligatorio u opcional.
- Los controles del kit (UiInput, UiSelect, UiTextarea, UiChoiceChips) leen su
idy su estado de error de aquí.
Importación: import UiField from "@/components/ui/UiField.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
label | string | |
hint | string | |
error | string | null | |
required | boolean | |
optional | boolean |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiField :label="t('Título')" :error="errors.title" required>
<UiInput v-model="form.title" :maxlength="80" />
</UiField>Fuente: admin/src/components/ui/UiField.vue.
UiFileInput
Selector de un fichero. El modelo es File | null; accept limita los tipos.
Importación: import UiFileInput from "@/components/ui/UiFileInput.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
accept | string | |
placeholder | string | |
disabled | boolean |
v-model
| Modelo | Tipo |
|---|---|
v-model | File | null |
Ejemplo
vue
<UiFileInput v-model="file" accept="image/png" :placeholder="t('Elige una imagen')" />Fuente: admin/src/components/ui/UiFileInput.vue.
UiInput
Campo de texto. Tipos habituales de HTML, con prefijo, sufijo, icono y slot end.
Importación: import UiInput from "@/components/ui/UiInput.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
type | "text" | "email" | "password" | "number" | "date" | "time" | "datetime-local" | "tel" | "url" | "search" | "color" | "text" |
placeholder | string | |
prefix | string | |
suffix | string | |
icon | IconName | |
invalid | boolean | |
disabled | boolean | |
readonly | boolean | |
min | number | string | |
max | number | string | |
step | number | string | |
maxlength | number | |
autocomplete | string | |
inputmode | "text" | "numeric" | "decimal" | "email" | "tel" | "search" | |
mono | boolean | |
autofocus | boolean | |
ariaLabel | string |
v-model
| Modelo | Tipo |
|---|---|
v-model | string | number | null |
Slots
| Slot | Datos que recibe |
|---|---|
end | — |
Ejemplo
vue
<UiInput v-model="form.email" type="email" icon="mail" :placeholder="t('nombre@ejemplo.com')" />Fuente: admin/src/components/ui/UiInput.vue.
UiMoneyInput
Campo de importe cuyo modelo son céntimos enteros (number | null); muestra y entiende el formato local.
Importación: import UiMoneyInput from "@/components/ui/UiMoneyInput.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
placeholder | string | |
invalid | boolean | |
disabled | boolean | |
ariaLabel | string |
v-model
| Modelo | Tipo |
|---|---|
v-model | number | null |
Ejemplo
vue
<UiMoneyInput v-model="form.priceCents" :aria-label="t('Precio')" />Fuente: admin/src/components/ui/UiMoneyInput.vue.
UiSearch
Campo de búsqueda con retardo (debounce, 250 ms por defecto) que actualiza el modelo cuando el usuario deja de escribir.
Importación: import UiSearch from "@/components/ui/UiSearch.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
placeholder | string | |
debounce | number | 250 |
v-model
| Modelo | Tipo |
|---|---|
v-model | string |
Ejemplo
vue
<UiSearch v-model="q" :placeholder="t('Buscar notas')" />Fuente: admin/src/components/ui/UiSearch.vue.
UiSelect
Selector de opción única. Las opciones son { value, label, disabled? }.
Importación: import UiSelect from "@/components/ui/UiSelect.vue". Genérico: V extends string | number | null.
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
options * | Option<V>[] | |
icon | IconName | |
invalid | boolean | |
disabled | boolean | |
ariaLabel | string |
v-model
| Modelo | Tipo |
|---|---|
v-model | V |
Ejemplo
vue
<UiSelect v-model="form.category" :options="[{ value: 'ops', label: t('Operación') }, { value: 'mkt', label: t('Marketing') }]" />Fuente: admin/src/components/ui/UiSelect.vue.
UiSwitch
Interruptor de encendido y apagado con etiqueta y descripción opcionales.
Importación: import UiSwitch from "@/components/ui/UiSwitch.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
label | string | ||
description | string | ||
disabled | boolean | ||
ariaLabel | string | Nombre accesible cuando el interruptor va sin texto visible. |
v-model
| Modelo | Tipo |
|---|---|
v-model | boolean |
Ejemplo
vue
<UiSwitch v-model="form.notify" :label="t('Avisar al equipo')" :description="t('Se envía una notificación al guardar.')" />Fuente: admin/src/components/ui/UiSwitch.vue.
UiTextarea
Área de texto de varias líneas; mono usa la tipografía de código.
Importación: import UiTextarea from "@/components/ui/UiTextarea.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
rows | number | 3 |
placeholder | string | |
invalid | boolean | |
maxlength | number | |
mono | boolean |
v-model
| Modelo | Tipo |
|---|---|
v-model | string | null |
Ejemplo
vue
<UiTextarea v-model="form.body" :rows="4" :maxlength="500" />Fuente: admin/src/components/ui/UiTextarea.vue.
Acciones y navegación
UiButton
Botón único del panel. Variantes primary, secondary (por defecto), ghost, danger y danger-ghost.
- Con
tose pinta como enlace del router; conhref, como enlace externo. - Solo icono: pasa
iconylabel(el texto accesible) y no pongas contenido.
Importación: import UiButton from "@/components/ui/UiButton.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "danger" | "danger-ghost" | "secondary" | |
size | "sm" | "md" | "md" | |
icon | IconName | ||
iconRight | IconName | ||
loading | boolean | ||
disabled | boolean | ||
type | "button" | "submit" | "button" | |
to | RouteLocationRaw | ||
href | string | ||
block | boolean | ||
label | string | Solo icono: exige label para accesibilidad. |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiButton variant="primary" icon="check" :loading="saving" @click="save">{{ t("Guardar") }}</UiButton>
<UiButton variant="ghost" size="sm" icon="trash" :label="t('Borrar')" />Fuente: admin/src/components/ui/UiButton.vue.
UiMenu
Menú desplegable de acciones. Cada elemento lleva label, onSelect y, opcionalmente, icon y tone: 'danger'.
Importación: import UiMenu from "@/components/ui/UiMenu.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
items * | MenuItem[] | |
align | "left" | "right" | "right" |
Slots
| Slot | Datos que recibe |
|---|---|
trigger | open, toggle |
header | — |
Ejemplo
vue
<UiMenu :items="[{ label: t('Editar'), icon: 'edit', onSelect: edit }, { label: t('Borrar'), icon: 'trash', tone: 'danger', onSelect: remove }]" />Fuente: admin/src/components/ui/UiMenu.vue.
UiSegmented
Control segmentado de opción única (un radiogroup). Para pocas opciones visibles a la vez.
Importación: import UiSegmented from "@/components/ui/UiSegmented.vue". Genérico: V extends string | number | null.
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
options * | Option<V>[] | |
ariaLabel | string |
v-model
| Modelo | Tipo |
|---|---|
v-model | V |
Ejemplo
vue
<UiSegmented v-model="range" :options="[{ value: 'day', label: t('Día') }, { value: 'week', label: t('Semana') }]" :aria-label="t('Periodo')" />Fuente: admin/src/components/ui/UiSegmented.vue.
UiTabs
Pestañas de una misma pantalla. El modelo es el id de la pestaña activa; count muestra un contador.
Importación: import UiTabs from "@/components/ui/UiTabs.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
tabs * | TabItem[] |
v-model
| Modelo | Tipo |
|---|---|
v-model | string |
Ejemplo
vue
<UiTabs v-model="tab" :tabs="[{ id: 'open', label: t('Abiertas'), count: 3 }, { id: 'done', label: t('Cerradas') }]" />Fuente: admin/src/components/ui/UiTabs.vue.
Avisos y estados
StatusBadge
UiBadge a partir de un diccionario de estados: of mapea cada valor a { label, tone }. Un valor ausente pinta una raya.
Importación: import StatusBadge from "@/components/ui/StatusBadge.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
of * | Record<string, { label: string; tone: Tone }> | |
value * | string | boolean | null | undefined | |
dot | boolean |
Ejemplo
vue
<StatusBadge :of="{ open: { label: t('Abierta'), tone: 'info' }, done: { label: t('Cerrada'), tone: 'success' } }" :value="note.status" dot />Fuente: admin/src/components/ui/StatusBadge.vue.
UiBadge
Etiqueta corta con tono (neutral, accent, success, danger, warning, info). El color solo transmite significado.
Importación: import UiBadge from "@/components/ui/UiBadge.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
tone | Tone | "neutral" |
dot | boolean | |
size | "sm" | "md" | "md" |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiBadge tone="success" dot>{{ t("Activo") }}</UiBadge>
<UiBadge tone="warning" size="sm">{{ t("Pendiente") }}</UiBadge>Fuente: admin/src/components/ui/UiBadge.vue.
UiCallout
Aviso en línea dentro de la página (información, éxito, atención, error). Lleva título, texto opcional y acciones.
Importación: import UiCallout from "@/components/ui/UiCallout.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
tone | Exclude<Tone, "accent"> | "info" |
title * | string | |
icon | IconName |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
actions | — |
Ejemplo
vue
<UiCallout tone="warning" :title="t('Sin permisos')">{{ t("Pide acceso a un administrador.") }}</UiCallout>Fuente: admin/src/components/ui/UiCallout.vue.
UiEmpty
Estado vacío: explica qué es la pantalla y ofrece el primer paso en el slot por defecto.
Importación: import UiEmpty from "@/components/ui/UiEmpty.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
icon | IconName | "sparkles" |
title * | string | |
text | string | |
compact | boolean |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
Ejemplo
vue
<UiEmpty icon="message" :title="t('Aún no hay notas')" :text="t('Crea la primera para tu equipo.')">
<UiButton variant="primary" icon="plus">{{ t("Nueva nota") }}</UiButton>
</UiEmpty>Fuente: admin/src/components/ui/UiEmpty.vue.
UiLoadError
Estado de error de carga con botón de reintento. Recibe el ApiError de useResource y emite retry.
Importación: import UiLoadError from "@/components/ui/UiLoadError.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
error * | ApiError | null | |
compact | boolean |
Eventos
| Evento | Argumentos |
|---|---|
retry |
Ejemplo
vue
<UiLoadError v-if="notes.error.value" :error="notes.error.value" @retry="notes.reload" />Fuente: admin/src/components/ui/UiLoadError.vue.
UiSkeleton
Marcador animado mientras carga un dato. Tamaño y radio configurables.
Importación: import UiSkeleton from "@/components/ui/UiSkeleton.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
width | string | "100%" |
height | string | "14px" |
radius | string | "6px" |
Ejemplo
vue
<UiSkeleton width="60%" height="28px" />Fuente: admin/src/components/ui/UiSkeleton.vue.
Ventanas y paneles
UiDrawer
Panel lateral para ver y editar sin perder la lista: cabecera (título, subtítulo, metadatos, acciones), pestañas, cuerpo y pie.
Importación: import UiDrawer from "@/components/ui/UiDrawer.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto |
|---|---|---|
open * | boolean | |
title | string | |
subtitle | string | |
size | "md" | "lg" | "md" |
Eventos
| Evento | Argumentos |
|---|---|
close |
Slots
| Slot | Datos que recibe |
|---|---|
title | — |
meta | — |
actions | — |
tabs | — |
default | — |
footer | — |
Ejemplo
vue
<UiDrawer :open="!!current" :title="current?.title" size="lg" @close="current = null">
…
<template #footer><UiButton variant="primary" @click="save">{{ t("Guardar") }}</UiButton></template>
</UiDrawer>Fuente: admin/src/components/ui/UiDrawer.vue.
UiModal
Ventana modal con foco atrapado. Con cambios sin guardar, un clic fuera o Escape piden confirmación (salvo plain-close). Emite close.
Importación: import UiModal from "@/components/ui/UiModal.vue".
Props (con *, obligatorias)
| Prop | Tipo | Defecto | Descripción |
|---|---|---|---|
open * | boolean | ||
title | string | ||
size | "sm" | "md" | "lg" | "md" | |
elevated | boolean | Por encima de cualquier otra ventana (confirmaciones que se abren desde dentro de otra). | |
plainClose | boolean | Cierra sin preguntar aunque se haya escrito algo (cobros: nada que perder). |
Eventos
| Evento | Argumentos |
|---|---|
close |
Slots
| Slot | Datos que recibe |
|---|---|
default | — |
footer | — |
Ejemplo
vue
<UiModal :open="open" :title="t('Nueva nota')" size="sm" @close="open = false">
<UiField :label="t('Título')"><UiInput v-model="form.title" /></UiField>
<template #footer><UiButton variant="primary" @click="save">{{ t("Guardar") }}</UiButton></template>
</UiModal>Fuente: admin/src/components/ui/UiModal.vue.
Montados por el panel
ConfirmHost
Ventana de confirmación única del panel. NO se monta en un plugin: lo hace el AppShell. Se usa con useFeedback().confirm().
Importación: import ConfirmHost from "@/components/ui/ConfirmHost.vue".
Ejemplo
ts
const fb = useFeedback();
if (await fb.confirm({ title: t("¿Borrar la nota?"), tone: "danger", confirmLabel: t("Borrar") })) await remove();Fuente: admin/src/components/ui/ConfirmHost.vue.
UiToaster
Avisos efímeros (toasts). NO se monta en un plugin: lo hace el AppShell. Se usan con useFeedback().toast() y useFeedback().error().
Importación: import UiToaster from "@/components/ui/UiToaster.vue".
Ejemplo
ts
const fb = useFeedback();
fb.toast(t("Nota guardada"));
fb.error(err); // mensaje traducido por el código del ApiErrorFuente: admin/src/components/ui/UiToaster.vue.