@iclips/ui 2.0.2 → 3.0.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/MIGRATION-v3.md +244 -0
- package/README.md +1 -1
- package/dist/alert-theme.css +20 -79
- package/dist/components/ui/charts/donut-chart.cjs +1 -1
- package/dist/components/ui/charts/donut-chart.cjs.map +1 -1
- package/dist/components/ui/charts/donut-chart.js +39 -60
- package/dist/components/ui/charts/donut-chart.js.map +1 -1
- package/dist/components/ui/charts/kpi-card.cjs +1 -1
- package/dist/components/ui/charts/kpi-card.cjs.map +1 -1
- package/dist/components/ui/charts/kpi-card.js +19 -19
- package/dist/components/ui/charts/kpi-card.js.map +1 -1
- package/dist/components/ui/data/comment-system.cjs +1 -1
- package/dist/components/ui/data/comment-system.cjs.map +1 -1
- package/dist/components/ui/data/comment-system.js +67 -67
- package/dist/components/ui/data/comment-system.js.map +1 -1
- package/dist/components/ui/data/data-table.cjs +1 -1
- package/dist/components/ui/data/data-table.cjs.map +1 -1
- package/dist/components/ui/data/data-table.js +112 -101
- package/dist/components/ui/data/data-table.js.map +1 -1
- package/dist/components/ui/data/editorial-calendar.cjs +1 -1
- package/dist/components/ui/data/editorial-calendar.cjs.map +1 -1
- package/dist/components/ui/data/editorial-calendar.js +291 -291
- package/dist/components/ui/data/editorial-calendar.js.map +1 -1
- package/dist/components/ui/data/filter-builder.cjs +1 -1
- package/dist/components/ui/data/filter-builder.cjs.map +1 -1
- package/dist/components/ui/data/filter-builder.js +1 -1
- package/dist/components/ui/data/filter-builder.js.map +1 -1
- package/dist/components/ui/feedback/empty-state.cjs +1 -1
- package/dist/components/ui/feedback/empty-state.cjs.map +1 -1
- package/dist/components/ui/feedback/empty-state.js +14 -14
- package/dist/components/ui/feedback/empty-state.js.map +1 -1
- package/dist/components/ui/feedback/toast.cjs +1 -1
- package/dist/components/ui/feedback/toast.cjs.map +1 -1
- package/dist/components/ui/feedback/toast.js +53 -22
- package/dist/components/ui/feedback/toast.js.map +1 -1
- package/dist/components/ui/forms/calendar.cjs +1 -1
- package/dist/components/ui/forms/calendar.cjs.map +1 -1
- package/dist/components/ui/forms/calendar.js +16 -16
- package/dist/components/ui/forms/calendar.js.map +1 -1
- package/dist/components/ui/forms/combobox.cjs +1 -1
- package/dist/components/ui/forms/combobox.cjs.map +1 -1
- package/dist/components/ui/forms/combobox.js +78 -76
- package/dist/components/ui/forms/combobox.js.map +1 -1
- package/dist/components/ui/forms/file-attachment-list.cjs +1 -1
- package/dist/components/ui/forms/file-attachment-list.cjs.map +1 -1
- package/dist/components/ui/forms/file-attachment-list.js +4 -4
- package/dist/components/ui/forms/file-attachment-list.js.map +1 -1
- package/dist/components/ui/forms/file-upload-zone.cjs +2 -2
- package/dist/components/ui/forms/file-upload-zone.cjs.map +1 -1
- package/dist/components/ui/forms/file-upload-zone.js +55 -49
- package/dist/components/ui/forms/file-upload-zone.js.map +1 -1
- package/dist/components/ui/forms/form.cjs +1 -1
- package/dist/components/ui/forms/form.cjs.map +1 -1
- package/dist/components/ui/forms/form.js +11 -11
- package/dist/components/ui/forms/form.js.map +1 -1
- package/dist/components/ui/forms/input-validation.cjs +1 -1
- package/dist/components/ui/forms/input-validation.cjs.map +1 -1
- package/dist/components/ui/forms/input-validation.js +20 -20
- package/dist/components/ui/forms/input-validation.js.map +1 -1
- package/dist/components/ui/forms/multi-select.cjs +1 -1
- package/dist/components/ui/forms/multi-select.cjs.map +1 -1
- package/dist/components/ui/forms/multi-select.js +69 -67
- package/dist/components/ui/forms/multi-select.js.map +1 -1
- package/dist/components/ui/forms/radio-group.cjs +1 -1
- package/dist/components/ui/forms/radio-group.cjs.map +1 -1
- package/dist/components/ui/forms/radio-group.js +19 -19
- package/dist/components/ui/forms/radio-group.js.map +1 -1
- package/dist/components/ui/forms/social-button.cjs +1 -1
- package/dist/components/ui/forms/social-button.cjs.map +1 -1
- package/dist/components/ui/forms/social-button.js +6 -6
- package/dist/components/ui/forms/social-button.js.map +1 -1
- package/dist/components/ui/forms/toggle.cjs +1 -1
- package/dist/components/ui/forms/toggle.cjs.map +1 -1
- package/dist/components/ui/forms/toggle.js +7 -7
- package/dist/components/ui/forms/toggle.js.map +1 -1
- package/dist/components/ui/navigation/navigation-menu.cjs +1 -1
- package/dist/components/ui/navigation/navigation-menu.cjs.map +1 -1
- package/dist/components/ui/navigation/navigation-menu.js +14 -14
- package/dist/components/ui/navigation/navigation-menu.js.map +1 -1
- package/dist/components/ui/navigation/sidebar.cjs +1 -1
- package/dist/components/ui/navigation/sidebar.cjs.map +1 -1
- package/dist/components/ui/navigation/sidebar.js +44 -44
- package/dist/components/ui/navigation/sidebar.js.map +1 -1
- package/dist/components/ui/overlays/context-menu.cjs +1 -1
- package/dist/components/ui/overlays/context-menu.cjs.map +1 -1
- package/dist/components/ui/overlays/context-menu.js +1 -1
- package/dist/components/ui/overlays/context-menu.js.map +1 -1
- package/dist/components/ui/overlays/dropdown-menu.cjs +1 -1
- package/dist/components/ui/overlays/dropdown-menu.cjs.map +1 -1
- package/dist/components/ui/overlays/dropdown-menu.js +5 -5
- package/dist/components/ui/overlays/dropdown-menu.js.map +1 -1
- package/dist/components/ui/overlays/menubar.cjs +1 -1
- package/dist/components/ui/overlays/menubar.cjs.map +1 -1
- package/dist/components/ui/overlays/menubar.js +29 -29
- package/dist/components/ui/overlays/menubar.js.map +1 -1
- package/dist/components/ui/overlays/tooltip.cjs +1 -1
- package/dist/components/ui/overlays/tooltip.cjs.map +1 -1
- package/dist/components/ui/overlays/tooltip.js +12 -12
- package/dist/components/ui/overlays/tooltip.js.map +1 -1
- package/dist/components/ui/primitives/avatar.cjs +1 -1
- package/dist/components/ui/primitives/avatar.cjs.map +1 -1
- package/dist/components/ui/primitives/avatar.js +20 -27
- package/dist/components/ui/primitives/avatar.js.map +1 -1
- package/dist/components/ui/primitives/badge.cjs +1 -1
- package/dist/components/ui/primitives/badge.cjs.map +1 -1
- package/dist/components/ui/primitives/badge.js +13 -13
- package/dist/components/ui/primitives/badge.js.map +1 -1
- package/dist/components/ui/primitives/button.cjs +1 -1
- package/dist/components/ui/primitives/button.cjs.map +1 -1
- package/dist/components/ui/primitives/button.js +11 -8
- package/dist/components/ui/primitives/button.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +34 -35
- package/dist/index.js.map +1 -1
- package/dist/preset.css +1 -2
- package/dist/toast-theme.css +100 -64
- package/dist/tokens.css +59 -159
- package/package.json +5 -3
- package/scripts/check-colors.mjs +208 -0
package/MIGRATION-v3.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Migração para @iclips/ui v3
|
|
2
|
+
|
|
3
|
+
`3.0.0` reduz a paleta à **paleta iClips** — "um roxo, muito cinza, e cor só onde
|
|
4
|
+
ela significa algo" — e junta as mudanças visuais que iam sair na 2.1. Nenhum
|
|
5
|
+
import nem componente foi renomeado; o que quebra é **cor crua** no seu código e
|
|
6
|
+
dois valores de prop (seção 1).
|
|
7
|
+
|
|
8
|
+
**Comece por aqui**, na raiz do app, depois de instalar a 3.0.0:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
node node_modules/@iclips/ui/scripts/check-colors.mjs src
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Ele lista cada cor crua (`arquivo:linha classe → sugestão`) e não altera nada.
|
|
15
|
+
Exit 0 = nada a migrar.
|
|
16
|
+
|
|
17
|
+
## 1. Paleta iClips enxuta (BREAKING)
|
|
18
|
+
|
|
19
|
+
### O que existe agora
|
|
20
|
+
|
|
21
|
+
Só tokens semânticos viram classe: `bg-background`, `bg-card`, `bg-muted`,
|
|
22
|
+
`text-foreground`, `text-muted-foreground`, `border-border`, `border-input`,
|
|
23
|
+
`bg-primary`, `bg-secondary`, `bg-accent`, `bg-destructive`/`success`/`warning`/`info`,
|
|
24
|
+
`text-<status>-emphasis`, `bg-chart-1..8`, `bg-sidebar-*` e afins — mais
|
|
25
|
+
`white`, `black`, `transparent` e `current`.
|
|
26
|
+
|
|
27
|
+
Os primitivos que sobraram (gray 50–900; purple 50–500 e 700, com 300 e 400
|
|
28
|
+
novos; red/green/cyan 600; yellow 500/600) existem só como custom property
|
|
29
|
+
(`var(--gray-100)`), para alimentar os semânticos. **Não geram classe.**
|
|
30
|
+
|
|
31
|
+
### O que saiu
|
|
32
|
+
|
|
33
|
+
- As famílias `pink`, `blue`, `teal` e `orange` inteiras, e os degraus sem
|
|
34
|
+
consumidor das demais (`purple-600`, `red-50`, `green-200`, `cyan-500`…).
|
|
35
|
+
- Toda classe de cor crua: `bg-gray-100`, `text-purple-600`, `border-red-200`…
|
|
36
|
+
- A **paleta padrão do Tailwind** para quem usa o preset (modo B): o `@theme`
|
|
37
|
+
da lib começa com `--color-*: initial`, então `bg-zinc-900`, `text-sky-500`
|
|
38
|
+
etc. deixam de gerar CSS. Classe que não gera CSS não dá erro — ela só não
|
|
39
|
+
pinta. Por isso rode o `check-colors.mjs`.
|
|
40
|
+
- `var(--pink-*)`, `var(--blue-*)`, `var(--teal-*)`, `var(--orange-*)` e os
|
|
41
|
+
degraus removidos.
|
|
42
|
+
|
|
43
|
+
> **Tem cores próprias no `@theme` do app?** Declare-as **depois** do
|
|
44
|
+
> `@import "@iclips/ui/preset.css"` — o `--color-*: initial` da lib zera o que
|
|
45
|
+
> vier antes.
|
|
46
|
+
|
|
47
|
+
### Tabela de equivalência
|
|
48
|
+
|
|
49
|
+
| Antes | Depois |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `bg-gray-50` | `bg-background` ou `bg-muted/40` |
|
|
52
|
+
| `bg-gray-100`, `bg-gray-200` | `bg-muted` |
|
|
53
|
+
| `bg-gray-700`…`bg-zinc-900` (superfície escura) | `bg-card` |
|
|
54
|
+
| `border-gray-100`, `border-gray-200` | `border-border` |
|
|
55
|
+
| `border-gray-300` | `border-input` |
|
|
56
|
+
| `text-gray-400`…`text-gray-600` | `text-muted-foreground` |
|
|
57
|
+
| `text-gray-700`…`text-gray-900` | `text-foreground` |
|
|
58
|
+
| `bg-purple-500`, `bg-purple-600` | `bg-primary` |
|
|
59
|
+
| `bg-purple-50`, `bg-purple-100` | `bg-secondary` (seleção) |
|
|
60
|
+
| `text-purple-500`, `text-purple-600` | `text-primary` |
|
|
61
|
+
| `text-purple-700` | `text-secondary-foreground` |
|
|
62
|
+
| `border-purple-*`, `ring-purple-*` | `border-primary`, `ring-ring` |
|
|
63
|
+
| `text-red-600`, `text-green-600`, `text-yellow-700`, `text-blue-600` | `text-destructive-emphasis`, `text-success-emphasis`, `text-warning-emphasis`, `text-info-emphasis` |
|
|
64
|
+
| `bg-red-600` / `bg-red-50` (idem green, yellow, blue) | `bg-destructive` / `bg-destructive/10` |
|
|
65
|
+
| `border-red-200` (idem) | `border-destructive/20` |
|
|
66
|
+
| `teal`, `orange`, `pink`, `indigo`, `violet`… | sem equivalente: a paleta não tem essas cores. Em dados, `var(--chart-N)`; fora disso, um token semântico |
|
|
67
|
+
|
|
68
|
+
### Componentes
|
|
69
|
+
|
|
70
|
+
- **`Avatar`** — `colorVariant` aceita só `"gray"` e `"purple"` (roxo =
|
|
71
|
+
`bg-secondary text-secondary-foreground`). `pink`, `blue`, `cyan`, `teal`,
|
|
72
|
+
`green`, `yellow`, `orange` e `red` não compilam mais no TypeScript: troque por
|
|
73
|
+
`purple` ou `gray`. `getColorFromString` passa a devolver um dos dois.
|
|
74
|
+
- **`DonutChart`** — fatia sem `color` usa `var(--chart-N)`, não mais 10 hex
|
|
75
|
+
da paleta padrão do Tailwind.
|
|
76
|
+
- **`<Button variant="cta">`** e `--cta` — não existem (anunciados para a 2.1,
|
|
77
|
+
que não chegou a sair). Use `variant="default"` para a ação principal.
|
|
78
|
+
|
|
79
|
+
### Gráficos
|
|
80
|
+
|
|
81
|
+
`--chart-1..8` passam a roxo e cinza alternando claro e escuro, começando pelo
|
|
82
|
+
roxo. Quem usa `var(--chart-N)` vê o gráfico mudar de cor. Categoria é só para
|
|
83
|
+
dados — nunca botão, link ou fundo de seção.
|
|
84
|
+
|
|
85
|
+
## 2. Hover deixa de ser lavanda; lavanda passa a significar "selecionado"
|
|
86
|
+
|
|
87
|
+
`--accent` e `--secondary` tinham o mesmo valor. Como `--accent` é a cor de
|
|
88
|
+
hover e de item realçado em todo o sistema, e `--secondary` é a de item
|
|
89
|
+
selecionado, **selecionado e sob o cursor pintavam o mesmo pixel**: numa lista
|
|
90
|
+
não havia como distinguir a linha escolhida da linha sob o mouse.
|
|
91
|
+
|
|
92
|
+
Hover foi quem cedeu — dura 200ms, não existe em tela de toque e não diz nada
|
|
93
|
+
sobre o dado.
|
|
94
|
+
|
|
95
|
+
| Token | Claro, antes → depois | Escuro, antes → depois |
|
|
96
|
+
| --------------------------- | ----------------------------------- | --------------------------------- |
|
|
97
|
+
| `--accent` | `purple.50` → `gray.100` | `purple.700` → `gray.600` |
|
|
98
|
+
| `--accent-foreground` | `purple.600` → `--foreground` | `purple.100` → `gray.50` |
|
|
99
|
+
| `--secondary` | sem mudança (`purple.50`) | `gray.700` → `purple.700` |
|
|
100
|
+
| `--secondary-foreground` | sem mudança (`purple.700`) | `gray.50` → `purple.100` |
|
|
101
|
+
| `--sidebar-accent` | `purple.50` → `gray.100` | sem mudança (`gray.700`) |
|
|
102
|
+
| `--sidebar-accent-foreground` | `purple.700` → `--sidebar-foreground` | `purple.100` → `gray.50` |
|
|
103
|
+
|
|
104
|
+
No escuro o defeito era o inverso: `--secondary` tinha a mesma cor de `--card`
|
|
105
|
+
e `--popover`, então item selecionado dentro de popover era invisível.
|
|
106
|
+
|
|
107
|
+
### O que você vai ver mudar
|
|
108
|
+
|
|
109
|
+
| Onde | Antes | Depois |
|
|
110
|
+
| ------------------------------------------------------- | ------- | --------------------------- |
|
|
111
|
+
| `Button` `outline` e `ghost`, sob o cursor | lavanda | cinza |
|
|
112
|
+
| Item realçado em menus, `Select` e `Command` | lavanda | cinza |
|
|
113
|
+
| Dia de hoje e dia sob o cursor no `Calendar` | lavanda | cinza |
|
|
114
|
+
| Item de sidebar sob o cursor | lavanda | cinza |
|
|
115
|
+
| Item **selecionado** em `Combobox`, `MultiSelect`, faixa do `Calendar` | lavanda (ou só ✓) | lavanda + texto roxo + peso 500 |
|
|
116
|
+
| Item **ativo** na sidebar e no `NavigationMenu` | lavanda | lavanda, sem mudança |
|
|
117
|
+
| `Toggle` ligado, status "aprovação", reação ativa, menção | lavanda | lavanda, sem mudança |
|
|
118
|
+
| `Button`/`Badge` `secondary` **no tema escuro** | cinza | roxo escuro |
|
|
119
|
+
|
|
120
|
+
Foco por teclado nas listas passou a ser um anel, não um fundo, para
|
|
121
|
+
sobreviver sobre qualquer um dos dois.
|
|
122
|
+
|
|
123
|
+
Efeito colateral bem-vindo: passar o mouse por uma barra de ferramentas deixa
|
|
124
|
+
de pintar a tela de roxo da marca.
|
|
125
|
+
|
|
126
|
+
### Se você usou `bg-accent` para decoração
|
|
127
|
+
|
|
128
|
+
`bg-accent` agora renderiza cinza. Se a intenção era o tom da marca, troque por
|
|
129
|
+
`bg-secondary` — **só em tela sem lista, tabela, menu ou calendário**, onde
|
|
130
|
+
lavanda passou a significar "isto está selecionado".
|
|
131
|
+
|
|
132
|
+
### Se você sobrescreveu `--accent`
|
|
133
|
+
|
|
134
|
+
Nada muda para você. Vale reavaliar: o papel do token mudou de "realce da
|
|
135
|
+
marca" para "hover neutro".
|
|
136
|
+
|
|
137
|
+
## 3. Novo token: `--sidebar-selected`
|
|
138
|
+
|
|
139
|
+
- `--sidebar-selected` / `--sidebar-selected-foreground` — item ativo da
|
|
140
|
+
sidebar (os valores que `--sidebar-accent` tinha).
|
|
141
|
+
|
|
142
|
+
Adição; nada existente mudou por causa dela.
|
|
143
|
+
|
|
144
|
+
## 4. Botões um degrau mais pesados
|
|
145
|
+
|
|
146
|
+
`Button` e `SocialButton` passaram de `font-medium` (500) para `font-semibold`
|
|
147
|
+
(600). Peso 600 agora significa uma coisa só no sistema: ação.
|
|
148
|
+
|
|
149
|
+
Botão com rótulo longo em espaço apertado: confira — 600 ocupa um pouco mais de
|
|
150
|
+
largura.
|
|
151
|
+
|
|
152
|
+
## 5. Tooltip neutro
|
|
153
|
+
|
|
154
|
+
`Tooltip` era roxo da marca; agora é cinza-escuro no claro e claro no escuro.
|
|
155
|
+
Sobreposição informativa não compete mais com os botões primários.
|
|
156
|
+
|
|
157
|
+
## 6. Contraste de status (sem ação necessária)
|
|
158
|
+
|
|
159
|
+
- Texto e ícone de status passaram de `text-<status>` para
|
|
160
|
+
`text-<status>-emphasis` (`Form`, `EmptyState`, menus, anexos, validação).
|
|
161
|
+
- No escuro, `--*-emphasis` passou a misturar 45% do status (era 80%, que
|
|
162
|
+
ficava entre 2,4 e 4,2:1 sobre `card`/`popover`) e é redeclarado em `.dark`.
|
|
163
|
+
Se o seu app aplica `.dark` numa seção em vez do `<html>`, o `-emphasis`
|
|
164
|
+
agora acompanha o tema — antes herdava a mistura do claro.
|
|
165
|
+
- As iniciais do `Avatar` e os chips do `EditorialCalendar` misturam o token
|
|
166
|
+
com `--foreground` para passar AA. Se você usa `text-chart-N` em texto,
|
|
167
|
+
faça o mesmo: os tokens de gráfico são cor de preenchimento.
|
|
168
|
+
|
|
169
|
+
## 6b. Roxo de ação mais claro no escuro
|
|
170
|
+
|
|
171
|
+
No tema escuro, `--primary` passou de purple-200 (`#BB6DF2`) para purple-100
|
|
172
|
+
(`#DAA6FF`). O antigo dava 3,8:1 como texto sobre `card`/`popover` (gray-700),
|
|
173
|
+
abaixo de AA; o novo dá ~6,4:1. Botões `default`, checkbox marcado e slider
|
|
174
|
+
ficam lavanda-claro no escuro, com o mesmo texto quase preto. `ring` e
|
|
175
|
+
`chart-1` continuam em purple-200. Se você fixou `#BB6DF2` como "roxo da marca
|
|
176
|
+
no escuro" no seu app, troque por `var(--primary)`.
|
|
177
|
+
|
|
178
|
+
## 6c. Estado suave: nada de vermelho, verde, amarelo ou azul cheio
|
|
179
|
+
|
|
180
|
+
Cor de estado não é mais fundo sólido em lugar nenhum. `Button`
|
|
181
|
+
`variant="destructive"` e `"success"`, `Badge variant="destructive"` e os
|
|
182
|
+
`Alert` de estado usam a receita suave: fundo `bg-<status>/10`, texto e ícone
|
|
183
|
+
`text-<status>-emphasis`, borda `border-<status>/20` (Alert: tinta de 10%, era
|
|
184
|
+
22%). Os nomes de variante não mudam.
|
|
185
|
+
|
|
186
|
+
- Se você passou `className="text-white"` (ou similar) para um Button/Badge de
|
|
187
|
+
estado presumindo fundo cheio, remova: o texto agora é escuro no claro e
|
|
188
|
+
claro no escuro.
|
|
189
|
+
- Não escreva `bg-destructive`/`bg-success`/`bg-warning`/`bg-info` sem
|
|
190
|
+
opacidade no seu app; use a receita acima.
|
|
191
|
+
|
|
192
|
+
## 6d. Tons de estado mais calmos (sem ação necessária)
|
|
193
|
+
|
|
194
|
+
Os primitivos de estado perderam cerca de 40% da saturação, com a mesma
|
|
195
|
+
luminosidade (o contraste não muda):
|
|
196
|
+
|
|
197
|
+
| Primitivo | Antes | Agora |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `red-600` (`destructive`) | `#CC0A1A` | `#AB4A43` |
|
|
200
|
+
| `green-600` (`success`) | `#007F2A` | `#427748` |
|
|
201
|
+
| `cyan-600` (`info`) | `#0053A6` | `#315687` |
|
|
202
|
+
| `yellow-500` (`warning` claro) | `#FAB325` | `#E4BB78` |
|
|
203
|
+
| `yellow-600` (`warning` escuro) | `#E6960B` | `#CFA066` |
|
|
204
|
+
|
|
205
|
+
Quem usa os tokens acompanha sozinho. Se o seu app fixou algum desses hex,
|
|
206
|
+
troque pelo token.
|
|
207
|
+
|
|
208
|
+
## 7. Alvos de toque (sem ação necessária)
|
|
209
|
+
|
|
210
|
+
Quatro botões de ícone de 24px (`CommentSystem` reagir/excluir,
|
|
211
|
+
`EditorialCalendar` agendar/recolher backlog) mantêm o tamanho visual e ganham
|
|
212
|
+
área de toque de 32–36px.
|
|
213
|
+
|
|
214
|
+
## 8. Toast: superfície neutra e durações por tipo
|
|
215
|
+
|
|
216
|
+
O visual muda sem mudar a API: `toast`, `toast.success/error/warning/info`,
|
|
217
|
+
`toast.promise` e `<Toaster />` continuam iguais.
|
|
218
|
+
|
|
219
|
+
- **Visual:** o toast não é mais um retângulo colorido. Fundo `card`, título
|
|
220
|
+
`foreground`, descrição `muted-foreground`; a cor de estado fica só no ícone,
|
|
221
|
+
dentro de um disco. O toast de processando (`toast.loading` /
|
|
222
|
+
`toast.promise`) é o único roxo. `toast(msg, { action })` **sem tipo** vem em
|
|
223
|
+
superfície invertida (escura no tema claro): é o padrão para "Desfazer".
|
|
224
|
+
- **Durações padrão** (quando você não passa `duration`): sucesso 4s, aviso 6s,
|
|
225
|
+
informação 5s, sem tipo com `action` 8s. **`toast.error` não some mais
|
|
226
|
+
sozinho**: fica até o usuário fechar, com botão de fechar. Se algum fluxo
|
|
227
|
+
depende do erro sumir, passe `duration` explicitamente.
|
|
228
|
+
- Se você estilizava `.toast-variant-*`, essas classes saíram; o estilo agora
|
|
229
|
+
vive em `[data-sonner-toast].toast[data-type=…]`.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Resumo do que conferir
|
|
234
|
+
|
|
235
|
+
0. **`check-colors.mjs` sem achados** — seção 1. É o único passo que quebra
|
|
236
|
+
em silêncio.
|
|
237
|
+
1. Telas com **lista, tabela ou calendário selecionável** — seção 2, para
|
|
238
|
+
melhor.
|
|
239
|
+
2. Uso de `bg-accent` **decorativo** no seu código.
|
|
240
|
+
3. `Button`/`Badge` `secondary` no tema escuro.
|
|
241
|
+
4. Botões com rótulo longo (seção 4).
|
|
242
|
+
5. Fluxos que contam com `toast.error` sumindo sozinho (seção 8).
|
|
243
|
+
|
|
244
|
+
O resto é repintura sem risco.
|
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Before pushing, run the checks that CI also runs so issues are caught locally fi
|
|
|
94
94
|
A few things to keep in mind while working in this repo:
|
|
95
95
|
|
|
96
96
|
- Hardcoded hex colors are blocked by lint in new code — use the semantic design tokens instead (see `src/styles/tokens.css`).
|
|
97
|
-
-
|
|
97
|
+
- Releases are automatic: a push to `master` that changes the published package gets a patch version after CI passes. Run `npx changeset` only to ship a `minor` or `major`, or to write a custom CHANGELOG entry.
|
|
98
98
|
|
|
99
99
|
### Size variant naming
|
|
100
100
|
|
package/dist/alert-theme.css
CHANGED
|
@@ -1,94 +1,35 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Alert
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Variables used (defined in globals.css, change between modes):
|
|
7
|
-
* --info → cyan-500 (light) / cyan-600 (dark)
|
|
8
|
-
* --success → green-500 (light) / green-600 (dark)
|
|
9
|
-
* --warning → yellow-500 (light) / yellow-600 (dark)
|
|
10
|
-
* --destructive → red-500 (light) / red-600 (dark)
|
|
11
|
-
* --foreground → gray-800 (light) / gray-50 (dark)
|
|
12
|
-
* --card → white (light) / gray-700 (dark)
|
|
13
|
-
* --border → gray-200 (light) / gray-600 (dark)
|
|
2
|
+
* Alert de estado — mesma receita suave do resto da lib: tinta de 10% da cor
|
|
3
|
+
* de estado sobre o card, borda levemente tingida, texto e ícone na camada
|
|
4
|
+
* -emphasis (que passa AA nos dois temas). Estado nunca é fundo sólido nem
|
|
5
|
+
* tinta forte: a cor identifica, a superfície não grita.
|
|
14
6
|
*/
|
|
15
7
|
|
|
16
|
-
/* ── Info ─────────────────────────────────────────────────── */
|
|
17
8
|
[data-slot="alert"].alert-variant-info {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
border-color: color-mix(in oklab, var(--info) 50%, var(--border));
|
|
9
|
+
--alert-state: var(--info);
|
|
10
|
+
--alert-text: var(--info-emphasis);
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
[data-slot="alert"].alert-variant-info > svg {
|
|
24
|
-
color: var(--info);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
[data-slot="alert"].alert-variant-info *[data-slot="alert-description"] {
|
|
28
|
-
color: color-mix(in oklab, var(--info) 65%, var(--foreground));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
[data-slot="alert"].alert-variant-info *[data-slot="alert-title"] {
|
|
32
|
-
color: color-mix(in oklab, var(--info) 80%, var(--foreground));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* ── Success ──────────────────────────────────────────────── */
|
|
36
12
|
[data-slot="alert"].alert-variant-success {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
border-color: color-mix(in oklab, var(--success) 50%, var(--border));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
[data-slot="alert"].alert-variant-success > svg {
|
|
43
|
-
color: var(--success);
|
|
13
|
+
--alert-state: var(--success);
|
|
14
|
+
--alert-text: var(--success-emphasis);
|
|
44
15
|
}
|
|
45
|
-
|
|
46
|
-
[data-slot="alert"].alert-variant-success *[data-slot="alert-description"] {
|
|
47
|
-
color: color-mix(in oklab, var(--success) 65%, var(--foreground));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
[data-slot="alert"].alert-variant-success *[data-slot="alert-title"] {
|
|
51
|
-
color: color-mix(in oklab, var(--success) 80%, var(--foreground));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/* ── Warning ──────────────────────────────────────────────── */
|
|
55
|
-
/* --warning é o único token de status "claro" (yellow-500). Derivar texto
|
|
56
|
-
com o mesmo 80/65% dos outros deixaria ~2,4:1 sobre o fundo pálido — abaixo
|
|
57
|
-
de AA. O texto do warning mistura muito mais --foreground (fica âmbar
|
|
58
|
-
escuro); o ícone continua no amarelo cheio. */
|
|
59
16
|
[data-slot="alert"].alert-variant-warning {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
border-color: color-mix(in oklab, var(--warning) 50%, var(--border));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
[data-slot="alert"].alert-variant-warning > svg {
|
|
66
|
-
color: color-mix(in oklab, var(--warning) 80%, var(--foreground));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
[data-slot="alert"].alert-variant-warning *[data-slot="alert-description"] {
|
|
70
|
-
color: color-mix(in oklab, var(--warning) 26%, var(--foreground));
|
|
17
|
+
--alert-state: var(--warning);
|
|
18
|
+
--alert-text: var(--warning-emphasis);
|
|
71
19
|
}
|
|
72
|
-
|
|
73
|
-
[data-slot="alert"].alert-variant-warning *[data-slot="alert-title"] {
|
|
74
|
-
color: color-mix(in oklab, var(--warning) 32%, var(--foreground));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* ── Destructive ──────────────────────────────────────────── */
|
|
78
20
|
[data-slot="alert"].alert-variant-destructive {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
border-color: color-mix(in oklab, var(--destructive) 50%, var(--border));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
[data-slot="alert"].alert-variant-destructive > svg {
|
|
85
|
-
color: var(--destructive);
|
|
21
|
+
--alert-state: var(--destructive);
|
|
22
|
+
--alert-text: var(--destructive-emphasis);
|
|
86
23
|
}
|
|
87
24
|
|
|
88
|
-
[data-slot="alert"]
|
|
89
|
-
color:
|
|
25
|
+
[data-slot="alert"][class*="alert-variant-"] {
|
|
26
|
+
color: var(--alert-text);
|
|
27
|
+
background-color: color-mix(in oklab, var(--alert-state) 10%, var(--card));
|
|
28
|
+
border-color: color-mix(in oklab, var(--alert-state) 20%, var(--border));
|
|
90
29
|
}
|
|
91
30
|
|
|
92
|
-
[data-slot="alert"]
|
|
93
|
-
|
|
31
|
+
[data-slot="alert"][class*="alert-variant-"] > svg,
|
|
32
|
+
[data-slot="alert"][class*="alert-variant-"] *[data-slot="alert-title"],
|
|
33
|
+
[data-slot="alert"][class*="alert-variant-"] *[data-slot="alert-description"] {
|
|
34
|
+
color: var(--alert-text);
|
|
94
35
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),w=require("react"),h=require("recharts"),p=require("../utils.cjs");function z(n){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const s in n)if(s!=="default"){const l=Object.getOwnPropertyDescriptor(n,s);Object.defineProperty(t,s,l.get?l:{enumerable:!0,get:()=>n[s]})}}return t.default=n,Object.freeze(t)}const
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),w=require("react"),h=require("recharts"),p=require("../utils.cjs");function z(n){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const s in n)if(s!=="default"){const l=Object.getOwnPropertyDescriptor(n,s);Object.defineProperty(t,s,l.get?l:{enumerable:!0,get:()=>n[s]})}}return t.default=n,Object.freeze(t)}const N=z(w),b=Array.from({length:8},(n,t)=>`var(--chart-${t+1})`);function A(n){return new Intl.NumberFormat("pt-BR",{style:"currency",currency:"BRL"}).format(n)}function D({layout:n,chartSize:t}){const s=[1,2,3].map(l=>e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"w-2.5 h-2.5 rounded-full bg-muted shrink-0"}),e.jsx("div",{className:"h-3 bg-muted rounded flex-1"})]},l));return n==="horizontal"?e.jsxs("div",{className:"flex items-center gap-6 w-full animate-pulse",children:[e.jsx("div",{className:"rounded-full bg-muted shrink-0",style:{width:t,height:t}}),e.jsx("div",{className:"flex flex-col gap-2 flex-1",children:s})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center w-full animate-pulse gap-4",children:[e.jsx("div",{className:"rounded-full bg-muted",style:{width:140,height:140}}),e.jsx("div",{className:"flex flex-col gap-2 w-full max-w-50",children:s})]})}function M(){return e.jsxs("div",{className:"flex flex-col items-center justify-center w-full h-full text-muted-foreground gap-2",children:[e.jsx("div",{className:"rounded-full border-4 border-dashed border-muted",style:{width:100,height:100}}),e.jsx("span",{className:"text-sm",children:"Sem dados para exibir"})]})}function R({active:n,payload:t,formatValue:s,total:l}){if(!n||!(t!=null&&t.length))return null;const c=t[0],r=l>0?(c.value/l*100).toFixed(1):"0.0";return e.jsxs("div",{className:"bg-popover border border-border rounded shadow-lg p-2 text-xs min-w-35",children:[e.jsx("div",{className:"font-medium mb-1",children:c.name}),e.jsxs("div",{className:"flex justify-between gap-4",children:[e.jsx("span",{className:"text-muted-foreground",children:"Valor"}),e.jsx("span",{className:"font-mono",children:s(c.value)})]}),e.jsxs("div",{className:"flex justify-between gap-4",children:[e.jsx("span",{className:"text-muted-foreground",children:"%"}),e.jsxs("span",{className:"font-mono",children:[r,"%"]})]})]})}function k({viewBox:n,activeIndex:t,chartData:s,formatValue:l,total:c}){const r=(n==null?void 0:n.cx)??0,i=(n==null?void 0:n.cy)??0,o=t!==null&&t>=0?s[t]:null;if(o){const a=c>0?(o.value/c*100).toFixed(1):"0.0",d=13,j=o.name.length>d?o.name.substring(0,d-1)+"…":o.name;return e.jsxs("g",{children:[e.jsx("text",{x:r,y:i-14,textAnchor:"middle",dominantBaseline:"middle",style:{fontSize:10,fill:"var(--muted-foreground)"},children:j}),e.jsx("text",{x:r,y:i+2,textAnchor:"middle",dominantBaseline:"middle",style:{fontSize:11,fontWeight:700,fill:o.color},children:l(o.value)}),e.jsxs("text",{x:r,y:i+18,textAnchor:"middle",dominantBaseline:"middle",style:{fontSize:11,fill:"var(--muted-foreground)"},children:[a,"%"]})]})}return e.jsxs("g",{children:[e.jsx("text",{x:r,y:i-8,textAnchor:"middle",dominantBaseline:"middle",style:{fontSize:10,fill:"var(--muted-foreground)"},children:"Total"}),e.jsx("text",{x:r,y:i+8,textAnchor:"middle",dominantBaseline:"middle",style:{fontSize:13,fontWeight:700,fill:"currentColor"},children:l(c)})]})}function C({chartData:n,total:t,formatValue:s,activeIndex:l,onHover:c}){return e.jsxs("div",{className:"flex flex-col gap-1.5",children:[n.map((r,i)=>{const o=t>0?(r.value/t*100).toFixed(1):"0.0",a=l===i,d=l!==null&&!a;return e.jsxs("div",{className:"flex items-center gap-2 text-xs cursor-default rounded px-1 py-0.5 transition-opacity",style:{opacity:d?.4:1},onMouseEnter:()=>c(i),onMouseLeave:()=>c(null),children:[e.jsx("span",{className:"inline-block w-2.5 h-2.5 rounded-full shrink-0 transition-transform",style:{background:r.color,transform:a?"scale(1.3)":"scale(1)"}}),e.jsx("span",{className:"flex-1 truncate text-muted-foreground",children:r.name}),e.jsx("span",{className:"font-mono text-foreground",children:s(r.value)}),e.jsxs("span",{className:"text-muted-foreground w-10.5 text-right",children:[o,"%"]})]},i)}),e.jsxs("div",{className:"flex items-center gap-2 text-xs border-t border-border pt-1.5 mt-0.5",children:[e.jsx("span",{className:"flex-1 font-medium",children:"Total"}),e.jsx("span",{className:"font-mono font-semibold",children:s(t)})]})]})}function L({data:n,title:t,formatValue:s=A,className:l,isLoading:c=!1,layout:r="vertical",activeLabel:i=!1,chartSize:o}){const[a,d]=N.useState(null),j=N.useMemo(()=>n.reduce((u,m)=>u+m.value,0),[n]),g=N.useMemo(()=>n.map((u,m)=>({name:u.label,value:u.value,color:u.color??b[m%b.length]})),[n]),x=r==="horizontal",f=o??(x?140:220),y=x?Math.round(f*.34):55,S=x?Math.round(f*.51):85;return e.jsxs("div",{className:p.cn("flex flex-col gap-4 w-full",l),children:[t&&e.jsx("div",{className:"text-sm font-semibold text-foreground",children:t}),c?e.jsx(D,{layout:r,chartSize:f}):n.length===0?e.jsx(M,{}):e.jsxs("div",{className:p.cn(x?"flex items-center gap-4":"flex flex-col gap-4"),children:[e.jsx("div",{className:"shrink-0",style:x?{width:f,height:f}:{width:"100%",height:f},children:e.jsx(h.ResponsiveContainer,{width:"100%",height:"100%",children:e.jsxs(h.PieChart,{children:[e.jsx(h.Pie,{data:g,cx:"50%",cy:"50%",innerRadius:y,outerRadius:S,paddingAngle:2,dataKey:"value",nameKey:"name",onMouseEnter:(u,m)=>d(m),onMouseLeave:()=>d(null),shape:(u,m)=>{var v;return e.jsx(h.Sector,{...u,fill:(v=g[m])==null?void 0:v.color,stroke:"transparent",opacity:a!==null&&a!==m?.4:1})},children:i&&e.jsx(h.Label,{content:e.jsx(k,{activeIndex:a,chartData:g,formatValue:s,total:j})})}),!i&&e.jsx(h.Tooltip,{content:e.jsx(R,{formatValue:s,total:j})})]})})}),e.jsx("div",{className:p.cn(x?"flex-1 min-w-0":""),children:e.jsx(C,{chartData:g,total:j,formatValue:s,activeIndex:a,onHover:d})})]})]})}exports.DonutChart=L;
|
|
2
2
|
//# sourceMappingURL=donut-chart.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"donut-chart.cjs","sources":["../../../../src/components/ui/charts/donut-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Label, Pie, PieChart, ResponsiveContainer, Sector, Tooltip } from \"recharts\";\nimport { cn } from \"../utils\";\n\n// ── Paleta padrão ─────────────────────────────────────────────────────────────\nconst DEFAULT_COLORS = [\n \"#3b82f6\", // blue-500\n \"#10b981\", // emerald-500\n \"#f59e0b\", // amber-500\n \"#ef4444\", // red-500\n \"#8b5cf6\", // violet-500\n \"#06b6d4\", // cyan-500\n \"#f97316\", // orange-500\n \"#14b8a6\", // teal-500\n \"#ec4899\", // pink-500\n \"#6366f1\", // indigo-500\n];\n\n// ── Tipos ─────────────────────────────────────────────────────────────────────\n\nexport interface DonutChartItem {\n label: string;\n value: number;\n color?: string;\n}\n\nexport interface DonutChartProps {\n data: DonutChartItem[];\n title?: string;\n formatValue?: (v: number) => string;\n className?: string;\n isLoading?: boolean;\n /**\n * Orientação da legenda em relação ao gráfico.\n * - \"vertical\": legenda abaixo do donut (padrão)\n * - \"horizontal\": legenda ao lado direito do donut\n */\n layout?: \"vertical\" | \"horizontal\";\n /**\n * Quando true, exibe o nome, valor e % da categoria no centro do\n * donut ao passar o mouse — desabilita o tooltip flutuante padrão.\n */\n activeLabel?: boolean;\n /**\n * Tamanho (px) do container do gráfico no eixo fixo.\n * - horizontal: largura e altura do donut (padrão: 140)\n * - vertical: altura do donut (padrão: 220)\n */\n chartSize?: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction defaultFormat(v: number): string {\n return new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(v);\n}\n\n// ── Skeleton ──────────────────────────────────────────────────────────────────\n\nfunction DonutSkeleton({ layout, chartSize }: { layout: \"vertical\" | \"horizontal\"; chartSize: number }) {\n const legendItems = [1, 2, 3].map((i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-muted shrink-0\" />\n <div className=\"h-3 bg-muted rounded flex-1\" />\n </div>\n ));\n\n if (layout === \"horizontal\") {\n return (\n <div className=\"flex items-center gap-6 w-full animate-pulse\">\n <div className=\"rounded-full bg-muted shrink-0\" style={{ width: chartSize, height: chartSize }} />\n <div className=\"flex flex-col gap-2 flex-1\">{legendItems}</div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center justify-center w-full animate-pulse gap-4\">\n <div className=\"rounded-full bg-muted\" style={{ width: 140, height: 140 }} />\n <div className=\"flex flex-col gap-2 w-full max-w-50\">{legendItems}</div>\n </div>\n );\n}\n\n// ── Empty state ───────────────────────────────────────────────────────────────\n\nfunction DonutEmpty() {\n return (\n <div className=\"flex flex-col items-center justify-center w-full h-full text-muted-foreground gap-2\">\n <div\n className=\"rounded-full border-4 border-dashed border-muted\"\n style={{ width: 100, height: 100 }}\n />\n <span className=\"text-sm\">Sem dados para exibir</span>\n </div>\n );\n}\n\n// ── Tooltip flutuante (usado quando activeLabel=false) ────────────────────────\n\nfunction CustomTooltip({\n active,\n payload,\n formatValue,\n total,\n}: {\n active?: boolean;\n payload?: any[];\n formatValue: (v: number) => string;\n total: number;\n}) {\n if (!active || !payload?.length) return null;\n const item = payload[0];\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n return (\n <div className=\"bg-popover border border-border rounded shadow-lg p-2 text-xs min-w-35\">\n <div className=\"font-medium mb-1\">{item.name}</div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Valor</span>\n <span className=\"font-mono\">{formatValue(item.value)}</span>\n </div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">%</span>\n <span className=\"font-mono\">{pct}%</span>\n </div>\n </div>\n );\n}\n\n// ── Label central (usado quando activeLabel=true) ─────────────────────────────\n\ninterface CenterLabelProps {\n viewBox?: { cx: number; cy: number; innerRadius?: number; outerRadius?: number };\n activeIndex: number | null;\n chartData: Array<{ name: string; value: number; color: string }>;\n formatValue: (v: number) => string;\n total: number;\n}\n\nfunction CenterLabel({ viewBox, activeIndex, chartData, formatValue, total }: CenterLabelProps) {\n const cx = viewBox?.cx ?? 0;\n const cy = viewBox?.cy ?? 0;\n\n const item = activeIndex !== null && activeIndex >= 0 ? chartData[activeIndex] : null;\n\n if (item) {\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n const maxLen = 13;\n const name = item.name.length > maxLen ? item.name.substring(0, maxLen - 1) + \"…\" : item.name;\n return (\n <g>\n <text\n x={cx}\n y={cy - 14}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 10, fill: \"var(--muted-foreground)\" }}\n >\n {name}\n </text>\n <text\n x={cx}\n y={cy + 2}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 11, fontWeight: 700, fill: item.color }}\n >\n {formatValue(item.value)}\n </text>\n <text\n x={cx}\n y={cy + 18}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 11, fill: \"var(--muted-foreground)\" }}\n >\n {pct}%\n </text>\n </g>\n );\n }\n\n return (\n <g>\n <text\n x={cx}\n y={cy - 8}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 10, fill: \"var(--muted-foreground)\" }}\n >\n Total\n </text>\n <text\n x={cx}\n y={cy + 8}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 13, fontWeight: 700, fill: \"currentColor\" }}\n >\n {formatValue(total)}\n </text>\n </g>\n );\n}\n\n// ── Legenda ───────────────────────────────────────────────────────────────────\n\nfunction DonutLegend({\n chartData,\n total,\n formatValue,\n activeIndex,\n onHover,\n}: {\n chartData: Array<{ name: string; value: number; color: string }>;\n total: number;\n formatValue: (v: number) => string;\n activeIndex: number | null;\n onHover: (index: number | null) => void;\n}) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chartData.map((item, i) => {\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n const isActive = activeIndex === i;\n const isDimmed = activeIndex !== null && !isActive;\n return (\n <div\n key={i}\n className=\"flex items-center gap-2 text-xs cursor-default rounded px-1 py-0.5 transition-opacity\"\n style={{ opacity: isDimmed ? 0.4 : 1 }}\n onMouseEnter={() => onHover(i)}\n onMouseLeave={() => onHover(null)}\n >\n <span\n className=\"inline-block w-2.5 h-2.5 rounded-full shrink-0 transition-transform\"\n style={{\n background: item.color,\n transform: isActive ? \"scale(1.3)\" : \"scale(1)\",\n }}\n />\n <span className=\"flex-1 truncate text-muted-foreground\">{item.name}</span>\n <span className=\"font-mono text-foreground\">{formatValue(item.value)}</span>\n <span className=\"text-muted-foreground w-10.5 text-right\">{pct}%</span>\n </div>\n );\n })}\n <div className=\"flex items-center gap-2 text-xs border-t border-border pt-1.5 mt-0.5\">\n <span className=\"flex-1 font-medium\">Total</span>\n <span className=\"font-mono font-semibold\">{formatValue(total)}</span>\n </div>\n </div>\n );\n}\n\n// ── Componente principal ──────────────────────────────────────────────────────\n\nexport function DonutChart({\n data,\n title,\n formatValue = defaultFormat,\n className,\n isLoading = false,\n layout = \"vertical\",\n activeLabel = false,\n chartSize: chartSizeProp,\n}: DonutChartProps) {\n const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n\n const total = React.useMemo(() => data.reduce((acc, d) => acc + d.value, 0), [data]);\n\n const chartData = React.useMemo(\n () =>\n data.map((item, i) => ({\n name: item.label,\n value: item.value,\n color: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length],\n })),\n [data],\n );\n\n const isHorizontal = layout === \"horizontal\";\n const chartSize = chartSizeProp ?? (isHorizontal ? 140 : 220);\n const innerRadius = isHorizontal ? Math.round(chartSize * 0.34) : 55;\n const outerRadius = isHorizontal ? Math.round(chartSize * 0.51) : 85;\n\n return (\n <div className={cn(\"flex flex-col gap-4 w-full\", className)}>\n {title && <div className=\"text-sm font-semibold text-foreground\">{title}</div>}\n\n {isLoading ? (\n <DonutSkeleton layout={layout} chartSize={chartSize} />\n ) : data.length === 0 ? (\n <DonutEmpty />\n ) : (\n <div className={cn(isHorizontal ? \"flex items-center gap-4\" : \"flex flex-col gap-4\")}>\n {/* Gráfico */}\n <div\n className=\"shrink-0\"\n style={\n isHorizontal\n ? { width: chartSize, height: chartSize }\n : { width: \"100%\", height: chartSize }\n }\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={chartData}\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n paddingAngle={2}\n dataKey=\"value\"\n nameKey=\"name\"\n onMouseEnter={(_, index) => setActiveIndex(index)}\n onMouseLeave={() => setActiveIndex(null)}\n shape={(props, index) => (\n <Sector\n {...props}\n fill={chartData[index]?.color}\n stroke=\"transparent\"\n opacity={activeIndex !== null && activeIndex !== index ? 0.4 : 1}\n />\n )}\n >\n {activeLabel && (\n <Label\n content={\n <CenterLabel\n activeIndex={activeIndex}\n chartData={chartData}\n formatValue={formatValue}\n total={total}\n />\n }\n />\n )}\n </Pie>\n {!activeLabel && (\n <Tooltip\n content={<CustomTooltip formatValue={formatValue} total={total} />}\n />\n )}\n </PieChart>\n </ResponsiveContainer>\n </div>\n\n {/* Legenda */}\n <div className={cn(isHorizontal ? \"flex-1 min-w-0\" : \"\")}>\n <DonutLegend\n chartData={chartData}\n total={total}\n formatValue={formatValue}\n activeIndex={activeIndex}\n onHover={setActiveIndex}\n />\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"names":["DEFAULT_COLORS","defaultFormat","v","DonutSkeleton","layout","chartSize","legendItems","i","jsxs","jsx","DonutEmpty","CustomTooltip","active","payload","formatValue","total","item","pct","CenterLabel","viewBox","activeIndex","chartData","cx","cy","maxLen","name","DonutLegend","onHover","isActive","isDimmed","DonutChart","data","title","className","isLoading","activeLabel","chartSizeProp","setActiveIndex","React","acc","d","isHorizontal","innerRadius","outerRadius","cn","ResponsiveContainer","PieChart","Pie","_","index","props","Sector","_a","Label","Tooltip"],"mappings":"+dAOMA,EAAiB,CACrB,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,EAqCA,SAASC,EAAcC,EAAmB,CACxC,OAAO,IAAI,KAAK,aAAa,QAAS,CAAE,MAAO,WAAY,SAAU,KAAA,CAAO,EAAE,OAAOA,CAAC,CACxF,CAIA,SAASC,EAAc,CAAE,OAAAC,EAAQ,UAAAC,GAAuE,CACtG,MAAMC,EAAc,CAAC,EAAG,EAAG,CAAC,EAAE,IAAKC,GACjCC,EAAAA,KAAC,MAAA,CAAY,UAAU,0BACrB,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAI,UAAU,4CAAA,CAA6C,EAC5DA,EAAAA,IAAC,MAAA,CAAI,UAAU,6BAAA,CAA8B,CAAA,CAAA,EAFrCF,CAGV,CACD,EAED,OAAIH,IAAW,aAEXI,EAAAA,KAAC,MAAA,CAAI,UAAU,+CACb,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAI,UAAU,iCAAiC,MAAO,CAAE,MAAOJ,EAAW,OAAQA,CAAA,CAAU,CAAG,EAChGI,EAAAA,IAAC,MAAA,CAAI,UAAU,6BAA8B,SAAAH,CAAA,CAAY,CAAA,EAC3D,EAKFE,EAAAA,KAAC,MAAA,CAAI,UAAU,uEACb,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAI,UAAU,wBAAwB,MAAO,CAAE,MAAO,IAAK,OAAQ,GAAA,CAAI,CAAG,EAC3EA,EAAAA,IAAC,MAAA,CAAI,UAAU,sCAAuC,SAAAH,CAAA,CAAY,CAAA,EACpE,CAEJ,CAIA,SAASI,GAAa,CACpB,OACEF,EAAAA,KAAC,MAAA,CAAI,UAAU,sFACb,SAAA,CAAAC,EAAAA,IAAC,MAAA,CACC,UAAU,mDACV,MAAO,CAAE,MAAO,IAAK,OAAQ,GAAA,CAAI,CAAA,EAEnCA,EAAAA,IAAC,OAAA,CAAK,UAAU,UAAU,SAAA,uBAAA,CAAqB,CAAA,EACjD,CAEJ,CAIA,SAASE,EAAc,CACrB,OAAAC,EACA,QAAAC,EACA,YAAAC,EACA,MAAAC,CACF,EAKG,CACD,GAAI,CAACH,GAAU,EAACC,GAAA,MAAAA,EAAS,QAAQ,OAAO,KACxC,MAAMG,EAAOH,EAAQ,CAAC,EAChBI,EAAMF,EAAQ,GAAMC,EAAK,MAAQD,EAAS,KAAK,QAAQ,CAAC,EAAI,MAClE,OACEP,EAAAA,KAAC,MAAA,CAAI,UAAU,yEACb,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAI,UAAU,mBAAoB,SAAAO,EAAK,KAAK,EAC7CR,EAAAA,KAAC,MAAA,CAAI,UAAU,6BACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,wBAAwB,SAAA,QAAK,QAC5C,OAAA,CAAK,UAAU,YAAa,SAAAK,EAAYE,EAAK,KAAK,CAAA,CAAE,CAAA,EACvD,EACAR,EAAAA,KAAC,MAAA,CAAI,UAAU,6BACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,wBAAwB,SAAA,IAAC,EACzCD,EAAAA,KAAC,OAAA,CAAK,UAAU,YAAa,SAAA,CAAAS,EAAI,GAAA,CAAA,CAAC,CAAA,CAAA,CACpC,CAAA,EACF,CAEJ,CAYA,SAASC,EAAY,CAAE,QAAAC,EAAS,YAAAC,EAAa,UAAAC,EAAW,YAAAP,EAAa,MAAAC,GAA2B,CAC9F,MAAMO,GAAKH,GAAA,YAAAA,EAAS,KAAM,EACpBI,GAAKJ,GAAA,YAAAA,EAAS,KAAM,EAEpBH,EAAOI,IAAgB,MAAQA,GAAe,EAAIC,EAAUD,CAAW,EAAI,KAEjF,GAAIJ,EAAM,CACR,MAAMC,EAAMF,EAAQ,GAAMC,EAAK,MAAQD,EAAS,KAAK,QAAQ,CAAC,EAAI,MAC5DS,EAAS,GACTC,EAAOT,EAAK,KAAK,OAASQ,EAASR,EAAK,KAAK,UAAU,EAAGQ,EAAS,CAAC,EAAI,IAAMR,EAAK,KACzF,cACG,IAAA,CACC,SAAA,CAAAP,EAAAA,IAAC,OAAA,CACC,EAAGa,EACH,EAAGC,EAAK,GACR,WAAW,SACX,iBAAiB,SACjB,MAAO,CAAE,SAAU,GAAI,KAAM,yBAAA,EAE5B,SAAAE,CAAA,CAAA,EAEHhB,EAAAA,IAAC,OAAA,CACC,EAAGa,EACH,EAAGC,EAAK,EACR,WAAW,SACX,iBAAiB,SACjB,MAAO,CAAE,SAAU,GAAI,WAAY,IAAK,KAAMP,EAAK,KAAA,EAElD,SAAAF,EAAYE,EAAK,KAAK,CAAA,CAAA,EAEzBR,EAAAA,KAAC,OAAA,CACC,EAAGc,EACH,EAAGC,EAAK,GACR,WAAW,SACX,iBAAiB,SACjB,MAAO,CAAE,SAAU,GAAI,KAAM,yBAAA,EAE5B,SAAA,CAAAN,EAAI,GAAA,CAAA,CAAA,CACP,EACF,CAEJ,CAEA,cACG,IAAA,CACC,SAAA,CAAAR,EAAAA,IAAC,OAAA,CACC,EAAGa,EACH,EAAGC,EAAK,EACR,WAAW,SACX,iBAAiB,SACjB,MAAO,CAAE,SAAU,GAAI,KAAM,yBAAA,EAC9B,SAAA,OAAA,CAAA,EAGDd,EAAAA,IAAC,OAAA,CACC,EAAGa,EACH,EAAGC,EAAK,EACR,WAAW,SACX,iBAAiB,SACjB,MAAO,CAAE,SAAU,GAAI,WAAY,IAAK,KAAM,cAAA,EAE7C,WAAYR,CAAK,CAAA,CAAA,CACpB,EACF,CAEJ,CAIA,SAASW,EAAY,CACnB,UAAAL,EACA,MAAAN,EACA,YAAAD,EACA,YAAAM,EACA,QAAAO,CACF,EAMG,CACD,OACEnB,EAAAA,KAAC,MAAA,CAAI,UAAU,wBACZ,SAAA,CAAAa,EAAU,IAAI,CAACL,EAAM,IAAM,CAC1B,MAAMC,EAAMF,EAAQ,GAAMC,EAAK,MAAQD,EAAS,KAAK,QAAQ,CAAC,EAAI,MAC5Da,EAAWR,IAAgB,EAC3BS,EAAWT,IAAgB,MAAQ,CAACQ,EAC1C,OACEpB,EAAAA,KAAC,MAAA,CAEC,UAAU,wFACV,MAAO,CAAE,QAASqB,EAAW,GAAM,CAAA,EACnC,aAAc,IAAMF,EAAQ,CAAC,EAC7B,aAAc,IAAMA,EAAQ,IAAI,EAEhC,SAAA,CAAAlB,EAAAA,IAAC,OAAA,CACC,UAAU,sEACV,MAAO,CACL,WAAYO,EAAK,MACjB,UAAWY,EAAW,aAAe,UAAA,CACvC,CAAA,EAEFnB,EAAAA,IAAC,OAAA,CAAK,UAAU,wCAAyC,WAAK,KAAK,QAClE,OAAA,CAAK,UAAU,4BAA6B,SAAAK,EAAYE,EAAK,KAAK,EAAE,EACrER,EAAAA,KAAC,OAAA,CAAK,UAAU,0CAA2C,SAAA,CAAAS,EAAI,GAAA,CAAA,CAAC,CAAA,CAAA,EAf3D,CAAA,CAkBX,CAAC,EACDT,EAAAA,KAAC,MAAA,CAAI,UAAU,uEACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,qBAAqB,SAAA,QAAK,QACzC,OAAA,CAAK,UAAU,0BAA2B,SAAAK,EAAYC,CAAK,CAAA,CAAE,CAAA,CAAA,CAChE,CAAA,EACF,CAEJ,CAIO,SAASe,EAAW,CACzB,KAAAC,EACA,MAAAC,EACA,YAAAlB,EAAcb,EACd,UAAAgC,EACA,UAAAC,EAAY,GACZ,OAAA9B,EAAS,WACT,YAAA+B,EAAc,GACd,UAAWC,CACb,EAAoB,CAClB,KAAM,CAAChB,EAAaiB,CAAc,EAAIC,EAAM,SAAwB,IAAI,EAElEvB,EAAQuB,EAAM,QAAQ,IAAMP,EAAK,OAAO,CAACQ,EAAKC,IAAMD,EAAMC,EAAE,MAAO,CAAC,EAAG,CAACT,CAAI,CAAC,EAE7EV,EAAYiB,EAAM,QACtB,IACEP,EAAK,IAAI,CAACf,EAAMT,KAAO,CACrB,KAAMS,EAAK,MACX,MAAOA,EAAK,MACZ,MAAOA,EAAK,OAAShB,EAAeO,EAAIP,EAAe,MAAM,CAAA,EAC7D,EACJ,CAAC+B,CAAI,CAAA,EAGDU,EAAerC,IAAW,aAC1BC,EAAY+B,IAAkBK,EAAe,IAAM,KACnDC,EAAcD,EAAe,KAAK,MAAMpC,EAAY,GAAI,EAAI,GAC5DsC,EAAcF,EAAe,KAAK,MAAMpC,EAAY,GAAI,EAAI,GAElE,cACG,MAAA,CAAI,UAAWuC,EAAAA,GAAG,6BAA8BX,CAAS,EACvD,SAAA,CAAAD,GAASvB,EAAAA,IAAC,MAAA,CAAI,UAAU,wCAAyC,SAAAuB,EAAM,EAEvEE,EACCzB,EAAAA,IAACN,EAAA,CAAc,OAAAC,EAAgB,UAAAC,CAAA,CAAsB,EACnD0B,EAAK,SAAW,QACjBrB,EAAA,CAAA,CAAW,SAEX,MAAA,CAAI,UAAWkC,KAAGH,EAAe,0BAA4B,qBAAqB,EAEjF,SAAA,CAAAhC,EAAAA,IAAC,MAAA,CACC,UAAU,WACV,MACEgC,EACI,CAAE,MAAOpC,EAAW,OAAQA,CAAA,EAC5B,CAAE,MAAO,OAAQ,OAAQA,CAAA,EAG/B,eAACwC,sBAAA,CAAoB,MAAM,OAAO,OAAO,OACvC,gBAACC,EAAAA,SAAA,CACC,SAAA,CAAArC,EAAAA,IAACsC,EAAAA,IAAA,CACC,KAAM1B,EACN,GAAG,MACH,GAAG,MACH,YAAAqB,EACA,YAAAC,EACA,aAAc,EACd,QAAQ,QACR,QAAQ,OACR,aAAc,CAACK,EAAGC,IAAUZ,EAAeY,CAAK,EAChD,aAAc,IAAMZ,EAAe,IAAI,EACvC,MAAO,CAACa,EAAOD,WACbxC,OAAAA,EAAAA,IAAC0C,EAAAA,OAAA,CACE,GAAGD,EACJ,MAAME,EAAA/B,EAAU4B,CAAK,IAAf,YAAAG,EAAkB,MACxB,OAAO,cACP,QAAShC,IAAgB,MAAQA,IAAgB6B,EAAQ,GAAM,CAAA,CAAA,GAIlE,SAAAd,GACC1B,EAAAA,IAAC4C,EAAAA,MAAA,CACC,QACE5C,EAAAA,IAACS,EAAA,CACC,YAAAE,EACA,UAAAC,EACA,YAAAP,EACA,MAAAC,CAAA,CAAA,CACF,CAAA,CAEJ,CAAA,EAGH,CAACoB,GACA1B,EAAAA,IAAC6C,EAAAA,QAAA,CACC,QAAS7C,EAAAA,IAACE,EAAA,CAAc,YAAAG,EAA0B,MAAAC,CAAA,CAAc,CAAA,CAAA,CAClE,CAAA,CAEJ,CAAA,CACF,CAAA,CAAA,QAID,MAAA,CAAI,UAAW6B,EAAAA,GAAGH,EAAe,iBAAmB,EAAE,EACrD,SAAAhC,EAAAA,IAACiB,EAAA,CACC,UAAAL,EACA,MAAAN,EACA,YAAAD,EACA,YAAAM,EACA,QAASiB,CAAA,CAAA,CACX,CACF,CAAA,CAAA,CACF,CAAA,EAEJ,CAEJ"}
|
|
1
|
+
{"version":3,"file":"donut-chart.cjs","sources":["../../../../src/components/ui/charts/donut-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Label, Pie, PieChart, ResponsiveContainer, Sector, Tooltip } from \"recharts\";\nimport { cn } from \"../utils\";\n\n// ── Paleta padrão ─────────────────────────────────────────────────────────────\n// Séries de gráfico da paleta iClips (roxo + cinza, vizinhas distintas em luminância).\nconst DEFAULT_COLORS = Array.from({ length: 8 }, (_, i) => `var(--chart-${i + 1})`);\n\n// ── Tipos ─────────────────────────────────────────────────────────────────────\n\nexport interface DonutChartItem {\n label: string;\n value: number;\n color?: string;\n}\n\nexport interface DonutChartProps {\n data: DonutChartItem[];\n title?: string;\n formatValue?: (v: number) => string;\n className?: string;\n isLoading?: boolean;\n /**\n * Orientação da legenda em relação ao gráfico.\n * - \"vertical\": legenda abaixo do donut (padrão)\n * - \"horizontal\": legenda ao lado direito do donut\n */\n layout?: \"vertical\" | \"horizontal\";\n /**\n * Quando true, exibe o nome, valor e % da categoria no centro do\n * donut ao passar o mouse — desabilita o tooltip flutuante padrão.\n */\n activeLabel?: boolean;\n /**\n * Tamanho (px) do container do gráfico no eixo fixo.\n * - horizontal: largura e altura do donut (padrão: 140)\n * - vertical: altura do donut (padrão: 220)\n */\n chartSize?: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction defaultFormat(v: number): string {\n return new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(v);\n}\n\n// ── Skeleton ──────────────────────────────────────────────────────────────────\n\nfunction DonutSkeleton({ layout, chartSize }: { layout: \"vertical\" | \"horizontal\"; chartSize: number }) {\n const legendItems = [1, 2, 3].map((i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-muted shrink-0\" />\n <div className=\"h-3 bg-muted rounded flex-1\" />\n </div>\n ));\n\n if (layout === \"horizontal\") {\n return (\n <div className=\"flex items-center gap-6 w-full animate-pulse\">\n <div className=\"rounded-full bg-muted shrink-0\" style={{ width: chartSize, height: chartSize }} />\n <div className=\"flex flex-col gap-2 flex-1\">{legendItems}</div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center justify-center w-full animate-pulse gap-4\">\n <div className=\"rounded-full bg-muted\" style={{ width: 140, height: 140 }} />\n <div className=\"flex flex-col gap-2 w-full max-w-50\">{legendItems}</div>\n </div>\n );\n}\n\n// ── Empty state ───────────────────────────────────────────────────────────────\n\nfunction DonutEmpty() {\n return (\n <div className=\"flex flex-col items-center justify-center w-full h-full text-muted-foreground gap-2\">\n <div\n className=\"rounded-full border-4 border-dashed border-muted\"\n style={{ width: 100, height: 100 }}\n />\n <span className=\"text-sm\">Sem dados para exibir</span>\n </div>\n );\n}\n\n// ── Tooltip flutuante (usado quando activeLabel=false) ────────────────────────\n\nfunction CustomTooltip({\n active,\n payload,\n formatValue,\n total,\n}: {\n active?: boolean;\n payload?: any[];\n formatValue: (v: number) => string;\n total: number;\n}) {\n if (!active || !payload?.length) return null;\n const item = payload[0];\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n return (\n <div className=\"bg-popover border border-border rounded shadow-lg p-2 text-xs min-w-35\">\n <div className=\"font-medium mb-1\">{item.name}</div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">Valor</span>\n <span className=\"font-mono\">{formatValue(item.value)}</span>\n </div>\n <div className=\"flex justify-between gap-4\">\n <span className=\"text-muted-foreground\">%</span>\n <span className=\"font-mono\">{pct}%</span>\n </div>\n </div>\n );\n}\n\n// ── Label central (usado quando activeLabel=true) ─────────────────────────────\n\ninterface CenterLabelProps {\n viewBox?: { cx: number; cy: number; innerRadius?: number; outerRadius?: number };\n activeIndex: number | null;\n chartData: Array<{ name: string; value: number; color: string }>;\n formatValue: (v: number) => string;\n total: number;\n}\n\nfunction CenterLabel({ viewBox, activeIndex, chartData, formatValue, total }: CenterLabelProps) {\n const cx = viewBox?.cx ?? 0;\n const cy = viewBox?.cy ?? 0;\n\n const item = activeIndex !== null && activeIndex >= 0 ? chartData[activeIndex] : null;\n\n if (item) {\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n const maxLen = 13;\n const name = item.name.length > maxLen ? item.name.substring(0, maxLen - 1) + \"…\" : item.name;\n return (\n <g>\n <text\n x={cx}\n y={cy - 14}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 10, fill: \"var(--muted-foreground)\" }}\n >\n {name}\n </text>\n <text\n x={cx}\n y={cy + 2}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 11, fontWeight: 700, fill: item.color }}\n >\n {formatValue(item.value)}\n </text>\n <text\n x={cx}\n y={cy + 18}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 11, fill: \"var(--muted-foreground)\" }}\n >\n {pct}%\n </text>\n </g>\n );\n }\n\n return (\n <g>\n <text\n x={cx}\n y={cy - 8}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 10, fill: \"var(--muted-foreground)\" }}\n >\n Total\n </text>\n <text\n x={cx}\n y={cy + 8}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n style={{ fontSize: 13, fontWeight: 700, fill: \"currentColor\" }}\n >\n {formatValue(total)}\n </text>\n </g>\n );\n}\n\n// ── Legenda ───────────────────────────────────────────────────────────────────\n\nfunction DonutLegend({\n chartData,\n total,\n formatValue,\n activeIndex,\n onHover,\n}: {\n chartData: Array<{ name: string; value: number; color: string }>;\n total: number;\n formatValue: (v: number) => string;\n activeIndex: number | null;\n onHover: (index: number | null) => void;\n}) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n {chartData.map((item, i) => {\n const pct = total > 0 ? ((item.value / total) * 100).toFixed(1) : \"0.0\";\n const isActive = activeIndex === i;\n const isDimmed = activeIndex !== null && !isActive;\n return (\n <div\n key={i}\n className=\"flex items-center gap-2 text-xs cursor-default rounded px-1 py-0.5 transition-opacity\"\n style={{ opacity: isDimmed ? 0.4 : 1 }}\n onMouseEnter={() => onHover(i)}\n onMouseLeave={() => onHover(null)}\n >\n <span\n className=\"inline-block w-2.5 h-2.5 rounded-full shrink-0 transition-transform\"\n style={{\n background: item.color,\n transform: isActive ? \"scale(1.3)\" : \"scale(1)\",\n }}\n />\n <span className=\"flex-1 truncate text-muted-foreground\">{item.name}</span>\n <span className=\"font-mono text-foreground\">{formatValue(item.value)}</span>\n <span className=\"text-muted-foreground w-10.5 text-right\">{pct}%</span>\n </div>\n );\n })}\n <div className=\"flex items-center gap-2 text-xs border-t border-border pt-1.5 mt-0.5\">\n <span className=\"flex-1 font-medium\">Total</span>\n <span className=\"font-mono font-semibold\">{formatValue(total)}</span>\n </div>\n </div>\n );\n}\n\n// ── Componente principal ──────────────────────────────────────────────────────\n\nexport function DonutChart({\n data,\n title,\n formatValue = defaultFormat,\n className,\n isLoading = false,\n layout = \"vertical\",\n activeLabel = false,\n chartSize: chartSizeProp,\n}: DonutChartProps) {\n const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n\n const total = React.useMemo(() => data.reduce((acc, d) => acc + d.value, 0), [data]);\n\n const chartData = React.useMemo(\n () =>\n data.map((item, i) => ({\n name: item.label,\n value: item.value,\n color: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length],\n })),\n [data],\n );\n\n const isHorizontal = layout === \"horizontal\";\n const chartSize = chartSizeProp ?? (isHorizontal ? 140 : 220);\n const innerRadius = isHorizontal ? Math.round(chartSize * 0.34) : 55;\n const outerRadius = isHorizontal ? Math.round(chartSize * 0.51) : 85;\n\n return (\n <div className={cn(\"flex flex-col gap-4 w-full\", className)}>\n {title && <div className=\"text-sm font-semibold text-foreground\">{title}</div>}\n\n {isLoading ? (\n <DonutSkeleton layout={layout} chartSize={chartSize} />\n ) : data.length === 0 ? (\n <DonutEmpty />\n ) : (\n <div className={cn(isHorizontal ? \"flex items-center gap-4\" : \"flex flex-col gap-4\")}>\n {/* Gráfico */}\n <div\n className=\"shrink-0\"\n style={\n isHorizontal\n ? { width: chartSize, height: chartSize }\n : { width: \"100%\", height: chartSize }\n }\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Pie\n data={chartData}\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n paddingAngle={2}\n dataKey=\"value\"\n nameKey=\"name\"\n onMouseEnter={(_, index) => setActiveIndex(index)}\n onMouseLeave={() => setActiveIndex(null)}\n shape={(props, index) => (\n <Sector\n {...props}\n fill={chartData[index]?.color}\n stroke=\"transparent\"\n opacity={activeIndex !== null && activeIndex !== index ? 0.4 : 1}\n />\n )}\n >\n {activeLabel && (\n <Label\n content={\n <CenterLabel\n activeIndex={activeIndex}\n chartData={chartData}\n formatValue={formatValue}\n total={total}\n />\n }\n />\n )}\n </Pie>\n {!activeLabel && (\n <Tooltip\n content={<CustomTooltip formatValue={formatValue} total={total} />}\n />\n )}\n </PieChart>\n </ResponsiveContainer>\n </div>\n\n {/* Legenda */}\n <div className={cn(isHorizontal ? \"flex-1 min-w-0\" : \"\")}>\n <DonutLegend\n chartData={chartData}\n total={total}\n formatValue={formatValue}\n activeIndex={activeIndex}\n onHover={setActiveIndex}\n />\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"names":["DEFAULT_COLORS","_","i","defaultFormat","v","DonutSkeleton","layout","chartSize","legendItems","jsxs","jsx","DonutEmpty","CustomTooltip","active","payload","formatValue","total","item","pct","CenterLabel","viewBox","activeIndex","chartData","cx","cy","maxLen","name","DonutLegend","onHover","isActive","isDimmed","DonutChart","data","title","className","isLoading","activeLabel","chartSizeProp","setActiveIndex","React","acc","d","isHorizontal","innerRadius","outerRadius","cn","ResponsiveContainer","PieChart","Pie","index","props","Sector","_a","Label","Tooltip"],"mappings":"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"}
|