@softize/opus 12.7.0 → 12.8.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 +72 -0
- package/bin/lib/gen-dicts.mjs +11 -1
- package/bin/lib/gen-runner.mjs +8 -2
- package/bin/lib/materialize.mjs +5 -2
- package/docs/adr/0003-dictionary-presentation-is-declared.md +160 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +31 -10
- package/registry/skills/build-opus-ui/references/evaluations.md +25 -0
- package/registry/skills/build-opus-ui/references/ui-patterns.md +85 -0
- package/registry/skills/implement-opus-change/SKILL.md +4 -3
- package/registry/skills/model-opus-dictionary/SKILL.md +76 -0
- package/registry/skills/model-opus-dictionary/agents/openai.yaml +4 -0
- package/registry/skills/model-opus-dictionary/references/evaluations.md +18 -0
- package/src/core/dictionary.ts +152 -0
- package/src/core/index.ts +18 -0
- package/src/core/types.ts +9 -1
- package/src/schema/drivers/zod.ts +46 -13
- package/src/ui/components/patterns/list.tsx +136 -51
- package/src/ui/components/primitives/badge.tsx +3 -0
- package/src/ui/components/primitives/detail.tsx +12 -1
- package/src/ui/components/primitives/dictionary-value.tsx +141 -0
- package/src/ui/components/primitives/empty-value.tsx +50 -0
- package/src/ui/components/primitives/pagination.tsx +86 -56
- package/src/ui/components/primitives/tabs.tsx +1 -1
- package/src/ui/docs/content/action-list-dialog.md +1 -1
- package/src/ui/docs/content/action-list.md +34 -9
- package/src/ui/docs/content/badge.md +6 -3
- package/src/ui/docs/content/detail.md +19 -2
- package/src/ui/docs/content/dictionary-value.md +120 -0
- package/src/ui/docs/content/empty-value.md +48 -0
- package/src/ui/docs/content/pagination.md +54 -17
- package/src/ui/docs/doc-client.tsx +21 -4
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +14 -2
- package/src/ui/react.tsx +6 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Um valor de dicionário (`t.dict`) apresentado do jeito que o **dicionário** declarou. A tela não
|
|
2
|
+
decide se o valor vira badge, cor ou ícone: ela passa o dicionário e o código, e o renderer
|
|
3
|
+
aplica os defaults da apresentação declarada. O rótulo está sempre presente como texto — cor e
|
|
4
|
+
ícone nunca comunicam sozinhos.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<DictionaryValue
|
|
8
|
+
dict={{ keys: ['pf', 'pj'], entries: { pf: { label: 'Pessoa física', icon: 'user' }, pj: { label: 'Empresa', icon: 'building' } }, presentation: 'classification' }}
|
|
9
|
+
value="pj"
|
|
10
|
+
/>
|
|
11
|
+
<DictionaryValue
|
|
12
|
+
dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', tone: 'success' } }, presentation: 'stage' }}
|
|
13
|
+
value="customer"
|
|
14
|
+
/>
|
|
15
|
+
<DictionaryValue
|
|
16
|
+
dict={{ keys: ['open', 'blocked'], entries: { open: { label: 'Aberto', tone: 'warning', icon: 'clock', description: 'Aguarda uma decisão da Fonte.' }, blocked: { label: 'Bloqueado', tone: 'danger' } }, presentation: 'status' }}
|
|
17
|
+
value="open"
|
|
18
|
+
/>
|
|
19
|
+
<DictionaryValue
|
|
20
|
+
dict={{ keys: ['nbs', 'followize'], entries: { nbs: { label: 'NBS' }, followize: { label: 'Followize' } } }}
|
|
21
|
+
value="nbs"
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## O dicionário declara o papel
|
|
26
|
+
|
|
27
|
+
`presentation` fica no `t.dict`, ao lado de `doc`. Sem ele, o valor é texto: a apresentação é
|
|
28
|
+
declarada, nunca inferida pelo nome do dicionário.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export const customerKindDict = t.dict(
|
|
32
|
+
{
|
|
33
|
+
pf: { label: 'Pessoa física', icon: 'user' },
|
|
34
|
+
pj: { label: 'Empresa', icon: 'building' },
|
|
35
|
+
},
|
|
36
|
+
{ doc: 'Natureza da parte no cadastro global.', presentation: 'classification' },
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export const customerStageDict = t.dict(
|
|
40
|
+
{
|
|
41
|
+
prospect: { label: 'Prospect', description: 'Relacionamento ainda em prospecção.' },
|
|
42
|
+
customer: { label: 'Cliente', tone: 'success' },
|
|
43
|
+
},
|
|
44
|
+
{ doc: 'Estágio comercial atual da parte.', presentation: 'stage' },
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Papel | Quando | Forma | Variante |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `classification` | Tipo, categoria, natureza — uma dimensão estável de comparação. | Badge | `outline`; ignora `tone`. |
|
|
51
|
+
| `status` | Situação operacional que muda com o tempo. | Badge | Tonal pelo `tone` da entrada; `neutral` sem tom. Nunca `outline`. |
|
|
52
|
+
| `stage` | Etapa de um ciclo ou funil. | Badge | Igual a `status`. |
|
|
53
|
+
| `plain` ou ausente | Valor que só precisa ser legível. | Texto | — |
|
|
54
|
+
|
|
55
|
+
## Por entrada: tom, ícone e descrição
|
|
56
|
+
|
|
57
|
+
- `tone` (`neutral` · `info` · `success` · `warning` · `danger`) só existe em status e estágio;
|
|
58
|
+
`t.dict` rejeita o tom em classificação e em `plain`.
|
|
59
|
+
- `icon` é um nome do catálogo do Opus (`iconPickerIcons`). Nome fora do catálogo não renderiza
|
|
60
|
+
ícone; o renderer não inventa. Vocabulário próprio entra por `icons`.
|
|
61
|
+
- `description` é o texto curto para a pessoa e aparece em tooltip focável. Descrição igual ao
|
|
62
|
+
rótulo não vira tooltip. `doc` é o entendimento de negócio para manifest e Lens; não aparece.
|
|
63
|
+
|
|
64
|
+
```tsx preview
|
|
65
|
+
<DictionaryValue
|
|
66
|
+
dict={{ keys: ['closed'], entries: { closed: { label: 'Fechado', icon: 'nao-existe', description: 'Fechado' } }, presentation: 'status' }}
|
|
67
|
+
value="closed"
|
|
68
|
+
/>
|
|
69
|
+
<DictionaryValue
|
|
70
|
+
dict={{ keys: ['closed'], entries: { closed: { label: 'Fechado', icon: 'archive' } }, presentation: 'status' }}
|
|
71
|
+
value="closed"
|
|
72
|
+
icons={{ archive: Archive }}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Override explícito
|
|
77
|
+
|
|
78
|
+
Os defaults vêm do dicionário; a tela sobrepõe só quando tem um motivo, e sobrepõe às claras.
|
|
79
|
+
|
|
80
|
+
```tsx preview
|
|
81
|
+
<DictionaryValue
|
|
82
|
+
dict={{ keys: ['customer'], entries: { customer: { label: 'Cliente', tone: 'success' } }, presentation: 'stage' }}
|
|
83
|
+
value="customer"
|
|
84
|
+
presentation="plain"
|
|
85
|
+
/>
|
|
86
|
+
<DictionaryValue
|
|
87
|
+
dict={{ keys: ['pf'], entries: { pf: { label: 'Pessoa física', icon: 'user' } }, presentation: 'classification' }}
|
|
88
|
+
value="pf"
|
|
89
|
+
icon={false}
|
|
90
|
+
/>
|
|
91
|
+
<DictionaryValue
|
|
92
|
+
dict={{ keys: ['pf'], entries: { pf: { label: 'Pessoa física' } }, presentation: 'classification' }}
|
|
93
|
+
value={null}
|
|
94
|
+
fallback={<span className="text-muted-foreground">—</span>}
|
|
95
|
+
/>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Valor fora do dicionário aparece como texto com o próprio código, marcado com
|
|
99
|
+
`data-dictionary-unknown`, sem badge, ícone ou tooltip, mesmo com `variant` sobreposto.
|
|
100
|
+
|
|
101
|
+
## Em ActionList
|
|
102
|
+
|
|
103
|
+
Coluna cujo campo do schema de saída é um `t.dict().zod()` já renderiza `DictionaryValue` com o
|
|
104
|
+
papel declarado, sem `cells`. Quando o campo de saída é uma string comum, a coluna nomeia o
|
|
105
|
+
dicionário registrado no provider: `{ key: 'source', label: 'Fonte', dictionary: 'customerSource' }`.
|
|
106
|
+
Dimensões independentes (tipo e estágio, por exemplo) ficam em colunas distintas; não empilhar
|
|
107
|
+
uma sob a outra como texto secundário.
|
|
108
|
+
|
|
109
|
+
## Props
|
|
110
|
+
|
|
111
|
+
| Prop | Tipo | Default | Descrição |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `dict` | `DictType \| LogicalTypeMeta \| DictionaryDescriptor` | — | O dicionário (`t.dict`), a meta lida do schema ou um descritor normalizado. |
|
|
114
|
+
| `value` | `string \| null \| undefined` | — | O código. Vazio renderiza `fallback`. |
|
|
115
|
+
| `presentation` | `'classification' \| 'status' \| 'stage' \| 'plain'` | do dicionário | Sobrepõe o papel só nesta ocorrência. |
|
|
116
|
+
| `variant` | variante de `Badge` | pelo papel | Sobrepõe a variante e promove texto a badge. |
|
|
117
|
+
| `icon` | `boolean \| ReactNode` | `true` | `false` esconde o ícone declarado; um nó substitui. |
|
|
118
|
+
| `icons` | `Record<string, LucideIcon>` | `iconPickerIcons` | Catálogo nome → componente. |
|
|
119
|
+
| `tooltip` | `boolean` | `true` | `false` suprime a descrição. |
|
|
120
|
+
| `fallback` | `ReactNode` | `null` | Conteúdo quando `value` é vazio. |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Valor ausente tem uma representação padrão. Em célula compacta de tabela aparece o travessão,
|
|
2
|
+
e a leitura assistiva recebe “Não informado”; em texto corrido, como no `DetailField`, aparece o
|
|
3
|
+
próprio rótulo. As colunas de `ActionList` e o `DetailField` já fazem isso sozinhos; um renderer
|
|
4
|
+
customizado reutiliza a primitiva em vez de repetir a condicional.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<div className="flex items-center gap-6 text-sm">
|
|
8
|
+
<EmptyValue />
|
|
9
|
+
<EmptyValue compact />
|
|
10
|
+
<EmptyValue label="Nunca enviado" />
|
|
11
|
+
<EmptyValue compact label="Sem vencimento" />
|
|
12
|
+
</div>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## O que conta como ausência
|
|
16
|
+
|
|
17
|
+
`null`, `undefined`, string vazia e string só com espaços. `0`, `false` e coleções vazias são
|
|
18
|
+
valores legítimos: quem apresenta booleanos e listas decide a própria forma. `isAbsentValue`
|
|
19
|
+
exporta a mesma regra para renderers customizados.
|
|
20
|
+
|
|
21
|
+
## O domínio dá o significado
|
|
22
|
+
|
|
23
|
+
A ausência raramente é neutra: um envio que nunca aconteceu, um prazo que não existe, um campo
|
|
24
|
+
que não se aplica. `label` troca o texto, e `ListColumnSpec.empty` faz o mesmo na coluna do
|
|
25
|
+
contrato, sem condicional na tela.
|
|
26
|
+
|
|
27
|
+
```tsx preview col
|
|
28
|
+
<DetailGroup columns={2}>
|
|
29
|
+
<DetailField label="E-mail" value="" />
|
|
30
|
+
<DetailField label="Último envio" value={null} empty="Nunca enviado" />
|
|
31
|
+
<DetailField label="Saldo" value={0} />
|
|
32
|
+
<DetailField label="Vencimento" value={undefined} empty="Sem vencimento" />
|
|
33
|
+
</DetailGroup>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
columns: [
|
|
38
|
+
{ key: 'email', label: 'E-mail' }, // — · “Não informado”
|
|
39
|
+
{ key: 'sentAt', label: 'Último envio', empty: 'Nunca enviado' }, // — · “Nunca enviado”
|
|
40
|
+
]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Prop | Tipo | Default | Descrição |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `label` | `string` | `'Não informado'` | O que a ausência significa neste domínio. |
|
|
48
|
+
| `compact` | `boolean` | `false` | Célula compacta: travessão visível, rótulo só para leitura assistiva. |
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
## Básico
|
|
2
2
|
|
|
3
|
-
É composição: Pagination embrulha PaginationContent, e cada PaginationItem segura um link.
|
|
3
|
+
É composição: Pagination embrulha PaginationContent, e cada PaginationItem segura um link. `page`
|
|
4
|
+
dá o número e o nome acessível (“Página N”); `isActive` marca a página atual (vira outline). Cada
|
|
5
|
+
número tem altura fixa e largura mínima quadrada que cresce com os dígitos — 5726 e 5727 nunca se
|
|
6
|
+
colam. As setas seguem quadradas e já falam pt-BR; `label` localiza, `iconOnly` deixa só a seta.
|
|
7
|
+
É o único paginador da casa: `ActionList` compõe esta primitiva no rodapé, na escala densa.
|
|
4
8
|
|
|
5
9
|
```tsx preview
|
|
6
10
|
<Pagination>
|
|
@@ -9,15 +13,13 @@
|
|
|
9
13
|
<PaginationPrevious href="#" />
|
|
10
14
|
</PaginationItem>
|
|
11
15
|
<PaginationItem>
|
|
12
|
-
<PaginationLink href="#"
|
|
16
|
+
<PaginationLink href="#" page={1} />
|
|
13
17
|
</PaginationItem>
|
|
14
18
|
<PaginationItem>
|
|
15
|
-
<PaginationLink href="#" isActive
|
|
16
|
-
2
|
|
17
|
-
</PaginationLink>
|
|
19
|
+
<PaginationLink href="#" page={2} isActive />
|
|
18
20
|
</PaginationItem>
|
|
19
21
|
<PaginationItem>
|
|
20
|
-
<PaginationLink href="#"
|
|
22
|
+
<PaginationLink href="#" page={3} />
|
|
21
23
|
</PaginationItem>
|
|
22
24
|
<PaginationItem>
|
|
23
25
|
<PaginationNext href="#" />
|
|
@@ -26,6 +28,37 @@
|
|
|
26
28
|
</Pagination>
|
|
27
29
|
```
|
|
28
30
|
|
|
31
|
+
## Números longos
|
|
32
|
+
|
|
33
|
+
A largura mínima é quadrada; o número manda no resto. Na escala densa de um rodapé, ajuste por
|
|
34
|
+
`className` (`h-7 min-w-7 text-xs` nos números; `size-7` mais `iconClassName="size-3.5"` nas
|
|
35
|
+
setas com `iconOnly`).
|
|
36
|
+
|
|
37
|
+
```tsx preview
|
|
38
|
+
<Pagination>
|
|
39
|
+
<PaginationContent className="gap-0.5">
|
|
40
|
+
<PaginationItem>
|
|
41
|
+
<PaginationPrevious href="#" iconOnly className="size-7" iconClassName="size-3.5" />
|
|
42
|
+
</PaginationItem>
|
|
43
|
+
<PaginationItem>
|
|
44
|
+
<PaginationLink href="#" page={1} className="h-7 min-w-7 text-xs" />
|
|
45
|
+
</PaginationItem>
|
|
46
|
+
<PaginationItem>
|
|
47
|
+
<PaginationEllipsis className="size-7" />
|
|
48
|
+
</PaginationItem>
|
|
49
|
+
<PaginationItem>
|
|
50
|
+
<PaginationLink href="#" page={5726} className="h-7 min-w-7 text-xs" />
|
|
51
|
+
</PaginationItem>
|
|
52
|
+
<PaginationItem>
|
|
53
|
+
<PaginationLink href="#" page={5727} isActive className="h-7 min-w-7 text-xs" />
|
|
54
|
+
</PaginationItem>
|
|
55
|
+
<PaginationItem>
|
|
56
|
+
<PaginationNext href="#" iconOnly className="size-7" iconClassName="size-3.5" />
|
|
57
|
+
</PaginationItem>
|
|
58
|
+
</PaginationContent>
|
|
59
|
+
</Pagination>
|
|
60
|
+
```
|
|
61
|
+
|
|
29
62
|
## Com elipse
|
|
30
63
|
|
|
31
64
|
PaginationEllipsis é o atalho decorativo (aria-hidden) entre blocos de páginas distantes — útil quando a lista de sessões do workspace tem páginas demais pra caber na barra.
|
|
@@ -37,18 +70,16 @@ PaginationEllipsis é o atalho decorativo (aria-hidden) entre blocos de páginas
|
|
|
37
70
|
<PaginationPrevious href="#" />
|
|
38
71
|
</PaginationItem>
|
|
39
72
|
<PaginationItem>
|
|
40
|
-
<PaginationLink href="#"
|
|
73
|
+
<PaginationLink href="#" page={1} />
|
|
41
74
|
</PaginationItem>
|
|
42
75
|
<PaginationItem>
|
|
43
76
|
<PaginationEllipsis />
|
|
44
77
|
</PaginationItem>
|
|
45
78
|
<PaginationItem>
|
|
46
|
-
<PaginationLink href="#" isActive
|
|
47
|
-
8
|
|
48
|
-
</PaginationLink>
|
|
79
|
+
<PaginationLink href="#" page={8} isActive />
|
|
49
80
|
</PaginationItem>
|
|
50
81
|
<PaginationItem>
|
|
51
|
-
<PaginationLink href="#"
|
|
82
|
+
<PaginationLink href="#" page={9} />
|
|
52
83
|
</PaginationItem>
|
|
53
84
|
<PaginationItem>
|
|
54
85
|
<PaginationNext href="#" />
|
|
@@ -59,7 +90,9 @@ PaginationEllipsis é o atalho decorativo (aria-hidden) entre blocos de páginas
|
|
|
59
90
|
|
|
60
91
|
## Controlado
|
|
61
92
|
|
|
62
|
-
|
|
93
|
+
Sem `href`, cada link vira `<button>`: foco, teclado e `disabled` funcionam como em qualquer
|
|
94
|
+
botão. Guarde a página no estado e responda ao `onClick`. Aqui a barra pagina os repositórios do
|
|
95
|
+
workspace Empresa X.
|
|
63
96
|
|
|
64
97
|
```tsx preview col
|
|
65
98
|
const [page, setPage] = useState(2)
|
|
@@ -70,18 +103,18 @@ render(
|
|
|
70
103
|
<PaginationContent>
|
|
71
104
|
<PaginationItem>
|
|
72
105
|
<PaginationPrevious
|
|
106
|
+
disabled={page <= 1}
|
|
73
107
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
74
108
|
/>
|
|
75
109
|
</PaginationItem>
|
|
76
110
|
{pages.map((n) => (
|
|
77
111
|
<PaginationItem key={n}>
|
|
78
|
-
<PaginationLink isActive={n === page} onClick={() => setPage(n)}
|
|
79
|
-
{n}
|
|
80
|
-
</PaginationLink>
|
|
112
|
+
<PaginationLink page={n} isActive={n === page} onClick={() => setPage(n)} />
|
|
81
113
|
</PaginationItem>
|
|
82
114
|
))}
|
|
83
115
|
<PaginationItem>
|
|
84
116
|
<PaginationNext
|
|
117
|
+
disabled={page >= pages.length}
|
|
85
118
|
onClick={() => setPage((p) => Math.min(pages.length, p + 1))}
|
|
86
119
|
/>
|
|
87
120
|
</PaginationItem>
|
|
@@ -94,6 +127,10 @@ render(
|
|
|
94
127
|
|
|
95
128
|
| Prop | Tipo | Default | Descrição |
|
|
96
129
|
|---|---|---|---|
|
|
130
|
+
| `page (PaginationLink)` | `number` | | Número da página: vira o conteúdo (quando não há `children`) e o nome acessível “Página N”. |
|
|
97
131
|
| `isActive (PaginationLink)` | `boolean` | `false` | Marca a página atual: vira outline e ganha aria-current="page". Os demais ficam ghost. |
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
132
|
+
| `href (PaginationLink)` | `string` | | Com `href` o link é um `<a>`; sem `href` é um `<button>` controlado por `onClick`, com `disabled`. |
|
|
133
|
+
| `size (PaginationLink)` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | Escala do Button. Números usam `default` (largura mínima quadrada que cresce); as setas com `iconOnly` usam `icon`. |
|
|
134
|
+
| `label (Previous/Next)` | `string` | `'Página anterior'` / `'Próxima página'` | Nome acessível e texto visível das setas. |
|
|
135
|
+
| `iconOnly (Previous/Next)` | `boolean` | `false` | Só a seta, quadrada (`size="icon"`); o `label` continua como nome acessível. |
|
|
136
|
+
| `iconClassName (Previous/Next)` | `string` | | Classe do svg da seta, para a escala densa (`size-3.5`). |
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
8
8
|
import { z } from 'zod'
|
|
9
|
-
import { defineContract, error } from '../../core/index.ts'
|
|
10
|
-
import type { ActionDef, ActionResult, ClientAdapter, Paginated } from '../../core/index.ts'
|
|
9
|
+
import { attachLogicalType, defineContract, error } from '../../core/index.ts'
|
|
10
|
+
import type { ActionDef, ActionResult, ClientAdapter, LogicalTypeMeta, Paginated } from '../../core/index.ts'
|
|
11
11
|
import { TbdlibProvider } from '../react.tsx'
|
|
12
12
|
|
|
13
13
|
// — Dados enlatados (domínio da casa) —
|
|
@@ -28,6 +28,23 @@ const WORKSPACES: DocBrowserWorkspace[] = [
|
|
|
28
28
|
|
|
29
29
|
const STATUS = ['active', 'onboarding', 'paused'] as const
|
|
30
30
|
|
|
31
|
+
/** O vocabulário de status do workspace com a apresentação declarada (ADR 0003). O palco mora
|
|
32
|
+
* na UI, e a fronteira ui→core não deixa importar o driver de schema; isto é exatamente o que
|
|
33
|
+
* `t.dict(entries, { presentation: 'status' })` produz — o enum com a meta lógica anexada. */
|
|
34
|
+
export const docWorkspaceStatus: LogicalTypeMeta = {
|
|
35
|
+
logicalType: 'dict',
|
|
36
|
+
params: {
|
|
37
|
+
keys: [...STATUS],
|
|
38
|
+
entries: {
|
|
39
|
+
active: { label: 'Ativo', tone: 'success', description: 'Agentes em operação para o cliente.' },
|
|
40
|
+
onboarding: { label: 'Onboarding', tone: 'warning' },
|
|
41
|
+
paused: { label: 'Pausado' },
|
|
42
|
+
},
|
|
43
|
+
presentation: 'status',
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
const workspaceStatus = attachLogicalType(z.enum(STATUS), docWorkspaceStatus)
|
|
47
|
+
|
|
31
48
|
// — Contratos fake (mesmo shape dos reais; ver apps/admin/shared/src/workspaces.ts) —
|
|
32
49
|
export const docWorkspaceCreate = defineContract({
|
|
33
50
|
name: 'workspace.create',
|
|
@@ -65,7 +82,7 @@ export const docWorkspaceList = defineContract({
|
|
|
65
82
|
id: z.string(),
|
|
66
83
|
name: z.string(),
|
|
67
84
|
client: z.string(),
|
|
68
|
-
status:
|
|
85
|
+
status: workspaceStatus,
|
|
69
86
|
agents: z.number(),
|
|
70
87
|
}),
|
|
71
88
|
// O modo DECLARATIVO do ActionList: a tabela e a toolbar derivam daqui.
|
|
@@ -104,7 +121,7 @@ export const docWorkspaceView = defineContract({
|
|
|
104
121
|
id: z.string(),
|
|
105
122
|
name: z.string(),
|
|
106
123
|
client: z.string(),
|
|
107
|
-
status:
|
|
124
|
+
status: workspaceStatus,
|
|
108
125
|
agents: z.number(),
|
|
109
126
|
}),
|
|
110
127
|
projection: ['id', 'name', 'client', 'status', 'agents'],
|
package/src/ui/docs/registry.tsx
CHANGED
|
@@ -68,6 +68,8 @@ import composerMd from './content/composer.md?raw'
|
|
|
68
68
|
import copyableMd from './content/copyable.md?raw'
|
|
69
69
|
import dialogMd from './content/dialog.md?raw'
|
|
70
70
|
import detailMd from './content/detail.md?raw'
|
|
71
|
+
import dictionaryValueMd from './content/dictionary-value.md?raw'
|
|
72
|
+
import emptyValueMd from './content/empty-value.md?raw'
|
|
71
73
|
import drawerMd from './content/drawer.md?raw'
|
|
72
74
|
import menuMd from './content/menu.md?raw'
|
|
73
75
|
import emptyMd from './content/empty.md?raw'
|
|
@@ -106,6 +108,7 @@ import {
|
|
|
106
108
|
docSessionDelete,
|
|
107
109
|
docWorkspaceCreate,
|
|
108
110
|
docWorkspaceList,
|
|
111
|
+
docWorkspaceStatus,
|
|
109
112
|
docWorkspaceView,
|
|
110
113
|
} from './doc-client'
|
|
111
114
|
import actionFormMd from './content/action-form.md?raw'
|
|
@@ -175,6 +178,7 @@ const patternScope = {
|
|
|
175
178
|
DocBrowserActionProvider,
|
|
176
179
|
docWorkspaceCreate,
|
|
177
180
|
docWorkspaceList,
|
|
181
|
+
docWorkspaceStatus,
|
|
178
182
|
docWorkspaceView,
|
|
179
183
|
docSessionArchive,
|
|
180
184
|
docSessionDelete,
|
|
@@ -365,6 +369,8 @@ export const UI_SECTIONS: DocSection[] = [
|
|
|
365
369
|
{ slug: 'carousel', title: 'Carousel', render: comp('Carousel', 'carousel', carouselMd) },
|
|
366
370
|
{ slug: 'copyable', title: 'Copyable', render: comp('Copyable', 'copyable', copyableMd) },
|
|
367
371
|
{ slug: 'detail', title: 'Detail', render: comp('Detail', 'detail', detailMd) },
|
|
372
|
+
{ slug: 'dictionary-value', title: 'Dictionary Value', render: comp('Dictionary Value', 'dictionary-value', dictionaryValueMd) },
|
|
373
|
+
{ slug: 'empty-value', title: 'Empty Value', render: comp('Empty Value', 'empty-value', emptyValueMd) },
|
|
368
374
|
{ slug: 'item', title: 'Item', render: comp('Item', 'item', itemMd) },
|
|
369
375
|
{ slug: 'kbd', title: 'Kbd', render: comp('Kbd', 'kbd', kbdMd) },
|
|
370
376
|
{ slug: 'markdown', title: 'Markdown', render: comp('Markdown', 'markdown', markdownMd) },
|
package/src/ui/meta.ts
CHANGED
|
@@ -27,7 +27,19 @@ export const componentMeta = {
|
|
|
27
27
|
name: 'badge',
|
|
28
28
|
ancestry: 'shadcn',
|
|
29
29
|
whenToUse:
|
|
30
|
-
'Rótulo curto de status/categoria. `variant` pra intenção (default/secondary/destructive/outline) ou tom de status (success/warning/info, fill tingido). Não-interativo — pra clique, use Button ou `asChild` num <a>.',
|
|
30
|
+
'Rótulo curto de status/categoria. `variant` pra intenção (default/secondary/destructive/outline) ou tom de status (success/warning/info/danger, fill tingido). Não-interativo — pra clique, use Button ou `asChild` num <a>. Valor de dicionário com papel declarado usa DictionaryValue em vez de escolher a variante à mão.',
|
|
31
|
+
},
|
|
32
|
+
'empty-value': {
|
|
33
|
+
name: 'empty-value',
|
|
34
|
+
ancestry: 'opus',
|
|
35
|
+
whenToUse:
|
|
36
|
+
'Representação padrão de valor ausente (`null`, `undefined`, string vazia ou só espaços): em célula compacta (`compact`) mostra o travessão e reserva “Não informado” à leitura assistiva; em texto corrido mostra o rótulo. `label` troca o significado da ausência no domínio (“Nunca enviado”, “Sem vencimento”). `0` e `false` são valores, nunca ausência. Colunas de ActionList e DetailField já a usam; renderer customizado reutiliza em vez de repetir a condicional.',
|
|
37
|
+
},
|
|
38
|
+
'dictionary-value': {
|
|
39
|
+
name: 'dictionary-value',
|
|
40
|
+
ancestry: 'opus',
|
|
41
|
+
whenToUse:
|
|
42
|
+
'Valor de um `t.dict` com a apresentação que o dicionário declarou (ADR 0003): `classification` vira Badge outline, `status`/`stage` viram Badge tonal pelo `tone` da entrada, `plain` ou sem papel vira texto. Ícone só quando a entrada declara `icon` do catálogo; tooltip só quando `description` acrescenta ao rótulo. O texto está sempre presente. Overrides explícitos por `presentation`, `variant`, `icon`, `tooltip` e `fallback`; não inferir badge, cor ou ícone a partir do nome do dicionário. Colunas de ActionList cujo campo é dicionário já usam este renderer.',
|
|
31
43
|
},
|
|
32
44
|
'dot': {
|
|
33
45
|
name: 'dot',
|
|
@@ -293,7 +305,7 @@ export const componentMeta = {
|
|
|
293
305
|
name: 'pagination',
|
|
294
306
|
ancestry: 'shadcn',
|
|
295
307
|
whenToUse:
|
|
296
|
-
'Navegação entre páginas montada por composição
|
|
308
|
+
'Navegação entre páginas montada por composição: Pagination › PaginationContent › PaginationItem com PaginationLink, mais PaginationPrevious/PaginationNext e PaginationEllipsis. `page` dá o número e o nome acessível (“Página N”); `isActive` marca a atual. Com `href` o link é `<a>`; sem `href` vira `<button>` (modo controlado, com foco e `disabled`). Os números têm largura mínima quadrada e crescem com os dígitos; as setas seguem quadradas (`iconOnly`). `label` localiza as setas. É o único paginador: ActionList compõe esta primitiva na escala densa do rodapé. Pra rolagem infinita ou listas curtas, dispense a barra.',
|
|
297
309
|
},
|
|
298
310
|
'progress': {
|
|
299
311
|
name: 'progress',
|
package/src/ui/react.tsx
CHANGED
|
@@ -47,6 +47,11 @@ export { Label } from './components/primitives/label.tsx'
|
|
|
47
47
|
export { Badge, badgeVariants } from './components/primitives/badge.tsx'
|
|
48
48
|
export type { BadgeProps } from './components/primitives/badge.tsx'
|
|
49
49
|
|
|
50
|
+
export { DictionaryValue } from './components/primitives/dictionary-value.tsx'
|
|
51
|
+
export { EmptyValue, isAbsentValue } from './components/primitives/empty-value.tsx'
|
|
52
|
+
export type { EmptyValueProps } from './components/primitives/empty-value.tsx'
|
|
53
|
+
export type { DictionaryValueProps, DictionaryValueSource } from './components/primitives/dictionary-value.tsx'
|
|
54
|
+
|
|
50
55
|
export { DetailField, DetailGroup } from './components/primitives/detail.tsx'
|
|
51
56
|
export type {
|
|
52
57
|
DetailFieldProps,
|
|
@@ -172,6 +177,7 @@ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './comp
|
|
|
172
177
|
export { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle } from './components/primitives/item.tsx'
|
|
173
178
|
export { Kbd, KbdGroup } from './components/primitives/kbd.tsx'
|
|
174
179
|
export { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from './components/primitives/pagination.tsx'
|
|
180
|
+
export type { PaginationLinkProps, PaginationControlProps } from './components/primitives/pagination.tsx'
|
|
175
181
|
export { Progress } from './components/primitives/progress.tsx'
|
|
176
182
|
export { RadioGroup, RadioGroupItem } from './components/primitives/radio-group.tsx'
|
|
177
183
|
export { ScrollArea, ScrollBar } from './components/primitives/scroll-area.tsx'
|