@softize/opus 18.5.0 → 20.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 (151) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. package/src/vite/design.ts +16 -4
@@ -1,38 +1,44 @@
1
1
  ## Seção recolhível
2
2
 
3
- CollapsibleTrigger alterna o CollapsibleContent — o Trigger já é o <button>. defaultOpen deixa o estado com o componente.
3
+ Use `Collapsible` para esconder um bloco secundário atrás de um gatilho, mantendo o restante da
4
+ tela visível. `CollapsibleTrigger` já renderiza um `<button>` e alterna o `CollapsibleContent`.
5
+ Sem `open`, o componente guarda o próprio estado; `defaultOpen` apenas define se ele começa aberto.
4
6
 
5
7
  ```tsx preview col
6
8
  <Collapsible defaultOpen className="w-full max-w-sm space-y-2">
7
- <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
8
- Repositórios do workspace
9
- <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
9
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
10
+ Serviços incluídos na revisão
11
+ <ChevronDown className="size-4 text-muted-foreground transition-transform" />
10
12
  </CollapsibleTrigger>
11
13
  <CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
12
- <p>empresa-x-api</p>
13
- <p>empresa-x-web</p>
14
- <p>empresa-x-infra</p>
14
+ <p>Troca de óleo</p>
15
+ <p>Filtro de ar</p>
16
+ <p>Alinhamento</p>
15
17
  </CollapsibleContent>
16
18
  </Collapsible>
17
19
  ```
18
20
 
21
+ O gatilho recebe `data-state="open"` ou `data-state="closed"`. O seletor
22
+ `[&[data-state=open]>svg]:rotate-180` usa esse atributo para girar o ícone sem estado próprio.
23
+
19
24
  ## Controlado
20
25
 
21
- open + onOpenChange põem o estado nas suas mãos — dá para refletir no gatilho (aqui o chevron gira) ou guardar a preferência.
26
+ Use `open` e `onOpenChange` quando outra parte da tela precisar conhecer o estado, por exemplo para
27
+ trocar o texto do gatilho ou guardar a preferência da pessoa.
22
28
 
23
29
  ```tsx preview col
24
30
  const [open, setOpen] = useState(false)
25
31
 
26
32
  render(
27
33
  <Collapsible open={open} onOpenChange={setOpen} className="w-full max-w-sm space-y-2">
28
- <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
29
- Skills do agente developer
30
- <ChevronDown className={open ? 'h-4 w-4 rotate-180 transition-transform' : 'h-4 w-4 transition-transform'} />
34
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted [&[data-state=open]>svg]:rotate-180">
35
+ {open ? 'Ocultar peças utilizadas' : 'Mostrar peças utilizadas'}
36
+ <ChevronDown className="size-4 text-muted-foreground transition-transform" />
31
37
  </CollapsibleTrigger>
32
38
  <CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
33
- <p>clean-code</p>
34
- <p>code-style</p>
35
- <p>ddd</p>
39
+ <p>Filtro de óleo</p>
40
+ <p>Pastilhas de freio</p>
41
+ <p>Velas de ignição</p>
36
42
  </CollapsibleContent>
37
43
  </Collapsible>,
38
44
  )
@@ -40,16 +46,17 @@ render(
40
46
 
41
47
  ## Desabilitado
42
48
 
43
- disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual (aqui, fechada).
49
+ Use `disabled` no `Collapsible` quando a seção não puder mudar de estado por enquanto. O gatilho
50
+ fica desabilitado e a seção permanece como está; no exemplo, fechada.
44
51
 
45
52
  ```tsx preview col
46
53
  <Collapsible disabled className="w-full max-w-sm space-y-2">
47
54
  <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-50">
48
- Sessões arquivadas
49
- <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
55
+ Revisões anteriores
56
+ <ChevronDown className="size-4 text-muted-foreground" />
50
57
  </CollapsibleTrigger>
51
58
  <CollapsibleContent className="px-3 text-sm text-muted-foreground">
52
- <p>Nada para mostrar enquanto o workspace está em sincronização.</p>
59
+ <p>O histórico fica disponível depois da sincronização com a concessionária.</p>
53
60
  </CollapsibleContent>
