@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,83 +1,86 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import type { UiContext } from
|
|
3
|
-
import { cn } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { UiContext } from "../../../core/ui-context.ts";
|
|
3
|
+
import { cn } from "../../lib/cn.ts";
|
|
4
|
+
import { Skeleton } from "./skeleton.tsx";
|
|
5
|
+
import { Surface } from "../patterns/surface.tsx";
|
|
6
6
|
|
|
7
7
|
/** @deprecated Use `MetricCardContext`. */
|
|
8
|
-
export type MetricCardTone =
|
|
9
|
-
export type MetricCardContext = Exclude<UiContext,
|
|
8
|
+
export type MetricCardTone = "default" | "warning";
|
|
9
|
+
export type MetricCardContext = Exclude<UiContext, "primary">;
|
|
10
10
|
|
|
11
11
|
export interface MetricCardSecondaryMetric {
|
|
12
12
|
/** Nome curto da medida complementar. */
|
|
13
|
-
label: React.ReactNode
|
|
13
|
+
label: React.ReactNode;
|
|
14
14
|
/** Valor complementar já formatado pelo consumidor. */
|
|
15
|
-
value: React.ReactNode
|
|
15
|
+
value: React.ReactNode;
|
|
16
16
|
/** Controle complementar próprio, como uma explicação em tooltip. */
|
|
17
|
-
labelAction?: React.ReactNode
|
|
17
|
+
labelAction?: React.ReactNode;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const iconContextClasses: Record<MetricCardContext, string> = {
|
|
21
|
-
neutral:
|
|
22
|
-
info:
|
|
23
|
-
success:
|
|
24
|
-
warning:
|
|
25
|
-
danger:
|
|
26
|
-
}
|
|
21
|
+
neutral: "bg-context-neutral-subtle text-context-neutral-emphasis",
|
|
22
|
+
info: "bg-context-info-subtle text-context-info-emphasis",
|
|
23
|
+
success: "bg-context-success-subtle text-context-success-emphasis",
|
|
24
|
+
warning: "bg-context-warning-subtle text-context-warning-emphasis",
|
|
25
|
+
danger: "bg-context-danger-subtle text-context-danger-emphasis",
|
|
26
|
+
};
|
|
27
27
|
|
|
28
|
-
interface MetricCardBaseProps extends Omit<
|
|
28
|
+
interface MetricCardBaseProps extends Omit<
|
|
29
|
+
React.ComponentProps<"div">,
|
|
30
|
+
"children" | "title"
|
|
31
|
+
> {
|
|
29
32
|
/** Controle complementar exibido ao lado do rótulo, como uma explicação em tooltip. */
|
|
30
|
-
labelAction?: React.ReactNode
|
|
33
|
+
labelAction?: React.ReactNode;
|
|
31
34
|
/** Contexto que explica recorte, proporção ou significado do valor. */
|
|
32
|
-
description?: React.ReactNode
|
|
35
|
+
description?: React.ReactNode;
|
|
33
36
|
/** Ícone decorativo que identifica a natureza da medida. */
|
|
34
|
-
icon?: React.ReactNode
|
|
37
|
+
icon?: React.ReactNode;
|
|
35
38
|
/** Contexto semântico do ícone; não altera a superfície do card. */
|
|
36
|
-
context?: MetricCardContext
|
|
39
|
+
context?: MetricCardContext;
|
|
37
40
|
/** @deprecated Use `context`. Compatibilidade temporária da ADR 0006. */
|
|
38
|
-
tone?: MetricCardTone
|
|
41
|
+
tone?: MetricCardTone;
|
|
39
42
|
/** Ação relacionada diretamente à medida. */
|
|
40
|
-
action?: React.ReactNode
|
|
43
|
+
action?: React.ReactNode;
|
|
41
44
|
/** Medida complementar exibida como uma lâmina posterior sob o card principal. */
|
|
42
|
-
secondaryMetric?: MetricCardSecondaryMetric
|
|
45
|
+
secondaryMetric?: MetricCardSecondaryMetric;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
type MetricCardActivationProps =
|
|
46
49
|
| {
|
|
47
50
|
/** Indica se a medida controla o recorte atualmente aplicado. */
|
|
48
|
-
active: boolean
|
|
51
|
+
active: boolean;
|
|
49
52
|
/** Ativa a medida como recorte ou seleção. */
|
|
50
|
-
onActivate: () => void
|
|
53
|
+
onActivate: () => void;
|
|
51
54
|
/** Nome acessível da ativação, como “Filtrar por Agendados”. */
|
|
52
|
-
activationLabel: string
|
|
55
|
+
activationLabel: string;
|
|
53
56
|
/** Use `onActivate`; o clique genérico conflita com os controles internos do card. */
|
|
54
|
-
onClick?: never
|
|
57
|
+
onClick?: never;
|
|
55
58
|
}
|
|
56
59
|
| {
|
|
57
|
-
active?: never
|
|
58
|
-
onActivate?: never
|
|
59
|
-
activationLabel?: never
|
|
60
|
-
}
|
|
60
|
+
active?: never;
|
|
61
|
+
onActivate?: never;
|
|
62
|
+
activationLabel?: never;
|
|
63
|
+
};
|
|
61
64
|
|
|
62
65
|
export type MetricCardProps = MetricCardBaseProps &
|
|
63
66
|
MetricCardActivationProps &
|
|
64
67
|
(
|
|
65
68
|
| {
|
|
66
69
|
/** Nome curto da medida apresentada. */
|
|
67
|
-
label: React.ReactNode
|
|
70
|
+
label: React.ReactNode;
|
|
68
71
|
/** Valor já formatado pelo consumidor. */
|
|
69
|
-
value: React.ReactNode
|
|
70
|
-
loading?: false
|
|
72
|
+
value: React.ReactNode;
|
|
73
|
+
loading?: false;
|
|
71
74
|
}
|
|
72
75
|
| {
|
|
73
76
|
/** O rótulo pode ser omitido porque o placeholder ocupa toda a composição. */
|
|
74
|
-
label?: React.ReactNode
|
|
77
|
+
label?: React.ReactNode;
|
|
75
78
|
/** O valor ainda não está disponível durante o carregamento. */
|
|
76
|
-
value?: React.ReactNode
|
|
79
|
+
value?: React.ReactNode;
|
|
77
80
|
/** Substitui o conteúdo por placeholders preservando a geometria do card. */
|
|
78
|
-
loading: true
|
|
81
|
+
loading: true;
|
|
79
82
|
}
|
|
80
|
-
)
|
|
83
|
+
);
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
86
|
* MetricCard apresenta uma medida resumida. Formatação, cálculo e carregamento dos dados
|
|
@@ -101,19 +104,24 @@ export function MetricCard({
|
|
|
101
104
|
...props
|
|
102
105
|
}: MetricCardProps): React.ReactElement {
|
|
103
106
|
if (context !== undefined && tone !== undefined) {
|
|
104
|
-
throw new Error(
|
|
107
|
+
throw new Error("MetricCard não permite combinar context com tone legado.");
|
|
105
108
|
}
|
|
106
|
-
const hasIcon = icon !== undefined && icon !== null
|
|
107
|
-
const resolvedContext =
|
|
108
|
-
|
|
109
|
+
const hasIcon = icon !== undefined && icon !== null;
|
|
110
|
+
const resolvedContext =
|
|
111
|
+
context ?? (tone === "warning" ? "warning" : "neutral");
|
|
112
|
+
const interactive = onActivate !== undefined;
|
|
109
113
|
|
|
110
114
|
const primaryCard = (
|
|
111
|
-
<
|
|
115
|
+
<Surface
|
|
116
|
+
framed
|
|
117
|
+
elevation="raised"
|
|
112
118
|
data-slot="metric-card"
|
|
113
|
-
aria-busy={
|
|
119
|
+
aria-busy={
|
|
120
|
+
secondaryMetric === undefined ? loading || undefined : undefined
|
|
121
|
+
}
|
|
114
122
|
className={cn(
|
|
115
|
-
|
|
116
|
-
secondaryMetric !== undefined &&
|
|
123
|
+
"min-w-0 p-4",
|
|
124
|
+
secondaryMetric !== undefined && "relative z-10",
|
|
117
125
|
secondaryMetric === undefined && !interactive && className,
|
|
118
126
|
)}
|
|
119
127
|
{...(secondaryMetric === undefined && !interactive ? props : {})}
|
|
@@ -146,7 +154,7 @@ export function MetricCard({
|
|
|
146
154
|
data-context={resolvedContext}
|
|
147
155
|
aria-hidden="true"
|
|
148
156
|
className={cn(
|
|
149
|
-
|
|
157
|
+
"flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4",
|
|
150
158
|
iconContextClasses[resolvedContext],
|
|
151
159
|
)}
|
|
152
160
|
>
|
|
@@ -161,7 +169,10 @@ export function MetricCard({
|
|
|
161
169
|
{value}
|
|
162
170
|
</div>
|
|
163
171
|
{description !== undefined && (
|
|
164
|
-
<div
|
|
172
|
+
<div
|
|
173
|
+
data-slot="metric-card-description"
|
|
174
|
+
className="mt-1 text-xs text-muted-foreground"
|
|
175
|
+
>
|
|
165
176
|
{description}
|
|
166
177
|
</div>
|
|
167
178
|
)}
|
|
@@ -172,8 +183,8 @@ export function MetricCard({
|
|
|
172
183
|
)}
|
|
173
184
|
</>
|
|
174
185
|
)}
|
|
175
|
-
</
|
|
176
|
-
)
|
|
186
|
+
</Surface>
|
|
187
|
+
);
|
|
177
188
|
|
|
178
189
|
const metric =
|
|
179
190
|
secondaryMetric === undefined ? (
|
|
@@ -183,10 +194,12 @@ export function MetricCard({
|
|
|
183
194
|
{...(!interactive ? props : {})}
|
|
184
195
|
data-slot="metric-card-stack"
|
|
185
196
|
aria-busy={loading || undefined}
|
|
186
|
-
className={cn(
|
|
197
|
+
className={cn("relative isolate min-w-0", !interactive && className)}
|
|
187
198
|
>
|
|
188
199
|
{primaryCard}
|
|
189
|
-
<
|
|
200
|
+
<Surface
|
|
201
|
+
framed
|
|
202
|
+
elevation="raised"
|
|
190
203
|
data-slot="metric-card-secondary"
|
|
191
204
|
className="relative z-0 -mt-8 flex w-full min-w-0 items-center gap-1.5 bg-[color-mix(in_oklab,var(--card)_88%,black)] px-4 pt-9 pb-1"
|
|
192
205
|
>
|
|
@@ -208,18 +221,21 @@ export function MetricCard({
|
|
|
208
221
|
{secondaryMetric.label}
|
|
209
222
|
</div>
|
|
210
223
|
{secondaryMetric.labelAction !== undefined && (
|
|
211
|
-
<div
|
|
224
|
+
<div
|
|
225
|
+
data-slot="metric-card-secondary-label-action"
|
|
226
|
+
className="shrink-0"
|
|
227
|
+
>
|
|
212
228
|
{secondaryMetric.labelAction}
|
|
213
229
|
</div>
|
|
214
230
|
)}
|
|
215
231
|
</div>
|
|
216
232
|
</>
|
|
217
233
|
)}
|
|
218
|
-
</
|
|
234
|
+
</Surface>
|
|
219
235
|
</div>
|
|
220
|
-
)
|
|
236
|
+
);
|
|
221
237
|
|
|
222
|
-
if (!interactive) return metric
|
|
238
|
+
if (!interactive) return metric;
|
|
223
239
|
|
|
224
240
|
return (
|
|
225
241
|
<div
|
|
@@ -230,7 +246,7 @@ export function MetricCard({
|
|
|
230
246
|
// Ativa = a borda na cor primária, e só. Anel somaria uma segunda linha de
|
|
231
247
|
// outra cor à borda e, pintado para fora, sumiria no primeiro ancestral com
|
|
232
248
|
// `overflow-hidden`.
|
|
233
|
-
|
|
249
|
+
"relative isolate min-w-0 data-[active=true]:[&_[data-slot=metric-card]]:border-primary",
|
|
234
250
|
className,
|
|
235
251
|
)}
|
|
236
252
|
>
|
|
@@ -247,5 +263,5 @@ export function MetricCard({
|
|
|
247
263
|
{metric}
|
|
248
264
|
</div>
|
|
249
265
|
</div>
|
|
250
|
-
)
|
|
266
|
+
);
|
|
251
267
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { CircleIcon } from "lucide-react"
|
|
3
|
-
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { CircleIcon } from "lucide-react";
|
|
3
|
+
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
|
-
import { cn } from
|
|
5
|
+
import { cn } from "../../lib/cn.ts";
|
|
6
6
|
|
|
7
7
|
function RadioGroup({
|
|
8
8
|
className,
|
|
@@ -14,7 +14,7 @@ function RadioGroup({
|
|
|
14
14
|
className={cn("grid gap-3", className)}
|
|
15
15
|
{...props}
|
|
16
16
|
/>
|
|
17
|
-
)
|
|
17
|
+
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function RadioGroupItem({
|
|
@@ -25,8 +25,8 @@ function RadioGroupItem({
|
|
|
25
25
|
<RadioGroupPrimitive.Item
|
|
26
26
|
data-slot="radio-group-item"
|
|
27
27
|
className={cn(
|
|
28
|
-
"peer aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:
|
|
29
|
-
className
|
|
28
|
+
"peer aspect-square size-4 shrink-0 rounded-full border border-input bg-control text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40",
|
|
29
|
+
className,
|
|
30
30
|
)}
|
|
31
31
|
{...props}
|
|
32
32
|
>
|
|
@@ -37,7 +37,7 @@ function RadioGroupItem({
|
|
|
37
37
|
<CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-primary" />
|
|
38
38
|
</RadioGroupPrimitive.Indicator>
|
|
39
39
|
</RadioGroupPrimitive.Item>
|
|
40
|
-
)
|
|
40
|
+
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export { RadioGroup, RadioGroupItem }
|
|
43
|
+
export { RadioGroup, RadioGroupItem };
|