@snksergio/design-system 0.61.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) 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/AlertModal.md +47 -0
  7. package/dist-lib/ai/componentes/AppShell.md +117 -0
  8. package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
  9. package/dist-lib/ai/componentes/Button.md +116 -0
  10. package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
  11. package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
  12. package/dist-lib/ai/componentes/CardOption.md +133 -0
  13. package/dist-lib/ai/componentes/Chart.md +93 -0
  14. package/dist-lib/ai/componentes/Chip.md +68 -0
  15. package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
  16. package/dist-lib/ai/componentes/ColorPicker.md +70 -0
  17. package/dist-lib/ai/componentes/Combobox.md +51 -0
  18. package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
  19. package/dist-lib/ai/componentes/DataList.md +111 -0
  20. package/dist-lib/ai/componentes/DataTable.md +867 -0
  21. package/dist-lib/ai/componentes/DatePicker.md +84 -0
  22. package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
  23. package/dist-lib/ai/componentes/EmptyState.md +72 -0
  24. package/dist-lib/ai/componentes/FileUploadField.md +95 -0
  25. package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
  26. package/dist-lib/ai/componentes/FooterTable.md +62 -0
  27. package/dist-lib/ai/componentes/FormField.md +110 -0
  28. package/dist-lib/ai/componentes/Gantt.md +552 -0
  29. package/dist-lib/ai/componentes/Header.md +98 -0
  30. package/dist-lib/ai/componentes/Icon.md +65 -0
  31. package/dist-lib/ai/componentes/Kanban.md +343 -0
  32. package/dist-lib/ai/componentes/Kpi.md +103 -0
  33. package/dist-lib/ai/componentes/List.md +61 -0
  34. package/dist-lib/ai/componentes/MarkdownText.md +59 -0
  35. package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
  36. package/dist-lib/ai/componentes/MessageAck.md +53 -0
  37. package/dist-lib/ai/componentes/MessageBubble.md +115 -0
  38. package/dist-lib/ai/componentes/MessageComposer.md +80 -0
  39. package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
  40. package/dist-lib/ai/componentes/Modal.md +88 -0
  41. package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
  42. package/dist-lib/ai/componentes/PageHeader.md +129 -0
  43. package/dist-lib/ai/componentes/Panel.md +84 -0
  44. package/dist-lib/ai/componentes/Scheduler.md +421 -0
  45. package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
  46. package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
  47. package/dist-lib/ai/componentes/Spinner.md +52 -0
  48. package/dist-lib/ai/componentes/Table.md +192 -0
  49. package/dist-lib/ai/componentes/TableToolbar.md +87 -0
  50. package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
  51. package/dist-lib/ai/componentes/Toast.md +49 -0
  52. package/dist-lib/ai/componentes/_primitivos.md +74 -0
  53. package/dist-lib/ai/componentes/avatar-ig.md +181 -0
  54. package/dist-lib/ai/componentes/indice.json +49 -0
  55. package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
  56. package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
  57. package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
  58. package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
  59. package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
  60. package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
  61. package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
  62. package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
  63. package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
  64. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
  65. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
  66. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
  67. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
  68. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
  69. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
  70. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
  71. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
  72. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
  73. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
  74. package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
  75. package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
  76. package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
  77. package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
  78. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
  79. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
  80. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
  81. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
  82. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
  83. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
  84. package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
  85. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
  86. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
  87. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
  88. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
  89. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
  90. package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
  91. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
  92. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
  93. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
  94. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
  95. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
  96. package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
  97. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
  98. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
  99. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
  100. package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
  101. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
  102. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
  103. package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
  104. package/dist-lib/ai/exemplos/chat/index.ts +1 -0
  105. package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
  106. package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
  107. package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
  108. package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
  109. package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
  110. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
  111. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
  112. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
  113. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
  114. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
  115. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  116. package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
  117. package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
  118. package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
  119. package/dist-lib/ai/exemplos/dashboard/index.ts +1 -0
  120. package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
  121. package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
  122. package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
  123. package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
  124. package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
  125. package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
  126. package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
  127. package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
  128. package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
  129. package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
  130. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
  131. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
  132. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
  133. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
  134. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
  135. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
  136. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
  137. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  138. package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
  139. package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
  140. package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
  141. package/dist-lib/ai/exemplos/finance/index.ts +1 -0
  142. package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
  143. package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
  144. package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
  145. package/dist-lib/ai/exemplos/login/index.ts +1 -0
  146. package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
  147. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
  148. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
  149. package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
  150. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
  151. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
  152. package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
  153. package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
  154. package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
  155. package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
  156. package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
  157. package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
  158. package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
  159. package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
  160. package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
  161. package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
  162. package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
  163. package/dist-lib/ai/global/componentes.md +217 -0
  164. package/dist-lib/ai/global/composicao.md +182 -0
  165. package/dist-lib/ai/indice.json +192 -0
  166. package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
  167. package/dist-lib/ai/manifest.json +42 -0
  168. package/dist-lib/ai/regras/design.md +88 -0
  169. package/dist-lib/ai/regras/temas.md +192 -0
  170. package/dist-lib/ai/regras-por-componente.json +103 -0
  171. package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
  172. package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
  173. package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
  174. package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
  175. package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
  176. package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
  177. package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
  178. package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
  179. package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
  180. package/dist-lib/ai/roteiros/dashboard-builder/blueprint.md +47 -0
  181. package/dist-lib/ai/roteiros/dashboard-builder/entrevista.md +62 -0
  182. package/dist-lib/ai/roteiros/dashboard-builder/geracao.md +88 -0
  183. package/dist-lib/ai/roteiros/dashboard-builder/roteiro.md +88 -0
  184. package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
  185. package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
  186. package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
  187. package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
  188. package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
  189. package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
  190. package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
  191. package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
  192. package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
  193. package/package.json +4 -1
