@softize/opus 30.0.0 → 31.1.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 (64) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/bin/lib/check.mjs +109 -101
  3. package/bin/lib/copy.mjs +2973 -1801
  4. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +52 -0
  5. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +27 -2
  6. package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +142 -0
  7. package/docs/adr/0064-action-list-com-itens-e-fontes-externas.md +47 -0
  8. package/docs/adr/0065-presentation-composes-settings-sections.md +295 -0
  9. package/docs/adr/0066-civil-date-input-and-calendar-list.md +28 -0
  10. package/docs/adr/README.md +6 -0
  11. package/package.json +1 -1
  12. package/registry/skills/build-opus-ui/SKILL.md +21 -19
  13. package/registry/skills/build-opus-ui/references/evaluations.md +13 -11
  14. package/registry/skills/build-opus-ui/references/ui-patterns.md +24 -27
  15. package/src/core/presentation.ts +938 -32
  16. package/src/core/types.ts +2 -0
  17. package/src/ui/components/patterns/form.tsx +22 -1
  18. package/src/ui/components/patterns/grouped-list.ts +2 -2
  19. package/src/ui/components/patterns/list-calendar.tsx +129 -0
  20. package/src/ui/components/patterns/list.tsx +612 -382
  21. package/src/ui/components/patterns/page-heading-context.tsx +12 -5
  22. package/src/ui/components/patterns/page.tsx +227 -197
  23. package/src/ui/components/patterns/presentation-fields.tsx +246 -0
  24. package/src/ui/components/patterns/presentation-sections.tsx +494 -0
  25. package/src/ui/components/patterns/presentation.tsx +1142 -229
  26. package/src/ui/components/patterns/structural-header.tsx +23 -32
  27. package/src/ui/components/patterns/surface-anatomy.ts +27 -217
  28. package/src/ui/components/patterns/surface.tsx +19 -29
  29. package/src/ui/components/patterns/trigger.tsx +10 -2
  30. package/src/ui/components/primitives/calendar.tsx +39 -18
  31. package/src/ui/components/primitives/close-button.tsx +11 -3
  32. package/src/ui/components/primitives/command.tsx +2 -8
  33. package/src/ui/components/primitives/date-input.tsx +276 -0
  34. package/src/ui/components/primitives/detail.tsx +1 -1
  35. package/src/ui/components/primitives/dialog.tsx +19 -81
  36. package/src/ui/components/primitives/drawer.tsx +17 -79
  37. package/src/ui/components/primitives/field.tsx +1 -1
  38. package/src/ui/components/primitives/pagination.tsx +3 -2
  39. package/src/ui/components/primitives/table.tsx +1 -1
  40. package/src/ui/components/primitives/tabs.tsx +13 -2
  41. package/src/ui/docs/content/action-form.md +6 -1
  42. package/src/ui/docs/content/action-list.md +156 -68
  43. package/src/ui/docs/content/action-trigger.md +5 -0
  44. package/src/ui/docs/content/button.md +5 -6
  45. package/src/ui/docs/content/calendar-month-view.md +50 -0
  46. package/src/ui/docs/content/calendar.md +6 -1
  47. package/src/ui/docs/content/command.md +62 -50
  48. package/src/ui/docs/content/compositions.md +129 -459
  49. package/src/ui/docs/content/date-input.md +67 -0
  50. package/src/ui/docs/content/dialog.md +44 -112
  51. package/src/ui/docs/content/drawer.md +44 -103
  52. package/src/ui/docs/content/field.md +2 -1
  53. package/src/ui/docs/content/input.md +3 -0
  54. package/src/ui/docs/content/page.md +204 -412
  55. package/src/ui/docs/content/presentation.md +371 -23
  56. package/src/ui/docs/content/separator.md +1 -1
  57. package/src/ui/docs/content/surface.md +65 -120
  58. package/src/ui/docs/content/tokens.md +18 -4
  59. package/src/ui/docs/registry.tsx +38 -9
  60. package/src/ui/drivers/context.tsx +11 -4
  61. package/src/ui/drivers/react.tsx +27 -7
  62. package/src/ui/meta.ts +17 -6
  63. package/src/ui/react.tsx +41 -8
  64. package/src/ui/theme.css +44 -33