54
61
  </Collapsible>
55
62
  ```
@@ -61,4 +68,21 @@ disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual
61
68
  | `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
62
69
  | `open` | `boolean` | | Estado no modo controlado. Use com `onOpenChange`. |
63
70
  | `onOpenChange` | `(open: boolean) => void` | | Chamado a cada abertura ou fechamento. |
64
- | `disabled` | `boolean` | `false` | Bloqueia o gatilho e mantém a seção no estado atual. |
71
+ | `disabled` | `boolean` | `false` | Desabilita o gatilho e mantém a seção no estado atual. |
72
+ | `className` | `string` | | Ajusta o contêiner. |
73
+
74
+ ## Propriedades de CollapsibleTrigger
75
+
76
+ | Propriedade | Tipo | Padrão | Descrição |
77
+ |---|---|---|---|
78
+ | `asChild` | `boolean` | `false` | Usa o filho como gatilho, como um `Button`, sem criar outro `<button>`. |
79
+ | `className` | `string` | | Ajusta o gatilho. O estado chega em `data-state`. |
80
+ | `children` | `ReactNode` | | Rótulo do gatilho e, quando houver, o ícone de estado. |
81
+
82
+ ## Propriedades de CollapsibleContent
83
+
84
+ | Propriedade | Tipo | Padrão | Descrição |
85
+ |---|---|---|---|
86
+ | `forceMount` | `boolean` | `false` | Mantém o conteúdo montado mesmo fechado, por exemplo para animá-lo por CSS. |
87
+ | `className` | `string` | | Ajusta a região recolhível. |
88
+ | `children` | `ReactNode` | | Conteúdo exibido quando a seção está aberta. |
@@ -1,7 +1,9 @@
1
1
  ## Lista filtrável inline
2
2
 
3
- Digite para filtrar uma coleção com navegação por teclado, grupos e estado vazio. Command é a base
4
- do Select pesquisável; para uma escolha em formulário, use Select.
3
+ Use `Command` quando a pessoa precisar encontrar um item ou uma ação digitando parte do nome e
4
+ escolher pelo teclado. A lista filtra enquanto a busca muda, organiza os itens em grupos e mostra
5
+ um estado vazio quando nada corresponde. `Command` também é a base do `Select` pesquisável; para
6
+ escolher um valor em um formulário, use [`Select`](/ui/select).
5
7
 
6
8
  ```tsx preview
7
9
  <Command className="max-w-sm rounded-lg border border-border">
@@ -14,26 +16,38 @@ do Select pesquisável; para uma escolha em formulário, use Select.
14
16
  </CommandGroup>
15
17
  <CommandSeparator />
16
18
  <CommandGroup heading="Ações">
17
- <CommandItem><Plus /> Nova sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
19
+ <CommandItem><Plus /> Criar sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
18
20
  <CommandItem><Play /> Iniciar ambiente</CommandItem>
19
21
  </CommandGroup>
20
22
  </CommandList>
21
23
  </Command>
22
24
  ```
23
25
 
24
- ## Palette modal (CommandDialog)
26
+ ## Paleta de comandos em modal
25
27
 
26
- O Command embrulhado em um Dialog mantém um header compacto com título e close. A descrição
27
- permanece disponível para tecnologias assistivas. O atalho ⌘K (keydown no app) só troca o open —
28
- o conteúdo é o mesmo do inline.
28
+ `CommandDialog` apresenta a mesma lista dentro de um [`Dialog`](/ui/dialog), com um cabeçalho
29
+ compacto que mostra o título e o botão de fechar. A descrição fica disponível somente para
30
+ tecnologias assistivas. O componente não registra atalho de teclado: para abrir a paleta com
31
+ ⌘K, o aplicativo escuta o evento e altera `open`, como no exemplo.
29
32
 
