@softize/opus 19.0.0 → 20.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 (148) hide show
  1. package/CHANGELOG.md +239 -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/0020-icon-tiles-are-explicitly-composed.md +8 -0
  7. package/docs/adr/0021-content-carries-structure-and-semantic-element.md +7 -0
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +19 -1
  9. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  10. package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +134 -0
  11. package/docs/data-products.md +15 -9
  12. package/docs/protocol.md +3 -4
  13. package/docs/releasing.md +2 -2
  14. package/package.json +2 -2
  15. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  16. package/registry/templates/app/package.json +1 -1
  17. package/src/core/runtime.ts +96 -8
  18. package/src/core/types.ts +5 -2
  19. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  20. package/src/ui/components/patterns/content-header.tsx +109 -18
  21. package/src/ui/components/patterns/fill-region.ts +150 -0
  22. package/src/ui/components/patterns/list.tsx +91 -92
  23. package/src/ui/components/patterns/page.tsx +20 -4
  24. package/src/ui/components/patterns/presentation.tsx +52 -37
  25. package/src/ui/components/patterns/split.tsx +44 -4
  26. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  27. package/src/ui/components/patterns/surface-header.tsx +58 -25
  28. package/src/ui/components/patterns/trigger.tsx +12 -2
  29. package/src/ui/components/primitives/alert.tsx +9 -1
  30. package/src/ui/components/primitives/avatar.tsx +7 -3
  31. package/src/ui/components/primitives/card.tsx +1 -1
  32. package/src/ui/components/primitives/carousel.tsx +34 -5
  33. package/src/ui/components/primitives/checkbox.tsx +11 -3
  34. package/src/ui/components/primitives/command.tsx +2 -1
  35. package/src/ui/components/primitives/copyable.tsx +120 -10
  36. package/src/ui/components/primitives/dialog.tsx +42 -11
  37. package/src/ui/components/primitives/drawer.tsx +10 -2
  38. package/src/ui/components/primitives/empty.tsx +1 -1
  39. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  40. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  41. package/src/ui/components/primitives/input-group.tsx +9 -2
  42. package/src/ui/components/primitives/input-otp.tsx +1 -1
  43. package/src/ui/components/primitives/kbd.tsx +1 -1
  44. package/src/ui/components/primitives/label.tsx +3 -0
  45. package/src/ui/components/primitives/menu.tsx +5 -0
  46. package/src/ui/components/primitives/popover.tsx +1 -1
  47. package/src/ui/components/primitives/progress.tsx +9 -1
  48. package/src/ui/components/primitives/slider.tsx +62 -11
  49. package/src/ui/components/primitives/truncate.tsx +10 -1
  50. package/src/ui/docs/content/accordion.md +74 -27
  51. package/src/ui/docs/content/action-form-card.md +24 -15
  52. package/src/ui/docs/content/action-form-dialog.md +17 -7
  53. package/src/ui/docs/content/action-form.md +92 -77
  54. package/src/ui/docs/content/action-list-dialog.md +75 -38
  55. package/src/ui/docs/content/action-list.md +141 -58
  56. package/src/ui/docs/content/action-trigger.md +27 -10
  57. package/src/ui/docs/content/action-view.md +14 -14
  58. package/src/ui/docs/content/actions.md +161 -66
  59. package/src/ui/docs/content/ai.md +43 -44
  60. package/src/ui/docs/content/alert.md +83 -29
  61. package/src/ui/docs/content/ask.md +51 -26
  62. package/src/ui/docs/content/aspect-ratio.md +22 -24
  63. package/src/ui/docs/content/audit.md +23 -19
  64. package/src/ui/docs/content/auth.md +38 -19
  65. package/src/ui/docs/content/avatar.md +54 -5
  66. package/src/ui/docs/content/badge.md +29 -9
  67. package/src/ui/docs/content/breadcrumb.md +21 -8
  68. package/src/ui/docs/content/button.md +73 -43
  69. package/src/ui/docs/content/calendar.md +134 -26
  70. package/src/ui/docs/content/card.md +41 -15
  71. package/src/ui/docs/content/carousel.md +37 -6
  72. package/src/ui/docs/content/chat.md +118 -37
  73. package/src/ui/docs/content/checkbox.md +73 -7
  74. package/src/ui/docs/content/cli.md +94 -55
  75. package/src/ui/docs/content/collapsible.md +43 -19
  76. package/src/ui/docs/content/command.md +30 -8
  77. package/src/ui/docs/content/composer.md +23 -8
  78. package/src/ui/docs/content/content.md +119 -27
  79. package/src/ui/docs/content/copyable.md +39 -8
  80. package/src/ui/docs/content/customization.md +55 -46
  81. package/src/ui/docs/content/cycle.md +33 -18
  82. package/src/ui/docs/content/data-products.md +115 -0
  83. package/src/ui/docs/content/data-state.md +20 -6
  84. package/src/ui/docs/content/data.md +20 -19
  85. package/src/ui/docs/content/detail.md +12 -5
  86. package/src/ui/docs/content/dialog.md +107 -24
  87. package/src/ui/docs/content/dictionary-value.md +4 -4
  88. package/src/ui/docs/content/dock.md +54 -19
  89. package/src/ui/docs/content/dot.md +35 -5
  90. package/src/ui/docs/content/drawer.md +63 -19
  91. package/src/ui/docs/content/empty-value.md +20 -6
  92. package/src/ui/docs/content/empty.md +24 -3
  93. package/src/ui/docs/content/events.md +76 -16
  94. package/src/ui/docs/content/executions.md +9 -9
  95. package/src/ui/docs/content/field.md +21 -6
  96. package/src/ui/docs/content/getting-started.md +44 -34
  97. package/src/ui/docs/content/icon-picker.md +19 -13
  98. package/src/ui/docs/content/icon-tile.md +38 -15
  99. package/src/ui/docs/content/input-otp.md +58 -7
  100. package/src/ui/docs/content/input.md +22 -3
  101. package/src/ui/docs/content/item.md +53 -6
  102. package/src/ui/docs/content/kbd.md +5 -1
  103. package/src/ui/docs/content/label.md +13 -4
  104. package/src/ui/docs/content/log.md +5 -5
  105. package/src/ui/docs/content/markdown.md +5 -4
  106. package/src/ui/docs/content/mcp.md +49 -23
  107. package/src/ui/docs/content/menu.md +95 -10
  108. package/src/ui/docs/content/metric-card.md +12 -4
  109. package/src/ui/docs/content/observability.md +10 -9
  110. package/src/ui/docs/content/page.md +118 -12
  111. package/src/ui/docs/content/pagination.md +13 -5
  112. package/src/ui/docs/content/pane.md +55 -5
  113. package/src/ui/docs/content/popover.md +42 -5
  114. package/src/ui/docs/content/presentation.md +91 -47
  115. package/src/ui/docs/content/progress.md +27 -14
  116. package/src/ui/docs/content/queue.md +39 -21
  117. package/src/ui/docs/content/radio-group.md +26 -4
  118. package/src/ui/docs/content/router.md +29 -10
  119. package/src/ui/docs/content/runtime.md +90 -54
  120. package/src/ui/docs/content/scheduler.md +53 -21
  121. package/src/ui/docs/content/scroll-area.md +42 -35
  122. package/src/ui/docs/content/select.md +78 -33
  123. package/src/ui/docs/content/semantic-context.md +21 -2
  124. package/src/ui/docs/content/separator.md +15 -9
  125. package/src/ui/docs/content/sidebar.md +3 -2
  126. package/src/ui/docs/content/skeleton.md +6 -1
  127. package/src/ui/docs/content/slider.md +37 -9
  128. package/src/ui/docs/content/spinner.md +4 -3
  129. package/src/ui/docs/content/split.md +19 -0
  130. package/src/ui/docs/content/storage.md +8 -9
  131. package/src/ui/docs/content/switch.md +10 -7
  132. package/src/ui/docs/content/table.md +46 -8
  133. package/src/ui/docs/content/tabs.md +14 -4
  134. package/src/ui/docs/content/testing.md +76 -31
  135. package/src/ui/docs/content/textarea.md +5 -4
  136. package/src/ui/docs/content/toast.md +48 -7
  137. package/src/ui/docs/content/toggle.md +3 -3
  138. package/src/ui/docs/content/tokens.md +15 -26
  139. package/src/ui/docs/content/tooltip.md +18 -2
  140. package/src/ui/docs/content/truncate.md +36 -6
  141. package/src/ui/docs/content/ui.md +74 -22
  142. package/src/ui/docs/content/upgrading.md +45 -36
  143. package/src/ui/docs/plugin.ts +36 -2
  144. package/src/ui/docs/registry.tsx +6 -0
  145. package/src/ui/drivers/react.tsx +16 -8
  146. package/src/ui/meta.ts +32 -32
  147. package/src/ui/react.tsx +1 -0
  148. package/src/vite/design.ts +16 -4
