@snksergio/design-system 0.60.0 → 0.62.0

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.
Files changed (65) hide show
  1. package/dist-lib/ai/componentes/AlertModal.md +47 -0
  2. package/dist-lib/ai/componentes/AppShell.md +117 -0
  3. package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
  4. package/dist-lib/ai/componentes/Button.md +116 -0
  5. package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
  6. package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
  7. package/dist-lib/ai/componentes/CardOption.md +133 -0
  8. package/dist-lib/ai/componentes/Chart.md +93 -0
  9. package/dist-lib/ai/componentes/Chip.md +68 -0
  10. package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
  11. package/dist-lib/ai/componentes/ColorPicker.md +70 -0
  12. package/dist-lib/ai/componentes/Combobox.md +51 -0
  13. package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
  14. package/dist-lib/ai/componentes/DataList.md +111 -0
  15. package/dist-lib/ai/componentes/DataTable.md +867 -0
  16. package/dist-lib/ai/componentes/DatePicker.md +84 -0
  17. package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
  18. package/dist-lib/ai/componentes/EmptyState.md +72 -0
  19. package/dist-lib/ai/componentes/FileUploadField.md +95 -0
  20. package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
  21. package/dist-lib/ai/componentes/FooterTable.md +62 -0
  22. package/dist-lib/ai/componentes/FormField.md +110 -0
  23. package/dist-lib/ai/componentes/Gantt.md +552 -0
  24. package/dist-lib/ai/componentes/Header.md +98 -0
  25. package/dist-lib/ai/componentes/Icon.md +65 -0
  26. package/dist-lib/ai/componentes/Kanban.md +343 -0
  27. package/dist-lib/ai/componentes/Kpi.md +103 -0
  28. package/dist-lib/ai/componentes/List.md +61 -0
  29. package/dist-lib/ai/componentes/MarkdownText.md +59 -0
  30. package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
  31. package/dist-lib/ai/componentes/MessageAck.md +53 -0
  32. package/dist-lib/ai/componentes/MessageBubble.md +115 -0
  33. package/dist-lib/ai/componentes/MessageComposer.md +80 -0
  34. package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
  35. package/dist-lib/ai/componentes/Modal.md +88 -0
  36. package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
  37. package/dist-lib/ai/componentes/PageHeader.md +129 -0
  38. package/dist-lib/ai/componentes/Panel.md +84 -0
  39. package/dist-lib/ai/componentes/Scheduler.md +421 -0
  40. package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
  41. package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
  42. package/dist-lib/ai/componentes/Spinner.md +52 -0
  43. package/dist-lib/ai/componentes/Table.md +192 -0
  44. package/dist-lib/ai/componentes/TableToolbar.md +87 -0
  45. package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
  46. package/dist-lib/ai/componentes/Toast.md +49 -0
  47. package/dist-lib/ai/componentes/_primitivos.md +74 -0
  48. package/dist-lib/ai/componentes/avatar-ig.md +181 -0
  49. package/dist-lib/ai/componentes/indice.json +49 -0
  50. package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
  51. package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
  52. package/dist-lib/ai/exemplos/dashboard/index.ts +1 -0
  53. package/dist-lib/ai/global/componentes.md +217 -0
  54. package/dist-lib/ai/global/composicao.md +182 -0
  55. package/dist-lib/ai/indice.json +177 -0
  56. package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
  57. package/dist-lib/ai/manifest.json +16 -0
  58. package/dist-lib/ai/regras/design.md +88 -0
  59. package/dist-lib/ai/regras/temas.md +192 -0
  60. package/dist-lib/ai/regras-por-componente.json +103 -0
  61. package/dist-lib/ai/roteiros/dashboard/blueprint.md +47 -0
  62. package/dist-lib/ai/roteiros/dashboard/entrevista.md +62 -0
  63. package/dist-lib/ai/roteiros/dashboard/geracao.md +88 -0
  64. package/dist-lib/ai/roteiros/dashboard/roteiro.md +88 -0
  65. package/package.json +4 -1
