@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,129 @@
1
+ # PageHeader — USAGE
2
+
3
+ Bloco de título reutilizável renderizado dentro do body do `<AppShell>`. Cobre o pattern comum de "header de página" do CRM iGreen: **título + descrição + badge + actions**, com slot opcional pra conteúdo extra abaixo (tabs, filtros).
4
+
5
+ ## Quando usar
6
+ - Páginas listagem (CRUD, dashboards) que precisam de título + CTA principal
7
+ - Páginas com tabs/filtros abaixo do header
8
+ - Qualquer tela que reaproveite a estrutura "title/description/badge + actions"
9
+
10
+ Quando **não** usar:
11
+ - Telas chat-like (ChatV2/ChatShowcase) — o header global do AppShell já basta
12
+ - Detail pages onde o header é parte de um card/drawer (use composição direta)
13
+
14
+ ## Import
15
+ ```tsx
16
+ import { PageHeader } from "@/components/ui/PageHeader";
17
+ ```
18
+
19
+ ## Props essenciais
20
+
21
+ | Prop | Tipo | Default | Função |
22
+ |---|---|---|---|
23
+ | `title` | string | — | Título h1 (text-title-lg) |
24
+ | `description` | string | — | Sub-linha (text-body-md fg-subtle) |
25
+ | `badge` | ReactNode | — | Inline ao lado do title (geralmente `<Chip>`) |
26
+ | `actions` | ReactNode | — | Bloco de ações à direita (Buttons, dropdowns) |
27
+ | `children` | ReactNode | — | Conteúdo extra abaixo (tabs, filtros) |
28
+ | `hideTextOnMobile` | boolean | `true` | Em mobile (<md), esconde title/description/badge |
29
+ | `fluidPrimaryOnMobile` | boolean | `true` | Em mobile, último filho do `actions` vira fluid (full-width CTA) |
30
+ | `className` | string | — | className extra no `<header>` root |
31
+
32
+ ## Exemplos
33
+
34
+ ### Mínimo (só título)
35
+ ```tsx
36
+ <PageHeader title="Configurações" />
37
+ ```
38
+
39
+ ### Pattern CRUD (replica o header da página de Clientes)
40
+ ```tsx
41
+ <PageHeader
42
+ title="Clientes"
43
+ description="Gerencie sua base de clientes, acompanhe status e abra atendimentos pelo WhatsApp."
44
+ badge={
45
+ <Chip color="primary" variant="soft" size="sm" shape="rounded">
46
+ 87 registros
47
+ </Chip>
48
+ }
49
+ actions={
50
+ <>
51
+ <Button variant="outline" color="secondary" size="icon-md" aria-label="Mais ações">
52
+ <MoreHorizontal />
53
+ </Button>
54
+ <Button variant="filled" color="primary" iconLeft={<Plus />}>
55
+ Novo cliente
56
+ </Button>
57
+ </>
58
+ }
59
+ />
60
+ ```
61
+
62
+ ### Com tabs abaixo do header
63
+ ```tsx
64
+ <PageHeader
65
+ title="Relatórios"
66
+ description="Análise consolidada de vendas e leads."
67
+ actions={<Button>Exportar</Button>}
68
+ >
69
+ <Tabs value={tab} onValueChange={setTab}>
70
+ <TabsList>
71
+ <TabsTrigger value="day">Hoje</TabsTrigger>
72
+ <TabsTrigger value="week">Semana</TabsTrigger>
73
+ <TabsTrigger value="month">Mês</TabsTrigger>
74
+ </TabsList>
75
+ </Tabs>
76
+ </PageHeader>
77
+ ```
78
+
79
+ ### Mantendo título visível no mobile
80
+ ```tsx
81
+ <PageHeader
82
+ title="Termos de uso"
83
+ description="Última atualização: jan/2026"
84
+ hideTextOnMobile={false}
85
+ />
86
+ ```
87
+
88
+ ### Sem actions, só conteúdo
89
+ ```tsx
90
+ <PageHeader title="FAQ">
91
+ <SearchBar />
92
+ </PageHeader>
93
+ ```
94
+
95
+ ## Cuidados / Gotchas
96
+
97
+ - **`title` é renderizado como `<h1>`** — use **apenas um** `<PageHeader>` com title por página (semântica HTML).
98
+ - **Mobile fluid** assume que o último filho do `actions` é o CTA primary. Se a ordem for diferente, desligue via `fluidPrimaryOnMobile={false}` e controle manualmente.
99
+ - **`description` truncate em 1 linha** (`whitespace-nowrap overflow-hidden text-ellipsis`) — pra descrições longas, considere usar o slot `children` em vez disso.
100
+ - **Body do AppShell** já aplica `gap-gp-4xl` entre filhos diretos. Não envolva o `<PageHeader>` num wrapper extra — coloque direto como filho do `<AppShell>`.
101
+
102
+ ## Anatomia
103
+
104
+ ```
105
+ <PageHeader> (header.flex-col.gap-gp-md)
106
+ ├─ topRow (flex.justify-between.gap-gp-2xl)
107
+ │ ├─ textCol [optional] (flex-col.gap-gp-xs.flex-1)
108
+ │ │ ├─ titleRow (flex.gap-gp-md)
109
+ │ │ │ ├─ <h1 title>
110
+ │ │ │ └─ {badge}
111
+ │ │ └─ <p description>
112
+ │ └─ actionsRow [optional] (flex.gap-gp-sm)
113
+ │ └─ {actions}
114
+ └─ extraRow [optional — children] (w-full)
115
+ ```
116
+
117
+ ## Composição manual
118
+
119
+ Pra layouts que fogem do pattern (ex: title centralizado, ações em coluna), use os slots de styles direto:
120
+
121
+ ```tsx
122
+ import { pageHeaderStyles } from "@/components/ui/PageHeader";
123
+
124
+ const s = pageHeaderStyles();
125
+
126
+ <header className={s.root()}>
127
+ {/* sua estrutura custom */}
128
+ </header>
129
+ ```
@@ -0,0 +1,84 @@
1
+ # Panel — USAGE
2
+
3
+ <!-- ds:regras
4
+ - painel de DETALHE → siga o bloco `dsgreen-paneldetail-1` (o **padrão**). `-2` (tarefa com abas) e `-3` (com tabela) só se o usuário citar o ID
5
+ - header aqui é só STRING (`title`/`description`): precisa de avatar, Chip de status ou ação de ícone? → `FloatingPanel`
6
+ - form dentro dele → `<FormField>` + `gap-form-gap`, nunca `<label>` cru
7
+ - aba dentro dele → `<Tabs fullWidth>` na variante default (560px é estreito pro `line`)
8
+ -->
9
+
10
+ Drawer flutuante lateral (right/left/top/bottom) — default `md` (560px) com header + body scrollável + footer sticky.
11
+
12
+ ## Quando usar
13
+ - Detail/edit panel que precisa de mais espaço que `<Modal>`
14
+ - Forms longos com seções (preferível a Modal large)
15
+ - Quando user precisa ver conteúdo ao lado enquanto interage
16
+
17
+ ## Import
18
+ ```tsx
19
+ import { Panel } from "@/components/ui/Panel";
20
+
21
+ // Composição manual (avançado): PanelRoot, PanelTrigger, PanelContent,
22
+ // PanelHeader, PanelBody, PanelFooter, PanelCloseAction
23
+ ```
24
+
25
+ ## Variants
26
+ | Variant | Valores | Default | Tamanho |
27
+ |---|---|---|---|
28
+ | `side` | right / left / top / bottom | right | right/left: largura via `size`, altura full (gutter 24px); top/bottom: largura via inset, altura conforme conteúdo |
29
+ | `size` | sm / md / lg / xl / full \| string CSS | md | 320 / 560 / 720 / 920px / full (viewport − gutter). Aceita CSS arbitrário: `"720px"`, `"60vw"` |
30
+
31
+ ## Props essenciais
32
+ | Prop | Tipo | Função |
33
+ |---|---|---|
34
+ | `open` | boolean | Visibilidade (controlled) |
35
+ | `onOpenChange` | (open: boolean) => void | Callback de abertura/fechamento (o X do header fecha por aqui) |
36
+ | `trigger` | ReactNode | Elemento que abre o panel sem state externo (vira `<SheetTrigger asChild>`) |
37
+ | `side` | "right" \| "left" \| "top" \| "bottom" | Lado de ancoragem (default: "right") |
38
+ | `size` | "sm" \| "md" \| "lg" \| "xl" \| "full" \| string | Largura (default: "md" = 560px) |
39
+ | `title` | string | Header title |
40
+ | `description` | string | Header subtitle |
41
+ | `titleIcon` | LucideIcon | Ícone à esquerda do título (cor brand) |
42
+ | `footer` | ReactNode | Footer sticky — geralmente botões de ação (único caminho pro footer no modo all-in-one) |
43
+
44
+ ## Exemplo mínimo
45
+ ```tsx
46
+ <Panel
47
+ open={panelOpen}
48
+ onOpenChange={setPanelOpen}
49
+ side="right"
50
+ title="Editar cliente"
51
+ description="Salvo automaticamente"
52
+ footer={
53
+ <>
54
+ <Button variant="ghost" onClick={() => setPanelOpen(false)}>Cancelar</Button>
55
+ <Button onClick={handleSave}>Salvar</Button>
56
+ </>
57
+ }
58
+ >
59
+ <ClientForm />
60
+ </Panel>
61
+ ```
62
+
63
+ ## Painel de DETALHE de registro → o bloco `dsgreen-paneldetail-1`
64
+
65
+ Detalhe de registro tem estrutura definida e referenciável por ID: a composição inteira mora
66
+ em `src/blocks/paneldetail/` e renderiza em `#/blocks-paneldetail`. Cite o ID em vez de
67
+ recompor — `use a referência dsgreen-paneldetail-1 no painel de detalhe do pedido`. As zonas
68
+ (identidade no header · métricas em cards · seções colapsáveis · ação primária no footer) e o
69
+ porquê de cada uma estão documentados no arquivo do bloco.
70
+
71
+ ⚠️ **O bloco usa `FloatingPanel`, e a razão é este componente aqui:** o header do `Panel`
72
+ aceita `title`/`description` como **string**, então avatar, `Chip` de status inline e ação de
73
+ ícone não cabem nele. Precisa desse header → `FloatingPanel` (`titleSlot` + `headerActions`).
74
+ Escolha o `Panel` quando o que importa é **bloquear** a tela atrás e o header é texto simples.
75
+
76
+ ## Cuidados / Gotchas
77
+ - **Aba dentro do Panel** → `<Tabs fullWidth>` com a variante **default** (`segmented`). O Panel tem 560px: `line` aqui vira um trilho curto que lê como fragmento, e sem `fullWidth` as abas ficam num canto. `line` só se houver um `segmented` num nível acima.
78
+
79
+ - **Children são auto-embrulhados em `<PanelBody>`** — NÃO passar `<PanelBody>`/`<PanelFooter>` como filhos do `<Panel>` (duplica o body e o footer perde o sticky). Footer vai SEMPRE via prop `footer`. Pra composição manual, usar `PanelRoot`/`PanelTrigger`/`PanelContent` do barrel
80
+ - **Modal** (com backdrop suave / `SheetOverlay`) — bloqueia interação com página atrás. Pra non-modal, usar `<FloatingPanel>`
81
+ - **Mobile (<md)**: independente do `side`, vira **sheet bottom-up colado nas bordas** do device — flush nas laterais + bottom, só cantos superiores arredondados, sem outline/shadow, `max-height: 92vh`, slide bottom-up. O backdrop (modal) continua
82
+ - Body é único elemento scrollável (`overflow-y-auto` + `min-h-0`) — header e footer ficam fixos; conteúdo longo rola automaticamente
83
+ - **Footer fluido**: botões crescem lado a lado e **empilham quando não cabem** (`flex-wrap` + `flex-1` + `min-w-140px`). Não precisa passar `fullWidth` nos Buttons
84
+ - No desktop o `side` controla orientação/slide normalmente (right/left/top/bottom)
@@ -0,0 +1,421 @@
1
+ # Scheduler
2
+
3
+ **Categoria:** Data Display · **Dep:** `date-fns`
4
+
5
+ Calendário de **eventos ao longo do tempo**, com 4 modos de visualização
6
+ (mês · semana · dia · lista), toolbar embutida e detalhe por callback.
7
+
8
+ A toolbar traz: título do período + grupo `‹ Hoje ›` à esquerda; e à direita
9
+ busca, área custom, **seletor de view** (dropdown), **Filtro** e ação primária —
10
+ nessa ordem, do recorte mais amplo (qual período) pro mais específico (o que
11
+ dentro dele).
12
+
13
+ **O botão Filtro carrega dois sinais independentes:**
14
+
15
+ - **Verde** (`primary soft` + `border-border-brand` → brand a **14%** de
16
+ opacidade, texto e borda em brand) = a ferramenta está **engajada**: painel
17
+ aberto **ou** filtro aplicado.
18
+ - **Ponto** no canto = existe **filtro aplicado**, e só isso.
19
+
20
+ | Situação | Verde? | Ponto? |
21
+ |---|---|---|
22
+ | nada aberto, nada filtrado | não | não |
23
+ | painel aberto, sem filtro | **sim** | não |
24
+ | filtro aplicado, painel fechado | **sim** | **sim** |
25
+ | filtro aplicado, painel aberto | **sim** | **sim** |
26
+
27
+ A cor é a mesma receita do `ToolbarToolButton` do `TableToolbar` — o precedente
28
+ do DS pra "esta ferramenta tem algo ligado". O ponto **não** é redundante com
29
+ ela: é o que separa "abri pra olhar" de "tem filtro mexendo no que eu vejo", e é
30
+ o portador não-cromático da informação pra quem não distingue o verde.
31
+
32
+ O botão Filtro abre um **painel-coluna à direita da grade** — não um overlay.
33
+
34
+ > ⚠️ **Não confunda com `Calendar`.** `Calendar` é o primitivo
35
+ > shadcn/react-day-picker: **seletor de data** dentro de um form. O `Scheduler`
36
+ > exibe **eventos**. As intenções são opostas, por isso os nomes são distintos.
37
+
38
+ ---
39
+
40
+ ## Quando usar
41
+
42
+ | Precisa de | Use |
43
+ |---|---|
44
+ | escolher uma data num campo | `Calendar` / `DatePicker` |
45
+ | escolher mês+ano | `MonthYearPicker` |
46
+ | **ver compromissos numa grade de tempo** | **`Scheduler`** |
47
+ | grade de registros com colunas | `DataTable` |
48
+ | lista de cards | `DataList` |
49
+ | quadro por estágio de workflow | `Kanban` |
50
+
51
+ ---
52
+
53
+ ## Estado atual — o que está e o que NÃO está pronto
54
+
55
+ Leia antes de planejar uma tela em cima disto:
56
+
57
+ | | Status |
58
+ |---|---|
59
+ | view `month` | ✅ grade 6×7, multi-dia com pontas truncadas, `+N mais` em popover, `+` de criar no hover |
60
+ | views `week` / `day` | ✅ a MESMA view (`views/time-grid.tsx`) com 7 ou 1 coluna — gutter de horas, banda de dia inteiro, lane-packing, linha do "agora", faixa de hora clicável |
61
+ | view `list` | ✅ agenda agrupada por dia, **só os dias que têm evento** |
62
+ | drag & drop | ✅ **por ponteiro** — mover no mês (muda a data, preserva hora e duração), mover em week/day (coluna + minutos snapados), **redimensionar** pela borda do bloco em week/day |
63
+ | navegação por teclado | ✅ roving tabindex — cada grade é **uma** parada de `Tab`, setas movem dentro, `Home`/`End` vão às pontas da linha, `Enter` cria no slot focado. ⛔ **Arrastar por teclado não existe** — ver o gotcha do dnd |
64
+
65
+ O núcleo é puro e testado: `hooks/layout.test.ts` (51 casos de borda) mais
66
+ `hooks/use-scheduler-dnd.test.ts` (19 casos da resolução do drop).
67
+
68
+ ---
69
+
70
+ ## Props essenciais
71
+
72
+ | Prop | Tipo | Default | Nota |
73
+ |---|---|---|---|
74
+ | `events` | `SchedulerEvent[]` | — | nunca é mutado pelo componente |
75
+ | `view` / `defaultView` | `"month" \| "week" \| "day" \| "list"` | `"month"` | controlado / não-controlado |
76
+ | `date` / `defaultDate` | `Date` | hoje | idem |
77
+ | `onDateChange` · `onViewChange` | `(v) => void` | — | **obrigatórios no modo controlado** |
78
+ | `locale` | `Locale` (date-fns) | en-US | passe `ptBR` |
79
+ | `weekStartsOn` | `0`–`6` | `0` (domingo) | |
80
+ | `hourFormat` | `"12h" \| "24h"` | `"24h"` | |
81
+ | `onEventClick` | `(event, evt) => void` | — | é assim que o detalhe abre |
82
+ | `onSlotClick` | `(start, end) => void` | — | o `+` revelado no hover da célula |
83
+ | `searchable` | `boolean` | `true` | |
84
+ | `filterFields` | `SchedulerFilterField[]` | — | declarativo; o componente filtra sozinho |
85
+ | `filterModel` | `Record<string, string[]>` | — | controlado / **pré-aplicado** |
86
+ | `filterMode` | `"client" \| "server"` | `"client"` | `server` só emite |
87
+ | `toolbarActions` | `ReactNode` | — | área custom, entre o filtro e o seletor de view |
88
+ | `defaultFilterPanelOpen` | `boolean` | `false` | o painel-coluna de filtro já vem aberto |
89
+ | `primaryAction` | `ReactNode` | — | botão primário à direita |
90
+ | `renderEvent` | `({event, view, selected}) => ReactNode` | — | troca só o miolo |
91
+
92
+ ### `SchedulerEvent`
93
+
94
+ | Campo | Tipo | Nota |
95
+ |---|---|---|
96
+ | `id` | `string` | |
97
+ | `title` | `ReactNode` | |
98
+ | `start` · `end` | `Date` | **`end` é obrigatório**; duração zero se diz `end === start` |
99
+ | `allDay` | `boolean` | |
100
+ | `color` | `"brand" \| "info" \| "success" \| "warning" \| "danger" \| "neutral"` | default `"brand"` |
101
+ | `categoryId` · `tagIds` | `string` · `string[]` | filtráveis |
102
+ | `description` | `ReactNode` | aparece na view `list` |
103
+ | `searchText` | `string` | **necessário quando `title` não é string** |
104
+ | `meta` | `unknown` | payload cru, devolvido em `onEventClick` |
105
+
106
+ ---
107
+
108
+ ## Exemplo mínimo
109
+
110
+ ```tsx
111
+ import { ptBR } from "date-fns/locale";
112
+ import { Scheduler } from "@/components/ui/Scheduler";
113
+
114
+ <div className="h-[720px]">
115
+ <Scheduler
116
+ events={eventos}
117
+ locale={ptBR}
118
+ filterFields={[
119
+ { id: "categoryId", label: "Categoria", options: [
120
+ { value: "interna", label: "Interna" },
121
+ { value: "cliente", label: "Cliente" },
122
+ ]},
123
+ ]}
124
+ onEventClick={(evento) => setSelecionado(evento)}
125
+ primaryAction={<Button variant="filled" size="sm">Novo evento</Button>}
126
+ />
127
+ </div>
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Gotchas
133
+
134
+ ### 1. O pai precisa ter altura — e é a altura dele que dimensiona tudo
135
+
136
+ A grade é `flex-1` dentro de um `flex-col`. Num pai sem altura definida ela
137
+ colapsa pra altura mínima das células. Embrulhe em algo com altura
138
+ (`h-[720px]`, ou `flex-1 min-h-0` numa página que já tem altura).
139
+
140
+ **Não existe prop de "tela cheia".** O componente já é `h-full`: pra usá-lo como
141
+ página inteira, dê `h-screen` ao container e deixe o `Scheduler` em `flex-1`.
142
+
143
+ E isso não é só esticar — **a célula do mês passa a caber mais eventos**. O corte
144
+ do `+N mais` é derivado da altura MEDIDA da linha, como as linhas da tabela.
145
+ Medido no browser, mesmo dataset:
146
+
147
+ | viewport | altura da linha | pills por célula | células com "+N mais" |
148
+ |---|---|---|---|
149
+ | 800px | 76px | **2** | 13 |
150
+ | 1400px | 176px | **6** | 2 |
151
+
152
+ Pra travar num número fixo (quando a tela precisa de altura de linha previsível
153
+ independentemente do conteúdo), a view de mês aceita `maxPerCell` — mas o
154
+ default adaptativo é o que aproveita a tela.
155
+
156
+ Exemplo vivo: **Scheduler Full Screen**, na categoria Examples do showcase
157
+ (`#/scheduler-full`).
158
+
159
+ > ⚠️ A re-medição pós-montagem depende de `ResizeObserver`. Ela **não pôde ser
160
+ > verificada** no browser de teste: a emulação de viewport por CDP não dispara
161
+ > `ResizeObserver`, nem `window.resize`, nem `MediaQueryList.change` — medido,
162
+ > zero callbacks nos três. O que foi verificado é a montagem correta em cada
163
+ > altura (a tabela acima veio de reloads reais). Em browser de verdade o
164
+ > `ResizeObserver` é o mesmo mecanismo que o resto do DS usa.
165
+
166
+ ### 2. A cor **nunca** vai no texto — e nunca é o único portador da informação
167
+
168
+ Medido em WCAG antes de decidir: `text-fg-{cor}` sobre a pílula tingida dá
169
+ **1.72–4.49** no claro e **2.97–4.31** no escuro. Nenhuma das 6 famílias passa
170
+ AA, e `warning` chega a 1.72:1.
171
+
172
+ Por isso o texto é sempre `fg-default` (medido no browser: **16.2–18.2** no
173
+ claro, **16.4–17.8** no escuro — AAA nos dois modos) e a cor mora no **dot, na
174
+ borda e no tint de fundo**.
175
+
176
+ Corolário pra quem monta a tela: **cor é reforço**. O título tem que dizer o
177
+ que o evento é — quem não distingue as cores precisa continuar entendendo a
178
+ agenda.
179
+
180
+ ### 3. O teto real é ~5 categorias, não 6
181
+
182
+ `brand` (matiz 151) e `success` (161) estão a **10°** de distância. Como tint a
183
+ 14% os dois viram praticamente o mesmo off-white — **não use os dois como
184
+ categorias distintas no mesmo calendário**.
185
+
186
+ `info` é matiz 280, ou seja **violeta**: se você precisa de "roxo", ele já
187
+ existe.
188
+
189
+ Precisa de 6+ categorias simultâneas e distinguíveis? Isso é o gatilho pra
190
+ abrir uma paleta categórica própria (`event-*`) via cascata de token — não
191
+ force a união atual.
192
+
193
+ ### 4. Busca não lê `title` rico
194
+
195
+ O motor casa **só string**: `searchText`, `title` (se for string),
196
+ `description` (idem) e `tagIds`. Varrer a árvore de um `ReactNode` é caro e
197
+ frágil, então não é feito.
198
+
199
+ Consequência: `title={<b>Reunião</b> — Cliente X}` **não é encontrável** —
200
+ falha em silêncio. Declare `searchText` nesses casos.
201
+
202
+ ### 4b. O painel de filtro é uma COLUNA, e some abaixo de 1024px
203
+
204
+ O botão **Filtro** não abre popover nem sheet: abre uma coluna à direita que
205
+ **empurra** a grade (~296px). É deliberado — filtro é o controle cujo resultado
206
+ você quer ver *enquanto* mexe, e por cima da grade isso viraria
207
+ marcar → fechar → olhar → reabrir.
208
+
209
+ Três consequências práticas:
210
+
211
+ - **A grade encolhe.** Se a sua tela é estreita, prefira deixar
212
+ `defaultFilterPanelOpen` em `false` (o default).
213
+ - **O cabeçalho do painel é `sticky` e a barra é a do DS.** O bloco "Filtros /
214
+ Limpar / ×" fica fixo no topo enquanto o resto rola, com `bg-bg-surface`
215
+ opaco — `sticky` não cria fundo, e sem ele o mini-calendário passaria por
216
+ baixo do título. A rolagem usa `scrollbar-thin`, a `@utility` do tema gerado
217
+ (trilho transparente + thumb tokenizado), que é o padrão do repo com 26 usos.
218
+ - **O painel nunca estica a altura da linha.** Ele acompanha a altura da grade e
219
+ **rola por dentro** (medido: 664px de caixa com 958px de conteúdo). É por isso
220
+ que o root é `h-full` — sem isso ele dimensionava por conteúdo, o painel ficava
221
+ mais alto que a grade e passava a mandar na altura das duas, vazando pra fora
222
+ do container. Se você vir o calendário cortado, o pai é que está sem altura
223
+ (gotcha nº 1).
224
+ - **Abaixo de 1024px o painel vira DRAWER**, não some. A coluna não cabe junto
225
+ de uma semana legível, então nessa faixa o mesmo painel abre num
226
+ `FloatingPanel side="right"` por cima — o veículo que `DataTable` e
227
+ `DataList` já usam pro filtro deles (`ToolbarSimpleFilterDrawer`). Mesmo
228
+ conteúdo, mesmo estado, mesmo botão: só o invólucro muda.
229
+
230
+ No drawer, escolher uma data no mini-calendário **fecha** o painel — ele
231
+ cobre a grade, e manter aberto esconderia justamente o resultado. Como
232
+ coluna isso não acontece.
233
+
234
+ ⚠️ **O breakpoint vive em UM lugar só:** `useMediaQuery("(min-width: 1024px)")`
235
+ em `scheduler.tsx`. Não acrescente media query no `schedulerFilterAside`.
236
+
237
+ > Até 2026-09-01 este parágrafo mandava mudar **dois** lugares — o `lg:flex` do
238
+ > style *mais* o hook. O `lg:flex` foi removido justamente porque a duplicação
239
+ > produzia um estado em que o botão se dizia aberto e o painel estava
240
+ > `display: none`; o `scheduler.tsx` diz isso em maiúsculas desde então. A doc
241
+ > continuou mandando reintroduzir o defeito — L-060: comentário é load-bearing.
242
+
243
+ ### 4c. "Nada marcado" = sem filtro, não "esconde tudo"
244
+
245
+ Num app de calendário de verdade, desmarcar todas as agendas esconde tudo.
246
+ **Aqui não**: campo sem opção marcada significa *sem restrição* — a convenção do
247
+ `filterModel` do DS, a mesma do `DataTable` e do `DataList`. O cabeçalho do
248
+ grupo mostra **"Todas"** nesse estado, pra não parecer que a seleção se perdeu.
249
+
250
+ Inverter isso só neste componente faria o mesmo `filterModel` significar coisas
251
+ opostas em telas diferentes.
252
+
253
+ ### 4d. A contagem ao lado da opção ignora os filtros de campo
254
+
255
+ O número é "quantos eventos casam com a **busca** e têm esta opção" — **não**
256
+ "quantos vou ver se marcar".
257
+
258
+ A base exclui de propósito todos os filtros de campo, inclusive o do próprio
259
+ campo, pra que os números fiquem **estáveis enquanto você marca e desmarca**.
260
+ Com a facet count clássica (base = os outros campos já aplicados), cada clique
261
+ mexeria nos números das outras listas, e a leitura vira alvo móvel exatamente na
262
+ hora de decidir. As duas leituras são defensáveis; confundi-las é que engana.
263
+
264
+ ### 5. `filterFields` no modo `client` só filtra 3 ids
265
+
266
+ `categoryId`, `tagIds` e `color`. Qualquer outro `id` **renderiza o chip e não
267
+ filtra nada** — o componente emite `console.warn` em DEV nomeando o campo.
268
+ Para filtrar por campo próprio, use `filterMode="server"` e filtre fora.
269
+
270
+ ### 5b. Drag & drop: você PRECISA aplicar a mudança
271
+
272
+ O componente é dumb sobre mutação — ele emite e para:
273
+
274
+ ```tsx
275
+ const [eventos, setEventos] = useState(EVENTOS);
276
+ const aplicar = ({ id, start, end }) =>
277
+ setEventos((a) => a.map((e) => (e.id === id ? { ...e, start, end } : e)));
278
+
279
+ <Scheduler
280
+ events={eventos}
281
+ draggable
282
+ resizable
283
+ onEventMove={aplicar}
284
+ onEventResize={aplicar}
285
+ />
286
+ ```
287
+
288
+ O que cada gesto significa:
289
+
290
+ | gesto | resultado |
291
+ |---|---|
292
+ | arrastar no **mês** | muda a **data**; hora e duração preservadas |
293
+ | arrastar em **week/day** | combina coluna (dia) + `delta.y` (minutos, snapado por `snapMinutes`) |
294
+ | arrastar a **borda** do bloco (week/day) | muda a **duração**, uma ponta só |
295
+
296
+ Detalhes que evitam surpresa:
297
+
298
+ - **A duração nunca é snapada separadamente.** Um evento de 50min movido continua
299
+ com 50min — snapar as duas pontas o encurtaria pra 45 a cada arrasto.
300
+ - **Resize não existe no mês** (a altura da pílula não representa duração) nem em
301
+ evento `allDay`.
302
+ - **All-day em week/day ignora o `delta.y`** e só troca de dia: ele mora na banda,
303
+ não na grade de horas.
304
+ - **Arrastar pra baixo perto do fim da grade encosta no limite** em vez de vazar
305
+ pro dia seguinte. Mudar de dia se faz atravessando a coluna — o gesto explícito.
306
+ - **O bloco que segue o cursor é um `DragOverlay`, em `position: fixed`.** É
307
+ obrigatório: três ancestrais cortam (o frame da grade, a pilha de eventos da
308
+ célula e o corpo rolável de week/day), e elemento transformado continua sujeito
309
+ ao clipping do ancestral — a primeira versão movia o próprio bloco e ele
310
+ **desaparecia** ao sair da célula. Se você embrulhar o `Scheduler` num
311
+ container com `transform`, `filter` ou `contain: paint`, esse container passa
312
+ a ser o containing block do `fixed` e o defeito volta.
313
+ - **Só dá pra mover pra um dia VISÍVEL na grade.** No mês isso inclui as células
314
+ cinzas dos meses vizinhos (a grade de setembro/2026 vai de 30/ago a 10/out, e
315
+ as duas pontas são alvo válido). Pra um mês fora da tela, navegue primeiro —
316
+ não há auto-avanço ao arrastar sobre as setas.
317
+
318
+ - **O dnd é por PONTEIRO.** O `KeyboardSensor` do dnd-kit não está registrado,
319
+ de propósito: ele reivindica `Space`/`Enter`, que num `<button>` de evento já
320
+ significam "abrir o detalhe" — as duas coisas brigavam e o arraste era
321
+ cancelado (medido na live region do dnd-kit). `Space`/`Enter` abrem o detalhe,
322
+ que é onde data e hora se editam em campo de formulário: a rota acessível pra
323
+ reagendar. O raciocínio completo está no topo de `use-scheduler-dnd.ts`.
324
+
325
+ > ⚠️ O gesto de ponteiro **não pôde ser verificado no browser de teste**: o
326
+ > `PointerSensor` usa `setPointerCapture` com `pointerId` real, e nem
327
+ > `left_click_drag` nem `PointerEvent` sintéticos o ativam — medido, o evento não
328
+ > sai do lugar. A resolução do drop é coberta por
329
+ > `hooks/use-scheduler-dnd.test.ts` (20 casos), e no DOM foi medido: 12 blocos
330
+ > com 24 alças na semana, e o droppable respondendo (a live region do dnd-kit
331
+ > nomeou `day:<ms>` ao passar por cima).
332
+
333
+ ### 5c. Teclado: a grade é UMA parada de `Tab`
334
+
335
+ Roving tabindex (padrão WAI-ARIA de `grid`): a célula ativa tem `tabIndex=0`, as
336
+ outras `-1`.
337
+
338
+ | tecla | ação |
339
+ |---|---|
340
+ | `←` `→` | dia anterior / seguinte |
341
+ | `↑` `↓` | mesma coluna, linha acima / abaixo (mês: semana; week/day: hora) |
342
+ | `Home` / `End` | início / fim da **linha** — não da grade |
343
+ | `PageUp` / `PageDown` | primeira / última célula |
344
+ | `Enter` ou `Space` | cria no slot focado (dispara `onSlotClick`) |
345
+
346
+ Sem isso a grade do mês custava **42 `Tab`** e a da semana **168**. O `+` de
347
+ criar tem `tabIndex={-1}` de propósito: ele é alcançado pelo `Enter` na célula,
348
+ e deixá-lo tabbável reintroduziria as 42 paradas.
349
+
350
+ Medido no browser: `→` levou de 0 pra 1, `↓` de 1 pra 8 (+7), `Home` de 8 pra 7
351
+ (início da linha), com sempre exatamente 1 célula em `tabIndex=0`.
352
+
353
+ ### 6. `draggable` e `resizable` nascem `false`
354
+
355
+ Deliberado (mesmo default do `enableDnD` do `Kanban`): dnd ligado sem
356
+ `onEventMove` conectado deixa o usuário arrastar e ver o evento voltar sozinho
357
+ — o pior estado possível, porque parece bug do app.
358
+
359
+ O componente **não liga o dnd** se não houver `onEventMove`/`onEventResize`
360
+ conectado, e em DEV avisa por `console.warn` nomeando qual falta. `event.draggable`
361
+ e `event.resizable` sobrepõem por evento — é como se diz "esta reunião é fixa"
362
+ sem desligar o board todo.
363
+
364
+ ### 6b. `SchedulerFilterPanel` também é exportado solto
365
+
366
+ Mesmo padrão do `TableToolbar`, que expõe `ToolbarSearch`, `ToolbarSegmented` e
367
+ as outras partes pelo barrel. Serve pra tela que queira posicionar o painel em
368
+ outro lugar do próprio layout — e pra doc page, que o renderiza isolado pra
369
+ mostrar a anatomia (cabeçalho sticky · mini-calendário · um grupo por campo).
370
+
371
+ **Em uso normal você não precisa dele:** o `Scheduler` monta e controla o seu.
372
+ Usando solto, todo o estado é seu — `filterModel`, `counts`, `date`, `now` e os
373
+ callbacks vêm por prop, e nada é derivado sozinho.
374
+
375
+ #### `embedded` — quando o painel NÃO é a superfície
376
+
377
+ | | `embedded={false}` (default) | `embedded` |
378
+ |---|---|---|
379
+ | Largura | `w-[280px]` fixa | `w-full` |
380
+ | Moldura | borda + radius + `bg-bg-surface` | nenhuma |
381
+ | Cabeçalho próprio | título + Limpar + × | não renderiza |
382
+
383
+ Ligue quando ele viver **dentro** de outro contêiner que já é a superfície e já
384
+ traz cabeçalho — é o que o próprio `Scheduler` faz no modo drawer (<1024px),
385
+ dentro de um `FloatingPanel`.
386
+
387
+ São duas mudanças numa prop só porque é uma decisão só: *"existe outro contêiner
388
+ em volta?"*. Separadas, dava pra combinar metade de cada e obter um card de 280px
389
+ sem cabeçalho dentro de um painel de 375 — que é exatamente o defeito.
390
+
391
+ ### 7. O painel de detalhe é seu, não do componente
392
+
393
+ O `Scheduler` não importa `FloatingPanel`. Ele emite
394
+ `onEventClick(event, evt)` e devolve `event.meta` intacto; quem monta o painel
395
+ é a tela. O padrão de referência é o bloco **`dsgreen-paneldetail-2`**
396
+ (`side="right"`, `size="lg"`, `titleSlot` com o contexto, lista plana de
397
+ propriedades) — há um exemplo completo em `src/preview/pages/SchedulerDoc.tsx`.
398
+
399
+ ⚠️ O body do `FloatingPanel` **não tem gap entre filhos**. Sem um wrapper
400
+ `flex flex-col gap-gp-2xl`, título e lista ficam colados.
401
+
402
+ ### 8. No mês, evento multi-dia aparece por dia, não como barra contínua
403
+
404
+ `hooks/layout.ts` já calcula `colStart`/`colSpan` por linha, mas a view desenha
405
+ o evento em **cada dia que ocupa**, com as pontas truncadas marcando a
406
+ continuação. A barra única atravessando as colunas exige reservar a mesma faixa
407
+ vertical em todas as células (lane-packing por linha da grade) e vem com o dnd.
408
+ A extensão do evento é lida corretamente — o que falta é refinamento visual.
409
+
410
+ ### 9. `filterModel` pré-aplicado é o caminho certo pra "já vem filtrado"
411
+
412
+ Nunca monte um form de filtro acima da grade (L-051). Passe `filterModel` e o
413
+ chip aparece já aplicado, com o `×` pra desfazer.
414
+
415
+ ### 10. Nada aqui importa de `TableToolbar`
416
+
417
+ A gramática visual do segmented, da busca e dos chips é **copiada** em
418
+ `scheduler.styles.ts`, não importada. Cross-import entre pastas de `ui/` é o
419
+ que gerou o `registryDependency` dangling da L-049 (`@igreen/table-toolbar` não
420
+ existe como item de registry). Se você editar o visual da toolbar do
421
+ `TableToolbar`, considere se esta cópia deve acompanhar.