@snksergio/design-system 0.61.0 → 0.63.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.
- package/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
- package/dist-lib/ai/blocos/indice.md +43 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
- package/dist-lib/ai/componentes/AlertModal.md +47 -0
- package/dist-lib/ai/componentes/AppShell.md +117 -0
- package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
- package/dist-lib/ai/componentes/Button.md +116 -0
- package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
- package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
- package/dist-lib/ai/componentes/CardOption.md +133 -0
- package/dist-lib/ai/componentes/Chart.md +93 -0
- package/dist-lib/ai/componentes/Chip.md +68 -0
- package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
- package/dist-lib/ai/componentes/ColorPicker.md +70 -0
- package/dist-lib/ai/componentes/Combobox.md +51 -0
- package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
- package/dist-lib/ai/componentes/DataList.md +111 -0
- package/dist-lib/ai/componentes/DataTable.md +867 -0
- package/dist-lib/ai/componentes/DatePicker.md +84 -0
- package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
- package/dist-lib/ai/componentes/EmptyState.md +72 -0
- package/dist-lib/ai/componentes/FileUploadField.md +95 -0
- package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
- package/dist-lib/ai/componentes/FooterTable.md +62 -0
- package/dist-lib/ai/componentes/FormField.md +110 -0
- package/dist-lib/ai/componentes/Gantt.md +552 -0
- package/dist-lib/ai/componentes/Header.md +98 -0
- package/dist-lib/ai/componentes/Icon.md +65 -0
- package/dist-lib/ai/componentes/Kanban.md +343 -0
- package/dist-lib/ai/componentes/Kpi.md +103 -0
- package/dist-lib/ai/componentes/List.md +61 -0
- package/dist-lib/ai/componentes/MarkdownText.md +59 -0
- package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
- package/dist-lib/ai/componentes/MessageAck.md +53 -0
- package/dist-lib/ai/componentes/MessageBubble.md +115 -0
- package/dist-lib/ai/componentes/MessageComposer.md +80 -0
- package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
- package/dist-lib/ai/componentes/Modal.md +88 -0
- package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
- package/dist-lib/ai/componentes/PageHeader.md +129 -0
- package/dist-lib/ai/componentes/Panel.md +84 -0
- package/dist-lib/ai/componentes/Scheduler.md +421 -0
- package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
- package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
- package/dist-lib/ai/componentes/Spinner.md +52 -0
- package/dist-lib/ai/componentes/Table.md +192 -0
- package/dist-lib/ai/componentes/TableToolbar.md +87 -0
- package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
- package/dist-lib/ai/componentes/Toast.md +49 -0
- package/dist-lib/ai/componentes/_primitivos.md +74 -0
- package/dist-lib/ai/componentes/avatar-ig.md +181 -0
- package/dist-lib/ai/componentes/indice.json +49 -0
- package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
- package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
- package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
- package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
- package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
- package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
- package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
- package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
- package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
- package/dist-lib/ai/exemplos/chat/index.ts +1 -0
- package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
- package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
- package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
- package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
- package/dist-lib/ai/exemplos/dashboard/index.ts +1 -0
- package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
- package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
- package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
- package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
- package/dist-lib/ai/exemplos/finance/index.ts +1 -0
- package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
- package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
- package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
- package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
- package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
- package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
- package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
- package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
- package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
- package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
- package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
- package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
- package/dist-lib/ai/global/componentes.md +217 -0
- package/dist-lib/ai/global/composicao.md +182 -0
- package/dist-lib/ai/indice.json +192 -0
- package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
- package/dist-lib/ai/manifest.json +42 -0
- package/dist-lib/ai/regras/design.md +88 -0
- package/dist-lib/ai/regras/temas.md +192 -0
- package/dist-lib/ai/regras-por-componente.json +103 -0
- package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
- package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
- package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
- package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
- package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
- package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
- package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
- package/dist-lib/ai/roteiros/dashboard-builder/blueprint.md +47 -0
- package/dist-lib/ai/roteiros/dashboard-builder/entrevista.md +62 -0
- package/dist-lib/ai/roteiros/dashboard-builder/geracao.md +88 -0
- package/dist-lib/ai/roteiros/dashboard-builder/roteiro.md +88 -0
- package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
- package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
- package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
- package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
- package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
- package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
- package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
- 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
|
+
```
|