@softize/opus 12.11.0 → 13.0.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 (119) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/lib/check.mjs +2 -7
  3. package/bin/lib/copy.mjs +1 -5
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
  5. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  6. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  7. package/docs/radius-scale.md +1 -1
  8. package/package.json +1 -1
  9. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  10. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  11. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  12. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  13. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  14. package/src/ui/components/patterns/confirm.tsx +140 -40
  15. package/src/ui/components/patterns/list.tsx +35 -40
  16. package/src/ui/components/patterns/page-state.tsx +2 -2
  17. package/src/ui/components/patterns/sidebar.tsx +26 -26
  18. package/src/ui/components/patterns/trigger.tsx +25 -22
  19. package/src/ui/components/primitives/alert.tsx +3 -3
  20. package/src/ui/components/primitives/dialog.tsx +196 -39
  21. package/src/ui/components/primitives/drawer.tsx +8 -5
  22. package/src/ui/components/primitives/empty.tsx +3 -3
  23. package/src/ui/components/primitives/item.tsx +3 -3
  24. package/src/ui/components/primitives/sonner.tsx +187 -8
  25. package/src/ui/docs/DocBrowser.tsx +102 -23
  26. package/src/ui/docs/content/accordion.md +22 -16
  27. package/src/ui/docs/content/action-form-card.md +8 -8
  28. package/src/ui/docs/content/action-form-dialog.md +9 -9
  29. package/src/ui/docs/content/action-form.md +28 -34
  30. package/src/ui/docs/content/action-list-dialog.md +11 -6
  31. package/src/ui/docs/content/action-list.md +64 -39
  32. package/src/ui/docs/content/action-trigger.md +21 -14
  33. package/src/ui/docs/content/action-view.md +8 -8
  34. package/src/ui/docs/content/actions.md +9 -9
  35. package/src/ui/docs/content/ai.md +3 -3
  36. package/src/ui/docs/content/alert.md +14 -12
  37. package/src/ui/docs/content/aspect-ratio.md +4 -4
  38. package/src/ui/docs/content/audit.md +2 -2
  39. package/src/ui/docs/content/auth.md +3 -3
  40. package/src/ui/docs/content/avatar.md +34 -14
  41. package/src/ui/docs/content/badge.md +3 -3
  42. package/src/ui/docs/content/breadcrumb.md +13 -8
  43. package/src/ui/docs/content/button.md +81 -6
  44. package/src/ui/docs/content/calendar.md +5 -5
  45. package/src/ui/docs/content/card.md +1 -1
  46. package/src/ui/docs/content/carousel.md +16 -11
  47. package/src/ui/docs/content/chat.md +3 -3
  48. package/src/ui/docs/content/checkbox.md +7 -7
  49. package/src/ui/docs/content/cli.md +5 -5
  50. package/src/ui/docs/content/collapsible.md +8 -8
  51. package/src/ui/docs/content/command.md +16 -8
  52. package/src/ui/docs/content/composer.md +2 -2
  53. package/src/ui/docs/content/content.md +2 -2
  54. package/src/ui/docs/content/copyable.md +4 -3
  55. package/src/ui/docs/content/customization.md +5 -5
  56. package/src/ui/docs/content/cycle.md +3 -3
  57. package/src/ui/docs/content/data-state.md +11 -12
  58. package/src/ui/docs/content/data.md +26 -33
  59. package/src/ui/docs/content/detail.md +3 -3
  60. package/src/ui/docs/content/dialog.md +339 -31
  61. package/src/ui/docs/content/dictionary-value.md +8 -8
  62. package/src/ui/docs/content/dock.md +3 -3
  63. package/src/ui/docs/content/drawer.md +27 -14
  64. package/src/ui/docs/content/empty-value.md +2 -2
  65. package/src/ui/docs/content/empty.md +19 -12
  66. package/src/ui/docs/content/events.md +4 -4
  67. package/src/ui/docs/content/field.md +34 -12
  68. package/src/ui/docs/content/getting-started.md +1 -1
  69. package/src/ui/docs/content/icon-picker.md +8 -4
  70. package/src/ui/docs/content/input-otp.md +20 -12
  71. package/src/ui/docs/content/input.md +121 -9
  72. package/src/ui/docs/content/item.md +27 -13
  73. package/src/ui/docs/content/kbd.md +19 -11
  74. package/src/ui/docs/content/label.md +5 -3
  75. package/src/ui/docs/content/log.md +4 -4
  76. package/src/ui/docs/content/markdown.md +7 -6
  77. package/src/ui/docs/content/mcp.md +13 -15
  78. package/src/ui/docs/content/menu.md +34 -16
  79. package/src/ui/docs/content/observability.md +2 -2
  80. package/src/ui/docs/content/page.md +51 -6
  81. package/src/ui/docs/content/pagination.md +22 -17
  82. package/src/ui/docs/content/popover.md +16 -8
  83. package/src/ui/docs/content/progress.md +7 -5
  84. package/src/ui/docs/content/queue.md +5 -5
  85. package/src/ui/docs/content/radio-group.md +20 -12
  86. package/src/ui/docs/content/router.md +11 -6
  87. package/src/ui/docs/content/scheduler.md +4 -5
  88. package/src/ui/docs/content/scroll-area.md +12 -7
  89. package/src/ui/docs/content/select.md +42 -29
  90. package/src/ui/docs/content/separator.md +5 -5
  91. package/src/ui/docs/content/sidebar.md +323 -54
  92. package/src/ui/docs/content/skeleton.md +3 -2
  93. package/src/ui/docs/content/slider.md +8 -7
  94. package/src/ui/docs/content/spinner.md +8 -8
  95. package/src/ui/docs/content/split.md +8 -5
  96. package/src/ui/docs/content/storage.md +6 -8
  97. package/src/ui/docs/content/switch.md +8 -7
  98. package/src/ui/docs/content/table.md +13 -3
  99. package/src/ui/docs/content/tabs.md +28 -14
  100. package/src/ui/docs/content/testing.md +9 -11
  101. package/src/ui/docs/content/textarea.md +5 -4
  102. package/src/ui/docs/content/toast.md +47 -13
  103. package/src/ui/docs/content/toggle.md +75 -7
  104. package/src/ui/docs/content/tokens.md +3 -3
  105. package/src/ui/docs/content/tooltip.md +19 -11
  106. package/src/ui/docs/content/truncate.md +7 -8
  107. package/src/ui/docs/content/ui.md +10 -9
  108. package/src/ui/docs/content/upgrading.md +7 -8
  109. package/src/ui/docs/registry.tsx +20 -37
  110. package/src/ui/meta.ts +64 -94
  111. package/src/ui/react.tsx +15 -16
  112. package/src/ui/theme.css +50 -0
  113. package/src/ui/components/primitives/alert-dialog.tsx +0 -192
  114. package/src/ui/docs/content/alert-dialog.md +0 -73
  115. package/src/ui/docs/content/button-group.md +0 -71
  116. package/src/ui/docs/content/confirm.md +0 -120
  117. package/src/ui/docs/content/input-group.md +0 -79
  118. package/src/ui/docs/content/page-state.md +0 -45
  119. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -12,10 +12,16 @@