@@ -22,8 +22,8 @@ A barra distribui os controles `inline` em mais de uma linha quando necessário,
22
22
  ordem. Ela não move filtros automaticamente para o modal. Seletores de valor único acompanham o
23
23
  valor escolhido até `20rem` e truncam o excedente; a busca varia entre `8rem` e `16rem`.
24
24
  Declare como `advanced` os filtros que não precisam ficar sempre à vista, como descreve a seção
25
- “Onde cada filtro aparece”. Recarregar e exibição formam um `ButtonGroup` espaçado;
26
- `toolbarActions` acrescenta, depois deles, operações ligadas ao recorte atual.
25
+ “Onde cada filtro aparece”. As opções da lista e os filtros avançados formam um `ButtonGroup` espaçado;
26
+ `toolbarActions` acrescenta, depois deles, ações da coleção, como criar, importar e exportar.
27
27
 
28
28
  ```tsx preview col
29
29
  render(
@@ -41,24 +41,30 @@ render(
41
41
 
42
42
  ## Coleção nomeada
43
43
 
44
- Em uma página, envolva a listagem com [`Surface`](/ui/surface). O título e a criação ocupam
45
- extremos opostos do cabeçalho da coleção; busca, filtros e atualização permanecem na toolbar do
46
- `ActionList`.
44
+ Em uma página, use [`Page`](/ui/page) para o título e ações gerais. Ações da coleção
45
+ ficam na barra do `ActionList`, inclusive a criação, mesmo sem depender dos filtros atuais.
46
+ Em abas, a barra acompanha a lista do painel ativo.
47
47
 
48
48
  ```tsx
49
- <Surface
50
- as="section"
51
- title="Workspaces"
52
- level={1}
53
- actions={<Button>Criar workspace</Button>}
54
- >
55
- <ActionList action={workspaceList} input={{}} />
56
- </Surface>
49
+ <Page title="Workspaces" layout="fill">
50
+ <ActionList
51
+ action={workspaceList}
52
+ input={{}}
53
+ toolbarActions={
54
+ <Button aria-label="Criar workspace" onClick={openCreate}>
55
+ Criar
56
+ </Button>
57
+ }
58
+ />
59
+ </Page>
57
60
  ```
58
61
 
59
- Use `toolbarActions` somente quando a operação depender do recorte visível, como exportar os
60
- resultados filtrados. A propriedade recebe os nós já prontos: em uma ação somente com ícone, forneça
61
- você mesmo o `aria-label` e o `Tooltip` com o nome da operação.
62
+ O botão exibe “Criar”; seu nome acessível e o formulário identificam o recurso.
63
+ Menu com vários tipos de criação preserva os nomes específicos. Operações sobre a seleção
64
+ usam `batchActions`, e ações de um registro usam `rowActions`.
65
+
66
+ `toolbarActions` recebe os nós já prontos e aparece também em visualizações sem filtros
67
+ ou tabela. Em uma ação somente com ícone, forneça `aria-label` e tooltip.
62
68
 
63
69
  ## Barra de filtros compartilhada
64
70
 
@@ -228,7 +234,7 @@ Declare `grouping.fields` quando uma dimensão enumerável puder organizar a tab
228
234
  recorte de negócio. Cada campo precisa ser um filtro `select` ou `lookup`; texto, número e data não
229
235
  são dimensões de agrupamento. O filtro pode aceitar uma ou várias opções: cada consulta de grupo
230
236
  envia um único valor na forma declarada. A lista continua plana por padrão, e a pessoa escolhe
231
- **Agrupar por** em **Exibição**.
237
+ **Agrupar por** em **Opções da lista**.
232
238
 
233
239
  ```ts