@@ -0,0 +1,552 @@
1
+ # Gantt
2
+
3
+ <!-- ds:regras
4
+ - monte a tela a partir do `example-gantt` (`igreen:add example-gantt`) — é o comportamento COMPLETO, não um toy
5
+ - o pai precisa ter ALTURA (o componente é `h-full`): sem isso você vê só a toolbar
6
+ - busca do servidor → passe `loading`, senão `rows={[]}` afirma "Nenhuma tarefa neste período"
7
+ - `colorKey` diz CATEGORIA (qual frente), não status: status vai em `row.trailing` como `Chip`
8
+ -->
9
+
10
+ Cronograma de projeto: hierarquia de tarefas à esquerda, tempo à direita, e
11
+ **vínculos** entre as barras. Categoria: data-display.
12
+
13
+ ## Quando usar
14
+
15
+ Quando a pergunta é **"o que depende de quê"**. Sem vínculo isto é uma timeline,
16
+ não um Gantt — e o DS já tem timeline.
17
+
18
+ | Precisa de | Use |
19
+ |---|---|
20
+ | escolher **uma data** num formulário | `DatePicker` · `Calendar` · `MonthYearPicker` |
21
+ | mostrar **quando** algo acontece (compromisso, reserva, agenda) | `Scheduler` |
22
+ | mostrar **o que bloqueia o quê**, com hierarquia e progresso | **`Gantt`** |
23
+ | linhas de registro sem eixo de tempo | `DataTable` · `DataList` |
24
+
25
+ ## Import
26
+
27
+ ```tsx
28
+ import { Gantt } from "@/components/ui/Gantt";
29
+ import type { GanttRow, GanttLink, GanttColumn } from "@/components/ui/Gantt";
30
+ ```
31
+
32
+ ## ⭐ A referência é o `example-gantt`, não este arquivo
33
+
34
+ ```bash
35
+ npx igreen:add example-gantt
36
+ ```
37
+
38
+ **Antes de montar uma tela com o `Gantt`, comece por aqui.** O `example-gantt` é a
39
+ extração 1:1 do showcase `#/gantt-full` do DS — não é um toy, é a tela que valida
40
+ o componente a fundo, e ela é a fonte de verdade do **comportamento**. Este
41
+ `USAGE.md` descreve a API; o exemplo mostra como as peças se comportam **juntas**,
42
+ que é onde as decisões erradas aparecem.
43
+
44
+ O que ele exercita, e que um exemplo mínimo não mostra:
45
+
46
+ | | |
47
+ |---|---|
48
+ | **dado real** | 20 linhas, 4 níveis de hierarquia, `summary` derivado, marco, progresso |
49
+ | **grafo real** | 16 vínculos dos 4 tipos com `lag`, **2 conflitos** (um deles não foi plantado — as datas foram escritas e o componente achou) e caminho crítico |
50
+ | **os 4 gestos aplicando** | `rows`/`links` são estado da tela: arrastar move e a barra **fica**; criar e remover vínculo funcionam |
51
+ | **as 3 visões** | o mesmo dado em `timeline`, `calendar` e `list` |
52
+ | **filtro nos 6 `kind`** | com painel lateral e chips de aplicado |
53
+ | **o que é DA TELA** | painel de detalhe (`onBarClick`) e drawer de nova tarefa (`onDayAdd`) — o componente só emite |
54
+
55
+ ⚠️ **O ponto mais fácil de errar** está lá resolvido: `draggable`/`resizable`
56
+ ligados **sem handler que aplique** fazem o usuário arrastar e ver a barra
57
+ **voltar**. No exemplo os handlers reescrevem o estado — copie essa parte.
58
+
59
+ O arquivo `_gantt-data.tsx` é o que você troca primeiro; `gantt-screen.tsx` tem um
60
+ cabeçalho "Cuidado ao adaptar" dizendo o que ligar ao seu estado, o que remover se
61
+ não servir e o que **não** mexer.
62
+
63
+ ## Exemplo mínimo
64
+
65
+ ```tsx
66
+ <Gantt
67
+ rows={[
68
+ { id: "fase", label: "Descoberta", type: "summary", bars: [] },
69
+ {
70
+ id: "t1", label: "Entrevistas", parent: "fase",
71
+ bars: [{ id: "b1", label: "Entrevistas", start: d1, end: d2,
72
+ colorKey: "chart-1", progress: 100 }],
73
+ },
74
+ {
75
+ id: "t2", label: "Escopo", parent: "fase",
76
+ bars: [{ id: "b2", label: "Escopo", start: d2, end: d3, colorKey: "chart-1" }],
77
+ },
78
+ ]}
79
+ links={[{ id: "v1", source: "b1", target: "b2", type: "FS" }]}
80
+ searchable
81
+ locale={ptBR}
82
+ onBarClick={(bar, row) => abrirDetalhe(row.id)}
83
+ />
84
+ ```
85
+
86
+ ## Props essenciais
87
+
88
+ | Prop | Tipo | Default |
89
+ |---|---|---|
90
+ | `rows` | `GanttRow[]` | — |
91
+ | `links` | `GanttLink[]` — ausente = sem setas | — |
92
+ | `views` | `GanttView[]` — **quais visões existem** | as três |
93
+ | `view` | `"timeline" \| "calendar" \| "list"` — qual está **aberta** | `"timeline"` |
94
+ | `toolbarActions` / `primaryAction` | `ReactNode` — os dois slots livres da toolbar | — |
95
+ | `emptyState` | `ReactNode` — substitui o vazio de busca/filtro | ícone + texto |
96
+ | `loading` / `loadingState` | `boolean` + `ReactNode` — esqueleto no lugar do conteúdo | `false` |
97
+ | `windowStart` / `windowEnd` | `Date` — **do consumidor** | derivada dos dados |
98
+ | `granularity` | `"day" \| "week" \| "month" \| "quarter"` | `"day"` |
99
+ | `onViewChange` / `onGranularityChange` / `onCriticalPathChange` | os pares dos três estados de UI — ver abaixo | — |
100
+ | `columns` | `GanttColumn[]` | nome + início + fim |
101
+ | `gridWidth` | `number` — largura inicial; o divisor é arrastável | `360` |
102
+ | `draggable` / `resizable` / `linkable` | `boolean` | **`false`** |
103
+ | `criticalPath` | `boolean` — liga o realce | **`false`** |
104
+ | `criticalPathToggle` | `boolean` — mostra o botão "Crítico" na toolbar | `true` |
105
+ | `onBarClick` | `(bar, row, evt) => void` | — |
106
+ | `onBarMove` / `onBarResize` | `(change) => void` — **emite, não aplica** | — |
107
+ | `onLinkViolations` | `(violations) => void` | — |
108
+ | `onGraphError` | `({ kind: "cycle", barIds }) => void` | — |
109
+
110
+ `GanttBar`: `{ id, start, end, label?, searchText?, colorKey?, progress?, continuesBefore?, continuesAfter?, meta? }`
111
+ `GanttLink`: `{ id, source, target, type?, lag? }`
112
+ `GanttFilterField`: `{ id, label, kind?, options?, accessor, searchable?, placeholder? }`
113
+
114
+ ### Filtro — os 6 `kind`
115
+
116
+ O vocabulário espelha o da `DataTable`, pra o usuário ler a mesma frase nas duas
117
+ telas: *"Status é Ativo"*, *"Duração entre 3 e 10"*, *"Início a partir de 01/09/26"*.
118
+
119
+ | `kind` | Controle no painel | `filterModel[id]` | Operador no chip |
120
+ |---|---|---|---|
121
+ | `multi` *(default)* | checkboxes + busca (≥7 opções) + "selecionar todas" | valores marcados | `é` |
122
+ | `single` | radio | um valor | `é` |
123
+ | `text` | um campo de texto | `[termo]` | `contém` |
124
+ | `number` | dois campos numéricos | `[min, max]` — `""` = livre | `entre` / `≥` / `≤` |
125
+ | `date` | dois campos de data | `[de, até]` — `""` = livre | `entre` / `a partir de` / `até` |
126
+ | `boolean` | radio Sim/Não (ou seus `options`) | `["true"]` \| `["false"]` | `é` |
127
+
128
+ ```tsx
129
+ const FILTROS: GanttFilterField[] = [
130
+ { id: "frente", label: "Frente", options: [...], accessor: r => meta(r).frente },
131
+ { id: "desc", label: "Descrição", kind: "text", accessor: r => meta(r).descricao },
132
+ { id: "dur", label: "Duração", kind: "number", accessor: r => dias(r) },
133
+ { id: "inicio", label: "Início", kind: "date", accessor: r => r.bars[0]?.start },
134
+ { id: "ok", label: "Concluída", kind: "boolean", accessor: r => r.bars[0]?.progress === 100 },
135
+ ];
136
+ ```
137
+
138
+ ⚠️ **`GanttFilterModel` não mudou de forma** — segue `Record<string, string[]>`. Os 6
139
+ tipos codificam o valor em `string[]`, então `filterModel` controlado, persistência e
140
+ "Limpar tudo" que você já tinha continuam funcionando **sem migração**.
141
+
142
+ ## Gotchas / cuidados
143
+
144
+ ### 1. O pai precisa ter altura
145
+
146
+ O componente é `h-full`. Sem altura no pai, ele colapsa e você vê só a toolbar.
147
+
148
+ ```tsx
149
+ <div className="flex h-[520px] flex-col"> {/* ou h-full num pai com altura */}
150
+ <Gantt … />
151
+ </div>
152
+ ```
153
+
154
+ ### 2. `links` referencia id de BARRA, não de linha
155
+
156
+ ```tsx
157
+ rows: { id: "t1", bars: [{ id: "b1", … }] }
158
+ links: { source: "b1", target: "b2" } // ✅ id da barra
159
+ links: { source: "t1", target: "t2" } // ❌ silenciosamente ignorado
160
+ ```
161
+
162
+ Uma linha-contêiner tem N barras, e o vínculo é entre trabalhos. Vínculo cujas
163
+ pontas não existem é **ignorado**, não reportado como erro — referência pendente
164
+ acontece em paginação e em edição otimista, e tratar como conflito encheria a
165
+ tela de falso positivo transitório.
166
+
167
+ ### 3. O componente NÃO reagenda
168
+
169
+ Arrastar emite `onBarMove`. Vínculo violado emite `onLinkViolations` com o
170
+ déficit em dias. **Nada se move sozinho.**
171
+
172
+ Corrigir cronograma é decisão de negócio: mover a tarefa que atrasou, cortar
173
+ escopo, aceitar o atraso ou renegociar o vínculo são quatro respostas diferentes
174
+ pro mesmo conflito. Datas reescritas sozinhas parecem dados, e o erro seria
175
+ invisível.
176
+
177
+ ### 4. A janela é do consumidor
178
+
179
+ `windowStart`/`windowEnd` vêm por prop, e as setas `‹ ›` **não fazem nada**
180
+ quando você as controla — mover é sua decisão. Omitidas, o componente mantém a
181
+ janela em estado próprio, derivada dos dados na primeira montagem.
182
+
183
+ ### 5. `progress: undefined` ≠ `progress: 0`
184
+
185
+ Ausente = "não rastreia progresso", e não desenha trilha nenhuma.
186
+ `0` = "rastreia, e está em zero", e desenha a trilha vazia. Os dois estados
187
+ aparecem lado a lado em cronograma real.
188
+
189
+ ### 6. A cor diz CATEGORIA, não estado
190
+
191
+ `colorKey` usa a paleta de chart (`chart-1`…`chart-5`) porque no Gantt a cor diz
192
+ **qual frente**. Status vai em `row.trailing` como `Chip`:
193
+
194
+ ```tsx
195
+ { id: "fase", label: "Design", type: "summary", bars: [],
196
+ trailing: <Chip size="sm" variant="soft" color="warning">Em andamento</Chip> }
197
+ ```
198
+
199
+ Azul (`chart-3`) é legítimo aqui: o `DESIGN.md` proíbe azul na **interface**, e o
200
+ `Chart/USAGE.md` o inclui como **dado categórico**. Barra de Gantt é dado.
201
+
202
+ ### 7. A barra é tingida, não sólida com texto branco
203
+
204
+ Se você vier de uma referência com barra saturada e texto branco: o DS não faz
205
+ isso, e a razão foi **medida** — texto branco ou colorido sobre pílula tingida dá
206
+ contraste de **1.72–4.49 no light**, e nenhuma família passa AA. A cor viva vive
207
+ no acento da borda esquerda e no preenchimento de progresso; o texto é
208
+ `fg-default`.
209
+
210
+ ### 8. `summary` deriva o intervalo dos filhos
211
+
212
+ `type: "summary"` com `bars: []` calcula o intervalo de **toda** a descendência
213
+ (não só filhos diretos). Passar `bars` num `summary` vence o cálculo — serve pra
214
+ quem tem o agregado do servidor.
215
+
216
+ O intervalo é derivado de **todas** as linhas, não das visíveis: colapsar não
217
+ encolhe a barra do summary, porque colapsar é justamente quando ela passa a ser
218
+ a única informação.
219
+
220
+ ### 9. Caminho crítico só considera `FS`
221
+
222
+ Limite conhecido e declarado. Os outros três tipos exigem tratar as duas pontas
223
+ como nós independentes no grafo, e implementar isso pela metade daria caminho
224
+ crítico **plausível e errado** — que é pior que não ter. `SS`, `FF` e `SF`
225
+ continuam sendo validados como conflito; só não entram no cálculo de criticidade.
226
+
227
+ Ciclo no grafo → `onGraphError` e o crítico simplesmente não pinta. Ciclo é dado
228
+ do consumidor, não exceção do componente.
229
+
230
+ ### 10. Uma altura de linha, duas superfícies
231
+
232
+ `GANTT_ROW_HEIGHT_PX` (48px) é constante e é consumida pelos **dois** painéis. Não
233
+ tente sobrescrever por CSS num deles: o desalinho entre nome e barra é o pior
234
+ defeito possível aqui, porque produz leitura errada sem parecer quebrado.
235
+
236
+ ### 11. Trocar de escala nunca esconde trabalho
237
+
238
+ Quando o consumidor não controla `windowStart`/`windowEnd`, trocar a escala faz
239
+ duas coisas:
240
+
241
+ 1. **A janela** vira a UNIÃO da largura própria daquela escala (60 dias em
242
+ `day`, 1825 em `quarter`) com a extensão real do cronograma. União e não
243
+ `max` porque ela é idempotente: `day → quarter → day` devolve exatamente a
244
+ janela original. Antes a janela era só a largura da escala, e voltar de
245
+ `quarter` pra `day` recortava em 60 dias — as barras das duas pontas
246
+ desapareciam sem nada dizer que havia mais.
247
+ 2. **A viewport** recentra no mesmo instante do tempo que estava no meio da
248
+ tela. Sem isso, ir pra `quarter` deixava você olhando o começo de uma janela
249
+ de 5 anos cujos dados vivem no meio dela: canvas vazio.
250
+
251
+ Com `windowStart`/`windowEnd` controlados, nada disso acontece: a janela é sua,
252
+ e o componente não sobrescreve a sua decisão.
253
+
254
+ ### 12. O gesto EMITE, você aplica
255
+
256
+ `draggable` / `resizable` / `linkable` ligam os gestos:
257
+
258
+ | Gesto | Onde | Emite |
259
+ |---|---|---|
260
+ | mover | `pointerdown` no corpo da barra | `onBarMove` |
261
+ | redimensionar | punho de 8px em cada ponta | `onBarResize` |
262
+ | criar vínculo | porta redonda fora da ponta → soltar sobre outra barra | `onLinkCreate` |
263
+ | remover vínculo | clique na seta | `onLinkDelete` |
264
+
265
+ ⚠️ **Ligar sem handler = arrastar e ver voltar.** A prévia acontece (a barra
266
+ acompanha o ponteiro com snap de dia), mas nada persiste: o `Gantt` é dumb sobre
267
+ mutação, igual ao `Kanban`. Datas reescritas sozinhas parecem dados, e o erro
268
+ seria invisível.
269
+
270
+ ```tsx
271
+ const [rows, setRows] = useState(SEMENTE);
272
+
273
+ <Gantt
274
+ rows={rows}
275
+ draggable resizable linkable
276
+ onBarMove={({ bar, start, end }) => setRows(atualizar(bar.id, start, end))}
277
+ onBarResize={({ bar, start, end }) => setRows(atualizar(bar.id, start, end))}
278
+ onLinkCreate={(novo) => setLinks((l) => [...l, { ...novo, id: gerarId() }])}
279
+ />
280
+ ```
281
+
282
+ **O tipo do vínculo sai das duas pontas** — `linkTypeFromSides`: sair da ponta
283
+ direita e soltar na metade **esquerda** do destino dá `FS` (o caso comum); as
284
+ outras três combinações dão `SS`, `FF` e `SF`. O alvo é **meia barra**, não a
285
+ porta de 9px do destino: 9px não é afordância.
286
+
287
+ ⛔ **Linha `summary` não aceita gesto.** O intervalo dela é derivado dos filhos e
288
+ a barra é sintética (id `<rowId>__summary`, que não existe no seu `rows`) — um
289
+ gesto ali emitiria uma `bar` que você não possui e um `source` pendurado. Mover
290
+ a fase é mover os filhos, e isso é decisão sua.
291
+
292
+ **Snap em dia inteiro**, sempre. Arraste menor que meio dia não move nada, e
293
+ arrastar o punho inicial para depois do fim **para** em `start === end` (1 dia)
294
+ em vez de inverter a barra — sem esse clamp a largura ficaria negativa e a barra
295
+ desapareceria no meio do gesto.
296
+
297
+ ### 13. O `accessor` tem que casar com o `kind`
298
+
299
+ `number` precisa de número (ou string numérica); `date` precisa de `Date` ou ISO;
300
+ `boolean` aceita `true`/`1`/`"sim"`. Devolver a coisa errada **não é erro de tipo** —
301
+ o `accessor` é declarado largo de propósito — e o campo simplesmente não casa nada.
302
+
303
+ E `undefined` **exclui** a linha quando o filtro está ativo: filtrar por
304
+ "Responsável = Ana" e receber de volta as linhas sem responsável nenhum é o oposto
305
+ do pedido.
306
+
307
+ ⚠️ Se você monta o valor de `date` na mão, **não** use `new Date("2026-09-30")`: ISO
308
+ date-only é parseado como **UTC** e volta um dia em fuso negativo (medido em UTC−3:
309
+ vira 29/09 21:00). O componente usa `parseDiaISO` internamente pelos dois lados —
310
+ predicado e chip — pra os dois nunca divergirem.
311
+
312
+ ### 14. As duas visões respondem perguntas diferentes
313
+
314
+ | Visão | Responde | Título | `‹ ›` anda | Escala | Painel esquerdo |
315
+ |---|---|---|---|---|---|
316
+ | `timeline` | *o que depende do quê* | o intervalo ("31 ago – 2 nov 2026") | meia janela | ✅ | ✅ |
317
+ | `calendar` | *o que acontece no dia 12* | o **mês** ("outubro 2026") | **1 mês** | ⛔ | ⛔ |
318
+ | `list` | *o que vem a seguir* | o **mês** | **1 mês** | ⛔ | ⛔ |
319
+
320
+ O seletor é o **primeiro item da toolbar**, à esquerda, seguido de divisor —
321
+ igual à `DataTable`, onde o toggle Tabela/Lista/Kanban abre a toolbar. A razão é
322
+ hierárquica: a visão decide o que todo o resto da toolbar significa (o período
323
+ vira um mês no calendário, a escala desaparece), então ela não pode vir depois
324
+ do que governa.
325
+
326
+ **`list` é uma AGENDA, não uma tabela de tarefas.** Tabela com hierarquia e
327
+ filtro é o `DataTable` (view lista, `hierarchical`). O que faz esta visão
328
+ diferente é o agrupamento por **dia**: a mesma tarefa aparece em cada dia que
329
+ ocupa, com a posição no intervalo à direita ("dia 2 de 6"). Numa tarefa repetida
330
+ em 6 blocos, o intervalo seria idêntico nos seis e não diria nada novo — a
331
+ posição diz onde no trabalho aquele dia está.
332
+
333
+ ⚠️ **Só os dias que TÊM tarefa aparecem.** Uma agenda que lista 60 dias pra
334
+ mostrar 20 tarefas obriga a rolar por 40 blocos vazios; o salto entre datas é o
335
+ próprio sinal de que não há nada no meio. (A grade de mês faz o oposto, e está
336
+ certa: lá o dia vazio *é* a informação.)
337
+
338
+ ⚠️ **A agenda é recortada no MÊS, não na janela** — como a grade. Lendo a janela
339
+ inteira ela saía com **62 blocos de dia e 168 cartões** no exemplo: rolagem
340
+ infinita disfarçada de agenda. No mês são no máximo 31 blocos, e o `‹ ›` andando
341
+ um mês exato dá o gesto pra chegar nos outros. A janela existe pro EIXO, que
342
+ comprime 64 dias em pixels; agenda e grade não comprimem — cada dia custa uma
343
+ linha.
344
+
345
+ ⚠️ Numa linha com **várias barras** (`lanePacking`), a sublinha do cartão é o
346
+ rótulo da BARRA, não o `sublabel` da linha. Sem isso a linha aparecia N vezes no
347
+ mesmo dia com título e sublinha idênticos, só o "dia N de M" diferente — N
348
+ cartões indistinguíveis.
349
+
350
+ A toolbar **muda com a visão**, não só o conteúdo. O título de `calendar` é o mês
351
+ porque a grade é de um mês — anunciar 64 dias sobre uma grade que mostra 31 seria
352
+ o título mentindo. O `‹ ›` anda um mês exato ali (meia janela poderia cair no
353
+ mesmo mês, e o botão não faria o que o título promete). E a escala **desaparece**:
354
+ "Dia/Semana/Mês/Trimestre" é a densidade do eixo horizontal, e na grade de mês não
355
+ há eixo pra adensar — controle que não muda nada é a mesma classe de defeito do
356
+ toggle de crítico sem vínculo.
357
+
358
+ ⚠️ A escala **não é resetada** ao esconder: voltando pra timeline, o zoom que
359
+ estava selecionado continua. Zerar faria trocar de visão e voltar perder trabalho
360
+ do usuário.
361
+
362
+ ### Recortar as visões — `views` ≠ `view`
363
+
364
+ `views` diz **o que existe**; `view` diz **o que está aberto**. Nem todo
365
+ cronograma quer as três: um painel de dependências não ganha nada com a agenda,
366
+ uma tela de acompanhamento mensal pode não querer o eixo.
367
+
368
+ ```tsx
369
+ <Gantt rows={ROWS} views={["timeline"]} /> // só o cronograma
370
+ <Gantt rows={ROWS} views={["calendar", "list"]} /> // sem eixo
371
+ ```
372
+
373
+ ⚠️ **Com uma visão só, o seletor e o divisor não são renderizados.** Segmentado
374
+ de uma opção é um controle que nunca muda nada — a mesma regra do toggle de
375
+ crítico sem vínculo e do "+" da grade sem `onDayAdd`.
376
+
377
+ ⚠️ **`views` vence `view`.** `view="list"` com `views={["timeline"]}` abre a
378
+ timeline: renderizar o que o consumidor excluiu seria pior que corrigir. Lista
379
+ vazia (`views={[]}`) é tratada como omitida.
380
+
381
+ ### A toolbar tem dois slots livres
382
+
383
+ **É aqui que entra o botão de opções.** O componente não inventa "⋯", "exportar"
384
+ nem "imprimir" — ele abre espaço e você põe o que a sua tela precisa, do mesmo
385
+ jeito que a `TableToolbar` recebe `actions`.
386
+
387
+ ```
388
+ [visão] │ [‹ Hoje ›] [período] … [busca] [toolbarActions] [crítico] [escala] [filtro] [primaryAction]
389
+ ```
390
+
391
+ ```tsx
392
+ <Gantt
393
+ rows={ROWS}
394
+ toolbarActions={
395
+ <DropdownMenu>
396
+ <DropdownMenuTrigger asChild>
397
+ <Button variant="outline" color="secondary" size="icon-md"
398
+ aria-label="Mais opções"><MoreHorizontal /></Button>
399
+ </DropdownMenuTrigger>
400
+ <DropdownMenuContent align="end">…</DropdownMenuContent>
401
+ </DropdownMenu>
402
+ }
403
+ primaryAction={<Button iconLeft={<Plus />}>Nova tarefa</Button>}
404
+ />
405
+ ```
406
+
407
+ `toolbarActions` entra **antes** da ação primária, de propósito: a ação primária
408
+ fecha a barra, e é o último lugar onde o olho para.
409
+
410
+ ### Carregando ≠ vazio
411
+
412
+ ⚠️ **Se você busca do servidor, passe `loading`.** Sem ele, `rows={[]}` durante o
413
+ fetch faz o componente afirmar *"Nenhuma tarefa neste período"* — uma frase que
414
+ ele não tem como saber que é verdade, e que faz o usuário desistir antes de o
415
+ dado chegar.
416
+
417
+ ```tsx
418
+ <Gantt rows={rows} loading={isFetching} />
419
+ ```
420
+
421
+ `loading` **vence** `rows`: mesmo com linhas na tela ele mostra o esqueleto,
422
+ porque durante um refetch o que está renderizado pode estar velho. A **toolbar
423
+ continua** — período, busca e filtro não dependem dos dados, e apagá-la faria a
424
+ tela inteira piscar quando eles chegam.
425
+
426
+ O esqueleto imita a silhueta da visão atual (painel + barras na timeline, 6×7 na
427
+ grade, blocos na agenda), pra a chegada do dado não deslocar nada. `loadingState`
428
+ troca por um seu — mantenha a altura.
429
+
430
+ ### O imperativo — `GanttRef`
431
+
432
+ Quatro métodos, pra quando a tela precisa mandar no cronograma sem passar por
433
+ prop: `goToDate(date)` · `goToToday()` · `expandAll()` · `collapseAll()`.
434
+
435
+ ```tsx
436
+ const gantt = useRef<GanttRef>(null);
437
+ <Button onClick={() => gantt.current?.collapseAll()}>Recolher tudo</Button>
438
+ <Gantt ref={gantt} rows={ROWS} />
439
+ ```
440
+
441
+ ### Os três estados de UI seguem o MESMO par
442
+
443
+ `view`, `granularity` e `criticalPath` são estados que a UI do próprio
444
+ componente mexe — o seletor de visão, o dropdown de escala e o botão "Crítico".
445
+ Cada um aceita as duas formas:
446
+
447
+ | Você passa | Quem manda | O `on*Change` |
448
+ |---|---|---|
449
+ | nada | o componente | não existe pra chamar |
450
+ | só o valor | você — **o controle congela** | não existe pra chamar |
451
+ | valor + callback | você | avisa a intenção do usuário |
452
+
453
+ É a semântica normal de campo controlado no React, e a linha do meio é
454
+ deliberada — não é bug.
455
+
456
+ ⛔ **`granularity` e `criticalPath` não tinham callback até a v0.59.** O
457
+ resultado era só a linha do meio, sem escapatória: passar `granularity="day"`
458
+ como valor inicial matava o dropdown de escala em silêncio — o clique mudava o
459
+ estado interno e a prop o mascarava de volta. Se você copiou um exemplo com
460
+ `granularity` fixo e a escala "não funciona", é isto: **remova a prop** (o
461
+ componente já começa em `day`) ou acrescente `onGranularityChange`.
462
+
463
+ **A barra é UM segmento contínuo** atravessando os dias que ocupa — não um chip
464
+ por dia. Uma tarefa de 10 a 15 é uma barra de 6 colunas, e se cruzar a virada de
465
+ semana vira dois segmentos com a ponta cortada marcada (mesmo vocabulário
466
+ `continuesBefore`/`continuesAfter` da timeline). Um chip por dia leria como seis
467
+ tarefas de um dia em vez de uma de seis.
468
+
469
+ `onDayAdd` liga o **"+" no hover da célula**. Sem o handler o botão não é
470
+ renderizado — um "+" que não adiciona nada é pior que a ausência dele.
471
+
472
+ ⚠️ Ele significa **"o usuário pediu pra adicionar no dia X"**, não "adicione
473
+ isto". Abra seu drawer/modal com os campos que a SUA tela precisa (nome, cor,
474
+ responsável, duração) e crie a linha no salvar — mesma divisão do `onBarClick`,
475
+ que devolve o payload e deixa a ficha pra você. O `GanttFullPreview` mostra o
476
+ drawer completo com `FormFieldInput`/`FormFieldSelect`.
477
+
478
+ ⛔ Não ofereça "escolha uma cor" por NOME DE COR no seu formulário. A cor da barra
479
+ diz CATEGORIA (qual frente), e um seletor de "verde/roxo" convida o usuário a usar
480
+ cor como STATUS — que é exatamente a assumption que este componente declara.
481
+ Rotule as opções pela frente ("Design", "Engenharia") e mapeie pra `colorKey` por
482
+ dentro.
483
+
484
+ ⚠️ **Linha `summary` não entra na grade de mês.** O intervalo dela é a união dos
485
+ filhos, então ela pintaria exatamente as células que os filhos já pintam — e ali a
486
+ hierarquia não é visível pra dar sentido ao agregado.
487
+
488
+ ⚠️ **O corte do `+N mais` é MEDIDO, não fixo.** A célula tem altura mínima de 88px
489
+ (cabeçalho + 3 chips) e a grade rola quando não cabe; um `ResizeObserver` recalcula
490
+ quantos chips entram quando a altura muda. Com corte fixo, uma viewport curta
491
+ recortava os chips e o próprio `+N mais` — o usuário via 2 tarefas e nenhuma pista
492
+ de que havia 4.
493
+
494
+ ### 15. Em telas estreitas, a TOOLBAR se adapta — a MATRIZ não
495
+
496
+ São dois problemas diferentes, e só um tem solução dentro do componente.
497
+
498
+ **A toolbar resolve sozinha, com a regra da `TableToolbar`.** Abaixo de `md`
499
+ (768px) saem da barra o **seletor de visão**, o **`‹ Hoje ›`**, a **escala** e o
500
+ **caminho crítico**; eles reaparecem num menu (ícone de sliders) ao lado do
501
+ título, que abre como bottom-sheet. Ficam na barra: **título do período**,
502
+ **busca**, **filtro** e a **ação primária**.
503
+
504
+ Medido a 375px: antes, a busca era espremida a **51px** (o ícone e nada mais) e o
505
+ título saía cortado no meio; depois, a busca tem **153px** e o título cabe
506
+ inteiro. O título é a única coisa que o Gantt preserva e a tabela não tem — sem
507
+ ele o usuário não sabe que período está vendo.
508
+
509
+ ⚠️ **A `primaryAction` é sua.** O componente não pode encolhê-la (é um
510
+ `ReactNode` que você passa). Em mobile ela come ~126px com rótulo — passe um
511
+ botão **icon-only** abaixo de `md` se a barra ficar apertada.
512
+
513
+ **A matriz não cabe, e o componente não finge que cabe.** Grade + eixo em 375px
514
+ não é problema de layout, é de densidade: um Gantt legível precisa de ~900px.
515
+ Use `granularity="week"` ou mais, reduza `gridWidth`, ou — melhor — mande
516
+ `view="calendar"` / `view="list"` no mobile, que são justamente as visões sem
517
+ eixo horizontal.
518
+
519
+ ## ⚠️ O que ainda NÃO existe
520
+
521
+ Declarado pra não virar descoberta:
522
+
523
+ | | Estado |
524
+ |---|---|
525
+ | setas de vínculo na visão `calendar` | **não existem, e é decisão** — numa grade de mês uma seta do dia 3 ao 19 atravessaria 3 semanas passando por cima de 16 células alheias. O grafo segue vivo (conflito marcado no chip, `onLinkViolations` emitindo); sai só o desenho |
526
+ | gesto na visão `calendar` | só clique. Arrastar num calendário move por dia, não por pixel — é outro gesto, e misturá-lo com o da timeline daria duas semânticas pro mesmo arraste |
527
+ | criar vínculo `FF` / `SF` por gesto | possível, mas exige soltar na **metade direita** da barra de destino. Descoberta só pela doc — não há dica visual da metade |
528
+
529
+ O que está completo: as duas visões de dado (tarefa e portfólio), hierarquia com
530
+ collapse e **conectores de árvore**, `summary` derivado, marcos, progresso, os
531
+ **4 tipos de vínculo** com `lag`, detecção de conflito, caminho crítico, busca,
532
+ zoom em 4 escalas, divisor arrastável, **filtro nos 6 tipos** com painel lateral
533
+ e chips de aplicado, seleção de linha e de coluna, virada de mês no eixo e os
534
+ **4 gestos** (mover, redimensionar, criar e remover vínculo) com snap de dia, e as
535
+ **três visões** (`timeline`, `calendar`, `list`) com o seletor segmentado abrindo
536
+ a toolbar.
537
+
538
+ ## Núcleo puro exportado
539
+
540
+ Útil fora do render — validar cronograma no servidor, calcular crítico num job:
541
+
542
+ ```tsx
543
+ import { checkAllLinks, computeCriticalPath, topoSort } from "@/components/ui/Gantt";
544
+ import { buildTimeAxis, clipToWindow, packLanes } from "@/components/ui/Gantt";
545
+ ```
546
+
547
+ **146 testes** cobrem as bordas: virada de mês, barra que cruza a janela, lane
548
+ packing com sobreposição parcial, ciclo em `parent` (que devolvia lista VAZIA até
549
+ o teste existir), auto-vínculo, as 4 restrições nas pontas certas, o índice
550
+ `ancestorHasNext[i+1]` do conector (L-045) e o parse de `YYYY-MM-DD` como
551
+ meia-noite local (com `new Date` era UTC e o filtro de data voltava um dia), e o
552
+ clamp do resize que impedia a barra de inverter e desaparecer.
@@ -0,0 +1,98 @@
1
+ # Header — USAGE
2
+
3
+ Barra superior fixa (60px) com breadcrumb à esquerda + search/theme/notifications/messages/user à direita.
4
+
5
+ ## Quando usar
6
+ - Topo de qualquer página dentro de `<AppShell>`
7
+ - Standalone (raro): pages que não usam AppShell mas precisam de chrome consistente
8
+
9
+ ## Import
10
+ ```tsx
11
+ import { Header } from "@/components/ui/Header";
12
+ ```
13
+
14
+ ## Props essenciais
15
+ | Prop | Tipo | Função |
16
+ |---|---|---|
17
+ | `breadcrumb` | `HeaderBreadcrumbItem[]` | Array de items (último = página atual, nunca link). Ver a forma completa abaixo |
18
+ | `onCollapseMenu` | () => void | Botão de collapse do MenuSidebar (omitido = botão escondido) |
19
+ | `menuCollapsed` | boolean | Controla o ícone (PanelLeftClose vs PanelLeftOpen) |
20
+ | `showSearch` | boolean (default `true`) | Mostra o fake-input de busca; `false` desliga |
21
+ | `searchPlaceholder` | string | Texto do fake-input de busca |
22
+ | `commandGroups` | `HeaderCommandGroup[]` | Comandos do Command palette interno — sem isso o palette abre vazio |
23
+ | `theme` | string | Tema ativo |
24
+ | `themeOptions` | HeaderThemeOption[] | Lista de temas pro dropdown |
25
+ | `onThemeChange` | (id: string) => void | Callback |
26
+ | `notifications` | { items, onMarkAllRead, onViewAll } | Popover sino (vira bottom-sheet no mobile via `mobileSheet`) |
27
+ | `messages` | { items, onNewMessage, onExpand, onViewAll } | Popover chat (vira bottom-sheet no mobile via `mobileSheet`) |
28
+ | `rightSlot` | ReactNode | Slot livre à direita (botões custom antes dos ícones default) |
29
+
30
+ ### `HeaderBreadcrumbItem` — a forma completa
31
+
32
+ ```ts
33
+ {
34
+ label: string;
35
+ href?: string; // vira link; o ÚLTIMO item nunca é link
36
+ onClick?: (e) => void;
37
+
38
+ // Vira SELETOR de registro. Precisa dos TRÊS juntos; faltando um, o item
39
+ // renderiza como texto — gatilho que abre lista vazia é pior que texto.
40
+ switcher?: BreadcrumbSwitcherOption[];
41
+ value?: string;
42
+ onValueChange?: (v: string) => void;
43
+ switcherTitle?: ReactNode;
44
+ switcherSearchPlaceholder?: string;
45
+ switcherFooter?: ReactNode;
46
+
47
+ // Conteúdo livre DEPOIS do rótulo, no mesmo item.
48
+ trailing?: ReactNode;
49
+ }
50
+ ```
51
+
52
+ **`trailing`** aceita qualquer nó — o caso de origem foi um chip de status ao lado
53
+ do nome do registro aberto (`Clientes / Maria Silva [Ativo]`), mas o slot não sabe
54
+ disso. É a única forma de pôr algo ali neste modo: quem monta o `<li>` é o
55
+ componente, então não há onde escrever um irmão na mão.
56
+
57
+ ⚠️ Ele é **irmão** do gatilho do seletor, não filho — é o que permite conteúdo
58
+ interativo (`<button>` dentro de `<button>` é HTML inválido). Consequência: clicar
59
+ no `trailing` **não** abre a lista do seletor.
60
+
61
+ ```tsx
62
+ <Header
63
+ breadcrumb={[
64
+ { label: "Clientes", href: "/clientes" },
65
+ {
66
+ label: cliente.nome,
67
+ switcher: CLIENTES, value: id, onValueChange: abrirCliente,
68
+ trailing: <Chip size="sm" variant="soft" color={cliente.statusCor}>{cliente.status}</Chip>,
69
+ },
70
+ ]}
71
+ />
72
+ ```
73
+
74
+ ## Exemplo mínimo
75
+ ```tsx
76
+ <Header
77
+ breadcrumb={[{ label: "Clientes" }, { label: "Maria Silva" }]}
78
+ onCollapseMenu={() => setCollapsed((c) => !c)}
79
+ menuCollapsed={collapsed}
80
+ searchPlaceholder="Buscar..."
81
+ commandGroups={[
82
+ {
83
+ heading: "Ações",
84
+ items: [{ label: "Novo cliente", onSelect: () => abrirDrawer() }],
85
+ },
86
+ ]}
87
+ theme={theme}
88
+ onThemeChange={setTheme}
89
+ themeOptions={APP_SHELL_THEME_OPTIONS}
90
+ notifications={{ items: notifs, onMarkAllRead, onViewAll }}
91
+ />
92
+ ```
93
+
94
+ ## Cuidados / Gotchas
95
+ - Position é responsabilidade do consumer/template (Header só define altura + layout)
96
+ - Breadcrumb com 1 único item renderiza automaticamente como título standalone (15px); 2+ items viram cadeia (13px). Último item nunca é link
97
+ - Search é fake-input que abre o Command palette interno (⌘K / Ctrl+K) — popular via `commandGroups`, senão o palette abre vazio
98
+ - Badge dot no icon button: `kind="brand"` (mensagens) vs `kind="danger"` (alertas)