@softize/opus 23.0.0 → 24.0.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 +81 -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/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} +50 -19
- 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/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +100 -58
- package/src/ui/docs/content/drawer.md +63 -19
- 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 +34 -26
- package/src/ui/docs/content/pane.md +41 -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 +4 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Anatomia de superfície (interno): as medidas que
|
|
3
|
-
* compartilham —
|
|
2
|
+
* Anatomia de superfície (interno): as medidas que Surface, Dialog e Drawer
|
|
3
|
+
* compartilham — ADRs 0023, 0045 e 0047.
|
|
4
4
|
*
|
|
5
5
|
* Aqui mora o que antes era escrito seis vezes: recuo de cada região, ritmo vertical,
|
|
6
6
|
* escala do título, posição das ações e a regra dos divisores. As famílias públicas
|
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* - `continuous` — sem divisores. O ritmo separa: o header se aproxima do corpo e a
|
|
12
12
|
* última região — seja ela o header, o body ou o footer — fecha o espaço de baixo com
|
|
13
|
-
* o mesmo respiro do topo. É
|
|
13
|
+
* o mesmo respiro do topo. É a superfície de leitura.
|
|
14
14
|
* - `sectioned` — regiões simétricas, com divisor entre as que forem adjacentes. É o
|
|
15
15
|
* modal, o drawer e o painel.
|
|
16
16
|
*
|
|
17
17
|
* O recuo lateral é o mesmo nas três regiões da mesma superfície; é isso que a suíte
|
|
18
|
-
* transversal verifica.
|
|
19
|
-
* é mais folgado que um painel de operação.
|
|
18
|
+
* transversal verifica.
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
21
|
import {
|
|
@@ -28,7 +27,7 @@ import {
|
|
|
28
27
|
} from "react";
|
|
29
28
|
import { FillBoundary } from "./fill-region.ts";
|
|
30
29
|
|
|
31
|
-
export type SurfaceFamily = "
|
|
30
|
+
export type SurfaceFamily = "surface" | "dialog" | "drawer";
|
|
32
31
|
export type SurfaceLayout = "continuous" | "sectioned";
|
|
33
32
|
export type SurfaceRegion = "header" | "body" | "footer";
|
|
34
33
|
|
|
@@ -70,44 +69,56 @@ interface FamilyAnatomy {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
/**
|
|
73
|
-
* O recuo seccionado de
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* O recuo seccionado de Dialog e Drawer: 1rem, a densidade das superfícies de operação.
|
|
73
|
+
* A Surface usa o mesmo recuo lateral, para conviver com um drawer sem degrau visível, e
|
|
74
|
+
* faixas de header e footer mais baixas.
|
|
76
75
|
*/
|
|
77
76
|
const sectionedRegions: RegionAnatomy = {
|
|
78
77
|
header: "p-4",
|
|
79
|
-
body: "
|
|
78
|
+
body: "px-4 py-6",
|
|
80
79
|
footer: "p-4",
|
|
81
80
|
};
|
|
82
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Ritmo entre blocos diretos do conteúdo; relações internas pertencem a um wrapper filho.
|
|
84
|
+
* Os blocos não encolhem: o body é quem rola quando a família tem altura limitada.
|
|
85
|
+
*/
|
|
86
|
+
const structuralBodyRhythm = "flex flex-col gap-6 *:shrink-0";
|
|
87
|
+
|
|
83
88
|
export const surfaceAnatomy: Record<SurfaceFamily, FamilyAnatomy> = {
|
|
84
|
-
|
|
89
|
+
// Medidas da Surface delimitada (com moldura ou elevação). Sem delimitação, as regiões
|
|
90
|
+
// não recebem recuo: a superfície flui alinhada ao hospedeiro (ADR 0045).
|
|
91
|
+
surface: {
|
|
85
92
|
regions: {
|
|
86
|
-
// O
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// do que em cima.
|
|
93
|
+
// O espaço entre regiões soma o ritmo estrutural. Quando o header fica sozinho, ele
|
|
94
|
+
// reassume o respiro compacto da faixa; quando o body abre ou fecha a superfície,
|
|
95
|
+
// recebe o respiro inteiro do conteúdo.
|
|
90
96
|
continuous: {
|
|
91
|
-
header: "px-
|
|
92
|
-
body: "px-
|
|
93
|
-
footer: "px-
|
|
97
|
+
header: "px-4 pt-4 pb-6 [&:last-child]:pb-4",
|
|
98
|
+
body: "px-4 pt-0 pb-6 [&:first-child]:pt-6",
|
|
99
|
+
footer: "px-4 pt-0 pb-4 [&:first-child]:pt-4",
|
|
100
|
+
},
|
|
101
|
+
sectioned: {
|
|
102
|
+
header: "px-4 py-3",
|
|
103
|
+
body: "px-4 py-6",
|
|
104
|
+
footer: "px-4 py-3",
|
|
94
105
|
},
|
|
95
|
-
sectioned: sectionedRegions,
|
|
96
106
|
},
|
|
107
|
+
// Uma escala só: o nível 1 troca a apresentação inteira (ADR 0038), nunca o layout.
|
|
97
108
|
title: {
|
|
98
|
-
continuous: "text-
|
|
99
|
-
sectioned: "text-sm font-semibold
|
|
109
|
+
continuous: "text-sm font-semibold",
|
|
110
|
+
sectioned: "text-sm font-semibold",
|
|
100
111
|
},
|
|
101
|
-
description: "text-
|
|
102
|
-
headerGap: "gap-
|
|
112
|
+
description: "mt-0.5 text-xs text-muted-foreground",
|
|
113
|
+
headerGap: "gap-2",
|
|
103
114
|
footerBand: "",
|
|
104
115
|
},
|
|
105
116
|
dialog: {
|
|
106
117
|
regions: {
|
|
107
118
|
continuous: {
|
|
108
|
-
header: "p-4 pb-
|
|
109
|
-
body: "
|
|
110
|
-
footer: "p-4 pt-
|
|
119
|
+
header: "p-4 pb-3 [&:last-child]:pb-4",
|
|
120
|
+
body: "px-4 py-3 [&:last-child]:pb-6 [&:first-child]:pt-6",
|
|
121
|
+
footer: "p-4 pt-3",
|
|
111
122
|
},
|
|
112
123
|
sectioned: sectionedRegions,
|
|
113
124
|
},
|
|
@@ -122,9 +133,9 @@ export const surfaceAnatomy: Record<SurfaceFamily, FamilyAnatomy> = {
|
|
|
122
133
|
drawer: {
|
|
123
134
|
regions: {
|
|
124
135
|
continuous: {
|
|
125
|
-
header: "p-4 pb-
|
|
126
|
-
body: "
|
|
127
|
-
footer: "p-4 pt-
|
|
136
|
+
header: "p-4 pb-3 [&:last-child]:pb-4",
|
|
137
|
+
body: "px-4 py-3 [&:last-child]:pb-6 [&:first-child]:pt-6",
|
|
138
|
+
footer: "p-4 pt-3",
|
|
128
139
|
},
|
|
129
140
|
sectioned: sectionedRegions,
|
|
130
141
|
},
|
|
@@ -136,29 +147,6 @@ export const surfaceAnatomy: Record<SurfaceFamily, FamilyAnatomy> = {
|
|
|
136
147
|
headerGap: "gap-4",
|
|
137
148
|
footerBand: "bg-muted/20 dark:bg-input/20",
|
|
138
149
|
},
|
|
139
|
-
pane: {
|
|
140
|
-
regions: {
|
|
141
|
-
continuous: {
|
|
142
|
-
header: "flex shrink-0 items-center px-3 pt-3 pb-2 [&:last-child]:pb-3",
|
|
143
|
-
body: "p-3 [&:first-child]:pt-3",
|
|
144
|
-
footer: "px-3 pt-2 pb-3",
|
|
145
|
-
},
|
|
146
|
-
sectioned: {
|
|
147
|
-
// A faixa tem a altura da barra do shell; `flex items-center` centraliza a linha
|
|
148
|
-
// dentro dela, e `shrink-0` impede que um corpo longo comprima a faixa.
|
|
149
|
-
header: "flex h-[3.75rem] shrink-0 items-center px-3",
|
|
150
|
-
body: "p-3",
|
|
151
|
-
footer: "px-3 py-2",
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
title: {
|
|
155
|
-
continuous: "text-sm font-semibold leading-none",
|
|
156
|
-
sectioned: "text-sm font-semibold leading-none",
|
|
157
|
-
},
|
|
158
|
-
description: "text-xs text-muted-foreground",
|
|
159
|
-
headerGap: "gap-2",
|
|
160
|
-
footerBand: "",
|
|
161
|
-
},
|
|
162
150
|
};
|
|
163
151
|
|
|
164
152
|
/** Classes da região, já com o divisor que o layout deriva da vizinhança. */
|
|
@@ -166,14 +154,22 @@ export function surfaceRegionClasses(
|
|
|
166
154
|
family: SurfaceFamily,
|
|
167
155
|
layout: SurfaceLayout,
|
|
168
156
|
region: SurfaceRegion,
|
|
157
|
+
{ flush = false }: { flush?: boolean } = {},
|
|
169
158
|
): string {
|
|
170
159
|
const anatomy = surfaceAnatomy[family];
|
|
171
|
-
const base =
|
|
172
|
-
|
|
173
|
-
|
|
160
|
+
const base =
|
|
161
|
+
region === "body" && flush
|
|
162
|
+
? structuralBodyRhythm
|
|
163
|
+
: anatomy.regions[layout][region];
|
|
164
|
+
const regionClasses =
|
|
165
|
+
region === "body" && !flush ? `${base} ${structuralBodyRhythm}` : base;
|
|
166
|
+
if (layout !== "sectioned") return regionClasses;
|
|
167
|
+
if (region === "header") return `${regionClasses} ${headerDivider}`;
|
|
174
168
|
if (region === "footer")
|
|
175
|
-
return [
|
|
176
|
-
|
|
169
|
+
return [regionClasses, footerDivider, anatomy.footerBand]
|
|
170
|
+
.filter(Boolean)
|
|
171
|
+
.join(" ");
|
|
172
|
+
return regionClasses;
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
/** Escala visual do título — o nível do heading vem da composição, nunca daqui. */
|
|
@@ -191,8 +187,7 @@ export function surfaceDescriptionClasses(family: SurfaceFamily): string {
|
|
|
191
187
|
/**
|
|
192
188
|
* A linha do header: mídia, heading que absorve a folga, ações no extremo oposto.
|
|
193
189
|
*
|
|
194
|
-
* `flex-1`
|
|
195
|
-
* barra —, a linha vira um item que se mede pelo conteúdo e as ações param no meio.
|
|
190
|
+
* `flex-1` mantém o heading como região elástica e as ações no extremo oposto.
|
|
196
191
|
*/
|
|
197
192
|
export function surfaceHeaderRowClasses(family: SurfaceFamily): string {
|
|
198
193
|
return `flex min-w-0 flex-1 items-center ${surfaceAnatomy[family].headerGap}`;
|
|
@@ -201,6 +196,22 @@ export function surfaceHeaderRowClasses(family: SurfaceFamily): string {
|
|
|
201
196
|
/** Ações do header e do footer: fora do fluxo de compressão, na escala densa. */
|
|
202
197
|
export const surfaceActionsClasses = "flex shrink-0 items-center gap-2";
|
|
203
198
|
|
|
199
|
+
/**
|
|
200
|
+
* A `legend` de uma Surface `fieldset` delimitada (ADR 0045).
|
|
201
|
+
*
|
|
202
|
+
* - `notch`: moldura contínua. A legend nativa ocupa a borda, afastada do canto pelo
|
|
203
|
+
* mesmo recuo lateral das regiões; a região seguinte recebe o intervalo que a legend
|
|
204
|
+
* não tem como declarar.
|
|
205
|
+
* - `band`: a legend é a faixa do header dentro da superfície. Uma legend só ocupa a
|
|
206
|
+
* largura inteira fora do fluxo de borda, e a região seguinte precisa limpar o float.
|
|
207
|
+
*/
|
|
208
|
+
export const surfaceLegendClasses = {
|
|
209
|
+
notch: "ml-2 w-auto px-2",
|
|
210
|
+
afterNotch: "pt-3",
|
|
211
|
+
band: "float-left w-full",
|
|
212
|
+
afterBand: "clear-left",
|
|
213
|
+
} as const;
|
|
214
|
+
|
|
204
215
|
/**
|
|
205
216
|
* O layout é do root e chega aos slots por contexto — ADR 0023. Slot não recebe
|
|
206
217
|
* `layout`: o divisor depende de quem está do outro lado, então a decisão precisa valer
|
|
@@ -230,10 +241,9 @@ export function SurfaceLayoutProvider({
|
|
|
230
241
|
}
|
|
231
242
|
|
|
232
243
|
const familyDefaultLayout: Record<SurfaceFamily, SurfaceLayout> = {
|
|
233
|
-
|
|
244
|
+
surface: "continuous",
|
|
234
245
|
dialog: "sectioned",
|
|
235
246
|
drawer: "sectioned",
|
|
236
|
-
pane: "sectioned",
|
|
237
247
|
};
|
|
238
248
|
|
|
239
249
|
/** O layout vigente: o declarado pelo root da superfície, ou o default da família. */
|
|
@@ -45,7 +45,6 @@ export function SurfaceAssistant({
|
|
|
45
45
|
key="surface-content"
|
|
46
46
|
id="surface-content"
|
|
47
47
|
grow
|
|
48
|
-
inset="none"
|
|
49
48
|
className={cn("h-full", contentClassName)}
|
|
50
49
|
>
|
|
51
50
|
{children}
|
|
@@ -57,13 +56,12 @@ export function SurfaceAssistant({
|
|
|
57
56
|
initialSize={initialSize}
|
|
58
57
|
minSize={minSize}
|
|
59
58
|
maxSize={maxSize}
|
|
60
|
-
|
|
61
|
-
className={cn("h-full bg-background", assistantClassName)}
|
|
59
|
+
className={cn("h-full", assistantClassName)}
|
|
62
60
|
>
|
|
63
61
|
<aside
|
|
64
62
|
data-slot="surface-assistant-panel"
|
|
65
63
|
aria-label={assistantLabel}
|
|
66
|
-
className="h-full min-w-0 overflow-hidden"
|
|
64
|
+
className="h-full min-w-0 overflow-hidden bg-background"
|
|
67
65
|
>
|
|
68
66
|
{assistant}
|
|
69
67
|
</aside>
|