@softize/opus 23.0.0 → 24.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 (95) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/bin/cli.mjs +2 -0
  3. package/bin/lib/check.mjs +241 -42
  4. package/bin/lib/copy.mjs +16 -34
  5. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  6. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
  7. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
  8. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  9. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  10. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  11. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  12. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  13. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  14. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  15. package/package.json +5 -1
  16. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  17. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  18. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  19. package/registry/templates/app/src/App.tsx +24 -12
  20. package/src/ui/appearance.ts +149 -0
  21. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  22. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  23. package/src/ui/components/patterns/confirm.tsx +3 -1
  24. package/src/ui/components/patterns/fill-region.ts +8 -7
  25. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  26. package/src/ui/components/patterns/form.tsx +8 -7
  27. package/src/ui/components/patterns/list.tsx +15 -19
  28. package/src/ui/components/patterns/page.tsx +15 -11
  29. package/src/ui/components/patterns/pane.tsx +9 -240
  30. package/src/ui/components/patterns/presentation.tsx +14 -14
  31. package/src/ui/components/patterns/sidebar.tsx +7 -17
  32. package/src/ui/components/patterns/split.tsx +111 -59
  33. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  34. package/src/ui/components/patterns/structural-media.tsx +26 -0
  35. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  36. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  37. package/src/ui/components/patterns/surface.tsx +620 -0
  38. package/src/ui/components/patterns/trigger.tsx +11 -9
  39. package/src/ui/components/primitives/ask.tsx +155 -83
  40. package/src/ui/components/primitives/button.tsx +11 -9
  41. package/src/ui/components/primitives/checkbox.tsx +8 -8
  42. package/src/ui/components/primitives/command.tsx +1 -1
  43. package/src/ui/components/primitives/composer.tsx +47 -45
  44. package/src/ui/components/primitives/detail.tsx +9 -2
  45. package/src/ui/components/primitives/dialog.tsx +60 -26
  46. package/src/ui/components/primitives/drawer.tsx +58 -12
  47. package/src/ui/components/primitives/icon-picker.tsx +90 -73
  48. package/src/ui/components/primitives/input-group.tsx +35 -28
  49. package/src/ui/components/primitives/input-otp.tsx +16 -16
  50. package/src/ui/components/primitives/input.tsx +20 -10
  51. package/src/ui/components/primitives/item.tsx +9 -12
  52. package/src/ui/components/primitives/menu.tsx +44 -65
  53. package/src/ui/components/primitives/metric-card.tsx +75 -59
  54. package/src/ui/components/primitives/radio-group.tsx +9 -9
  55. package/src/ui/components/primitives/select.tsx +344 -209
  56. package/src/ui/components/primitives/switch.tsx +10 -10
  57. package/src/ui/components/primitives/table.tsx +29 -24
  58. package/src/ui/components/primitives/tabs.tsx +4 -2
  59. package/src/ui/components/primitives/textarea.tsx +6 -6
  60. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  61. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  62. package/src/ui/components/primitives/toggle.tsx +82 -16
  63. package/src/ui/docs/DocBrowser.tsx +77 -69
  64. package/src/ui/docs/content/action-form-card.md +9 -9
  65. package/src/ui/docs/content/action-list.md +76 -70
  66. package/src/ui/docs/content/action-view.md +33 -18
  67. package/src/ui/docs/content/button.md +1 -1
  68. package/src/ui/docs/content/communication.md +1 -1
  69. package/src/ui/docs/content/customization.md +64 -11
  70. package/src/ui/docs/content/detail.md +8 -6
  71. package/src/ui/docs/content/dialog.md +100 -58
  72. package/src/ui/docs/content/drawer.md +63 -19
  73. package/src/ui/docs/content/field.md +14 -15
  74. package/src/ui/docs/content/icon-tile.md +14 -13
  75. package/src/ui/docs/content/page.md +34 -26
  76. package/src/ui/docs/content/pane.md +41 -290
  77. package/src/ui/docs/content/presentation.md +3 -3
  78. package/src/ui/docs/content/separator.md +6 -6
  79. package/src/ui/docs/content/sidebar.md +68 -266
  80. package/src/ui/docs/content/skeleton.md +20 -18
  81. package/src/ui/docs/content/split.md +14 -14
  82. package/src/ui/docs/content/surface.md +513 -0
  83. package/src/ui/docs/content/tabs.md +20 -20
  84. package/src/ui/docs/content/toggle.md +117 -65
  85. package/src/ui/docs/content/tokens.md +124 -40
  86. package/src/ui/docs/registry.tsx +4 -6
  87. package/src/ui/index.ts +30 -27
  88. package/src/ui/meta.ts +15 -27
  89. package/src/ui/react.tsx +40 -43
  90. package/src/ui/theme.css +311 -64
  91. package/src/ui/components/patterns/content-header.tsx +0 -528
  92. package/src/ui/components/patterns/surface-media.tsx +0 -69
  93. package/src/ui/components/primitives/card.tsx +0 -191
  94. package/src/ui/docs/content/card.md +0 -292
  95. package/src/ui/docs/content/content.md +0 -275