12
12
  * Nos dois, navegar emite `popstate`: host que guarda o path por conta própria acompanha.
13
13
  * `basePath` adapta o prefixo das URLs (`/docs` no Maestro, `/__docs` nos apps).
14
14
  */
15
- import { useEffect } from 'react'
15
+ import { useEffect, useState } from 'react'
16
16
  import { DOC_SECTIONS, type DocSection, type DocEntry } from './registry'
17
17
  import { Pane, Split } from '../components/patterns/split.tsx'
18
- import { PaneBody, Sidebar, SidebarNav, type SidebarNavGroup } from '../components/patterns/sidebar.tsx'
18
+ import {
19
+ PaneBody,
20
+ Sidebar,
21
+ SidebarGroupLabel,
22
+ SidebarItem,
23
+ SidebarTreeGroup,
24
+ } from '../components/patterns/sidebar.tsx'
19
25
  import { navigate, usePathname } from '../router.ts'
20
26
 
21
27
  function findPage(sections: DocSection[], slug: string): DocEntry | undefined {
@@ -42,6 +48,95 @@ export interface DocBrowserProps {
42
48
  documentTitle?: (pageTitle: string) => string
43
49
  }
44
50
 
51
+ function DocSidebarTree({
52
+ sections,
53
+ activeSlug,
54
+ onSelect,
55
+ }: {
56
+ sections: DocSection[]
57
+ activeSlug?: string
58
+ onSelect: (slug: string) => void
59
+ }): React.ReactElement {
60
+ const [closedGroups, setClosedGroups] = useState<Set<string>>(() => new Set())
61
+
62
+ useEffect(() => {
63
+ const activeGroup = sections.flatMap((section, sectionIndex) =>
64
+ section.groups.map((group, groupIndex) => ({
65
+ key: `${sectionIndex}:${groupIndex}`,
66
+ group,
67
+ })),
68
+ ).find(({ group }) => group.pages.some((page) => page.slug === activeSlug))
69
+
70
+ if (activeGroup === undefined) return
71
+ setClosedGroups((current) => {
72
+ if (!current.has(activeGroup.key)) return current
73
+ const next = new Set(current)
74
+ next.delete(activeGroup.key)
75
+ return next
76
+ })
77
+ }, [activeSlug, sections])
78
+
79
+ const toggleGroup = (key: string): void => {
80
+ setClosedGroups((current) => {
81
+ const next = new Set(current)
82
+ if (next.has(key)) next.delete(key)
83
+ else next.add(key)
84
+ return next
85
+ })
86
+ }
87
+
88
+ const renderPage = (page: DocEntry): React.ReactElement => (
89
+ <SidebarItem
90
+ key={page.slug}
91
+ label={page.title}
92
+ badge={page.badge === undefined ? undefined : (
93
+ <span className="rounded border border-border/60 px-1 font-mono text-[0.625rem] leading-tight text-muted-foreground/60">
94
+ {page.badge}
95
+ </span>
96
+ )}
97
+ active={page.slug === activeSlug}
98
+ onClick={() => onSelect(page.slug)}
99
+ />
100
+ )
101
+
102
+ return (
103
+ <nav aria-label="Navegação da documentação" className="space-y-4 p-2">
104
+ {sections.map((section, sectionIndex) => (
105
+ <div key={`${section.label}:${sectionIndex}`} className="space-y-0.5">
106
+ {section.label.trim() !== '' && (
107
+ <SidebarGroupLabel>{section.label}</SidebarGroupLabel>
108
+ )}
109
+ {section.groups.map((group, groupIndex) => {
110
+ const key = `${sectionIndex}:${groupIndex}`
111
+ if (group.pages.length === 0) return null
112
+ if ((group.label ?? '').trim() === '') {
113
+ return group.pages.map((page) => renderPage(page))
114
+ }
115
+
116
+ const expanded = !closedGroups.has(key)
117
+ return (
118
+ <div key={key} className="space-y-0.5">
119
+ <SidebarItem
120
+ label={group.label!}
121
+ icon={group.icon}
122
+ expanded={expanded}
123
+ onToggle={() => toggleGroup(key)}
124
+ onClick={() => toggleGroup(key)}
125
+ />
126
+ {expanded && (
127
+ <SidebarTreeGroup>
128
+ {group.pages.map((page) => renderPage(page))}
129
+ </SidebarTreeGroup>
130
+ )}
131
+ </div>
132
+ )
133
+ })}
134
+ </div>
135
+ ))}
136
+ </nav>
137
+ )
138
+ }
139
+
45
140
  export function DocBrowser({
46
141
  basePath = '/docs',
47
142
  path: controlledPath,
@@ -76,31 +171,15 @@ export function DocBrowser({
76
171
  if (page && documentTitle) document.title = documentTitle(page.title)
77
172
  }, [page, documentTitle])
78
173
 
79
- // Seção → grupo, e os grupos da seção → subgrupos. O tier do meio NÃO pode ser
80
- // achatado: `docSectionsFromFolder` o preenche a partir de sub-pasta (ou do
81
- // frontmatter `group:`), que é o caminho do `opusDocs({ source })`.
82
- const navGroups: SidebarNavGroup[] = sections.map((section) => ({
83
- label: section.label,
84
- subgroups: section.groups.map((g) => ({
85
- label: g.label,
86
- items: g.pages.map((p) => ({
87
- id: p.slug,
88
- label: p.title,
89
- badge: p.badge === undefined ? undefined : <span className="rounded border border-border/60 px-1 font-mono text-[0.625rem] leading-tight text-muted-foreground/60">{p.badge}</span>,
90
- })),
91
- })),
92
- }))
93
-
94
174
  return (
95
175
  <Split className="h-full">
96
- <Pane inset="none" className="w-56">
176
+ <Pane initialSize="14rem" inset="none">
97
177
  <Sidebar className="w-full">
98
178
  <PaneBody>
99
- <SidebarNav
100
- groups={navGroups}
101
- activeId={page?.slug}
102
- navLabel="Navegação da documentação"
103
- onSelect={(slug) => go(`${basePath}/${slug}`)}
179
+ <DocSidebarTree
180
+ sections={sections}
181
+ activeSlug={page?.slug}
182
+ onSelect={(nextSlug) => go(`${basePath}/${nextSlug}`)}
104
183
  />
105
184
  </PaneBody>
106
185
  </Sidebar>
@@ -1,7 +1,8 @@
1
- ## Padrão (single)
1
+ ## Um painel por vez
2
2
 
3
- `type=single` abre um painel por vez. `collapsible` deixa fechar o que está aberto — sem ele,
4
- sempre fica um item expandido. `defaultValue` deixa o estado com o componente.
3
+ Use `type="single"` quando somente um painel deve permanecer aberto. Com `collapsible`, a pessoa
4
+ também pode fechar o painel atual; sem essa propriedade, um item permanece expandido.
5
+ `defaultValue` define o item inicialmente aberto no modo não controlado.
5
6
 
6
7
  ```tsx preview
7
8
  <Accordion type="single" collapsible defaultValue="overview" className="w-full">
@@ -28,8 +29,8 @@ sempre fica um item expandido. `defaultValue` deixa o estado com o componente.
28
29
 
29
30
  ## Múltiplos abertos
30
31
 
31
- `type=multiple` permite vários painéis expandidos ao mesmo tempo `defaultValue` vira um
32
- array com os itens abertos de saída.
32
+ Use `type="multiple"` quando os painéis puderem permanecer abertos ao mesmo tempo. Nesse modo,
33
+ `defaultValue` recebe um array com os itens inicialmente expandidos.
33
34
 
34
35
  ```tsx preview
35
36
  <Accordion type="multiple" defaultValue={['skills', 'prompt']} className="w-full">
@@ -48,10 +49,10 @@ array com os itens abertos de saída.
48
49
  </Accordion>
49
50
  ```
50
51
 
51
- ## Controlado (com estado)
52
+ ## Estado controlado
52
53
 
53
- No modo `single`, `value`/`onValueChange` tiram o estado do componente dá pra abrir um item
54
- de fora. Exemplo **com estado** (o `render()` deixa o hook rodar):
54
+ Use `value` e `onValueChange` quando outro elemento ou estado do aplicativo também precisar
55
+ controlar o painel aberto.
55
56
 
56
57
  ```tsx preview
57
58
  const [open, setOpen] = React.useState('developer')
@@ -74,13 +75,18 @@ render(
74
75
  )
75
76
  ```
76
77
 
77
- ## Props
78
+ ## Propriedades de Accordion
78
79
 
79
- | Prop | Tipo | Default | Descrição |
80
+ | Propriedade | Tipo | Padrão | Descrição |
80
81
  |---|---|---|---|
81
- | `type` (Accordion) | `'single' \| 'multiple'` | | single abre um painel por vez; multiple permite vários. |
82
- | `collapsible` (Accordion) | `boolean` | `false` | no single permite fechar o item aberto. |
83
- | `defaultValue` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo não controlado. |
84
- | `value` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo controlado pareie com onValueChange. |
85
- | `onValueChange` (Accordion) | `(value) => void` | | Chamado quando o usuário abre ou fecha um item. |
86
- | `value` (AccordionItem) | `string` | | Identificador do item — é o que defaultValue/value referenciam. |
82
+ | `type` | `'single' \| 'multiple'` | | `single` abre um painel por vez; `multiple` permite manter vários abertos. |
83
+ | `collapsible` | `boolean` | `false` | No modo `single`, permite fechar o item aberto. |
84
+ | `defaultValue` | `string \| string[]` | | Item ou itens inicialmente abertos no modo não controlado. |
85
+ | `value` | `string \| string[]` | | Item ou itens abertos no modo controlado. Use com `onValueChange`. |
86
+ | `onValueChange` | `(value) => void` | | Chamado quando a pessoa abre ou fecha um item. |
87
+
88
+ ## Propriedades de AccordionItem
89
+
90
+ | Propriedade | Tipo | Padrão | Descrição |
91
+ |---|---|---|---|
92
+ | `value` | `string` | | Identificador usado por `defaultValue` e `value` no `Accordion`. |
@@ -1,7 +1,7 @@
1
- ## Form como card
1
+ ## Formulário em uma seção
2
2
 
3
- O ActionForm dentro de um Card header, conteúdo e rodapé, no respiro do Card (sem divisor nem
4
- faixa de modal). Pra estruturar uma seção da página como painel. O título é opcional.
3
+ Use `ActionFormCard` para apresentar um `ActionForm` como seção delimitada da página. O componente
4
+ aplica a estrutura e o espaçamento de `Card`, sem a faixa de ações de um modal. O título é opcional.
5
5
 
6
6
  ```tsx preview col md
7
7
  <DocBrowserActionProvider>
@@ -14,11 +14,11 @@ faixa de modal). Pra estruturar uma seção da página como painel. O título é
14
14
  </DocBrowserActionProvider>
15
15
  ```
16
16
 
17
- ## Props
17
+ ## Propriedades de ActionFormCard
18
18
 
19
- | Prop | Tipo | Default | Descrição |
19
+ | Propriedade | Tipo | Padrão | Descrição |
20
20
  |---|---|---|---|
21
- | `title` | `string` | | Título do header (com divisor embaixo). Sem ele, o card começa direto no corpo. |
21
+ | `title` | `string` | | Título do cabeçalho. Sem ele, o card começa diretamente pelo corpo. |
22
22
  | `description` | `string` | | Subtítulo opcional, abaixo do título. |
23
- | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | `— (iguais ao ActionForm)` | | O resto é o ActionForm o card injeta o corpo (CardBody) e o rodapé (CardFooter) por baixo. |
24
- | `cardClassName` | `string` | | Classes da SUPERFÍCIE do card (ex.: largura). `className` vai pro `<form>`. |
23
+ | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | Propriedades de `ActionForm` | | Mantêm o mesmo comportamento do formulário interno. |
24
+ | `cardClassName` | `string` | | Classes aplicadas à superfície do card. `className` continua sendo aplicado ao `<form>`. |
@@ -1,8 +1,8 @@
1
- ## Form em modal
1
+ ## Formulário em um modal
2
2
 
3
- O open é de quem orquestra; o fechamento no sucesso é do pattern. Header e X fixos, os campos
4
- scrollam, o rodapé é a faixa da casa. O preview roda num client de mentira no app, o contrato
5
- vem do spec.
3
+ Use `ActionFormDialog` quando o formulário precisar interromper o fluxo atual sem levar a pessoa
4
+ para outra página. O consumidor controla `open`; depois de uma execução bem-sucedida, o componente
5
+ fecha o modal. O cabeçalho e o rodapé permanecem visíveis enquanto os campos podem rolar.
6
6
 
7
7
  ```tsx preview
8
8
  const [open, setOpen] = useState(false)
@@ -22,11 +22,11 @@ render(
22
22
  )
23
23
  ```
24
24
 
25
- ## Props
25
+ ## Propriedades de ActionFormDialog
26
26
 
27
- | Prop | Tipo | Default | Descrição |
27
+ | Propriedade | Tipo | Padrão | Descrição |
28
28
  |---|---|---|---|
29
- | `open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — o pattern chama onOpenChange(false) no sucesso e no Cancelar. |
30
- | `title / description` | `string` | | O DialogHeader fixo description é opcional. |
29
+ | `open / onOpenChange` | `boolean / (open: boolean) => void` | | Estado controlado do modal. `onOpenChange(false)` é chamado no sucesso e ao cancelar. |
30
+ | `title / description` | `string` | | Conteúdo do cabeçalho; `description` é opcional. |
31
31
  | `submitLabel` | `string` | `'Salvar'` | Resultado da ação principal. Em criação, informe `Criar {recurso}`; em edição, `Salvar alterações`. |
32
- | `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Todo o resto desce pro ActionForm interno — mesma API da página dele. |
32
+ | `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Demais propriedades repassadas ao `ActionForm` interno. |
@@ -1,8 +1,8 @@
1
- ## Form derivado do contrato
1
+ ## Formulário derivado do contrato
2
2
 
3
- Zero JSX de campo: enum vira Select, string longa vira Textarea, obrigatório ganha o asterisco. O
4
- preview roda num client de mentira digite "Softize" no nome pra ver o erro de servidor inline; o
5
- submit habilita com mudança real (dirty).
3
+ Use `ActionForm` para gerar campos, validação e mensagens a partir de uma `FormAction`. Sem
4
+ `children`, o componente escolhe o controle adequado para cada campo e preserva a ordem declarada
5
+ no contrato. No exemplo, altere um valor para habilitar a ação principal.
6
6
 
7
7
  ```tsx preview col md
8
8
  <DocBrowserActionProvider>
@@ -10,13 +10,12 @@ submit só habilita com mudança real (dirty).
10
10
  </DocBrowserActionProvider>
11
11
  ```
12
12
 
13
- ## Composição (diagramação livre)
13
+ ## Composição dos campos
14
14
 
15
- Com children, o AUTO desliga e você diagrama: `<ActionFormField name />` coloca cada campo — com
16
- label, hint, widget, erro e asterisco derivados do contrato — onde quiser. Condicional é JSX;
17
- opções de runtime entram por prop no campo. O comportamento (submit, validação, toast,
18
- invalidação) continua encapsulado e o espaçamento é 100% seu: o campo não impõe margem
19
- (quem diagrama usa grid/gap/space-y como quiser).
15
+ Passe `children` quando a disposição automática não atender ao formulário. Cada
16
+ `ActionFormField` mantém rótulo, ajuda, controle, obrigatoriedade e erro derivados do contrato;
17
+ o consumidor define apenas a grade, as condições e o espaçamento. Execução, validação, toast e
18
+ invalidação continuam sob responsabilidade de `ActionForm`.
20
19
 
21
20
  ```tsx preview col md
22
21
  <DocBrowserActionProvider>
@@ -35,8 +34,8 @@ invalidação) continua encapsulado — e o espaçamento é 100% seu: o campo n
35
34
 
36
35
  ## Valores iniciais e rótulos
37
36
 
38
- defaultValues pré-carrega (modo edição); submitLabel/cancelLabel trocam o rodapé. O onCancel é de
39
- quem orquestra (fechar drawer, voltar…).
37
+ `defaultValues` preenche o formulário para edição. `submitLabel` e `cancelLabel` nomeiam as ações;
38
+ `onCancel` devolve ao consumidor a decisão de fechar um painel ou navegar para outra página.
40
39
 
41
40
  ```tsx preview col md
42
41
  <DocBrowserActionProvider>
@@ -51,8 +50,7 @@ quem orquestra (fechar drawer, voltar…).
51
50
 
52
51
  ## Pré-requisitos
53
52
 
54
- O driver precisa dos dois providers no root exatamente o wiring do admin (o consumidor
55
- canônico).
53
+ Monte os providers de consulta e execução uma vez na raiz do aplicativo:
56
54
 
57
55
  ```tsx
58
56
  /* main.tsx do app. */
@@ -63,35 +61,32 @@ canônico).
63
61
  </QueryClientProvider>
64
62
  ```
65
63
 
66
- ## Props
64
+ ## Propriedades de ActionForm
67
65
 
68
- | Prop | Tipo | Default | Descrição |
66
+ | Propriedade | Tipo | Padrão | Descrição |
69
67
  |---|---|---|---|
70
68
  | `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
71
69
  | `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
72
70
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
73
71
  | `submitLabel / cancelLabel / onCancel` | `string / string / () => void` | `'Salvar' / 'Cancelar'` | Rodapé do form — o Cancelar só aparece com onCancel. |
74
72
  | `fieldOptions` | `Record<string, SelectOption[]>` | | Opções de runtime por campo (ex.: ids de skills) — sobrepõe as inferidas do z.enum. |
75
- | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = SLOTS: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
76
- | `children` | `ReactNode` | | Modo COMPOSIÇÃO: diagrame com `<ActionFormField name />`. Sem children, o AUTO monta todos os campos na ordem do contrato. |
73
+ | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = slots: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
74
+ | `children` | `ReactNode` | | Modo composição: diagrame com `<ActionFormField name />`. Sem children, o automático monta todos os campos na ordem do contrato. |
77
75
 
78
- ## ActionFormField
76
+ ## Propriedades de ActionFormField
79
77
 
80
- | Prop | Tipo | Descrição |
78
+ | Propriedade | Tipo | Descrição |
81
79
  |---|---|---|
82
80
  | `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
83
81
  | `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
84
- | `className` | `string` | Classes do invólucro (ex.: `col-span-2` numa grid). |
82
+ | `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
85
83
 
86
- ## De onde vêm as opções de um select
84
+ ## Origem das opções de seleção
87
85
 
88
- Precedência, da mais específica pra mais automática: **prop `options` do campo** >
89
- **`fieldOptions` do form** > **`options` do FieldSpec no contrato** (`{ kind: 'dictionary',
90
- ref }` resolve pelos dicts do `<TbdlibProvider dicts={{ ref: meuDict }}>` — o `DictType` do
91
- `t.dict` encaixa direto; `{ kind: 'static', items }` renderiza como declarado) > **meta do
92
- `t.dict` no schema** (zero-config: campo `meuDict.zod()` — ou multiselect com elemento dict —
93
- resolve value→label pela meta que viaja no contrato, sem registry) > **chaves cruas do
94
- z.enum**. Campo texto com opções declaradas (runtime ou spec) vira single-select por-id.
86
+ As opções seguem esta precedência: `options` no campo, `fieldOptions` no formulário, `options` no
87
+ `FieldSpec`, metadata de `t.dict` no schema e, por último, as chaves de `z.enum`. Uma fonte mais
88
+ específica substitui as seguintes. Dicionários registrados no `TbdlibProvider` resolvem rótulos por
89
+ referência; itens estáticos permanecem como declarados no contrato.
95
90
 
96
91
  ## Widgets declarativos
97
92
 
@@ -103,11 +98,10 @@ escolha rica em card, com conteúdo de apoio; para uma lista textual comum, pref
103
98
  Outros identificadores continuam válidos como metadado
104
99
  para renderers próprios; o `ActionForm` aplica sua inferência normal quando não reconhece o widget.
105
100
 
106
- ## Controle custom: useActionFormContext
101
+ ## Controle próprio com useActionFormContext
107
102
 
108
- Campo com UI própria (grade de permissões, canvas…) que nenhum widget cobre? No modo
109
- composição, o hook acesso ao form do contrato o campo custom participa do submit
110
- sem abandonar o `<ActionForm>`:
103
+ Quando nenhum widget atender ao campo, use `useActionFormContext` no modo de composição. O controle
104
+ próprio participa da mesma validação e execução sem abandonar o `ActionForm`:
111
105
 
112
106
  ```tsx
113
107
  import { useActionFormContext } from '@softize/opus/ui/react'
@@ -132,4 +126,4 @@ function PermissionGrid() {
132
126
  ```
133
127
 
134
128
  O contexto também expõe `shape` (Zod por campo), `fields` (FieldSpec) e `fieldOptions`.
135
- Fora de um `<ActionForm>`, o hook explode com mensagem clara igual ao `ActionFormField`.
129
+ Fora de um `<ActionForm>`, o hook lança um erro que informa o provider ausente.
@@ -1,6 +1,9 @@
1
- ## ListAction em modal
1
+ ## Listagem em um modal
2
2
 
3
- O modo modal da listagem, action-driven: a lista é query-backed (busca no mount, re-busca quando o input muda e refaz sozinha quando um form/trigger invalida a action), os estados derivam do fetch, e a nota da toolbar já vem com o "N no total". Children diagrama os itens — composição, como nos irmãos.
3
+ Use `ActionListDialog` para consultar e apresentar uma `ListAction` sem sair do contexto atual. A
4
+ lista carrega ao abrir, refaz a consulta quando `input` muda e acompanha invalidações declaradas por
5
+ outras actions. O consumidor compõe os itens por `children`; o modal mantém os estados e a barra de
6
+ ações.
4
7
 
5
8
  ```tsx preview col
6
9
  const [open, setOpen] = useState(false)
@@ -38,9 +41,11 @@ render(
38
41
  )
39
42
  ```
40
43
 
41
- ## O que o contrato não sabe
44
+ ## Estado adicional do consumidor
42
45
 
43
- Dois escape hatches, pros casos em que o modal agrega mais de uma fonte: `loading` soma a carga de uma query irmã (ex.: os papéis que os cards precisam pra rotular) e `empty` sobrepõe o vazio derivado (ex.: com o form inline de criar aberto, a lista vazia mostra o form, não o emptyText).
46
+ Quando o modal depender de outra consulta, `loading` combina esse carregamento ao estado da lista.
47
+ Use `empty` para substituir a regra de vazio, por exemplo enquanto um formulário de criação ocupa o
48
+ corpo do modal.
44
49
 
45
50
  ```tsx
46
51
  <ActionListDialog
@@ -52,9 +57,9 @@ Dois escape hatches, pros casos em que o modal agrega mais de uma fonte: `loadin
52
57
  />
53
58
  ```
54
59
 
55
- ## Props
60
+ ## Propriedades de ActionListDialog
56
61
 
57
- | Prop | Tipo | Default | Descrição |
62
+ | Propriedade | Tipo | Padrão | Descrição |
58
63
  |---|---|---|---|
59
64
  | `action / input` | `ListAction / TInput` | | O contrato e os filtros — mudou o input, re-busca; `invalidates` de forms/triggers refaz sozinho. |
60
65
  | `open / onOpenChange` | `boolean / (open) => void` | | Controle do modal — de quem orquestra. |