@softize/opus 30.0.0 → 31.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -0
- package/bin/lib/check.mjs +109 -101
- package/bin/lib/copy.mjs +2973 -1801
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +52 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +27 -2
- package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +142 -0
- package/docs/adr/0064-action-list-com-itens-e-fontes-externas.md +47 -0
- package/docs/adr/0065-presentation-composes-settings-sections.md +295 -0
- package/docs/adr/0066-civil-date-input-and-calendar-list.md +28 -0
- package/docs/adr/README.md +6 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +21 -19
- package/registry/skills/build-opus-ui/references/evaluations.md +13 -11
- package/registry/skills/build-opus-ui/references/ui-patterns.md +24 -27
- package/src/core/presentation.ts +938 -32
- package/src/core/types.ts +2 -0
- package/src/ui/components/patterns/form.tsx +22 -1
- package/src/ui/components/patterns/grouped-list.ts +2 -2
- package/src/ui/components/patterns/list-calendar.tsx +129 -0
- package/src/ui/components/patterns/list.tsx +612 -382
- package/src/ui/components/patterns/page-heading-context.tsx +12 -5
- package/src/ui/components/patterns/page.tsx +227 -197
- package/src/ui/components/patterns/presentation-fields.tsx +246 -0
- package/src/ui/components/patterns/presentation-sections.tsx +494 -0
- package/src/ui/components/patterns/presentation.tsx +1142 -229
- package/src/ui/components/patterns/structural-header.tsx +23 -32
- package/src/ui/components/patterns/surface-anatomy.ts +27 -217
- package/src/ui/components/patterns/surface.tsx +19 -29
- package/src/ui/components/patterns/trigger.tsx +10 -2
- package/src/ui/components/primitives/calendar.tsx +39 -18
- package/src/ui/components/primitives/close-button.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -8
- package/src/ui/components/primitives/date-input.tsx +276 -0
- package/src/ui/components/primitives/detail.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +19 -81
- package/src/ui/components/primitives/drawer.tsx +17 -79
- package/src/ui/components/primitives/field.tsx +1 -1
- package/src/ui/components/primitives/pagination.tsx +3 -2
- package/src/ui/components/primitives/table.tsx +1 -1
- package/src/ui/components/primitives/tabs.tsx +13 -2
- package/src/ui/docs/content/action-form.md +6 -1
- package/src/ui/docs/content/action-list.md +156 -68
- package/src/ui/docs/content/action-trigger.md +5 -0
- package/src/ui/docs/content/button.md +5 -6
- package/src/ui/docs/content/calendar-month-view.md +50 -0
- package/src/ui/docs/content/calendar.md +6 -1
- package/src/ui/docs/content/command.md +62 -50
- package/src/ui/docs/content/compositions.md +129 -459
- package/src/ui/docs/content/date-input.md +67 -0
- package/src/ui/docs/content/dialog.md +44 -112
- package/src/ui/docs/content/drawer.md +44 -103
- package/src/ui/docs/content/field.md +2 -1
- package/src/ui/docs/content/input.md +3 -0
- package/src/ui/docs/content/page.md +204 -412
- package/src/ui/docs/content/presentation.md +371 -23
- package/src/ui/docs/content/separator.md +1 -1
- package/src/ui/docs/content/surface.md +65 -120
- package/src/ui/docs/content/tokens.md +18 -4
- package/src/ui/docs/registry.tsx +38 -9
- package/src/ui/drivers/context.tsx +11 -4
- package/src/ui/drivers/react.tsx +27 -7
- package/src/ui/meta.ts +17 -6
- package/src/ui/react.tsx +41 -8
- package/src/ui/theme.css +44 -33
|
@@ -89,20 +89,15 @@ fundo sustenta o modal, mas não é uma etapa para onde voltar. Formulários mod
|
|
|
89
89
|
rodapé pelo conteúdo, com `Cancelar` em `ghost` e a ação principal sólida.
|
|
90
90
|
|
|
91
91
|
Dentro de [`PageShell`](/ui/page), a superfície `page` usa a anatomia real da aplicação. O
|
|
92
|
-
`PageHeader` reúne
|
|
93
|
-
recebe ações de rodapé. Comandos textuais nos headers de todas as superfícies usam `
|
|
94
|
-
usam `icon
|
|
92
|
+
`PageHeader` reúne título, retorno e ações gerais, e `PageFooter`
|
|
93
|
+
recebe ações de rodapé. Comandos textuais nos headers de todas as superfícies usam `default`; retorno e assistente
|
|
94
|
+
usam `icon`. O rodapé preserva `default`.
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Presentation é um `Button` sólido, e uma action `simple` vira um
|
|
102
|
-
[`ActionTrigger`](/ui/action-trigger) `ghost`. O rótulo vem do `label` da action, como `Criar workspace`.
|
|
103
|
-
|
|
104
|
-
Não envolva a Presentation em um card para simular a página. Valide proporção, rolagem e ações no
|
|
105
|
-
shell que efetivamente hospeda a rota.
|
|
96
|
+
Em uma Presentation de listagem, comandos da coleção usam `placement: "toolbar"`
|
|
97
|
+
e são enviados a `ActionList.toolbarActions`, depois dos controles da lista. Use `label: "Criar"`
|
|
98
|
+
para abreviar apenas o botão; o contrato e a Presentation do formulário preservam o nome
|
|
99
|
+
específico. Comandos gerais continuam em `header`; ações de conclusão permanecem em `footer`.
|
|
100
|
+
A validação rejeita `toolbar` se o body não declarar uma action list.
|
|
106
101
|
|
|
107
102
|
## Assistência contextual
|
|
108
103
|
|
|
@@ -148,7 +143,8 @@ const workspaceList = definePresentation({
|
|
|
148
143
|
actions: [
|
|
149
144
|
{
|
|
150
145
|
action: "workspace.create",
|
|
151
|
-
placement: "
|
|
146
|
+
placement: "toolbar",
|
|
147
|
+
label: "Criar",
|
|
152
148
|
target: { presentation: "workspace.create", surface: "dialog" },
|
|
153
149
|
},
|
|
154
150
|
],
|
|
@@ -185,15 +181,15 @@ O identificador de `component` vai para o manifest; o JSX, não. Uma Presentatio
|
|
|
185
181
|
componente exige `children`, e uma com body de action os recusa: nos dois casos, a renderização
|
|
186
182
|
lança um erro que nomeia a Presentation.
|
|
187
183
|
|
|
188
|
-
Com `heading: "shell"`, o padrão, a casca emite o título da definição.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
Com `heading: "shell"`, o padrão, a casca emite o título da definição. Com
|
|
185
|
+
`heading: "content"`, o consumidor fornece uma Page completa, com título no PageHeader
|
|
186
|
+
ou pela forma curta `Page title`. A Presentation contribui com ações e rodapé sem criar
|
|
187
|
+
outra Page. O runtime reprova conteúdo sem Page e rodapés concorrentes. Em Dialog e
|
|
188
|
+
Drawer, o título continua no cabeçalho da superfície.
|
|
192
189
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
assume `fill` sozinha.
|
|
190
|
+
Para uma lista ocupar a altura disponível, declare `layout="fill"`. Quando o conteúdo
|
|
191
|
+
fornece a própria Page, declare fill também nela. Em flow, o body da Page rola e o header
|
|
192
|
+
permanece fixo. ActionList recebe fill pela cadeia estrutural sem classes adicionais.
|
|
197
193
|
|
|
198
194
|
## Registrar as declarações
|
|
199
195
|
|
|
@@ -249,7 +245,10 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
|
|
|
249
245
|
| `className` | `string` | | Classes adicionais da superfície. |
|
|
250
246
|
| `bodyClassName` | `string` | | Classes adicionais do body, aplicadas uma única vez. |
|
|
251
247
|
| `layout` | `'flow' \| 'fill'` | `'flow'` | Em `page`, `fill` limita a altura ao shell e entrega a rolagem ao conteúdo do body; a lista de um body `list` ocupa essa altura e rola só os resultados. Dialog e Drawer ignoram. |
|
|
252
|
-
| `children` | `ReactNode` | | Conteúdo React do body declarado com `body.component`. Obrigatório nesse caso e recusado
|
|
248
|
+
| `children` | `ReactNode` | | Conteúdo React do body declarado com `body.component`. Obrigatório nesse caso e recusado nos demais tipos de body. |
|
|
249
|
+
| `compositionRuntime` | `PresentationCompositionRuntime` | | Slots, fontes, células, ícones, Presentations de abas e seleção controlada. |
|
|
250
|
+
| `can` | `(permission: string) => boolean` | | Resolve permissões declaradas em `requires`. Ausência nega permissões explícitas. |
|
|
251
|
+
| `embedded` | `boolean` | `false` | Renderiza conteúdo e ações sem título nem retorno de uma página hospedada em outra composição. |
|
|
253
252
|
|
|
254
253
|
## Propriedades de PresentationInspector
|
|
255
254
|
|
|
@@ -262,3 +261,352 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
|
|
|
262
261
|
| `triggerLabel` | `string` | `Ver JSON da Presentation` | Nome acessível e tooltip do botão. |
|
|
263
262
|
| `title` | `string` | `JSON da Presentation` | Título do dialog de inspeção. |
|
|
264
263
|
| `className` | `string` | | Classes adicionais do gatilho. |
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
A Page usa uma única aparência aberta. `page.width` limita sua coluna, inclusive quando
|
|
267
|
+
o conteúdo declara a Page. Uma Page completa pode substituir o limite com a prop `width`.
|
|
268
|
+
`layout="flow"` rola título, corpo e rodapé juntos; `layout="fill"` entrega a altura restante
|
|
269
|
+
ao conteúdo. Essas opções não alteram Dialog ou Drawer.
|
|
270
|
+
|
|
271
|
+
## Compor uma página por seções
|
|
272
|
+
|
|
273
|
+
Para descrever a estrutura interna de uma página, use `body.sections`. São aceitas
|
|
274
|
+
seções de itens (`kind: "items"`), listas (`kind: "list"`) e controles específicos
|
|
275
|
+
(`kind: "content"`). A ordem do array é a ordem da tela. Seções e itens possuem ids estáveis e únicos
|
|
276
|
+
no seu grupo. Os mesmos Surface, ItemGroup e ActionList do catálogo fazem a renderização.
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
const securityPresentation = definePresentation({
|
|
280
|
+
schemaVersion: 1,
|
|
281
|
+
id: "settings.security",
|
|
282
|
+
title: "Segurança",
|
|
283
|
+
page: { width: "default" },
|
|
284
|
+
body: {
|
|
285
|
+
sections: [
|
|
286
|
+
{
|
|
287
|
+
id: "access",
|
|
288
|
+
kind: "items",
|
|
289
|
+
title: "Acesso",
|
|
290
|
+
actions: "password.edit",
|
|
291
|
+
items: [{ id: "password", title: "Senha", content: "password.summary" }],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "devices",
|
|
295
|
+
kind: "list",
|
|
296
|
+
title: "Dispositivos conectados",
|
|
297
|
+
source: "account.sessions",
|
|
298
|
+
item: { title: "device", description: "since" },
|
|
299
|
+
emptyMessage: "Nenhuma sessão ativa.",
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
overlays: ["password.dialog"],
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
A definição contém somente JSON. `page.width` escolhe `narrow`, `default` ou `wide`.
|
|
308
|
+
A largura da página não altera Dialog ou Drawer.
|
|
309
|
+
|
|
310
|
+
`compositionRuntime` registra as extensões que a definição nomeia:
|
|
311
|
+
|
|
312
|
+
- `slots` fornece ReactNode para ações de seção, conteúdo de item, feedback e overlays.
|
|
313
|
+
`null` representa uma extensão disponível, mas sem conteúdo naquele estado. Uma referência
|
|
314
|
+
sem registro gera erro antes de iniciar as consultas da composição.
|
|
315
|
+
- `lists` associa cada `source` ao resultado de `bindPresentationList(source, actions)`.
|
|
316
|
+
O helper conserva a tipagem dos itens e aceita `toolbarActions` e `rowActions`, com o
|
|
317
|
+
contexto de consulta do ActionList. A definição escolhe título, campos e mensagens.
|
|
318
|
+
|
|
319
|
+
Os adaptadores continuam em código confiável da aplicação. Não persistir callbacks, tokens,
|
|
320
|
+
URLs de execução ou nomes de módulos no artefato. O JSON pode ser inspecionado e transportado,
|
|
321
|
+
mas cada hospedeiro precisa registrar as extensões referenciadas. Formulários, views e listas
|
|
322
|
+
baseados em actions continuam usando `body.action`; componentes específicos podem continuar
|
|
323
|
+
usando `body.component`. Abas têm uma composição própria, descrita a seguir.
|
|
324
|
+
|
|
325
|
+
### Cabeçalho, controles e painel lateral
|
|
326
|
+
|
|
327
|
+
`page.layout` declara `flow` ou `fill`; a prop `layout` pode substituí-lo na invocação.
|
|
328
|
+
`header.title`, `header.meta` e `header.actions` referenciam slots de dados dinâmicos.
|
|
329
|
+
Sem `header.title`, o título continua vindo de `title`.
|
|
330
|
+
|
|
331
|
+
Uma seção `content` usa um slot para um controle específico, como uma matriz de preferências.
|
|
332
|
+
O título, as ações e a posição desse controle pertencem à declaração. Não esconder uma página
|
|
333
|
+
inteira nesse slot: grupos estáticos devem usar `items`, e coleções devem usar `list`.
|
|
334
|
+
|
|
335
|
+
Nos itens, `labelId` permite associar o controle ao título acessível; `placement: "actions"`
|
|
336
|
+
posiciona switches e seletores em ItemActions. O padrão é ItemBody com dois terços da largura;
|
|
337
|
+
`placement: "below"` coloca conteúdo extenso abaixo do título, ocupando a linha. `intro` e `feedback`
|
|
338
|
+
referenciam conteúdos antes e depois do grupo. O título de seção é opcional.
|
|
339
|
+
|
|
340
|
+
`bindPresentationActionList(contract, props)` registra uma lista de action, conservando filtros,
|
|
341
|
+
células, callbacks e toolbar do ActionList. A declaração pode escolher `item` para linhas de
|
|
342
|
+
itens; sem ele, vale a tabela do contrato. `bindPresentationList` continua atendendo fontes locais.
|
|
343
|
+
|
|
344
|
+
`body.aside: { content: "test", label: "Teste do agente", width: "default" }` declara uma
|
|
345
|
+
coluna lateral. As larguras são `narrow` (20rem), `default` (24rem) e `wide` (28rem).
|
|
346
|
+
Em uma página `fill`, o conteúdo principal rola dentro da altura disponível e o painel conserva
|
|
347
|
+
seu lugar. `compositionRuntime.asideOpen` controla sua visibilidade sem perder o estado montado.
|
|
348
|
+
`compositionRuntime.state` substitui temporariamente o conteúdo durante carga, falha ou ausência;
|
|
349
|
+
os slots que dependem dos dados só são exigidos quando esse estado termina.
|
|
350
|
+
|
|
351
|
+
## Declarar um CRUD com ações por linha
|
|
352
|
+
|
|
353
|
+
Uma lista convencional continua usando `body.action`, com rotas para as Presentations de
|
|
354
|
+
criação e edição. A criação usa `placement: "toolbar"`. Para editar ou excluir um item,
|
|
355
|
+
use `placement: "row"` e bindings `source: "item"`:
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
const storesPresentation = definePresentation({
|
|
359
|
+
schemaVersion: 1,
|
|
360
|
+
id: "settings.stores",
|
|
361
|
+
title: "Lojas",
|
|
362
|
+
page: { width: "default" },
|
|
363
|
+
body: { action: "store.list", cells: "stores.cells", emptyMessage: "Nenhuma loja." },
|
|
364
|
+
actions: [{
|
|
365
|
+
action: "store.delete",
|
|
366
|
+
placement: "row",
|
|
367
|
+
label: "Excluir",
|
|
368
|
+
icon: "trash",
|
|
369
|
+
itemLabel: "name",
|
|
370
|
+
input: { id: { source: "item", field: "id" } },
|
|
371
|
+
}],
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Uma action `simple` executa por ActionTrigger, incluindo confirmação, mensagens e
|
|
376
|
+
invalidação de consultas. Actions de formulário ou view declaram `target` para abrir outra
|
|
377
|
+
Presentation. `itemLabel` identifica o campo usado no nome acessível e na confirmação;
|
|
378
|
+
`icon` identifica um ReactNode em `compositionRuntime.icons`. Ambos são próprios do
|
|
379
|
+
placement `row`. O bloqueio de linha é `action` e cada gatilho mantém sua execução isolada.
|
|
380
|
+
|
|
381
|
+
`body.cells` referencia uma entrada de `compositionRuntime.cells`. Registre-a com
|
|
382
|
+
`bindPresentationCells(listContract, cells)`: os callbacks recebem o item tipado, e o
|
|
383
|
+
renderer recusa o adaptador de outra action. Essa extensão atende ícones e controles
|
|
384
|
+
específicos sem mover funções para o JSON. O ActionList mantém consulta, estados, busca,
|
|
385
|
+
ordenação e paginação. `body.emptyMessage` pode particularizar o vazio da coleção.
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
## Abas declaradas
|
|
389
|
+
|
|
390
|
+
Use `body.tabs` quando a estrutura da navegação precisa estar na definição. A página
|
|
391
|
+
mantém título, abas e conteúdo na mesma coluna, com rolagem definida por `layout`.
|
|
392
|
+
O renderer usa os headers e as abas nativas, inclusive em Dialog e Drawer.
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const emails = definePresentation({
|
|
396
|
+
schemaVersion: 1,
|
|
397
|
+
id: 'settings.emails',
|
|
398
|
+
title: 'E-mails',
|
|
399
|
+
page: { width: 'default' },
|
|
400
|
+
body: {
|
|
401
|
+
tabLabel: 'Configurações de e-mail',
|
|
402
|
+
defaultTab: 'deliveries',
|
|
403
|
+
tabs: [
|
|
404
|
+
{ id: 'deliveries', label: 'Envios', icon: 'mail', content: 'emails.deliveries', route: '/settings/emails' },
|
|
405
|
+
{ id: 'senders', label: 'Remetentes', content: 'emails.senders', route: '/settings/emails/senders' },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Cada `content` referencia um slot de `compositionRuntime.slots`; cada `icon`, uma entrada
|
|
412
|
+
em `compositionRuntime.icons`. As referências são conferidas antes da montagem e somente
|
|
413
|
+
o conteúdo da aba ativa monta. Os ids devem ser únicos e `defaultTab` precisa existir.
|
|
414
|
+
Rotas são opcionais, absolutas dentro da aplicação, sem parâmetros e únicas na composição;
|
|
415
|
+
não representam URLs externas. O JSON não executa imports nem callbacks.
|
|
416
|
+
|
|
417
|
+
Sem controle externo, a seleção usa estado local. Para acompanhar a URL, forneça
|
|
418
|
+
`compositionRuntime.tabs: { value, onValueChange }`. O roteador da aplicação resolve a
|
|
419
|
+
aba pelo endereço e usa `tab.route` para navegar; recargas e voltar do navegador atualizam
|
|
420
|
+
`value`. A Presentation não presume como áreas, filtros ou subrotas de formulários funcionam.
|
|
421
|
+
Os conteúdos registrados continuam responsáveis pelas listas, permissões e operações específicas.
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
## Páginas sem componente específico
|
|
425
|
+
|
|
426
|
+
Uma família de Presentations pode descrever a página e seus diálogos sem um arquivo React
|
|
427
|
+
por recurso. `body.tabs[].presentation` referencia uma Presentation de action. O hospedeiro
|
|
428
|
+
resolve essa referência e fornece `compositionRuntime.presentations`; `embedded` permite
|
|
429
|
+
renderizar o conteúdo na aba sem outro cabeçalho de página. Declare apenas `content` ou
|
|
430
|
+
`presentation` em cada aba. A aplicação ainda conecta o roteador, os contratos e os ícones.
|
|
431
|
+
|
|
432
|
+
Listas podem declarar `body.columns`: `text` aceita `prefix`, `empty`, `labels`, `fallback` e
|
|
433
|
+
`icon`; `switch` declara action, bindings, `valueField`, `label` e `itemLabel`. O switch usa a
|
|
434
|
+
invalidação da action e bloqueia submissões concorrentes. `body.opens` oferece destinos em
|
|
435
|
+
ordem de prioridade, com condições `when` sobre campos da linha: `eq`, `neq`, `in`, `notIn`.
|
|
436
|
+
As comparações aceitam valores escalares JSON e todas as condições precisam ser atendidas.
|
|
437
|
+
Campo ausente não atende uma condição. `open` e `opens` são mutuamente exclusivos.
|
|
438
|
+
|
|
439
|
+
`requires`, na Presentation, no comando ou no switch, é resolvido pelo callback `can` do
|
|
440
|
+
hospedeiro. Sem resolvedor, uma permissão explícita é negada. Isso controla a interface;
|
|
441
|
+
a action continua responsável pela autorização do servidor. Comandos de linha podem usar
|
|
442
|
+
`when`; confirmações específicas usam `confirm: { title, body?, actionLabel? }`.
|
|
443
|
+
|
|
444
|
+
Formulários podem declarar `formFields` para selecionar campos, `lookups` com action list e
|
|
445
|
+
`paginate: false`, campos `label`/`value`, e `defaultsFromRecord` para aproveitar o registro obtido pelo
|
|
446
|
+
hospedeiro. Bindings explícitos prevalecem sobre os valores desse registro. Campos, validação,
|
|
447
|
+
submissão e efeitos continuam pertencendo ao ActionForm. Lookups exibem carga e falha com
|
|
448
|
+
retentativa usando os drivers existentes.
|
|
449
|
+
|
|
450
|
+
Views aceitam `fields[].format` como `text`, `pre` ou `html`. O HTML é exibido em iframe
|
|
451
|
+
com sandbox sem permissões, CSP que bloqueia scripts e recursos externos e sem referrer.
|
|
452
|
+
Use para prévia de conteúdo, não para documentos que precisem de navegação ou scripts.
|
|
453
|
+
`width: "wide"` amplia um diálogo para conteúdo extenso; `body.intro` acrescenta orientação
|
|
454
|
+
visível. `route.parent` identifica a página de retorno; parâmetros de rota aceitam camelCase.
|
|
455
|
+
|
|
456
|
+
O protocolo é serializável e validado contra o registro de actions e Presentations. Ele não
|
|
457
|
+
executa expressões de JavaScript recebidas no JSON. A resolução de registros e a sincronização
|
|
458
|
+
de URL continuam no hospedeiro; uma aplicação que consulte só a primeira página de uma lista
|
|
459
|
+
precisa tornar essa limitação visível ou fornecer uma consulta própria por identidade.
|
|
460
|
+
|
|
461
|
+
## Arquivos JSON e permissões
|
|
462
|
+
|
|
463
|
+
Uma página pode ser mantida em `presentations/<recurso>-list.json` e importada pelo registro do
|
|
464
|
+
domínio. Valide o import com `presentationSchema.parse` e o conjunto com `validatePresentations`.
|
|
465
|
+
O JSON contém apenas dados: componentes e handlers continuam em registros confiáveis da aplicação.
|
|
466
|
+
`opus copy` inclui esses arquivos no inventário e aponta textos para a linha do JSON autoral.
|
|
467
|
+
|
|
468
|
+
O callback `can` resolve os requisitos da declaração e das actions. Quando ambos existem, ambos
|
|
469
|
+
precisam ser atendidos; uma lista em `requires` aceita qualquer uma das permissões indicadas.
|
|
470
|
+
Não fornecer `can` nega requisitos explícitos. Um roteador que carrega registros antes da renderização
|
|
471
|
+
deve chamar `canAccessPresentation(definition, actions, can)` antes de iniciar essas consultas.
|
|
472
|
+
Ocultar controles não substitui a autorização executada no servidor; restrições por registro podem
|
|
473
|
+
continuar declaradas em `when`, usando capacidades devolvidas pela própria action.
|
|
474
|
+
|
|
475
|
+
## Detalhe especializado ao selecionar uma linha
|
|
476
|
+
|
|
477
|
+
Use `body.inspect: { component: "audit.detail" }` quando o detalhe é local e não muda a URL.
|
|
478
|
+
Registre `details["audit.detail"] = bindPresentationDetail(auditList, (item, onClose) =>
|
|
479
|
+
<AuditDetailDialog item={item} onClose={onClose} />)` no runtime. O adaptador recebe o item
|
|
480
|
+
tipado pelo contrato. A seleção e o fechamento ficam com a Presentation; o componente só é
|
|
481
|
+
montado após o clique. `requires` e `when` restringem a abertura e a permanência do detalhe.
|
|
482
|
+
|
|
483
|
+
`inspect` só vale para lists e não pode coexistir com `open` ou `opens`. Para destinos navegáveis,
|
|
484
|
+
continue usando esses últimos. Um adaptador ausente ou vinculado a outra action é recusado antes
|
|
485
|
+
de carregar a lista. As actions consultadas dentro do detalhe mantêm sua autorização no servidor.
|
|
486
|
+
|
|
487
|
+
## Opções e filtros de coleção
|
|
488
|
+
|
|
489
|
+
`body.filterDefaults` passa os filtros iniciais ao ActionList; valores explícitos na URL continuam
|
|
490
|
+
prevalecendo. Nos formulários, cada lookup aceita `prepend: [{ value: "*", label: "Todas as unidades" }]`.
|
|
491
|
+
Essas opções aparecem antes das linhas retornadas pela action. Quando o rótulo precisa combinar
|
|
492
|
+
dados, declare `format: "units.options"` e registre o adaptador com
|
|
493
|
+
`bindPresentationOptions(unitList, unit => ({ value: unit.id, label: unit.name }))` em `runtime.options`.
|
|
494
|
+
|
|
495
|
+
O formatador apenas apresenta a opção. A consulta continua sendo a action declarada no lookup,
|
|
496
|
+
e seu `requires` é verificado antes da busca. O inventário de copy lê os rótulos de `prepend` no JSON.
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
## Campos especializados e resumos de coleção
|
|
500
|
+
|
|
501
|
+
Quando um campo exige um editor próprio, declare `body.fieldComponents` com o nome do campo e
|
|
502
|
+
um identificador do registro. Por exemplo: `fieldComponents: { questions: "loss.questions" }`.
|
|
503
|
+
Registre `fields["loss.questions"] = bindPresentationField(questionnaireUpdate, "questions",
|
|
504
|
+
props => <QuestionnaireField {...props} />)` no runtime. O componente recebe `name` e `context`,
|
|
505
|
+
pode ler os dados do registro e usa `useActionFormContext` para atualizar o campo e mostrar seus
|
|
506
|
+
erros. ActionForm continua validando e enviando pelo contrato; o componente não cria outro form
|
|
507
|
+
nem outro botão Salvar. A declaração não pode combinar lookup e componente no mesmo campo.
|
|
508
|
+
|
|
509
|
+
Para cartões ou resumos antes da tabela, declare `body.beforeContent: "usage.summary"` e registre
|
|
510
|
+
`listContent["usage.summary"] = bindPresentationListContent(usageList, input =>
|
|
511
|
+
<UsageSummary input={input} />)`. O input vem do ActionList, com período, busca, filtros e escopo
|
|
512
|
+
materializados, inclusive quando a lista usa estado interno. O resumo pode consultar seus totais
|
|
513
|
+
por uma action própria, sem somar somente as linhas da página atual.
|
|
514
|
+
|
|
515
|
+
As duas extensões são código confiável registrado pelo consumidor, não código transportado no
|
|
516
|
+
JSON. O renderer verifica action e campo do adaptador antes da montagem, e não monta controles
|
|
517
|
+
nem resumos quando a pessoa não pode acessar a Presentation.
|
|
518
|
+
|
|
519
|
+
## Configurações com leitura e edição
|
|
520
|
+
|
|
521
|
+
Use `source` quando seções ou formulários precisarem carregar um registro. Declare a action
|
|
522
|
+
`view` e seus bindings de input. Para uma action `list`, declare também `record.field` e
|
|
523
|
+
`record.param`: o renderer seleciona apenas entre os itens retornados nesse recorte.
|
|
524
|
+
Loading, erro e ausência pertencem ao renderer; requisitos da fonte e da apresentação se somam.
|
|
525
|
+
|
|
526
|
+
Itens de uma seção podem usar `value: { field, empty?, suffix? }`. Mensagens em `notices`
|
|
527
|
+
usam `text` e condições `when`. Comandos `placement: "section"` são referenciados por `id`
|
|
528
|
+
ou pelo nome da action em `section.actions`. `accessibleLabel` distingue botões com o mesmo
|
|
529
|
+
texto visível. Ações de seção usam `ghost`. Declare `icon` para exibir somente o ícone,
|
|
530
|
+
com tooltip e nome acessível; mantenha o texto quando ele for necessário para entender
|
|
531
|
+
o efeito. Ícones também podem acompanhar comandos do cabeçalho.
|
|
532
|
+
|
|
533
|
+
Formulários podem declarar `formGroups: [{ title, fields }]`. `initialValues` fornece o
|
|
534
|
+
rascunho inicial; esse rascunho não precisa passar na validação antes do preenchimento.
|
|
535
|
+
Os nomes dos campos devem existir no contrato, e ActionForm valida o envio. Bindings de
|
|
536
|
+
input explícitos têm precedência sobre o rascunho.
|
|
537
|
+
|
|
538
|
+
Registre controles e conteúdos específicos com `bindPresentationComponent(contract, render)`
|
|
539
|
+
em `runtime.components`. O retorno do render é conteúdo React; hooks pertencem aos componentes
|
|
540
|
+
retornados. O componente recebe contexto, input e controles de fechar a superfície e o aside.
|
|
541
|
+
Use esses vínculos em slots ou em `body: { component, contract, input }`. O vínculo verifica
|
|
542
|
+
acesso, mas não executa a action automaticamente. A estrutura da página permanece no JSON.
|
|
543
|
+
|
|
544
|
+
Rotas com `record` podem declarar `alternatives` com condições e outra apresentação de mesma
|
|
545
|
+
superfície, sem alternativas encadeadas. Isso permite preservar endereços legados que passam
|
|
546
|
+
a abrir uma consulta conforme o registro. O host deve verificar a permissão do destino antes
|
|
547
|
+
de montá-lo. `record.fallbackFields` identifica dados transitórios necessários a fluxos como
|
|
548
|
+
criar e abrir permissões: o host aceita somente o retorno em memória, nunca parâmetros da URL.
|
|
549
|
+
|
|
550
|
+
### Conta externa e navegação com filtros
|
|
551
|
+
|
|
552
|
+
Use `externalSource` para ler dados de uma integração que já possua cliente próprio, como um
|
|
553
|
+
provedor de identidade. Registre seu `load(input, { signal })` em `recordSources`. O renderer
|
|
554
|
+
trata carga, falha e repetição, ignora respostas canceladas e verifica o acesso antes da leitura.
|
|
555
|
+
O JSON contém apenas o identificador do adaptador. Não combine esse modo com `source`.
|
|
556
|
+
|
|
557
|
+
Controles externos usam `bindPresentationContent`. Um formulário específico pode declarar
|
|
558
|
+
`body.footer: true`, renderizar suas ações em `footerTarget` e informar seu envio por
|
|
559
|
+
`onBusyChange`; a moldura continua pertencendo à apresentação. Actions Opus continuam usando
|
|
560
|
+
`ActionForm` e seus contratos. Para navegação sem execução, declare `links` com identificador,
|
|
561
|
+
rótulo, apresentação de destino, posição e bindings de input. Links de seção também usam
|
|
562
|
+
`ghost` e aceitam `icon` do registro runtime; nesse caso, `label` fica no tooltip e no
|
|
563
|
+
nome acessível. Declare `showLabel: true` para combinar ícone e texto visível.
|
|
564
|
+
|
|
565
|
+
Em detalhes abertos na mesma URL da lista, declare, por exemplo,
|
|
566
|
+
`route.query: { "param": "customer", "input": "id" }`. O host passa os parâmetros de busca a
|
|
567
|
+
`matchPresentationRoute` e combina o caminho gerado com os filtros existentes. Ao fechar,
|
|
568
|
+
remove o parâmetro de seleção. `body.rowKey` define a identidade da linha quando a chave pública
|
|
569
|
+
da coleção difere de `id`, como em listas de revisões.
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
### Conteúdo próprio em listas e regiões de trabalho
|
|
573
|
+
|
|
574
|
+
Para apresentar os resultados em cards ou outra composição, declare
|
|
575
|
+
`body.collection: "catalog.cards"` e registre o adaptador em `runtime.collections`
|
|
576
|
+
com `bindPresentationCollection(contract, (items, controls) => ...)`.
|
|
577
|
+
O ActionList conserva busca, estados e paginação; `items` contém a página atual.
|
|
578
|
+
Use `controls.canOpen(item)` e `controls.onOpen(item)` para respeitar os destinos
|
|
579
|
+
autorizados do JSON, e `controls.actions(item)` para manter os comandos declarados.
|
|
580
|
+
`controls.refetch()` atualiza a consulta. O adaptador deve oferecer foco e nome
|
|
581
|
+
acessível aos controles que desenhar.
|
|
582
|
+
|
|
583
|
+
`body.rowContent` referencia `compositionRuntime.rowContent`, registrado com
|
|
584
|
+
`bindPresentationRowContent(listContract, render)`. Use para uma linha específica do domínio;
|
|
585
|
+
comandos declarados por linha continuam disponíveis. `body.toolbarContent` referencia uma
|
|
586
|
+
entrada de `listContent`, registrada com `bindPresentationListContent`. Ambos precisam pertencer
|
|
587
|
+
à mesma list action. Estados, busca e paginação continuam no ActionList.
|
|
588
|
+
|
|
589
|
+
Uma seção `kind: "content"` pode declarar `layout: "fill"` para um canvas ocupar a altura restante.
|
|
590
|
+
Use junto de `page.layout: "fill"`; `page.flush: true` deixa o conteúdo controlar seu recuo.
|
|
591
|
+
`header.description` referencia um slot quando a orientação depende do registro carregado.
|
|
592
|
+
|
|
593
|
+
`body.tabs` aceita `framed` e `distribution` (`content` ou `equal`), encaminhados ao Tabs nativo.
|
|
594
|
+
O modo embedded mantém o contexto das abas mesmo sem renderizar o heading da página.
|
|
595
|
+
`width: "wide"` também se aplica a Drawer; abrir seu assistente pode ampliar a superfície.
|
|
596
|
+
|
|
597
|
+
Comandos podem declarar `successMessage`, apresentada somente após a operação bem-sucedida.
|
|
598
|
+
Ela entra no inventário de copy como mensagem de sucesso; não substitui erro nem confirmação.
|
|
599
|
+
|
|
600
|
+
Em rotas, `params` limita valores de parâmetros do caminho. `query` pode ser um seletor ou uma
|
|
601
|
+
lista de seletores `{ param, input }`; todos precisam estar presentes para selecionar a rota.
|
|
602
|
+
Os nomes de busca e seus destinos de binding devem ser distintos, inclusive dos parâmetros do
|
|
603
|
+
caminho. A geração da URL também valida as restrições declaradas.
|
|
604
|
+
|
|
605
|
+
Ao fornecer uma Page completa por `heading: "content"`, mantenha a Page montada também
|
|
606
|
+
durante carregamento ou Suspense. Use PageState dentro dela para os estados integrais;
|
|
607
|
+
o renderer precisa dessa estrutura para integrar cabeçalho e ações.
|
|
608
|
+
|
|
609
|
+
No modo `embedded`, o formulário conserva seu botão de envio e ações declaradas ficam
|
|
610
|
+
junto ao conteúdo da aba. O título e o retorno pertencem à página que hospeda as abas.
|
|
611
|
+
`successMessage` substitui a notificação de comandos diretos; comandos com `target` usam
|
|
612
|
+
o resultado e a mensagem do formulário de destino.
|
|
@@ -14,7 +14,7 @@ com regiões. A orientação horizontal fica entre blocos empilhados e ocupa tod
|
|
|
14
14
|
|
|
15
15
|
Não use `Separator` entre cabeçalho, corpo e rodapé de [`Surface`](/ui/surface), [`Dialog`](/ui/dialog) ou
|
|
16
16
|
[`Drawer`](/ui/drawer). Nessas superfícies, os divisores são derivados das regiões presentes: use
|
|
17
|
-
|
|
17
|
+
a composição das regiões no componente raiz.
|
|
18
18
|
|
|
19
19
|
## Vertical
|
|
20
20
|
|