@@ -1,275 +0,0 @@
1
- ## Região de conteúdo
2
-
3
- Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `CardBody`,
4
- `DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
5
- espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
6
-
7
- O `Content` é uma região nomeada dentro de outra superfície. Sem moldura, ele mantém a anatomia
8
- contínua. Com `framed`, pode permanecer contínuo ou separar header e body com
9
- `layout="sectioned"`. O layout organiza as regiões internas; não transforma `Content` em
10
- [`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou [`Pane`](/ui/pane). Pelo mesmo
11
- motivo o `Content` não tem rodapé: um rodapé pertence à superfície que o hospeda.
12
-
13
- No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
14
-
15
- ```tsx preview col
16
- render(
17
- <Content
18
- title="Dispositivos conectados"
19
- count={3}
20
- description="Sessões com acesso à sua conta."
21
- actions={<Button variant="outline">Encerrar outras sessões</Button>}
22
- >
23
- <div className="rounded-lg border border-border p-4">
24
- MacBook Pro · ativo agora
25
- </div>
26
- </Content>,
27
- );
28
- ```
29
-
30
- ## Região com moldura
31
-
32
- Use `framed` quando título e corpo precisarem formar uma única região delimitada. A moldura pertence
33
- ao `Content`; componentes internos, como `DetailGroup`, continuam responsáveis somente pelo layout
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.
36
-
37
- ```tsx preview col
38
- render(
39
- <Content framed title="Cliente" media={<UserRound aria-hidden="true" />}>
40
- <DetailGroup columns={2}>
41
- <DetailField label="Nome" value="Carla Souza" />
42
- <DetailField label="Telefone" value="+55 11 97333-4455" />
43
- </DetailGroup>
44
- </Content>,
45
- );
46
- ```
47
-
48
- ## Header integrado à moldura
49
-
50
- Quando uma região compacta precisar integrar visualmente o header à borda, use
51
- `frameVariant="legend"`. O acabamento não muda a semântica: uma região de leitura continua sendo
52
- uma `section`, o título continua sendo um heading e descrição, contador e ações permanecem
53
- disponíveis.
54
-
55
- ```tsx preview col
56
- render(
57
- <Content
58
- framed
59
- frameVariant="legend"
60
- title="Veículo"
61
- media={<CarFront aria-hidden="true" />}
62
- >
63
- <DetailGroup columns={2}>
64
- <DetailField label="Placa" value="ABC1D23" />
65
- <DetailField label="Modelo" value="Corolla XEi" />
66
- </DetailGroup>
67
- </Content>,
68
- );
69
- ```
70
-
71
- Para interromper a borda, o header pinta o título e as ações com o fundo da superfície que hospeda o
72
- `Content`, lido da variável CSS `--surface-background`. `Card`, `Dialog` e `Drawer` já a declaram
73
- com o fundo que pintam; fora deles, vale `--background`. Uma superfície com fundo próprio, como um
74
- `Card` com `bg-*`, um `Popover` ou um contêiner da aplicação, precisa declarar a variável com o
75
- mesmo fundo. Sem isso, a faixa atrás do título aparece com a cor da página.
76
-
77
- ```tsx preview col
78
- render(
79
- <Card className="bg-muted [--surface-background:var(--muted)]">
80
- <CardBody>
81
- <Content framed frameVariant="legend" title="Veículo">
82
- <DetailGroup columns={2}>
83
- <DetailField label="Placa" value="ABC1D23" />
84
- <DetailField label="Modelo" value="Corolla XEi" />
85
- </DetailGroup>
86
- </Content>
87
- </CardBody>
88
- </Card>,
89
- );
90
- ```
91
-
92
- ## Header e body seccionados
93
-
94
- Use `layout="sectioned"` quando o filho do body já controlar o recuo de suas células, linhas ou
95
- itens. O header recebe `p-3` e divisor inferior; o body fica sem padding para que a estrutura interna
96
- alcance a moldura. Esse layout exige `framed`.
97
-
98
- ```tsx preview col
99
- render(
100
- <Content
101
- framed
102
- layout="sectioned"
103
- title="Veículo"
104
- media={<CarFront aria-hidden="true" />}
105
- >
106
- <DetailGroup dividers columns={2} orientation="horizontal">
107
- <DetailField label="Marca" value="Toyota" />
108
- <DetailField label="Modelo" value="Yaris XLS" />
109
- <DetailField label="Ano/modelo" value="2021" />
110
- <DetailField label="Placa" value="EVT0012" />
111
- </DetailGroup>
112
- </Content>,
113
- );
114
- ```
115
-
116
- Conteúdo livre que não possui recuo próprio continua no layout `continuous`. Não combine
117
- `layout="sectioned"` com `frameVariant="legend"`: `sectioned` coloca o header dentro da moldura;
118
- `legend` integra o header ao traço externo.
119
-
120
- ## Grupo de campos
121
-
122
- Quando a região nomear um conjunto de campos relacionados, use `as="fieldset"`. O `Content`
123
- renderiza o título como `legend`, preserva as props nativas do `fieldset` e mantém o mesmo contrato
124
- de título e corpo. Essa escolha altera somente a semântica e o comportamento nativo; com `framed`,
125
- a moldura permanece contínua. A mídia usa a mesma apresentação da `section`; contador, descrição e
126
- ações não entram na legenda.
127
-
128
- ```tsx preview col
129
- render(
130
- <Content
131
- as="fieldset"
132
- framed
133
- title="Proprietário"
134
- media={<UserRound aria-hidden="true" />}
135
- >
136
- <FieldGroup>
137
- <Field>
138
- <FieldLabel htmlFor="owner-name">Nome</FieldLabel>
139
- <Input id="owner-name" />
140
- </Field>
141
- <Field>
142
- <FieldLabel htmlFor="owner-document">CPF/CNPJ</FieldLabel>
143
- <Input id="owner-document" />
144
- </Field>
145
- </FieldGroup>
146
- </Content>,
147
- );
148
- ```
149
-
150
- O mesmo acabamento pode integrar a legenda nativa à borda. Declare `frameVariant="legend"` em
151
- agrupamentos compactos, como as seções de filtros avançados; `as="fieldset"` continua sendo a
152
- decisão semântica, e não uma forma de obter esse desenho.
153
-
154
- ```tsx preview col
155
- render(
156
- <Content as="fieldset" framed frameVariant="legend" title="Dados do veículo">
157
- <FieldGroup>
158
- <Field>
159
- <FieldLabel htmlFor="vehicle-plate">Placa</FieldLabel>
160
- <Input id="vehicle-plate" />
161
- </Field>
162
- </FieldGroup>
163
- </Content>,
164
- );
165
- ```
166
-
167
- ## Composição explícita
168
-
169
- Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
170
- `ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
171
- uma mídia (`ContentMedia`), uma descrição, um contador (`ContentMeta`) e uma região de ações. O
172
- contador é próprio de `Content`; `PageHeader` reconhece somente navegação, título e ações.
173
-
174
- ```tsx preview col
175
- render(
176
- <Content level={2}>
177
- <ContentHeader>
178
- <ContentMedia>
179
- <Monitor aria-hidden="true" />
180
- </ContentMedia>
181
- <ContentTitle>Dispositivos conectados</ContentTitle>
182
- <ContentMeta>3</ContentMeta>
183
- <ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
184
- <ContentActions>
185
- <Button variant="outline">Atualizar</Button>
186
- </ContentActions>
187
- </ContentHeader>
188
- <ContentBody>
189
- <div className="rounded-lg border border-border p-4">
190
- MacBook Pro · ativo agora
191
- </div>
192
- </ContentBody>
193
- </Content>,
194
- );
195
- ```
196
-
197
- As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
198
- do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
199
-
200
- ## Coleção nomeada
201
-
202
- Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
203
- [`ActionList`](/ui/action-list) executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
204
- `ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
205
- toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
206
- paralela de componentes `List*`.
207
-
208
- Quando a coleção é o assunto da página, use `level={1}`: o `Content` passa a ser o heading
209
- principal, assume automaticamente a escala do cabeçalho de [`Page`](/ui/page) e mantém o mesmo
210
- ritmo entre introdução e corpo. As ações ficam no extremo oposto ao título. Níveis de 2 a 6
211
- continuam usando a apresentação compacta de uma seção.
212
-
213
- ```tsx preview col
214
- render(
215
- <Content
216
- level={1}
217
- title="Clientes"
218
- actions={<Button>Criar cliente</Button>}
219
- >
220
- <div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
221
- A lista de clientes ocupa o corpo.
222
- </div>
223
- </Content>,
224
- );
225
- ```
226
-
227
- A criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
228
- nomeado `Criar {recurso}` (por exemplo, `Criar cliente`); o diálogo aberto pelo gatilho repete esse
229
- título, e a edição usa `Editar {recurso}`.
230
-
231
- Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção pode ocupar a altura da página e rolar só os
232
- resultados. `Content` e `ContentBody` repassam a altura até a [`ActionList`](/ui/action-list) sem
233
- classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio conteúdo. A seção
234
- “Ocupar a altura disponível” da `ActionList` mostra a composição completa.
235
-
236
- ## Propriedades de Content
237
-
238
- | Propriedade | Tipo | Padrão | Descrição |
239
- | -------------- | ----------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------- |
240
- | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
241
- | `media` | `ReactNode` | | Forma curta: ícone ou imagem que identifica a região antes do título. |
242
- | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
243
- | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
244
- | `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
245
- | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível do heading; `1` identifica o conteúdo principal da página e os demais, uma seção interna. |
246
- | `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
247
- | `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
248
- | `layout` | `'continuous' \| 'sectioned'` | `'continuous'` | Mantém header e body contínuos ou os separa com divisor e body sem recuo. `sectioned` exige `framed`. |
249
- | `frameVariant` | `'default' \| 'legend'` | `'default'` | Mantém a borda contínua ou integra visualmente o header à moldura. `legend` não combina com `layout="sectioned"`. |
250
- | `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
251
-
252
- As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
253
-
254
- Na composição explícita de uma `section`, `ContentHeader` recebe `ContentMedia`, `ContentTitle`,
255
- `ContentMeta`, `ContentDescription` e `ContentActions`. Em `fieldset`, recebe `ContentMedia` e
256
- `ContentTitle`, preservando a apresentação enquanto a legenda continua nomeando somente o grupo de
257
- campos. `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level` próprios — a
258
- hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Content`.
259
-
260
- ## Propriedades de ContentMedia
261
-
262
- | Propriedade | Tipo | Padrão | Descrição |
263
- | ----------- | ------------------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
264
- | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'xs'` | Tamanho da moldura e do glifo na escala compartilhada dos controles. |
265
- | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
266
- | `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
267
- | `className` | `string` | | Ajusta a moldura sem substituir a escala. |
268
- | `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. |
269
-
270
- `ContentMedia` já é a moldura. Passe só o glifo e escolha `size`, `context` e `variant` no slot. Um
271
- `IconTile` passado em `media` ou dentro de `ContentMedia` não vira moldura dentro de moldura: o slot
272
- assume o lugar dele e herda o que ele declara. `media` com `null` ou `false` não desenha nada.
273
-
274
- Em `fieldset`, a mídia é sempre decorativa: o `Content` a esconde da árvore de acessibilidade para
275
- que o grupo seja anunciado apenas pelo título.