Migración
Esta guía cubre la migración de Spartan 2.x (TailwindCSS 3) a Spartan 3.x (TailwindCSS 4). La migración implica pasar del sistema de plugins en JavaScript a una configuración íntegramente basada en CSS.
Prerequisitos
- Node.js
20.19o superior - NPM
^10 - Un proyecto existente que use Spartan 2.x
Pasos de migración
Remover el plugin de Spartan
En tu tailwind.config.js, elimina la ruta de contenido de Spartan y su plugin:
module.exports = {
content: [
'./src/**/*.{vue,js,ts,jsx,tsx}',
- 'node_modules/@placetopay/spartan-vue/dist/**/*.js',
],
plugins: [
- require('@placetopay/spartan-vue/plugin'),
],
}
Ejecutar la herramienta de actualización de TailwindCSS
Usa la herramienta oficial de migración de TailwindCSS para actualizar tu proyecto automáticamente:
npx @tailwindcss/upgrade
pnpm dlx @tailwindcss/upgrade
yarn dlx @tailwindcss/upgrade
Esta herramienta se encarga automáticamente de:
- Migrar tu
tailwind.config.jsa configuración basada en CSS - Actualizar las clases de utilidades que cambiaron
- Identificar incompatibilidades
Remover la configuración de PostCSS (si la usas)
Si estabas usando PostCSS con TailwindCSS 3, elimina el archivo de configuración y las dependencias:
rm postcss.config.js
{
"devDependencies": {
- "@tailwindcss/postcss": "^4.1.18",
- "postcss": "^8.4.33",
}
}
Instalar el plugin de Vite de TailwindCSS (si usas Vite)
Si tu proyecto usa Vite, instala el plugin oficial de TailwindCSS para Vite:
npm install -D @tailwindcss/vite
pnpm add -D @tailwindcss/vite
yarn add --dev @tailwindcss/vite
Y agrégalo a tu configuración de Vite:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
vue(),
tailwindcss(),
],
})
Actualizar Spartan a 3.x
Instala la última versión de Spartan:
npm install @placetopay/spartan-vue@latest
pnpm add @placetopay/spartan-vue@latest
yarn add @placetopay/spartan-vue@latest
Actualizar los imports de estilos
Hay dos cambios en cómo se importan los estilos. Primero, remove el import de CSS que tenías en tu entry point:
- import '@placetopay/spartan-vue/style.css'
import { createApp } from 'vue'
import App from './App.vue'
Luego, agrega el import de Spartan en tu archivo de estilos CSS principal:
@import "@placetopay/spartan-vue/styles.css";
@source '../src/**/*.{vue,js,ts,jsx,tsx}';
E importa ese archivo de estilos desde tu entry point:
import './styles.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Actualizar imports de colores de Tailwind (si los usas)
TailwindCSS 4 cambió la forma de exportar colores. Si importabas colores de Tailwind en archivos JS/TS:
- // Tailwind 3 (ya no funciona)
- import { red, blue } from 'tailwindcss/colors'
+ // Tailwind 4
+ import colors from 'tailwindcss/colors'
+
+ const red = colors.red
+ const blue = colors.blue
Verificar y testear
Realiza un build y levanta el servidor de desarrollo para validar que todo funciona:
npm run build
npm run dev
Revisa visualmente tu aplicación para asegurarte de que todos los estilos se aplican correctamente.
Breaking changes
TailwindCSS 4
- Configuración: Ahora se hace desde CSS con
@theme,@plugin, entre otros, en lugar detailwind.config.js - Dark mode: Se configura mediante
@custom-varianten CSS - Fuentes de contenido: Se usa la directiva
@sourceen CSS en lugar del arraycontenten el archivo de configuración - Utilidad
ring:ring-3ya no es válido, usarringcon ancho personalizado si es necesario
Spartan 3.x
- Sin plugin de JavaScript: Toda la configuración se hace desde CSS
- Imports simplificados: Un solo
@importdestyles.cssincluye todo - Peer dependencies actualizados: Requiere TailwindCSS 4.x
SComboboxeliminado:SComboboxySComboboxBlock(junto conSComboboxOptionySComboboxOptionGroup) fueron eliminados. UsaSSelectorySSelectorBlockcomo reemplazo — cubren los mismos casos de uso de selección de un valor con búsqueda opcional, con una API mejorada y consistencia visual con el resto de la librería.- Variantes de
SButtonseparadas:outlineylinkpasaron de ser valores devarianta modificadores booleanos. Ver SButton. SFilterreescrito:fieldspasa afilters+v-model, y todos los tipos exportados se renombraron. Ver SFilter.SCard.iconreducido:'primary'y'secondary'ya no son presets deicon. Ver SCard.- Slot de
SSelectorBlockrenombrado:options→option. Ver SSelectorBlock. SModalya no emiteclose: se disparaba junto aupdate:openen el mismo gatillo (clic en el backdrop) sin aportar nada, y chocaba conSModalLeft/SModalSide, dondeclosesignifica el botón X. Escucha@update:openen su lugar:<SModal @close="...">→<SModal @update:open="open = $event">.- Subpath
plugineliminado:@placetopay/spartan-vue/pluginya no existe; en 3.x no hay plugin JavaScript de Tailwind.
- <SCombobox v-model="value" :display-button-text="getLabel">
- <SComboboxOption :value="1">Opción 1</SComboboxOption>
- <SComboboxOption :value="2">Opción 2</SComboboxOption>
- </SCombobox>
+ <SSelector v-model="value" :options="options" />
SButton
Motivación
La API anterior de SButton usaba un enum plano de variant (primary | secondary | danger | outline | link) que mezclaba la intención de color con modificadores de presentación. Esto hacía imposible crear combinaciones como un botón danger outline o un botón secondary link — cada combinación requería su propio valor de variante, generando una API rígida que no podía representar todos los estados definidos en el sistema de diseño de Figma.
Ventajas
- Modificadores composables:
variantdefine la paleta de color (primary,secondary,danger), mientras queoutline,linkycircularson modificadores booleanos independientes que se pueden combinar libremente - 9+ combinaciones nuevas:
variant="danger" outline,variant="secondary" link,variant="danger" linky más — todas previamente imposibles - Alineación con Figma: El nuevo modelo coincide directamente con la estructura del sistema de diseño, eliminando la brecha entre diseño e implementación
- Nuevo modo
circular: Botones de solo icono con forma perfectamente redonda, ideales para barras de herramientas y UIs compactas
Migración
outline y link ya no son valores de la prop variant. Ahora son props booleanas independientes:
- <SButton variant="outline">Guardar</SButton>
+ <SButton outline>Guardar</SButton>
- <SButton variant="link">Cancelar</SButton>
+ <SButton link>Cancelar</SButton>
Ambos modificadores usan por defecto la variante primary, preservando el comportamiento visual anterior sin cambios adicionales.
Nuevas combinaciones disponibles:
<!-- Danger outline (antes imposible) -->
<SButton variant="danger" outline>Eliminar</SButton>
<!-- Secondary link / transparente (antes imposible) -->
<SButton variant="secondary" link>Cancelar</SButton>
<!-- Botón circular con icono (nuevo) -->
<SButton circular :icon="AddIcon" />
SFilter
Motivación
SFilter recibía un arreglo fields y lo mutaba en el sitio para guardar el estado de los filtros activos. El arreglo del consumidor era a la vez la fuente de verdad y el borrador interno del componente, así que no había forma de leer los filtros actuales de forma reactiva, reiniciarlos o hidratarlos desde una URL sin meter mano en el mismo arreglo que el componente estaba escribiendo. Las capacidades de cada campo se describían con un objeto interfaces sin tipar, lo que permitía asociar cualquier operador a cualquier campo.
Ventajas
v-model: el estado es unRecord<string, { operator, value }>plano y serializable, propiedad del consumidor. Nada se muta a tus espaldas.- Campos tipados: cada campo declara un
type, y susoperatorsquedan restringidos a los válidos para ese tipo. - Definición estática:
filtersdescribe qué se puede filtrar;modelValueguarda qué está filtrado ahora. Dejan de estar entrelazados.
Migración
El arreglo fields se convierte en un registro filters indexado por el id del campo, y el estado se mueve a v-model:
- <SFilter :fields="fields" @apply="onApply" />
+ <SFilter v-model="value" :filters="filters" @apply="onApply" />
- const fields = ref([
- {
- id: 'name',
- name: 'Nombre',
- interfaces: { oneInput: { type: 'text' } },
- state: { operator: { id: 'contains' }, value: 'ana' },
- },
- ]);
+ const filters = {
+ name: { type: 'text', label: 'Nombre' },
+ };
+ const value = ref({
+ name: { operator: 'contains', value: 'ana' },
+ });
Cambios en la forma del campo:
Antes (TField) | Ahora (SFilterField) |
|---|---|
id | la clave del registro |
name | label |
interfaces | type ('text' | 'number' | 'amount' | 'date' | 'dateRange' | 'options' | 'selection') |
state | la entrada correspondiente en modelValue |
validate(value, operator: TOperator) | validate(value, operator: string) |
permanent | permanent (sin cambios) |
Los payloads de los eventos también cambiaron:
- @apply="(fields: TField[]) => ..."
+ @apply="(value: SFilterValue) => ..." // Record<string, { operator, value }>
- @clear="(fields: TField[]) => ..."
+ @clear="(value: SFilterValue) => ..."
- @save="(data: TSaveData[]) => ..." // { name, filters }[]
+ @save="(name: string, snapshot: SFilterValue) => ..."
- @load="(filters: TSaveData['filters']) => ..."
+ @load="(snapshot: SFilterValue) => ..."
Se añade un evento delete que emite el nombre del filtro guardado que se elimina, y saved ahora recibe SFilterSaved[] ({ name, snapshot }) en lugar de { name, filters }.
Todos los tipos T* que exportaba SFilter fueron reemplazados por la familia SFilter* (SFilterField, SFilterValue, SFilterEntry, SFilterSaved, SFilterProps, SFilterEmits, …). La tabla de operadores se exporta como valor mediante OPERATORS_BY_TYPE.
Declarar dos operadores con el mismo id en un campo ahora lanza un error al montar. Antes se renderizaba una lista de operadores rota, en silencio.
SCard
Los presets de texto de la prop icon se redujeron de seis a cuatro. 'primary' y 'secondary' ya no son valores válidos de icon — sus glifos por defecto no tenían significado semántico. Siguen disponibles como valores de iconColor.
- <SCard icon="primary" />
+ <SCard icon="info" iconColor="primary" />
Los cuatro presets de estado ('success', 'danger', 'warning', 'info') no cambian.
SSelectorBlock
El slot para personalizar las opciones del dropdown se renombró de options (plural, sin scope) a option (singular, con scope), igualando a SSelector. El slot anterior nunca recibía la opción iterada, así que era imposible renderizar contenido por ítem a través de él.
- <template #options>
- <span>...</span>
- </template>
+ <template #option="{ option }">
+ <span>{{ option.label }}</span>
+ </template>
Color primario personalizado
Si en Spartan 2.x personalizabas el color primario mediante el plugin, necesitas migrar esa configuración a variables CSS:
- // tailwind.config.js (Spartan 2.x)
- plugins: [
- require('@placetopay/spartan-vue/plugin')({
- primary: {
- 50: '228 242 253',
- 500: '33 150 243',
- // ...
- }
- })
- ]
+ /* styles.css (Spartan 3.x) */
+ @import "@placetopay/spartan-vue/styles.css";
+
+ @source '../src/**/*.{vue,js,ts,jsx,tsx}';
+
+ @theme {
+ --color-spartan-primary-50: rgb(228 242 253);
+ --color-spartan-primary-100: rgb(187 222 251);
+ --color-spartan-primary-200: rgb(144 202 249);
+ --color-spartan-primary-300: rgb(100 181 246);
+ --color-spartan-primary-400: rgb(66 165 245);
+ --color-spartan-primary-500: rgb(33 150 243);
+ --color-spartan-primary-600: rgb(30 132 229);
+ --color-spartan-primary-700: rgb(25 103 196);
+ --color-spartan-primary-800: rgb(21 78 148);
+ --color-spartan-primary-900: rgb(13 42 84);
+ }