@softize/opus 18.1.0 → 18.2.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 +75 -0
- package/PROMOTED.md +4 -5
- package/README.md +5 -4
- package/bin/cli.mjs +4 -0
- package/docs/adr/0004-page-content-state-is-composed.md +3 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -2
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +6 -1
- package/docs/adr/0014-structural-headers-do-not-carry-description.md +2 -2
- package/docs/adr/0015-action-size-follows-interaction-density.md +4 -3
- package/docs/adr/{0012-modal-header-only-names-the-surface.md → 0018-modal-header-only-names-the-surface.md} +4 -1
- package/docs/adr/{0016-productive-surfaces-use-compact-density.md → 0019-productive-surfaces-use-compact-density.md} +4 -1
- package/docs/code-style.md +2 -2
- package/docs/consumer-upgrade-propagation.md +1 -1
- package/docs/data-products.md +5 -3
- package/docs/protocol.md +6 -6
- package/docs/releasing.md +28 -4
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +11 -1
- package/src/auth/drivers/jwt.ts +2 -1
- package/src/core/presentation.ts +143 -58
- package/src/core/runtime.ts +25 -4
- package/src/core/types.ts +4 -4
- package/src/mcp/index.ts +9 -0
- package/src/ui/components/patterns/content-header.tsx +1 -1
- package/src/ui/components/patterns/form.tsx +1 -1
- package/src/ui/components/patterns/presentation.tsx +199 -60
- package/src/ui/components/patterns/sidebar.tsx +1 -1
- package/src/ui/components/patterns/split.tsx +5 -2
- package/src/ui/components/patterns/surface-assistant.tsx +74 -0
- package/src/ui/components/primitives/card.tsx +1 -1
- package/src/ui/components/primitives/detail.tsx +7 -7
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/select.tsx +1 -1
- package/src/ui/docs/content/action-form-dialog.md +11 -4
- package/src/ui/docs/content/action-form.md +7 -16
- package/src/ui/docs/content/action-list-dialog.md +4 -6
- package/src/ui/docs/content/action-list.md +46 -4
- package/src/ui/docs/content/action-trigger.md +9 -5
- package/src/ui/docs/content/action-view.md +12 -8
- package/src/ui/docs/content/actions.md +36 -13
- package/src/ui/docs/content/ai.md +26 -7
- package/src/ui/docs/content/alert.md +4 -3
- package/src/ui/docs/content/aspect-ratio.md +2 -2
- package/src/ui/docs/content/auth.md +25 -10
- package/src/ui/docs/content/avatar.md +1 -1
- package/src/ui/docs/content/badge.md +2 -2
- package/src/ui/docs/content/breadcrumb.md +3 -2
- package/src/ui/docs/content/button.md +31 -7
- package/src/ui/docs/content/calendar.md +1 -1
- package/src/ui/docs/content/card.md +1 -1
- package/src/ui/docs/content/carousel.md +14 -3
- package/src/ui/docs/content/chat.md +1 -1
- package/src/ui/docs/content/cli.md +13 -7
- package/src/ui/docs/content/command.md +34 -2
- package/src/ui/docs/content/composer.md +1 -1
- package/src/ui/docs/content/content.md +5 -4
- package/src/ui/docs/content/customization.md +1 -1
- package/src/ui/docs/content/cycle.md +7 -5
- package/src/ui/docs/content/data-state.md +6 -5
- package/src/ui/docs/content/data.md +3 -3
- package/src/ui/docs/content/detail.md +3 -2
- package/src/ui/docs/content/dialog.md +2 -2
- package/src/ui/docs/content/dictionary-value.md +1 -1
- package/src/ui/docs/content/dock.md +23 -2
- package/src/ui/docs/content/dot.md +0 -2
- package/src/ui/docs/content/drawer.md +1 -1
- package/src/ui/docs/content/empty.md +1 -4
- package/src/ui/docs/content/events.md +1 -1
- package/src/ui/docs/content/field.md +20 -11
- package/src/ui/docs/content/getting-started.md +4 -2
- package/src/ui/docs/content/icon-picker.md +2 -2
- package/src/ui/docs/content/input-otp.md +2 -0
- package/src/ui/docs/content/input.md +2 -3
- package/src/ui/docs/content/item.md +5 -4
- package/src/ui/docs/content/kbd.md +2 -1
- package/src/ui/docs/content/mcp.md +10 -4
- package/src/ui/docs/content/menu.md +27 -0
- package/src/ui/docs/content/page.md +19 -5
- package/src/ui/docs/content/pagination.md +9 -2
- package/src/ui/docs/content/popover.md +2 -2
- package/src/ui/docs/content/presentation.md +85 -58
- package/src/ui/docs/content/progress.md +2 -6
- package/src/ui/docs/content/runtime.md +8 -5
- package/src/ui/docs/content/scheduler.md +1 -1
- package/src/ui/docs/content/select.md +13 -8
- package/src/ui/docs/content/sidebar.md +3 -2
- package/src/ui/docs/content/skeleton.md +1 -1
- package/src/ui/docs/content/slider.md +4 -4
- package/src/ui/docs/content/spinner.md +3 -3
- package/src/ui/docs/content/split.md +80 -19
- package/src/ui/docs/content/tabs.md +6 -6
- package/src/ui/docs/content/testing.md +4 -2
- package/src/ui/docs/content/toast.md +3 -5
- package/src/ui/docs/content/toggle.md +37 -0
- package/src/ui/docs/content/tooltip.md +4 -3
- package/src/ui/docs/content/truncate.md +3 -2
- package/src/ui/docs/content/ui.md +3 -1
- package/src/ui/docs/content/upgrading.md +43 -13
- package/src/ui/docs/doc-client.tsx +1 -1
- package/src/ui/docs/registry.tsx +30 -5
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +5 -0
|
@@ -15,9 +15,9 @@ representa um único controle. `min`, `max` e `step` delimitam os valores dispon
|
|
|
15
15
|
const [parallelism, setParallelism] = useState([4])
|
|
16
16
|
|
|
17
17
|
render(
|
|
18
|
-
<div className="grid w-full gap-3">
|
|
18
|
+
<div role="group" aria-labelledby="parallelism-label" className="grid w-full gap-3">
|
|
19
19
|
<div className="flex items-center justify-between">
|
|
20
|
-
<Label>Sessões em paralelo</Label>
|
|
20
|
+
<Label id="parallelism-label">Sessões em paralelo</Label>
|
|
21
21
|
<span className="text-sm text-muted-foreground">{parallelism[0]}</span>
|
|
22
22
|
</div>
|
|
23
23
|
<Slider value={parallelism} onValueChange={setParallelism} min={1} max={8} step={1} />
|
|
@@ -33,9 +33,9 @@ Dois números em `value` criam um intervalo selecionável entre dois controles.
|
|
|
33
33
|
const [budget, setBudget] = useState([20, 60])
|
|
34
34
|
|
|
35
35
|
render(
|
|
36
|
-
<div className="grid w-full gap-3">
|
|
36
|
+
<div role="group" aria-labelledby="budget-label" className="grid w-full gap-3">
|
|
37
37
|
<div className="flex items-center justify-between">
|
|
38
|
-
<Label>Custo estimado (US$)</Label>
|
|
38
|
+
<Label id="budget-label">Custo estimado (US$)</Label>
|
|
39
39
|
<span className="text-sm text-muted-foreground">{budget[0]} – {budget[1]}</span>
|
|
40
40
|
</div>
|
|
41
41
|
<Slider value={budget} onValueChange={setBudget} min={0} max={100} step={5} />
|
|
@@ -14,11 +14,11 @@ de um `lg`. O ícone é decorativo; quem nomeia a espera é o contêiner (`role=
|
|
|
14
14
|
|
|
15
15
|
## No botão
|
|
16
16
|
|
|
17
|
-
Durante uma ação,
|
|
18
|
-
|
|
17
|
+
Durante uma ação, use `busy` no `Button` com um rótulo que descreva o andamento. O botão fica
|
|
18
|
+
desabilitado e o spinner ocupa o lugar do ícone, no glifo do tamanho do botão.
|
|
19
19
|
|
|
20
20
|
```tsx preview
|
|
21
|
-
<Button
|
|
21
|
+
<Button busy>Publicando…</Button>
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Em carga de conteúdo
|
|
@@ -12,11 +12,13 @@ render(
|
|
|
12
12
|
<div className="p-4 text-sm">Navegação</div>
|
|
13
13
|
</Pane>
|
|
14
14
|
<Pane grow inset="lg">
|
|
15
|
-
<p className="text-sm text-muted-foreground">
|
|
15
|
+
<p className="text-sm text-muted-foreground">
|
|
16
|
+
Conteúdo. Arraste a divisória.
|
|
17
|
+
</p>
|
|
16
18
|
</Pane>
|
|
17
19
|
</Split>
|
|
18
20
|
</div>,
|
|
19
|
-
)
|
|
21
|
+
);
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
## Espaçamento interno
|
|
@@ -30,8 +32,12 @@ Quando uma área precisa continuar legível em telas largas, use uma unidade abs
|
|
|
30
32
|
|
|
31
33
|
```tsx
|
|
32
34
|
<Split resizable>
|
|
33
|
-
<Pane grow inset="none"
|
|
34
|
-
|
|
35
|
+
<Pane grow inset="none">
|
|
36
|
+
<Main />
|
|
37
|
+
</Pane>
|
|
38
|
+
<Pane initialSize="28rem" minSize="20rem" inset="none">
|
|
39
|
+
<Inspector />
|
|
40
|
+
</Pane>
|
|
35
41
|
</Split>
|
|
36
42
|
```
|
|
37
43
|
|
|
@@ -75,23 +81,78 @@ const defaultLayout = readLayout('workspace-layout')
|
|
|
75
81
|
|
|
76
82
|
Em aplicações renderizadas no servidor, leia o armazenamento somente no cliente. `onLayoutChanged` também permite usar `sessionStorage` ou uma camada própria quando o layout precisa acompanhar outro escopo.
|
|
77
83
|
|
|
84
|
+
## Assistência dentro da superfície
|
|
85
|
+
|
|
86
|
+
`SurfaceAssistant` especializa o split usado por uma Page, Dialog ou Drawer que abre assistência
|
|
87
|
+
contextual. O conteúdo principal permanece montado e o painel aparece à direita com largura
|
|
88
|
+
redimensionável. A superfície hospedeira controla o gatilho, o estado aberto e os dados passados ao
|
|
89
|
+
painel; o pattern cuida apenas da relação espacial. Envolva somente o body da superfície com
|
|
90
|
+
`SurfaceAssistant`: cabeçalho e rodapé devem permanecer fora do split e ocupar toda a largura.
|
|
91
|
+
|
|
92
|
+
```tsx preview
|
|
93
|
+
function Example() {
|
|
94
|
+
const [open, setOpen] = React.useState(true);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="h-72 overflow-hidden rounded-lg border border-border">
|
|
98
|
+
<SurfaceAssistant
|
|
99
|
+
open={open}
|
|
100
|
+
assistantLabel="Assistente do pedido"
|
|
101
|
+
assistant={
|
|
102
|
+
<div className="h-full p-3">
|
|
103
|
+
<p className="text-sm">Conversa vinculada ao pedido carregado.</p>
|
|
104
|
+
<Button
|
|
105
|
+
className="mt-3"
|
|
106
|
+
variant="ghost"
|
|
107
|
+
onClick={() => setOpen(false)}
|
|
108
|
+
>
|
|
109
|
+
Fechar
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
<div className="h-full p-3">
|
|
115
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
116
|
+
Abrir assistente
|
|
117
|
+
</Button>
|
|
118
|
+
</div>
|
|
119
|
+
</SurfaceAssistant>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render(<Example />);
|
|
125
|
+
```
|
|
126
|
+
|
|
78
127
|
## Propriedades de Split
|
|
79
128
|
|
|
80
|
-
| Propriedade
|
|
81
|
-
|
|
82
|
-
| `direction`
|
|
83
|
-
| `resizable`
|
|
84
|
-
| `handle`
|
|
85
|
-
| `id`
|
|
86
|
-
| `defaultLayout`
|
|
87
|
-
| `onLayoutChanged` | `(layout, { isUserInteraction }) => void` |
|
|
129
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
130
|
+
| ----------------- | ----------------------------------------- | -------------- | ----------------------------------------------------------------------- |
|
|
131
|
+
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Sentido em que os panes se alinham. |
|
|
132
|
+
| `resizable` | `boolean` | `false` | Cada fronteira ganha um separador acessível e arrastável. |
|
|
133
|
+
| `handle` | `boolean` | `false` | Mostra a alça visual no separador. |
|
|
134
|
+
| `id` | `string` | | Identidade estável do grupo redimensionável. |
|
|
135
|
+
| `defaultLayout` | `Record<string, number>` | | Layout percentual restaurado, indexado pelos ids dos panes. |
|
|
136
|
+
| `onLayoutChanged` | `(layout, { isUserInteraction }) => void` | | Chamado ao concluir uma mudança de layout; persista onde fizer sentido. |
|
|
88
137
|
|
|
89
138
|
## Propriedades de Pane
|
|
90
139
|
|
|
91
|
-
| Propriedade
|
|
92
|
-
|
|
93
|
-
| `id`
|
|
94
|
-
| `initialSize`
|
|
95
|
-
| `minSize` / `maxSize` | `number \| string`
|
|
96
|
-
| `grow`
|
|
97
|
-
| `inset`
|
|
140
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
141
|
+
| --------------------- | -------------------------------- | ------- | ----------------------------------------------------------------------------------------- |
|
|
142
|
+
| `id` | `string` | | Identidade estável usada pelo layout redimensionável e persistido. |
|
|
143
|
+
| `initialSize` | `number \| string` | | Tamanho inicial; número é porcentagem, string aceita `%`, `rem`, `em`, `vh`, `vw` e `px`. |
|
|
144
|
+
| `minSize` / `maxSize` | `number \| string` | | Limites quando o split é redimensionável. |
|
|
145
|
+
| `grow` | `boolean` | `false` | Ocupa o espaço remanescente no layout simples. |
|
|
146
|
+
| `inset` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Respiro interno; `none` para chrome, navegação ou conteúdo com inset próprio. |
|
|
147
|
+
|
|
148
|
+
## Propriedades de SurfaceAssistant
|
|
149
|
+
|
|
150
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
151
|
+
| ---------------- | ----------- | ------- | ------------------------------------------------------- |
|
|
152
|
+
| `open` | `boolean` | | Exibe o painel complementar à direita. |
|
|
153
|
+
| `assistantLabel` | `string` | | Nome acessível da região complementar. |
|
|
154
|
+
| `children` | `ReactNode` | | Conteúdo principal que permanece montado. |
|
|
155
|
+
| `assistant` | `ReactNode` | | Painel fornecido pela superfície que conhece o recurso. |
|
|
156
|
+
| `initialSize` | `PaneSize` | `28rem` | Largura inicial do painel. |
|
|
157
|
+
| `minSize` | `PaneSize` | `20rem` | Largura mínima durante o redimensionamento. |
|
|
158
|
+
| `maxSize` | `PaneSize` | `40rem` | Largura máxima durante o redimensionamento. |
|
|
@@ -58,8 +58,8 @@ vertical amplie a área interativa.
|
|
|
58
58
|
|
|
59
59
|
## Vertical
|
|
60
60
|
|
|
61
|
-
Com `orientation="vertical"`, a lista forma uma coluna
|
|
62
|
-
lateral do gatilho.
|
|
61
|
+
Com `orientation="vertical"`, a lista forma uma coluna. O exemplo usa a variante padrão; com
|
|
62
|
+
`variant="line"`, a marca da aba ativa passa para a lateral do gatilho.
|
|
63
63
|
|
|
64
64
|
```tsx preview col
|
|
65
65
|
<Tabs defaultValue="prompt" orientation="vertical">
|
|
@@ -87,9 +87,9 @@ lateral do gatilho.
|
|
|
87
87
|
```tsx preview col
|
|
88
88
|
<Tabs defaultValue="preview" size="sm">
|
|
89
89
|
<TabsList>
|
|
90
|
-
<TabsTrigger value="preview"
|
|
91
|
-
<TabsTrigger value="code"
|
|
92
|
-
<TabsTrigger value="ai"
|
|
90
|
+
<TabsTrigger value="preview">Preview</TabsTrigger>
|
|
91
|
+
<TabsTrigger value="code">Código</TabsTrigger>
|
|
92
|
+
<TabsTrigger value="ai">IA</TabsTrigger>
|
|
93
93
|
</TabsList>
|
|
94
94
|
</Tabs>
|
|
95
95
|
```
|
|
@@ -101,7 +101,7 @@ lateral do gatilho.
|
|
|
101
101
|
| `defaultValue` | `string` | | Aba inicial no modo não controlado. |
|
|
102
102
|
| `value` | `string` | | Aba ativa no modo controlado. Use com `onValueChange`. |
|
|
103
103
|
| `onValueChange` | `(value: string) => void` | | Chamado quando a pessoa seleciona outra aba. |
|
|
104
|
-
| `size` | `'default' \| 'sm'` | `'default'` | Altura da lista na escala única dos controles (2.25 e 2rem), aplicada em `TabsList`. |
|
|
104
|
+
| `size` | `'default' \| 'sm'` | `'default'` | Altura da lista na escala única dos controles (2.25 e 2rem), aplicada em `TabsList`. Não altera a altura de `line` horizontal, que acompanha o conteúdo. |
|
|
105
105
|
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da lista de abas. |
|
|
106
106
|
|
|
107
107
|
## Propriedades de TabsList
|
|
@@ -72,10 +72,12 @@ schema zod — matéria-prima do `mockHandler` (modo design) e de fixtures de te
|
|
|
72
72
|
url, datetime) e por nome de campo (email, id, `*At`→data, telefone, cpf/cnpj, nome…), sabor pt-BR.
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
+
import { entityRowSchema } from '@softize/opus/schema'
|
|
75
76
|
import { fake, fakeMany } from '@softize/opus/testing'
|
|
76
77
|
|
|
77
|
-
const
|
|
78
|
-
const
|
|
78
|
+
const eventRowSchema = entityRowSchema(EventEntity) // schema da linha da entidade
|
|
79
|
+
const one = fake(eventRowSchema) // 1 evento válido (safeParse passa)
|
|
80
|
+
const many = fakeMany(eventRowSchema, 20) // 20, estáveis entre execuções
|
|
79
81
|
fake(schema, { seed: 7 }) // seed própria
|
|
80
82
|
```
|
|
81
83
|
|
|
@@ -31,11 +31,9 @@ hierarquia precisar ser diferente. A região fica alinhada ao fim lógico da sup
|
|
|
31
31
|
canto usado pelas ações do Alert. O clique fecha o toast, salvo quando o handler chama
|
|
32
32
|
`event.preventDefault()`.
|
|
33
33
|
|
|
34
|
-
Na versão 13, `actions` substitui os campos `action` e `cancel` da versão 12. Migre cada controle
|
|
35
|
-
para uma entrada da coleção e preserve a ordem visual desejada.
|
|
36
|
-
|
|
37
34
|
`toast.promise` acompanha uma promessa e atualiza a mesma notificação nos estados de carregamento,
|
|
38
|
-
sucesso ou erro.
|
|
35
|
+
sucesso ou erro. Ela não aceita `actions`; use `toast` com `actions` quando a notificação precisar
|
|
36
|
+
oferecer uma ação.
|
|
39
37
|
|
|
40
38
|
```tsx preview
|
|
41
39
|
<Button
|
|
@@ -88,7 +86,7 @@ Monte um único `Toaster` na raiz do aplicativo. O tema vem da propriedade `them
|
|
|
88
86
|
|---|---|---|---|
|
|
89
87
|
| `description` | `React.ReactNode` | | Complemento exibido abaixo do título. |
|
|
90
88
|
| `actions` | `ToastAction[]` | | Coleção ordenada de ações; a última recebe destaque primário por padrão. |
|
|
91
|
-
| `duration` | `number` |
|
|
89
|
+
| `duration` | `number` | `4000` | Tempo de permanência da notificação, em milissegundos. O `Toaster` pode mudar o padrão, e `toast.loading` não expira sozinho. |
|
|
92
90
|
|
|
93
91
|
## Propriedades de ToastAction
|
|
94
92
|
|
|
@@ -71,6 +71,7 @@ disabled esmaece e bloqueia o clique — o estado pressed permanece visível.
|
|
|
71
71
|
| `defaultPressed` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
72
72
|
| `variant` | `'default' \| 'outline'` | `'default'` | default não tem borda (fundo só quando ativo); outline carrega a borda. |
|
|
73
73
|
| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Altura na escala única dos controles (2 · 2.25 · 2.5rem) — sm para toolbar densa, lg para alvo mais confortável. |
|
|
74
|
+
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria do controle; `pill` arredonda as extremidades. |
|
|
74
75
|
| `disabled` | `boolean` | `false` | Esmaece e bloqueia o clique, preservando o estado visual. |
|
|
75
76
|
|
|
76
77
|
## ToggleGroup
|
|
@@ -106,6 +107,39 @@ render(
|
|
|
106
107
|
)
|
|
107
108
|
```
|
|
108
109
|
|
|
110
|
+
### Itens só com ícone e tooltip
|
|
111
|
+
|
|
112
|
+
Quando o item mostra somente um ícone, mantenha o `aria-label` e acrescente uma dica visual. Envolva o
|
|
113
|
+
`ToggleGroupItem` em `TooltipTrigger asChild`: o item preserva o estado selecionado e seus atributos,
|
|
114
|
+
e a dica aparece sem criar outro botão. O `TooltipProvider` da raiz do aplicativo continua necessário.
|
|
115
|
+
|
|
116
|
+
```tsx preview
|
|
117
|
+
const [align, setAlign] = useState('left')
|
|
118
|
+
|
|
119
|
+
render(
|
|
120
|
+
<ToggleGroup type="single" value={align} onValueChange={(v) => v && setAlign(v)}>
|
|
121
|
+
<Tooltip>
|
|
122
|
+
<TooltipTrigger asChild>
|
|
123
|
+
<ToggleGroupItem value="left" aria-label="Alinhar à esquerda"><AlignLeft /></ToggleGroupItem>
|
|
124
|
+
</TooltipTrigger>
|
|
125
|
+
<TooltipContent>Alinhar à esquerda</TooltipContent>
|
|
126
|
+
</Tooltip>
|
|
127
|
+
<Tooltip>
|
|
128
|
+
<TooltipTrigger asChild>
|
|
129
|
+
<ToggleGroupItem value="center" aria-label="Centralizar"><AlignCenter /></ToggleGroupItem>
|
|
130
|
+
</TooltipTrigger>
|
|
131
|
+
<TooltipContent>Centralizar</TooltipContent>
|
|
132
|
+
</Tooltip>
|
|
133
|
+
<Tooltip>
|
|
134
|
+
<TooltipTrigger asChild>
|
|
135
|
+
<ToggleGroupItem value="right" aria-label="Alinhar à direita"><AlignRight /></ToggleGroupItem>
|
|
136
|
+
</TooltipTrigger>
|
|
137
|
+
<TooltipContent>Alinhar à direita</TooltipContent>
|
|
138
|
+
</Tooltip>
|
|
139
|
+
</ToggleGroup>,
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
109
143
|
### Variante e espaçamento
|
|
110
144
|
|
|
111
145
|
`variant`, `size` e `shape` definidos no grupo chegam aos itens por contexto. `spacing` separa os
|
|
@@ -138,3 +172,6 @@ itens; com zero, eles formam um bloco contínuo.
|
|
|
138
172
|
|---|---|---|---|
|
|
139
173
|
| `value` | `string` | | Identificador que entra no valor do grupo quando o item é ativado. |
|
|
140
174
|
| `disabled` | `boolean` | `false` | Bloqueia somente este item e preserva seu estado visual. |
|
|
175
|
+
| `variant` | `'default' \| 'outline'` | `'default'` | Tratamento do item quando o grupo não declara `variant`; o valor do grupo prevalece. |
|
|
176
|
+
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Tamanho do item quando o grupo não declara `size`; o valor do grupo prevalece. |
|
|
177
|
+
| `shape` | `'default' \| 'pill'` | shape do grupo | Geometria deste item; quando informada, prevalece sobre a do grupo. |
|
|
@@ -24,17 +24,18 @@ precisando de `aria-label`.
|
|
|
24
24
|
</Tooltip>
|
|
25
25
|
<Tooltip>
|
|
26
26
|
<TooltipTrigger asChild><Button variant="ghost">Direita</Button></TooltipTrigger>
|
|
27
|
-
<TooltipContent side="right">
|
|
27
|
+
<TooltipContent side="right">Abre à direita do botão.</TooltipContent>
|
|
28
28
|
</Tooltip>
|
|
29
29
|
<Tooltip>
|
|
30
30
|
<TooltipTrigger asChild><Button variant="ghost">Embaixo</Button></TooltipTrigger>
|
|
31
|
-
<TooltipContent side="bottom">
|
|
31
|
+
<TooltipContent side="bottom">Abre abaixo do botão.</TooltipContent>
|
|
32
32
|
</Tooltip>
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Provider na raiz
|
|
36
36
|
|
|
37
|
-
Monte um único `TooltipProvider` na raiz
|
|
37
|
+
Monte um único `TooltipProvider` na raiz do aplicativo. Ele é obrigatório: um `Tooltip` fora de um
|
|
38
|
+
provider lança erro. O provider também compartilha o atraso de exibição, então cada `Tooltip` não
|
|
38
39
|
precisa de um provider próprio.
|
|
39
40
|
|
|
40
41
|
```tsx
|
|
@@ -59,7 +59,8 @@ quando o texto completo ainda não oferecer contexto suficiente.
|
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
Requer `TooltipProvider` na raiz (o esqueleto do `opus create` já monta). Largura vem do
|
|
62
|
-
container ou de `className` (`max-w-*`) — o span é `block truncate
|
|
62
|
+
container ou de `className` (`max-w-*`) — o span é `block` e corta em reticências (`truncate`); com
|
|
63
|
+
`fade`, esmaece o fim da linha em vez das reticências.
|
|
63
64
|
|
|
64
65
|
## Propriedades de Truncate
|
|
65
66
|
|
|
@@ -67,4 +68,4 @@ container ou de `className` (`max-w-*`) — o span é `block truncate`.
|
|
|
67
68
|
|---|---|---|---|
|
|
68
69
|
| `tooltip` | `ReactNode` | os próprios `children` | Conteúdo da dica quando o texto transborda. |
|
|
69
70
|
| `fade` | `boolean` | `false` | Sinaliza o corte esmaecendo o fim da linha, no lugar das reticências. |
|
|
70
|
-
| `className` | `string` | | Largura (`max-w-*`) e demais ajustes; o span é `block truncate`. |
|
|
71
|
+
| `className` | `string` | | Largura (`max-w-*`) e demais ajustes; o span é `block`, com `truncate` fora do modo `fade`. |
|
|
@@ -24,10 +24,12 @@ para o aplicativo quando a API pública já atender ao caso.
|
|
|
24
24
|
```tsx
|
|
25
25
|
// Componentes e hooks — tudo do mesmo barrel.
|
|
26
26
|
import { Button, Dialog, useAction } from '@softize/opus/ui/react'
|
|
27
|
+
```
|
|
27
28
|
|
|
29
|
+
```css
|
|
28
30
|
/* index.css — o tema canônico + os componentes do Opus no scan do Tailwind. */
|
|
29
31
|
@import '@softize/opus/ui/theme.css';
|
|
30
|
-
@source '../node_modules/@softize/opus/src/ui';
|
|
32
|
+
@source '../node_modules/@softize/opus/src/ui/**/*.{ts,tsx}';
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## Prebundle do Vite
|
|
@@ -10,10 +10,23 @@ gates apontam as adaptações necessárias no projeto.
|
|
|
10
10
|
## 1. Identificar a versão disponível
|
|
11
11
|
|
|
12
12
|
O **Maestro** compara o pin (`opus.json`) com a versão instalada e com a fonte a cada
|
|
13
|
-
sessão — o alerta no rodapé diz a distância (`
|
|
13
|
+
sessão — o alerta no rodapé diz a distância (`v18.0.1 → v18.1.0`) e o **Atualizar** faz
|
|
14
14
|
a parte mecânica: bump da dependência, re-materialização da camada de IA (skills,
|
|
15
|
-
hooks, agentes) e re-sync do bloco gerenciado do CLAUDE.md.
|
|
16
|
-
|
|
15
|
+
hooks, agentes) e re-sync do bloco gerenciado do CLAUDE.md.
|
|
16
|
+
|
|
17
|
+
Fora do Maestro, faça essa parte à mão:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm outdated @softize/opus # distância até a versão publicada
|
|
21
|
+
pnpm -r up @softize/opus@<versão> # em todo workspace que declara o Opus
|
|
22
|
+
pnpm run setup # opus setup && base setup: re-materializa base.json e skills
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Rode `setup` no diretório de cada app que tem `opus.json`; num monorepo criado por
|
|
26
|
+
`opus create --monorepo`, a raiz não tem esse script, e `pnpm -r run setup` alcança todos os apps.
|
|
27
|
+
O postinstall do pacote tenta materializar sozinho, mas é best-effort; se a camada materializada
|
|
28
|
+
ainda corresponder à versão anterior, o `opus check` aborta com
|
|
29
|
+
`base.json: Opus aplicado X, instalado Y`.
|
|
17
30
|
|
|
18
31
|
## 2. Ler as mudanças acumuladas
|
|
19
32
|
|
|
@@ -22,19 +35,36 @@ O changelog **viaja no pacote** — depois do bump, está em
|
|
|
22
35
|
a seção **Breaking** diz a migração (o que renomeou, o que fazer no seu código).
|
|
23
36
|
A esteira de release **recusa** publicar versão sem entrada — o arquivo não defasa.
|
|
24
37
|
|
|
25
|
-
- **minor** (
|
|
26
|
-
|
|
38
|
+
- **minor e patch** (18.0 → 18.1): leia todas as entradas intermediárias. Minors podem pedir ação,
|
|
39
|
+
como a troca de `PageHeader` por `PageIntro` no `PageShell` (17.2.0) ou o layout desktop fixo (18.1.0).
|
|
40
|
+
- **major** (17.x → 18.0): além disso, aplique as seções Breaking de cada versão pulada, na ordem.
|
|
27
41
|
|
|
28
42
|
## 3. Executar as verificações
|
|
29
43
|
|
|
30
|
-
O Opus
|
|
44
|
+
O Opus é publicado como código-fonte TypeScript: o que quebrou aparece com arquivo e linha.
|
|
45
|
+
Rode, na ordem:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm typecheck # a API nova cobra os tipos
|
|
49
|
+
pnpm exec opus check # convenções (regras novas inclusas)
|
|
50
|
+
pnpm exec opus copy --check # inventário de copy em dia
|
|
51
|
+
pnpm exec base copy check # política de copy da base
|
|
52
|
+
pnpm test # comportamento
|
|
53
|
+
pnpm manifest:check # a spec projetada ficou fresca?
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Num monorepo, `typecheck` e `test` existem na raiz, mas `manifest:check` é script de cada app:
|
|
57
|
+
rode-o no diretório do app ou com `pnpm -r run manifest:check`. Nenhum desses comandos migra o
|
|
58
|
+
banco. Se o projeto tiver testes de integração, eles usam o banco que o próprio projeto configurar.
|
|
59
|
+
|
|
60
|
+
## 4. Migrar o banco, se houver
|
|
61
|
+
|
|
62
|
+
Se o `opus.config.ts` declara `database`, confira o schema e aplique a migração como uma etapa
|
|
63
|
+
separada, contra o banco do ambiente que você pretende alterar:
|
|
31
64
|
|
|
32
65
|
```bash
|
|
33
|
-
pnpm
|
|
34
|
-
pnpm exec opus
|
|
35
|
-
pnpm test # comportamento
|
|
36
|
-
pnpm manifest:check # a spec projetada ficou fresca?
|
|
37
|
-
opus db migrate # schema em dia (drift-check embutido)
|
|
66
|
+
pnpm exec opus db check # compara entidades e banco, sem escrever
|
|
67
|
+
pnpm exec opus db migrate # aplica o schema idempotente; escreve no banco
|
|
38
68
|
```
|
|
39
69
|
|
|
40
70
|
Com todas as verificações verdes, revise a atualização como qualquer outra mudança de código antes
|
|
@@ -42,6 +72,6 @@ de entregá-la.
|
|
|
42
72
|
|
|
43
73
|
## Pulou muitas versões?
|
|
44
74
|
|
|
45
|
-
O ritual não muda, só o volume: leia as
|
|
46
|
-
uma lista, não um diff), aplique na ordem e deixe os gates validarem o conjunto.
|
|
75
|
+
O ritual não muda, só o volume: leia todas as entradas acumuladas, não só as seções Breaking
|
|
76
|
+
(o changelog é uma lista, não um diff), aplique na ordem e deixe os gates validarem o conjunto.
|
|
47
77
|
Não há caminho especial — é o mesmo laço, com mais iterações.
|
|
@@ -105,7 +105,7 @@ export const docWorkspaceList = defineContract({
|
|
|
105
105
|
],
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
|
-
client: { label: 'Cliente', type: 'text',
|
|
108
|
+
client: { label: 'Cliente', type: 'text', placement: 'advanced' },
|
|
109
109
|
},
|
|
110
110
|
text: { fields: ['name'] },
|
|
111
111
|
sort: { fields: ['name'] },
|
package/src/ui/docs/registry.tsx
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* helper `comp`; páginas conceituais renderizam o markdown direto (o próprio md traz o H1/lead).
|
|
12
12
|
*/
|
|
13
13
|
import { useEffect } from "react";
|
|
14
|
+
import {
|
|
15
|
+
definePresentation,
|
|
16
|
+
definePresentationInvocation,
|
|
17
|
+
} from "../../core/presentation.ts";
|
|
14
18
|
import * as lucideIcons from "lucide-react";
|
|
15
19
|
import * as opusUi from "../react.tsx";
|
|
16
20
|
import { componentMeta } from "../meta.ts";
|
|
@@ -154,7 +158,7 @@ export interface DocSection {
|
|
|
154
158
|
// o scope injetado entra POR CIMA do default no DocMarkdown, e `<Badge>` num exemplo
|
|
155
159
|
// tem que ser o COMPONENTE (chip), não o selo do lucide. Ícone colidido, se um exemplo
|
|
156
160
|
// precisar, entra por alias no scope do chamador.
|
|
157
|
-
const iconScope: Record<string, unknown> = Object.fromEntries(
|
|
161
|
+
export const iconScope: Record<string, unknown> = Object.fromEntries(
|
|
158
162
|
Object.entries(lucideIcons).filter(([name]) => !(name in opusUi)),
|
|
159
163
|
);
|
|
160
164
|
|
|
@@ -177,7 +181,7 @@ const comp =
|
|
|
177
181
|
);
|
|
178
182
|
|
|
179
183
|
/** O palco dos patterns no scope dos previews: ícones, cliente simulado e contratos de exemplo. */
|
|
180
|
-
const patternScope = {
|
|
184
|
+
export const patternScope = {
|
|
181
185
|
...iconScope,
|
|
182
186
|
DocBrowserActionProvider,
|
|
183
187
|
docWorkspaceCreate,
|
|
@@ -188,12 +192,28 @@ const patternScope = {
|
|
|
188
192
|
docSessionDelete,
|
|
189
193
|
};
|
|
190
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Palco da página de Presentation: o exemplo declara a própria Presentation sobre um contrato do
|
|
197
|
+
* palco, então precisa das funções de declaração além do palco dos patterns. Fora daqui elas não
|
|
198
|
+
* entram no scope — os demais previews consomem definições já prontas.
|
|
199
|
+
*/
|
|
200
|
+
export const presentationScope = {
|
|
201
|
+
...patternScope,
|
|
202
|
+
definePresentation,
|
|
203
|
+
definePresentationInvocation,
|
|
204
|
+
};
|
|
205
|
+
|
|
191
206
|
/** Página de pattern: igual ao `comp`, mas os previews enxergam o palco doc-client no scope. */
|
|
192
207
|
const pattern =
|
|
193
|
-
(
|
|
208
|
+
(
|
|
209
|
+
title: string,
|
|
210
|
+
key: keyof typeof componentMeta,
|
|
211
|
+
content: string,
|
|
212
|
+
scope: Record<string, unknown> = patternScope,
|
|
213
|
+
) =>
|
|
194
214
|
(): React.ReactElement => (
|
|
195
215
|
<DocPage title={title} meta={componentMeta[key]}>
|
|
196
|
-
<DocMarkdown content={content} scope={
|
|
216
|
+
<DocMarkdown content={content} scope={scope} />
|
|
197
217
|
</DocPage>
|
|
198
218
|
);
|
|
199
219
|
|
|
@@ -406,7 +426,12 @@ export const UI_SECTIONS: DocSection[] = [
|
|
|
406
426
|
{
|
|
407
427
|
slug: "presentation",
|
|
408
428
|
title: "Presentation",
|
|
409
|
-
render: pattern(
|
|
429
|
+
render: pattern(
|
|
430
|
+
"Presentation",
|
|
431
|
+
"presentation",
|
|
432
|
+
presentationMd,
|
|
433
|
+
presentationScope,
|
|
434
|
+
),
|
|
410
435
|
},
|
|
411
436
|
{
|
|
412
437
|
slug: "content",
|
package/src/ui/meta.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const componentMeta = {
|
|
|
15
15
|
name: "content",
|
|
16
16
|
ancestry: "opus",
|
|
17
17
|
whenToUse:
|
|
18
|
-
"Organize uma região nomeada dentro de uma página ou de outra superfície. Use as propriedades de Content no caso comum e componha seus slots quando precisar controlar a estrutura.
|
|
18
|
+
"Organize uma região nomeada dentro de uma página ou de outra superfície. Use as propriedades de Content no caso comum e componha seus slots quando precisar controlar a estrutura. Numa página centrada em uma coleção, `level={1}` faz do Content o heading principal; nas demais, ele vem de PageTitle.",
|
|
19
19
|
},
|
|
20
20
|
ask: {
|
|
21
21
|
name: "ask",
|
|
@@ -153,7 +153,7 @@ export const componentMeta = {
|
|
|
153
153
|
name: "menu",
|
|
154
154
|
ancestry: "opus",
|
|
155
155
|
whenToUse:
|
|
156
|
-
'O menu de ações da casa: lista flutuante ancorada em um gatilho (Radix DropdownMenu). Compõe Menu > MenuTrigger + MenuContent e seus itens. `context="danger"` sinaliza item com consequência perigosa
|
|
156
|
+
'O menu de ações da casa: lista flutuante ancorada em um gatilho (Radix DropdownMenu). Compõe Menu > MenuTrigger + MenuContent e seus itens. `context="danger"` sinaliza item com consequência perigosa. Para escolher um valor, use Select; para busca por teclado, Command; para conteúdo livre ancorado, Popover.',
|
|
157
157
|
},
|
|
158
158
|
popover: {
|
|
159
159
|
name: "popover",
|
|
@@ -189,7 +189,7 @@ export const componentMeta = {
|
|
|
189
189
|
name: "table",
|
|
190
190
|
ancestry: "shadcn",
|
|
191
191
|
whenToUse:
|
|
192
|
-
'Tabela de dados. Compõe Table > (TableHeader > TableRow > TableHead, TableBody > TableRow > TableCell). `variant="plain"` vem sem borda externa; `variant="framed"` aplica a moldura canônica, recorta o scroll e destaca o cabeçalho. Para
|
|
192
|
+
'Tabela de dados. Compõe Table > (TableHeader > TableRow > TableHead, TableBody > TableRow > TableCell). `variant="plain"` vem sem borda externa; `variant="framed"` aplica a moldura canônica, recorta o scroll e destaca o cabeçalho. Para uma coleção com busca, filtros e paginação, use ActionList.',
|
|
193
193
|
},
|
|
194
194
|
tabs: {
|
|
195
195
|
name: "tabs",
|
|
@@ -279,7 +279,7 @@ export const componentMeta = {
|
|
|
279
279
|
name: "field",
|
|
280
280
|
ancestry: "shadcn",
|
|
281
281
|
whenToUse:
|
|
282
|
-
"Componha rótulo, controle, ajuda e erro com espaçamento consistente. Use a orientação vertical no caso comum e
|
|
282
|
+
"Componha rótulo, controle, ajuda e erro com espaçamento consistente. Use a orientação vertical no caso comum e a horizontal quando controle e texto precisarem ficar lado a lado; `responsive` continua aceito como alias da horizontal. Field cuida do layout; o formulário continua responsável por estado e validação.",
|
|
283
283
|
},
|
|
284
284
|
"input-otp": {
|
|
285
285
|
name: "input-otp",
|
|
@@ -321,7 +321,7 @@ export const componentMeta = {
|
|
|
321
321
|
name: "split",
|
|
322
322
|
ancestry: "opus",
|
|
323
323
|
whenToUse:
|
|
324
|
-
"Divide uma área em panes em sequência horizontal ou vertical. Use `resizable` quando a pessoa deve ajustar a fronteira; o mesmo `<Split>` vira flex simples sem ele. Cada `<Pane>` declara tamanho inicial/mínimo e inset.
|
|
324
|
+
"Divide uma área em panes em sequência horizontal ou vertical. Use `resizable` quando a pessoa deve ajustar a fronteira; o mesmo `<Split>` vira flex simples sem ele. Cada `<Pane>` declara tamanho inicial/mínimo e inset. SurfaceAssistant especializa esse layout para assistência contextual dentro de Page, Dialog ou Drawer.",
|
|
325
325
|
},
|
|
326
326
|
sidebar: {
|
|
327
327
|
name: "sidebar",
|
package/src/ui/react.tsx
CHANGED
|
@@ -424,6 +424,7 @@ export {
|
|
|
424
424
|
PresentationInspector,
|
|
425
425
|
} from "./components/patterns/presentation.tsx";
|
|
426
426
|
export type {
|
|
427
|
+
PresentationAssistantRuntime,
|
|
427
428
|
PresentationProps,
|
|
428
429
|
PresentationInspectorProps,
|
|
429
430
|
} from "./components/patterns/presentation.tsx";
|
|
@@ -483,6 +484,10 @@ export type {
|
|
|
483
484
|
SplitLayoutChange,
|
|
484
485
|
} from "./components/patterns/split.tsx";
|
|
485
486
|
|
|
487
|
+
// Assistência contextual: mantém o recurso visível e abre um painel redimensionável na superfície.
|
|
488
|
+
export { SurfaceAssistant } from "./components/patterns/surface-assistant.tsx";
|
|
489
|
+
export type { SurfaceAssistantProps } from "./components/patterns/surface-assistant.tsx";
|
|
490
|
+
|
|
486
491
|
// A barra de ferramentas que flutua sobre uma superfície de trabalho (canvas, editor, preview).
|
|
487
492
|
export {
|
|
488
493
|
Dock,
|