@snksergio/design-system 0.62.0 → 0.64.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 (149) hide show
  1. package/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
  2. package/dist-lib/ai/blocos/indice.md +43 -0
  3. package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
  4. package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
  5. package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
  6. package/dist-lib/ai/componentes/DataTable.md +2 -1
  7. package/dist-lib/ai/componentes/TableToolbar.md +1 -1
  8. package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
  9. package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
  10. package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
  11. package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
  12. package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
  13. package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
  14. package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
  15. package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
  16. package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
  17. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
  18. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
  19. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
  20. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
  21. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
  22. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
  23. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
  24. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
  25. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
  26. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
  27. package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
  28. package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
  29. package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
  30. package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
  31. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
  32. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
  33. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
  34. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
  35. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
  36. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
  37. package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
  38. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
  39. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
  40. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
  41. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
  42. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
  43. package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
  44. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
  45. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
  46. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
  47. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
  48. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
  49. package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
  50. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
  51. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
  52. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
  53. package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
  54. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
  55. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
  56. package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
  57. package/dist-lib/ai/exemplos/chat/index.ts +1 -0
  58. package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
  59. package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
  60. package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
  61. package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
  62. package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
  63. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
  64. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
  65. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
  66. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
  67. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
  68. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  69. package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
  70. package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
  71. package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
  72. package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
  73. package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
  74. package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
  75. package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
  76. package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
  77. package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
  78. package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
  79. package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
  80. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
  81. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
  82. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
  83. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
  84. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
  85. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
  86. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
  87. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  88. package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
  89. package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
  90. package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
  91. package/dist-lib/ai/exemplos/finance/index.ts +1 -0
  92. package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
  93. package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
  94. package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
  95. package/dist-lib/ai/exemplos/login/index.ts +1 -0
  96. package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
  97. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
  98. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
  99. package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
  100. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
  101. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
  102. package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
  103. package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
  104. package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
  105. package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
  106. package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
  107. package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
  108. package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
  109. package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
  110. package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
  111. package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
  112. package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
  113. package/dist-lib/ai/global/componentes.md +7 -0
  114. package/dist-lib/ai/indice.json +30 -15
  115. package/dist-lib/ai/manifest.json +32 -6
  116. package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
  117. package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
  118. package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
  119. package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
  120. package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
  121. package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
  122. package/dist-lib/ai/roteiros/crud-builder/entrevista.md +146 -0
  123. package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
  124. package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
  125. package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
  126. package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
  127. package/dist-lib/ai/roteiros/list-builder/entrevista.md +193 -0
  128. package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
  129. package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
  130. package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
  131. package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
  132. package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
  133. package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
  134. package/dist-lib/chunks/{panel-B-liPlxr.mjs → panel-CN0ChoHM.mjs} +5 -2
  135. package/dist-lib/chunks/{panel-B-liPlxr.mjs.map → panel-CN0ChoHM.mjs.map} +1 -1
  136. package/dist-lib/chunks/{panel-DoNbjeqX.cjs → panel-Cpq1xHck.cjs} +5 -2
  137. package/dist-lib/chunks/{panel-DoNbjeqX.cjs.map → panel-Cpq1xHck.cjs.map} +1 -1
  138. package/dist-lib/index.cjs +1 -1
  139. package/dist-lib/index.mjs +2 -2
  140. package/dist-lib/preview/clientes.cjs +1 -1
  141. package/dist-lib/preview/clientes.mjs +1 -1
  142. package/dist-lib/src/components/ui/DataTable/data-table.d.ts.map +1 -1
  143. package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts +20 -2
  144. package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts.map +1 -1
  145. package/package.json +1 -1
  146. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/blueprint.md +0 -0
  147. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/entrevista.md +0 -0
  148. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/geracao.md +0 -0
  149. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/roteiro.md +0 -0
