@softize/opus 23.0.0 → 24.0.1
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 +99 -0
- package/bin/cli.mjs +2 -0
- package/bin/lib/check.mjs +241 -42
- package/bin/lib/copy.mjs +16 -34
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
- package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
- package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
- package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
- package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
- package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
- package/docs/adr/0049-inline-navigation-groups-with-the-heading.md +95 -0
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/SKILL.md +11 -11
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
- package/registry/templates/app/src/App.tsx +24 -12
- package/src/ui/appearance.ts +149 -0
- package/src/ui/components/patterns/action-form-card.tsx +46 -33
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
- package/src/ui/components/patterns/confirm.tsx +3 -1
- package/src/ui/components/patterns/fill-region.ts +8 -7
- package/src/ui/components/patterns/form-dialog.tsx +1 -3
- package/src/ui/components/patterns/form.tsx +8 -7
- package/src/ui/components/patterns/list.tsx +15 -19
- package/src/ui/components/patterns/page.tsx +15 -11
- package/src/ui/components/patterns/pane.tsx +9 -240
- package/src/ui/components/patterns/presentation.tsx +14 -14
- package/src/ui/components/patterns/sidebar.tsx +7 -17
- package/src/ui/components/patterns/split.tsx +111 -59
- package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +90 -27
- package/src/ui/components/patterns/structural-media.tsx +26 -0
- package/src/ui/components/patterns/surface-anatomy.ts +71 -61
- package/src/ui/components/patterns/surface-assistant.tsx +2 -4
- package/src/ui/components/patterns/surface.tsx +620 -0
- package/src/ui/components/patterns/trigger.tsx +11 -9
- package/src/ui/components/primitives/ask.tsx +155 -83
- package/src/ui/components/primitives/button.tsx +11 -9
- package/src/ui/components/primitives/checkbox.tsx +8 -8
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/composer.tsx +47 -45
- package/src/ui/components/primitives/detail.tsx +9 -2
- package/src/ui/components/primitives/dialog.tsx +60 -26
- package/src/ui/components/primitives/drawer.tsx +58 -12
- package/src/ui/components/primitives/icon-picker.tsx +90 -73
- package/src/ui/components/primitives/input-group.tsx +35 -28
- package/src/ui/components/primitives/input-otp.tsx +16 -16
- package/src/ui/components/primitives/input.tsx +20 -10
- package/src/ui/components/primitives/item.tsx +9 -12
- package/src/ui/components/primitives/menu.tsx +44 -65
- package/src/ui/components/primitives/metric-card.tsx +75 -59
- package/src/ui/components/primitives/radio-group.tsx +9 -9
- package/src/ui/components/primitives/select.tsx +344 -209
- package/src/ui/components/primitives/switch.tsx +10 -10
- package/src/ui/components/primitives/table.tsx +29 -24
- package/src/ui/components/primitives/tabs.tsx +4 -2
- package/src/ui/components/primitives/textarea.tsx +6 -6
- package/src/ui/components/primitives/toggle-group-context.ts +24 -0
- package/src/ui/components/primitives/toggle-group.tsx +116 -74
- package/src/ui/components/primitives/toggle.tsx +82 -16
- package/src/ui/docs/DocBrowser.tsx +77 -69
- package/src/ui/docs/content/action-form-card.md +9 -9
- package/src/ui/docs/content/action-list.md +76 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/compositions.md +479 -0
- package/src/ui/docs/content/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +105 -60
- package/src/ui/docs/content/drawer.md +68 -22
- package/src/ui/docs/content/field.md +14 -15
- package/src/ui/docs/content/icon-tile.md +14 -13
- package/src/ui/docs/content/page.md +44 -34
- package/src/ui/docs/content/pane.md +42 -290
- package/src/ui/docs/content/presentation.md +3 -3
- package/src/ui/docs/content/separator.md +6 -6
- package/src/ui/docs/content/sidebar.md +68 -266
- package/src/ui/docs/content/skeleton.md +20 -18
- package/src/ui/docs/content/split.md +14 -14
- package/src/ui/docs/content/surface.md +513 -0
- package/src/ui/docs/content/tabs.md +20 -20
- package/src/ui/docs/content/toggle.md +117 -65
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +10 -6
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +40 -43
- package/src/ui/theme.css +311 -64
- package/src/ui/components/patterns/content-header.tsx +0 -528
- package/src/ui/components/patterns/surface-media.tsx +0 -69
- package/src/ui/components/primitives/card.tsx +0 -191
- package/src/ui/docs/content/card.md +0 -292
- package/src/ui/docs/content/content.md +0 -275
|
@@ -14,18 +14,14 @@ inicial no modo não controlado; o conteúdo pode ser texto ou ícone.
|
|
|
14
14
|
Use `pressed` e `onPressedChange` quando o estado pertencer ao consumidor.
|
|
15
15
|
|
|
16
16
|
```tsx preview
|
|
17
|
-
const [readOnly, setReadOnly] = useState(true)
|
|
17
|
+
const [readOnly, setReadOnly] = useState(true);
|
|
18
18
|
|
|
19
19
|
render(
|
|
20
|
-
<Toggle
|
|
21
|
-
variant="outline"
|
|
22
|
-
pressed={readOnly}
|
|
23
|
-
onPressedChange={setReadOnly}
|
|
24
|
-
>
|
|
20
|
+
<Toggle variant="outline" pressed={readOnly} onPressedChange={setReadOnly}>
|
|
25
21
|
<Eye />
|
|
26
22
|
Somente leitura
|
|
27
23
|
</Toggle>,
|
|
28
|
-
)
|
|
24
|
+
);
|
|
29
25
|
```
|
|
30
26
|
|
|
31
27
|
## Variantes e tamanhos
|
|
@@ -64,15 +60,15 @@ borda e usa um fundo sutil distinto dela no estado ativo. `size` ajusta a altura
|
|
|
64
60
|
|
|
65
61
|
## Propriedades de Toggle
|
|
66
62
|
|
|
67
|
-
| Propriedade
|
|
68
|
-
|
|
69
|
-
| `pressed`
|
|
70
|
-
| `onPressedChange` | `(pressed: boolean) => void` |
|
|
71
|
-
| `defaultPressed`
|
|
72
|
-
| `variant`
|
|
73
|
-
| `size`
|
|
74
|
-
| `shape`
|
|
75
|
-
| `disabled`
|
|
63
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
64
|
+
| ----------------- | ---------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| `pressed` | `boolean` | | Estado no modo controlado. Use com `onPressedChange`. |
|
|
66
|
+
| `onPressedChange` | `(pressed: boolean) => void` | | Chamado a cada alternância, com o novo estado. |
|
|
67
|
+
| `defaultPressed` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
68
|
+
| `variant` | `'default' \| 'outline'` | `'default'` | `default` não tem borda e só ganha fundo quando ativo; `outline` mantém a borda. |
|
|
69
|
+
| `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. |
|
|
70
|
+
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria do controle; `pill` arredonda as extremidades. |
|
|
71
|
+
| `disabled` | `boolean` | `false` | Esmaece e bloqueia o clique, preservando o estado visual. |
|
|
76
72
|
|
|
77
73
|
## ToggleGroup
|
|
78
74
|
|
|
@@ -82,101 +78,157 @@ relacionados. `type="single"` mantém um item ativo; `type="multiple"` aceita v
|
|
|
82
78
|
### Escolha única
|
|
83
79
|
|
|
84
80
|
```tsx preview
|
|
85
|
-
const [view, setView] = useState(
|
|
81
|
+
const [view, setView] = useState("sessions");
|
|
86
82
|
|
|
87
83
|
render(
|
|
88
|
-
<ToggleGroup
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
<ToggleGroup
|
|
85
|
+
type="single"
|
|
86
|
+
value={view}
|
|
87
|
+
onValueChange={(v) => v && setView(v)}
|
|
88
|
+
>
|
|
89
|
+
<Toggle value="overview">Visão geral</Toggle>
|
|
90
|
+
<Toggle value="sessions">Sessões</Toggle>
|
|
91
|
+
<Toggle value="skills">Habilidades</Toggle>
|
|
92
92
|
</ToggleGroup>,
|
|
93
|
-
)
|
|
93
|
+
);
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
### Escolha múltipla
|
|
97
97
|
|
|
98
98
|
```tsx preview
|
|
99
|
-
const [marks, setMarks] = useState([
|
|
99
|
+
const [marks, setMarks] = useState(["bold"]);
|
|
100
100
|
|
|
101
101
|
render(
|
|
102
102
|
<ToggleGroup type="multiple" value={marks} onValueChange={setMarks}>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
<Toggle value="bold" aria-label="Negrito">
|
|
104
|
+
<Bold />
|
|
105
|
+
</Toggle>
|
|
106
|
+
<Toggle value="italic" aria-label="Itálico">
|
|
107
|
+
<Italic />
|
|
108
|
+
</Toggle>
|
|
109
|
+
<Toggle value="underline" aria-label="Sublinhado">
|
|
110
|
+
<Underline />
|
|
111
|
+
</Toggle>
|
|
106
112
|
</ToggleGroup>,
|
|
107
|
-
)
|
|
113
|
+
);
|
|
108
114
|
```
|
|
109
115
|
|
|
110
116
|
### Itens só com ícone e tooltip
|
|
111
117
|
|
|
112
118
|
Quando o item mostra somente um ícone, mantenha o `aria-label` e acrescente uma dica visual. Envolva o
|
|
113
|
-
`
|
|
119
|
+
`Toggle` em `TooltipTrigger asChild`: o controle preserva o estado selecionado e seus atributos,
|
|
114
120
|
e a dica aparece sem criar outro botão. O `TooltipProvider` da raiz do aplicativo continua necessário.
|
|
115
121
|
|
|
116
122
|
```tsx preview
|
|
117
|
-
const [align, setAlign] = useState(
|
|
123
|
+
const [align, setAlign] = useState("left");
|
|
118
124
|
|
|
119
125
|
render(
|
|
120
|
-
<ToggleGroup
|
|
126
|
+
<ToggleGroup
|
|
127
|
+
type="single"
|
|
128
|
+
value={align}
|
|
129
|
+
onValueChange={(v) => v && setAlign(v)}
|
|
130
|
+
>
|
|
121
131
|
<Tooltip>
|
|
122
132
|
<TooltipTrigger asChild>
|
|
123
|
-
<
|
|
133
|
+
<Toggle value="left" aria-label="Alinhar à esquerda">
|
|
134
|
+
<AlignLeft />
|
|
135
|
+
</Toggle>
|
|
124
136
|
</TooltipTrigger>
|
|
125
137
|
<TooltipContent>Alinhar à esquerda</TooltipContent>
|
|
126
138
|
</Tooltip>
|
|
127
139
|
<Tooltip>
|
|
128
140
|
<TooltipTrigger asChild>
|
|
129
|
-
<
|
|
141
|
+
<Toggle value="center" aria-label="Centralizar">
|
|
142
|
+
<AlignCenter />
|
|
143
|
+
</Toggle>
|
|
130
144
|
</TooltipTrigger>
|
|
131
145
|
<TooltipContent>Centralizar</TooltipContent>
|
|
132
146
|
</Tooltip>
|
|
133
147
|
<Tooltip>
|
|
134
148
|
<TooltipTrigger asChild>
|
|
135
|
-
<
|
|
149
|
+
<Toggle value="right" aria-label="Alinhar à direita">
|
|
150
|
+
<AlignRight />
|
|
151
|
+
</Toggle>
|
|
136
152
|
</TooltipTrigger>
|
|
137
153
|
<TooltipContent>Alinhar à direita</TooltipContent>
|
|
138
154
|
</Tooltip>
|
|
139
155
|
</ToggleGroup>,
|
|
140
|
-
)
|
|
156
|
+
);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Relação entre os controles
|
|
160
|
+
|
|
161
|
+
`mode` segue a mesma gramática de `ButtonGroup`. `connected`, o padrão, une os controles e remove
|
|
162
|
+
bordas internas duplicadas; `spaced` preserva cada controle com um pequeno intervalo; `divided`
|
|
163
|
+
acrescenta divisores entre eles. `variant`, `size` e `shape` definidos no grupo chegam aos controles
|
|
164
|
+
por contexto.
|
|
165
|
+
|
|
166
|
+
```tsx preview
|
|
167
|
+
<ToggleGroup type="single" variant="outline" defaultValue="call">
|
|
168
|
+
<Toggle value="call">
|
|
169
|
+
<Phone /> Telefone
|
|
170
|
+
</Toggle>
|
|
171
|
+
<Toggle value="email">
|
|
172
|
+
<Mail /> E-mail
|
|
173
|
+
</Toggle>
|
|
174
|
+
<Toggle value="whatsapp">
|
|
175
|
+
<MessageCircle /> WhatsApp
|
|
176
|
+
</Toggle>
|
|
177
|
+
</ToggleGroup>
|
|
141
178
|
```
|
|
142
179
|
|
|
143
|
-
|
|
180
|
+
Use `mode="spaced"` quando cada controle precisar preservar sua própria borda e seu próprio raio.
|
|
181
|
+
Use `mode="divided"` quando a separação também precisar ficar explícita por divisores.
|
|
144
182
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
### Aparência segmentada
|
|
184
|
+
|
|
185
|
+
Use `mode="segmented"` para escolhas curtas que precisam permanecer visíveis dentro de uma
|
|
186
|
+
superfície compartilhada. O item ativo usa o mesmo fundo de uma superfície elevada; os demais
|
|
187
|
+
permanecem no fundo do grupo. Segmentos inativos vizinhos recebem um divisor sutil; o divisor
|
|
188
|
+
desaparece dos dois lados da seleção. Essa aparência escolhe um valor no contexto atual: para
|
|
189
|
+
navegar entre painéis, use Tabs.
|
|
149
190
|
|
|
150
191
|
```tsx preview
|
|
151
|
-
<ToggleGroup type="single"
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
192
|
+
<ToggleGroup type="single" mode="segmented" defaultValue="sessions">
|
|
193
|
+
<Toggle value="overview">
|
|
194
|
+
<LayoutGrid /> Visão geral
|
|
195
|
+
</Toggle>
|
|
196
|
+
<Toggle value="sessions">
|
|
197
|
+
<History /> Sessões
|
|
198
|
+
</Toggle>
|
|
199
|
+
<Toggle value="skills">
|
|
200
|
+
<Settings /> Habilidades
|
|
201
|
+
</Toggle>
|
|
155
202
|
</ToggleGroup>
|
|
156
203
|
```
|
|
157
204
|
|
|
158
|
-
|
|
159
|
-
|
|
205
|
+
`mode` trata a relação visual entre os controles. `variant` continua tratando cada `Toggle`; por
|
|
206
|
+
isso, a composição segmentada recomendada usa a variante padrão. O recuo fica dentro da altura da
|
|
207
|
+
escala de controles: o conjunto mantém `2rem`, `2.25rem` ou `2.5rem` conforme `size`.
|
|
208
|
+
|
|
209
|
+
### Distribuição e moldura
|
|
210
|
+
|
|
211
|
+
`distribution="content"`, o padrão, preserva a largura dos rótulos. Use `equal` para dividir toda a
|
|
212
|
+
largura disponível igualmente entre os controles. `framed` acrescenta borda e recuo estrutural sem
|
|
213
|
+
mudar o modo de agrupamento.
|
|
160
214
|
|
|
161
215
|
### Propriedades de ToggleGroup
|
|
162
216
|
|
|
163
|
-
| Propriedade
|
|
164
|
-
|
|
165
|
-
| `type`
|
|
166
|
-
| `value`
|
|
167
|
-
| `onValueChange` | `(value: string \| string[]) => void`
|
|
168
|
-
| `defaultValue`
|
|
169
|
-
| `variant`
|
|
170
|
-
| `size`
|
|
171
|
-
| `shape`
|
|
172
|
-
| `
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Tamanho do item quando o grupo não declara `size`; o valor do grupo prevalece. |
|
|
182
|
-
| `shape` | `'default' \| 'pill'` | shape do grupo | Geometria deste item; quando informada, prevalece sobre a do grupo. |
|
|
217
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
218
|
+
| --------------- | ----------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
|
|
219
|
+
| `type` | `'single' \| 'multiple'` | | Define seleção única (`string`) ou múltipla (`string[]`). |
|
|
220
|
+
| `value` | `string \| string[]` | | Seleção controlada; o tipo acompanha `type`. |
|
|
221
|
+
| `onValueChange` | `(value: string \| string[]) => void` | | Informa a nova seleção. No modo single, uma string vazia representa nenhum item ativo. |
|
|
222
|
+
| `defaultValue` | `string \| string[]` | | Seleção inicial no modo não controlado. |
|
|
223
|
+
| `variant` | `'default' \| 'outline'` | `'default'` | Tratamento visual repassado aos itens. `outline` aplica borda a cada item. |
|
|
224
|
+
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Tamanho repassado aos itens. |
|
|
225
|
+
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria do grupo e de suas extremidades. |
|
|
226
|
+
| `mode` | `'connected' \| 'spaced' \| 'divided' \| 'segmented'` | `'connected'` | Define como os controles se relacionam; `segmented` cria uma superfície compartilhada para a seleção. |
|
|
227
|
+
| `distribution` | `'content' \| 'equal'` | `'content'` | Preserva a largura dos rótulos ou reparte igualmente a largura disponível. |
|
|
228
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Organiza os controles e os divisores no eixo indicado. |
|
|
229
|
+
| `framed` | `boolean` | `false` | Acrescenta borda, raio e recuo ao grupo sem alterar o modo. |
|
|
230
|
+
|
|
231
|
+
Dentro de `ToggleGroup`, cada `Toggle` exige `value`. O grupo controla o estado selecionado; props
|
|
232
|
+
standalone como `pressed` e `onPressedChange` não participam dessa composição. `disabled`, `variant`,
|
|
233
|
+
No grupo, `variant` e `size` pertencem ao conjunto e chegam a todos os controles. `shape` no item
|
|
234
|
+
pode declarar uma pílula local dentro do grupo.
|
|
@@ -15,19 +15,30 @@ exemplos leem as variáveis ao vivo; troque o tema do app e a página acompanha.
|
|
|
15
15
|
|
|
16
16
|
```tsx preview
|
|
17
17
|
const SURFACES = [
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[
|
|
26
|
-
|
|
18
|
+
["background", "foreground", "O fundo da página e o texto padrão."],
|
|
19
|
+
["card", "card-foreground", "Superfície elevada (Surface, painéis)."],
|
|
20
|
+
[
|
|
21
|
+
"popover",
|
|
22
|
+
"popover-foreground",
|
|
23
|
+
"Superfície flutuante (menus, popovers, dialogs).",
|
|
24
|
+
],
|
|
25
|
+
["muted", "muted-foreground", "Fundo discreto e texto secundário."],
|
|
26
|
+
[
|
|
27
|
+
"accent",
|
|
28
|
+
"accent-foreground",
|
|
29
|
+
"Alias de compatibilidade para hover neutro.",
|
|
30
|
+
],
|
|
31
|
+
["secondary", "secondary-foreground", "Ação secundária."],
|
|
32
|
+
["primary", "primary-foreground", "A ação principal — alto contraste."],
|
|
33
|
+
["destructive", "destructive-foreground", "Ação destrutiva e erro."],
|
|
34
|
+
];
|
|
27
35
|
render(
|
|
28
36
|
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
29
37
|
{SURFACES.map(([token, fg, desc]) => (
|
|
30
|
-
<div
|
|
38
|
+
<div
|
|
39
|
+
key={token}
|
|
40
|
+
className="flex items-center gap-4 border-b border-border px-4 py-3 last:border-b-0"
|
|
41
|
+
>
|
|
31
42
|
<div
|
|
32
43
|
className="flex h-10 w-28 shrink-0 items-center justify-center rounded-md border border-border text-xs font-medium"
|
|
33
44
|
style={{ backgroundColor: `var(--${token})`, color: `var(--${fg})` }}
|
|
@@ -35,15 +46,75 @@ render(
|
|
|
35
46
|
Texto
|
|
36
47
|
</div>
|
|
37
48
|
<div className="min-w-0">
|
|
38
|
-
<div className="font-mono text-xs"
|
|
49
|
+
<div className="font-mono text-xs">
|
|
50
|
+
--{token} · --{fg}
|
|
51
|
+
</div>
|
|
39
52
|
<div className="text-xs text-muted-foreground">{desc}</div>
|
|
40
53
|
</div>
|
|
41
54
|
</div>
|
|
42
55
|
))}
|
|
43
56
|
</div>,
|
|
44
|
-
)
|
|
57
|
+
);
|
|
45
58
|
```
|
|
46
59
|
|
|
60
|
+
### Controles e estados neutros
|
|
61
|
+
|
|
62
|
+
Canvas, superfície elevada, controle e interação formam uma escala opaca. Um input não muda de
|
|
63
|
+
tom ao sair do canvas e entrar num card, porque `control` representa o resultado final — não uma
|
|
64
|
+
cor translúcida a ser misturada com o ancestral. Da mesma forma, hover, seleção e pressão usam
|
|
65
|
+
papéis diferentes e preservam a mesma ordem comportamental nos dois temas.
|
|
66
|
+
|
|
67
|
+
```tsx preview
|
|
68
|
+
const NEUTRAL_STATES = [
|
|
69
|
+
["control", "Controle em repouso."],
|
|
70
|
+
["interaction-hover", "Resposta transitória ao apontador ou foco de item."],
|
|
71
|
+
["interaction-selected", "Seleção ou expansão persistente."],
|
|
72
|
+
["interaction-pressed", "Pressão transitória de um controle neutro."],
|
|
73
|
+
];
|
|
74
|
+
render(
|
|
75
|
+
<div className="grid w-full grid-cols-2 gap-2">
|
|
76
|
+
{NEUTRAL_STATES.map(([token, desc]) => (
|
|
77
|
+
<div
|
|
78
|
+
key={token}
|
|
79
|
+
className="rounded-md border border-border p-3"
|
|
80
|
+
style={{ backgroundColor: `var(--${token})` }}
|
|
81
|
+
>
|
|
82
|
+
<div className="font-mono text-xs">--{token}</div>
|
|
83
|
+
<div className="mt-1 text-xs text-muted-foreground">{desc}</div>
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
</div>,
|
|
87
|
+
);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Ajuste de aparência
|
|
91
|
+
|
|
92
|
+
Os tokens neutros desta seção não são valores fixos: o tema os calcula a partir de quatro eixos,
|
|
93
|
+
declarados separadamente para o modo claro e para o modo escuro. Com os eixos no preset, o
|
|
94
|
+
resultado é o tema padrão. Uma aplicação que oferece preferências de aparência altera os eixos, e
|
|
95
|
+
toda a escala — superfícies, estados, bordas e texto secundário — se move em conjunto, sem perder a
|
|
96
|
+
ordem entre os papéis.
|
|
97
|
+
|
|
98
|
+
| Eixo | Custom property (`light` ou `dark`) | O que muda | Preset claro | Preset escuro |
|
|
99
|
+
| ----------------------- | ---------------------------------------- | ------------------------------------------------------ | ------------ | ------------- |
|
|
100
|
+
| Intensidade de cor | `--appearance-<modo>-color-intensity` | Quanto do matiz `--neutral-hue` aparece nos neutros | 0 | 0 |
|
|
101
|
+
| Contraste | `--appearance-<modo>-contrast` | Distância de hover, seleção, pressão, `muted` e bordas | 0 | 0 |
|
|
102
|
+
| Brilho das superfícies | `--appearance-<modo>-surface-brightness` | Luminosidade do canvas e das superfícies | 100 | 50 |
|
|
103
|
+
| Contraste entre camadas | `--appearance-<modo>-layer-contrast` | Distância entre canvas, controle e superfície elevada | 0 | 0 |
|
|
104
|
+
|
|
105
|
+
Cada eixo aceita um inteiro de 0 a 100. Os limites protegem o tema padrão: nenhum valor dessa faixa
|
|
106
|
+
reduz a separação entre os papéis nem o contraste do texto sobre as superfícies em repouso abaixo do
|
|
107
|
+
que o preset oferece. Por isso contraste e contraste entre camadas partem de zero e só aumentam, e o
|
|
108
|
+
texto secundário acompanha as superfícies quando o brilho muda.
|
|
109
|
+
|
|
110
|
+
A intensidade de cor tinge somente os neutros. `primary`, `destructive` e os contextos `info`,
|
|
111
|
+
`success`, `warning` e `danger` mantêm as próprias cores. O matiz aplicado vem de `--neutral-hue`,
|
|
112
|
+
que a aplicação define junto com a identidade.
|
|
113
|
+
|
|
114
|
+
Os nomes, os presets e a conversão de valores em custom properties ficam em
|
|
115
|
+
`@softize/opus/ui/appearance`. O passo a passo para aplicar e desfazer um ajuste está em
|
|
116
|
+
[Customização](/ui/customization). A ADR 0044 registra a decisão e os limites.
|
|
117
|
+
|
|
47
118
|
## Famílias contextuais
|
|
48
119
|
|
|
49
120
|
Os tokens `primary`, `secondary` e `destructive` acima permanecem fundações de superfície. A API
|
|
@@ -52,7 +123,7 @@ família oferece sólido, foreground, superfície sutil, ênfase e borda. Use as
|
|
|
52
123
|
`variant`; classes contextuais diretas ficam reservadas à implementação dos primitives.
|
|
53
124
|
|
|
54
125
|
```tsx preview
|
|
55
|
-
const CONTEXTS = [
|
|
126
|
+
const CONTEXTS = ["neutral", "primary", "info", "success", "warning", "danger"];
|
|
56
127
|
render(
|
|
57
128
|
<div className="grid w-full grid-cols-3 gap-2">
|
|
58
129
|
{CONTEXTS.map((context) => (
|
|
@@ -69,7 +140,7 @@ render(
|
|
|
69
140
|
</div>
|
|
70
141
|
))}
|
|
71
142
|
</div>,
|
|
72
|
-
)
|
|
143
|
+
);
|
|
73
144
|
```
|
|
74
145
|
|
|
75
146
|
## Linhas e foco
|
|
@@ -79,15 +150,21 @@ render(
|
|
|
79
150
|
|
|
80
151
|
```tsx preview
|
|
81
152
|
const LINES = [
|
|
82
|
-
[
|
|
83
|
-
[
|
|
84
|
-
[
|
|
85
|
-
]
|
|
153
|
+
["border", "Bordas de contêineres (cards, tabelas, separadores)."],
|
|
154
|
+
["input", "Borda de campos de formulário; o fundo usa control."],
|
|
155
|
+
["ring", "O anel de foco (focus-visible)."],
|
|
156
|
+
];
|
|
86
157
|
render(
|
|
87
158
|
<div className="w-full overflow-hidden rounded-lg border border-border">
|
|
88
159
|
{LINES.map(([token, desc]) => (
|
|
89
|
-
<div
|
|
90
|
-
|
|
160
|
+
<div
|
|
161
|
+
key={token}
|
|
162
|
+
className="flex items-center gap-4 border-b border-border px-4 py-3 last:border-b-0"
|
|
163
|
+
>
|
|
164
|
+
<div
|
|
165
|
+
className="h-10 w-28 shrink-0 rounded-md border-2"
|
|
166
|
+
style={{ borderColor: `var(--${token})` }}
|
|
167
|
+
/>
|
|
91
168
|
<div className="min-w-0">
|
|
92
169
|
<div className="font-mono text-xs">--{token}</div>
|
|
93
170
|
<div className="text-xs text-muted-foreground">{desc}</div>
|
|
@@ -95,7 +172,7 @@ render(
|
|
|
95
172
|
</div>
|
|
96
173
|
))}
|
|
97
174
|
</div>,
|
|
98
|
-
)
|
|
175
|
+
);
|
|
99
176
|
```
|
|
100
177
|
|
|
101
178
|
## Escala de elevação
|
|
@@ -107,16 +184,19 @@ render(
|
|
|
107
184
|
|
|
108
185
|
```tsx preview
|
|
109
186
|
const LEVELS = [
|
|
110
|
-
[
|
|
111
|
-
[
|
|
112
|
-
[
|
|
113
|
-
]
|
|
187
|
+
["sm", "Superfície em repouso."],
|
|
188
|
+
["md", "Flutuante intermediário."],
|
|
189
|
+
["lg", "Modal ou destaque forte."],
|
|
190
|
+
];
|
|
114
191
|
render(
|
|
115
192
|
<div className="flex flex-wrap gap-8 py-4">
|
|
116
193
|
{LEVELS.map(([level, desc]) => (
|
|
117
194
|
<div key={level} className="flex flex-col items-center gap-3">
|
|
118
195
|
<div
|
|
119
|
-
className={
|
|
196
|
+
className={
|
|
197
|
+
"flex h-24 w-40 items-center justify-center rounded-xl border border-border bg-card text-xs text-card-foreground shadow-" +
|
|
198
|
+
level
|
|
199
|
+
}
|
|
120
200
|
>
|
|
121
201
|
Texto
|
|
122
202
|
</div>
|
|
@@ -127,7 +207,7 @@ render(
|
|
|
127
207
|
</div>
|
|
128
208
|
))}
|
|
129
209
|
</div>,
|
|
130
|
-
)
|
|
210
|
+
);
|
|
131
211
|
```
|
|
132
212
|
|
|
133
213
|
## Escala relativa
|
|
@@ -172,7 +252,7 @@ render(
|
|
|
172
252
|
</p>
|
|
173
253
|
</div>
|
|
174
254
|
</div>,
|
|
175
|
-
)
|
|
255
|
+
);
|
|
176
256
|
```
|
|
177
257
|
|
|
178
258
|
Alterar `font-size` em `html` permanece válido quando a aplicação quer recalibrar **toda** a
|
|
@@ -190,15 +270,17 @@ o mecanismo para fazer `text-sm` se chamar `text-base`.
|
|
|
190
270
|
render(
|
|
191
271
|
<div className="flex flex-wrap gap-4">
|
|
192
272
|
{[
|
|
193
|
-
[
|
|
194
|
-
[
|
|
195
|
-
[
|
|
196
|
-
[
|
|
197
|
-
[
|
|
198
|
-
[
|
|
273
|
+
["rounded-xs", "radius − 0.375rem"],
|
|
274
|
+
["rounded-sm", "radius − 0.25rem"],
|
|
275
|
+
["rounded-md", "radius − 0.125rem"],
|
|
276
|
+
["rounded-lg", "radius (0.75rem)"],
|
|
277
|
+
["rounded-xl", "radius + 0.25rem"],
|
|
278
|
+
["rounded-2xl", "radius + 0.5rem"],
|
|
199
279
|
].map(([cls, calc]) => (
|
|
200
280
|
<div key={cls} className="flex items-center gap-3">
|
|
201
|
-
<div
|
|
281
|
+
<div
|
|
282
|
+
className={"h-12 w-12 border-2 border-foreground/30 bg-muted " + cls}
|
|
283
|
+
/>
|
|
202
284
|
<div>
|
|
203
285
|
<div className="font-mono text-xs">{cls}</div>
|
|
204
286
|
<div className="text-xs text-muted-foreground">{calc}</div>
|
|
@@ -206,7 +288,7 @@ render(
|
|
|
206
288
|
</div>
|
|
207
289
|
))}
|
|
208
290
|
</div>,
|
|
209
|
-
)
|
|
291
|
+
);
|
|
210
292
|
```
|
|
211
293
|
|
|
212
294
|
## Divergências declaradas vs shadcn
|
|
@@ -214,10 +296,12 @@ render(
|
|
|
214
296
|
> Qualquer diferença em relação ao shadcn que não esteja declarada aqui (e no `theme.css`) é uma
|
|
215
297
|
> divergência a sincronizar, conforme a skill `build-opus-ui`.
|
|
216
298
|
|
|
217
|
-
1. Formato HSL nos tokens (o upstream migrou para oklch), escolhido pela legibilidade
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
299
|
+
1. Formato HSL nos tokens (o upstream migrou para oklch), escolhido pela legibilidade.
|
|
300
|
+
2. Hierarquia opaca de superfícies e estados nos dois temas: canvas, card/popover, controle, hover,
|
|
301
|
+
seleção e pressão têm papéis próprios. No light, o canvas quase branco sustenta superfícies
|
|
302
|
+
brancas; no dark, cards e popovers permanecem acima do canvas. A ADR 0043 registra os valores e
|
|
303
|
+
as restrições de composição. A escala neutra é derivada dos eixos de aparência (ADR 0044); no
|
|
304
|
+
preset, os valores são os calibrados.
|
|
221
305
|
3. Radius mapeado (`xs` a `2xl`) preservando o sentido pré-v4 dos utilitários `rounded-*`. Esse
|
|
222
306
|
trecho derivado permanece monotônico quando a base muda; aplicações que também usam `3xl`
|
|
223
307
|
precisam revisar a transição para esse degrau nativo ao customizar `--radius`.
|
package/src/ui/docs/registry.tsx
CHANGED
|
@@ -49,6 +49,7 @@ import tokensMd from "./content/tokens.md?raw";
|
|
|
49
49
|
import semanticContextMd from "./content/semantic-context.md?raw";
|
|
50
50
|
import communicationMd from "./content/communication.md?raw";
|
|
51
51
|
import customizationMd from "./content/customization.md?raw";
|
|
52
|
+
import compositionsMd from "./content/compositions.md?raw";
|
|
52
53
|
|
|
53
54
|
// — Componentes —
|
|
54
55
|
import accordionMd from "./content/accordion.md?raw";
|
|
@@ -61,7 +62,6 @@ import dockMd from "./content/dock.md?raw";
|
|
|
61
62
|
import breadcrumbMd from "./content/breadcrumb.md?raw";
|
|
62
63
|
import buttonMd from "./content/button.md?raw";
|
|
63
64
|
import calendarMd from "./content/calendar.md?raw";
|
|
64
|
-
import cardMd from "./content/card.md?raw";
|
|
65
65
|
import metricCardMd from "./content/metric-card.md?raw";
|
|
66
66
|
import carouselMd from "./content/carousel.md?raw";
|
|
67
67
|
import askMd from "./content/ask.md?raw";
|
|
@@ -125,7 +125,7 @@ import actionTriggerMd from "./content/action-trigger.md?raw";
|
|
|
125
125
|
import actionSearchDialogMd from "./content/action-list-dialog.md?raw";
|
|
126
126
|
import pageMd from "./content/page.md?raw";
|
|
127
127
|
import presentationMd from "./content/presentation.md?raw";
|
|
128
|
-
import
|
|
128
|
+
import surfaceMd from "./content/surface.md?raw";
|
|
129
129
|
import paneMd from "./content/pane.md?raw";
|
|
130
130
|
import sidebarMd from "./content/sidebar.md?raw";
|
|
131
131
|
import splitMd from "./content/split.md?raw";
|
|
@@ -396,6 +396,11 @@ export const UI_SECTIONS: DocSection[] = [
|
|
|
396
396
|
title: "Comunicação",
|
|
397
397
|
render: doc(communicationMd),
|
|
398
398
|
},
|
|
399
|
+
{
|
|
400
|
+
slug: "compositions",
|
|
401
|
+
title: "Composições",
|
|
402
|
+
render: doc(compositionsMd),
|
|
403
|
+
},
|
|
399
404
|
{
|
|
400
405
|
slug: "customization",
|
|
401
406
|
title: "Customização",
|
|
@@ -454,9 +459,9 @@ export const UI_SECTIONS: DocSection[] = [
|
|
|
454
459
|
),
|
|
455
460
|
},
|
|
456
461
|
{
|
|
457
|
-
slug: "
|
|
458
|
-
title: "
|
|
459
|
-
render: pattern("
|
|
462
|
+
slug: "surface",
|
|
463
|
+
title: "Surface",
|
|
464
|
+
render: pattern("Surface", "surface", surfaceMd),
|
|
460
465
|
},
|
|
461
466
|
{
|
|
462
467
|
slug: "accordion",
|
|
@@ -468,7 +473,6 @@ export const UI_SECTIONS: DocSection[] = [
|
|
|
468
473
|
title: "Aspect Ratio",
|
|
469
474
|
render: comp("Aspect Ratio", "aspect-ratio", aspectRatioMd),
|
|
470
475
|
},
|
|
471
|
-
{ slug: "card", title: "Card", render: comp("Card", "card", cardMd) },
|
|
472
476
|
{
|
|
473
477
|
slug: "metric-card",
|
|
474
478
|
title: "Metric Card",
|