30
33
  ```tsx preview
31
34
  const [open, setOpen] = useState(false)
32
35
 
36
+ useEffect(() => {
37
+ const onKeyDown = (event: KeyboardEvent): void => {
38
+ if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
39
+ event.preventDefault()
40
+ setOpen((current) => !current)
41
+ }
42
+ }
43
+ document.addEventListener('keydown', onKeyDown)
44
+ return () => document.removeEventListener('keydown', onKeyDown)
45
+ }, [])
46
+
33
47
  render(
34
48
  <>
35
49
  <Button variant="outline" onClick={() => setOpen(true)}>
36
- Abrir palette <CommandShortcut>⌘K</CommandShortcut>
50
+ Abrir paleta <CommandShortcut>⌘K</CommandShortcut>
37
51
  </Button>
38
52
  <CommandDialog open={open} onOpenChange={setOpen}>
39
53
  <CommandInput placeholder="Buscar comando…" />
@@ -80,6 +94,7 @@ render(
80
94
  | `title` | `string` | `'Paleta de comandos'` | Título exibido no header compacto; também nomeia o diálogo para tecnologias assistivas. |
81
95
  | `description` | `string` | `'Busque um comando para executar…'` | Descrição disponível somente para tecnologias assistivas. |
82
96
  | `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. |
97
+ | `className` | `string` | | Classes do conteúdo do diálogo, como a largura. |
83
98
 
84
99
  ## Propriedades de CommandItem
85
100
 
