@softize/opus 18.2.0 → 18.3.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 +18 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +3 -2
- package/registry/skills/build-opus-ui/references/ui-patterns.md +4 -3
- package/src/core/errors.ts +0 -1
- package/src/core/types.ts +4 -0
- package/src/ui/components/patterns/data-state.tsx +1 -1
- package/src/ui/components/patterns/list.tsx +606 -251
- package/src/ui/components/patterns/page.tsx +16 -5
- package/src/ui/components/patterns/presentation.tsx +32 -6
- package/src/ui/components/primitives/field.tsx +23 -2
- package/src/ui/components/primitives/metric-card.tsx +64 -5
- package/src/ui/components/primitives/select.tsx +27 -23
- package/src/ui/components/primitives/table.tsx +4 -1
- package/src/ui/docs/content/action-list.md +84 -46
- package/src/ui/docs/content/field.md +6 -1
- package/src/ui/docs/content/metric-card.md +43 -0
- package/src/ui/docs/content/select.md +4 -0
- package/src/ui/meta.ts +1 -1
- package/src/ui/react.tsx +1 -0
|
@@ -60,9 +60,24 @@ superfícies que não são listagens, como relatórios. O estado é controlado p
|
|
|
60
60
|
/>
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Seletores inline
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
Seletores inline de valor único abraçam o valor selecionado até `max-w-80`. Valores maiores são
|
|
64
|
+
truncados no controle, enquanto o dropdown pode crescer além dele para preservar a opção.
|
|
65
|
+
Multiseletores mantêm `w-52`; no modal de filtros avançados, o seletor ocupa toda a largura.
|
|
66
|
+
`filterIcons` associa um ícone leading ao nome do filtro quando a superfície precisa reforçar
|
|
67
|
+
dimensões recorrentes, sem substituir o rótulo. Busca, filtros e período aparecem somente quando
|
|
68
|
+
declarados.
|
|
69
|
+
|
|
70
|
+
Quando o valor de repouso depende da superfície, use `filterDefaults`. O valor padrão participa da
|
|
71
|
+
consulta, mas não exibe a ação de limpar. Depois de uma alteração, o `X` aparece e restaura esse
|
|
72
|
+
valor, inclusive quando ele é definido em runtime, como a pessoa da sessão. Sem declaração, filtros
|
|
73
|
+
simples usam o valor vazio como padrão; por isso, opções como “Todos” também não nascem com `X`.
|
|
74
|
+
|
|
75
|
+
Use `periodPosition` para manter o período antes ou depois dos filtros inline. Quando ele precisa
|
|
76
|
+
apenas aparecer depois de um filtro específico, use `{ afterFilter: 'dateBasis' }`. Se tipo de data
|
|
77
|
+
e período formam uma única decisão, declare `period={{ basisFilter: 'dateBasis' }}`: o Opus os reúne
|
|
78
|
+
num só controle, com ícone no início e uma única ação de limpar no fim. Quando a pessoa muda o tipo
|
|
79
|
+
ou o período padrão, essa ação aparece e restaura os dois valores. Se a âncora de `periodPosition`
|
|
80
|
+
não existir, o período permanece visível no fim dos filtros inline.
|
|
66
81
|
|
|
67
82
|
## Onde cada filtro aparece
|
|
68
83
|
|
|
@@ -70,7 +85,10 @@ Cada filtro declara `placement`. `inline`, o padrão, mantém o controle na barr
|
|
|
70
85
|
ao modal “Filtros”; `external` mantém o filtro no estado navegável e no input, mas deixa a
|
|
71
86
|
apresentação para o consumidor, como cards de status acima da lista. No modal, filtros com a mesma
|
|
72
87
|
`section` formam um grupo, e `advancedFilters.columns` distribui os grupos em até três colunas.
|
|
73
|
-
|
|
88
|
+
Use `advancedFilters.sectionIcons` quando os grupos representarem dimensões recorrentes; a chave é
|
|
89
|
+
o mesmo rótulo de `section`, e a legenda mantém o agrupamento leve com ícone e divisor.
|
|
90
|
+
Um filtro `select` pode declarar `searchable: true` quando a lista fechada for longa; a opção geral
|
|
91
|
+
continua disponível e participa da busca. Campos de data avançados usam calendário. A antiga `advanced: true` continua aceita durante a
|
|
74
92
|
migração e equivale a `placement: 'advanced'`.
|
|
75
93
|
|
|
76
94
|
```tsx
|
|
@@ -79,6 +97,7 @@ filters: {
|
|
|
79
97
|
status: {
|
|
80
98
|
label: 'Status',
|
|
81
99
|
type: 'select',
|
|
100
|
+
searchable: true,
|
|
82
101
|
placement: 'inline',
|
|
83
102
|
options: { kind: 'dictionary', ref: 'workspace.status' },
|
|
84
103
|
},
|
|
@@ -99,7 +118,17 @@ filters: {
|
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
// Na tela:
|
|
102
|
-
<ActionList
|
|
121
|
+
<ActionList
|
|
122
|
+
action={workspaceList}
|
|
123
|
+
input={{}}
|
|
124
|
+
advancedFilters={{
|
|
125
|
+
columns: 2,
|
|
126
|
+
sectionIcons: {
|
|
127
|
+
Relacionamento: <UserRound aria-hidden="true" />,
|
|
128
|
+
Datas: <CalendarDays aria-hidden="true" />,
|
|
129
|
+
},
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
103
132
|
```
|
|
104
133
|
|
|
105
134
|
## Colunas de dicionário
|
|
@@ -327,27 +356,31 @@ render(
|
|
|
327
356
|
|
|
328
357
|
## Propriedades de ActionFilterBar
|
|
329
358
|
|
|
330
|
-
| Propriedade
|
|
331
|
-
|
|
|
332
|
-
| `action`
|
|
333
|
-
| `state`
|
|
334
|
-
| `onStateChange`
|
|
335
|
-
| `filterOptions`
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
359
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
360
|
+
| ----------------- | ---------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
|
|
361
|
+
| `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
|
|
362
|
+
| `state` | `ActionFilterState` | | Estado atual da barra. |
|
|
363
|
+
| `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
|
|
364
|
+
| `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
|
|
365
|
+
| `filterDefaults` | `Record<string, string>` | | Define o valor de repouso de filtros simples e o destino da ação de limpar. |
|
|
366
|
+
| `filterIcons` | `Record<string, ReactNode>` | | Associa um ícone leading ao controle de cada filtro. |
|
|
367
|
+
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode> }` | `{ columns: 1 }` | Define as colunas do diálogo e os ícones opcionais das seções; o Opus deriva a largura correspondente. |
|
|
368
|
+
| `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
|
|
369
|
+
| `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
|
|
370
|
+
| `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
|
|
371
|
+
| `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
|
|
372
|
+
| `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
|
|
373
|
+
| `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
|
|
341
374
|
|
|
342
375
|
## Declaração na ListAction
|
|
343
376
|
|
|
344
|
-
| Chave | O que declara
|
|
345
|
-
| --------- |
|
|
346
|
-
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência.
|
|
347
|
-
| `filters` | `{ [nome]: { label, type, options?, multiple?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
348
|
-
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input.
|
|
349
|
-
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`.
|
|
350
|
-
| `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input.
|
|
377
|
+
| Chave | O que declara |
|
|
378
|
+
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
379
|
+
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
|
|
380
|
+
| `filters` | `{ [nome]: { label, type, options?, multiple?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
381
|
+
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
|
|
382
|
+
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
383
|
+
| `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
|
|
351
384
|
|
|
352
385
|
## useListAction
|
|
353
386
|
|
|
@@ -359,26 +392,31 @@ chamar a action.
|
|
|
359
392
|
|
|
360
393
|
## Propriedades de ActionList
|
|
361
394
|
|
|
362
|
-
| Propriedade | Tipo
|
|
363
|
-
| ----------------------- |
|
|
364
|
-
| `action` | `ListAction<TInput, TItem>`
|
|
365
|
-
| `input` | `TInput`
|
|
366
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
372
|
-
| `
|
|
373
|
-
| `
|
|
374
|
-
| `
|
|
375
|
-
| `
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
380
|
-
| `
|
|
381
|
-
| `
|
|
382
|
-
| `
|
|
383
|
-
| `
|
|
384
|
-
| `
|
|
395
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
396
|
+
| ----------------------- | --------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
397
|
+
| `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item). A paginação envia `limit`/`page` e usa o `total` devolvido. |
|
|
398
|
+
| `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
|
|
399
|
+
| `layout` | `'flow' \| 'fill'` | `'flow'` | Em `fill`, ocupa a altura disponível e mantém a rolagem dentro da região de resultados. |
|
|
400
|
+
| `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
|
|
401
|
+
| `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
|
|
402
|
+
| `filterDefaults` | `Record<string, string>` | | Valores de repouso aplicados à consulta; o `X` só aparece quando o filtro diverge deles e retorna a eles. |
|
|
403
|
+
| `filterIcons` | `Record<string, ReactNode>` | | Ícones leading dos filtros, indexados pelo nome declarado no contrato. |
|
|
404
|
+
| `advancedFilters` | `{ columns?: 1 \| 2 \| 3; sectionIcons?: Record<string, ReactNode> }` | `{ columns: 1 }` | Define as colunas e os ícones opcionais dos grupos de filtros avançados; o Opus deriva a largura correspondente do diálogo. |
|
|
405
|
+
| `periodPosition` | `'before' \| 'after' \| { afterFilter: string }` | `'before'` | Posiciona o período antes, depois ou imediatamente após um filtro inline. |
|
|
406
|
+
| `period` | `{ basisFilter: string }` | | Reúne o período e o filtro inline indicado em um único controle. |
|
|
407
|
+
| `views` | `Record<string, ActionListView<TItem>>` | | Visualizações alternativas `{ label, icon?, render }`; o seletor alterna entre a tabela e estas. |
|
|
408
|
+
| `beforeContent` | `ReactNode` | | Conteúdo entre a toolbar e o corpo, como um resumo que reage ao recorte atual. |
|
|
409
|
+
| `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
|
|
410
|
+
| `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
|
|
411
|
+
| `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?: { title, message? }, destructive? }` — liga a multi-seleção. |
|
|
412
|
+
| `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
|
|
413
|
+
| `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
|
|
414
|
+
| `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
|
|
415
|
+
| `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado, por exemplo quando um formulário inline aberto conta como conteúdo. |
|
|
416
|
+
| `loading` | `boolean` | | Carga extra agregada à da consulta, como uma query irmã de que os `children` dependem. |
|
|
417
|
+
| `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
|
|
418
|
+
| `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
|
|
419
|
+
| `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
|
|
420
|
+
| `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
|
|
421
|
+
| `toolbarActions` | `ReactNode` | | Operações ligadas ao recorte atual, no fim da barra e separadas do grupo de controles. |
|
|
422
|
+
| `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita). A partir de duas, entram num grupo com intervalo compacto; uma só fica solta, sem grupo. Cliques ali não disparam o `onRowClick`. |
|
|
@@ -38,6 +38,10 @@ vincula ao controle, ligue-o ao controle por `aria-labelledby` para manter o nom
|
|
|
38
38
|
Declare `data-invalid="true"` no `Field` para que o rótulo acompanhe o contexto de erro;
|
|
39
39
|
`aria-invalid` marca apenas o controle. O `ActionForm` define os dois atributos sozinho.
|
|
40
40
|
|
|
41
|
+
Quando vários conjuntos compartilham a mesma superfície, use `variant="section"` para separar cada
|
|
42
|
+
grupo com uma linha leve. `icon` acrescenta uma referência visual antes do título sem transformar o
|
|
43
|
+
conjunto em outro card; escolha um ícone que represente a dimensão do grupo, não uma ação.
|
|
44
|
+
|
|
41
45
|
```tsx preview col md
|
|
42
46
|
<FieldSet>
|
|
43
47
|
<FieldLegend>Novo agente</FieldLegend>
|
|
@@ -70,7 +74,8 @@ Declare `data-invalid="true"` no `Field` para que o rótulo acompanhe o contexto
|
|
|
70
74
|
|
|
71
75
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
72
76
|
|---|---|---|---|
|
|
73
|
-
| `variant` | `'legend' \| 'label'` | `'legend'` | Define o porte de título de
|
|
77
|
+
| `variant` | `'legend' \| 'label' \| 'section'` | `'legend'` | Define o porte de título, de rótulo compacto ou de seção com divisor. |
|
|
78
|
+
| `icon` | `ReactNode` | | Exibe um ícone decorativo antes da legenda. Na variante `section`, o ícone recebe um suporte tonal. |
|
|
74
79
|
|
|
75
80
|
## Propriedades de FieldError
|
|
76
81
|
|
|
@@ -31,6 +31,40 @@ relacionada à medida. Um controle que explica o rótulo, como um tooltip, perte
|
|
|
31
31
|
/>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Com medida complementar
|
|
35
|
+
|
|
36
|
+
Use `secondaryMetric` quando a mesma dimensão precisar mostrar uma segunda leitura sem competir
|
|
37
|
+
com o valor principal. A medida complementar aparece como uma lâmina posterior, com valor, rótulo
|
|
38
|
+
e `labelAction` próprios. Não use essa composição para duas métricas sem relação direta; nesse caso,
|
|
39
|
+
apresente dois cards independentes.
|
|
40
|
+
|
|
41
|
+
```tsx preview col
|
|
42
|
+
<MetricCard
|
|
43
|
+
label="Agendados"
|
|
44
|
+
value="18"
|
|
45
|
+
labelAction={
|
|
46
|
+
<Tooltip>
|
|
47
|
+
<TooltipTrigger asChild>
|
|
48
|
+
<Button size="icon-xs" variant="ghost" aria-label="Sobre Agendados"><Info /></Button>
|
|
49
|
+
</TooltipTrigger>
|
|
50
|
+
<TooltipContent>Eventos atualmente agendados.</TooltipContent>
|
|
51
|
+
</Tooltip>
|
|
52
|
+
}
|
|
53
|
+
secondaryMetric={{
|
|
54
|
+
label: 'agendadas no período',
|
|
55
|
+
value: '7',
|
|
56
|
+
labelAction: (
|
|
57
|
+
<Tooltip>
|
|
58
|
+
<TooltipTrigger asChild>
|
|
59
|
+
<Button size="icon-xs" variant="ghost" aria-label="Sobre o período"><Info /></Button>
|
|
60
|
+
</TooltipTrigger>
|
|
61
|
+
<TooltipContent>Agendamentos registrados dentro do período.</TooltipContent>
|
|
62
|
+
</Tooltip>
|
|
63
|
+
),
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
```
|
|
67
|
+
|
|
34
68
|
## Carregamento
|
|
35
69
|
|
|
36
70
|
`loading` preserva a geometria do card com placeholders e marca a superfície como ocupada para
|
|
@@ -49,6 +83,15 @@ tecnologias assistivas. O contêiner da coleção continua responsável pela men
|
|
|
49
83
|
| `loading` | `boolean` | `false` | Substitui rótulo e valor por skeletons e marca `aria-busy`. |
|
|
50
84
|
| `labelAction` | `ReactNode` | | Controle ao lado do rótulo, como uma explicação em tooltip. |
|
|
51
85
|
| `description` | `ReactNode` | | Contexto que explica recorte, proporção ou significado do valor. |
|
|
86
|
+
| `secondaryMetric` | `MetricCardSecondaryMetric` | | Medida complementar exibida como uma lâmina posterior sob o card principal. |
|
|
52
87
|
| `icon` | `ReactNode` | | Ícone decorativo que identifica a natureza da medida. |
|
|
53
88
|
| `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Contexto semântico do ícone; não altera a superfície do card. |
|
|
54
89
|
| `action` | `ReactNode` | | Ação relacionada diretamente à medida. |
|
|
90
|
+
|
|
91
|
+
## Propriedades de MetricCardSecondaryMetric
|
|
92
|
+
|
|
93
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `label` | `ReactNode` | | O nome da medida complementar. |
|
|
96
|
+
| `value` | `ReactNode` | | O valor complementar já formatado pelo consumidor. |
|
|
97
|
+
| `labelAction` | `ReactNode` | | Controle próprio ao lado do rótulo, como uma explicação em tooltip. |
|
|
@@ -24,6 +24,10 @@ render(
|
|
|
24
24
|
)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
O gatilho respeita a largura definida pelo contexto. Ao abrir, a lista usa essa largura como mínimo
|
|
28
|
+
e pode crescer até `32rem` para preservar rótulos maiores sem alterar o layout do formulário ou da
|
|
29
|
+
toolbar. Rótulos que ultrapassarem esse limite continuam truncados como proteção de layout.
|
|
30
|
+
|
|
27
31
|
## Lista pesquisável
|
|
28
32
|
|
|
29
33
|
Use `searchable` quando a quantidade ou os rótulos dificultarem encontrar uma opção. O gatilho
|
package/src/ui/meta.ts
CHANGED
|
@@ -81,7 +81,7 @@ export const componentMeta = {
|
|
|
81
81
|
name: "metric-card",
|
|
82
82
|
ancestry: "opus",
|
|
83
83
|
whenToUse:
|
|
84
|
-
"Medida resumida com rótulo, valor em destaque e descrição opcional. `context` realça semanticamente somente o ícone; aceita ação relacionada e estado de carregamento. O consumidor calcula e formata
|
|
84
|
+
"Medida resumida com rótulo, valor em destaque e descrição opcional. Use `secondaryMetric` para uma segunda leitura diretamente relacionada, exibida como lâmina posterior com explicação própria. `context` realça semanticamente somente o ícone; aceita ação relacionada e estado de carregamento. O consumidor calcula e formata os valores; para conteúdo geral ou estrutura livre, use Card.",
|
|
85
85
|
},
|
|
86
86
|
chat: {
|
|
87
87
|
name: "chat",
|
package/src/ui/react.tsx
CHANGED