KoreKanban
KanbanClase abstracta Livewire para un tablero Kanban con estado y persistencia. Se extiende como KoreDataTable: implementas columns(), cards() y persistMove(), y el board se encarga del arrastre, el layout y la reconciliación.
Esta es la forma con estado. Si el board no necesita vivir en su propia página —por ejemplo, dentro de un modal o de otro componente Livewire ya existente—, monta el componente anónimo directamente: ve a Kanban — Primeros pasos.
Uso
Extiende KoreKanban, define tres métodos y ya tienes un board completo.
<?php
namespace App\Livewire;
use App\Models\Task;
use KoreUi\Kanban\KoreKanban;
class TasksBoard extends KoreKanban
{
public function columns(): array
{
return [
['id' => 'todo', 'label' => 'Por hacer'],
['id' => 'doing', 'label' => 'En curso', 'color' => 'warning'],
['id' => 'done', 'label' => 'Hecho', 'color' => 'success'],
];
}
public function cards(): array
{
return Task::orderBy('position')->get()
->map(fn ($t) => [
'id' => $t->id,
'column' => $t->status,
'title' => $t->title,
'description' => $t->description,
])
->all();
}
protected function persistMove(string|int $cardId, string|int $toColumn, int $position): void
{
Task::whereKey($cardId)->update([
'status' => $toColumn,
'position' => $position,
]);
}
}<?php
namespace App\Livewire;
use App\Models\Task;
use KoreUi\Kanban\KoreKanban;
class TasksBoard extends KoreKanban
{
public function columns(): array
{
return [
['id' => 'todo', 'label' => 'Por hacer'],
['id' => 'doing', 'label' => 'En curso', 'color' => 'warning'],
['id' => 'done', 'label' => 'Hecho', 'color' => 'success'],
];
}
public function cards(): array
{
return Task::orderBy('position')->get()
->map(fn ($t) => [
'id' => $t->id,
'column' => $t->status,
'title' => $t->title,
'description' => $t->description,
])
->all();
}
protected function persistMove(string|int $cardId, string|int $toColumn, int $position): void
{
Task::whereKey($cardId)->update([
'status' => $toColumn,
'position' => $position,
]);
}
}<livewire:tasks-board /><livewire:tasks-board />Métodos a implementar
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
columns()
|
array | — | Columnas del board: [[id, label, color?], ...] (obligatorio) |
cards()
|
array | — | Tarjetas: [[id, column, title, ...], ...] (obligatorio) |
persistMove()
|
void | — | Persiste una tarjeta soltada: columna y posición destino (protected, obligatorio) |
El orden de los parámetros cambia. El handler público que llama el drag-and-drop es moveCard($cardId, $position, $toColumn), pero el método que tú implementas es persistMove($cardId, $toColumn, $position) —columna y posición van al revés—. La clase base hace la traducción por ti; solo importa si alguna vez sobreescribes moveCard() directamente.
Cómo funciona
Tres piezas ya resueltas: el render, el drop y el refresco.
- render() pinta la vista
kore::kanban.index, que por dentro reutiliza el componente anónimo kore::kanban con las columnas y tarjetas que le devuelven tus métodos. No necesitas escribir ningún Blade. - En cada drop, el board invoca
moveCard($cardId, $position, $toColumn)—ya implementado en la clase base—, que delega en tupersistMove(). - Tras persistir, Livewire re-renderiza y
cards()vuelve a leer el estado actualizado, así que el orden final que ve el usuario es siempre el que devuelve tu fuente de datos, no el DOM que quedó tras arrastrar.
Personalizar la tarjeta
Dos caminos: publicar la vista del card, o montar tú mismo el componente anónimo.
La clase base no expone una prop de slot por tarjeta —no hay dónde inyectar Blade a mitad del render() heredado—. Si necesitas contenido por tarjeta distinto del title/description/badge de serie, tienes dos opciones: publicar y sobrescribir la vista kore::components.kanban.card para todo el proyecto, o dejar de extender KoreKanban y montar kore::kanban tú mismo desde un componente Livewire normal, donde sí controlas el Blade y puedes usar el slot de kore::kanban.card por tarjeta —es exactamente lo que hace la demo de esta página—.
Demo de referencia
Visualmente es el mismo board: seis tareas, cuatro columnas, arrastre libre.
Esta demo en particular usa el componente anónimo directamente (para no acoplar la documentación al render() interno de la clase base), pero el resultado interactivo —arrastre dentro y entre columnas, wire:key por tarjeta, reconciliación tras el drop— es idéntico al de un board que extienda KoreKanban.
Investigar competidores
Analizar 3 productos del mercado
Definir métricas
KPIs del trimestre
Diseñar el esquema de datos
Wireframes del panel
Migrar autenticación
A Livewire 4
Configurar CI
Configuración y notas
Personaliza los defaults en config/kore-ui.php, sección kanban.
'kanban' => [
'group' => 'kanban', // grupo x-sort compartido entre columnas
'animation' => 150, // ms de la animación de arrastre
'column_width' => '18rem', // ancho de cada columna
],'kanban' => [
'group' => 'kanban', // grupo x-sort compartido entre columnas
'animation' => 150, // ms de la animación de arrastre
'column_width' => '18rem', // ancho de cada columna
],- El arrastre entre columnas usa un grupo
x-sortcompartido; si montas dos boards en la misma página con el mismogroup, sus tarjetas se pueden mezclar entre sí. - El ancho de columna es global vía
column_width—el componente anónimo no expone una prop para cambiarlo por instancia—. - Cuida las colisiones de posición bajo concurrencia. Si
positiontiene un índice único en base de datos, dos usuarios arrastrando a la vez pueden chocar: aplica la reordenación dentro de una transacción en tupersistMove().