@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,99 @@
1
+ # CardCheckbox
2
+
3
+ **Categoria:** Form input (opção destacada).
4
+ **Status:** Disponível v0.7.0+.
5
+
6
+ ## O que é
7
+
8
+ Checkbox apresentado como card clicável grande, com label + description
9
+ visíveis. Mesma estética dos radio cards do design system (bg verde fraco no
10
+ selected, border-brand, shadow leve).
11
+
12
+ Diferente do `FormFieldCheckbox` que é layout horizontal compact (ideal pra
13
+ um aceite "li os termos") — `CardCheckbox` é pra opções que merecem destaque
14
+ visual ("salvar essa conta", "marcar como favorito", "ativar notificações").
15
+
16
+ ## Quando usar
17
+
18
+ - Opção dentro de form que precisa de visibilidade extra (com explicação)
19
+ - Toggle de feature opcional após uma ação principal (ex: "salvar essa conta
20
+ pra usar depois")
21
+ - Item de lista de opções binárias onde cada item tem título + descrição
22
+ - Quando você quer área-clique grande (acessibilidade) sem inflar visualmente
23
+ um checkbox simples
24
+
25
+ ## Quando NÃO usar
26
+
27
+ - Lista de aceite legal (terms & conditions) → use `FormFieldCheckbox`
28
+ - Múltiplos checkboxes em sequência (form denso) → use `FormFieldCheckbox` com
29
+ `gap-form-gap`
30
+ - Toggle ON/OFF de uma config (sem descrição longa) → use `Switch`
31
+
32
+ ## Props essenciais
33
+
34
+ | Prop | Tipo | Default | Descrição |
35
+ |-------------------|-------------------------|---------|----------------------------------------------|
36
+ | `label` | `ReactNode` | — | Título do card (obrigatório) |
37
+ | `description` | `ReactNode` | — | Texto secundário abaixo do label |
38
+ | `icon` | `ReactNode` | — | Ícone opcional à esquerda (antes do checkbox)|
39
+ | `checked` | `boolean \| "indeterminate"` | — | Estado controlado |
40
+ | `onCheckedChange` | `(checked) => void` | — | Callback de mudança |
41
+ | `disabled` | `boolean` | `false` | Desativa o card inteiro |
42
+ | `className` | `string` | — | Override de classe no card root |
43
+
44
+ Aceita também as demais props do `CheckboxPrimitive.Root` do Radix
45
+ (`name`, `required`, etc), **exceto `id`** (gerado automaticamente via
46
+ `useId()` pra vincular o `<label>` ao checkbox) e `className`, que é
47
+ aplicado ao card root (`<label>`), não ao checkbox Radix.
48
+
49
+ ## Exemplo mínimo
50
+
51
+ ```tsx
52
+ import { useState } from "react";
53
+ import { CardCheckbox } from "@/components/ui/CardCheckbox";
54
+
55
+ function MyForm() {
56
+ const [save, setSave] = useState(true);
57
+ return (
58
+ <CardCheckbox
59
+ label="Salvar essa conta pra usar depois"
60
+ description="A conta aparecerá nas próximas vezes em 'Contas cadastradas'."
61
+ checked={save}
62
+ onCheckedChange={(v) => setSave(v === true)}
63
+ />
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Variants (via card-checkbox.styles.ts)
69
+
70
+ | Variant | Valores | Efeito |
71
+ |-----------|--------------------------|---------------------------------------------------|
72
+ | `selected` | `true` \| `false` | Selected: bg-success-muted + border-brand + shadow|
73
+ | `disabled` | `true` | Opacity 50% + pointer-events none |
74
+
75
+ `selected` é derivado de `checked` automaticamente — não precisa passar.
76
+
77
+ ## Gotchas
78
+
79
+ - **Não usar dentro de `<form>` sem prevenir submit** — clicar no card
80
+ dispara o checkbox via label htmlFor. Se houver `<button type="submit">`
81
+ no mesmo form, o teclado Enter no card pode submeter. Use
82
+ `<button type="button">` em outros controles próximos.
83
+ - **`onCheckedChange` recebe `boolean | "indeterminate"`** — não pode passar
84
+ direto pra `setState<boolean>`. Use `(v) => setX(v === true)`.
85
+ - **icon prop** é renderizado dentro de um `<span aria-hidden>` — não use
86
+ ícone como controle clicável, só como dica visual.
87
+
88
+ ## Tokens usados
89
+
90
+ - `bg-bg-success-muted` (selected bg)
91
+ - `border-border-brand` (selected border)
92
+ - `bg-bg-muted` (hover bg, não-selected)
93
+ - `text-fg-default` (label)
94
+ - `text-fg-muted` (description)
95
+ - `shadow-sh-sm` (selected shadow)
96
+ - `rounded-radius-lg`
97
+ - `p-pad-xl`
98
+ - `gap-gp-lg` (entre checkbox/icon/body)
99
+ - `ring-ring-brand` (focus)
@@ -0,0 +1,133 @@
1
+ # CardOption
2
+
3
+ <!-- ds:regras
4
+ - switch = efeito IMEDIATO, sem Salvar, e só em `layout="list"`; tela com botão Salvar → `type="checkbox"`
5
+ - opções que se COMPARAM (plano, tier, preço, descrição longa) → cards espaçados; itens do mesmo tipo com rótulo curto (settings, permissões, pagamento, endereço) → `layout="list"`
6
+ - mais de ~5 opções → não é CardOption: `Select`/`Combobox`. E on/off de UMA coisa nunca são 2 radios
7
+ - `type="radio"` EXIGE `<CardOptionGroup type="radio">` em volta; checkbox e switch funcionam soltos
8
+ - em lista o selecionado NÃO pinta por default (a borda ali é a divisória) — queira pintado? `highlightSelected` no grupo
9
+ - omita `orientation`, `highlightSelected` e `size`: derivam do type · `md` é o calibrado
10
+ -->
11
+
12
+ **O que é** — controle de formulário apresentado como **card clicável** (área grande, label +
13
+ descrição visíveis), com o controle trocável por prop: `checkbox`, `radio` ou `switch`.
14
+ **Categoria**: Form Controls.
15
+
16
+ **Quando usar** — opção destacada que merece área de clique grande e texto de apoio: escolha
17
+ de plano/frete, lista de permissões, painel de configurações. Para campo compacto em
18
+ formulário, use `FormFieldCheckbox` / `RadioGroup` cru. Para um único toggle inline, `Switch`
19
+ direto. **Acima de ~5 opções, não use CardOption**: vira parede de cards — é `Select` ou
20
+ `Combobox` ([Soul DS](https://soul.emplifi.io/latest/components/in-progress/radio-button-card-Pnx3WsEU)).
21
+
22
+ ## Escolhendo: duas perguntas
23
+
24
+ ### 1. Qual `type`? — depende da DECISÃO, não da aparência
25
+
26
+ | Use | Quando | Fonte |
27
+ |---|---|---|
28
+ | `switch` | liga/desliga uma funcionalidade **com efeito imediato**, sem Salvar | [NN/g](https://www.nngroup.com/articles/toggle-switch-guidelines/) |
29
+ | `radio` | uma entre várias mutuamente exclusivas, e o usuário precisa **ver todas** pra decidir | [NN/g](https://www.nngroup.com/articles/checkboxes-vs-radio-buttons/) |
30
+ | `checkbox` | combinação livre (zero, uma ou várias) — **e** o substituto do switch quando há Salvar | idem |
31
+
32
+ Os dois erros que essa tabela evita, os dois de fonte externa:
33
+
34
+ - **Switch em tela com botão Salvar.** O switch promete efeito imediato — "should take
35
+ immediate effect and should not require the user to click Save or Submit" (NN/g). Num form
36
+ com submit, o usuário não sabe se já valeu. Ali o controle é `type="checkbox"`.
37
+ - **Dois radios pra on/off de uma coisa.** Um par "Ativado / Desativado" é um switch (se
38
+ imediato) ou **um** checkbox (se tem submit) — nunca dois radios.
39
+
40
+ ### 2. Lista ou cards espaçados? — depende de quanta COMPARAÇÃO a decisão pede
41
+
42
+ | Use | Quando |
43
+ |---|---|
44
+ | `layout="list"` | itens **do mesmo tipo**, rótulo curto, decisão já conhecida: configurações (switch), permissões (checkbox), meio de pagamento / endereço / forma de entrega (radio). Densidade > destaque |
45
+ | `spaced` (default) | cada opção precisa ser **comparada** — preço, descrição longa, ícone, badge: plano, tier, onboarding. O gap é o que separa as unidades de comparação |
46
+
47
+ **Switch vive em lista, não em card solto.** É literal na Apple HIG: *"Use the switch toggle
48
+ style only in a list row"* — e a razão é que o switch tem mais peso visual que um checkbox, o
49
+ que só se justifica quando a linha inteira lhe dá contexto
50
+ ([HIG](https://developers.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles/)).
51
+
52
+ Meio de pagamento é o caso clássico de **radio em lista**: alvo grande e indicador visível
53
+ resolvem o problema do radio nativo, e o logo/ícone entra à esquerda, junto do nome
54
+ ([Baymard](https://baymard.com/blog/payment-method-selection)) — o que o `icon` do CardOption
55
+ já faz por construção.
56
+
57
+ ## Props essenciais
58
+
59
+ | Prop | Tipo | Default | Descrição |
60
+ |------|------|---------|-----------|
61
+ | `type` | `"checkbox" \| "radio" \| "switch"` | herda do grupo, ou `"checkbox"` | qual controle o card embrulha |
62
+ | `value` | `string` | — | **obrigatório com `type="radio"`** |
63
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 8 / 12 / 16px de padding |
64
+ | `orientation` | `"left" \| "right"` | **derivado do `type`** | lado do controle |
65
+ | `highlightSelected` | `boolean` | do `type`; **em lista: `false`** | fundo + cor de borda no selecionado |
66
+ | `label` | `ReactNode` | — | obrigatório |
67
+ | `description` | `ReactNode` | — | texto de apoio |
68
+ | `icon` | `ReactNode` | — | **sempre à esquerda**, mesmo com `orientation="right"`. Piso de 20×20px |
69
+ | `checked` / `onCheckedChange` | | — | controlado (no radio, quem manda é o grupo) |
70
+ | `disabled` | `boolean` | — | |
71
+
72
+ ### `CardOptionGroup`
73
+
74
+ | Prop | Tipo | Default | Descrição |
75
+ |------|------|---------|-----------|
76
+ | `type` | igual ao item | `"checkbox"` | `"radio"` faz o grupo **ser** o `RadioGroup` do Radix |
77
+ | `layout` | `"spaced" \| "list"` | `"spaced"` | `list` = contorno no grupo + divisória entre linhas, sem gap. Vale pros **3 tipos** |
78
+ | `highlightSelected` | `boolean` | — | liga/desliga o destaque em todos os filhos de uma vez |
79
+ | `size` / `orientation` | | | aplicados a todos os filhos |
80
+ | `value` / `defaultValue` / `onValueChange` / `name` | | — | só `type="radio"` |
81
+
82
+ ## Exemplo mínimo
83
+
84
+ ```tsx
85
+ // checkbox solto
86
+ <CardOption
87
+ label="Salvar essa conta pra usar depois"
88
+ description="Aparece na lista de contas favoritas"
89
+ checked={salvar}
90
+ onCheckedChange={setSalvar}
91
+ />
92
+
93
+ // radio — o grupo é obrigatório
94
+ <CardOptionGroup type="radio" value={frete} onValueChange={setFrete}>
95
+ <CardOption value="standard" label="Standard" description="4 a 10 dias úteis" />
96
+ <CardOption value="express" label="Express" description="2 a 3 dias úteis" />
97
+ </CardOptionGroup>
98
+
99
+ // lista de settings — switch, à direita, sem destaque de selecionado
100
+ <CardOptionGroup type="switch" layout="list">
101
+ <CardOption label="Wi-Fi" description="Conectar a redes sem fio" checked={wifi} onCheckedChange={setWifi} />
102
+ <CardOption label="Bluetooth" description="Permitir conexões Bluetooth" checked={bt} onCheckedChange={setBt} />
103
+ </CardOptionGroup>
104
+ ```
105
+
106
+ ## Gotchas / cuidados
107
+
108
+ - **`type="radio"` sem `CardOptionGroup` não funciona direito.** É o `RadioGroup` do Radix que
109
+ dá navegação por seta e agrupamento por `name`. Checkbox e switch são autônomos.
110
+ - **Não passe `orientation` nem `highlightSelected` sem motivo.** Eles derivam do `type`, e o
111
+ default é a convenção: switch fica **à direita** (linha de configuração) e **sem destaque de
112
+ selecionado**, porque switch é *estado*, não seleção — uma lista de settings toda pintada de
113
+ verde é ruído. Foi por isso que o exemplo antigo do Card Toggle não tinha estado visual.
114
+ - **`layout="list"` não é exclusivo do switch.** Com radio vira seletor de linha única; com
115
+ checkbox, lista de permissões. No modo lista o contorno é do grupo e a divisória é a borda
116
+ de baixo de cada item (a última suprimida) — não force borda no item.
117
+ - **Em lista, o destaque de selecionado vem desligado — inclusive em checkbox e radio.** É
118
+ consequência de onde a borda mora: em lista a única borda do item é a de baixo, ou seja a
119
+ **divisória**, então o `border-brand` não contorna o selecionado — pinta a linha que o separa
120
+ do vizinho — e o fundo vira faixa colorida no meio da lista (medido em 2026-08-27, antes do
121
+ ajuste: linha verde com divisória verde). Quem quer o pintado liga `highlightSelected` no
122
+ grupo ou no item; a prop vence nas duas direções, e em lista a sombra do destaque sai (dentro
123
+ do `overflow-hidden` do grupo ela não eleva, só vaza).
124
+ - **O ícone fica sempre à esquerda**, inclusive com `orientation="right"`: o `order-last` move
125
+ só o controle. Ele identifica a opção e pertence ao lado do texto. Piso de 20×20px.
126
+ - **É um `<label htmlFor>` nativo, nunca `<button>` (L-025).** Não embrulhe em outro botão nem
127
+ ponha `onClick` no card: o clique já chega ao controle real pelo label, e trocar isso quebra
128
+ o leitor de tela ("button" em vez de checkbox) e o submit nativo.
129
+ - **O anel de foco é do CARD**, via `has-[:focus-visible]`. O `CardCheckbox` antigo declarava
130
+ `focus-visible:ring-4` no próprio `<label>` — inerte, porque label não recebe foco (medido
131
+ em 2026-08-27: o único anel visível era o do controle de 16px).
132
+ - **`CardCheckbox` continua existindo** como atalho de `type="checkbox"`. Componente novo
133
+ deve usar `CardOption`.
@@ -0,0 +1,93 @@
1
+ # Chart — USAGE
2
+
3
+ Wrapper sobre o **Recharts 3** que aplica a paleta do DS (tokens `--color-chart-1..5`) e fornece Tooltip/Legend já estilizados com os tokens iGreen. Categoria: data-display.
4
+
5
+ ## Quando usar
6
+
7
+ - Qualquer gráfico (área, barras, linhas, pizza, radar, radial) numa tela do iGreen
8
+ - Garantir cores, tooltip e legenda consistentes entre gráficos
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import {
14
+ ChartContainer,
15
+ ChartTooltip,
16
+ ChartTooltipContent,
17
+ ChartLegend,
18
+ ChartLegendContent,
19
+ type ChartConfig,
20
+ } from "@/components/ui/Chart";
21
+ import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
22
+ ```
23
+
24
+ ## Conceito
25
+
26
+ `ChartContainer` recebe um `config` (mapa série → label/cor/ícone) e injeta cada
27
+ cor como CSS var `--color-{key}` **escopada nessa instância**. Os elementos do
28
+ Recharts referenciam `var(--color-{key})`.
29
+
30
+ ```tsx
31
+ const config = {
32
+ desktop: { label: "Desktop", color: "var(--color-chart-1)" },
33
+ mobile: { label: "Mobile", color: "var(--color-chart-2)" },
34
+ } satisfies ChartConfig;
35
+
36
+ <ChartContainer config={config} className="h-[260px] w-full">
37
+ <AreaChart data={data}>
38
+ <CartesianGrid vertical={false} />
39
+ <XAxis dataKey="month" tickLine={false} axisLine={false} />
40
+ <ChartTooltip content={<ChartTooltipContent />} />
41
+ <Area dataKey="desktop" fill="var(--color-desktop)" stroke="var(--color-desktop)" />
42
+ <Area dataKey="mobile" fill="var(--color-mobile)" stroke="var(--color-mobile)" />
43
+ </AreaChart>
44
+ </ChartContainer>
45
+ ```
46
+
47
+ ## Props essenciais
48
+
49
+ | Componente | Prop | Função |
50
+ |---|---|---|
51
+ | `ChartContainer` | `config: ChartConfig` | mapa série → `{ label, color?, icon?, theme? }` |
52
+ | `ChartContainer` | `className` | controle de altura/aspect (ex: `h-[260px] w-full`) |
53
+ | `ChartTooltip` | `content={<ChartTooltipContent/>}` | tooltip do DS |
54
+ | `ChartTooltipContent` | `hideLabel`, `hideIndicator`, `indicator` (`dot`/`line`/`dashed`), `labelFormatter`, `formatter`, `nameKey`, `labelKey` | customização |
55
+ | `ChartLegend` | `content={<ChartLegendContent/>}` | legenda do DS |
56
+
57
+ ## Cores
58
+
59
+ Use os tokens `var(--color-chart-1)` … `var(--color-chart-5)` no `config`.
60
+ Paleta verde-marca + harmônicas (teal/azul/âmbar/violeta), light/dark-aware.
61
+
62
+ ## Grid (linhas-guia)
63
+
64
+ `<CartesianGrid vertical={false} strokeDasharray="4 4" />` — **sem** passar
65
+ `stroke`. O container já reescreve o stroke default pro token `--color-chart-grid`
66
+ (visível em light e dark). Mesmo vale pro `PolarGrid` (radar/radial).
67
+
68
+ ## Gotchas
69
+
70
+ - `ChartContainer` já embute o `ResponsiveContainer` — passe só o gráfico (ex:
71
+ `<AreaChart>`) como filho, **sem** envolver em outro ResponsiveContainer.
72
+ - Defina **altura** via `className` (ex: `h-[260px]`) — o aspect default é `video`.
73
+ - Recharts 3: `dataKey` precisa casar com a chave do `config` pra `--color-{key}`
74
+ resolver.
75
+
76
+ ### Recharts 3 — caveats que quebram silenciosamente
77
+
78
+ - **`text-display-sm` / `text-display-xs` NÃO existem** como utility (renderizam
79
+ 14px). Em KPIs use `text-heading-sm` (24–32px) / `text-heading-xs` (24px) ou
80
+ `text-display-md` (28–39px).
81
+ - **Pizza**: não há `activeIndex`/`activeShape` no `Pie` — usar a prop
82
+ `shape={(props, index) => <Sector .../>}` (render por setor).
83
+ - **Radial empilhado**: precisa de `<PolarAngleAxis type="number" domain={[0,total]} />`
84
+ senão só 1 segmento aparece.
85
+ - **Eixo Y omite ticks de borda** (ex: o `0`): `interval={0}` força todos.
86
+ - **Linha-guia duplicada no topo**: o `domain` máximo deve ser igual ao maior
87
+ `tick` (ex: `domain={[0,90]}` com tick 90, não `[0,95]`).
88
+
89
+ ## Composições de dashboard
90
+
91
+ Catálogo + padrões de header/card/categoria/largura na rota `#/chart-showcase` do
92
+ catálogo hospedado. (Fonte no repo do DS: `.ai/context/components/chart-patterns.md`
93
+ — caminho **interno**, não existe em quem consome por npm ou copy-in.)
@@ -0,0 +1,68 @@
1
+ # Chip — USAGE
2
+
3
+ Pílula compacta para status, tags, filtros — dual-mode (span estático ou button interativo).
4
+
5
+ ## Quando usar
6
+ - Tags / categorias (status, prioridade, label)
7
+ - Filtros aplicados (com onClick = remove)
8
+ - Chips de seleção (selected state)
9
+ - Counters inline
10
+
11
+ ## Import
12
+ ```tsx
13
+ import { Chip, ChipGroup, ChipGroupItem } from "@/components/ui/Chip";
14
+ ```
15
+
16
+ ## Variants
17
+ | Variant | Valores | Default | Quando |
18
+ |---|---|---|---|
19
+ | `color` | primary / neutral / danger / warning / success / info | neutral | Cor semântica |
20
+ | `variant` | solid / outline / soft / soft-outline | soft | Intensidade visual |
21
+ | `size` | sm / md / lg / xl | md | sm=24px, md=28px, lg=32px, xl=36px |
22
+ | `shape` | pill / rounded | pill | Border-radius |
23
+
24
+ ## Props essenciais
25
+ | Prop | Tipo | Função |
26
+ |---|---|---|
27
+ | `onClick` | () => void | Vira `<button>` automaticamente |
28
+ | `asButton` | boolean | Força modo button mesmo sem onClick |
29
+ | `selected` | boolean | Visual ativo (force soft) |
30
+
31
+ ## ChipGroup props
32
+ Seleção via `@radix-ui/react-toggle-group`. Visual ativo/inativo configurável independentemente.
33
+
34
+ | Prop | Tipo | Default | Função |
35
+ |---|---|---|---|
36
+ | `type` | `"single"` \| `"multiple"` | — (obrigatória) | single = radio-like, multiple = checkbox-like |
37
+ | `value` | string (single) / string[] (multiple) | — | Seleção controlada |
38
+ | `defaultValue` | string (single) / string[] (multiple) | — | Seleção inicial (uncontrolled) |
39
+ | `onValueChange` | (value: string) => void (single) / (value: string[]) => void (multiple) | — | Callback de mudança |
40
+ | `inactiveColor` | mesmos valores de `color` | `"neutral"` | Cor dos items NÃO selecionados |
41
+ | `inactiveVariant` | mesmos valores de `variant` | `"outline"` | Variant dos items NÃO selecionados |
42
+ | `activeColor` | mesmos valores de `color` | `"primary"` | Cor dos items selecionados |
43
+ | `activeVariant` | mesmos valores de `variant` | `"soft-outline"` | Variant dos items selecionados |
44
+ | `size` | sm / md / lg / xl | `"md"` | Size aplicado em todos os chips do grupo |
45
+ | `shape` | pill / rounded | `"pill"` | Shape aplicado em todos os chips |
46
+ | `orientation` | `"horizontal"` \| `"vertical"` | `"horizontal"` | Direção do grupo |
47
+ | `ariaLabel` | string | — | Aria-label do grupo |
48
+
49
+ ## Exemplo mínimo
50
+ ```tsx
51
+ // Tag estática
52
+ <Chip color="warning" variant="soft">Royal</Chip>
53
+
54
+ // Filtro com remove
55
+ <Chip color="primary" onClick={removeFilter}>Status: Ativo ×</Chip>
56
+
57
+ // Group multi-select
58
+ <ChipGroup type="multiple" value={selected} onValueChange={setSelected}>
59
+ <ChipGroupItem value="all">Todas</ChipGroupItem>
60
+ <ChipGroupItem value="unread">Não lidas</ChipGroupItem>
61
+ </ChipGroup>
62
+ ```
63
+
64
+ ## Cuidados / Gotchas
65
+ - Sem `onClick` e sem `asButton={true}` renderiza como `<span>` (decorativo)
66
+ - O visual interativo (cursor pointer + focus ring) é calculado internamente a partir de `onClick`/`asButton` — não existe prop `interactive` no Chip
67
+ - `selected=true` força visual "soft" da cor mesmo se `variant="outline"`
68
+ - Pra counter inline, usar `chipCount` slot (10px font-semibold)
@@ -0,0 +1,118 @@
1
+ # ChoroplethMap
2
+
3
+ **Categoria:** iGreen (tv() + d3-geo). Primitiva **genérica** de mapa coroplético (regiões SVG coloridas por valor).
4
+
5
+ ## Quando usar
6
+
7
+ - Visualizar uma métrica por região geográfica: municípios (IBGE), estados, países.
8
+ - Ex.: "Mapa de Cidades" do Rankings — clientes por município do Brasil.
9
+
10
+ Não é acoplado a nenhum dataset: você passa a geografia (GeoJSON/TopoJSON) + um mapa `id → número`.
11
+
12
+ ## Props essenciais
13
+
14
+ | Prop | Tipo | Default | Descrição |
15
+ |------|------|---------|-----------|
16
+ | `geography` | `FeatureCollection \| Feature[] \| Topology` | — | Fonte geográfica. TopoJSON com 2+ objetos exige `topologyObject`. |
17
+ | `topologyObject` | `string` | objeto único da `Topology` | Nome do objeto a extrair do `Topology` (ex.: `"municipios"`). Obrigatório só quando há 2+ objetos — com 2+ e sem a prop, o mapa renderiza **vazio** (sem erro). |
18
+ | `values` | `Record<string \| number, number>` | — | Mapa `id → valor`. Ids casam com `getFeatureId`. |
19
+ | `getFeatureId` | `(f) => string \| number` | `f.id` → `f.properties.id` | Como obter o id de uma feature. |
20
+ | `getFeatureName` | `(f) => string` | `properties.name/nome/NOME` → id | Nome exibido no tooltip. |
21
+ | `colorScale` | `(value, {min,max}) => string` | gradiente token | Escala custom (retorna cor CSS). |
22
+ | `scaleToken` | `"brand"\|"success"\|"info"\|"warning"\|"danger"` | `"brand"` | Token DS do extremo "cheio" do gradiente default. |
23
+ | `domain` | `[number, number]` | min/max de `values` | Domínio fixo da escala. |
24
+ | `projection` | `GeoProjection` (d3-geo) | `geoMercator().fitSize(...)` | Projeção custom (senão auto-fit). |
25
+ | `width` / `height` | `number` | `800` / `600` | ViewBox (o SVG é responsivo, `w-full h-auto`). |
26
+ | `strokeWidth` | `number` | `0.5` | Espessura das divisas (unidades do viewBox). |
27
+ | `showLegend` | `boolean` | `true` | Barra de gradiente + min/max. |
28
+ | `legendTitle` | `ReactNode` | — | Título da legenda. |
29
+ | `formatValue` | `(v) => string` | `Intl.NumberFormat("pt-BR")` | Formata legenda + tooltip. |
30
+ | `renderTooltip` | `(info) => ReactNode` | nome + valor | Conteúdo custom do tooltip. |
31
+ | `onFeatureClick` | `(info) => void` | — | Clique numa região. |
32
+ | `selectedId` | `string \| number \| null` | — | Destaque PERSISTENTE de uma região (controlado; par natural de `onFeatureClick` pra seleção por clique). |
33
+ | `ariaLabel` | `string` | `"Mapa"` | Rótulo acessível (`role="img"`). |
34
+
35
+ ## Exemplo mínimo (municípios do Brasil, IBGE TopoJSON)
36
+
37
+ ```tsx
38
+ import { ChoroplethMap } from "@snksergio/design-system";
39
+ import brasilMunicipios from "./geo/br-municipios.topo.json"; // TopoJSON IBGE
40
+
41
+ <ChoroplethMap
42
+ geography={brasilMunicipios}
43
+ topologyObject="municipios"
44
+ values={clientesPorMunicipio} // { "3550308": 120, "3304557": 88, ... }
45
+ getFeatureId={(f) => f.id ?? f.properties?.codarea}
46
+ getFeatureName={(f) => f.properties?.name}
47
+ scaleToken="brand"
48
+ legendTitle="Clientes por cidade"
49
+ ariaLabel="Clientes por município"
50
+ />
51
+ ```
52
+
53
+ ## Interação (comportamento embutido — não reimplemente)
54
+
55
+ - **Hover**: a região sob o cursor é REDESENHADA por cima de todas (contorno
56
+ `fg-{scaleToken}` com 3× a espessura das divisas + tinta de 18% do token).
57
+ O contorno acompanha a família de cor do mapa — amarelo forte num mapa
58
+ `warning`, roxo forte num `info` — nunca verde fixo.
59
+ - **Tooltip**: próprio (NÃO Radix), vive numa camada `pointer-events-none` e
60
+ SEGUE o cursor, trocando só o conteúdo. Nunca captura o mouse (era a causa
61
+ de flicker direcional com o Tooltip portalado). Flip automático perto das
62
+ bordas. Região sem valor mostra "Sem dados".
63
+ - **Seleção** (`selectedId`, controlado): destaque persistente com a mesma
64
+ técnica do hover, tinta mais forte (32% vs 18%). Toggle é responsabilidade
65
+ do consumer: `onFeatureClick={(i) => setSel(s => s?.id === i.id ? null : i)}`.
66
+
67
+ ## Receita: UFs do Brasil (malha IBGE em runtime)
68
+
69
+ ```tsx
70
+ const IBGE_UF = "https://servicodados.ibge.gov.br/api/v3/malhas/paises/BR?formato=application/json&qualidade=minima&intrarregiao=UF";
71
+ // devolve TopoJSON { objects: { BRUF } } — objeto único, extraído automaticamente
72
+ // qualidade=minima NÃO traz nome — só properties.codarea. Mapeie código → nome:
73
+ <ChoroplethMap
74
+ geography={geo} // fetch da URL acima
75
+ topologyObject="BRUF" // explícito por clareza (opcional: é único)
76
+ values={clientesPorUf} // { "35": 612, "31": 388, ... } (código IBGE)
77
+ getFeatureId={(f) => String((f.properties as { codarea?: string })?.codarea ?? f.id ?? "")}
78
+ getFeatureName={(f) => UF_NOMES[id] ?? id} // tabela código → "São Paulo"...
79
+ />
80
+ ```
81
+
82
+ ## Receita: master-detail (mapa + painel de detalhe)
83
+
84
+ Espelhe a seção "Seleção por clique" da doc page (`ChoroplethMapDoc.tsx`):
85
+
86
+ - Wrapper `relative w-full` com o mapa em largura total; painel FLUTUANDO no
87
+ canto superior direito (área de oceano): `md:absolute md:right-0 md:top-0
88
+ md:w-[220px] md:shadow-sh-md` + card `rounded-radius-lg border
89
+ border-border-default bg-bg-surface p-pad-xl`. Mobile: empilha (`mt-gp-2xl`).
90
+ - ⚠️ **Largura do painel SEMPRE fixa** — painel que cresce com o conteúdo
91
+ redimensiona o svg (que é `w-full` do espaço restante) a cada seleção.
92
+ - Dentro: título + subtítulo num card interno `rounded-radius-md bg-bg-muted
93
+ px-pad-xl py-pad-lg` (`subtle` sobre `surface` mal aparece; `muted` dá o
94
+ contraste do header); métricas abaixo, UMA por linha (`flex justify-between`,
95
+ label `text-caption-md text-fg-muted`, valor `text-body-sm font-semibold
96
+ tabular-nums`).
97
+ - Cobertura parcial: UF fora de `values` fica neutra (`bg-bg-muted`) — estado
98
+ "vazio" de graça, sem prop.
99
+
100
+ ## Gotchas
101
+
102
+ - **Dependências:** usa `d3-geo` (projeção + path) e `topojson-client` (TopoJSON → features) — as MESMAS primitivas que a `react-simple-maps` embrulha. Não usamos `react-simple-maps` porque ela trava peer em React ≤18 (o DS é React 19) — usar `--legacy-peer-deps` seria hack e contaminaria a árvore de todos os consumidores.
103
+ - **Cor data-driven é inline:** o `fill` de cada região é `color-mix(... var(--color-bg-{scaleToken}) ...)` e o contorno de hover/seleção é `var(--color-fg-{scaleToken})` (derivados de tokens, valor vem do dado) — não dá pra virar classe utilitária (valor contínuo/infinito). Mesma exceção justificada do `Avatar.colorHex` (L-027). Todo o resto (shell/legenda/tooltip) é classe token.
104
+ - **Não envolva o tooltip em Radix/portal:** qualquer wrapper portalado captura o mouse e reintroduz o flicker direcional (cursor persegue o tooltip → mouseleave do svg → fecha/reabre em loop).
105
+ - **Ids precisam casar:** as chaves de `values` têm que bater com o retorno de `getFeatureId`. TopoJSON do IBGE costuma trazer o código em `properties.codarea` (malhas v3) ou `feature.id` — confira a fonte e ajuste `getFeatureId`.
106
+ - **Dentro de container flex centrado** (ex.: preview de ExampleSection): dê `w-full` ao wrapper do mapa — sem ele o svg cai na largura intrínseca default (300px).
107
+ - **Performance:** milhares de `<path>` renderizam bem em SVG, mas re-render pesado; passe `values`/`geography` estáveis (memoize no consumer). A geometria (paths) é memoizada por `geography/projection/width/height`.
108
+
109
+ ## Quando NÃO usar
110
+
111
+ Mapa **fixo** do Brasil por UF (KPI de dashboard) → use a **receita de paths inline**
112
+ (`#/chart-map`, `_dashboard-brazil-map.ts`): é SVG puro, zero dependência e não busca
113
+ malha em runtime.
114
+
115
+ Este componente existe para o caso **data-driven**: topologia arbitrária (municípios de
116
+ um estado, recortes que mudam), projeção real via `d3-geo` e drill-down por
117
+ `onFeatureClick`. Ele traz `d3-geo` + `topojson-client` junto — não pague esse custo
118
+ para desenhar o Brasil parado.
@@ -0,0 +1,70 @@
1
+ # ColorPicker
2
+
3
+ **Categoria:** composto (Popover + Input + FormField + Button + Separator). Seletor de cor **hex `#RRGGBB`** controlado, pensado para **Tags** e **Filas**.
4
+
5
+ ## Quando usar
6
+
7
+ - Escolher uma cor de marca/identidade para uma entidade (tag, fila, etiqueta, status custom).
8
+ - Quando o usuário precisa **ver e digitar** o hex E ter atalho para uma **paleta curada**.
9
+
10
+ Não use para escolha semântica (sucesso/erro/aviso) — isso são tokens DS, não cor livre.
11
+
12
+ ## Anatomia
13
+
14
+ ```
15
+ [ swatch ] [ Input hex (#RRGGBB) ] ← trigger inline (swatch abre o popover)
16
+ ▼ (clique no swatch)
17
+ ┌─────────────────────────────┐
18
+ │ grid 10-col de presets │ ← swatch selecionado = checkmark (contraste auto)
19
+ │ ───────── Separator ─────── │
20
+ │ Cor personalizada [input] │ ← allowCustomHex (default true)
21
+ │ [ Aplicar ] │
22
+ └─────────────────────────────┘
23
+ ```
24
+
25
+ ## Props essenciais
26
+
27
+ | Prop | Tipo | Default | Descrição |
28
+ |------|------|---------|-----------|
29
+ | `value` | `string` | — | **Obrigatório.** Hex controlado `#RRGGBB`. |
30
+ | `onValueChange` | `(hex: string) => void` | — | **Obrigatório.** Recebe sempre `#RRGGBB` maiúsculo normalizado. |
31
+ | `presets` | `string[]` | `DEFAULT_COLOR_PRESETS` | Cores do grid (paleta iGreen + neutras, ~26 hex). |
32
+ | `id` | `string` | auto | id do input hex (linka label externo via `htmlFor`). |
33
+ | `state` | `"default" \| "error" \| "warning" \| "success"` | `"default"` | Colore **só a borda** do trigger. **Não** é color variant. |
34
+ | `size` | `"xxs" \| "xs" \| "sm" \| "md"` | `"md"` | Mesma escala do `Input`, e repassado a ele: `xxs` 28px · `xs` 32px · `sm` 36px · `md` 40px. |
35
+ | `disabled` | `boolean` | `false` | Desabilita o seletor inteiro. |
36
+ | `allowCustomHex` | `boolean` | `true` | Mostra o input hex livre + botão Aplicar no popover. |
37
+ | `placeholder` | `string` | `"#RRGGBB"` | Placeholder dos inputs hex. |
38
+ | `open` / `onOpenChange` | — | — | Controle externo de abertura do popover. |
39
+ | `className` | `string` | — | className do container (root). |
40
+
41
+ ## Exemplo mínimo
42
+
43
+ ```tsx
44
+ import { ColorPicker } from "@snksergio/design-system";
45
+
46
+ const [color, setColor] = useState("#16A34A");
47
+
48
+ <FormField label="Cor da tag" id="tag-color">
49
+ {({ id }) => (
50
+ <ColorPicker id={id} value={color} onValueChange={setColor} />
51
+ )}
52
+ </FormField>
53
+ ```
54
+
55
+ ## Variants
56
+
57
+ | Variant | Valores | Efeito |
58
+ |---------|---------|--------|
59
+ | `size` | `xxs` · `xs` · `sm` · `md` | altura do trigger (swatch + input) — escala do `Input` |
60
+ | `state` | `default` · `error` · `warning` · `success` | cor da borda do swatch |
61
+ | `disabled` | — | último compoundVariant (L-006); desabilita tudo |
62
+
63
+ ## Gotchas
64
+
65
+ - **Normalização:** aceita `3` ou `6` dígitos com/sem `#` (`fff`, `#FFF`, `00ff00`) → sempre emite `#RRGGBB` **maiúsculo**. Hex inválido no input inline restaura o `value` atual no blur.
66
+ - **bg dinâmico = exceção L-027:** o fundo do swatch e dos presets vem por `style={{ backgroundColor }}` (cor externa). É a única exceção de hardcode permitida; todo o resto é token DS.
67
+ - **Checkmark com contraste auto:** o preset selecionado usa `getContrastTextColor(hex)` para escolher branco/preto — não cor cega.
68
+ - **Anchor do Popover:** o swatch é `forwardRef` via `PopoverTrigger asChild` (L-021). O `ref` encaminhado vai para o botão do swatch.
69
+ - **Foco:** swatch + presets seguem Padrão 1 (botão, `ring-4 ring-ring-brand`); o input hex herda o foco animado do `Input` do DS (Padrão 2).
70
+ - Para parear visualmente com outros campos de um form, envolva em `<FormField>` (render-prop) — o `state` do ColorPicker espelha o do Input para casar a borda.
@@ -0,0 +1,51 @@
1
+ # Combobox
2
+
3
+ **Categoria:** composto (Popover + Command/cmdk). Select de escolha única com **busca (autocomplete)** e **lista rolável**.
4
+
5
+ ## Quando usar
6
+
7
+ - Lista de opções **longa** onde o usuário precisa **digitar pra achar** (ex.: escolher 1 coluna entre 30, 1 país, 1 cliente).
8
+ - Quando um `Select` simples ficaria alto demais / sem scroll confortável e sem busca.
9
+
10
+ Para listas curtas (≤ ~8 itens) e sem necessidade de busca, prefira `FormFieldSelect` / `Select`.
11
+
12
+ ## Props essenciais
13
+
14
+ | Prop | Tipo | Default | Descrição |
15
+ |------|------|---------|-----------|
16
+ | `options` | `ComboboxOption[]` | — | `{ value, label, keywords? }`. Busca casa por `label` + `keywords` (que já inclui `value`). |
17
+ | `value` | `string` | — | Valor selecionado (controlado). |
18
+ | `onValueChange` | `(value: string) => void` | — | Recebe o `value` da opção escolhida. |
19
+ | `placeholder` | `ReactNode` | `"Selecione…"` | Texto do trigger vazio. |
20
+ | `searchPlaceholder` | `string` | `"Buscar…"` | Placeholder do input de busca. |
21
+ | `emptyMessage` | `ReactNode` | `"Nenhum resultado."` | Quando a busca não casa nada. |
22
+ | `open` / `defaultOpen` / `onOpenChange` | — | — | Controle de abertura (igual a um Select). |
23
+ | `align` | `"start" \| "center" \| "end"` | `"start"` | Alinhamento do dropdown. |
24
+ | `className` | `string` | — | Estiliza o **trigger** (aceita os mesmos overrides de um `SelectTrigger`). |
25
+ | `contentClassName` | `string` | — | Estiliza o **dropdown** (PopoverContent). |
26
+ | `disabled` | `boolean` | — | Desabilita o trigger. |
27
+
28
+ `ComboboxOption = { value: string; label: string; keywords?: string[] }`
29
+
30
+ ## Exemplo mínimo
31
+
32
+ ```tsx
33
+ import { Combobox } from "@snksergio/design-system";
34
+
35
+ <Combobox
36
+ options={columns.map((c) => ({ value: c.key, label: c.label }))}
37
+ value={field}
38
+ onValueChange={setField}
39
+ placeholder="Campo"
40
+ searchPlaceholder="Buscar campo…"
41
+ emptyMessage="Nenhum campo"
42
+ aria-label="Campo"
43
+ />
44
+ ```
45
+
46
+ ## Gotchas
47
+
48
+ - O trigger é um `<button role="combobox">` e espelha o `SelectTrigger` (mesma altura/borda/foco). Para parear com Selects irmãos num form, passe o mesmo `className` que você passaria ao `SelectTrigger`.
49
+ - A seleção **não** depende do argumento do `onSelect` do cmdk (que vem normalizado/lowercased) — o componente fecha via closure sobre `option.value`. Por isso `value`s com maiúsculas/acentos/espaços funcionam.
50
+ - O dropdown nasce com a **largura do trigger** (`--radix-popover-trigger-width`). Para largura própria, use `contentClassName="w-[...]"`.
51
+ - `label`s devem ser únicos (o cmdk indexa por `value`/label do item). Se houver rótulos repetidos, diferencie via `keywords`.