234
240
  filters: {
@@ -261,9 +267,17 @@ aplicados à mesma dimensão restringem os grupos exibidos.
261
267
 
262
268
  ## Paginação e carregamento
263
269
 
264
- `ActionList` envia `limit`, definido por `pageSize`, e `page` no input. O handler aplica o recorte e
270
+ Para coleções pequenas que devem aparecer por inteiro, declare `paginate: false` no
271
+ contrato da list action. ActionList omite `page` e `limit` da consulta, do estado lido
272
+ da URL e dos links gerados; não exibe rodapé nem seletor de quantidade. O handler deve
273
+ devolver todos os itens que atendem à busca e aos filtros. O componente não busca páginas
274
+ adicionais em segundo plano nem aumenta o limite artificialmente. A opção também é
275
+ respeitada pelas listas renderizadas por Presentation, sem configuração visual duplicada.
276
+
277
+ Nos demais modos, `ActionList` envia `limit`, definido por `pageSize`, e `page` no input. O handler aplica o recorte e
265
278
  devolve `total` na resposta paginada. Quando existe total, o rodapé mostra a página atual, os links
266
- disponíveis e a quantidade de itens. Alterar busca, filtro, ordenação ou período retorna à primeira
279
+ disponíveis e a quantidade de itens. Com total zero, o rodapé e seu divisor não aparecem:
280
+ a mensagem de vazio permanece como indicação do resultado. Alterar busca, filtro, ordenação ou período retorna à primeira
267
281
  página. Durante a primeira consulta, `DataState` apresenta o carregamento; em atualizações
268
282
  posteriores, a lista permanece visível com `aria-busy`.
269
283
 
@@ -279,20 +293,8 @@ rodapé podem continuar visíveis enquanto só os resultados rolam. Para isso, d
279
293
  render(
280
294
  <DocBrowserActionProvider>
281
295
  <div className="h-80 w-full overflow-x-auto overflow-y-hidden rounded-lg border border-border">
282
- <Page layout="fill">
283
- <PageBody>
284
- <Surface as="section" level={1}>
285
- <SurfaceHeader>
286
- <SurfaceTitle>Workspaces</SurfaceTitle>
287
- <SurfaceActions>
288
- <Button>Criar workspace</Button>
289
- </SurfaceActions>
290
- </SurfaceHeader>
291
- <SurfaceBody>
292
- <ActionList action={docWorkspaceList} input={{}} />
293
- </SurfaceBody>
294
- </Surface>
295
- </PageBody>
296
+ <Page title="Workspaces" layout="fill">
297
+ <ActionList action={docWorkspaceList} input={{}} toolbarActions={<Button aria-label="Criar workspace">Criar</Button>} />
296
298
  </Page>
297
299
  </div>
298
300
  </DocBrowserActionProvider>,
@@ -319,8 +321,8 @@ componentes no caminho. Nos demais casos, a decisão é sua:
319
321
 
320
322
  - Para a lista manter a altura dos resultados, como em uma seção secundária cuja rolagem pertence ao
321
323
  `PageBody`, declare `layout="flow"`.
322
- - Um contêiner próprio entre `SurfaceBody` e a lista, como um `TabsPanel`, não repassa a altura, e
323
- a lista volta a `flow`. Declare `layout="fill"` na lista e dê ao contêiner as classes
324
+ - `TabsPanel` repassa a altura disponível à lista. Um contêiner próprio sem essa integração
325
+ interrompe a herança: declare `layout="fill"` na lista e dê ao contêiner as classes
324
326
  `flex min-h-0 flex-1 flex-col`.
325
327
  - Dentro de [`Surface`](/ui/surface), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou
326
328
  [`Pane`](/ui/pane), a lista não herda o `fill` da página: a altura do corpo pertence à
@@ -442,13 +444,17 @@ filters: {
442
444
 
443
445
  ## Exibição e estado na URL
444
446
 
445
- O botão de exibição abre as preferências da listagem em qualquer visualização. Na tabela, permite
447
+ O botão **Opções da lista** reúne as preferências disponíveis para a visualização. Na tabela, permite
446
448
  escolher a ordenação, inverter sua direção, agrupar e mostrar ou ocultar colunas, inclusive as
447
- declaradas com `hidden`; em todas as visualizações, permite alterar a quantidade de itens por
448
- página. Um ponto sinaliza quando alguma preferência diverge do padrão. O rodapé explicita a
449
+ declaradas com `hidden`; nas listas paginadas, permite alterar a quantidade de itens por
450
+ página. Quando não há preferências disponíveis, o botão não aparece. Um ponto sinaliza quando alguma preferência diverge do padrão. O rodapé explicita a
449
451
  ordenação e o agrupamento ativos junto da paginação e do total. Busca, ordenação, filtros,
450
452
  visualização, colunas e limite podem ser serializados na query string pelos helpers do componente:
451
453
 
454
+ Operações usam `invalidates` para atualizar os dados, e o consumidor pode manter suas
455
+ assinaturas de eventos. Em caso de falha, o estado de erro oferece **Tentar novamente**
456
+ diretamente. Não há comando de recarga manual no menu nem polling automático no ActionList.
457
+
452
458
  Cada campo de `sort.fields` precisa de uma coluna com rótulo textual para aparecer no seletor e no
453
459
  resumo do rodapé; a coluna pode estar `hidden`. Campos sem coluna continuam válidos para o handler e
454
460
  para uma ordenação padrão, mas o Opus não expõe seu identificador técnico na interface. **Sem
@@ -468,9 +474,55 @@ restaurá-lo.
468
474
 
469
475
  Convenção compacta (defaults omitidos): `?q=…&sort=key:dir&status=…&view=board&cols=a,b,c&limit=25`.
470
476
 
477
+ ## Itens conectados
478
+
479
+ Para coleções que apresentam um nome, uma descrição e ações por registro, declare os campos
480
+ em `item`. O ActionList monta a moldura e os separadores com ItemGroup; mantém carregamento,
481
+ erro, recuperação e vazio. `title` coloca o nome da coleção junto das `toolbarActions`.
482
+ Nome e descrição ocupam uma linha cada, com reticências quando ultrapassam a largura disponível.
483
+
484
+ ```tsx preview col
485
+ render(
486
+ <DocBrowserActionProvider>
487
+ <ActionList
488
+ action={docWorkspaceList}
489
+ input={{}}
490
+ title="Workspaces"
491
+ item={{ title: "name", description: "client" }}
492
+ />
493
+ </DocBrowserActionProvider>
494
+ );
495
+ ```
496
+
497
+ Os nomes em `item` são campos diretos do resultado, com valores textuais ou numéricos.
498
+ O objeto é serializável. `rowActions` recebe o registro e um contexto com `items`, `total`,
499
+ `isFetching`, `isError` e `refetch`; `toolbarActions` também pode ser uma função desse contexto.
500
+ O modo de itens não combina `children`, `views` ou seleção em lote. `onRowClick` continua
501
+ restrito à tabela; ações dos itens ficam nos controles explícitos de `rowActions`.
502
+
503
+ Com uma página, esse modo dispensa o rodapé. Com mais resultados, mantém a paginação.
504
+ Depois de remover o último item de uma página, volta à última página válida. A tabela conserva
505
+ seu rodapé e seus controles existentes.
506
+
507
+ ### Fonte governada por outro serviço
508
+
509
+ Quando a coleção pertence a uma integração que já possui cliente e autorização, `source`
510
+ conecta esse cliente sem criar uma action artificial. Declare uma chave com o escopo de acesso
511
+ e um `load(input, { signal })` que retorna `{ items, total, cursor }`. A fonte deve respeitar
512
+ `page` e `limit`; se o serviço retorna todos os registros, o adapter pagina o conjunto completo.
513
+
514
+ A fonte substitui `action`, exige QueryClientProvider e dispensa OpusProvider quando não há
515
+ actions nos controles. Seu cache é separado das actions, não reaproveita dados entre escopos e
516
+ é descartado após a desmontagem. Falhas usam a mensagem pública `errorMessage` e oferecem
517
+ recuperação; detalhes técnicos do erro não são exibidos. Dados sensíveis usados pelo cliente
518
+ externo devem permanecer no adapter, fora dos itens retornados.
519
+
520
+ `source` é runtime: seu loader não entra no JSON. Uma definição serializável futura deverá
521
+ referenciar uma fonte registrada, enquanto `item` já descreve a apresentação sem JSX.
522
+
471
523
  ## Composição dos itens
472
524
 
473
- Passe `children` para substituir a tabela por uma composição própria, como uma lista de
525
+ Quando `item` não representar a composição necessária, passe `children` para substituir a tabela por uma composição própria, como uma lista de
474
526
  [`Item`](/ui/item). A barra de filtros e os estados de carregamento, erro e vazio continuam sendo
475
527
  tratados por `ActionList`. O contrato não leva o tipo do item até o componente: anote o parâmetro de
476
528
  `children` (ou de `render`, em uma visualização) ou declare os genéricos, como em
@@ -516,6 +568,26 @@ render(
516
568
  );
517
569
  ```
518
570
 
571
+ ## Período controlado pelo consumidor
572
+
573
+ Use `periodControl` quando a coleção precisar de uma navegação temporal própria, como um mês de expediente. O controle ocupa o mesmo espaço do seletor padrão e respeita `periodPosition`, inclusive a composição com `period.basisFilter`.
574
+
575
+ Com uma action que recebe `month`, mantenha o valor no consumidor e envie-o no input:
576
+
577
+ ```tsx
578
+ const [month, setMonth] = useState("2026-11")
579
+
580
+ <ActionList
581
+ action={deliveryListContract}
582
+ input={{ month }}
583
+ periodControl={<Input type="month" value={month} onChange={(event) => setMonth(event.target.value)} />}
584
+ />
585
+ ```
586
+
587
+ Quando `periodControl` está presente, ActionList ignora os presets de `action.periods` e os campos `period`, `from` e `to` do estado da lista, inclusive os recuperados da URL. O intervalo vem do `input` do consumidor; não há filtro temporal oculto. Sem o controle personalizado, o comportamento padrão permanece. Ao hidratar detalhes pelo helper `listStateToInput`, passe `applyPeriod: false` para manter o mesmo recorte.
588
+
589
+ Para distribuir os resultados pelos dias, componha a extensão `views` com [CalendarMonthView](/ui/calendar-month-view). A lista continua responsável pela consulta e pelos estados assíncronos.
590
+
519
591
  ## Propriedades de ActionFilterBar
520
592
 
521
593
  | Propriedade | Tipo | Padrão | Descrição |
@@ -528,6 +600,7 @@ render(
528
600
  | `filterIcons` | `Record<string, ReactNode>` | | Associa um ícone leading ao controle de cada filtro. |
529
601
  | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento das seções; o Opus deriva a largura correspondente. |
530
602
  | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
603
+ | `periodControl` | `ReactNode` | — | Controle temporal do consumidor. Substitui o seletor padrão; em ActionList, também desativa a aplicação automática dos presets ao input. |
531
604
  | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
532
605
  | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
533
606
  | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
@@ -564,32 +637,47 @@ para fora de `toolbarActions`.
564
637
 
565
638
  ## Propriedades de ActionList
566
639
 
567
- | Propriedade | Tipo | Padrão | Descrição |
568
- | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
569
- | `action` | `ListActionLike` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
570
- | `input` | `TInput` | | O escopo base, como `{ workspaceId }`. A toolbar acrescenta busca, filtros e paginação sem sobrescrever essas chaves. |
571
- | `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `SurfaceBody` ou do corpo da `Presentation` em uma página `fill`; `'flow'` nos demais casos | Em `fill`, ocupa a altura de um ancestral limitado e rola somente a região de resultados; na tabela, o cabeçalho fica fixo. Sem ancestral limitado, equivale a `flow`. |
572
- | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
573
- | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para filtros select/lookup. Em uma dimensão de agrupamento, a coleção precisa ser completa e ter de 1 a 12 itens. |
574
- | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
575
- | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
576
- | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento dos grupos; o Opus deriva a largura correspondente do diálogo. |
577
- | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
578
- | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
579
- | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
580
- | `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
581
- | `columns` | `ActionListColumn<TItem>[]` | | Tabela explícita, que substitui as colunas declaradas no contrato. |
582
- | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
583
- | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
584
- | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
585
- | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
586
- | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
587
- | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
588
- | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
589
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
590
- | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
591
- | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
592
- | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
593
- | `viewControls` | `ReactNode` | | Controles da visualização ativa, imediatamente após o seletor de visualização. |
594
- | `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. Os nós são renderizados como recebidos: nome acessível e tooltip de uma ação somente com ícone ficam com o consumidor. |
595
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram em um grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
640
+ `action` e `source` são alternativas mutuamente exclusivas.
641
+
642
+ | Propriedade | Tipo | Padrão | Descrição |
643
+ | --- | --- | --- | --- |
644
+ | `source` | `ListDataSource<TItem>` | — | Fonte externa com `key` e `load`; substitui a action. |
645
+ | `title` | `string` | — | Título da coleção, ao lado das ações. |
646
+ | `item` | `ActionListItemSpec` | — | Campos `title` e `description?` da apresentação em itens. |
647
+
648
+
649
+ | Propriedade | Tipo | Padrão | Descrição |
650
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
651
+ | `action` | `ListActionLike` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
652
+ | `input` | `TInput` | | O escopo base, como `{ workspaceId }`. A toolbar acrescenta busca, filtros e paginação sem sobrescrever essas chaves. |
653
+ | `layout` | `'flow' \| 'fill'` | `'fill'` como filha direta de `PageBody`, `SurfaceBody` ou do corpo da `Presentation` em uma página `fill`; `'flow'` nos demais casos | Em `fill`, ocupa a altura de um ancestral limitado e rola somente a região de resultados; na tabela, o cabeçalho fica fixo. Sem ancestral limitado, equivale a `flow`. |
654
+ | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
655
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para filtros select/lookup. Em uma dimensão de agrupamento, a coleção precisa ser completa e ter de 1 a 12 itens. |
656
+ | `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
657
+ | `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
658
+ | `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode>; sectionVariant?: 'plain' \| 'framed' }` | `{ columns: 1, sectionVariant: 'plain' }` | Define as colunas, os ícones e o tratamento dos grupos; o Opus deriva a largura correspondente do diálogo. |
659
+ | `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
660
+ | `periodControl` | `ReactNode` | — | Controle temporal do consumidor. Substitui o seletor padrão; em ActionList, também desativa a aplicação automática dos presets ao input. |
661
+ | `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
662
+ | `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
663
+ | `beforeContent` | `ReactNode \| (input: TInput) => ReactNode` | | Conteúdo entre a toolbar e o corpo; a função recebe o mesmo input efetivo da consulta, para resumos que acompanham o recorte. |
664
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela explícita, que substitui as colunas declaradas no contrato. |
665
+ | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
666
+ | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
667
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
668
+ | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
669
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
670
+ | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
671
+ | `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
672
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
673
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
674
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
675
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
676
+ | `viewControls` | `ReactNode` | | Controles da visualização ativa, imediatamente após o seletor de visualização. |
677
+ | `toolbarActions` | `ReactNode \| ((context) => ReactNode)` | | Ações da coleção, incluindo criação, junto ao título quando declarado ou no fim da barra. Os nós são renderizados como recebidos: nome acessível e tooltip de uma ação somente com ícone ficam com o consumidor. |
678
+ | `rowActions` | `(item: TItem, context) => ReactNode` | | Ações por registro, na tabela ou à direita dos itens. Na tabela, a partir de duas, entram em um grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
679
+
680
+ Para carregar um detalhe a partir do mesmo recorte da coleção, use
681
+ `listStateToInput(state, action, { input, filterDefaults, pageSize })`. O helper materializa
682
+ busca, filtros, período, ordenação e paginação com a mesma regra usada pelo ActionList.
683
+ `input` transporta o escopo fixo; `filterDefaults` preenche filtros que não vieram no estado.
@@ -103,6 +103,11 @@ um atalho, um arrastar, um item de menu.
103
103
  | `disabled` | `boolean` | `false` | Desabilita o botão independentemente da execução em andamento. |
104
104
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
105
105
  | `onLoadingChange` | `(loading: boolean) => void` | | Informa início e fim da execução a quem coordena bloqueio entre várias actions. |
106
+ | `aria-label` | `string` | | Nome acessível completo quando o texto visível é abreviado. |
106
107
  | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
107
108
  | `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
108
109
  | `className` | `string` | | Classes do botão. O tamanho vem de `size` (escala única); com `icon`, o default é `icon-xs`. |
110
+
111
+ Quando `label` abrevia o texto visível, forneça `aria-label` com o nome completo da operação.
112
+ O nome acessível deve conter o texto visível; por exemplo, “Criar” e “Criar cliente”.
113
+ Presentation faz essa associação ao usar um rótulo contextual no comando.
@@ -32,8 +32,8 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
32
32
  | Nome | Medida | Uso |
33
33
  | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
34
34
  | `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`). |
35
- | `sm` | 2rem | Headers, criação de coleções e operações densas de toolbar ou seção. |
36
- | `default` | 2.25rem | Footers de Dialog e Drawer e controles comuns; é também a linha de `Input`. |
35
+ | `sm` | 2rem | Criação de coleções e operações densas de toolbar ou seção. |
36
+ | `default` | 2.25rem | Headers, footers de Dialog e Drawer e controles comuns; é também a linha de `Input`. |
37
37
  | `lg` | 2.5rem | Chamada principal com mais área de toque. |
38
38
  | `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo ou de uma linha densa; é o quadrado de `ActionTrigger` com `icon`. |
39
39
  | `icon-sm` | 2rem | Ação só de ícone em composições compactas que precisam de mais presença; também é o quadrado das setas do pager de `ActionList`. |
@@ -41,15 +41,14 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
41
41
  | `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`. |
42
42
 
43
43
  Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
44
- hierarquia da ação. Headers de Page, Dialog, Drawer e Surface usam `sm`/`icon-sm`. A criação
44
+ hierarquia da ação. Headers de Page, Dialog, Drawer e Surface usam `default`/`icon`. A criação
45
45
  no cabeçalho de uma coleção usa `sm` textual, sem ícone. Footers de
46
46
  [`Dialog`](/ui/dialog) e [`Drawer`](/ui/drawer) conservam `default`. Operações densas de toolbar ou de seção usam `sm`;
47
47
  ações dentro de linha ou célula usam `xs` ou `icon-xs`. Assim a mesma decisão mantém a mesma altura
48
48
  mesmo quando uma superfície troca uma ação secundária por uma primária.
49
49
 
50
- O retorno de `PageBack` usa `icon-sm`. O fechamento de Dialog e Drawer é um `CloseButton` circular
51
- (`shape="pill"`), `neutral` e `subtle`, no quadrado `icon-sm` por padrão, ou `icon-xs` com
52
- `closeSize="xs"`.
50
+ O retorno de `PageBack` usa `icon`. O fechamento de Dialog e Drawer é um `CloseButton` circular
51
+ (`shape="pill"`), `neutral` e `subtle`, no quadrado `icon-xs` (`closeSize="xs"`) por padrão.
53
52
 
54
53
  ```tsx preview
55
54
  <Button size="xs">Mínimo</Button>
@@ -0,0 +1,50 @@
1
+ ## Registros distribuídos pelos dias
2
+
3
+ Use `CalendarMonthView` para consultar uma agenda ou expediente com conteúdo em cada dia. Para escolher uma data num formulário, use [DateInput](/ui/date-input); para selecionar um intervalo, use [Calendar](/ui/calendar).
4
+
5
+ O exemplo mostra um registro no mês inteiro. Clique ou use o teclado para abrir um dia:
6
+
7
+ ```tsx preview
8
+ const [selected, setSelected] = useState("")
9
+ const entries = [{ date: "2026-11-02", title: "Expediente especial" }]
10
+
11
+ render(
12
+ <div className="space-y-3">
13
+ <CalendarMonthView
14
+ month="2026-11"
15
+ label="Agenda de novembro"
16
+ items={entries}
17
+ dateOf={(entry) => entry.date}
18
+ renderDay={(_date, items) => items.map((entry) => <span key={entry.title}>{entry.title}</span>)}
19
+ dayLabel={(date, items) => `${date}: ${items.map((entry) => entry.title).join(", ") || "Sem registros"}`}
20
+ onDayClick={(date) => setSelected(date)}
21
+ />
22
+ {selected && <p>Data selecionada: {selected}</p>}
23
+ </div>,
24
+ )
25
+ ```
26
+
27
+ A grade sempre mostra todos os dias do mês, mesmo sem itens. Ela agrupa os registros por `dateOf`, sem consultar dados ou calcular regras de expediente. Datas são strings civis; o componente não aplica o fuso do navegador.
28
+
29
+ ## Dentro de ActionList
30
+
31
+ Forneça a grade no renderer de `views`. A [ActionList](/ui/action-list) mantém consulta, filtros, carregamento, erro e vazio; use `paginate: false` no contrato para que uma página de resultados não esconda dias com registros. `periodControl` permite navegar pelo mês no espaço de período da lista, com o valor temporal controlado pelo consumidor.
32
+
33
+ ## Teclado e limites
34
+
35
+ Tab entra em um único dia. Setas esquerda e direita percorrem dias; cima e baixo avançam uma semana. Home e End levam ao início ou fim da semana visível. Enter e Espaço acionam o dia focado. A navegação fica dentro do mês: o consumidor fornece os controles para trocar de mês.
36
+
37
+ A semana começa no domingo, com cabeçalhos em pt-BR. Mês inválido é erro de programação; valide valores da URL antes de renderizar. O padrão não marca hoje automaticamente nem implementa seleção de datas ou PageUp/PageDown. A raiz da grade expõe `data-slot="calendar-month-view"`.
38
+
39
+ ## Propriedades de CalendarMonthView
40
+
41
+ | Propriedade | Tipo | Padrão | Descrição |
42
+ |---|---|---|---|
43
+ | `month` | `string` | Obrigatória | Mês civil `AAAA-MM`, de 1000-01 a 9999-12. |
44
+ | `items` | `readonly TItem[]` | Obrigatória | Registros do mês, fornecidos pela coleção. |
45
+ | `dateOf` | `(item: TItem) => string` | Obrigatória | Data civil `AAAA-MM-DD` de cada registro. |
46
+ | `renderDay` | `(date: string, items: readonly TItem[]) => ReactNode` | Obrigatória | Conteúdo do dia; evite controles interativos dentro do botão. |
47
+ | `dayLabel` | `(date: string, items: readonly TItem[]) => string` | Obrigatória | Nome acessível do botão, incluindo data e contexto útil. |
48
+ | `onDayClick` | `(date: string, items: readonly TItem[]) => void` | Obrigatória | Abre ou executa a ação do dia. |
49
+ | `label` | `string` | Obrigatória | Nome acessível da grade. |
50
+ | `className` | `string` | — | Classes do contêiner externo, que permite rolagem horizontal em telas estreitas. |
@@ -1,7 +1,8 @@
1
1
  ## Dia único
2
2
 
3
3
  Use `Calendar` quando a pessoa precisar escolher datas vendo o mês inteiro, como a data de uma
4
- entrega ou a janela de um relatório. Para abrir o calendário a partir de um campo, ancore-o em um
4
+ entrega ou a janela de um relatório. Para preencher uma data com digitação e calendário em um
5
+ campo, use [`DateInput`](/ui/date-input). Composições próprias podem ancorá-lo em um
5
6
  [`Popover`](/ui/popover).
6
7
 
7
8
  Com `mode="single"`, a seleção é uma `Date`, controlada por `selected` e `onSelect`. `defaultMonth`
@@ -23,6 +24,8 @@ render(
23
24
  O calendário usa pt-BR por padrão: os nomes de mês e de dia vêm do `locale` (`ptBR`, de
24
25
  `date-fns/locale`) e os controles de navegação têm nomes acessíveis em português. Passe outro
25
26
  `locale` para trocar o idioma; `labels` substitui nomes acessíveis individualmente.
27
+ O título usa mês com inicial maiúscula e barra antes do ano, como `Setembro/2026`.
28
+ Esse formato também aparece nos campos `DateInput`; `formatters.formatCaption` permite substituí-lo.
26
29
 
27
30
  ## Intervalo
28
31
 
@@ -181,3 +184,5 @@ no nome acessível do dia por `labels.labelDayButton`.
181
184
  | `className` | `string` | | Classes somadas ao botão do dia. |
182
185
 
183
186
  `CalendarDayButton` aceita também as props nativas de `<button>`, repassadas pelo `Calendar`.
187
+
188
+ Para consultar registros por dia, use [CalendarMonthView](/ui/calendar-month-view). A grade recebe um mês civil válido no formato `AAAA-MM`. Os cabeçalhos estão em pt-BR, e a semana começa no domingo. A consulta e os estados de carregamento e erro pertencem à ActionList.