@softize/opus 22.2.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 +120 -1
- package/bin/cli.mjs +3 -9
- package/bin/lib/check.mjs +242 -49
- package/bin/lib/copy.mjs +17 -35
- package/bin/lib/gen-runner.mjs +2 -0
- package/bin/lib/gen-stubs.mjs +3 -1
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
- 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/code-style.md +1 -1
- 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/skills/create-opus-action/references/contract-and-binding.md +3 -3
- package/registry/templates/app/src/App.tsx +24 -12
- package/src/core/contracts.ts +1 -1
- package/src/core/runtime.ts +4 -37
- package/src/core/types.ts +5 -23
- package/src/design/index.ts +14 -3
- package/src/testing/index.ts +6 -26
- 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 +64 -14
- 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 +93 -70
- 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 +58 -88
- 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 +85 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/actions.md +6 -7
- 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/semantic-context.md +0 -1
- 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 +82 -83
- package/src/ui/docs/content/testing.md +2 -3
- package/src/ui/docs/content/toggle.md +120 -63
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +4 -6
- package/src/ui/drivers/react.tsx +4 -3
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +42 -46
- package/src/ui/theme.css +311 -64
- package/src/vite/design.ts +23 -21
- package/src/ui/components/patterns/content-header.tsx +0 -532
- 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 -276
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mídia de superfície (interno): a moldura que abre a linha do header — ADR 0023.
|
|
3
|
-
*
|
|
4
|
-
* Reusa `IconTile`; nenhuma família cria outra escala de molduras. O slot público de
|
|
5
|
-
* cada família (`CardMedia`, `DialogMedia`, …) delega para cá, escolhendo apenas o
|
|
6
|
-
* `data-slot` e a escala default.
|
|
7
|
-
*/
|
|
8
|
-
import type { ReactElement } from "react";
|
|
9
|
-
|
|
10
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
IconTile,
|
|
14
|
-
iconTileVariants,
|
|
15
|
-
type IconTileContext,
|
|
16
|
-
type IconTileSize,
|
|
17
|
-
type IconTileVariant,
|
|
18
|
-
} from "../primitives/icon-tile.tsx";
|
|
19
|
-
import { cn } from "../../lib/cn.ts";
|
|
20
|
-
|
|
21
|
-
export interface SurfaceMediaProps
|
|
22
|
-
extends Omit<React.ComponentProps<"span">, "children"> {
|
|
23
|
-
/** Escala da moldura; a família define o default. */
|
|
24
|
-
size?: IconTileSize;
|
|
25
|
-
context?: IconTileContext;
|
|
26
|
-
variant?: IconTileVariant;
|
|
27
|
-
/**
|
|
28
|
-
* Renderiza como o filho: para a marca que já tem visual próprio — um orbe, um
|
|
29
|
-
* logotipo — ocupar o slot sem ganhar a moldura por cima.
|
|
30
|
-
*/
|
|
31
|
-
asChild?: boolean;
|
|
32
|
-
children?: React.ReactNode;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function SurfaceMedia({
|
|
36
|
-
slot,
|
|
37
|
-
size = "sm",
|
|
38
|
-
context,
|
|
39
|
-
variant,
|
|
40
|
-
asChild = false,
|
|
41
|
-
className,
|
|
42
|
-
...props
|
|
43
|
-
}: SurfaceMediaProps & { slot: string }): ReactElement {
|
|
44
|
-
if (asChild) {
|
|
45
|
-
return (
|
|
46
|
-
<Slot
|
|
47
|
-
data-slot={slot}
|
|
48
|
-
className={cn(
|
|
49
|
-
iconTileVariants({ size, context: null, variant: null }),
|
|
50
|
-
// Sem fundo nem borda: quem compõe traz o próprio visual.
|
|
51
|
-
"bg-transparent",
|
|
52
|
-
"shrink-0",
|
|
53
|
-
className,
|
|
54
|
-
)}
|
|
55
|
-
{...props}
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return (
|
|
60
|
-
<IconTile
|
|
61
|
-
data-slot={slot}
|
|
62
|
-
size={size}
|
|
63
|
-
{...(context !== undefined ? { context } : {})}
|
|
64
|
-
{...(variant !== undefined ? { variant } : {})}
|
|
65
|
-
className={cn("shrink-0", className)}
|
|
66
|
-
{...props}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
-
import { cn } from "../../lib/cn.ts";
|
|
4
|
-
import {
|
|
5
|
-
SurfaceLayoutProvider,
|
|
6
|
-
surfaceActionsClasses,
|
|
7
|
-
surfaceDescriptionClasses,
|
|
8
|
-
surfaceHeaderRowClasses,
|
|
9
|
-
surfaceRegionClasses,
|
|
10
|
-
surfaceTitleClasses,
|
|
11
|
-
useSurfaceLayout,
|
|
12
|
-
type SurfaceLayout,
|
|
13
|
-
} from "../patterns/surface-anatomy.ts";
|
|
14
|
-
import { SurfaceHeader } from "../patterns/surface-header.tsx";
|
|
15
|
-
import {
|
|
16
|
-
SurfaceMedia,
|
|
17
|
-
type SurfaceMediaProps,
|
|
18
|
-
} from "../patterns/surface-media.tsx";
|
|
19
|
-
import { headingTags, type HeadingLevel } from "../patterns/heading-level.ts";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Card — a superfície da casa: SÓ a casca (borda + raio + bg). Divergência DECLARADA vs
|
|
23
|
-
* shadcn: FLAT (sem `shadow-sm`) e SEM `flex/gap/py` forçados. O padding mora no SLOT
|
|
24
|
-
* (Header/Body/Footer) ou no `className` do consumidor (box simples).
|
|
25
|
-
*
|
|
26
|
-
* Box simples: <Card className="p-4">…</Card>
|
|
27
|
-
* Estruturado: <Card><CardHeader>…</CardHeader><CardBody>…</CardBody></Card>
|
|
28
|
-
*
|
|
29
|
-
* `layout` é o pacote de anatomia da ADR 0023 e vale para a superfície inteira:
|
|
30
|
-
*
|
|
31
|
-
* contínuo (default): sem divisores; o ritmo separa as regiões.
|
|
32
|
-
* seccionado: regiões simétricas, com divisor entre as adjacentes.
|
|
33
|
-
*
|
|
34
|
-
* O divisor é derivado da presença das regiões — o consumidor não escreve `border-b`.
|
|
35
|
-
*/
|
|
36
|
-
export function Card({
|
|
37
|
-
className,
|
|
38
|
-
asChild = false,
|
|
39
|
-
layout = "continuous",
|
|
40
|
-
...props
|
|
41
|
-
}: React.ComponentProps<"div"> & {
|
|
42
|
-
/** Renderiza como o filho (Radix Slot) — pra card CLICÁVEL manter a semântica do filho
|
|
43
|
-
* (ex.: <button>) com a superfície do Card: `<Card asChild><button onClick>…</button></Card>`. */
|
|
44
|
-
asChild?: boolean;
|
|
45
|
-
/** Anatomia da superfície: `continuous` (default) ou `sectioned`. */
|
|
46
|
-
layout?: SurfaceLayout;
|
|
47
|
-
}): React.ReactElement {
|
|
48
|
-
const Comp = asChild ? Slot : "div";
|
|
49
|
-
return (
|
|
50
|
-
<SurfaceLayoutProvider value={layout}>
|
|
51
|
-
<Comp
|
|
52
|
-
data-slot="card"
|
|
53
|
-
data-layout={layout}
|
|
54
|
-
className={cn(
|
|
55
|
-
"rounded-xl border border-border bg-card text-card-foreground [--surface-background:var(--card)]",
|
|
56
|
-
// Seccionado: o divisor encosta na borda, então o raio precisa recortar.
|
|
57
|
-
layout === "sectioned" && "overflow-hidden",
|
|
58
|
-
className,
|
|
59
|
-
)}
|
|
60
|
-
{...props}
|
|
61
|
-
/>
|
|
62
|
-
</SurfaceLayoutProvider>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function CardHeader({
|
|
67
|
-
className,
|
|
68
|
-
children,
|
|
69
|
-
...props
|
|
70
|
-
}: React.ComponentProps<"div">): React.ReactElement {
|
|
71
|
-
const layout = useSurfaceLayout("card");
|
|
72
|
-
return (
|
|
73
|
-
<SurfaceHeader
|
|
74
|
-
name="CardHeader"
|
|
75
|
-
slot="card"
|
|
76
|
-
slots={{
|
|
77
|
-
media: CardMedia,
|
|
78
|
-
title: CardTitle,
|
|
79
|
-
description: CardDescription,
|
|
80
|
-
actions: CardAction,
|
|
81
|
-
}}
|
|
82
|
-
leadingPlacement="inline"
|
|
83
|
-
titleRequired={false}
|
|
84
|
-
rowClassName={surfaceHeaderRowClasses("card")}
|
|
85
|
-
className={cn(surfaceRegionClasses("card", layout, "header"), className)}
|
|
86
|
-
{...props}
|
|
87
|
-
>
|
|
88
|
-
{children}
|
|
89
|
-
</SurfaceHeader>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Moldura de ícone ou imagem que abre a linha do header. */
|
|
94
|
-
export function CardMedia(props: SurfaceMediaProps): React.ReactElement {
|
|
95
|
-
return <SurfaceMedia slot="card-media" {...props} />;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function CardTitle({
|
|
99
|
-
className,
|
|
100
|
-
level = 3,
|
|
101
|
-
asChild = false,
|
|
102
|
-
...props
|
|
103
|
-
}: React.ComponentProps<"h3"> & {
|
|
104
|
-
/** Hierarquia do documento (`h1`–`h6`). O tamanho vem do layout, nunca do nível. */
|
|
105
|
-
level?: HeadingLevel;
|
|
106
|
-
asChild?: boolean;
|
|
107
|
-
}): React.ReactElement {
|
|
108
|
-
const layout = useSurfaceLayout("card");
|
|
109
|
-
const Comp = asChild ? Slot : headingTags[level];
|
|
110
|
-
return (
|
|
111
|
-
<Comp
|
|
112
|
-
data-slot="card-title"
|
|
113
|
-
className={cn(surfaceTitleClasses("card", layout), className)}
|
|
114
|
-
{...props}
|
|
115
|
-
/>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function CardDescription({
|
|
120
|
-
className,
|
|
121
|
-
...props
|
|
122
|
-
}: React.ComponentProps<"p">): React.ReactElement {
|
|
123
|
-
return (
|
|
124
|
-
<p
|
|
125
|
-
data-slot="card-description"
|
|
126
|
-
className={cn(surfaceDescriptionClasses("card"), className)}
|
|
127
|
-
{...props}
|
|
128
|
-
/>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Ações do header — a família posiciona no extremo oposto ao título. */
|
|
133
|
-
export function CardAction({
|
|
134
|
-
className,
|
|
135
|
-
...props
|
|
136
|
-
}: React.ComponentProps<"div">): React.ReactElement {
|
|
137
|
-
return (
|
|
138
|
-
<div
|
|
139
|
-
data-slot="card-action"
|
|
140
|
-
className={cn(surfaceActionsClasses, className)}
|
|
141
|
-
{...props}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function CardBody({
|
|
147
|
-
className,
|
|
148
|
-
...props
|
|
149
|
-
}: React.ComponentProps<"div">): React.ReactElement {
|
|
150
|
-
const layout = useSurfaceLayout("card");
|
|
151
|
-
return (
|
|
152
|
-
<div
|
|
153
|
-
data-slot="card-body"
|
|
154
|
-
className={cn(surfaceRegionClasses("card", layout, "body"), className)}
|
|
155
|
-
{...props}
|
|
156
|
-
/>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** @deprecated Use CardBody. Alias mantido para migração; sai numa próxima versão major. */
|
|
161
|
-
export function CardContent({
|
|
162
|
-
className,
|
|
163
|
-
...props
|
|
164
|
-
}: React.ComponentProps<"div">): React.ReactElement {
|
|
165
|
-
const layout = useSurfaceLayout("card");
|
|
166
|
-
return (
|
|
167
|
-
<div
|
|
168
|
-
data-slot="card-content"
|
|
169
|
-
className={cn(surfaceRegionClasses("card", layout, "body"), className)}
|
|
170
|
-
{...props}
|
|
171
|
-
/>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function CardFooter({
|
|
176
|
-
className,
|
|
177
|
-
...props
|
|
178
|
-
}: React.ComponentProps<"div">): React.ReactElement {
|
|
179
|
-
const layout = useSurfaceLayout("card");
|
|
180
|
-
return (
|
|
181
|
-
<div
|
|
182
|
-
data-slot="card-footer"
|
|
183
|
-
className={cn(
|
|
184
|
-
"flex items-center justify-end gap-2",
|
|
185
|
-
surfaceRegionClasses("card", layout, "footer"),
|
|
186
|
-
className,
|
|
187
|
-
)}
|
|
188
|
-
{...props}
|
|
189
|
-
/>
|
|
190
|
-
);
|
|
191
|
-
}
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
## Caixa simples
|
|
2
|
-
|
|
3
|
-
O uso mais comum é a superfície em repouso: raio, borda e fundo, sem sombra. O `Card` não impõe
|
|
4
|
-
flex, gap nem padding, então o miolo continua seu — inclusive o `space-y` ou o grid que a tela já
|
|
5
|
-
usa. Nesse formato o recuo vem do `className`.
|
|
6
|
-
|
|
7
|
-
```tsx preview col
|
|
8
|
-
<Card className="p-4">
|
|
9
|
-
<p className="text-sm text-muted-foreground">3 veículos aguardando retirada.</p>
|
|
10
|
-
</Card>
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Quando a hierarquia realmente pedir elevação, declare a intensidade, por exemplo
|
|
14
|
-
`className="shadow-sm"`.
|
|
15
|
-
|
|
16
|
-
## Cabeçalho, corpo e rodapé
|
|
17
|
-
|
|
18
|
-
Quando o card tem estrutura, cada região vira um slot e traz o próprio recuo. `CardHeader`
|
|
19
|
-
reconhece quatro slots — `CardMedia`, `CardTitle`, `CardDescription` e `CardAction` — e monta a
|
|
20
|
-
linha sozinho: mídia, título e descrição de um lado, ações no extremo oposto. A ordem em que os
|
|
21
|
-
slots são escritos não altera a posição.
|
|
22
|
-
|
|
23
|
-
```tsx preview col
|
|
24
|
-
<Card>
|
|
25
|
-
<CardHeader>
|
|
26
|
-
<CardTitle>Oficina Centro</CardTitle>
|
|
27
|
-
<CardDescription>4 boxes · 2 consultores de plantão.</CardDescription>
|
|
28
|
-
</CardHeader>
|
|
29
|
-
<CardBody>
|
|
30
|
-
<p className="text-sm text-muted-foreground">Próximo horário livre hoje, às 14h.</p>
|
|
31
|
-
</CardBody>
|
|
32
|
-
<CardFooter>
|
|
33
|
-
<Button size="sm" variant="outline">Ver agenda</Button>
|
|
34
|
-
<Button size="sm">Agendar revisão</Button>
|
|
35
|
-
</CardFooter>
|
|
36
|
-
</Card>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
O rodapé alinha as ações no fim da linha. Um nó que não seja um desses quatro slots dentro do
|
|
40
|
-
`CardHeader` interrompe a renderização com um erro: a linha do cabeçalho pertence à família, e um
|
|
41
|
-
`div` solto ali devolveria à tela a decisão de posicionamento. Conteúdo livre vai para `CardBody`.
|
|
42
|
-
|
|
43
|
-
## Escolher entre contínuo e seccionado
|
|
44
|
-
|
|
45
|
-
`layout` declara a anatomia da superfície inteira e fica no root. Não é uma borda ligada ou
|
|
46
|
-
desligada: cada valor é um pacote de recuo, ritmo vertical, escala do título e divisores.
|
|
47
|
-
|
|
48
|
-
| Aspecto | `continuous` (padrão) | `sectioned` |
|
|
49
|
-
| --- | --- | --- |
|
|
50
|
-
| Divisores | nenhum | entre as regiões vizinhas |
|
|
51
|
-
| Ritmo vertical | o cabeçalho se aproxima do corpo; o espaço separa | regiões simétricas; o divisor separa |
|
|
52
|
-
| Recuo lateral | mais folgado, para leitura | o mesmo das demais superfícies de operação |
|
|
53
|
-
| Escala do título | um degrau acima | um degrau mais contida |
|
|
54
|
-
|
|
55
|
-
Use `continuous` no card de leitura, que apresenta um assunto e termina. Use `sectioned` quando o
|
|
56
|
-
card for uma superfície de operação — regiões com pesos diferentes, lista dentro do corpo, rodapé
|
|
57
|
-
com ações — ou quando ele precisar conviver com um [`Dialog`](/ui/dialog), um
|
|
58
|
-
[`Drawer`](/ui/drawer) ou um [`Pane`](/ui/pane) sem degrau visível entre eles.
|
|
59
|
-
|
|
60
|
-
```tsx preview col
|
|
61
|
-
render(
|
|
62
|
-
<div className="grid grid-cols-2 gap-4">
|
|
63
|
-
<Card>
|
|
64
|
-
<CardHeader>
|
|
65
|
-
<CardTitle>Contínuo</CardTitle>
|
|
66
|
-
<CardDescription>O espaço separa as regiões.</CardDescription>
|
|
67
|
-
</CardHeader>
|
|
68
|
-
<CardBody>
|
|
69
|
-
<p className="text-sm text-muted-foreground">Um assunto, lido de cima a baixo.</p>
|
|
70
|
-
</CardBody>
|
|
71
|
-
<CardFooter>
|
|
72
|
-
<Button size="sm" variant="outline">Abrir</Button>
|
|
73
|
-
</CardFooter>
|
|
74
|
-
</Card>
|
|
75
|
-
<Card layout="sectioned">
|
|
76
|
-
<CardHeader>
|
|
77
|
-
<CardTitle>Seccionado</CardTitle>
|
|
78
|
-
<CardDescription>O divisor separa as regiões.</CardDescription>
|
|
79
|
-
</CardHeader>
|
|
80
|
-
<CardBody>
|
|
81
|
-
<p className="text-sm text-muted-foreground">Regiões com pesos diferentes.</p>
|
|
82
|
-
</CardBody>
|
|
83
|
-
<CardFooter>
|
|
84
|
-
<Button size="sm" variant="outline">Abrir</Button>
|
|
85
|
-
</CardFooter>
|
|
86
|
-
</Card>
|
|
87
|
-
</div>,
|
|
88
|
-
);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Os divisores são derivados
|
|
92
|
-
|
|
93
|
-
Em `sectioned`, cada região desenha a própria linha quando existe uma região vizinha do outro lado.
|
|
94
|
-
Um cabeçalho sozinho não fecha nada, um rodapé que é a única região também não, e um cabeçalho
|
|
95
|
-
seguido direto de um rodapé produz uma linha só, em vez de duas encostadas.
|
|
96
|
-
|
|
97
|
-
Por isso, não escreva `border-b` no cabeçalho nem `border-t` no rodapé: a regra depende da presença
|
|
98
|
-
das regiões irmãs, que o componente conhece e a tela não. Uma borda escrita à mão ignora quais
|
|
99
|
-
regiões estão presentes: `border-b` num cabeçalho sozinho desenha uma linha encostada na borda do
|
|
100
|
-
card, e `border-t` no corpo duplica a linha que o cabeçalho já desenha. Da mesma forma,
|
|
101
|
-
`border-b-0` não apaga a linha derivada. Para um card sem divisores, use `layout="continuous"`.
|
|
102
|
-
|
|
103
|
-
## Mídia e ações no cabeçalho
|
|
104
|
-
|
|
105
|
-
`CardMedia` abre a linha com a moldura de ícone do Opus — o mesmo [`IconTile`](/ui/icon-tile), na
|
|
106
|
-
escala `sm`, com `context` e `variant` para declarar o significado da cor. `CardAction` fica no
|
|
107
|
-
extremo oposto ao título, sem precisar de classe de alinhamento.
|
|
108
|
-
|
|
109
|
-
```tsx preview col
|
|
110
|
-
<Card layout="sectioned">
|
|
111
|
-
<CardHeader>
|
|
112
|
-
<CardMedia context="primary">
|
|
113
|
-
<CarFront />
|
|
114
|
-
</CardMedia>
|
|
115
|
-
<CardTitle>Revisão dos 20.000 km</CardTitle>
|
|
116
|
-
<CardDescription>Agendada para 12 de maio, às 9h.</CardDescription>
|
|
117
|
-
<CardAction>
|
|
118
|
-
<Button size="sm" variant="outline">Reagendar</Button>
|
|
119
|
-
</CardAction>
|
|
120
|
-
</CardHeader>
|
|
121
|
-
<CardBody>
|
|
122
|
-
<p className="text-sm text-muted-foreground">Consultor responsável: Marina Alves.</p>
|
|
123
|
-
</CardBody>
|
|
124
|
-
</Card>
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Hierarquia do título
|
|
128
|
-
|
|
129
|
-
`CardTitle` é um heading. `level` escolhe a hierarquia do documento, de `1` a `6`, com `3` como
|
|
130
|
-
padrão — um card costuma ser uma seção dentro de uma página que já tem o `h1`. O tamanho visual não
|
|
131
|
-
vem daí: ele é decidido pela família e pelo `layout`, então corrigir o nível para acertar a
|
|
132
|
-
estrutura do documento não muda a aparência.
|
|
133
|
-
|
|
134
|
-
```tsx preview col
|
|
135
|
-
<Card>
|
|
136
|
-
<CardHeader>
|
|
137
|
-
<CardTitle level={2}>Qualidade da base</CardTitle>
|
|
138
|
-
<CardDescription>O nível muda a semântica; a escala continua a do layout.</CardDescription>
|
|
139
|
-
</CardHeader>
|
|
140
|
-
</Card>
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Quando o título leva a outra tela, coloque o link dentro dele. O heading continua sendo o
|
|
144
|
-
`CardTitle`, e o link fica no seu conteúdo:
|
|
145
|
-
|
|
146
|
-
```tsx preview col
|
|
147
|
-
<Card>
|
|
148
|
-
<CardHeader>
|
|
149
|
-
<CardTitle>
|
|
150
|
-
<a href="#" className="hover:underline">Revisão dos 20.000 km</a>
|
|
151
|
-
</CardTitle>
|
|
152
|
-
<CardDescription>Agendada para 12 de maio, às 9h.</CardDescription>
|
|
153
|
-
</CardHeader>
|
|
154
|
-
</Card>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Use `asChild` só quando outro componente já renderiza o próprio heading e precisa receber a escala do
|
|
158
|
-
título. Com `asChild`, o elemento é o filho: `level` deixa de valer, e um link passado ali substitui
|
|
159
|
-
o heading em vez de ficar dentro dele.
|
|
160
|
-
|
|
161
|
-
## A matriz de slots
|
|
162
|
-
|
|
163
|
-
As regiões são independentes: o cabeçalho existe sem corpo, o corpo existe sem cabeçalho e o rodapé
|
|
164
|
-
entra apenas quando há ações persistentes. Os divisores acompanham as regiões presentes, e o corpo
|
|
165
|
-
reassume o respiro de cima ou de baixo quando é a primeira ou a última região. No layout contínuo, o
|
|
166
|
-
cabeçalho só se aproxima da região seguinte quando ela existe: sozinho, ele fecha o card com o mesmo
|
|
167
|
-
respiro que tem em cima.
|
|
168
|
-
|
|
169
|
-
```tsx preview col
|
|
170
|
-
render(
|
|
171
|
-
<div className="grid grid-cols-2 gap-4">
|
|
172
|
-
<Card>
|
|
173
|
-
<CardHeader>
|
|
174
|
-
<CardMedia>
|
|
175
|
-
<UserRound />
|
|
176
|
-
</CardMedia>
|
|
177
|
-
<CardTitle>Contínuo com mídia</CardTitle>
|
|
178
|
-
</CardHeader>
|
|
179
|
-
<CardBody>
|
|
180
|
-
<p className="text-sm text-muted-foreground">
|
|
181
|
-
Sem rodapé, o corpo reassume o respiro de baixo.
|
|
182
|
-
</p>
|
|
183
|
-
</CardBody>
|
|
184
|
-
</Card>
|
|
185
|
-
<Card>
|
|
186
|
-
<CardBody>
|
|
187
|
-
<p className="text-sm text-muted-foreground">
|
|
188
|
-
Só corpo: o card vira uma caixa com o recuo da família.
|
|
189
|
-
</p>
|
|
190
|
-
</CardBody>
|
|
191
|
-
</Card>
|
|
192
|
-
<Card layout="sectioned">
|
|
193
|
-
<CardHeader>
|
|
194
|
-
<CardTitle>Seccionado sem corpo</CardTitle>
|
|
195
|
-
<CardAction>
|
|
196
|
-
<Button size="sm" variant="ghost">Editar</Button>
|
|
197
|
-
</CardAction>
|
|
198
|
-
</CardHeader>
|
|
199
|
-
<CardFooter>
|
|
200
|
-
<Button size="sm">Salvar</Button>
|
|
201
|
-
</CardFooter>
|
|
202
|
-
</Card>
|
|
203
|
-
<Card layout="sectioned">
|
|
204
|
-
<CardHeader>
|
|
205
|
-
<CardMedia context="success">
|
|
206
|
-
<ShieldCheck />
|
|
207
|
-
</CardMedia>
|
|
208
|
-
<CardTitle>Seccionado completo</CardTitle>
|
|
209
|
-
<CardDescription>Cabeçalho, corpo e rodapé.</CardDescription>
|
|
210
|
-
</CardHeader>
|
|
211
|
-
<CardBody>
|
|
212
|
-
<p className="text-sm text-muted-foreground">Cada fronteira ganha uma linha.</p>
|
|
213
|
-
</CardBody>
|
|
214
|
-
<CardFooter>
|
|
215
|
-
<Button size="sm" variant="outline">Descartar</Button>
|
|
216
|
-
<Button size="sm">Publicar</Button>
|
|
217
|
-
</CardFooter>
|
|
218
|
-
</Card>
|
|
219
|
-
</div>,
|
|
220
|
-
);
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
No layout contínuo, o respiro de baixo de um cabeçalho sem corpo nem rodapé segue o de cima. Essa
|
|
224
|
-
regra só vale quando o cabeçalho é a última região e tem precedência sobre o `className`, então um
|
|
225
|
-
`pb-*` no `CardHeader` não altera esse recuo. Para um recuo diferente, escolha o `layout`: no
|
|
226
|
-
`sectioned`, as regiões usam o recuo simétrico e mais contido das superfícies de operação.
|
|
227
|
-
|
|
228
|
-
## Propriedades de Card
|
|
229
|
-
|
|
230
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
231
|
-
|---|---|---|---|
|
|
232
|
-
| `layout` | `'continuous' \| 'sectioned'` | `'continuous'` | Anatomia da superfície inteira; os slots leem esse valor e não o recebem. |
|
|
233
|
-
| `asChild` | `boolean` | `false` | Renderiza o filho com a superfície do Card, por exemplo um `<button>` clicável inteiro. |
|
|
234
|
-
| `className` | `string` | | Compõe sobre a superfície. Sem slots, é aqui que entra o recuo da caixa. |
|
|
235
|
-
|
|
236
|
-
As demais propriedades são as propriedades nativas de `div`.
|
|
237
|
-
|
|
238
|
-
## Propriedades de CardHeader
|
|
239
|
-
|
|
240
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
241
|
-
|---|---|---|---|
|
|
242
|
-
| `children` | `ReactNode` | | Somente `CardMedia`, `CardTitle`, `CardDescription` e `CardAction`, no máximo um de cada. |
|
|
243
|
-
| `className` | `string` | | Ajusta a faixa superior sobre o recuo do layout. |
|
|
244
|
-
|
|
245
|
-
## Propriedades de CardMedia
|
|
246
|
-
|
|
247
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
248
|
-
|---|---|---|---|
|
|
249
|
-
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'sm'` | Tamanho da moldura e do glifo na escala compartilhada dos controles. |
|
|
250
|
-
| `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
|
|
251
|
-
| `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
|
|
252
|
-
| `className` | `string` | | Ajusta a moldura sem substituir a escala. |
|
|
253
|
-
| `asChild` | `boolean` | `false` | Cede o elemento para uma marca com visual próprio — um orbe, um logotipo — que ocupa o slot sem ganhar a moldura por cima. |
|
|
254
|
-
|
|
255
|
-
## Propriedades de CardTitle
|
|
256
|
-
|
|
257
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
258
|
-
|---|---|---|---|
|
|
259
|
-
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `3` | Hierarquia do documento. O tamanho vem do `layout`, nunca do nível. |
|
|
260
|
-
| `asChild` | `boolean` | `false` | Usa o filho como elemento do título, com a escala do layout. Reserve para um componente que já renderiza um heading; com `asChild`, `level` não se aplica. |
|
|
261
|
-
| `className` | `string` | | Ajusta o título. |
|
|
262
|
-
|
|
263
|
-
## Propriedades de CardDescription
|
|
264
|
-
|
|
265
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
266
|
-
|---|---|---|---|
|
|
267
|
-
| `children` | `ReactNode` | | Frase de apoio sob o título, dentro do cabeçalho. |
|
|
268
|
-
| `className` | `string` | | Ajusta a descrição. |
|
|
269
|
-
|
|
270
|
-
## Propriedades de CardAction
|
|
271
|
-
|
|
272
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
273
|
-
|---|---|---|---|
|
|
274
|
-
| `children` | `ReactNode` | | Controles no extremo oposto ao título, como um botão ou um menu. |
|
|
275
|
-
| `className` | `string` | | Ajusta o agrupamento das ações. |
|
|
276
|
-
|
|
277
|
-
## Propriedades de CardBody
|
|
278
|
-
|
|
279
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
280
|
-
|---|---|---|---|
|
|
281
|
-
| `children` | `ReactNode` | | Conteúdo principal do card. |
|
|
282
|
-
| `className` | `string` | | Ajusta a região sobre o recuo do layout. |
|
|
283
|
-
|
|
284
|
-
`CardContent` continua exportado como alias depreciado de `CardBody` e sai em uma próxima versão
|
|
285
|
-
maior. Código novo usa `CardBody`.
|
|
286
|
-
|
|
287
|
-
## Propriedades de CardFooter
|
|
288
|
-
|
|
289
|
-
| Propriedade | Tipo | Padrão | Descrição |
|
|
290
|
-
|---|---|---|---|
|
|
291
|
-
| `children` | `ReactNode` | | Ações persistentes, alinhadas no fim da linha. |
|
|
292
|
-
| `className` | `string` | | Ajusta o rodapé, inclusive a distribuição das ações. |
|