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 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>