kanbase 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1178 -0
- package/dist/assets/kanbase.png +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/kanbase.es.js +2022 -0
- package/dist/kanbase.es.js.map +1 -0
- package/dist/kanbase.umd.js +2 -0
- package/dist/kanbase.umd.js.map +1 -0
- package/package.json +104 -0
package/README.md
ADDED
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="public/assets/kanbase.png" alt="Kanbase Logo" width="280" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Precisão Fluida para Workflows Modernos</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>Kanbase</strong> é um componente Kanban de alto desempenho e nível empresarial para React, projetado para lidar com workflows complexos sem comprometer velocidade ou estética. Construído para escala, capaz de renderizar milhares de cards a <strong>60 FPS</strong> graças à virtualização, oferecendo uma experiência premium com física e interações de "Precisão Fluida".
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 📦 Instalação
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install kanbase
|
|
19
|
+
# ou
|
|
20
|
+
pnpm add kanbase
|
|
21
|
+
# ou
|
|
22
|
+
yarn add kanbase
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Dependências Peer:**
|
|
26
|
+
Certifique-se de ter as seguintes dependências instaladas (seu projeto provavelmente já as possui):
|
|
27
|
+
```bash
|
|
28
|
+
npm install react react-dom tailwindcss
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**✨ Estilos Automáticos:**
|
|
32
|
+
Os estilos do Kanbase são injetados automaticamente quando você importa o componente. Não é necessário importar CSS manualmente!
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚀 Início Rápido
|
|
37
|
+
|
|
38
|
+
### Uso Básico
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { KanboomBoard, useKanbanStore, type KanboomData, type KanboomConfig } from 'kanbase';
|
|
42
|
+
// Estilos são injetados automaticamente - não precisa importar CSS!
|
|
43
|
+
|
|
44
|
+
function App() {
|
|
45
|
+
const { setBoardData } = useKanbanStore();
|
|
46
|
+
|
|
47
|
+
// Inicializar dados do board
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const data: KanboomData = {
|
|
50
|
+
cards: {
|
|
51
|
+
'c1': {
|
|
52
|
+
id: 'c1',
|
|
53
|
+
title: 'Pesquisar Competidores',
|
|
54
|
+
description: 'Analisar os 3 principais líderes de mercado',
|
|
55
|
+
metadata: { priority: 'high' }
|
|
56
|
+
},
|
|
57
|
+
'c2': {
|
|
58
|
+
id: 'c2',
|
|
59
|
+
title: 'Criar Protótipo',
|
|
60
|
+
description: 'Desenvolver MVP da funcionalidade',
|
|
61
|
+
metadata: { priority: 'medium' }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
columns: {
|
|
65
|
+
'col1': {
|
|
66
|
+
id: 'col1',
|
|
67
|
+
title: 'Backlog',
|
|
68
|
+
cardIds: ['c1', 'c2']
|
|
69
|
+
},
|
|
70
|
+
'col2': {
|
|
71
|
+
id: 'col2',
|
|
72
|
+
title: 'Em Progresso',
|
|
73
|
+
cardIds: []
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
columnOrder: ['col1', 'col2']
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
setBoardData(data);
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
const config: KanboomConfig = {
|
|
83
|
+
allowAdd: true,
|
|
84
|
+
allowEdit: true,
|
|
85
|
+
allowColumnAdd: true,
|
|
86
|
+
allowColumnEdit: true,
|
|
87
|
+
allowColumnDelete: true,
|
|
88
|
+
allowColumnReorder: true,
|
|
89
|
+
allowFilters: true,
|
|
90
|
+
onCardMove: (cardId, fromCol, toCol, index) => {
|
|
91
|
+
console.log('Card movido:', { cardId, fromCol, toCol, index });
|
|
92
|
+
// Sincronizar com sua API aqui
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="h-screen w-full bg-slate-50">
|
|
98
|
+
<KanboomBoard config={config} />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 📚 Tipos e Interfaces
|
|
107
|
+
|
|
108
|
+
### `KanboomCard`
|
|
109
|
+
|
|
110
|
+
Representa um card individual no board.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
interface KanboomCard {
|
|
114
|
+
id: string; // ID único do card (obrigatório)
|
|
115
|
+
title: string; // Título do card (obrigatório)
|
|
116
|
+
description?: string; // Descrição opcional
|
|
117
|
+
content?: any; // Conteúdo customizado
|
|
118
|
+
metadata?: any; // Metadados customizados (tags, prioridade, etc.)
|
|
119
|
+
previousColumnId?: string; // ID da coluna anterior (usado para restauração)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Exemplo:**
|
|
124
|
+
```typescript
|
|
125
|
+
const card: KanboomCard = {
|
|
126
|
+
id: 'card-1',
|
|
127
|
+
title: 'Implementar Autenticação',
|
|
128
|
+
description: 'Adicionar login com OAuth2',
|
|
129
|
+
metadata: {
|
|
130
|
+
priority: 'high',
|
|
131
|
+
tags: [{ name: 'Backend', color: 'bg-blue-100' }],
|
|
132
|
+
members: [{ name: 'João', initials: 'JS' }],
|
|
133
|
+
dueDate: '2024-12-31',
|
|
134
|
+
commentsCount: 3,
|
|
135
|
+
attachmentsCount: 2
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `KanboomColumn`
|
|
141
|
+
|
|
142
|
+
Representa uma coluna no board.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
interface KanboomColumn {
|
|
146
|
+
id: string; // ID único da coluna (obrigatório)
|
|
147
|
+
title: string; // Título da coluna (obrigatório)
|
|
148
|
+
cardIds: string[]; // Array de IDs dos cards nesta coluna
|
|
149
|
+
metadata?: any; // Metadados customizados da coluna
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Exemplo:**
|
|
154
|
+
```typescript
|
|
155
|
+
const column: KanboomColumn = {
|
|
156
|
+
id: 'col-1',
|
|
157
|
+
title: 'To Do',
|
|
158
|
+
cardIds: ['card-1', 'card-2', 'card-3'],
|
|
159
|
+
metadata: {
|
|
160
|
+
limit: 10,
|
|
161
|
+
color: '#3b82f6'
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `KanboomData`
|
|
167
|
+
|
|
168
|
+
Estrutura completa de dados do board.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
interface KanboomData<TCard = KanboomCard> {
|
|
172
|
+
cards: Record<string, TCard>; // Mapa de cards por ID
|
|
173
|
+
columns: Record<string, KanboomColumn>; // Mapa de colunas por ID
|
|
174
|
+
columnOrder: string[]; // Ordem das colunas (IDs)
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Exemplo:**
|
|
179
|
+
```typescript
|
|
180
|
+
const boardData: KanboomData = {
|
|
181
|
+
cards: {
|
|
182
|
+
'c1': { id: 'c1', title: 'Card 1', ... },
|
|
183
|
+
'c2': { id: 'c2', title: 'Card 2', ... }
|
|
184
|
+
},
|
|
185
|
+
columns: {
|
|
186
|
+
'col1': { id: 'col1', title: 'Backlog', cardIds: ['c1', 'c2'] },
|
|
187
|
+
'col2': { id: 'col2', title: 'Doing', cardIds: [] }
|
|
188
|
+
},
|
|
189
|
+
columnOrder: ['col1', 'col2']
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `KanboomConfig`
|
|
194
|
+
|
|
195
|
+
Configuração completa do componente, incluindo feature flags, callbacks e renderers customizados.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface KanboomConfig<TCard = KanboomCard, TColumn = KanboomColumn> {
|
|
199
|
+
// === RENDERERS CUSTOMIZADOS ===
|
|
200
|
+
renderCard?: (props: CardRenderProps<TCard>) => React.ReactNode;
|
|
201
|
+
renderColumnHeader?: (props: ColumnHeaderRenderProps<TColumn>) => React.ReactNode;
|
|
202
|
+
renderColumnEmpty?: (props: ColumnEmptyRenderProps) => React.ReactNode;
|
|
203
|
+
renderAddButton?: (props: { onClick: () => void; columnId: string }) => React.ReactNode;
|
|
204
|
+
renderAddForm?: (props: AddCardRenderProps<TCard>) => React.ReactNode;
|
|
205
|
+
renderEditForm?: (props: EditCardRenderProps<TCard>) => React.ReactNode;
|
|
206
|
+
renderAddColumnButton?: (props: { onClick: () => void }) => React.ReactNode;
|
|
207
|
+
renderAddColumnForm?: (props: AddColumnRenderProps<TColumn>) => React.ReactNode;
|
|
208
|
+
renderEditColumnForm?: (props: EditColumnRenderProps<TColumn>) => React.ReactNode;
|
|
209
|
+
renderCardView?: (props: ViewCardRenderProps<TCard>) => React.ReactNode;
|
|
210
|
+
|
|
211
|
+
// === FEATURE FLAGS ===
|
|
212
|
+
allowAdd?: boolean; // Permite adicionar cards (padrão: false)
|
|
213
|
+
allowEdit?: boolean; // Permite editar cards (padrão: false)
|
|
214
|
+
allowColumnAdd?: boolean; // Permite adicionar colunas (padrão: false)
|
|
215
|
+
allowColumnEdit?: boolean; // Permite editar colunas (padrão: false)
|
|
216
|
+
allowColumnDelete?: boolean; // Permite deletar colunas (padrão: false)
|
|
217
|
+
allowColumnReorder?: boolean; // Permite reordenar colunas (padrão: false)
|
|
218
|
+
allowFilters?: boolean; // Habilita drawer de filtros (padrão: true)
|
|
219
|
+
showURLSync?: boolean; // Sincroniza estado com URL (padrão: false)
|
|
220
|
+
|
|
221
|
+
// === CONFIGURAÇÕES DE DRAG & DROP ===
|
|
222
|
+
dragActivationDistance?: number; // Distância para ativar drag (padrão: 10px)
|
|
223
|
+
touchActivationDelay?: number; // Delay para touch (padrão: 250ms)
|
|
224
|
+
|
|
225
|
+
// === CONFIGURAÇÕES DE VIRTUALIZAÇÃO ===
|
|
226
|
+
virtualOverscan?: number; // Cards extras renderizados (padrão: 5)
|
|
227
|
+
estimatedCardHeight?: number; // Altura estimada do card (padrão: 90px)
|
|
228
|
+
|
|
229
|
+
// === CONFIGURAÇÕES DE LAYOUT ===
|
|
230
|
+
columnWidth?: number; // Largura das colunas (padrão: 320px)
|
|
231
|
+
columnMinHeight?: number; // Altura mínima das colunas (padrão: 500px)
|
|
232
|
+
gap?: number; // Espaçamento entre colunas (padrão: 16px)
|
|
233
|
+
|
|
234
|
+
// === CALLBACKS ===
|
|
235
|
+
onCardMove?: (cardId: string, fromColumn: string, toColumn: string, index: number) => void;
|
|
236
|
+
onCardClick?: (card: TCard) => void;
|
|
237
|
+
onEditCard?: (card: TCard) => void;
|
|
238
|
+
onColumnClick?: (column: TColumn) => void;
|
|
239
|
+
onEditColumn?: (column: TColumn) => void;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 🎨 Estilos e Theming
|
|
246
|
+
|
|
247
|
+
### Injeção Automática de Estilos
|
|
248
|
+
|
|
249
|
+
O Kanbase injeta seus estilos automaticamente quando você importa o componente. **Não é necessário importar CSS manualmente!**
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// ✅ Isso é tudo que você precisa fazer
|
|
253
|
+
import { KanboomBoard } from 'kanbase';
|
|
254
|
+
|
|
255
|
+
// ❌ Não precisa fazer isso mais
|
|
256
|
+
// import 'kanbase/style.css';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Os estilos são injetados no `<head>` do documento automaticamente quando o módulo é carregado. Isso garante que:
|
|
260
|
+
|
|
261
|
+
- ✅ Funciona imediatamente sem configuração adicional
|
|
262
|
+
- ✅ Não há conflitos de ordem de importação
|
|
263
|
+
- ✅ Funciona em todos os ambientes (Vite, Webpack, etc.)
|
|
264
|
+
|
|
265
|
+
### Importação Manual (Opcional)
|
|
266
|
+
|
|
267
|
+
Se você preferir ter controle total sobre quando e como os estilos são carregados, você ainda pode importar o CSS manualmente:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { KanboomBoard } from 'kanbase';
|
|
271
|
+
import 'kanbase/style.css'; // Opcional - apenas se quiser controle manual
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Nota:** Se você importar manualmente, os estilos não serão injetados automaticamente (evita duplicação).
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 🎨 Render Props e Customização
|
|
279
|
+
|
|
280
|
+
### Renderizar Card Customizado
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
const config: KanboomConfig = {
|
|
284
|
+
renderCard: ({ card, isDragging }) => (
|
|
285
|
+
<div className={`p-4 border rounded-lg ${isDragging ? 'shadow-2xl rotate-2' : ''}`}>
|
|
286
|
+
<h3 className="font-bold text-lg">{card.title}</h3>
|
|
287
|
+
{card.description && (
|
|
288
|
+
<p className="text-sm text-gray-600 mt-2">{card.description}</p>
|
|
289
|
+
)}
|
|
290
|
+
{card.metadata?.priority && (
|
|
291
|
+
<span className={`px-2 py-1 rounded text-xs ${
|
|
292
|
+
card.metadata.priority === 'high' ? 'bg-red-100' : 'bg-blue-100'
|
|
293
|
+
}`}>
|
|
294
|
+
{card.metadata.priority}
|
|
295
|
+
</span>
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
)
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Renderizar Formulário de Adição Customizado
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
const config: KanboomConfig = {
|
|
306
|
+
renderAddForm: ({ columnId, onAdd, onCancel }) => (
|
|
307
|
+
<Dialog open onOpenChange={onCancel}>
|
|
308
|
+
<DialogContent>
|
|
309
|
+
<DialogHeader>
|
|
310
|
+
<DialogTitle>Adicionar Card</DialogTitle>
|
|
311
|
+
</DialogHeader>
|
|
312
|
+
<form onSubmit={(e) => {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
const formData = new FormData(e.target);
|
|
315
|
+
onAdd({
|
|
316
|
+
title: formData.get('title') as string,
|
|
317
|
+
description: formData.get('description') as string,
|
|
318
|
+
metadata: { priority: formData.get('priority') as string }
|
|
319
|
+
});
|
|
320
|
+
}}>
|
|
321
|
+
<input name="title" placeholder="Título" required />
|
|
322
|
+
<textarea name="description" placeholder="Descrição" />
|
|
323
|
+
<select name="priority">
|
|
324
|
+
<option value="low">Baixa</option>
|
|
325
|
+
<option value="medium">Média</option>
|
|
326
|
+
<option value="high">Alta</option>
|
|
327
|
+
</select>
|
|
328
|
+
<button type="submit">Adicionar</button>
|
|
329
|
+
</form>
|
|
330
|
+
</DialogContent>
|
|
331
|
+
</Dialog>
|
|
332
|
+
)
|
|
333
|
+
};
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Renderizar Header de Coluna Customizado
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
const config: KanboomConfig = {
|
|
340
|
+
renderColumnHeader: ({ column, cardCount, isOver, dragHandleProps, onAddCard, onEditColumn }) => (
|
|
341
|
+
<div className="flex items-center justify-between p-3 bg-blue-100 rounded-t-lg">
|
|
342
|
+
<div className="flex items-center gap-2">
|
|
343
|
+
{dragHandleProps && (
|
|
344
|
+
<div {...dragHandleProps.attributes} {...dragHandleProps.listeners}>
|
|
345
|
+
<GripVertical className="cursor-grab" />
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
<h2 className="font-bold">{column.title}</h2>
|
|
349
|
+
<span className="text-sm text-gray-600">({cardCount})</span>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="flex gap-2">
|
|
352
|
+
{onAddCard && (
|
|
353
|
+
<button onClick={onAddCard} className="p-1 hover:bg-blue-200 rounded">
|
|
354
|
+
<Plus size={16} />
|
|
355
|
+
</button>
|
|
356
|
+
)}
|
|
357
|
+
{onEditColumn && (
|
|
358
|
+
<button onClick={onEditColumn} className="p-1 hover:bg-blue-200 rounded">
|
|
359
|
+
<Settings size={16} />
|
|
360
|
+
</button>
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
)
|
|
365
|
+
};
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 🔧 Store e Gerenciamento de Estado
|
|
371
|
+
|
|
372
|
+
O Kanbase usa **Zustand** para gerenciamento de estado. Você pode acessar e manipular o estado diretamente através do hook `useKanbanStore`.
|
|
373
|
+
|
|
374
|
+
### Hook `useKanbanStore`
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
import { useKanbanStore } from 'kanbase';
|
|
378
|
+
|
|
379
|
+
function MyComponent() {
|
|
380
|
+
const {
|
|
381
|
+
// Estado
|
|
382
|
+
cards,
|
|
383
|
+
columns,
|
|
384
|
+
columnOrder,
|
|
385
|
+
filters,
|
|
386
|
+
|
|
387
|
+
// Ações de Cards
|
|
388
|
+
addCard,
|
|
389
|
+
updateCard,
|
|
390
|
+
deleteCard,
|
|
391
|
+
duplicateCard,
|
|
392
|
+
|
|
393
|
+
// Ações de Colunas
|
|
394
|
+
addColumn,
|
|
395
|
+
updateColumn,
|
|
396
|
+
deleteColumn,
|
|
397
|
+
moveColumn,
|
|
398
|
+
|
|
399
|
+
// Movimentação
|
|
400
|
+
moveCard,
|
|
401
|
+
|
|
402
|
+
// Filtros
|
|
403
|
+
setSearchQuery,
|
|
404
|
+
addFilterGroup,
|
|
405
|
+
updateFilterGroup,
|
|
406
|
+
removeFilterGroup,
|
|
407
|
+
clearFilters,
|
|
408
|
+
|
|
409
|
+
// Utilitários
|
|
410
|
+
setBoardData,
|
|
411
|
+
setConfig,
|
|
412
|
+
clearBoard
|
|
413
|
+
} = useKanbanStore();
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Exemplos de Uso da Store
|
|
418
|
+
|
|
419
|
+
#### Adicionar Card Programaticamente
|
|
420
|
+
|
|
421
|
+
```tsx
|
|
422
|
+
const { addCard } = useKanbanStore();
|
|
423
|
+
|
|
424
|
+
const handleAddCard = () => {
|
|
425
|
+
const cardId = addCard('col-1', {
|
|
426
|
+
title: 'Novo Card',
|
|
427
|
+
description: 'Descrição do card',
|
|
428
|
+
metadata: { priority: 'high' }
|
|
429
|
+
});
|
|
430
|
+
console.log('Card criado com ID:', cardId);
|
|
431
|
+
};
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Atualizar Card
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
const { updateCard } = useKanbanStore();
|
|
438
|
+
|
|
439
|
+
updateCard('card-1', {
|
|
440
|
+
title: 'Título Atualizado',
|
|
441
|
+
metadata: { ...existingMetadata, priority: 'low' }
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Mover Card Programaticamente
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
const { moveCard } = useKanbanStore();
|
|
449
|
+
|
|
450
|
+
// Mover card 'c1' da coluna 'col1' para 'col2' na posição 0
|
|
451
|
+
moveCard('c1', 'col1', 'col2', 0);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
#### Adicionar Coluna
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
const { addColumn } = useKanbanStore();
|
|
458
|
+
|
|
459
|
+
const columnId = addColumn({
|
|
460
|
+
title: 'Nova Coluna',
|
|
461
|
+
metadata: { color: '#3b82f6' }
|
|
462
|
+
}, 1); // Insere na posição 1
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Gerenciar Filtros
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
const { addFilterGroup, setSearchQuery, clearFilters } = useKanbanStore();
|
|
469
|
+
|
|
470
|
+
// Adicionar busca global
|
|
471
|
+
setSearchQuery('react');
|
|
472
|
+
|
|
473
|
+
// Adicionar grupo de filtros
|
|
474
|
+
addFilterGroup({
|
|
475
|
+
id: 'group-1',
|
|
476
|
+
conjunction: 'and',
|
|
477
|
+
enabled: true,
|
|
478
|
+
rules: [
|
|
479
|
+
{
|
|
480
|
+
id: 'rule-1',
|
|
481
|
+
field: 'metadata.priority',
|
|
482
|
+
operator: 'eq',
|
|
483
|
+
value: 'high',
|
|
484
|
+
enabled: true
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Limpar todos os filtros
|
|
490
|
+
clearFilters();
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 🔍 Sistema de Filtros Avançado
|
|
496
|
+
|
|
497
|
+
O Kanbase inclui um sistema de filtros poderoso que suporta:
|
|
498
|
+
|
|
499
|
+
- **Busca Global**: Pesquisa em título, descrição e metadados
|
|
500
|
+
- **Filtros Complexos**: Grupos aninhados com lógica AND/OR
|
|
501
|
+
- **Descoberta Automática**: Detecta automaticamente campos disponíveis nos cards
|
|
502
|
+
- **Operadores Múltiplos**: `eq`, `neq`, `contains`, `gt`, `lt`, `between`, etc.
|
|
503
|
+
|
|
504
|
+
### Tipos de Filtros
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
type FilterOperator =
|
|
508
|
+
| 'eq' | 'neq' // Igual / Diferente
|
|
509
|
+
| 'contains' | 'notContains' // Contém / Não contém
|
|
510
|
+
| 'in' | 'notIn' // Em / Não em
|
|
511
|
+
| 'gt' | 'gte' | 'lt' | 'lte' // Maior que, Maior ou igual, Menor que, Menor ou igual
|
|
512
|
+
| 'between' // Entre dois valores
|
|
513
|
+
| 'isEmpty' | 'isNotEmpty'; // Vazio / Não vazio
|
|
514
|
+
|
|
515
|
+
interface FilterRule {
|
|
516
|
+
id: string;
|
|
517
|
+
field: string; // Caminho do campo (ex: 'metadata.priority')
|
|
518
|
+
operator: FilterOperator;
|
|
519
|
+
value: any;
|
|
520
|
+
enabled: boolean;
|
|
521
|
+
type?: 'text' | 'number' | 'date' | 'select' | 'boolean';
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
interface FilterGroup {
|
|
525
|
+
id: string;
|
|
526
|
+
conjunction: 'and' | 'or'; // Lógica do grupo
|
|
527
|
+
rules: (FilterRule | FilterGroup)[]; // Regras ou grupos aninhados
|
|
528
|
+
enabled: boolean;
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Exemplo de Uso de Filtros
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
import { useKanbanStore } from 'kanbase';
|
|
536
|
+
|
|
537
|
+
function FilterExample() {
|
|
538
|
+
const { addFilterGroup, setSearchQuery } = useKanbanStore();
|
|
539
|
+
|
|
540
|
+
// Busca global
|
|
541
|
+
const handleSearch = (query: string) => {
|
|
542
|
+
setSearchQuery(query);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Filtro por prioridade
|
|
546
|
+
const filterByPriority = (priority: string) => {
|
|
547
|
+
addFilterGroup({
|
|
548
|
+
id: crypto.randomUUID(),
|
|
549
|
+
conjunction: 'and',
|
|
550
|
+
enabled: true,
|
|
551
|
+
rules: [
|
|
552
|
+
{
|
|
553
|
+
id: crypto.randomUUID(),
|
|
554
|
+
field: 'metadata.priority',
|
|
555
|
+
operator: 'eq',
|
|
556
|
+
value: priority,
|
|
557
|
+
enabled: true,
|
|
558
|
+
type: 'select'
|
|
559
|
+
}
|
|
560
|
+
]
|
|
561
|
+
});
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<div>
|
|
566
|
+
<input
|
|
567
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
568
|
+
placeholder="Buscar cards..."
|
|
569
|
+
/>
|
|
570
|
+
<button onClick={() => filterByPriority('high')}>
|
|
571
|
+
Filtrar Alta Prioridade
|
|
572
|
+
</button>
|
|
573
|
+
</div>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Filtros Aninhados (AND/OR)
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
// Exemplo: (priority = 'high' OR priority = 'medium') AND tags contains 'urgent'
|
|
582
|
+
addFilterGroup({
|
|
583
|
+
id: 'group-1',
|
|
584
|
+
conjunction: 'and',
|
|
585
|
+
enabled: true,
|
|
586
|
+
rules: [
|
|
587
|
+
{
|
|
588
|
+
id: 'subgroup-1',
|
|
589
|
+
conjunction: 'or',
|
|
590
|
+
enabled: true,
|
|
591
|
+
rules: [
|
|
592
|
+
{
|
|
593
|
+
id: 'rule-1',
|
|
594
|
+
field: 'metadata.priority',
|
|
595
|
+
operator: 'eq',
|
|
596
|
+
value: 'high',
|
|
597
|
+
enabled: true
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: 'rule-2',
|
|
601
|
+
field: 'metadata.priority',
|
|
602
|
+
operator: 'eq',
|
|
603
|
+
value: 'medium',
|
|
604
|
+
enabled: true
|
|
605
|
+
}
|
|
606
|
+
]
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: 'rule-3',
|
|
610
|
+
field: 'metadata.tags',
|
|
611
|
+
operator: 'contains',
|
|
612
|
+
value: 'urgent',
|
|
613
|
+
enabled: true
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
});
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## 🎯 Funcionalidades Principais
|
|
622
|
+
|
|
623
|
+
### ✨ Performance e Virtualização
|
|
624
|
+
|
|
625
|
+
- **Virtualização de Colunas**: Renderiza apenas colunas visíveis usando `@tanstack/react-virtual`
|
|
626
|
+
- **Virtualização de Cards**: Renderiza apenas cards visíveis dentro de cada coluna
|
|
627
|
+
- **60 FPS Garantido**: Mantém performance mesmo com milhares de cards
|
|
628
|
+
- **Overscan Configurável**: Controla quantos itens extras são renderizados
|
|
629
|
+
|
|
630
|
+
### 🎨 Drag & Drop Avançado
|
|
631
|
+
|
|
632
|
+
- **Drag & Drop Suave**: Usa `@dnd-kit` com algoritmos de detecção de colisão otimizados
|
|
633
|
+
- **Reordenação de Cards**: Arraste cards dentro e entre colunas
|
|
634
|
+
- **Reordenação de Colunas**: Arraste colunas para reordenar horizontalmente
|
|
635
|
+
- **Criar Coluna ao Arrastar**: Arraste um card para a área "Nova Coluna" para criar uma coluna automaticamente
|
|
636
|
+
- **Feedback Visual**: Indicadores visuais durante o arraste (top/bottom/left/right)
|
|
637
|
+
- **Touch Support**: Suporte completo para dispositivos touch
|
|
638
|
+
|
|
639
|
+
### 🔍 Filtros e Busca
|
|
640
|
+
|
|
641
|
+
- **Busca Global**: Pesquisa em todos os campos do card
|
|
642
|
+
- **Filtros Complexos**: Grupos aninhados com lógica AND/OR
|
|
643
|
+
- **Descoberta Automática**: Detecta campos disponíveis automaticamente
|
|
644
|
+
- **Filtros Ativos**: Chips visuais mostrando filtros aplicados
|
|
645
|
+
- **Múltiplos Operadores**: Suporte a 12+ operadores diferentes
|
|
646
|
+
|
|
647
|
+
### 🎭 Customização Total
|
|
648
|
+
|
|
649
|
+
- **Render Props**: Customize qualquer parte da UI
|
|
650
|
+
- **Componentes Default**: Vem com componentes prontos baseados em Shadcn UI
|
|
651
|
+
- **Type-Safe**: Totalmente tipado com TypeScript
|
|
652
|
+
- **CSS Variables**: Temas via variáveis CSS padrão
|
|
653
|
+
|
|
654
|
+
### 📱 Modais e Formulários
|
|
655
|
+
|
|
656
|
+
- **Modal de Visualização**: Visualize detalhes completos do card
|
|
657
|
+
- **Formulário de Edição**: Edite cards com formulário padrão ou customizado
|
|
658
|
+
- **Formulário de Adição**: Adicione novos cards facilmente
|
|
659
|
+
- **Edição de Colunas**: Renomeie ou delete colunas
|
|
660
|
+
- **Todos Customizáveis**: Substitua qualquer modal por sua própria implementação
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## 🎨 Theming e Customização Visual
|
|
665
|
+
|
|
666
|
+
O Kanbase usa variáveis CSS para theming. Defina estas variáveis no seu CSS global:
|
|
667
|
+
|
|
668
|
+
```css
|
|
669
|
+
:root {
|
|
670
|
+
/* Cores do Board */
|
|
671
|
+
--color-board-bg: #f8fafc; /* Slate-50 */
|
|
672
|
+
--color-column-bg: rgba(241, 245, 249, 0.5); /* Slate-100 (Glass) */
|
|
673
|
+
|
|
674
|
+
/* Aparência dos Cards */
|
|
675
|
+
--color-card-bg: #ffffff;
|
|
676
|
+
--color-card-border: rgba(226, 232, 240, 0.8);
|
|
677
|
+
--radius-card: 8px;
|
|
678
|
+
--shadow-card: 0 1px 2px rgba(0,0,0,0.05);
|
|
679
|
+
--shadow-card-hover: 0 4px 6px -1px rgba(0,0,0,0.1);
|
|
680
|
+
|
|
681
|
+
/* Física de Animação */
|
|
682
|
+
--animate-tilt: tilt 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/* Suporte a Dark Mode */
|
|
686
|
+
.dark {
|
|
687
|
+
--color-board-bg: #0f172a;
|
|
688
|
+
--color-card-bg: #1e293b;
|
|
689
|
+
--color-column-bg: rgba(30, 41, 59, 0.5);
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Classes CSS Úteis
|
|
694
|
+
|
|
695
|
+
O Kanbase expõe classes utilitárias que você pode usar:
|
|
696
|
+
|
|
697
|
+
- `bg-board-bg`: Background do board
|
|
698
|
+
- `bg-column-bg`: Background das colunas
|
|
699
|
+
- `bg-card-bg`: Background dos cards
|
|
700
|
+
- `border-card-border`: Borda dos cards
|
|
701
|
+
- `rounded-card`: Border radius dos cards
|
|
702
|
+
- `rounded-column`: Border radius das colunas
|
|
703
|
+
- `shadow-card`: Sombra dos cards
|
|
704
|
+
- `shadow-card-hover`: Sombra no hover dos cards
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## 📖 Exemplos Completos
|
|
709
|
+
|
|
710
|
+
### Exemplo 1: Board Básico com Sincronização de API
|
|
711
|
+
|
|
712
|
+
```tsx
|
|
713
|
+
import { useEffect } from 'react';
|
|
714
|
+
import { KanboomBoard, useKanbanStore, type KanboomConfig } from 'kanbase';
|
|
715
|
+
// Estilos são injetados automaticamente!
|
|
716
|
+
|
|
717
|
+
function App() {
|
|
718
|
+
const { setBoardData, moveCard } = useKanbanStore();
|
|
719
|
+
|
|
720
|
+
// Carregar dados iniciais
|
|
721
|
+
useEffect(() => {
|
|
722
|
+
fetch('/api/kanban')
|
|
723
|
+
.then(res => res.json())
|
|
724
|
+
.then(data => setBoardData(data));
|
|
725
|
+
}, []);
|
|
726
|
+
|
|
727
|
+
const config: KanboomConfig = {
|
|
728
|
+
allowAdd: true,
|
|
729
|
+
allowEdit: true,
|
|
730
|
+
allowColumnAdd: true,
|
|
731
|
+
allowColumnReorder: true,
|
|
732
|
+
allowFilters: true,
|
|
733
|
+
|
|
734
|
+
onCardMove: async (cardId, fromCol, toCol, index) => {
|
|
735
|
+
// Sincronizar com API
|
|
736
|
+
try {
|
|
737
|
+
await fetch('/api/cards/move', {
|
|
738
|
+
method: 'POST',
|
|
739
|
+
headers: { 'Content-Type': 'application/json' },
|
|
740
|
+
body: JSON.stringify({ cardId, fromCol, toCol, index })
|
|
741
|
+
});
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.error('Erro ao mover card:', error);
|
|
744
|
+
// Reverter movimento em caso de erro
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
<div className="h-screen w-full">
|
|
751
|
+
<KanboomBoard config={config} />
|
|
752
|
+
</div>
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### Exemplo 2: Board com Cards Customizados
|
|
758
|
+
|
|
759
|
+
```tsx
|
|
760
|
+
import { KanboomBoard, type KanboomConfig, type CardRenderProps } from 'kanbase';
|
|
761
|
+
import { Badge } from '@/components/ui/badge';
|
|
762
|
+
|
|
763
|
+
function CustomCard({ card, isDragging }: CardRenderProps) {
|
|
764
|
+
return (
|
|
765
|
+
<div className={`p-4 bg-white rounded-lg border shadow-sm ${
|
|
766
|
+
isDragging ? 'opacity-50 scale-105' : 'hover:shadow-md'
|
|
767
|
+
}`}>
|
|
768
|
+
<div className="flex items-start justify-between mb-2">
|
|
769
|
+
<h3 className="font-semibold text-lg">{card.title}</h3>
|
|
770
|
+
{card.metadata?.priority && (
|
|
771
|
+
<Badge variant={card.metadata.priority === 'high' ? 'destructive' : 'default'}>
|
|
772
|
+
{card.metadata.priority}
|
|
773
|
+
</Badge>
|
|
774
|
+
)}
|
|
775
|
+
</div>
|
|
776
|
+
{card.description && (
|
|
777
|
+
<p className="text-sm text-gray-600">{card.description}</p>
|
|
778
|
+
)}
|
|
779
|
+
{card.metadata?.tags && (
|
|
780
|
+
<div className="flex gap-1 mt-2">
|
|
781
|
+
{card.metadata.tags.map((tag: string, i: number) => (
|
|
782
|
+
<Badge key={i} variant="outline" className="text-xs">
|
|
783
|
+
{tag}
|
|
784
|
+
</Badge>
|
|
785
|
+
))}
|
|
786
|
+
</div>
|
|
787
|
+
)}
|
|
788
|
+
</div>
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const config: KanboomConfig = {
|
|
793
|
+
allowAdd: true,
|
|
794
|
+
allowEdit: true,
|
|
795
|
+
renderCard: CustomCard
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
function App() {
|
|
799
|
+
return <KanboomBoard config={config} />;
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Exemplo 3: Integração com Estado Global (Redux/Zustand)
|
|
804
|
+
|
|
805
|
+
```tsx
|
|
806
|
+
import { useEffect } from 'react';
|
|
807
|
+
import { KanboomBoard, useKanbanStore } from 'kanbase';
|
|
808
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
809
|
+
import { syncKanbanData, moveCardAction } from './kanbanSlice';
|
|
810
|
+
|
|
811
|
+
function App() {
|
|
812
|
+
const dispatch = useDispatch();
|
|
813
|
+
const kanbanData = useSelector(state => state.kanban);
|
|
814
|
+
const { setBoardData } = useKanbanStore();
|
|
815
|
+
|
|
816
|
+
// Sincronizar store do Kanbase com Redux
|
|
817
|
+
useEffect(() => {
|
|
818
|
+
setBoardData(kanbanData);
|
|
819
|
+
}, [kanbanData, setBoardData]);
|
|
820
|
+
|
|
821
|
+
// Sincronizar mudanças do Kanbase com Redux
|
|
822
|
+
const config = {
|
|
823
|
+
allowAdd: true,
|
|
824
|
+
allowEdit: true,
|
|
825
|
+
onCardMove: (cardId, fromCol, toCol, index) => {
|
|
826
|
+
dispatch(moveCardAction({ cardId, fromCol, toCol, index }));
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
return <KanboomBoard config={config} />;
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Exemplo 4: Board com Permissões por Usuário
|
|
835
|
+
|
|
836
|
+
```tsx
|
|
837
|
+
import { useAuth } from './auth';
|
|
838
|
+
import { KanboomBoard, type KanboomConfig } from 'kanbase';
|
|
839
|
+
|
|
840
|
+
function App() {
|
|
841
|
+
const { user, hasPermission } = useAuth();
|
|
842
|
+
|
|
843
|
+
const config: KanboomConfig = {
|
|
844
|
+
// Habilitar features baseado em permissões
|
|
845
|
+
allowAdd: hasPermission('kanban:create'),
|
|
846
|
+
allowEdit: hasPermission('kanban:edit'),
|
|
847
|
+
allowColumnAdd: hasPermission('kanban:columns:create'),
|
|
848
|
+
allowColumnEdit: hasPermission('kanban:columns:edit'),
|
|
849
|
+
allowColumnDelete: hasPermission('kanban:columns:delete'),
|
|
850
|
+
allowColumnReorder: hasPermission('kanban:columns:reorder'),
|
|
851
|
+
|
|
852
|
+
onCardMove: (cardId, fromCol, toCol, index) => {
|
|
853
|
+
if (!hasPermission('kanban:move')) {
|
|
854
|
+
alert('Você não tem permissão para mover cards');
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
// Lógica de movimento
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
return <KanboomBoard config={config} />;
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## 🔌 Formas de Importação
|
|
868
|
+
|
|
869
|
+
### Importação Padrão (ES Modules)
|
|
870
|
+
|
|
871
|
+
```tsx
|
|
872
|
+
import { KanboomBoard, useKanbanStore, type KanboomData, type KanboomConfig } from 'kanbase';
|
|
873
|
+
// Estilos são injetados automaticamente - não precisa importar CSS!
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### Importação de Tipos
|
|
877
|
+
|
|
878
|
+
```tsx
|
|
879
|
+
import type {
|
|
880
|
+
KanboomCard,
|
|
881
|
+
KanboomColumn,
|
|
882
|
+
KanboomData,
|
|
883
|
+
KanboomConfig,
|
|
884
|
+
CardRenderProps,
|
|
885
|
+
FilterGroup,
|
|
886
|
+
FilterRule
|
|
887
|
+
} from 'kanbase';
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### Importação de Selectors
|
|
891
|
+
|
|
892
|
+
```tsx
|
|
893
|
+
import {
|
|
894
|
+
selectCard,
|
|
895
|
+
selectAllCards,
|
|
896
|
+
selectColumn,
|
|
897
|
+
selectColumnCards
|
|
898
|
+
} from 'kanbase';
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Importação de Utilitários
|
|
902
|
+
|
|
903
|
+
```tsx
|
|
904
|
+
import { evaluateFilter, discoverFields } from 'kanbase';
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Importação UMD (para uso em navegadores)
|
|
908
|
+
|
|
909
|
+
```html
|
|
910
|
+
<script src="https://unpkg.com/kanbase/dist/kanbase.umd.js"></script>
|
|
911
|
+
<!-- Estilos são injetados automaticamente pelo JavaScript -->
|
|
912
|
+
<script>
|
|
913
|
+
const { KanboomBoard } = Kanbase;
|
|
914
|
+
</script>
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
## 🎯 Propriedades e Configurações Detalhadas
|
|
920
|
+
|
|
921
|
+
### Feature Flags
|
|
922
|
+
|
|
923
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
924
|
+
|------------|------|--------|-----------|
|
|
925
|
+
| `allowAdd` | `boolean` | `false` | Mostra botões (+) para adicionar cards |
|
|
926
|
+
| `allowEdit` | `boolean` | `false` | Permite editar cards (menu de contexto) |
|
|
927
|
+
| `allowColumnAdd` | `boolean` | `false` | Mostra placeholder "Nova Coluna" |
|
|
928
|
+
| `allowColumnEdit` | `boolean` | `false` | Permite editar colunas |
|
|
929
|
+
| `allowColumnDelete` | `boolean` | `false` | Permite deletar colunas |
|
|
930
|
+
| `allowColumnReorder` | `boolean` | `false` | Habilita drag handles para reordenar colunas |
|
|
931
|
+
| `allowFilters` | `boolean` | `true` | Habilita drawer de filtros (FAB) |
|
|
932
|
+
| `showURLSync` | `boolean` | `false` | Sincroniza estado com query params da URL |
|
|
933
|
+
|
|
934
|
+
### Configurações de Drag & Drop
|
|
935
|
+
|
|
936
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
937
|
+
|------------|------|--------|-----------|
|
|
938
|
+
| `dragActivationDistance` | `number` | `10` | Distância em pixels para ativar drag (mouse) |
|
|
939
|
+
| `touchActivationDelay` | `number` | `250` | Delay em ms para ativar drag (touch) |
|
|
940
|
+
|
|
941
|
+
### Configurações de Virtualização
|
|
942
|
+
|
|
943
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
944
|
+
|------------|------|--------|-----------|
|
|
945
|
+
| `virtualOverscan` | `number` | `5` | Número de cards extras renderizados fora da viewport |
|
|
946
|
+
| `estimatedCardHeight` | `number` | `90` | Altura estimada do card em pixels (para virtualização) |
|
|
947
|
+
|
|
948
|
+
### Configurações de Layout
|
|
949
|
+
|
|
950
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
951
|
+
|------------|------|--------|-----------|
|
|
952
|
+
| `columnWidth` | `number` | `320` | Largura das colunas em pixels |
|
|
953
|
+
| `columnMinHeight` | `number` | `500` | Altura mínima das colunas em pixels |
|
|
954
|
+
| `gap` | `number` | `16` | Espaçamento entre colunas em pixels |
|
|
955
|
+
|
|
956
|
+
### Callbacks
|
|
957
|
+
|
|
958
|
+
| Callback | Assinatura | Descrição |
|
|
959
|
+
|----------|-----------|-----------|
|
|
960
|
+
| `onCardMove` | `(cardId: string, fromColumn: string, toColumn: string, index: number) => void` | Chamado quando um card é movido |
|
|
961
|
+
| `onCardClick` | `(card: TCard) => void` | Chamado quando um card é clicado |
|
|
962
|
+
| `onEditCard` | `(card: TCard) => void` | Chamado quando edição de card é iniciada |
|
|
963
|
+
| `onColumnClick` | `(column: TColumn) => void` | Chamado quando uma coluna é clicada |
|
|
964
|
+
| `onEditColumn` | `(column: TColumn) => void` | Chamado quando edição de coluna é iniciada |
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## 🛠️ API da Store
|
|
969
|
+
|
|
970
|
+
### Métodos de Cards
|
|
971
|
+
|
|
972
|
+
```typescript
|
|
973
|
+
// Adicionar card
|
|
974
|
+
addCard(columnId: string, card: Omit<KanboomCard, 'id'>): string
|
|
975
|
+
|
|
976
|
+
// Atualizar card
|
|
977
|
+
updateCard(cardId: string, updates: Partial<KanboomCard>): void
|
|
978
|
+
|
|
979
|
+
// Deletar card
|
|
980
|
+
deleteCard(cardId: string): void
|
|
981
|
+
|
|
982
|
+
// Duplicar card
|
|
983
|
+
duplicateCard(cardId: string): string
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### Métodos de Colunas
|
|
987
|
+
|
|
988
|
+
```typescript
|
|
989
|
+
// Adicionar coluna
|
|
990
|
+
addColumn(column: Omit<KanboomColumn, 'id' | 'cardIds'>, position?: number): string
|
|
991
|
+
|
|
992
|
+
// Atualizar coluna
|
|
993
|
+
updateColumn(columnId: string, updates: Partial<KanboomColumn>): void
|
|
994
|
+
|
|
995
|
+
// Deletar coluna
|
|
996
|
+
deleteColumn(columnId: string, moveCardsTo?: string): void
|
|
997
|
+
|
|
998
|
+
// Mover coluna
|
|
999
|
+
moveColumn(columnId: string, newIndex: number): void
|
|
1000
|
+
|
|
1001
|
+
// Criar coluna com card
|
|
1002
|
+
addColumnWithCard(cardId: string, sourceColId: string, columnData: Omit<KanboomColumn, 'id' | 'cardIds'>): void
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### Métodos de Movimentação
|
|
1006
|
+
|
|
1007
|
+
```typescript
|
|
1008
|
+
// Mover card
|
|
1009
|
+
moveCard(cardId: string, sourceColId: string, targetColId: string, newIndex: number): void
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### Métodos de Filtros
|
|
1013
|
+
|
|
1014
|
+
```typescript
|
|
1015
|
+
// Definir busca global
|
|
1016
|
+
setSearchQuery(query: string): void
|
|
1017
|
+
|
|
1018
|
+
// Adicionar grupo de filtros
|
|
1019
|
+
addFilterGroup(group: FilterGroup): void
|
|
1020
|
+
|
|
1021
|
+
// Atualizar grupo de filtros
|
|
1022
|
+
updateFilterGroup(groupId: string, updates: Partial<FilterGroup>): void
|
|
1023
|
+
|
|
1024
|
+
// Remover grupo de filtros
|
|
1025
|
+
removeFilterGroup(groupId: string): void
|
|
1026
|
+
|
|
1027
|
+
// Remover regra de filtro
|
|
1028
|
+
removeFilterRule(groupId: string, ruleId: string): void
|
|
1029
|
+
|
|
1030
|
+
// Definir filtros completos
|
|
1031
|
+
setFilters(filters: KanbanFilters): void
|
|
1032
|
+
|
|
1033
|
+
// Limpar todos os filtros
|
|
1034
|
+
clearFilters(): void
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### Métodos Utilitários
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
// Definir dados completos do board
|
|
1041
|
+
setBoardData(data: KanboomData): void
|
|
1042
|
+
|
|
1043
|
+
// Atualizar configuração
|
|
1044
|
+
setConfig(config: Partial<KanboomConfig>): void
|
|
1045
|
+
|
|
1046
|
+
// Limpar board completamente
|
|
1047
|
+
clearBoard(): void
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
---
|
|
1051
|
+
|
|
1052
|
+
## 🎨 Componentes Default
|
|
1053
|
+
|
|
1054
|
+
O Kanbase vem com componentes prontos para uso:
|
|
1055
|
+
|
|
1056
|
+
- **DefaultCard**: Card padrão com suporte a tags, membros, datas, etc.
|
|
1057
|
+
- **DefaultColumnHeader**: Header de coluna com contador de cards e ações
|
|
1058
|
+
- **DefaultColumnEmpty**: Estado vazio para colunas sem cards
|
|
1059
|
+
- **DefaultAddCardForm**: Formulário para adicionar novos cards
|
|
1060
|
+
- **DefaultEditForm**: Formulário para editar cards existentes
|
|
1061
|
+
- **DefaultCardView**: Modal de visualização detalhada do card
|
|
1062
|
+
- **DefaultEditColumnForm**: Formulário para editar colunas
|
|
1063
|
+
|
|
1064
|
+
Todos esses componentes podem ser substituídos usando render props.
|
|
1065
|
+
|
|
1066
|
+
---
|
|
1067
|
+
|
|
1068
|
+
## 📝 Boas Práticas
|
|
1069
|
+
|
|
1070
|
+
### 1. Gerenciamento de Estado
|
|
1071
|
+
|
|
1072
|
+
```tsx
|
|
1073
|
+
// ✅ BOM: Use a store do Kanbase para estado local
|
|
1074
|
+
const { cards, addCard } = useKanbanStore();
|
|
1075
|
+
|
|
1076
|
+
// ✅ BOM: Sincronize com sua API nos callbacks
|
|
1077
|
+
onCardMove: async (cardId, fromCol, toCol, index) => {
|
|
1078
|
+
await syncWithAPI({ cardId, fromCol, toCol, index });
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// ❌ EVITE: Não misture estado do Kanbase com estado externo desnecessariamente
|
|
1082
|
+
const [cards, setCards] = useState(); // Use a store do Kanbase
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### 2. Performance
|
|
1086
|
+
|
|
1087
|
+
```tsx
|
|
1088
|
+
// ✅ BOM: Use virtualização para muitos cards
|
|
1089
|
+
const config = {
|
|
1090
|
+
virtualOverscan: 5,
|
|
1091
|
+
estimatedCardHeight: 90
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
// ✅ BOM: Memoize render props customizados
|
|
1095
|
+
const renderCard = useMemo(() => ({ card, isDragging }) => (
|
|
1096
|
+
<CustomCard card={card} isDragging={isDragging} />
|
|
1097
|
+
), []);
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
### 3. Customização
|
|
1101
|
+
|
|
1102
|
+
```tsx
|
|
1103
|
+
// ✅ BOM: Comece com componentes default e customize gradualmente
|
|
1104
|
+
const config = {
|
|
1105
|
+
allowAdd: true,
|
|
1106
|
+
// Adicione render props apenas quando necessário
|
|
1107
|
+
renderCard: customCardNeeded ? CustomCard : undefined
|
|
1108
|
+
};
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
### 4. Tipos
|
|
1112
|
+
|
|
1113
|
+
```tsx
|
|
1114
|
+
// ✅ BOM: Estenda tipos para seus casos de uso
|
|
1115
|
+
interface MyCard extends KanboomCard {
|
|
1116
|
+
metadata: {
|
|
1117
|
+
priority: 'low' | 'medium' | 'high';
|
|
1118
|
+
assignee: string;
|
|
1119
|
+
sprint: number;
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const config: KanboomConfig<MyCard> = {
|
|
1124
|
+
// ...
|
|
1125
|
+
};
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
---
|
|
1129
|
+
|
|
1130
|
+
## 🐛 Troubleshooting
|
|
1131
|
+
|
|
1132
|
+
### Cards não aparecem
|
|
1133
|
+
|
|
1134
|
+
- Verifique se `setBoardData` foi chamado com dados válidos
|
|
1135
|
+
- Confirme que `cardIds` nas colunas correspondem aos IDs em `cards`
|
|
1136
|
+
- Verifique se `columnOrder` inclui todas as colunas
|
|
1137
|
+
|
|
1138
|
+
### Drag & Drop não funciona
|
|
1139
|
+
|
|
1140
|
+
- Verifique se `allowAdd`, `allowEdit`, etc. estão habilitados conforme necessário
|
|
1141
|
+
- Confirme que não há erros no console
|
|
1142
|
+
- Verifique se o container tem altura definida (`h-screen` ou similar)
|
|
1143
|
+
|
|
1144
|
+
### Filtros não funcionam
|
|
1145
|
+
|
|
1146
|
+
- Confirme que `allowFilters: true` está na config
|
|
1147
|
+
- Verifique se os campos nos filtros correspondem aos campos dos cards
|
|
1148
|
+
- Use caminhos completos para campos aninhados: `metadata.priority` não `priority`
|
|
1149
|
+
|
|
1150
|
+
### Performance lenta
|
|
1151
|
+
|
|
1152
|
+
- Aumente `virtualOverscan` se necessário
|
|
1153
|
+
- Ajuste `estimatedCardHeight` para corresponder à altura real dos cards
|
|
1154
|
+
- Verifique se há muitos re-renders (use React DevTools)
|
|
1155
|
+
|
|
1156
|
+
---
|
|
1157
|
+
|
|
1158
|
+
## 🤝 Contribuindo
|
|
1159
|
+
|
|
1160
|
+
Contribuições são bem-vindas! Por favor:
|
|
1161
|
+
|
|
1162
|
+
1. Fork o repositório
|
|
1163
|
+
2. Crie uma branch para sua feature (`git checkout -b feature/amazing-feature`)
|
|
1164
|
+
3. Commit suas mudanças (`git commit -m 'Add some amazing feature'`)
|
|
1165
|
+
4. Push para a branch (`git push origin feature/amazing-feature`)
|
|
1166
|
+
5. Abra um Pull Request
|
|
1167
|
+
|
|
1168
|
+
---
|
|
1169
|
+
|
|
1170
|
+
## 📄 Licença
|
|
1171
|
+
|
|
1172
|
+
[Adicione sua licença aqui]
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
<p align="center">
|
|
1177
|
+
Construído com ❤️ para equipes obcecadas por performance.
|
|
1178
|
+
</p>
|