@@ -10,12 +10,13 @@ superfície, e a escolha entre regiões seccionadas e contínuas pertence a quem
10
10
  Quando a própria região precisar de uma fronteira visível, use `framed`, descrito adiante. Pelo
11
11
  mesmo motivo o `Content` não tem rodapé: um rodapé pertence à superfície que a hospeda.
12
12
 
13
- No caso comum, prefira o shorthand:
13
+ No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
14
14
 
15
15
  ```tsx preview col
16
16
  render(
17
17
  <Content
18
18
  title="Dispositivos conectados"
19
+ count={3}
19
20
  description="Sessões com acesso à sua conta."
20
21
  actions={<Button variant="outline">Encerrar outras sessões</Button>}
21
22
  >
@@ -30,21 +31,15 @@ render(
30
31
 
31
32
  Use `framed` quando título e corpo precisarem formar uma única região delimitada. A moldura pertence
32
33
  ao `Content`; componentes internos, como `DetailGroup`, continuam responsáveis somente pelo layout
33
- e pela semântica dos próprios dados.
34
+ e pela semântica dos próprios dados. Por padrão, a borda permanece contínua e o header fica dentro
35
+ do recuo da região.
34
36
 
35
37
  ```tsx preview col
36
38
  render(
37
39
  <Content
38
40
  framed
39
- title={
40
- <span className="flex items-center gap-2">
41
- <UserRound
42
- className="size-4 text-muted-foreground"
43
- aria-hidden="true"
44
- />
45
- Cliente
46
- </span>
47
- }
41
+ title="Cliente"
42
+ media={<UserRound aria-hidden="true" />}
48
43
  >
49
44
  <DetailGroup columns={2}>
50
45
  <DetailField label="Nome" value="Carla Souza" />
@@ -54,16 +49,66 @@ render(
54
49
  );
55
50
  ```
56
51
 
52
+ ## Header integrado à moldura
53
+
54
+ Quando uma região compacta precisar integrar visualmente o header à borda, use
55
+ `frameVariant="legend"`. O acabamento não muda a semântica: uma região de leitura continua sendo
56
+ uma `section`, o título continua sendo um heading e descrição, contador e ações permanecem
57
+ disponíveis.
58
+
59
+ ```tsx preview col
60
+ render(
61
+ <Content
62
+ framed
63
+ frameVariant="legend"
64
+ title="Veículo"
65
+ media={<CarFront aria-hidden="true" />}
66
+ >
67
+ <DetailGroup columns={2}>
68
+ <DetailField label="Placa" value="ABC1D23" />
69
+ <DetailField label="Modelo" value="Corolla XEi" />
70
+ </DetailGroup>
71
+ </Content>,
72
+ );
73
+ ```
74
+
75
+ Para interromper a borda, o header pinta o título e as ações com o fundo da superfície que hospeda o
76
+ `Content`, lido da variável CSS `--surface-background`. `Card`, `Dialog` e `Drawer` já a declaram
77
+ com o fundo que pintam; fora deles, vale `--background`. Uma superfície com fundo próprio, como um
78
+ `Card` com `bg-*`, um `Popover` ou um contêiner da aplicação, precisa declarar a variável com o
79
+ mesmo fundo. Sem isso, a faixa atrás do título aparece com a cor da página.
80
+
81
+ ```tsx preview col
82
+ render(
83
+ <Card className="bg-muted [--surface-background:var(--muted)]">
84
+ <CardBody>
85
+ <Content framed frameVariant="legend" title="Veículo">
86
+ <DetailGroup columns={2}>
87
+ <DetailField label="Placa" value="ABC1D23" />
88
+ <DetailField label="Modelo" value="Corolla XEi" />
89
+ </DetailGroup>
90
+ </Content>
91
+ </CardBody>
92
+ </Card>,
93
+ );
94
+ ```
95
+
57
96
  ## Grupo de campos
58
97
 
59
98
  Quando a região nomear um conjunto de campos relacionados, use `as="fieldset"`. O `Content`
60
99
  renderiza o título como `legend`, preserva as props nativas do `fieldset` e mantém o mesmo contrato
61
100
  de título e corpo. Essa escolha altera somente a semântica e o comportamento nativo; com `framed`,
62
- a moldura permanece contínua. Nesse modo, contador, descrição e ações não entram na legenda.
101
+ a moldura permanece contínua. A mídia usa a mesma apresentação da `section`; contador, descrição e
102
+ ações não entram na legenda.
63
103
 
64
104
  ```tsx preview col
65
105
  render(
66
- <Content as="fieldset" framed title="Proprietário">
106
+ <Content
107
+ as="fieldset"
108
+ framed
109
+ title="Proprietário"
110
+ media={<UserRound aria-hidden="true" />}
111
+ >
67
112
  <FieldGroup>
68
113
  <Field>
69
114
  <FieldLabel htmlFor="owner-name">Nome</FieldLabel>
@@ -78,8 +123,9 @@ render(
78
123
  );
79
124
  ```
80
125
 
81
- Quando a legenda precisar integrar visualmente a borda, declare essa escolha separadamente. Use
82
- `frameVariant="legend"` em agrupamentos compactos, como as seções de filtros avançados.
126
+ O mesmo acabamento pode integrar a legenda nativa à borda. Declare `frameVariant="legend"` em
127
+ agrupamentos compactos, como as seções de filtros avançados; `as="fieldset"` continua sendo a
128
+ decisão semântica, e não uma forma de obter esse desenho.
83
129
 
84
130
  ```tsx preview col
85
131
  render(
@@ -103,13 +149,16 @@ render(
103
149
 
104
150
  Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
105
151
  `ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
106
- uma descrição, um contador (`ContentMeta`) e uma região de ações. O contador é próprio de
107
- `Content`; `PageHeader` reconhece somente navegação, título e ações.
152
+ uma mídia (`ContentMedia`), uma descrição, um contador (`ContentMeta`) e uma região de ações. O
153
+ contador é próprio de `Content`; `PageHeader` reconhece somente navegação, título e ações.
108
154
 
109
155
  ```tsx preview col
110
156
  render(
111
157
  <Content level={2}>
112
158
  <ContentHeader>
159
+ <ContentMedia>
160
+ <Monitor aria-hidden="true" />
161
+ </ContentMedia>
113
162
  <ContentTitle>Dispositivos conectados</ContentTitle>
114
163
  <ContentMeta>3</ContentMeta>
115
164
  <ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
@@ -132,28 +181,71 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
132
181
  ## Coleção nomeada
133
182
 
134
183
  Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
135
- `ActionList` executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
184
+ [`ActionList`](/ui/action-list) executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
136
185
  `ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
137
186
  toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
138
- paralela de componentes `List*`. Na variante `page`, as ações ficam no extremo oposto ao título. A
139
- criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
140
- nomeado `Criar recurso`; o diálogo aberto pelo
141
- gatilho repete esse título e a edição usa `Editar recurso`.
187
+ paralela de componentes `List*`.
188
+
189
+ Quando a coleção é o assunto da página, use `variant="page"` com `level={1}`: o `Content` assume a
190
+ escala do cabeçalho de [`Page`](/ui/page) e passa a ser o heading principal. As ações ficam no
191
+ extremo oposto ao título, como em qualquer variante.
192
+
193
+ ```tsx preview col
194
+ render(
195
+ <Content level={1} variant="page" title="Clientes" actions={<Button>Criar cliente</Button>}>
196
+ <div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
197
+ A lista de clientes ocupa o corpo.
198
+ </div>
199
+ </Content>,
200
+ );
201
+ ```
202
+
203
+ A criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
204
+ nomeado `Criar {recurso}` (por exemplo, `Criar cliente`); o diálogo aberto pelo gatilho repete esse
205
+ título, e a edição usa `Editar {recurso}`.
206
+
207
+ Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção pode ocupar a altura da página e rolar só os
208
+ resultados. `Content` e `ContentBody` repassam a altura até a [`ActionList`](/ui/action-list) sem
209
+ classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio conteúdo. A seção
210
+ “Ocupar a altura disponível” da `ActionList` mostra a composição completa.
142
211
 
143
212
  ## Propriedades de Content
144
213
 
145
214
  | Propriedade | Tipo | Padrão | Descrição |
146
215
  | -------------- | ---------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |
147
216
  | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
217
+ | `media` | `ReactNode` | | Forma curta: ícone ou imagem que identifica a região antes do título. |
148
218
  | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
149
219
  | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
150
- | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira; em `page`, ficam no extremo oposto ao título. |
220
+ | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
151
221
  | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
152
222
  | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
153
223
  | `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
154
224
  | `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
155
- | `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. |
225
+ | `frameVariant` | `'default' \| 'legend'` | `'default'` | Mantém a borda contínua ou integra visualmente o header à moldura. Não altera o elemento nem sua semântica. |
226
+ | `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
227
+
228
+ As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
229
+
230
+ Na composição explícita de uma `section`, `ContentHeader` recebe `ContentMedia`, `ContentTitle`,
231
+ `ContentMeta`, `ContentDescription` e `ContentActions`. Em `fieldset`, recebe `ContentMedia` e
232
+ `ContentTitle`, preservando a apresentação enquanto a legenda continua nomeando somente o grupo de
233
+ campos. `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level` próprios — a
234
+ hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Content`.
235
+
236
+ ## Propriedades de ContentMedia
237
+
238
+ | Propriedade | Tipo | Padrão | Descrição |
239
+ | ----------- | ------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
240
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'xs'` | Tamanho da moldura e do glifo na escala compartilhada dos controles. |
241
+ | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
242
+ | `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
243
+ | `className` | `string` | | Ajusta a moldura sem substituir a escala. |
244
+ | `asChild` | `boolean` | `false` | Cede o elemento para uma marca com visual próprio — um orbe, um logotipo — que ocupa o slot sem ganhar a moldura por cima. |
245
+
246
+ `ContentMedia` já é a moldura. Passe só o glifo e escolha `size`, `context` e `variant` no slot. Um
247
+ `IconTile` passado em `media` ou dentro de `ContentMedia` não vira moldura dentro de moldura: o slot
248
+ assume o lugar dele e herda o que ele declara. `media` com `null` ou `false` não desenha nada.
156
249
 
157
- Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
158
- `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
159
- próprios — a hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Content`.
250
+ Em `fieldset`, a mídia é sempre decorativa: o `Content` a esconde da árvore de acessibilidade para
251
+ que o grupo seja anunciado apenas pelo título.
@@ -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.
@@ -1,36 +1,51 @@
1
+ ---
2
+ title: Evolução contínua
3
+ ---
4
+
1
5
  # Evolução contínua
2
6
 
3
- O Opus cresce por **reincidência, nunca por especulação**. Quando o mesmo comportamento genérico reaparece em mais de um projeto, ele vira candidato a peça do Opus — e o projeto troca a implementação local pelo import. Estilo e conveniência não entram; comportamento que se repete, sim.
7
+ Use este fluxo quando o seu projeto precisar de algo que o Opus ainda não oferece ou encontrar um
8
+ defeito nele. O Opus incorpora um comportamento quando ele se repete em mais de um projeto; então o
9
+ projeto troca a implementação local pelo import. Preferência de estilo e conveniência isolada não
10
+ são motivo para incorporar uma peça.
4
11
 
5
- ## Onde você aponta
12
+ ## Registrar um apontamento
6
13
 
7
- Cada apontamento vira uma Issue no repositório do Opus. Antes de abrir, procure uma Issue existente para o mesmo comportamento e comente nela. Dois tipos:
14
+ Cada apontamento vira uma Issue em
15
+ [github.com/softize-dev/opus](https://github.com/softize-dev/opus/issues). Antes de abrir uma nova,
16
+ procure uma Issue sobre o mesmo comportamento e comente nela. Há dois tipos:
8
17
 
9
- - **`enhancement`** — código genérico que o Opus deveria ganhar (serviria a qualquer projeto). Antes de apontar, confira o que o Opus já tem (o inventário de componentes, os exports, esta doc): se já existe, use.
10
- - **`bug`** — um defeito ou limitação no próprio Opus, achado usando-o.
18
+ - **`enhancement`:** código genérico que serviria a qualquer projeto. Antes de apontar, confira se o
19
+ Opus já oferece o recurso no inventário de componentes, nos exports ou nesta documentação; se
20
+ oferecer, use-o.
21
+ - **`bug`:** defeito ou limitação do próprio Opus, encontrado durante o uso.
11
22
 
12
- Descreva cada apontamento com estes campos — um exemplo de cada tipo:
23
+ Descreva cada apontamento com estes campos, como nos exemplos:
13
24
 
14
25
  ```text
15
26
  {"type": "enhancement", "behavior": "o comportamento em uma frase.", "file": "src/onde-vive.ts", "kind": "ui|sdk|driver|infra"}
16
- {"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "repro + o workaround aplicado."}
27
+ {"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "como reproduzir e o contorno aplicado."}
17
28
  ```
18
29
 
19
- ## Não trave esperando
30
+ O pacote não coleta apontamentos do seu projeto automaticamente.
20
31
 
21
- O ponto do ciclo é **não bloquear a entrega**. Bateu em um gap ou em um bug do Opus:
32
+ ## Seguir sem esperar a correção
22
33
 
23
- 1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá para embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
24
- 2. **Entregue** a feature com o workaround.
25
- 3. **Aponte** na Issue e siga em frente.
34
+ Uma lacuna ou um defeito do Opus não precisa bloquear a entrega:
26
35
 
27
- O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu trabalho; o único custo de demorar é o workaround viver um pouco mais.
36
+ 1. **Contorne no projeto:** componha um wrapper local. O Opus é distribuído como código-fonte, então
37
+ qualquer superfície dele pode ser envolvida. Não edite `node_modules`, porque a alteração se
38
+ perde na próxima instalação.
39
+ 2. **Entregue** a funcionalidade com o contorno.
40
+ 3. **Registre** o apontamento na Issue.
28
41
 
29
- ## Como fecha
42
+ A correção no Opus acontece em paralelo. Enquanto ela não sai, o contorno continua no projeto.
30
43
 
31
- A triagem acontece em uma **Issue em [github.com/softize-dev/opus](https://github.com/softize-dev/opus/issues)**; procure uma existente antes de abrir. O pacote não coleta apontamentos do seu projeto por conta própria. O desfecho depende do tipo:
44
+ ## Como o apontamento termina
32
45
 
33
- - **Enhancement** aceito (com reincidência) → implementado no Opus → sai em um _bump_ → seu projeto [atualiza o Opus](upgrading) e troca o workaround pelo import.
34
- - **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
46
+ - Um **enhancement** aceito é implementado no Opus e publicado em uma nova versão. Ao
47
+ [atualizar o Opus](/docs/upgrading), o projeto troca o contorno pelo import.
48
+ - Um **bug** vira uma correção com entrada no `CHANGELOG`; na atualização, o contorno sai.
35
49
 
36
- A régua e a decisão ficam com quem mantém o Opus — hoje, a **Softize**. O registro curado das promoções vive no `PROMOTED.md` do pacote.
50
+ A decisão sobre o que entra no Opus fica com a Softize, que mantém o pacote. O registro das peças
51
+ incorporadas está no `PROMOTED.md` do pacote.