@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,570 @@
1
+ import { useState } from "react";
2
+ import {
3
+ Calendar,
4
+ Check,
5
+ CircleDot,
6
+ Clock,
7
+ Download,
8
+ FileText,
9
+ Flag,
10
+ Link2,
11
+ Paperclip,
12
+ Pencil,
13
+ Plus,
14
+ SendHorizontal,
15
+ Tag,
16
+ Users,
17
+ } from "lucide-react";
18
+ import type { LucideIcon } from "@/lib/lucide-types";
19
+ // Import por ARQUIVO, não pelo barrel `@/components/shadcn` — o barrel não é distribuído por
20
+ // nenhum item do registry, então um bloco que o importasse chegaria no consumidor de copy-in
21
+ // com import que não resolve (L-037, gate `registry-imports`).
22
+ import { Input } from "@/components/shadcn/input";
23
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/shadcn/tabs";
24
+ import { Avatar } from "@/components/ui/avatar-ig";
25
+ import { Button } from "@/components/ui/Button";
26
+ import { Chip } from "@/components/ui/Chip";
27
+ import { FloatingPanel } from "@/components/ui/FloatingPanel";
28
+
29
+ export const BLOCK = {
30
+ id: "dsgreen-paneldetail-2",
31
+ nome: "Detalhe de tarefa com abas",
32
+ descricao:
33
+ "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.",
34
+ usa: [
35
+ "FloatingPanel (side=\"right\", size=\"lg\", titleSlot + headerActions + maximizable + resizable; body PADDED, sem Section)",
36
+ "Tabs (fullWidth, variante default) + TabsContent",
37
+ "Avatar (pilha com sobreposição) · Chip (soft) · Input · Button",
38
+ ],
39
+ } as const;
40
+
41
+ /**
42
+ * dsgreen-paneldetail-2 — detalhe de TAREFA, com abas.
43
+ *
44
+ * ## Por que existe, tendo o `dsgreen-paneldetail-1`
45
+ *
46
+ * Os dois são painel de detalhe e param aí a semelhança. O que muda é **o que o registro é**:
47
+ *
48
+ * | | `-1` registro (cliente, pedido) | `-2` tarefa (esta) |
49
+ * |---|---|---|
50
+ * | identidade | nome curto → cabe no header | título é uma FRASE → vai no corpo, grande; o header leva o **contexto** (projeto / coluna) |
51
+ * | dados | 20+ campos, agrupáveis por assunto | ~6 propriedades, e nenhuma agrupável |
52
+ * | estrutura | `FloatingPanelSection` colapsável | **lista plana** com ícone à esquerda |
53
+ * | abas | ⛔ não — o colapso já esconde | ✅ **sim** — ver abaixo |
54
+ *
55
+ * ## Aqui aba está certa, e no `-1` estava errada
56
+ *
57
+ * A regra não é "aba é ruim". É: **aba serve pra tipo de conteúdo, não pra mais campos.**
58
+ *
59
+ * No `-1` as abas escondiam seções de campos — e um corpo que já é pilha de seções
60
+ * colapsáveis já tem mecanismo de esconder; os dois juntos fazem o usuário procurar o dado em
61
+ * dois lugares.
62
+ *
63
+ * Aqui cada aba é uma **natureza diferente**: subtarefa é checklist, anexo é arquivo,
64
+ * comentário é conversa, atividade é log. Nenhuma é campo, nenhuma cabe na lista de
65
+ * propriedades, e todas **crescem sem limite** — 40 comentários não podem empurrar o próximo
66
+ * dado 40 linhas pra baixo. É exatamente o caso pra aba.
67
+ *
68
+ * ## O body é PADDED aqui — o oposto do `-1`
69
+ *
70
+ * ⚠️ Não copie o `bodyPadded={false}` do `-1`. Lá ele é obrigatório porque as
71
+ * `FloatingPanelSection` trazem padding próprio e divisória de ponta a ponta. **Aqui não há
72
+ * Section nenhuma**, então o default (`bodyPadded` = true, gutter de 18px) é o certo, e some a
73
+ * necessidade de qualquer margem negativa. Ligar `false` aqui colaria todo o conteúdo na borda.
74
+ *
75
+ * ## A lista de propriedades: por que grid e não `justify-between`
76
+ *
77
+ * `grid-cols-[132px_1fr]` alinha os valores numa **coluna única**, que é o que dá a leitura de
78
+ * ficha. Com `justify-between` cada valor encostaria na borda direita numa distância diferente
79
+ * (depende do tamanho do label), e a coluna desapareceria.
80
+ *
81
+ * Também não é `FloatingPanelField`: ele é `label ... valor` **sem ícone** e com o valor
82
+ * alinhado à direita. Aqui o ícone à esquerda do label é parte do desenho — ele é o que
83
+ * permite varrer a lista por tipo de propriedade sem ler.
84
+ *
85
+ * ## Regras do DS que este bloco carrega (e que copiar sem elas quebra)
86
+ *
87
+ * - **Aba dentro de painel vem `fullWidth`, na variante default (`segmented`).** Regra
88
+ * declarada no `USAGE.md` do `FloatingPanel`: em 560px o `line` vira um trilho curto que lê
89
+ * como fragmento. Com 4 abas o `fullWidth` dá ~130px cada — cabe.
90
+ * - **Ação de ícone é `variant="soft"` + `aria-label`.** O maximize e o close que o
91
+ * componente renderiza são `soft`+`secondary`; `ghost` no meio da fileira fica sem container
92
+ * e lê como desabilitada. E botão de ícone sem `aria-label` é anunciado só como "button".
93
+ * - **Status/prioridade/tag são `Chip`**, nunca `<span>` estilizado — o Chip já resolve tom,
94
+ * contraste e altura de linha.
95
+ * - **Data com `tabular-nums`**, senão os dígitos dançam entre as linhas da lista.
96
+ * - **Pilha de avatares:** `-ml-sp-sm` (6px, token) + `ring-2 ring-bg-surface` pra separar um
97
+ * do outro. O DS **não tem** `AvatarGroup` — se passar a ter, troque por ele.
98
+ * - **Subtarefa concluída = disco CHEIO**, feito com `<span bg-bg-success>` + `<Check>`, não
99
+ * com o `CircleCheck` do lucide: ele desenha o círculo com `stroke="currentColor"`, e no
100
+ * dark o traço (preto, porque `fg-on-success` inverte) fica por cima do preenchimento e come
101
+ * ~2px de verde de cada lado. Medido no `-1`, mesma solução aqui.
102
+ *
103
+ * ## Cuidado ao adaptar
104
+ *
105
+ * - **O botão daqui é do EXEMPLO.** No board real quem abre o painel é o card clicado:
106
+ * `open={!!tarefaSelecionada}`.
107
+ * - **Contagem na aba** (`Comentários 3`) só quando o número informa. "Atividade 47" não
108
+ * ajuda ninguém a decidir se clica.
109
+ * - **O campo de mensagem aqui é `Input` + botão.** O DS tem um `MessageComposer` bem mais
110
+ * completo (anexo, menção, template), mas ele **não está no registry** — um bloco que o
111
+ * importasse não resolveria no consumidor de copy-in. Se ele entrar no registry, troque.
112
+ * - **Não empilhe as 4 abas em telas estreitas.** Em mobile o `FloatingPanel` vira sheet e 4
113
+ * triggers de ~80px ficam ilegíveis: ali o certo é reduzir pra 2 (Comentários + resto num
114
+ * menu) ou virar tela própria.
115
+ */
116
+
117
+ /** Fixture do bloco. Fica aqui de propósito: bloco é auto-contido, então quem copia vê a
118
+ * forma do dado esperado sem inferir do render. */
119
+ const TAREFA = {
120
+ contexto: "Projeto UI/UX",
121
+ coluna: "Em revisão",
122
+ titulo: "Redesenhar o painel de analytics do dashboard",
123
+ descricao:
124
+ "Revisar a hierarquia dos KPIs e trocar os quatro cards do topo por uma faixa única. O gráfico de barras precisa caber ao lado do ranking sem quebrar em 1280px.",
125
+ };
126
+
127
+ const PROPRIEDADES: {
128
+ icone: LucideIcon;
129
+ label: string;
130
+ valor: React.ReactNode;
131
+ }[] = [
132
+ {
133
+ icone: CircleDot,
134
+ label: "Status",
135
+ valor: (
136
+ <Chip color="info" variant="soft" size="sm">
137
+ Em revisão
138
+ </Chip>
139
+ ),
140
+ },
141
+ {
142
+ icone: Flag,
143
+ label: "Prioridade",
144
+ valor: (
145
+ <Chip color="warning" variant="soft" size="sm">
146
+ Alta
147
+ </Chip>
148
+ ),
149
+ },
150
+ {
151
+ icone: Calendar,
152
+ label: "Prazo",
153
+ valor: <span className="tabular-nums">3 out 2026 — 12 out 2026</span>,
154
+ },
155
+ {
156
+ icone: Tag,
157
+ label: "Tags",
158
+ valor: (
159
+ <span className="flex flex-wrap items-center gap-gp-sm">
160
+ {["Dashboard", "Analytics", "Frontend"].map((t) => (
161
+ <Chip key={t} color="neutral" variant="soft" size="sm">
162
+ {t}
163
+ </Chip>
164
+ ))}
165
+ </span>
166
+ ),
167
+ },
168
+ {
169
+ icone: Users,
170
+ label: "Responsáveis",
171
+ valor: (
172
+ /* Pilha com sobreposição: `-ml-sp-sm` (6px, token) no 2º em diante + `ring` da cor da
173
+ superfície, que é o que separa um avatar do outro. Sem AvatarGroup no DS. */
174
+ <span className="flex items-center">
175
+ {[
176
+ { ini: "MD", hex: "#2563EB" },
177
+ { ini: "AC", hex: "#CC092F" },
178
+ { ini: "JS", hex: "#7C3AED" },
179
+ ].map((p, i) => (
180
+ <Avatar
181
+ key={p.ini}
182
+ size="sm"
183
+ colorHex={p.hex}
184
+ aria-label={p.ini}
185
+ className={i > 0 ? "-ml-sp-sm ring-2 ring-bg-surface" : "ring-2 ring-bg-surface"}
186
+ >
187
+ {p.ini}
188
+ </Avatar>
189
+ ))}
190
+ </span>
191
+ ),
192
+ },
193
+ {
194
+ icone: Clock,
195
+ label: "Criada em",
196
+ valor: <span className="tabular-nums">20 set 2026, 10:35</span>,
197
+ },
198
+ ];
199
+
200
+ const SUBTAREFAS = [
201
+ { label: "Levantar os KPIs que o time realmente usa", feita: true },
202
+ { label: "Definir a faixa única no lugar dos 4 cards", feita: true },
203
+ { label: "Validar o gráfico ao lado do ranking em 1280px", feita: false },
204
+ { label: "Revisar com o time de dados", feita: false },
205
+ ];
206
+
207
+ const ANEXOS = [
208
+ { nome: "Briefing do redesign.pdf", tamanho: "1,5 MB", quando: "24 set 2026" },
209
+ { nome: "Medições de 1280px.png", tamanho: "820 KB", quando: "26 set 2026" },
210
+ ];
211
+
212
+ const COMENTARIOS = [
213
+ {
214
+ autor: "Terry James",
215
+ iniciais: "TJ",
216
+ hex: "#2563EB",
217
+ quando: "sex, 14:20",
218
+ texto:
219
+ "A faixa única resolve a hierarquia, mas o ranking fica sem respiro em 1280px. Consegue medir antes de fechar?",
220
+ reacoes: "2",
221
+ },
222
+ {
223
+ autor: "Aline Castro",
224
+ iniciais: "AC",
225
+ hex: "#CC092F",
226
+ quando: "sex, 15:02",
227
+ texto: "Medido: 148px pro ranking. Cabe, mas com o título em duas linhas.",
228
+ reacoes: null,
229
+ },
230
+ ];
231
+
232
+ /** Log agrupado por DIA. O agrupamento é do dado, não do render: quem monta a lista já sabe
233
+ * a que dia cada evento pertence, e derivar isso no componente esconderia a regra de fuso. */
234
+ const ATIVIDADE = [
235
+ {
236
+ dia: "Hoje",
237
+ eventos: [
238
+ { autor: "Talan Korsgaard", iniciais: "TK", hex: "#7C3AED", texto: "mudou o status de Em progresso para Em revisão", quando: "10:45" },
239
+ { autor: "Davis Donin", iniciais: "DD", hex: "#0891B2", texto: "anexou Medições de 1280px.png", quando: "10:20" },
240
+ { autor: "Aline Castro", iniciais: "AC", hex: "#CC092F", texto: "concluiu a subtarefa Definir a faixa única", quando: "09:58" },
241
+ ],
242
+ },
243
+ {
244
+ dia: "Ontem",
245
+ eventos: [
246
+ { autor: "Terry James", iniciais: "TJ", hex: "#2563EB", texto: "comentou sobre o respiro do ranking em 1280px", quando: "14:20" },
247
+ { autor: "Aline Castro", iniciais: "AC", hex: "#CC092F", texto: "adicionou a tag Analytics", quando: "11:07" },
248
+ ],
249
+ },
250
+ {
251
+ dia: "24 set 2026",
252
+ eventos: [
253
+ { autor: "Davis Donin", iniciais: "DD", hex: "#0891B2", texto: "anexou Briefing do redesign.pdf", quando: "16:32" },
254
+ { autor: "Talan Korsgaard", iniciais: "TK", hex: "#7C3AED", texto: "definiu o prazo para 12 out 2026", quando: "16:10" },
255
+ { autor: "Talan Korsgaard", iniciais: "TK", hex: "#7C3AED", texto: "criou a tarefa", quando: "16:04" },
256
+ ],
257
+ },
258
+ ];
259
+
260
+ /** Linha da lista de propriedades: ícone à esquerda, label, e o valor na coluna da direita. */
261
+ function Propriedade({
262
+ icone: Icone,
263
+ label,
264
+ valor,
265
+ }: {
266
+ icone: LucideIcon;
267
+ label: string;
268
+ valor: React.ReactNode;
269
+ }) {
270
+ return (
271
+ <>
272
+ {/* `min-h-form-md` (36px) nas DUAS células. Sem isso a linha tem altura do conteúdo, e
273
+ medido dá 30px na de texto puro contra 36px na que tem `Chip` — a lista fica com
274
+ ritmo irregular. 36 é o piso natural: é a altura que o Chip já impõe, então nada
275
+ cresce, só as menores igualam. */}
276
+ <div className="flex min-h-form-md items-center gap-gp-md text-body-sm text-fg-muted">
277
+ <Icone className="size-icon-sm shrink-0 text-fg-subtle" aria-hidden="true" />
278
+ <span className="truncate">{label}</span>
279
+ </div>
280
+ <div className="flex min-h-form-md min-w-0 items-center text-body-sm text-fg-default">
281
+ {valor}
282
+ </div>
283
+ </>
284
+ );
285
+ }
286
+
287
+ export function DetalheDeTarefaComAbas() {
288
+ const [aberto, setAberto] = useState(false);
289
+ const [aba, setAba] = useState("subtarefas");
290
+ const feitas = SUBTAREFAS.filter((s) => s.feita).length;
291
+
292
+ return (
293
+ <>
294
+ {/* No board real este botão não existe: quem abre o painel é o card clicado. */}
295
+ <Button variant="outline" color="secondary" onClick={() => setAberto(true)}>
296
+ Abrir detalhe da tarefa
297
+ </Button>
298
+
299
+ <FloatingPanel
300
+ open={aberto}
301
+ onOpenChange={setAberto}
302
+ side="right"
303
+ size="lg"
304
+ resizable
305
+ maximizable
306
+ resizableStorageKey="dsgreen-paneldetail-2.width"
307
+ /* Header leva o CONTEXTO, não a identidade: título de tarefa é uma frase e não cabe
308
+ numa linha de header. Quem identifica aqui é "onde a tarefa está". */
309
+ titleSlot={
310
+ <div className="flex min-w-0 items-center gap-gp-sm text-body-sm text-fg-muted">
311
+ <span className="truncate">{TAREFA.contexto}</span>
312
+ <span className="opacity-50">/</span>
313
+ <span className="truncate font-medium text-fg-default">{TAREFA.coluna}</span>
314
+ </div>
315
+ }
316
+ headerActions={
317
+ <>
318
+ <Button variant="soft" color="secondary" size="icon-sm" aria-label="Editar tarefa">
319
+ <Pencil />
320
+ </Button>
321
+ <Button variant="soft" color="secondary" size="icon-sm" aria-label="Copiar link">
322
+ <Link2 />
323
+ </Button>
324
+ </>
325
+ }
326
+ footer={
327
+ <>
328
+ <Button
329
+ variant="outline"
330
+ color="secondary"
331
+ size="sm"
332
+ onClick={() => setAberto(false)}
333
+ >
334
+ Fechar
335
+ </Button>
336
+ <Button variant="filled" size="sm" iconLeft={<Check />}>
337
+ Concluir tarefa
338
+ </Button>
339
+ </>
340
+ }
341
+ >
342
+ {/* ⚠️ Este wrapper de gap é OBRIGATÓRIO, e não é preferência: o body do
343
+ `FloatingPanel` **não tem gap entre filhos** (`row-gap: normal`, medido) — só
344
+ padding. Sem ele, título, lista, descrição e abas ficam com **0px** entre si, todos
345
+ colados. Não confunda com o `PanelBody` do `Panel`, que tem `gap-gp-3xl` embutido:
346
+ é a diferença que fez a 1ª versão deste bloco sair grudada. */}
347
+ <div className="flex flex-col gap-gp-2xl">
348
+ {/* Título no CORPO, grande. `text-balance` evita a última linha órfã de uma palavra. */}
349
+ <h2 className="text-title-lg text-balance text-fg-default">{TAREFA.titulo}</h2>
350
+
351
+ {/* Lista plana de propriedades. O grid alinha todos os valores numa coluna só — é
352
+ isso que dá leitura de ficha; `justify-between` desalinharia por tamanho de
353
+ label. */}
354
+ <div className="grid grid-cols-[132px_1fr] items-center gap-x-gp-md">
355
+ {PROPRIEDADES.map((p) => (
356
+ <Propriedade key={p.label} {...p} />
357
+ ))}
358
+ </div>
359
+
360
+ {/* `bg-bg-surface`, não `bg-bg-subtle`: medido no dark, o subtle dá
361
+ `oklch(1 0 0 / 0.01)` — 1% de branco sobre o painel, invisível. O surface é
362
+ superfície de verdade e destaca o bloco de texto do resto da ficha. */}
363
+ <div className="flex flex-col gap-gp-md rounded-radius-lg border border-border-default bg-bg-surface p-pad-2xl">
364
+ <span className="text-body-xs font-semibold text-fg-muted">Descrição</span>
365
+ <p className="text-body-sm text-fg-default">{TAREFA.descricao}</p>
366
+ </div>
367
+ </div>
368
+
369
+ {/* Abas: cada uma é uma NATUREZA de conteúdo que cresce sem limite — não são campos.
370
+ É a diferença que justifica aba aqui e a proíbe no `dsgreen-paneldetail-1`. */}
371
+ <Tabs value={aba} onValueChange={setAba} fullWidth className="mt-gp-2xl">
372
+ <TabsList>
373
+ <TabsTrigger value="subtarefas">Subtarefas</TabsTrigger>
374
+ <TabsTrigger value="anexos">Anexos</TabsTrigger>
375
+ <TabsTrigger value="comentarios">Comentários</TabsTrigger>
376
+ <TabsTrigger value="atividade">Atividade</TabsTrigger>
377
+ </TabsList>
378
+
379
+ <TabsContent value="subtarefas" className="flex flex-col gap-gp-lg pt-pad-xl">
380
+ <div className="flex items-center justify-between text-body-xs text-fg-muted">
381
+ <span className="font-semibold text-fg-default">Progresso</span>
382
+ <span className="tabular-nums">
383
+ {feitas}/{SUBTAREFAS.length}
384
+ </span>
385
+ </div>
386
+ {/* Timeline: a linha vertical liga um marcador ao próximo. Ela é um `<span>` que
387
+ cresce (`flex-1`) numa coluna ao lado do marcador — NÃO um `before:` com
388
+ `left-[7px]`, que seria valor na unha e desalinharia se o marcador mudar de
389
+ tamanho. O `gap-0` no `<ul>` é o que mantém a linha contínua; o respiro entre
390
+ itens vem do `pb` do texto. */}
391
+ <ul className="flex flex-col gap-0">
392
+ {SUBTAREFAS.map((s, i) => {
393
+ const ultima = i === SUBTAREFAS.length - 1;
394
+ return (
395
+ <li key={s.label} className="flex gap-gp-md text-body-sm">
396
+ <div className="flex flex-col items-center">
397
+ {s.feita ? (
398
+ /* Disco CHEIO: `<span>` + `<Check>`, não o `CircleCheck` do lucide — o
399
+ stroke dele fica por cima do fill e come o disco no dark. */
400
+ <span
401
+ className="grid size-icon-sm shrink-0 place-items-center rounded-radius-full bg-bg-success text-fg-on-success"
402
+ aria-hidden="true"
403
+ >
404
+ <Check className="size-icon-2xs" strokeWidth={3} />
405
+ </span>
406
+ ) : (
407
+ <span
408
+ className="size-icon-sm shrink-0 rounded-radius-full border border-border-input"
409
+ aria-hidden="true"
410
+ />
411
+ )}
412
+ {!ultima && (
413
+ <span
414
+ className="mt-gp-2xs w-px flex-1 bg-border-default"
415
+ aria-hidden="true"
416
+ />
417
+ )}
418
+ </div>
419
+ <span
420
+ className={`${ultima ? "" : "pb-pad-xl"} ${
421
+ s.feita ? "text-fg-muted line-through" : "text-fg-default"
422
+ }`}
423
+ >
424
+ {s.label}
425
+ </span>
426
+ </li>
427
+ );
428
+ })}
429
+ </ul>
430
+ </TabsContent>
431
+
432
+ <TabsContent value="anexos" className="flex flex-col gap-gp-md pt-pad-xl">
433
+ {ANEXOS.map((a) => (
434
+ <div
435
+ key={a.nome}
436
+ className="flex items-center gap-gp-md rounded-radius-lg border border-border-default bg-bg-surface p-pad-xl"
437
+ >
438
+ <FileText className="size-icon-md shrink-0 text-fg-subtle" aria-hidden="true" />
439
+ <div className="flex min-w-0 flex-1 flex-col">
440
+ <span className="truncate text-body-sm text-fg-default">{a.nome}</span>
441
+ {/* Tamanho e data na MESMA linha secundária, separados por `·`. Duas linhas
442
+ aqui esticariam a altura do anexo sem ganhar leitura — os dois são
443
+ metadado do mesmo arquivo. */}
444
+ <span className="text-caption-sm tabular-nums text-fg-muted">
445
+ {a.tamanho} · {a.quando}
446
+ </span>
447
+ </div>
448
+ <Button
449
+ variant="ghost"
450
+ color="secondary"
451
+ size="icon-sm"
452
+ aria-label={`Baixar ${a.nome}`}
453
+ >
454
+ <Download />
455
+ </Button>
456
+ </div>
457
+ ))}
458
+ <Button variant="outline" color="secondary" size="sm" iconLeft={<Plus />}>
459
+ Anexar arquivo
460
+ </Button>
461
+ </TabsContent>
462
+
463
+ <TabsContent value="comentarios" className="flex flex-col gap-gp-2xl pt-pad-xl">
464
+ <ul className="flex flex-col gap-gp-2xl">
465
+ {COMENTARIOS.map((c) => (
466
+ <li key={c.quando} className="flex gap-gp-md">
467
+ <Avatar
468
+ size="md"
469
+ colorHex={c.hex}
470
+ className="shrink-0"
471
+ aria-label={c.autor}
472
+ >
473
+ {c.iniciais}
474
+ </Avatar>
475
+ <div className="flex min-w-0 flex-col gap-gp-2xs">
476
+ <span className="flex flex-wrap items-baseline gap-gp-sm">
477
+ <span className="text-body-sm font-semibold text-fg-default">
478
+ {c.autor}
479
+ </span>
480
+ <span className="text-caption-sm tabular-nums text-fg-muted">
481
+ {c.quando}
482
+ </span>
483
+ </span>
484
+ <p className="text-body-sm text-fg-default">{c.texto}</p>
485
+ {c.reacoes && (
486
+ <span className="mt-gp-2xs w-fit">
487
+ <Chip color="neutral" variant="soft" size="sm">
488
+ 👍 {c.reacoes}
489
+ </Chip>
490
+ </span>
491
+ )}
492
+ </div>
493
+ </li>
494
+ ))}
495
+ </ul>
496
+ {/* Campo de mensagem: `Input` + botão de enviar. O `MessageComposer` do DS é mais
497
+ completo mas NÃO está no registry — ver "Cuidado ao adaptar". */}
498
+ <div className="flex items-center gap-gp-md">
499
+ <Input placeholder="Escreva um comentário…" aria-label="Novo comentário" />
500
+ <Button
501
+ variant="soft"
502
+ color="secondary"
503
+ size="icon-sm"
504
+ aria-label="Anexar ao comentário"
505
+ >
506
+ <Paperclip />
507
+ </Button>
508
+ <Button variant="filled" size="icon-sm" aria-label="Enviar comentário">
509
+ <SendHorizontal />
510
+ </Button>
511
+ </div>
512
+ </TabsContent>
513
+
514
+ <TabsContent value="atividade" className="flex flex-col gap-gp-2xl pt-pad-xl">
515
+ {ATIVIDADE.map((grupo) => (
516
+ <div key={grupo.dia} className="flex flex-col gap-gp-md">
517
+ {/* Separador de dia no padrão de conversa: fio · badge · fio. Centralizado
518
+ porque ele divide o fluxo, não rotula o item de baixo — encostado à
519
+ esquerda ele leria como título do primeiro evento.
520
+
521
+ O dia é um `Chip`, não texto solto: com 3 grupos na mesma rolagem, texto
522
+ fino do mesmo tamanho do resto some no meio do log. O badge dá o degrau
523
+ visual que separa bloco de bloco.
524
+
525
+ ⚠️ Os fios são `aria-hidden`, o Chip NÃO. A 1ª versão tinha `aria-hidden`
526
+ no container inteiro e escondia a data — e é ela que agrupa o log: sem
527
+ ela, no leitor de tela os 8 eventos viram uma lista corrida sem dia. */}
528
+ <div className="flex items-center gap-gp-md">
529
+ <span className="h-px flex-1 bg-border-subtle" aria-hidden="true" />
530
+ <Chip color="neutral" variant="soft" size="sm">
531
+ {grupo.dia}
532
+ </Chip>
533
+ <span className="h-px flex-1 bg-border-subtle" aria-hidden="true" />
534
+ </div>
535
+
536
+ <ul className="flex flex-col">
537
+ {grupo.eventos.map((a) => (
538
+ <li
539
+ key={a.autor + a.quando}
540
+ /* Divisória por evento: log é lista de linhas parecidas, e sem
541
+ separação elas viram parágrafo corrido. A última do grupo não leva,
542
+ porque o separador de dia já fecha o bloco. */
543
+ className="flex items-start gap-gp-md border-b border-border-subtle py-pad-lg first:pt-0 last:border-b-0 last:pb-0"
544
+ >
545
+ <Avatar
546
+ size="sm"
547
+ colorHex={a.hex}
548
+ className="shrink-0"
549
+ aria-label={a.autor}
550
+ >
551
+ {a.iniciais}
552
+ </Avatar>
553
+ <p className="min-w-0 flex-1 text-body-sm text-fg-muted">
554
+ <span className="font-semibold text-fg-default">{a.autor}</span>{" "}
555
+ {a.texto}
556
+ </p>
557
+ <span className="shrink-0 text-caption-sm tabular-nums text-fg-muted">
558
+ {a.quando}
559
+ </span>
560
+ </li>
561
+ ))}
562
+ </ul>
563
+ </div>
564
+ ))}
565
+ </TabsContent>
566
+ </Tabs>
567
+ </FloatingPanel>
568
+ </>
569
+ );
570
+ }