@@ -0,0 +1,174 @@
1
+ import { Cell, Pie, PieChart } from "recharts";
2
+ // Import por ARQUIVO, não pelo barrel `@/components/shadcn` — o barrel (`index.ts`) não é
3
+ // distribuído por nenhum item do registry, então um bloco que o importasse chegaria no
4
+ // consumidor de copy-in com import que não resolve. O gate `registry-imports` pega isso; foi
5
+ // ele que reprovou a primeira versão deste arquivo (L-037).
6
+ import {
7
+ Card,
8
+ CardHeader,
9
+ CardTitle,
10
+ CardDescription,
11
+ CardContent,
12
+ } from "@/components/shadcn/card";
13
+ import { Tabs, TabsList, TabsTrigger } from "@/components/shadcn/tabs";
14
+ import { Chip } from "@/components/ui/Chip";
15
+ import {
16
+ ChartContainer,
17
+ ChartTooltip,
18
+ ChartTooltipContent,
19
+ } from "@/components/ui/Chart";
20
+
21
+ /**
22
+ * dsgreen-chart-1 — donut de distribuição + lista de rateio no mesmo card.
23
+ *
24
+ * ## O que este bloco resolve
25
+ *
26
+ * A composição que a IA não monta sozinha: donut com **total no centro** e, abaixo,
27
+ * a **lista das mesmas fatias** onde a cor é a chave que liga linha ↔ setor. Pedindo
28
+ * "gráfico de pizza com os valores", sai o donut e uma legenda genérica ao lado; a
29
+ * leitura boa é esta — o olho vai ao total, depois desce a lista e usa a cor pra
30
+ * voltar ao setor.
31
+ *
32
+ * ## Regras do DS que este bloco carrega (e que copiar sem elas quebra)
33
+ *
34
+ * - **Cor só por token de chart** (`--color-chart-1..5`). Nunca hex, nunca paleta
35
+ * nativa do Tailwind. 5 fatias = as 5 chaves, na ordem.
36
+ * - **`fg-danger` NÃO entra como cor de categoria.** Ele é semântico de erro/queda
37
+ * (`chart-patterns.md` §2: "Status/uptime → down"). Usar vermelho de perigo numa
38
+ * categoria normal faz o leitor inferir problema onde não há.
39
+ * - **Valor numérico com `tabular-nums`**, senão os dígitos dançam entre linhas.
40
+ * - **Superfície = `Card` do DS.** Não recriar `<section>` com bg/shadow na unha.
41
+ *
42
+ * ## Cuidado ao adaptar
43
+ *
44
+ * As abas são **de composição visual**: elas mostram que o card cabe num contexto de
45
+ * 2+ recortes, mas não trocam dado nenhum aqui. Se o seu caso tem dois recortes de
46
+ * verdade, ligue-as a estado e troque o `data`; se tem um só, **remova-as** — controle
47
+ * que não faz nada é pior que controle ausente.
48
+ */
49
+ export const BLOCK = {
50
+ id: "dsgreen-chart-1",
51
+ nome: "Donut de distribuição com rateio",
52
+ descricao:
53
+ "Donut com total no centro + lista das fatias abaixo, onde a cor liga linha e setor. Para distribuição de um total por 3–5 categorias nomeadas.",
54
+ usa: ["Card (size=\"md\")", "Tabs", "Chip", "ChartContainer", "PieChart (recharts)"],
55
+ } as const;
56
+
57
+ /**
58
+ * Fixture do bloco. Fica aqui de propósito: bloco é auto-contido, então quem copia
59
+ * vê a forma do dado esperado sem ter que inferir do gráfico.
60
+ */
61
+ type BudgetRow = { readonly grupo: string; readonly valor: number; readonly cor: string };
62
+
63
+ const BUDGET: readonly BudgetRow[] = [
64
+ { grupo: "Engenharia", valor: 950, cor: "var(--color-chart-1)" },
65
+ { grupo: "Marketing", valor: 680, cor: "var(--color-chart-2)" },
66
+ { grupo: "Comercial", valor: 520, cor: "var(--color-chart-3)" },
67
+ { grupo: "Operações", valor: 310, cor: "var(--color-chart-4)" },
68
+ { grupo: "RH & Admin", valor: 195, cor: "var(--color-chart-5)" },
69
+ ];
70
+
71
+ const TOTAL = BUDGET.reduce((soma, linha) => soma + linha.valor, 0);
72
+
73
+ const brl = new Intl.NumberFormat("pt-BR", {
74
+ style: "currency",
75
+ currency: "BRL",
76
+ maximumFractionDigits: 0,
77
+ });
78
+
79
+ const share = (valor: number) => `${((valor / TOTAL) * 100).toFixed(1)}%`;
80
+
81
+ export function BudgetBreakdownBlock() {
82
+ // `size` cuida do padding de TODAS as partes do card — não escrever `px-*`/`p-0` na
83
+ // mão (era o que este bloco fazia antes do `Card` ganhar a prop, em 2026-08-19).
84
+ return (
85
+ <Card size="md" className="w-full max-w-[450px]">
86
+ <CardHeader>
87
+ <CardTitle>Distribuição de custo</CardTitle>
88
+ <CardDescription>
89
+ Rateio do gasto por área e categoria de custo.
90
+ </CardDescription>
91
+ </CardHeader>
92
+
93
+ {/* Cada seção vai em `CardContent` porque é ELE que carrega o padding
94
+ horizontal — o `Card` só carrega o vertical. `<div>` cru como filho direto
95
+ do Card encosta nas bordas, que é o que este bloco fazia por engano. */}
96
+ <CardContent>
97
+ {/* Ver o JSDoc: abas de composição. Ligue a estado ou remova. */}
98
+ <Tabs fullWidth defaultValue="area">
99
+ <TabsList>
100
+ <TabsTrigger value="area">
101
+ Por área
102
+ </TabsTrigger>
103
+ <TabsTrigger value="categoria">
104
+ Por categoria
105
+ </TabsTrigger>
106
+ </TabsList>
107
+ </Tabs>
108
+ </CardContent>
109
+
110
+ {/* Donut + total no centro. O overlay é pointer-events-none pra não roubar o
111
+ hover do setor — sem isso o tooltip do Recharts não abre no meio. */}
112
+ <CardContent className="relative mx-auto flex items-center justify-center">
113
+ <ChartContainer config={{}} className="aspect-square h-[210px]">
114
+ <PieChart>
115
+ <ChartTooltip
116
+ cursor={false}
117
+ content={<ChartTooltipContent hideLabel nameKey="grupo" />}
118
+ />
119
+ <Pie
120
+ data={BUDGET as BudgetRow[]}
121
+ dataKey="valor"
122
+ nameKey="grupo"
123
+ innerRadius={64}
124
+ outerRadius={92}
125
+ paddingAngle={3}
126
+ strokeWidth={0}
127
+ >
128
+ {BUDGET.map((linha) => (
129
+ <Cell key={linha.grupo} fill={linha.cor} />
130
+ ))}
131
+ </Pie>
132
+ </PieChart>
133
+ </ChartContainer>
134
+ <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
135
+ <span className="text-title-lg font-bold tabular-nums text-fg-default">
136
+ {brl.format(TOTAL)}
137
+ </span>
138
+ <span className="text-caption-sm text-fg-muted">gasto total</span>
139
+ </div>
140
+ </CardContent>
141
+
142
+ <CardContent>
143
+ <div className="mb-pad-md flex items-center justify-between text-caption-md text-fg-muted">
144
+ <span>ÁREA</span>
145
+ <span>VALOR / PARTICIPAÇÃO</span>
146
+ </div>
147
+ <div className="flex flex-col">
148
+ {BUDGET.map((linha) => (
149
+ <div
150
+ key={linha.grupo}
151
+ className="flex items-center gap-gp-md border-b border-border-subtle py-pad-lg last:border-0"
152
+ >
153
+ {/* A barrinha de cor é o que liga esta linha ao setor do donut. */}
154
+ <span
155
+ aria-hidden
156
+ className="h-[16px] w-[3px] rounded-radius-full"
157
+ style={{ background: linha.cor }}
158
+ />
159
+ <span className="flex-1 text-body-sm font-medium text-fg-default">
160
+ {linha.grupo}
161
+ </span>
162
+ <span className="text-body-sm font-semibold tabular-nums text-fg-default">
163
+ {brl.format(linha.valor)}
164
+ </span>
165
+ <Chip color="neutral" variant="soft" size="sm" shape="pill">
166
+ {share(linha.valor)}
167
+ </Chip>
168
+ </div>
169
+ ))}
170
+ </div>
171
+ </CardContent>
172
+ </Card>
173
+ );
174
+ }
@@ -0,0 +1,43 @@
1
+ <!-- GERADO por `npm run blocks:build` a partir dos `export const BLOCK` em src/blocks/**.
2
+ NÃO editar à mão: a próxima geração sobrescreve, e o gate `blocks-index` reprova divergência. -->
3
+
4
+ # Índice de blocos
5
+
6
+ **Bloco não é componente.** É uma composição de referência — feita só com componentes que você
7
+ já tem — que existe porque a IA sabe as peças e os tokens mas não sabe o **arranjo** que um
8
+ designer escolheu. Não tem props nem versão própria: você lê a estrutura e reconstrói com os
9
+ dados do usuário.
10
+
11
+ Este índice é lido pelo **Passo 0** da skill `ds-kit`, quando o usuário cita um código.
12
+
13
+ ## chart
14
+
15
+ | Código | Composição | Quando serve | Usa | Arquivo |
16
+ |---|---|---|---|---|
17
+ | `dsgreen-chart-1` | Donut de distribuição com rateio | Donut com total no centro + lista das fatias abaixo, onde a cor liga linha e setor. Para distribuição de um total por 3–5 categorias nomeadas. | Card (size="md") · Tabs · Chip · ChartContainer · PieChart (recharts) | `src/blocks/chart/budget-breakdown.tsx` |
18
+
19
+ ## paneldetail
20
+
21
+ | Código | Composição | Quando serve | Usa | Arquivo |
22
+ |---|---|---|---|---|
23
+ | `dsgreen-paneldetail-1` | Painel de detalhe do registro | Painel lateral aberto a partir de uma linha de tabela: header com avatar + nome + código e status, ações de ícone e maximizar; métricas em cards compactos; e os dados em seções colapsáveis — inclusive as que não são label:valor (conta bancária com marca, métodos em chips, e-mail e telefone acionáveis, gestor com avatar). Ação primária no footer. Mesmo padrão dos painéis de detalhe do Virtual Office e do CRUD de clientes. | FloatingPanel (side="right", size="lg", titleSlot + headerActions + maximizable + resizable + bodyPadded={false}) · FloatingPanelSection + FloatingPanelField · MetricaCartoes — cards compactos próprios (ícone + valor + rótulo); NÃO o Kpi · Avatar (colorHex, contraste WCAG automático) · Chip (soft) · Button (icon-sm no header, sm no footer) | `src/blocks/paneldetail/detalhe-do-registro.tsx` |
24
+ | `dsgreen-paneldetail-2` | Detalhe de tarefa com abas | Painel de detalhe de uma TAREFA: contexto (projeto/coluna) no header, título grande no corpo, propriedades em lista plana com ícone à esquerda e valor à direita, descrição, e abas pros conteúdos que crescem — subtarefas, anexos, comentários e atividade. | FloatingPanel (side="right", size="lg", titleSlot + headerActions + maximizable + resizable; body PADDED, sem Section) · Tabs (fullWidth, variante default) + TabsContent · Avatar (pilha com sobreposição) · Chip (soft) · Input · Button | `src/blocks/paneldetail/detalhe-de-tarefa-com-abas.tsx` |
25
+ | `dsgreen-paneldetail-3` | Detalhe largo com métricas e tabela | Painel de detalhe LARGO (720px): identidade e propriedades em duas colunas no topo, faixa de métricas do período, e uma tabela curta escopada no registro (últimos lançamentos) com navegação de período e exportar. Pra quando o detalhe inclui uma série de linhas — em 560px isso viraria scroll horizontal. | FloatingPanel (size="xl" 720px, titleSlot + headerActions + maximizable + resizable) · Table + TableHead/TableHeadCell/TableBody/TableRow/TableCell (o primitivo, NÃO o DataTable) · Avatar · Chip (soft) · Button (icon-sm e sm) | `src/blocks/paneldetail/detalhe-com-tabela.tsx` |
26
+
27
+ ## Como pegar o código do bloco
28
+
29
+ | modo de consumo | onde o arquivo está |
30
+ |---|---|
31
+ | **submódulo** (`.claude/ds-config.json` com `"mode": "submodule"`) | já no disco: `<dsPath>/<arquivo>` da tabela acima. Leia direto |
32
+ | **copy-in / scaffold** | não vem instalado: `npm run igreen:add -- <código>` traz o arquivo |
33
+
34
+ Leia o arquivo **inteiro**, incluindo o JSDoc do topo — ele carrega as regras que a composição
35
+ embute (qual token de cor, por que `tabular-nums`, o que NÃO copiar) e uma seção **Cuidado ao
36
+ adaptar** que diz o que ligar a estado e o que remover. Medido num consumidor real em
37
+ 2026-08-20: foi esse JSDoc que produziu o resultado bom, não só a resolução do ID.
38
+
39
+ Adapte dados e rótulos **preservando estrutura e espaçamento**. Não "melhore" o arranjo — ele é
40
+ o motivo pelo qual o código foi citado.
41
+
42
+ Se o código citado não estiver na tabela, **diga isso** em vez de montar algo parecido: o valor
43
+ do código é ser determinístico, e um palpite silencioso destrói exatamente isso.
@@ -0,0 +1,450 @@
1
+ import { useState } from "react";
2
+ import {
3
+ Briefcase,
4
+ Building2,
5
+ CalendarDays,
6
+ ChevronLeft,
7
+ ChevronRight,
8
+ Download,
9
+ FileText,
10
+ Mail,
11
+ MapPin,
12
+ Pencil,
13
+ Phone,
14
+ TrendingUp,
15
+ UserRound,
16
+ Users,
17
+ Zap,
18
+ } from "lucide-react";
19
+ import type { LucideIcon } from "@/lib/lucide-types";
20
+ import { Avatar } from "@/components/ui/avatar-ig";
21
+ import { Button } from "@/components/ui/Button";
22
+ import { Chip } from "@/components/ui/Chip";
23
+ import { FloatingPanel } from "@/components/ui/FloatingPanel";
24
+ import {
25
+ Table,
26
+ TableBody,
27
+ TableCell,
28
+ TableHead,
29
+ TableHeadCell,
30
+ TableRow,
31
+ } from "@/components/ui/Table";
32
+
33
+ export const BLOCK = {
34
+ id: "dsgreen-paneldetail-3",
35
+ nome: "Detalhe largo com métricas e tabela",
36
+ descricao:
37
+ "Painel de detalhe LARGO (720px): identidade e propriedades em duas colunas no topo, faixa de métricas do período, e uma tabela curta escopada no registro (últimos lançamentos) com navegação de período e exportar. Pra quando o detalhe inclui uma série de linhas — em 560px isso viraria scroll horizontal.",
38
+ usa: [
39
+ "FloatingPanel (size=\"xl\" 720px, titleSlot + headerActions + maximizable + resizable)",
40
+ "Table + TableHead/TableHeadCell/TableBody/TableRow/TableCell (o primitivo, NÃO o DataTable)",
41
+ "Avatar · Chip (soft) · Button (icon-sm e sm)",
42
+ ],
43
+ } as const;
44
+
45
+ /**
46
+ * dsgreen-paneldetail-3 — detalhe largo, com métricas e tabela.
47
+ *
48
+ * ## Quando este, e não o `-1` nem o `-2`
49
+ *
50
+ * O que decide é **se o detalhe contém uma série de linhas**:
51
+ *
52
+ * | | conteúdo | largura |
53
+ * |---|---|---|
54
+ * | `-1` | registro com muitos campos, em seções colapsáveis | `lg` (560px) |
55
+ * | `-2` | tarefa: poucas propriedades + abas de conteúdo que cresce | `lg` (560px) |
56
+ * | **`-3`** (este) | **campos + métricas + TABELA** escopada no registro | **`xl` (720px)** |
57
+ *
58
+ * ⚠️ **A largura não é gosto, é requisito da tabela.** Cinco colunas de dados precisam de
59
+ * ~640px úteis; em `lg` (560px, ~524 úteis) a mesma tabela entra em scroll horizontal — e
60
+ * tabela que rola de lado dentro de painel é pior que abrir a tela cheia, porque o usuário
61
+ * perde a coluna de referência ao rolar. Se não couber em `xl`, **o detalhe não é painel**: é
62
+ * tela própria.
63
+ *
64
+ * ## Por que `Table` e não `DataTable`
65
+ *
66
+ * O `DataTable` traz toolbar, filtros, visões salvas, paginação e virtualização. Dentro de um
67
+ * painel isso **compete com o painel**: dois níveis de filtro na tela, dois lugares de
68
+ * paginação, e o toolbar dele encostado no header do painel. Aqui a tabela é um **recorte
69
+ * curto e fechado** — os últimos N lançamentos do período — então o primitivo `Table` é o
70
+ * certo.
71
+ *
72
+ * Corolário: **se você precisar de filtro, ordenação persistente ou paginação nessa tabela,
73
+ * ela deixou de ser recorte.** Aí o lugar dela é uma tela com `DataTable`, e o painel volta a
74
+ * ser só o detalhe.
75
+ *
76
+ * ## O topo em duas colunas só existe por causa do `xl`
77
+ *
78
+ * São duas colunas de propriedades **de igual peso** (4 + 4), cada uma com ~320px em 720px —
79
+ * o que cabe rótulo de 104px + valor sem truncar. Não copie este arranjo pra um painel `lg`:
80
+ * ali cada coluna cairia pra ~250px e os valores começariam a quebrar. Em `lg` o desenho certo
81
+ * é o do `-2`: lista plana, uma propriedade por linha.
82
+ *
83
+ * ⚠️ E **nenhuma das duas leva card em volta.** Dado do mesmo nível não ganha superfície
84
+ * própria — ver a nota no `PROPRIEDADES`.
85
+ *
86
+ * ## Regras do DS que este bloco carrega (e que copiar sem elas quebra)
87
+ *
88
+ * - **`width` do `TableHeadCell` e do `TableCell` da mesma coluna têm que ser IGUAIS.** O
89
+ * `Table` do DS posiciona por largura declarada, não por `<colgroup>`: divergir desalinha
90
+ * header e corpo silenciosamente. Aqui as larguras vivem numa constante única (`COLUNAS`),
91
+ * que é o que impede a divergência.
92
+ * - **Faixa de métricas com `divide-x` + `divide-border-default`.** `divide-x` cru é só
93
+ * largura: sem classe de cor a divisória cai em `currentColor` (L-039 aplicada ao `divide`).
94
+ * - **Ação de ícone é `variant="soft"` + `aria-label`.** O maximize e o close que o
95
+ * `FloatingPanel` renderiza são `soft`+`secondary`; `ghost` no meio da fileira fica sem
96
+ * container e lê como desabilitada.
97
+ * - **Número e data com `tabular-nums`.** Numa tabela isso não é cosmético: sem ele os dígitos
98
+ * dançam entre as linhas e a coluna deixa de ser varrível.
99
+ * - **Status é `Chip`**, nunca `<span>` estilizado.
100
+ * - **Wrapper de `gap` é obrigatório no corpo.** O body do `FloatingPanel` **não tem gap entre
101
+ * filhos** (`row-gap: normal`, medido) — só padding. Sem o wrapper, topo, métricas e tabela
102
+ * ficam com 0px entre si. Não confunda com o `PanelBody` do `Panel`, que tem gap embutido.
103
+ *
104
+ * ## Cuidado ao adaptar
105
+ *
106
+ * - **O botão daqui é do EXEMPLO.** Na tela real quem abre o painel é a linha clicada.
107
+ * - **A navegação de período não é decorativa.** Se `‹ ›` não trocam o dado, tire — controle
108
+ * que não controla é pior que ausência de controle.
109
+ * - **`Exportar` exporta o RECORTE, não a base.** O usuário assume que o botão dentro do
110
+ * painel leva o que ele está vendo; exportar tudo é surpresa desagradável. Se o export é da
111
+ * base inteira, o botão pertence à tela, não ao painel.
112
+ * - **Linha da tabela clicável?** Só se levar a algum lugar. `TableRow` tem `onClick` e
113
+ * variante `clickable` — cursor de mão numa linha inerte é promessa falsa.
114
+ */
115
+
116
+ /** Fixture do bloco. Fica aqui de propósito: bloco é auto-contido, então quem copia vê a
117
+ * forma do dado esperado sem inferir do render. */
118
+ const LICENCIADO = {
119
+ nome: "Marina Duarte",
120
+ iniciais: "MD",
121
+ hex: "#0F766E",
122
+ codigo: "LIC-04812",
123
+ papel: "Licenciada Green",
124
+ status: "Ativa",
125
+ };
126
+
127
+ /**
128
+ * Propriedades em DUAS colunas de igual peso — é o desenho de ficha, e é o que o painel `xl`
129
+ * permite.
130
+ *
131
+ * ⚠️ A 1ª versão tinha um **card "Contato"** com borda e fundo à esquerda e a lista de
132
+ * propriedades à direita. Ficou ruim, e a razão é estrutural: aquele card criava uma segunda
133
+ * hierarquia visual pra dados do MESMO nível — e-mail não é mais importante que cargo. Card
134
+ * dentro do topo só se justifica pra algo que se destaque de verdade (um saldo, um alerta).
135
+ * Aqui e-mail e telefone são propriedades como as outras, com o mesmo ícone-rótulo-valor.
136
+ */
137
+ const PROPRIEDADES: { icone: LucideIcon; label: string; valor: React.ReactNode }[][] = [
138
+ [
139
+ { icone: Briefcase, label: "Cargo", valor: "Licenciada Green" },
140
+ { icone: Building2, label: "Escritório", valor: "Belo Horizonte — MG" },
141
+ { icone: MapPin, label: "Atuação", valor: "Zona Sul e Centro" },
142
+ { icone: CalendarDays, label: "Ativa desde", valor: <span className="tabular-nums">12/03/2024</span> },
143
+ ],
144
+ [
145
+ {
146
+ icone: Mail,
147
+ label: "E-mail",
148
+ valor: (
149
+ <a
150
+ href="mailto:marina.duarte@example.com"
151
+ className="truncate text-fg-brand hover:underline"
152
+ >
153
+ marina.duarte@example.com
154
+ </a>
155
+ ),
156
+ },
157
+ {
158
+ icone: Phone,
159
+ label: "Telefone",
160
+ valor: (
161
+ <a
162
+ href="tel:5531989012345"
163
+ className="tabular-nums text-fg-brand hover:underline"
164
+ >
165
+ +55 31 98901-2345
166
+ </a>
167
+ ),
168
+ },
169
+ { icone: UserRound, label: "Gestor", valor: "Aline Castro" },
170
+ { icone: FileText, label: "Contrato", valor: <span className="tabular-nums">CT-2024-0481</span> },
171
+ ],
172
+ ];
173
+
174
+ const METRICAS = [
175
+ { icone: Users, label: "Clientes ativos", valor: "64", tom: "brand" as const },
176
+ { icone: Zap, label: "kWh no mês", valor: "38.240", tom: "brand" as const },
177
+ { icone: TrendingUp, label: "Comissão", valor: "R$ 4.812", tom: "brand" as const },
178
+ { icone: CalendarDays, label: "Em análise", valor: "3", tom: "warning" as const },
179
+ ];
180
+
181
+ /**
182
+ * Larguras das colunas numa constante única — é isso que impede header e corpo de divergirem.
183
+ *
184
+ * Somam **678px**, que é exatamente a largura útil da linha num painel `xl`: 720 do painel
185
+ * − 18 de gutter de cada lado − 1 de borda de cada lado do container. Medido, não calculado
186
+ * de cabeça: a 1ª versão somava 644 e sobravam **34px vazios depois da coluna de Status**, que
187
+ * lêem como coluna faltando.
188
+ *
189
+ * **A folga vai sempre pra coluna de TEXTO** (`cliente`), nunca pra uma de número: número tem
190
+ * largura previsível e ganhar espaço não o ajuda; nome ganha caractere antes de truncar.
191
+ */
192
+ const COLUNAS = {
193
+ data: 92,
194
+ cliente: 170,
195
+ /* 128, não 112: a 112 o próprio HEADER truncava em "Distribuido…" — o rótulo é mais largo
196
+ que os valores ("Cemig", "Energisa"). Dimensione a coluna pelo MAIOR entre header e
197
+ conteúdo; header truncado lê como bug, não como economia de espaço. */
198
+ distribuidora: 128,
199
+ consumo: 80,
200
+ comissao: 104,
201
+ status: 104,
202
+ } as const;
203
+
204
+ const LANCAMENTOS = [
205
+ { data: "02/09", cliente: "Padaria Estrela do Sul", distribuidora: "Cemig", consumo: "1.240", comissao: "R$ 186,00", status: "Pago", tom: "success" as const },
206
+ { data: "05/09", cliente: "Mercearia Bom Preço", distribuidora: "Cemig", consumo: "890", comissao: "R$ 133,50", status: "Pago", tom: "success" as const },
207
+ { data: "08/09", cliente: "Oficina do Marcos", distribuidora: "Cemig", consumo: "1.470", comissao: "R$ 220,50", status: "Pago", tom: "success" as const },
208
+ { data: "11/09", cliente: "Auto Peças Duarte", distribuidora: "Energisa", consumo: "2.310", comissao: "R$ 346,50", status: "Em análise", tom: "warning" as const },
209
+ { data: "14/09", cliente: "Pet Shop Amigo Fiel", distribuidora: "Cemig", consumo: "620", comissao: "R$ 93,00", status: "Pago", tom: "success" as const },
210
+ { data: "17/09", cliente: "Studio Vitória", distribuidora: "Energisa", consumo: "540", comissao: "R$ 81,00", status: "Pago", tom: "success" as const },
211
+ { data: "21/09", cliente: "Mercado Central 24h", distribuidora: "Cemig", consumo: "4.180", comissao: "R$ 627,00", status: "Pago", tom: "success" as const },
212
+ { data: "23/09", cliente: "Clínica São Bento", distribuidora: "Cemig", consumo: "1.780", comissao: "R$ 267,00", status: "Em análise", tom: "warning" as const },
213
+ { data: "26/09", cliente: "Escola Novo Saber", distribuidora: "Energisa", consumo: "2.940", comissao: "R$ 441,00", status: "Em análise", tom: "warning" as const },
214
+ { data: "28/09", cliente: "Restaurante Aurora", distribuidora: "Cemig", consumo: "3.120", comissao: "R$ 468,00", status: "Atrasado", tom: "danger" as const },
215
+ ];
216
+
217
+ const COR_DO_TOM = {
218
+ brand: "text-fg-brand",
219
+ warning: "text-fg-warning",
220
+ danger: "text-fg-danger",
221
+ } as const;
222
+
223
+ /** Métrica compacta da faixa: ícone + valor na mesma linha, rótulo abaixo. */
224
+ function Metrica({
225
+ icone: Icone,
226
+ label,
227
+ valor,
228
+ tom,
229
+ }: {
230
+ icone: LucideIcon;
231
+ label: string;
232
+ valor: string;
233
+ tom: keyof typeof COR_DO_TOM;
234
+ }) {
235
+ return (
236
+ <div className="flex flex-col gap-gp-2xs p-pad-2xl">
237
+ <div className={`flex items-center gap-gp-sm ${COR_DO_TOM[tom]}`}>
238
+ <Icone className="size-icon-sm shrink-0" aria-hidden="true" />
239
+ <span className="text-body-xl font-bold tabular-nums leading-none">{valor}</span>
240
+ </div>
241
+ <span className="text-caption-sm text-fg-muted">{label}</span>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ export function DetalheComTabela() {
247
+ const [aberto, setAberto] = useState(false);
248
+
249
+ return (
250
+ <>
251
+ {/* Na tela real este botão não existe: quem abre o painel é a linha clicada. */}
252
+ <Button variant="outline" color="secondary" onClick={() => setAberto(true)}>
253
+ Abrir detalhe da licenciada
254
+ </Button>
255
+
256
+ <FloatingPanel
257
+ open={aberto}
258
+ onOpenChange={setAberto}
259
+ side="right"
260
+ /* `xl` = 720px. Requisito da tabela, não preferência — ver o JSDoc. */
261
+ size="xl"
262
+ resizable
263
+ maximizable
264
+ resizableStorageKey="dsgreen-paneldetail-3.width"
265
+ titleSlot={
266
+ <div className="flex min-w-0 items-center gap-gp-md">
267
+ <Avatar
268
+ size="lg"
269
+ colorHex={LICENCIADO.hex}
270
+ className="shrink-0"
271
+ aria-label={LICENCIADO.nome}
272
+ >
273
+ {LICENCIADO.iniciais}
274
+ </Avatar>
275
+ <div className="flex min-w-0 flex-col">
276
+ <span className="truncate text-body-md font-semibold text-fg-default">
277
+ {LICENCIADO.nome}
278
+ </span>
279
+ <span className="mt-[2px] flex items-center gap-gp-sm text-body-xs text-fg-muted">
280
+ <span className="tabular-nums">{LICENCIADO.codigo}</span>
281
+ <span className="opacity-50">·</span>
282
+ <Chip color="success" variant="soft" size="sm">
283
+ {LICENCIADO.status}
284
+ </Chip>
285
+ </span>
286
+ </div>
287
+ </div>
288
+ }
289
+ headerActions={
290
+ <Button variant="soft" color="secondary" size="icon-sm" aria-label="Editar licenciada">
291
+ <Pencil />
292
+ </Button>
293
+ }
294
+ footer={
295
+ <>
296
+ <Button
297
+ variant="outline"
298
+ color="secondary"
299
+ size="sm"
300
+ onClick={() => setAberto(false)}
301
+ >
302
+ Fechar
303
+ </Button>
304
+ <Button variant="filled" size="sm">
305
+ Abrir ficha completa
306
+ </Button>
307
+ </>
308
+ }
309
+ >
310
+ {/* Wrapper de gap OBRIGATÓRIO: o body do FloatingPanel não tem gap entre filhos. */}
311
+ <div className="flex flex-col gap-gp-3xl">
312
+ {/* 1. Ficha em duas colunas de igual peso — só cabe porque o painel é `xl`. Em `lg`
313
+ viraria lista plana de uma coluna (ver o `-2`). Sem card em volta: dado do
314
+ mesmo nível não ganha superfície própria. */}
315
+ <div className="grid grid-cols-2 gap-x-gp-4xl">
316
+ {PROPRIEDADES.map((coluna, i) => (
317
+ <div key={i} className="grid grid-cols-[104px_1fr] items-center gap-x-gp-md">
318
+ {coluna.map(({ icone: Icone, label, valor }) => (
319
+ <div key={label} className="contents">
320
+ {/* `min-h-form-sm` (32px) nas duas células: sem piso, a linha tem altura
321
+ do conteúdo e o ritmo da ficha fica irregular. */}
322
+ <div className="flex min-h-form-sm items-center gap-gp-sm text-body-sm text-fg-muted">
323
+ <Icone
324
+ className="size-icon-sm shrink-0 text-fg-subtle"
325
+ aria-hidden="true"
326
+ />
327
+ <span className="truncate">{label}</span>
328
+ </div>
329
+ <div className="flex min-h-form-sm min-w-0 items-center text-body-sm text-fg-default">
330
+ <span className="min-w-0 truncate">{valor}</span>
331
+ </div>
332
+ </div>
333
+ ))}
334
+ </div>
335
+ ))}
336
+ </div>
337
+
338
+ {/* 2. Faixa de métricas do período. `divide-border-default` é obrigatório: `divide-x`
339
+ cru é só largura e a divisória cairia em `currentColor` (L-039). */}
340
+ <div className="grid grid-cols-4 divide-x divide-border-default overflow-hidden rounded-radius-lg border border-border-default bg-bg-surface">
341
+ {METRICAS.map((m) => (
342
+ <Metrica key={m.label} {...m} />
343
+ ))}
344
+ </div>
345
+
346
+ {/* 3. Tabela do recorte: cabeçalho de período + exportar, e a série. */}
347
+ <div className="flex flex-col gap-gp-lg">
348
+ <div className="flex flex-wrap items-center justify-between gap-gp-md">
349
+ <div className="flex items-center gap-gp-md">
350
+ <span className="text-body-md font-semibold text-fg-default">
351
+ Setembro 2026
352
+ </span>
353
+ {/* Se `‹ ›` não trocarem o dado de verdade, tire: controle que não controla é
354
+ pior que ausência de controle. */}
355
+ <div className="flex items-center gap-gp-xs">
356
+ <Button
357
+ variant="soft"
358
+ color="secondary"
359
+ size="icon-xs"
360
+ aria-label="Mês anterior"
361
+ >
362
+ <ChevronLeft />
363
+ </Button>
364
+ <Button
365
+ variant="soft"
366
+ color="secondary"
367
+ size="icon-xs"
368
+ aria-label="Mês seguinte"
369
+ >
370
+ <ChevronRight />
371
+ </Button>
372
+ </div>
373
+ </div>
374
+ {/* Exporta o RECORTE (o mês visível), não a base — ver "Cuidado ao adaptar". */}
375
+ <Button variant="outline" color="secondary" size="sm" iconLeft={<Download />}>
376
+ Exportar mês
377
+ </Button>
378
+ </div>
379
+
380
+ {/* ⚠️ **Sem wrapper de borda aqui.** O `Table` do DS já É um card: ele traz
381
+ `bg-bg-table`, `border border-border-default`, raio de **14px** e
382
+ `overflow-hidden` no próprio container. Eu tinha envolvido ele num
383
+ `rounded-radius-lg border` (10px), e o resultado eram **dois raios
384
+ concêntricos** — 14px dentro de 10px — com o canto de dentro estourando o de
385
+ fora. Lê como bug de render, e é card-dentro-de-card: a mesma L-050 que proíbe
386
+ `PropsTable` dentro de `ExampleSection`.
387
+
388
+ Antes de embrulhar qualquer componente do DS numa superfície, verifique se ele
389
+ já tem a sua. */}
390
+ {/* `density="compact"` = linha de **40px** contra 56 do `standard`. É prop do
391
+ componente, não fonte reduzida na mão: a tipografia da tabela já é
392
+ `text-body-sm` (13px), o menor body do projeto. Numa série curta dentro de
393
+ painel, compact é o certo — cabe mais linha sem o corpo do painel rolar. */}
394
+ <Table density="compact" ariaLabel="Lançamentos do mês">
395
+ <TableHead>
396
+ <TableHeadCell field="data" width={COLUNAS.data}>
397
+ Data
398
+ </TableHeadCell>
399
+ <TableHeadCell field="cliente" width={COLUNAS.cliente}>
400
+ Cliente
401
+ </TableHeadCell>
402
+ <TableHeadCell field="distribuidora" width={COLUNAS.distribuidora}>
403
+ Distribuidora
404
+ </TableHeadCell>
405
+ <TableHeadCell field="consumo" width={COLUNAS.consumo} align="right">
406
+ kWh
407
+ </TableHeadCell>
408
+ <TableHeadCell field="comissao" width={COLUNAS.comissao} align="right">
409
+ Comissão
410
+ </TableHeadCell>
411
+ <TableHeadCell field="status" width={COLUNAS.status}>
412
+ Status
413
+ </TableHeadCell>
414
+ </TableHead>
415
+
416
+ <TableBody>
417
+ {LANCAMENTOS.map((l) => (
418
+ <TableRow key={l.data}>
419
+ {/* ⚠️ O `width` de cada célula tem que ser o MESMO do head — o Table do
420
+ DS posiciona por largura declarada. Daí a constante `COLUNAS`. */}
421
+ <TableCell field="data" width={COLUNAS.data}>
422
+ <span className="tabular-nums">{l.data}</span>
423
+ </TableCell>
424
+ <TableCell field="cliente" width={COLUNAS.cliente} ellipsis>
425
+ {l.cliente}
426
+ </TableCell>
427
+ <TableCell field="distribuidora" width={COLUNAS.distribuidora} ellipsis>
428
+ {l.distribuidora}
429
+ </TableCell>
430
+ <TableCell field="consumo" width={COLUNAS.consumo} align="right">
431
+ <span className="tabular-nums">{l.consumo}</span>
432
+ </TableCell>
433
+ <TableCell field="comissao" width={COLUNAS.comissao} align="right">
434
+ <span className="tabular-nums">{l.comissao}</span>
435
+ </TableCell>
436
+ <TableCell field="status" width={COLUNAS.status}>
437
+ <Chip color={l.tom} variant="soft" size="sm">
438
+ {l.status}
439
+ </Chip>
440
+ </TableCell>
441
+ </TableRow>
442
+ ))}
443
+ </TableBody>
444
+ </Table>
445
+ </div>
446
+ </div>
447
+ </FloatingPanel>
448
+ </>
449
+ );
450
+ }