Kanban
@vincisys/eyeson

1. Em Análise CTM

2
DA 2026-04-01

Inspeção Obrigatória Rotor Principal S-76C++

ANAC AD
SB 139-2026

Atualização de Aviônicos Primários AW139

Leonardo SB

2. FAPT Criado

1
DA 2026-02-14

Troca de Selos do Sistema Hidráulico AW139

FAA DRS
Visualizar
import { useState } from "react";
import { Kanban } from "@vincisys/eyeson";

export function KanbanBasicDemo() {
	const [value, setValue] = useState(INITIAL_VALUE);

	return (
		<div className="w-full rounded-eds-sm border border-eds-outline-border bg-white p-4">
			<Kanban
				columns={COLUMNS}
				items={value}
				getItemValue={(ticket) => ticket.id}
				onValueChange={setValue}
				renderItem={(ticket) => (
					<div className="rounded-eds-xs border border-eds-outline-border bg-white p-3 shadow-sm">
						<span className="font-mono text-xs font-bold text-eds-blue-main">
							{ticket.code}
						</span>
						<h4 className="mt-1 text-xs font-bold text-eds-neutral-main">
							{ticket.title}
						</h4>
						<span className="mt-2 block text-[10.5px] text-eds-neutral-elements">
							{ticket.source}
						</span>
					</div>
				)}
			/>
		</div>
	);
}

Instalação

import { Kanban } from '@vincisys/eyeson';

Uso

Kanban recebe columns + items e monta board, colunas, alças de arrasto e cards por baixo — mesmo espírito de DataTable sobre Table.

function Example() {
const [value, setValue] = useState(initialValue); // Record<string, Ticket[]>

return (

<Kanban
columns={[
{ id: 'col-1', title: 'Em Análise' },
{ id: 'col-2', title: 'FAPT Criado' },
]}
items={value}
getItemValue={(ticket) => ticket.id}
onValueChange={setValue}
renderItem={(ticket) => <TicketCard ticket={ticket} />}
/>
);
}

Composição manual

Para layout de coluna/card totalmente customizado (múltiplas linhas por card, filtros no cabeçalho da coluna, etc.), componha os primitivos diretamente em vez de usar <Kanban columns={...} items={...} />:

<Kanban.Root value={value} onValueChange={setValue} getItemValue={(t) => t.id}>
<Kanban.Board>
  {Object.entries(value).map(([columnId, tickets]) => (
    <Kanban.Column key={columnId} value={columnId}>
      <div className="flex items-center justify-between pb-3">
        <h3>{columnId}</h3>
        <Kanban.ColumnHandle>
          <GripVertical size={14} />
        </Kanban.ColumnHandle>
      </div>
      <Kanban.ColumnContent items={tickets.map((t) => t.id)}>
        {tickets.map((ticket) => (
          <Kanban.Item key={ticket.id} value={ticket.id}>
            <TicketCard ticket={ticket} />
          </Kanban.Item>
        ))}
      </Kanban.ColumnContent>
    </Kanban.Column>
  ))}
</Kanban.Board>

<Kanban.Overlay>
  {({ id, type }) =>
    type === 'item' ? <TicketCard ticket={findTicket(id)} /> : null
  }
</Kanban.Overlay>
</Kanban.Root>

API

Kanban

Versão pronta pra usar. Props: columns ({id, title, className?}[]), items (Record<string, T[]>), getItemValue, renderItem, renderColumnBadge?, onValueChange (preview em tempo real durante o arrasto), onValueCommit (uma vez, ao soltar), onColumnOrderChange.

Kanban.Root

Raiz dos primitivos: dona do estado de drag & drop, sem layout próprio. Mesmas props de dados que Kanban (value, getItemValue, onValueChange, onValueCommit, columnOrder, onColumnOrderChange).

Kanban.Board

Container em grid responsivo. Também é o SortableContext de reordenação das colunas.

Kanban.Column

Coluna individual. Props: value (id da coluna). Corpo livre via children.

Kanban.ColumnHandle

Alça de arrasto da coluna. Sem ela, a coluna não é reordenável.

Kanban.ColumnContent

Envolve a lista de Kanban.Item de uma coluna. Props: items (ids dos itens, na ordem exibida).

Kanban.Item

Card arrastável. Props: value (id do item). Por padrão o card inteiro é a alça de arrasto; passe asHandle={false} para usar um Kanban.ItemHandle dedicado.

Kanban.ItemHandle

Alça de arrasto explícita do item, para usar com <Kanban.Item asHandle={false}>.

Kanban.Overlay

Portal com o “fantasma” do item sendo arrastado. children recebe { id, type } do elemento ativo (type é 'item' ou 'column').