@@ -0,0 +1,65 @@
1
+ # Icon — USAGE
2
+
3
+ Biblioteca de ícones própria da iGreen. O SVG é fixo; só o `d` do path muda via
4
+ prop `name` (mapa de tokens em `icons.ts`). Categoria: data-display / foundation.
5
+
6
+ ## Quando usar
7
+
8
+ - Ícones da identidade iGreen (não cobertos / divergentes do lucide).
9
+ - Quando precisar de um set controlado e versionado dentro do DS.
10
+ - Para ícones genéricos de UI, `lucide-react` continua válido — este componente é
11
+ o set **próprio** da marca.
12
+
13
+ ## Import
14
+
15
+ ```tsx
16
+ import { Icon } from "@/components/ui/Icon";
17
+ ```
18
+
19
+ ## Exemplo mínimo
20
+
21
+ ```tsx
22
+ <Icon name="line-user" /> {/* md (20px), currentColor */}
23
+ <Icon name="fill-user" size="lg" tone="brand" />
24
+ <Icon name="line-edit" size={28} color="#0fc589" />
25
+ <span className="text-fg-danger"><Icon name="line-bin" /></span> {/* herda via CSS */}
26
+ ```
27
+
28
+ ## Props
29
+
30
+ | Prop | Valor | Default |
31
+ |---|---|---|
32
+ | `name` | `IconName` (autocomplete da lib) | — |
33
+ | `size` | preset `xs\|sm\|md\|lg\|xl` (tokens `size-icon-*` = 12/16/20/24/32) **ou** arbitrário (`number`→px / string CSS) | `md` |
34
+ | `tone` | semântico → `text-fg-{default\|muted\|brand\|danger\|success\|warning\|info}` | — |
35
+ | `color` | qualquer cor CSS (override; vence `tone`/CSS) | — |
36
+ | `title` / `aria-label` | acessível → `role="img"` + `<title>`. Sem isso = decorativo (`aria-hidden`) | decorativo |
37
+
38
+ ## Cor — 3 formas
39
+
40
+ 1. **CSS** (recomendado): herda `currentColor`, controlável por classe — `<span className="text-fg-brand"><Icon name="..."/></span>`.
41
+ 2. **`tone`**: token semântico do DS (`tone="danger"`).
42
+ 3. **`color`**: valor CSS arbitrário (`color="#0fc589"` / `color="var(--x)"`).
43
+
44
+ ## Convenção de nomes
45
+
46
+ - Prefixo `line-*` → contorno (viewBox `0 0 18 18` no set legado, senão 24).
47
+ - Prefixo `fill-*` → preenchido (viewBox `0 0 24 24`).
48
+ - Prefixo `igreen-*` → **ícones oficiais de marca** (produtos: green, livre, placas,
49
+ club, solar, telecom, licenciado, seguro, clientes). viewBox 24, herdam
50
+ `currentColor`/`tone` como qualquer ícone (sem cor fixa).
51
+ - O `viewBox` é inferido automaticamente pelo prefixo.
52
+
53
+ ## Adicionar um ícone novo
54
+
55
+ 1. Adicionar a entrada `"line-foo": "M…"` (ou `"fill-foo"` / `"igreen-foo"`) em `icons.ts`.
56
+ 2. O `IconName` atualiza sozinho (deriva das chaves). Sem mais nada.
57
+ 3. **Multi-path**: o valor pode ser `string` (1 path) **ou** `string[]` (vários paths
58
+ sobrepostos — ex.: `igreen-club`). Remova `fill=` hardcoded do SVG (herda `currentColor`).
59
+
60
+ ## Gotchas
61
+
62
+ - `size` arbitrário aplica `width`/`height` inline (não usa token) — prefira os presets.
63
+ - Decorativo por padrão (`aria-hidden`); passe `title`/`aria-label` quando o ícone
64
+ carregar significado sozinho.
65
+ - Catálogo navegável (busca + copiar nome): doc `#/icon`.
@@ -0,0 +1,343 @@
1
+ # Kanban — Guia de uso
2
+
3
+ Primitive dumb (igual `<Table>`, `<TableToolbar>`, `<FooterTable>`) — recebe `columns` + `cards` via props e renderiza um board horizontal de estágios. Não gerencia state de domínio (selection, detail, menus, dados) — tudo controlado externamente.
4
+
5
+ > Padrão equivalente ao DNA do DS: primitives dumb + smart orchestrators. Pra usar o Kanban acoplado a filter/sort/search da `<DataTable>`, veja [Integração com DataTable](#integração-com-datatable) abaixo.
6
+
7
+ ---
8
+
9
+ ## Imports
10
+
11
+ ```tsx
12
+ import {
13
+ Kanban,
14
+ type KanbanColumn,
15
+ type KanbanCardData,
16
+ type KanbanMenuItem,
17
+ type KanbanRenderCardParams,
18
+ } from "@/components/ui/Kanban";
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Quick start — uso standalone
24
+
25
+ ```tsx
26
+ const columns: KanbanColumn[] = [
27
+ { id: "todo", label: "A fazer", dotColor: "var(--color-fg-muted)" },
28
+ { id: "doing", label: "Em andamento", dotColor: "var(--color-fg-info)" },
29
+ { id: "done", label: "Concluído", dotColor: "var(--color-fg-success)", canReceiveDrop: false },
30
+ ];
31
+
32
+ const cards: KanbanCardData[] = tasks.map((t) => ({
33
+ id: t.id,
34
+ columnId: t.status,
35
+ title: t.title,
36
+ subtitle: t.code,
37
+ avatar: <Avatar size="sm" colorHex={t.assignee.color}>{t.assignee.initials}</Avatar>,
38
+ chip: <Chip color="warning" variant="soft" size="sm">Urgente</Chip>,
39
+ value: formatBRL(t.value),
40
+ }));
41
+
42
+ <Kanban
43
+ columns={columns}
44
+ cards={cards}
45
+ selectedIds={selectedIds}
46
+ onToggleSelect={toggleSelect}
47
+ onOpenCard={(id) => openTaskDetail(id)}
48
+ />
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Capacidades
54
+
55
+ | Capability | Como ativar |
56
+ |------------|-------------|
57
+ | **Seleção (bulk)** | `selectedIds: Set<string>` + `onToggleSelect: (cardId) => void` — checkbox aparece no hover do card |
58
+ | **Card aberto (detail)** | `openCardId` + `onOpenCard: (cardId) => void` — strip lateral brand |
59
+ | **Add card (header)** | `onAddCard: (columnId) => void` — botão `+` no header da coluna |
60
+ | **Add card (footer)** | `onAddInFooter: (columnId) => void` + `hideFooterAdd?: false` (default) |
61
+ | **Menu coluna** | `onColumnMenu: (columnId, anchor) => void` (manual) OU `getColumnMenuItems: (col) => KanbanMenuItem[]` (auto) |
62
+ | **Menu card** | `onCardMenu: (cardId, anchor) => void` (manual) OU `getCardMenuItems: (card) => KanbanMenuItem[]` (auto) |
63
+ | **Card render custom** | `renderCard: ({ card, selected, open }) => ReactNode` — substitui o miolo; wrapper continua sob controle do primitive |
64
+ | **DnD entre colunas** | `enableDnD: true` + `onCardMove: (cardId, from, to) => void \| Promise<unknown>` |
65
+ | **Constraint por coluna** | `column.canReceiveDrop: false` (terminal) + `column.canDragFrom: false` (locked) |
66
+
67
+ ---
68
+
69
+ ## Recipes
70
+
71
+ ### Menus padronizados via items
72
+
73
+ ```tsx
74
+ const cardMenu = (card: KanbanCardData): KanbanMenuItem[] => [
75
+ { label: "Ver detalhes", icon: <Eye />, onClick: () => openDetail(card.id) },
76
+ { label: "Editar", icon: <Pencil />, onClick: () => openEdit(card.id) },
77
+ { separator: true },
78
+ { label: "Excluir", icon: <Trash2 />, destructive: true, onClick: () => remove(card.id) },
79
+ ];
80
+
81
+ <Kanban {...} getCardMenuItems={cardMenu} />
82
+ ```
83
+
84
+ Primitive renderiza `<DropdownMenu>` DS automático. `KanbanMenuItem`:
85
+ - `label?` — string (ignorado se `separator: true`)
86
+ - `icon?` — ReactNode
87
+ - `onClick?` — handler
88
+ - `destructive?` — vermelho via `variant="destructive"`
89
+ - `disabled?` — bloqueia hover/click
90
+ - `separator?` — renderiza `<DropdownMenuSeparator>`
91
+
92
+ Coexiste com `onCardMenu`/`onColumnMenu` (callbacks manuais) — quando ambos fornecidos, `get*MenuItems` ganha.
93
+
94
+ ### Drag-and-Drop entre colunas
95
+
96
+ ```tsx
97
+ <Kanban
98
+ columns={[
99
+ { id: "active", label: "Ativo" },
100
+ { id: "pending", label: "Pendente" },
101
+ { id: "done", label: "Concluído", canReceiveDrop: false }, // terminal
102
+ { id: "archived", label: "Arquivado", canDragFrom: false }, // locked source
103
+ ]}
104
+ cards={cards}
105
+ enableDnD
106
+ onCardMove={(cardId, from, to) => {
107
+ // Optimistic update (consumer comita via props)
108
+ setCards((prev) =>
109
+ prev.map((c) => (c.id === cardId ? { ...c, columnId: to } : c)),
110
+ );
111
+ }}
112
+ />
113
+ ```
114
+
115
+ **Visual feedback built-in:**
116
+ - Card sendo arrastado: `opacity-40 cursor-grabbing`
117
+ - Coluna candidata válida durante drag: outline brand + bg subtle + **drop placeholder** (linha fina com ícone `+`) na posição que vai aterrissar (antes do card hovered, ou no fim se hover em área vazia)
118
+ - Coluna inválida (`canReceiveDrop: false`): `opacity-50` sempre durante drag (atenuada); `cursor-not-allowed` quando over
119
+
120
+ **Primitive não faz revert**: se `onCardMove` falhar, consumer mantém `cards` props inalterado e o card visualmente "volta" (porque a fonte da verdade são os props). Pra async commit:
121
+
122
+ ```tsx
123
+ onCardMove={async (cardId, from, to) => {
124
+ setCards((prev) => prev.map((c) => c.id === cardId ? { ...c, columnId: to } : c)); // optimistic
125
+ try {
126
+ await api.patch(`/cards/${cardId}`, { columnId: to });
127
+ } catch {
128
+ setCards((prev) => prev.map((c) => c.id === cardId ? { ...c, columnId: from } : c)); // revert
129
+ }
130
+ }}
131
+ ```
132
+
133
+ ### Render custom do card
134
+
135
+ ```tsx
136
+ <Kanban
137
+ {...}
138
+ renderCard={({ card, selected, open }) => (
139
+ <>
140
+ <div className="flex items-center gap-gp-md">
141
+ {card.avatar}
142
+ <span className="text-body-md font-medium flex-1 truncate">{card.title}</span>
143
+ {open && <Eye className="size-icon-sm text-fg-brand" />}
144
+ </div>
145
+ <Timeline events={card.description} />
146
+ </>
147
+ )}
148
+ />
149
+ ```
150
+
151
+ Consumer customiza o **miolo** do card. Wrapper externo (border, shadow, focus ring, checkbox/menu positioning, accessibility) continua sob controle do primitive — garante consistência visual entre boards customizados.
152
+
153
+ ---
154
+
155
+ ## Integração com DataTable
156
+
157
+ `<DataTable>` é o smart orchestrator. Quando `viewMode="kanban"` + `kanbanConfig`:
158
+ - Filter/search/sort/selection aplicam transparente nas rows
159
+ - O processado (já filtrado/ordenado) é mapeado em `<Kanban>` cards via `kanbanConfig.renderCard`
160
+ - Paginação é desligada automaticamente (não se aplica em board)
161
+ - TableToolbar parts irrelevantes (density, columns popover) podem ser ocultados via `toolbar.enableDensity: false`/`enableColumns: false` condicional
162
+
163
+ ### Paridade com `<Kanban>` standalone
164
+
165
+ O `DataTableKanbanConfig` é **1:1 com `KanbanProps`** — toda capability do primitive está exposta no smart wrapper. Bridge automático resolve `cardId → row` onde aplicável.
166
+
167
+ | `<Kanban>` prop | `kanbanConfig.*` | Bridge |
168
+ |---|---|---|
169
+ | `columns` | `columns?` | Auto-deriva de valores únicos do `groupByField` se omitido |
170
+ | `cards` | (derivado) | Transformer `useDataTableViewMode` mapeia rows → cards |
171
+ | `selectedIds` | (auto) | Bridge: `selectionModel.ids` (apenas modo include) |
172
+ | `onToggleSelect` | (auto) | Bridge: `cardId → row → selection.toggleRow(row)` |
173
+ | `openCardId` | `openCardId?` | Passthrough direto |
174
+ | `onOpenCard` | (auto) | Bridge: `cardId → row → onRowClick(row)` |
175
+ | `onAddCard` | `onAddCard?` | Passthrough direto (recebe `columnId`) |
176
+ | `onAddInFooter` | `onAddInFooter?` | Passthrough direto |
177
+ | `hideFooterAdd` | `hideFooterAdd?` | Passthrough direto |
178
+ | `getCardMenuItems` | `getCardMenuItems?` | Bridge: recebe `row` (não `card`) — consumer-friendly |
179
+ | `onCardMenu` | `onCardMenu?` | Passthrough direto (recebe `cardId, anchor`). Ignorado se `getCardMenuItems` definido |
180
+ | `getColumnMenuItems` | `getColumnMenuItems?` | Passthrough direto |
181
+ | `onColumnMenu` | `onColumnMenu?` | Passthrough direto. Ignorado se `getColumnMenuItems` definido |
182
+ | `renderCard` (slot) | `renderCardContent?` | Bridge: recebe `{ card, row, selected, open }` — adiciona `row` original |
183
+ | `enableDnD` | `enableDnD?` | Passthrough direto |
184
+ | `onCardMove` | `onCardMove?` | Passthrough direto |
185
+ | `emptyLabel` | `emptyLabel?` | Passthrough direto |
186
+ | `addLabel` | `addLabel?` | Passthrough direto |
187
+
188
+ > `renderCard` no `DataTableKanbanConfig` é o **mapeador de slots** (avatar/title/chip/...) — diferente do `renderCardContent` que substitui o miolo inteiro.
189
+
190
+ ### Exemplo completo
191
+
192
+ ```tsx
193
+ const [viewMode, setViewMode] = useState<DataTableViewMode>("table");
194
+ const [openCardId, setOpenCardId] = useState<string>();
195
+
196
+ <DataTable<Task>
197
+ rows={tasks}
198
+ columns={tableColumns}
199
+ viewMode={viewMode}
200
+ onViewModeChange={setViewMode}
201
+ onRowClick={(row) => setOpenCardId(row.id)} // bridge cobre kanban também
202
+ kanbanConfig={{
203
+ groupByField: "status",
204
+ columns: [
205
+ { id: "todo", label: "A fazer" },
206
+ { id: "doing", label: "Em andamento" },
207
+ { id: "done", label: "Concluído", canReceiveDrop: false },
208
+ ],
209
+
210
+ // Mapeador de slots (uso comum)
211
+ renderCard: ({ row }) => ({
212
+ title: row.title,
213
+ subtitle: row.code,
214
+ avatar: <Avatar size="sm" colorHex={row.assignee.color}>{row.assignee.initials}</Avatar>,
215
+ chip: <Chip color="warning" variant="soft" size="sm">{row.priority}</Chip>,
216
+ value: formatBRL(row.value),
217
+ }),
218
+
219
+ // Detail panel
220
+ openCardId,
221
+
222
+ // Add card buttons
223
+ onAddCard: (columnId) => createNewTask(columnId),
224
+ onAddInFooter: (columnId) => createNewTask(columnId),
225
+ hideFooterAdd: false,
226
+
227
+ // Menus padronizados (auto via DropdownMenu DS)
228
+ getCardMenuItems: (row) => [
229
+ { label: "Editar", icon: <Pencil />, onClick: () => editTask(row) },
230
+ { separator: true },
231
+ { label: "Excluir", icon: <Trash2 />, destructive: true, onClick: () => deleteTask(row) },
232
+ ],
233
+ getColumnMenuItems: (col) => [
234
+ { label: "Renomear", icon: <Pencil />, onClick: () => renameColumn(col.id) },
235
+ ],
236
+
237
+ // DnD
238
+ enableDnD: true,
239
+ onCardMove: (cardId, _from, to) =>
240
+ setTasks((prev) => prev.map((t) => (t.id === cardId ? { ...t, status: to } : t))),
241
+
242
+ // Override TOTAL do miolo (opcional — sobrescreve `renderCard` slots)
243
+ // renderCardContent: ({ card, row, selected, open }) => <CustomTimelineCard row={row} />,
244
+ }}
245
+ toolbar={{
246
+ enableSearch: true,
247
+ enableFilters: true,
248
+ enableColumns: viewMode === "table", // oculta em kanban
249
+ enableDensity: viewMode === "table", // oculta em kanban
250
+ customLeft: <ToolbarSegmented value={viewMode} onValueChange={setViewMode} items={VIEW_MODES} />,
251
+ }}
252
+ />
253
+ ```
254
+
255
+ Veja [src/preview/pages/ClientsKanbanPreview.tsx](../../../preview/pages/ClientsKanbanPreview.tsx) pra exemplo runnable com toggle table/kanban + DnD + filters compartilhados.
256
+
257
+ ---
258
+
259
+ ## API resumida
260
+
261
+ ### `KanbanProps`
262
+ | Prop | Type | Default | Descrição |
263
+ |---|---|---|---|
264
+ | `columns` | `KanbanColumn[]` | — | Definição das colunas (ordem = visual) |
265
+ | `cards` | `KanbanCardData[]` | — | Cards distribuídos via `columnId` |
266
+ | `selectedIds` | `Set<string>` | — | IDs selecionados (estado checked); checkbox aparece quando `onToggleSelect` é fornecido |
267
+ | `onToggleSelect` | `(cardId: string) => void` | — | Toggle do checkbox |
268
+ | `openCardId` | `string` | — | Card aberto no detail panel (strip brand) |
269
+ | `onOpenCard` | `(cardId: string) => void` | — | Click no card |
270
+ | `onAddCard` | `(columnId: string) => void` | — | Botão `+` do header |
271
+ | `onColumnMenu` | `(columnId: string, anchor: HTMLElement) => void` | — | Menu manual da coluna |
272
+ | `getColumnMenuItems` | `(column) => KanbanMenuItem[]` | — | Menu auto (ganha de `onColumnMenu`) |
273
+ | `onAddInFooter` | `(columnId: string) => void` | — | Botão `+ Adicionar` do rodapé |
274
+ | `hideFooterAdd` | `boolean` | `false` | Oculta o `+ Adicionar` |
275
+ | `onCardMenu` | `(cardId: string, anchor: HTMLElement) => void` | — | Menu manual do card |
276
+ | `getCardMenuItems` | `(card) => KanbanMenuItem[]` | — | Menu auto (ganha de `onCardMenu`) |
277
+ | `renderCard` | `({ card, selected, open }) => ReactNode` | — | Custom miolo do card |
278
+ | `enableDnD` | `boolean` | `false` | Habilita DnD |
279
+ | `onCardMove` | `(cardId, from, to) => void \| Promise<unknown>` | — | Callback de drop |
280
+ | `emptyLabel` | `string` | `"Nenhum item neste estágio"` | Texto do empty state |
281
+ | `addLabel` | `string` | `"Adicionar"` | Texto do botão `+ Adicionar` |
282
+ | `className` | `string` | — | className extra no root |
283
+
284
+ ### `KanbanColumn`
285
+ | Field | Type | Default | Descrição |
286
+ |---|---|---|---|
287
+ | `id` | `string` | — | Identificador único |
288
+ | `label` | `string` | — | Label do header |
289
+ | `dotColor` | `string` | — | Cor do dot (CSS var ou hex) |
290
+ | `count` | `number` | auto | Override do badge (default = `cards.filter(c => c.columnId === id).length`) |
291
+ | `canReceiveDrop` | `boolean` | `true` | `false` = coluna terminal/locked |
292
+ | `canDragFrom` | `boolean` | `true` | `false` = cards desta coluna não-arrastáveis |
293
+
294
+ ### `KanbanCardData`
295
+ Cada slot (`avatar`, `chip`, `value`, `footerLeft`, `footerRight`, `description`) é `ReactNode` — consumer monta usando DS (`<Avatar>`, `<Chip>`, etc) sem o Kanban ditar visual interno.
296
+
297
+ ---
298
+
299
+ ## Performance
300
+
301
+ - `cards` e `columns` devem ser memoizados pelo consumer (`useMemo`) — evita recompute do filter interno (`cards.filter(c => c.columnId === col.id)`) a cada render
302
+ - Em integração com DataTable, o transformer (`useDataTableViewMode`) já é memoizado em `rows`/`config`/`getRowId`
303
+ - Pra > 1000 cards, considere virtualizar dentro de cada coluna (não built-in V1; consumer pode wrap `renderCard` com `react-window` se precisar)
304
+
305
+ ---
306
+
307
+ ## ARIA
308
+
309
+ - Root `<Kanban>`: `role="region" aria-label="Quadro Kanban"`
310
+ - Botões de ação/menu (`<Button>` DS) têm `aria-label` derivados das props
311
+ - DnD via `@dnd-kit/core` traz suporte a teclado (Space/Setas) — KeyboardSensor sem configuração extra
312
+
313
+ ---
314
+
315
+ ## Troubleshooting
316
+
317
+ | Sintoma | Causa | Fix |
318
+ |---|---|---|
319
+ | Cards não aparecem em coluna esperada | `columnId` da card não bate com `column.id` | Verificar mapping `getValue` ou hard-coded values |
320
+ | Scroll horizontal aparece durante drag | Estado bug do `transform` (corrigido na V1 — não use `useDraggable.transform` no source quando há `<DragOverlay>`) | Atualizar Kanban primitive — V1 já não aplica transform no source |
321
+ | Checkbox visível ao desmarcar | Bug antigo (`group-focus-within` mantém visível) | Corrigido na V1 — usa `group-focus-visible` |
322
+ | Card "fica grande" no hover sem checkbox | `reserveCheck` aplicado por default | Corrigido na V1 — variant `reserveCheck` só ativa quando `onToggleSelect` é fornecido |
323
+ | Avatar com letra desproporcional/desalinhada | Avatar shadcn antigo, sem `<Avatar>` iGreen | Use `<Avatar size="xs\|sm\|md\|lg\|xl" colorHex={hex}>{initials}</Avatar>` |
324
+ | Scrollbar grossa demais | Não usa utility DS | Migrado pra `scrollbar-thin` (utility @utility no `tailwind-theme.css`) — V1 já aplica |
325
+ | `onCardMove` chamado mas card não move | Esquema reverse: consumer não atualiza `cards` props | Optimistic update via setState (veja DnD recipe) |
326
+ | DnD não preserva keyboard navigation | KeyboardSensor não fornecido | Já incluído via `useKanbanDnD` interno — nada a fazer |
327
+
328
+ ---
329
+
330
+ ## Versão atual: V1 — production-ready
331
+
332
+ **O que entrou na V1** (refator completo 2026-05):
333
+ - Bugs corrigidos: checkbox focus-within, avatar typography desalinhada, hover shift sem checkbox, scroll horizontal durante drag
334
+ - Features novas: `renderCard` slot, `getCardMenuItems`/`getColumnMenuItems` padronizados, DnD entre colunas com placeholder localizado + feedback visual de coluna inválida, empty state com ícone
335
+ - DS conformance: 3 botões raw → `<Button>` DS, 10+ hardcoded tokens migrados, scrollbar via utility `scrollbar-thin`, Avatar iGreen consumido nas previews
336
+ - Integração: `<DataTable viewMode="kanban" kanbanConfig={...}>` orquestra filter/sort/search/selection compartilhados
337
+
338
+ **Defer pra V2** (não bloqueia produção):
339
+ - Virtualização por coluna (cards count > 1000)
340
+ - Multi-level grouping (swimlanes)
341
+ - WIP limits per coluna (count + visual warning)
342
+ - Reordenação dentro da mesma coluna (hoje DnD só move entre colunas)
343
+ - Saved views capturarem `viewMode` no DataTable persistence
@@ -0,0 +1,103 @@
1
+ # Kpi
2
+
3
+ <!-- ds:regras
4
+ - `tone` e `KpiDelta tone` são DECISÃO, não derivação: subir nem sempre é bom (tempo de espera ↑ é ruim). `signed` só quando o sinal é literalmente bom/ruim
5
+ - o valor sai do preset `stat-*` pela prop `size` (default `md`=24px) — nunca `text-[Npx]` na unha; fora do `Kpi`, `text-stat-* tabular-nums`
6
+ - `divided` no `KpiGroup` vira UM card com divisórias e os filhos viram `plain` — não empilhe card dentro de card pra conseguir isso
7
+ -->
8
+
9
+ **O que é** — Primitivos composáveis pra cards de KPI/estatística.
10
+ **Categoria**: Templates / App-level. Doc viva: `#/kpi`.
11
+
12
+ 3 peças que compõem qualquer card de métrica:
13
+
14
+ | Peça | Papel |
15
+ | ---------- | ---------------------------------------------------------------------------------------- |
16
+ | `Kpi` | card base: label + ícone (por tone) + valor + delta + hint + slot (sparkline) + footnote |
17
+ | `KpiGroup` | layout: `columns` (2–6, responsivo) + `divided` (vira 1 card com divisórias) |
18
+ | `KpiDelta` | pílula de variação (sobre o `Chip`): tom semântico + seta |
19
+
20
+ ## Quando usar
21
+
22
+ Métricas de dashboard (rows de KPI, quad, cards com sparkline). Pra layouts
23
+ muito específicos (card de marca preenchido, faixa de detalhe), componha à mão —
24
+ veja as **Composições** em `#/kpi`.
25
+
26
+ ## Props
27
+
28
+ ### `Kpi`
29
+
30
+ | Prop | Tipo | Default |
31
+ | ---------- | ------------------------------------------------------ | ---------------------------- |
32
+ | `label` | `string` | — (obrigatório) |
33
+ | `value` | `ReactNode` | — (obrigatório) |
34
+ | `delta` | `ReactNode` (use `<KpiDelta>`) | — |
35
+ | `hint` | `ReactNode` (sublabel, ex.: "vs ontem") | — |
36
+ | `icon` | `ReactNode` | — |
37
+ | `tone` | `brand·success·warning·info·danger·neutral` | `neutral` |
38
+ | `size` | `sm·md·lg·xl` (preset `stat-*` do valor: 20/24/30/34px) | `md` (24px) |
39
+ | `footnote` | `ReactNode` (com divisória acima) | — |
40
+ | `children` | `ReactNode` (slot p/ sparkline/chart, abaixo do valor) | — |
41
+ | `surface` | `card · plain` | herda do `KpiGroup` (`card`) |
42
+
43
+ ### `KpiGroup`
44
+
45
+ | Prop | Tipo | Default |
46
+ | --------- | --------------------------------- | ------- |
47
+ | `columns` | `2·3·4·5·6` | `4` |
48
+ | `divided` | `boolean` (1 card com divisórias) | `false` |
49
+
50
+ ### `KpiDelta`
51
+
52
+ | Prop | Tipo | Default |
53
+ | ----------- | ---------------------------- | -------------------- |
54
+ | `value` | `ReactNode` | — |
55
+ | `tone` | `success · danger · neutral` | `success` |
56
+ | `direction` | `up · down` (mostra seta) | — |
57
+ | `signed` | `boolean` — deriva tom+seta do sinal do `value` | `false` |
58
+
59
+ ## Exemplo
60
+
61
+ ```tsx
62
+ import { Kpi, KpiGroup, KpiDelta } from "@/components/ui/Kpi";
63
+ import { Phone } from "lucide-react";
64
+
65
+ // Row com divisórias (1 card único)
66
+ <KpiGroup columns={4} divided>
67
+ <Kpi
68
+ label="Em atendimento"
69
+ value="12"
70
+ icon={<Phone />}
71
+ tone="success"
72
+ delta={<KpiDelta value="+3" />}
73
+ hint="vs ontem"
74
+ />
75
+ {/* ...mais Kpi */}
76
+ </KpiGroup>
77
+
78
+ // Card com sparkline (slot)
79
+ <Kpi label="Total Income" value="$6,280" delta={<KpiDelta value="+18%" />}>
80
+ <ChartContainer config={cfg} className="h-[64px]"> ... </ChartContainer>
81
+ </Kpi>
82
+ ```
83
+
84
+ ## Gotchas
85
+
86
+ - **`tone` é decisão do consumidor.** "Subir" nem sempre é positivo (tempo de
87
+ espera ↑ é ruim) → escolha o `KpiDelta tone` certo, não derive cego da direção.
88
+ Atalho: `<KpiDelta value="+458" signed />` deriva verde/vermelho + seta do sinal
89
+ — use SÓ quando o delta é literalmente +/- bom/ruim.
90
+ - **Composições de dashboard/lista** (Painel do Líder, fusão KPI+evolução,
91
+ chart-card, card dividido, distribuição de tabela/lista) → receitas canônicas na
92
+ rota `#/dashboard-showcase` do catálogo hospedado. (Fonte no repo do DS:
93
+ `.ai/context/components/dashboard-patterns.md` — caminho **interno**, não existe
94
+ em quem consome por npm ou copy-in.)
95
+ - **`divided` controla a superfície** dos `Kpi` filhos (viram `plain`); fora de um
96
+ group `divided`, cada `Kpi` é um card. Override com a prop `surface` se preciso.
97
+ - **Valor do KPI usa preset `stat-*`** (`size`, default `md`=24px). Números
98
+ grandes de dashboard → `size="lg"`/`"xl"`. Fora do `Kpi`, use `text-stat-*
99
+ tabular-nums` direto — nunca `text-[Npx]` na unha. O preset já traz bold +
100
+ leading tight; adicione só `tabular-nums`.
101
+ - **Sparkline via `Chart`** (Recharts) no slot `children` — id de `linearGradient`
102
+ sem espaço/`&` (use índice), senão o `url(#...)` não resolve.
103
+ - Cores 100% por token (tones via `bg-bg-*-muted`/`fg-*`). Sem hardcode.
@@ -0,0 +1,61 @@
1
+ # List — USAGE
2
+
3
+ **O que é:** primitivo de listagem em **cards** (cada row é um card). _Categoria: Data display._
4
+ Burro como o `Table` — a versão com toolbar/busca/filtros/server (`DataList`) é o passo 2.
5
+
6
+ **Quando usar:** listas de entidades em formato card, com 3 layouts:
7
+ - `standard` — lista plana.
8
+ - `grouped` — seções por status/seção + **drag-and-drop** (entre grupos e reorder dentro).
9
+ - `hierarchical` — árvore-como-lista colapsável com **linhas de conexão** (org, treeview).
10
+
11
+ Para tabela densa use `Table`/`DataTable`; para colunas horizontais use `Kanban`.
12
+
13
+ ## Conteúdo do card
14
+ Slots (`ListItemData`): `leading` (avatar/ícone), `title`, `subtitle`, `description`,
15
+ `meta` (colunas alinhadas tipo Role/Status), `trailing` (valor/badge). Para card rico,
16
+ `renderItem(item, state)` substitui só o miolo — o wrapper (card, selected/open, handle,
17
+ checkbox, indent, conectores) é sempre do List.
18
+
19
+ ## Exemplos mínimos
20
+ ```tsx
21
+ // standard
22
+ <List items={team} onItemClick={open} getMenuItems={(i) => [{ label: "Editar", onClick }]} />
23
+
24
+ // grouped + DnD (consumer commita no onMove)
25
+ <List layout="grouped" items={tasks} groups={groups} enableDnD
26
+ onMove={(id, from, to, toIndex) => commit(id, to, toIndex)} />
27
+
28
+ // hierarchical (children aninhados)
29
+ <List layout="hierarchical" items={org} defaultExpandedIds={new Set(["acme"])} />
30
+ ```
31
+
32
+ ## Props essenciais
33
+ | Prop | Nota |
34
+ |---|---|
35
+ | `layout` | `standard` (default) · `grouped` · `hierarchical` |
36
+ | `items` | `ListItemData[]`; na hierarquia, aninhe via `children` |
37
+ | `groups` | `ListGroup[]` (layout grouped) |
38
+ | `renderItem` | override do miolo do card |
39
+ | `getMenuItems` | kebab `⋯` por item |
40
+ | `selectable` + `selectedIds`/`defaultSelectedIds`/`onSelectionChange` | seleção (controlado/não-controlado) |
41
+ | `expandedIds`/`defaultExpandedIds`/`onExpandedChange` | colapso de grupos/hierarquia |
42
+ | `enableDnD` + `onReorder`(standard)/`onMove`(grouped) | drag-and-drop |
43
+ | `showConnectors` / `indentSize` | conectores / indent px (hierárquico) |
44
+ | `branchHighlight` | destaque de família (hierárquico): `none` (conectores, default) · `block` (painéis aninhados = 1 bloco por família) · `active` (só o ramo do último nó aberto recebe o painel + trilha) |
45
+ | `loading`/`skeletonCount` · `emptyState` · `density` | estados / densidade |
46
+
47
+ ## Gotchas
48
+ - **DnD via `@hello-pangea/dnd`** (física natural de lista — displacement suave +
49
+ placeholder que abre espaço). Burro: o List emite `onMove(id, from, to, toIndex)`
50
+ (grouped) / `onReorder(id, toIndex)` (standard) e **o consumer commita** o estado.
51
+ `toIndex` é o `destination.index` do hello-pangea (índice final no grupo destino).
52
+ - `groupSurface` envolve cada grupo num painel sutil ("card fino") pra diferenciar da
53
+ superfície. Realce da área de destino (`isDraggingOver`) e elevação do card em
54
+ arrasto (`isDragging`) são automáticos.
55
+ - **Grupos abrem por padrão**; hierarquia **colapsa** por padrão (semeie `defaultExpandedIds`).
56
+ - Hierarquia: a fonte canônica é a **árvore aninhada** (`children`), não `parentId`.
57
+ Hierárquico **não** tem DnD no v1.
58
+ - `meta` (colunas) é escondido em telas estreitas (`hidden md:flex`) — use `trailing`/
59
+ `subtitle` para info crítica em mobile.
60
+ - Dep extra: `@hello-pangea/dnd` (List); o Kanban usa `@dnd-kit` — DnD libs distintas
61
+ por componente, por design (lista vertical favorece o hello-pangea).
@@ -0,0 +1,59 @@
1
+ # MarkdownText
2
+
3
+ **Categoria:** composto (ui/) — renderização de texto · Chat/Atendimento
4
+
5
+ Renderiza markdown estilo WhatsApp já **sanitizado**. Faz parse manual do texto
6
+ para React nodes (sem `dangerouslySetInnerHTML`), então qualquer HTML/markdown
7
+ não suportado vira texto literal — seguro contra injeção por design. Porta do
8
+ legado `ui-igreen-hub/src/components/MarkdownWrapper`.
9
+
10
+ ## Quando usar
11
+
12
+ - Corpo de bolha de mensagem no chat (negrito/itálico/tachado/mono/links).
13
+ - Prévia de última mensagem na lista de tickets (`inline` + `line-clamp-*` do consumer).
14
+ - Qualquer texto vindo do WhatsApp que use a sintaxe `*_~` `` ` ``.
15
+
16
+ ## Sintaxe suportada
17
+
18
+ | Markdown | Resultado |
19
+ |----------|-----------|
20
+ | `*bold*` | **negrito** (`font-semibold`) |
21
+ | `_italic_` | _itálico_ |
22
+ | `~strike~` | ~~tachado~~ |
23
+ | `` `mono` `` ou ```` ```mono``` ```` | monoespaçado com fundo sutil (`bg-bg-muted`) |
24
+ | `http(s)://…` ou `www.…` | link em nova aba (`target=_blank rel=noopener noreferrer`) |
25
+
26
+ ## Props
27
+
28
+ | Prop | Tipo | Default | Descrição |
29
+ |------|------|---------|-----------|
30
+ | `children` | `string` | — (obrigatório) | Texto-fonte em markdown WhatsApp. |
31
+ | `inline` | `boolean` | `false` | `true` → `<span>` e colapsa quebras de linha (prévia truncável). `false` → `<p>` preservando quebras (`whitespace-pre-wrap`). |
32
+ | `className` | `string` | — | Classe extra no elemento raiz. |
33
+
34
+ ## Exemplo mínimo
35
+
36
+ ```tsx
37
+ import { MarkdownText } from "@/components/ui/MarkdownText";
38
+
39
+ // Bolha de mensagem (multilinha, preserva quebras)
40
+ <MarkdownText>{"Olá *João*, segue o _link_: https://igreen.com.br"}</MarkdownText>
41
+
42
+ // Prévia de última mensagem (truncável)
43
+ <MarkdownText inline className="line-clamp-1">
44
+ {lastMessage.body}
45
+ </MarkdownText>
46
+ ```
47
+
48
+ ## Gotchas
49
+
50
+ - **Sem `dangerouslySetInnerHTML`** — tags HTML digitadas pelo usuário aparecem
51
+ como texto literal (intencional, sanitização).
52
+ - `inline` colapsa **todas** as quebras/espaços redundantes num único espaço;
53
+ o truncamento (`line-clamp-1/2`) é responsabilidade do consumer.
54
+ - Code spans são **opacos**: o conteúdo entre crases não recebe bold/italic/link.
55
+ - `text-body-sm` é fixo (sem token novo). Para outro tamanho, sobreponha via
56
+ `className` no consumer.
57
+ - Não é interativo além dos links (que herdam o foco padrão do `<a>`); não há
58
+ variante de cor/foco própria.
59
+ ```