@@ -96,3 +111,10 @@ render(
96
111
  |---|---|---|---|
97
112
  | `heading` | `React.ReactNode` | | Título exibido acima dos itens do grupo. |
98
113
  | `value` | `string` | | Identificador do grupo; obrigatório e único quando não há `heading`. |
114
+
115
+ `CommandList` limita a altura a `18.75rem` e rola o excedente; `CommandEmpty` aparece somente quando
116
+ nenhum item corresponde à busca; `CommandSeparator` divide grupos; e `CommandShortcut` mostra, à
117
+ direita do item, a combinação de teclas que o aplicativo associou à ação. Esses componentes não têm
118
+ propriedades próprias além das props nativas do elemento que renderizam; `className` soma classes
119
+ ao estilo padrão de cada um. O atalho exibido por `CommandShortcut` é apenas informativo:
120
+ registrar o evento continua com o aplicativo.
@@ -1,6 +1,14 @@
1
1
  ## Envio de texto
2
2
 
3
- A caixa de escrever da casa: textarea em uma pílula elevada (`rounded-xl` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/ui/chat) extraído — use sozinho quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
3
+ Use `Composer` quando a tela precisar receber um texto para envio sem apresentar uma conversa, como
4
+ a descrição que abre uma nova sessão de trabalho. É a mesma caixa de escrita do [Chat](/ui/chat):
5
+ um campo que cresce com o texto, com o botão de enviar dentro da moldura. **Enter** envia e
6
+ **Shift+Enter** quebra a linha.
7
+
8
+ O componente é controlado: `value` e `onChange` pertencem a quem o compõe, que também limpa o texto
9
+ depois do envio. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse
10
+ dono use **↑** e **↓** para navegar por um histórico próprio; sem eles, as setas mantêm o
11
+ comportamento nativo do campo.
4
12
 
5
13
  ```tsx preview col
6
14
  const [text, setText] = React.useState('')
@@ -15,9 +23,15 @@ render(
15
23
  )
16
24
  ```
17
25
 
18
- ## Com seletores (barra de ações)
26
+ ## Com seletores na barra de ações
27
+
28
+ Com `actions`, o composer passa a ter duas linhas: o campo em cima e, embaixo, uma barra com os
29
+ seletores à esquerda e o botão de enviar à direita. Use ali escolhas que definem o destino do texto,
30
+ como o aplicativo, a tarefa ou o agente. Mantenha os controles discretos, como um `Button` `ghost` ou
31
+ um [`Select`](/ui/select) `variant="ghost"`: a barra acompanha o texto e não é um formulário.
19
32
 
20
- Passe `actions` e o composer vira **duas linhas**: textarea em cima, uma barra limpa embaixo com os seletores à ESQUERDA e o enviar à direita. Estilize os controles como discretos (ghost, sem borda) — a barra não é um formulário. É onde o Maestro prende **app + task** e a GB, o **agente**. O `submitDisabled` trava o enviar além de vazio/busy (ex.: falta escolher o app), sem esconder a barra.
33
+ `submitDisabled` impede o envio enquanto falta uma condição além do texto, como escolher o
34
+ aplicativo, sem esconder a barra.
21
35
 
22
36
  ```tsx preview col
23
37
  const [text, setText] = React.useState('')
@@ -55,11 +69,12 @@ render(
55
69
  |---|---|---|---|
56
70
  | `value` | `string` | | Texto controlado; o dono do estado é quem compõe. |
57
71
  | `onChange` | `(value: string) => void` | | Recebe cada alteração do texto. |
58
- | `onSubmit` | `() => void` | | Enter (sem Shift) ou o botão enviar; só dispara quando dá para enviar. |
72
+ | `onSubmit` | `() => void` | | Chamado por Enter (sem Shift) ou pelo botão de enviar, somente quando há texto e nem `busy` nem `submitDisabled` estão ativos. |
59
73
  | `onHistoryPrevious` / `onHistoryNext` | `() => boolean` | | Navegação por ↑/↓ no histórico do dono; retorne `true` quando a tecla foi consumida. |
60
- | `busy` | `boolean` | `false` | Trava o composer enquanto o turno corre; o enviar vira spinner. |
61
- | `submitDisabled` | `boolean` | `false` | Gate extra de envio além de vazio e `busy` (ex.: falta escolher o app). |
74
+ | `busy` | `boolean` | `false` | Bloqueia o envio enquanto a operação anterior corre e troca o ícone de enviar por um spinner. O campo continua editável. |
75
+ | `submitDisabled` | `boolean` | `false` | Bloqueia o envio por uma condição do consumidor, como um seletor ainda sem escolha. |
62
76
  | `placeholder` | `string` | `'Escreva uma mensagem…'` | Texto de orientação do campo. |
63
- | `rows` | `number` | `1` | Linhas iniciais do textarea; ele cresce com o conteúdo. |
77
+ | `rows` | `number` | `1` | Linhas iniciais do campo, que cresce com o conteúdo até `10rem`. |
64
78
  | `autoFocus` | `boolean` | | Foca o campo ao montar. |
65
- | `actions` | `ReactNode` | | Controles discretos à esquerda do enviar; presente, o composer vira duas linhas. |
79
+ | `actions` | `ReactNode` | | Controles discretos à esquerda do botão de enviar; com eles, o composer passa a ter duas linhas. |
80
+ | `className` | `string` | | Classes aplicadas à moldura do composer. |
@@ -4,12 +4,19 @@ Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `
4
4
  `DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
5
5
  espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
6
6
 
7
- No caso comum, prefira o shorthand:
7
+ O `Content` é contínuo por definição e não expõe `layout`: ele é uma região nomeada dentro de outra
8
+ superfície, e a escolha entre regiões seccionadas e contínuas pertence a quem a hospeda — o
9
+ [`Card`](/ui/card), o [`Dialog`](/ui/dialog), o [`Drawer`](/ui/drawer) ou o [`Pane`](/ui/pane).
10
+ Quando a própria região precisar de uma fronteira visível, use `framed`, descrito adiante. Pelo
11
+ mesmo motivo o `Content` não tem rodapé: um rodapé pertence à superfície que a hospeda.
12
+
13
+ No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
8
14
 
9
15
  ```tsx preview col
10
16
  render(
11
17
  <Content
12
18
  title="Dispositivos conectados"
19
+ count={3}
13
20
  description="Sessões com acesso à sua conta."
14
21
  actions={<Button variant="outline">Encerrar outras sessões</Button>}
15
22
  >
@@ -126,13 +133,33 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
126
133
  ## Coleção nomeada
127
134
 
128
135
  Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
129
- `ActionList` executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
136
+ [`ActionList`](/ui/action-list) executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
130
137
  `ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
131
138
  toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
132
- paralela de componentes `List*`. Na variante `page`, as ações ficam no extremo oposto ao título. A
133
- criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
134
- nomeado `Criar recurso`; o diálogo aberto pelo
135
- gatilho repete esse título e a edição usa `Editar recurso`.
139
+ paralela de componentes `List*`.
140
+
141
+ Quando a coleção é o assunto da página, use `variant="page"` com `level={1}`: o `Content` assume a
142
+ escala do cabeçalho de [`Page`](/ui/page) e passa a ser o heading principal. As ações ficam no
143
+ extremo oposto ao título, como em qualquer variante.
144
+
145
+ ```tsx preview col
146
+ render(
147
+ <Content level={1} variant="page" title="Clientes" actions={<Button>Criar cliente</Button>}>
148
+ <div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
149
+ A lista de clientes ocupa o corpo.
150
+ </div>
151
+ </Content>,
152
+ );
153
+ ```
154
+
155
+ A criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
156
+ nomeado `Criar {recurso}` (por exemplo, `Criar cliente`); o diálogo aberto pelo gatilho repete esse
157
+ título, e a edição usa `Editar {recurso}`.
158
+
159
+ Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção pode ocupar a altura da página e rolar só os
160
+ resultados. `Content` e `ContentBody` repassam a altura até a [`ActionList`](/ui/action-list) sem
161
+ classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio conteúdo. A seção
162
+ “Ocupar a altura disponível” da `ActionList` mostra a composição completa.
136
163
 
137
164
  ## Propriedades de Content
138
165
 
@@ -141,12 +168,15 @@ gatilho repete esse título e a edição usa `Editar recurso`.
141
168
  | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
142
169
  | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
143
170
  | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
144
- | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira; em `page`, ficam no extremo oposto ao título. |
171
+ | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
145
172
  | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
146
173
  | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
147
174
  | `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
148
175
  | `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
149
176
  | `frameVariant` | `'default' \| 'legend'` | `'default'` | Em `fieldset` com moldura, mantém a borda contínua ou integra visualmente a legenda à borda. Não altera a semântica. |
177
+ | `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
178
+
179
+ As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
150
180
 
151
181
  Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
152
182
  `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
@@ -1,6 +1,9 @@
1
1
  ## Copiar por ícone
2
2
 
3
- Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom para toolbar ou célula estreita, ao lado de um ID/token/slug.
3
+ Use `Copyable` para que a pessoa copie um valor exato com um clique, como um identificador, uma
4
+ chave ou um endereço. Sem filhos, ele é um botão só com ícone: copia `value` e troca o ícone por um
5
+ check durante 1,5 segundo. Essa forma cabe em barras de ferramentas e células estreitas, ao lado do
6
+ valor exibido.
4
7
 
5
8
  ```tsx preview
6
9
  <Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
@@ -8,7 +11,8 @@ Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5
8
11
 
9
12
  ## Com rótulo
10
13
 
11
- Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Componha o chrome (borda, mono) por fora, via `className`.
14
+ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Borda,
15
+ fonte monoespaçada e demais detalhes visuais entram por `className`.
12
16
 
13
17
  ```tsx preview
14
18
  <Copyable
@@ -19,21 +23,48 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
19
23
  </Copyable>
20
24
  ```
21
25
 
26
+ ## Nome acessível
27
+
28
+ Com rótulo visível, o nome do botão é o próprio texto dos filhos. Quem usa comando de voz aciona o
29
+ botão dizendo o que vê, e o nome não muda durante a confirmação. Sem filhos, o botão se chama
30
+ “Copiar” e, durante a confirmação, “Copiado”.
31
+
32
+ A confirmação também é anunciada por leitores de tela: uma região de status, invisível na tela e
33
+ fora do botão, diz “Copiado” sem interromper a leitura em andamento.
34
+
35
+ Quando houver mais de um botão só com ícone na mesma tela, passe `aria-label` para distinguir o que
36
+ cada um copia, como “Copiar o ID do pedido”. Com rótulo visível, o `aria-label` raramente é
37
+ necessário; se usá-lo, comece pelo texto que a pessoa vê. Um `aria-label` próprio substitui os nomes
38
+ padrão, inclusive o “Copiado”, e o anúncio da confirmação continua.
39
+
22
40
  ## Duração do feedback
23
41
 
24
- `feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos. Se a
25
- área de transferência não estiver disponível, a ação não produz efeito.
42
+ `feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos.
26
43
 
27
44
  ```tsx preview
28
- <Copyable value="copiado devagar" feedbackMs={3000} className="text-muted-foreground hover:text-foreground">
29
- copiar (3s)
45
+ <Copyable
46
+ value="https://opus.softize.dev/w/empresa-x"
47
+ feedbackMs={3000}
48
+ className="text-muted-foreground hover:text-foreground"
49
+ >
50
+ Copiar link
30
51
  </Copyable>
31
52
  ```
32
53
 
54
+ A confirmação só aparece depois que a área de transferência aceita o texto. Se ela não estiver
55
+ disponível, como em uma página sem HTTPS, ou recusar a escrita, o ícone não muda.
56
+
33
57
  ## Propriedades de Copyable
34
58
 
35
59
  | Propriedade | Tipo | Padrão | Descrição |
36
60
  |---|---|---|---|
37
- | `value` | `string` | | O texto que vai para a área de transferência no clique. |
61
+ | `value` | `string` | obrigatório | O texto que vai para a área de transferência no clique. |
38
62
  | `feedbackMs` | `number` | `1500` | Duração do estado “copiado”, em milissegundos. |
39
- | `children` | `ReactNode` | | Rótulo ao lado do ícone; sem ele, o controle é só o ícone com nome acessível. |
63
+ | `children` | `ReactNode` | | Rótulo visível ao lado do ícone. Quando presente, é o nome acessível do botão. |
64
+ | `aria-label` | `string` | sem filhos, `'Copiar'`, ou `'Copiado'` durante a confirmação | Nome acessível do botão. Substitui o nome vindo dos filhos e os nomes padrão. |
65
+
66
+ Os demais atributos vão para o elemento `button`. Um `onClick` próprio roda antes da cópia. O
67
+ botão é o único elemento que o Copyable coloca no contêiner: a região de status fica no fim do
68
+ `body`, visível apenas para leitores de tela, e é uma só para todos os Copyable da página. Ela
69
+ aparece com o primeiro Copyable montado e sai com o último, então uma tabela com um botão de copiar
70
+ por linha não multiplica regiões.
@@ -4,15 +4,16 @@ title: Customização
4
4
 
5
5
  # Customização
6
6
 
7
- Cinco alavancas, do global ao pontual — e um limite de propósito. O caminho previsto é compor
8
- e configurar, nunca forkar componente: o que não cabe nas alavancas evolui no Opus (com
9
- divergência declarada), para valer para casa toda.
7
+ Quando a aplicação precisa de outra marca, outra largura ou outro comportamento, o Opus oferece
8
+ cinco alavancas, da mais global à mais pontual. Todas partem de compor e configurar os componentes
9
+ existentes; nenhuma exige copiar um componente para dentro do projeto e mantê-lo à parte. O que não
10
+ couber nelas evolui no próprio Opus, para valer em todos os projetos.
10
11
 
11
12
  ## 1 · Identidade por tokens
12
13
 
13
- > O jeito canônico de vestir a marca. Sobrescreva as vars no `:root` depois de importar o
14
- > `theme.css` — todos os componentes acompanham, sem forkar nada. Dark incluso (a classe `.dark`
15
- > re-declara as vars).
14
+ Para aplicar a marca, sobrescreva as variáveis CSS em `:root` depois de importar `theme.css`. Todos
15
+ os componentes acompanham a mudança, e o tema escuro também: a classe `.dark` redeclara as mesmas
16
+ variáveis.
16
17
 
17
18
  ```css
18
19
  /* index.css do app — depois do import do tema. */
@@ -26,27 +27,20 @@ divergência declarada), para valer para casa toda.
26
27
  ```
27
28
 
28
29
  O Opus deriva de `rounded-xs` a `rounded-2xl` dessa base. Assim, a marca muda a presença dos
29
- cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel.
30
+ cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel. A lista
31
+ completa de variáveis está em [Tokens](/ui/tokens).
30
32
 
31
33
  O tema não define `font-size` em `html`: a fonte raiz pertence ao navegador e à aplicação.
32
34
  Quando o produto precisa de outra densidade, declare essa escolha no CSS do app. Por exemplo,
33
- `html { font-size: 93.75%; }` conserva a proporção que uma raiz de 15 teria sobre a base usual
34
- de 16, sem transformar esse valor em uma regra da biblioteca.
35
-
36
- ## Layout desktop
37
-
38
- O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
39
- Classes de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
40
- breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
41
-
42
- Não adicione uma adaptação isolada por viewport ou container em `className`. Uma futura
43
- responsividade precisa começar pelo shell e formar um contrato comum para navegação, splits,
44
- tabelas e componentes.
35
+ `html { font-size: 93.75%; }` reduz a raiz a 15/16 da fonte que o navegador oferece, e tudo que o
36
+ Opus mede em `rem` acompanha a proporção. O valor continua sendo uma escolha do app, não uma regra
37
+ da biblioteca.
45
38
 
46
39
  ## 2 · className em tudo
47
40
 
48
- > Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
49
- > vence o conflito. Para layout local (largura, margem, grid) — não para repintar o visual da casa.
41
+ Todo componente aceita `className` e o combina com as próprias classes por tailwind-merge: quando
42
+ duas classes disputam a mesma propriedade, a do consumidor vence. Use essa alavanca para o layout
43
+ local, como largura, margem e grid, e não para repintar o visual dos componentes.
50
44
 
51
45
  ```tsx
52
46
  <Button className="w-full">Continuar</Button>
@@ -56,73 +50,88 @@ tabelas e componentes.
56
50
 
57
51
  ## 3 · Recomposição estrutural
58
52
 
59
- > Os componentes são explodidos em slots. `asChild` (Radix Slot) renderiza como outro elemento
60
- > mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa em um elemento arbitrário.
53
+ Os componentes são divididos em partes nomeadas, como `CardHeader` e `CardBody`, e a tela usa só as
54
+ que precisa. `asChild` faz o filho assumir o estilo e o comportamento do componente, e as funções
55
+ `*Variants` aplicam o visual de um componente a um elemento qualquer.
61
56
 
62
57
  ```tsx preview
63
58
  <Button asChild variant="outline">
64
- <a href="#" onClick={(e) => e.preventDefault()}>Âncora com cara de botão</a>
59
+ <a href="#" onClick={(e) => e.preventDefault()}>Link com aparência de botão</a>
65
60
  </Button>
66
61
  ```
67
62
 
68
63
  ```tsx
69
- // Slots: só o que a tela pede.
64
+ // Partes: só o que a tela pede.
70
65
  <Card>
71
66
  <CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
72
67
  <CardBody>…</CardBody>
73
68
  </Card>
74
69
 
75
- // asChild: o filho VIRA o botão (sem forkar estilo).
70
+ // asChild: o link assume o estilo e o comportamento do botão.
76
71
  <Button asChild><a href="/docs">Abrir documentação</a></Button>
77
72
 
78
- // buttonVariants: a cara da casa em um elemento qualquer.
73
+ // buttonVariants: o visual do botão em outro elemento.
79
74
  <a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
80
75
  ```
81
76
 
82
77
  ## 4 · Comportamento por props
83
78
 
84
- > Modo controlado em tudo que abre ou seleciona (`open`/`onOpenChange`, `value`/`onValueChange`),
85
- > posicionamento (`align`/`side`/`sideOffset`), `showCloseButton`, `delayDuration`… A orquestração
86
- > é do implementador; o visual continua o da casa.
79
+ Tudo o que abre ou seleciona aceita modo controlado (`open` e `onOpenChange`, `value` e
80
+ `onValueChange`). Posicionamento (`align`, `side`, `sideOffset`) e tempos (`delayDuration`) também
81
+ são props. A orquestração fica com a aplicação; o visual continua o dos componentes.
87
82
 
88
83
  ```tsx
89
84
  const [open, setOpen] = useState(false)
90
85
 
91
86
  <Dialog open={open} onOpenChange={setOpen}>…</Dialog>
92
- <DialogContent showCloseButton={false} /> {/* modal que exige ação */}
87
+ <Dialog mode="alert">…</Dialog> {/* exige uma resposta explícita */}
93
88
  <PopoverContent align="start" sideOffset={8} />
94
89
  <TooltipProvider delayDuration={200}>…
95
90
  ```
96
91
 
92
+ Para exigir uma resposta, use `Dialog mode="alert"`: ele não fecha pelo clique externo e não mostra
93
+ o botão de fechar. Apenas esconder o botão com `showCloseButton={false}` não impede o fechamento por
94
+ Esc nem pelo clique externo. Veja [Dialog](/ui/dialog).
95
+
97
96
  ## 5 · Nos patterns, o contrato é a customização
98
97
 
99
- > Os patterns (ActionForm/Search/View/Trigger): o spec dirige. `fields`/`widget`/`showWhen`/
100
- > `fieldOptions` no form, `columns` no search, render prop no view. O caminho previsto é a feature
101
- > EMBRULHAR o pattern — nunca forkar.
98
+ Nos patterns — [`ActionForm`](/ui/action-form), [`ActionList`](/ui/action-list),
99
+ [`ActionView`](/ui/action-view) e [`ActionTrigger`](/ui/action-trigger) —, o contrato da action
100
+ determina campos, colunas e estados. A tela ajusta o que é dela: `fieldOptions` no formulário,
101
+ `columns` na lista, a função de render no `ActionView`. Para ir além, envolva o pattern em um
102
+ componente da feature em vez de copiá-lo.
102
103
 
103
104
  ```tsx
104
- // O form NASCE do contrato; a tela só ajusta o que é seu.
105
+ // O formulário vem do contrato; a tela só fornece as opções que conhece.
105
106
  <ActionForm
106
107
  action={workspaceCreateContract}
107
- fieldOptions={{ skillIds: skillsDoWorkspace }}
108
+ fieldOptions={{ ownerId: workspaceMembers }}
108
109
  onSuccess={(d) => navigate(`/workspaces/${d.id}`)}
109
110
  />
110
111
 
111
- // O search recebe as colunas; a célula é render livre.
112
+ // A lista recebe as colunas; a célula é render livre.
112
113
  <ActionList action={listAction} input={{ q }} columns={[
113
114
  { key: 'name', header: 'Workspace', cell: (w) => w.name },
114
115
  ]} />
115
116
  ```
116
117
 
117
- ## O limite — de propósito
118
+ ## Largura mínima e densidade
118
119
 
119
- > O look curado não é customizável no app: rodapé-faixa do dialog, elevação no dark, `active:brightness-90`
120
- > do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
120
+ O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
121
+ Props de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
122
+ breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
121
123
 
122
- ```tsx preview
123
- <Badge context="success">Cabe nas alavancas</Badge>
124
- ```
124
+ Não adicione em `className` uma adaptação isolada por viewport ou container. Uma responsividade
125
+ futura precisa começar pelo shell e formar um contrato comum para navegação, splits, tabelas e
126
+ componentes.
127
+
128
+ ## O que fica fora das alavancas
129
+
130
+ Alguns detalhes fazem parte da identidade visual do Opus e são iguais em todo projeto: a faixa do
131
+ rodapé do dialog, a elevação no tema escuro, o escurecimento do botão ao ser pressionado
132
+ (`active:brightness-90`) e a seta do tooltip. Eles não são configuráveis pela aplicação.
125
133
 
126
- Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
127
- contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
128
- declarada (skill `build-opus-ui`) — assim a mudança vale para casa toda, e esta doc passa a mostrá-la.
134
+ Se uma necessidade real não cabe nas alavancas (tokens, `className`, partes e `asChild`, props e
135
+ contrato), não crie uma variação local. Proponha a evolução do componente no Opus, com a
136
+ divergência declarada conforme a skill `build-opus-ui`; assim a mudança vale para todos os projetos
137
+ e passa a aparecer nesta documentação.