@softize/opus 21.0.0 → 22.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 +57 -0
- package/bin/lib/check.mjs +31 -28
- package/docs/adr/0004-page-content-state-is-composed.md +3 -2
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +3 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +23 -14
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
- package/docs/adr/0016-list-collection-header-belongs-to-content.md +13 -8
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +27 -26
- package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +1 -0
- package/docs/adr/0029-page-header-is-the-single-page-introduction.md +71 -0
- package/docs/adr/0030-framed-content-can-use-sectioned-anatomy.md +89 -0
- package/docs/adr/0031-detail-dividers-own-the-tabular-label-treatment.md +61 -0
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +59 -0
- package/docs/adr/0033-drawer-uses-an-inset-structural-frame.md +72 -0
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +60 -0
- package/docs/adr/0035-button-group-separates-frame-relationship-and-distribution.md +63 -0
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +58 -0
- package/docs/ownership-vs-shadcn-lock.md +5 -0
- package/docs/radius-scale.md +5 -2
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +9 -6
- package/registry/skills/build-opus-ui/references/evaluations.md +5 -2
- package/registry/skills/build-opus-ui/references/ui-patterns.md +14 -16
- package/src/queue/drivers/bullmq.ts +15 -2
- package/src/ui/components/patterns/content-header.tsx +51 -13
- package/src/ui/components/patterns/list.tsx +51 -33
- package/src/ui/components/patterns/page.tsx +139 -251
- package/src/ui/components/patterns/presentation.tsx +2 -6
- package/src/ui/components/primitives/button-group.tsx +80 -8
- package/src/ui/components/primitives/button.tsx +85 -60
- package/src/ui/components/primitives/detail.tsx +63 -57
- package/src/ui/components/primitives/dialog.tsx +8 -11
- package/src/ui/components/primitives/drawer.tsx +16 -12
- package/src/ui/components/primitives/tabs.tsx +123 -54
- package/src/ui/docs/content/button.md +55 -29
- package/src/ui/docs/content/content.md +61 -36
- package/src/ui/docs/content/detail.md +28 -19
- package/src/ui/docs/content/drawer.md +53 -47
- package/src/ui/docs/content/input.md +51 -35
- package/src/ui/docs/content/page.md +84 -95
- package/src/ui/docs/content/pane.md +83 -63
- package/src/ui/docs/content/presentation.md +20 -20
- package/src/ui/docs/content/tabs.md +107 -36
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +0 -1
|
@@ -17,10 +17,7 @@ import {
|
|
|
17
17
|
SurfaceMedia,
|
|
18
18
|
type SurfaceMediaProps,
|
|
19
19
|
} from "../patterns/surface-media.tsx";
|
|
20
|
-
import {
|
|
21
|
-
CloseButton,
|
|
22
|
-
type CloseButtonSize,
|
|
23
|
-
} from "./close-button.tsx";
|
|
20
|
+
import { CloseButton, type CloseButtonSize } from "./close-button.tsx";
|
|
24
21
|
import { overlayClasses, preventDismissWhen } from "./dialog.tsx";
|
|
25
22
|
|
|
26
23
|
interface DrawerContentContextValue {
|
|
@@ -117,15 +114,15 @@ function DrawerContent({
|
|
|
117
114
|
data-slot="drawer-content"
|
|
118
115
|
data-layout={layout}
|
|
119
116
|
className={cn(
|
|
120
|
-
"fixed z-50 flex flex-col bg-background shadow-lg transition ease-in-out [--surface-background:var(--background)] data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
117
|
+
"fixed z-50 flex flex-col overflow-hidden rounded-xl border bg-background shadow-lg transition ease-in-out [--surface-background:var(--background)] data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
121
118
|
side === "right" &&
|
|
122
|
-
"inset-y-
|
|
119
|
+
"inset-y-1 right-1 h-[calc(100%-0.5rem)] w-3/4 max-w-sm data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
|
123
120
|
side === "left" &&
|
|
124
|
-
"inset-y-
|
|
121
|
+
"inset-y-1 left-1 h-[calc(100%-0.5rem)] w-3/4 max-w-sm data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
|
|
125
122
|
side === "top" &&
|
|
126
|
-
"inset-x-
|
|
123
|
+
"inset-x-1 top-1 h-auto data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
127
124
|
side === "bottom" &&
|
|
128
|
-
"inset-x-
|
|
125
|
+
"inset-x-1 bottom-1 h-auto data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
129
126
|
className,
|
|
130
127
|
)}
|
|
131
128
|
aria-describedby={ariaDescribedBy}
|
|
@@ -189,7 +186,10 @@ function DrawerMedia(props: SurfaceMediaProps) {
|
|
|
189
186
|
}
|
|
190
187
|
|
|
191
188
|
/** Região introdutória do header — o voltar de um fluxo modal encadeado. */
|
|
192
|
-
function DrawerNavigation({
|
|
189
|
+
function DrawerNavigation({
|
|
190
|
+
className,
|
|
191
|
+
...props
|
|
192
|
+
}: React.ComponentProps<"div">) {
|
|
193
193
|
return (
|
|
194
194
|
<div
|
|
195
195
|
data-slot="drawer-navigation-content"
|
|
@@ -210,7 +210,11 @@ function DrawerActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
210
210
|
);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function DrawerBody({
|
|
213
|
+
function DrawerBody({
|
|
214
|
+
className,
|
|
215
|
+
style,
|
|
216
|
+
...props
|
|
217
|
+
}: React.ComponentProps<"div">) {
|
|
214
218
|
const layout = useSurfaceLayout("drawer");
|
|
215
219
|
return (
|
|
216
220
|
<div
|
|
@@ -220,7 +224,7 @@ function DrawerBody({ className, style, ...props }: React.ComponentProps<"div">)
|
|
|
220
224
|
surfaceRegionClasses("drawer", layout, "body"),
|
|
221
225
|
className,
|
|
222
226
|
)}
|
|
223
|
-
style={
|
|
227
|
+
style={style}
|
|
224
228
|
{...props}
|
|
225
229
|
/>
|
|
226
230
|
);
|
|
@@ -1,43 +1,65 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
|
-
import { cn } from
|
|
6
|
-
import { controlHeight, type ControlSize } from
|
|
5
|
+
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import { controlHeight, type ControlSize } from "./control.ts";
|
|
7
7
|
|
|
8
8
|
// Divergência da casa (ejetado): o Tabs ganhou `size` — Button e Select têm `sm`, o Tabs
|
|
9
9
|
// não tinha, e uma fileira densa ficava com o segmento 0.25rem mais alto
|
|
10
10
|
// que os irmãos. O tamanho flui pra TabsList via contexto (a altura é dela) e a medida é a
|
|
11
11
|
// da escala de control.ts.
|
|
12
|
-
type TabsSize = Extract<ControlSize, "default" | "sm"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
12
|
+
type TabsSize = Extract<ControlSize, "default" | "sm">;
|
|
13
|
+
type TabsDistribution = "content" | "equal";
|
|
14
|
+
const TabsSizeContext = React.createContext<TabsSize>("default");
|
|
15
|
+
const TabsDistributionContext =
|
|
16
|
+
React.createContext<TabsDistribution>("content");
|
|
17
|
+
const TabsFramedContext = React.createContext(false);
|
|
18
|
+
type TabsOrientation = "horizontal" | "vertical";
|
|
19
|
+
const TabsOrientationContext =
|
|
20
|
+
React.createContext<TabsOrientation>("horizontal");
|
|
21
|
+
const TabsListVariantContext = React.createContext<"default" | "line">(
|
|
22
|
+
"default",
|
|
23
|
+
);
|
|
17
24
|
|
|
18
25
|
function Tabs({
|
|
19
26
|
className,
|
|
20
27
|
orientation = "horizontal",
|
|
21
28
|
size = "default",
|
|
29
|
+
distribution = "content",
|
|
30
|
+
framed = false,
|
|
22
31
|
...props
|
|
23
|
-
}: React.ComponentProps<typeof TabsPrimitive.Root> & {
|
|
32
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root> & {
|
|
33
|
+
size?: TabsSize;
|
|
34
|
+
/** Distribui os gatilhos pela largura do conjunto ou preserva a largura do conteúdo. */
|
|
35
|
+
distribution?: TabsDistribution;
|
|
36
|
+
/** Delimita a composição sem criar uma nova superfície. */
|
|
37
|
+
framed?: boolean;
|
|
38
|
+
}) {
|
|
24
39
|
return (
|
|
25
40
|
<TabsOrientationContext.Provider value={orientation}>
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
<TabsDistributionContext.Provider value={distribution}>
|
|
42
|
+
<TabsFramedContext.Provider value={framed}>
|
|
43
|
+
<TabsSizeContext.Provider value={size}>
|
|
44
|
+
<TabsPrimitive.Root
|
|
45
|
+
data-slot="tabs"
|
|
46
|
+
data-orientation={orientation}
|
|
47
|
+
data-size={size}
|
|
48
|
+
data-distribution={distribution}
|
|
49
|
+
data-framed={framed}
|
|
50
|
+
orientation={orientation}
|
|
51
|
+
className={cn(
|
|
52
|
+
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
53
|
+
framed && "overflow-hidden rounded-xl border",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
</TabsSizeContext.Provider>
|
|
59
|
+
</TabsFramedContext.Provider>
|
|
60
|
+
</TabsDistributionContext.Provider>
|
|
39
61
|
</TabsOrientationContext.Provider>
|
|
40
|
-
)
|
|
62
|
+
);
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
const tabsListVariants = cva(
|
|
@@ -49,14 +71,14 @@ const tabsListVariants = cva(
|
|
|
49
71
|
variants: {
|
|
50
72
|
variant: {
|
|
51
73
|
default: "bg-muted",
|
|
52
|
-
line: "gap-
|
|
74
|
+
line: "gap-1 bg-transparent",
|
|
53
75
|
},
|
|
54
76
|
},
|
|
55
77
|
defaultVariants: {
|
|
56
78
|
variant: "default",
|
|
57
79
|
},
|
|
58
|
-
}
|
|
59
|
-
)
|
|
80
|
+
},
|
|
81
|
+
);
|
|
60
82
|
|
|
61
83
|
function TabsList({
|
|
62
84
|
className,
|
|
@@ -67,20 +89,33 @@ function TabsList({
|
|
|
67
89
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
68
90
|
VariantProps<typeof tabsListVariants>) {
|
|
69
91
|
// O size vem do <Tabs> (contexto) — a altura mora aqui, na list.
|
|
70
|
-
const size = React.useContext(TabsSizeContext)
|
|
71
|
-
const orientation = React.useContext(TabsOrientationContext)
|
|
72
|
-
const
|
|
73
|
-
const
|
|
92
|
+
const size = React.useContext(TabsSizeContext);
|
|
93
|
+
const orientation = React.useContext(TabsOrientationContext);
|
|
94
|
+
const distribution = React.useContext(TabsDistributionContext);
|
|
95
|
+
const normalizedVariant = variant ?? "default";
|
|
96
|
+
const hasHorizontalLine =
|
|
97
|
+
normalizedVariant === "line" && orientation === "horizontal";
|
|
74
98
|
return (
|
|
75
99
|
<TabsListVariantContext.Provider value={normalizedVariant}>
|
|
76
100
|
<TabsPrimitive.List
|
|
77
101
|
data-slot="tabs-list"
|
|
78
102
|
data-variant={normalizedVariant}
|
|
79
103
|
data-size={size}
|
|
80
|
-
|
|
104
|
+
data-distribution={distribution}
|
|
105
|
+
className={cn(
|
|
106
|
+
tabsListVariants({ variant: normalizedVariant }),
|
|
107
|
+
controlHeight[size],
|
|
108
|
+
distribution === "equal" && orientation === "horizontal" && "w-full",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
81
111
|
style={{
|
|
82
112
|
...(hasHorizontalLine
|
|
83
|
-
? {
|
|
113
|
+
? {
|
|
114
|
+
position: "relative",
|
|
115
|
+
width: "100%",
|
|
116
|
+
height: "auto",
|
|
117
|
+
justifyContent: "flex-start",
|
|
118
|
+
}
|
|
84
119
|
: {}),
|
|
85
120
|
...style,
|
|
86
121
|
}}
|
|
@@ -92,7 +127,7 @@ function TabsList({
|
|
|
92
127
|
data-slot="tabs-line"
|
|
93
128
|
style={{
|
|
94
129
|
position: "absolute",
|
|
95
|
-
insetInline: "
|
|
130
|
+
insetInline: "0rem",
|
|
96
131
|
bottom: 0,
|
|
97
132
|
borderBottomWidth: "0.0625rem",
|
|
98
133
|
borderBottomStyle: "solid",
|
|
@@ -104,7 +139,7 @@ function TabsList({
|
|
|
104
139
|
{children}
|
|
105
140
|
</TabsPrimitive.List>
|
|
106
141
|
</TabsListVariantContext.Provider>
|
|
107
|
-
)
|
|
142
|
+
);
|
|
108
143
|
}
|
|
109
144
|
|
|
110
145
|
function TabsTrigger({
|
|
@@ -115,38 +150,72 @@ function TabsTrigger({
|
|
|
115
150
|
...props
|
|
116
151
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger> & {
|
|
117
152
|
/** Ícone decorativo antes do rótulo. Em uma aba sem texto, declare também `aria-label`. */
|
|
118
|
-
icon?: React.ReactNode
|
|
153
|
+
icon?: React.ReactNode;
|
|
119
154
|
}) {
|
|
120
|
-
const variant = React.useContext(TabsListVariantContext)
|
|
121
|
-
const orientation = React.useContext(TabsOrientationContext)
|
|
122
|
-
const
|
|
155
|
+
const variant = React.useContext(TabsListVariantContext);
|
|
156
|
+
const orientation = React.useContext(TabsOrientationContext);
|
|
157
|
+
const size = React.useContext(TabsSizeContext);
|
|
158
|
+
const distribution = React.useContext(TabsDistributionContext);
|
|
159
|
+
const framed = React.useContext(TabsFramedContext);
|
|
160
|
+
const hasHorizontalLine = variant === "line" && orientation === "horizontal";
|
|
161
|
+
const content = (
|
|
162
|
+
<>
|
|
163
|
+
{icon !== undefined && (
|
|
164
|
+
<span
|
|
165
|
+
data-slot="tabs-trigger-icon"
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
className="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4"
|
|
168
|
+
>
|
|
169
|
+
{icon}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
{children}
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
123
175
|
return (
|
|
124
176
|
<TabsPrimitive.Trigger
|
|
125
177
|
data-slot="tabs-trigger"
|
|
126
178
|
// px: altura e indicador compensam a hairline do contêiner (um pixel físico, não escala).
|
|
127
179
|
className={cn(
|
|
128
180
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
129
|
-
"
|
|
181
|
+
"dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent",
|
|
130
182
|
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:text-foreground",
|
|
131
|
-
"after:absolute after:bg-
|
|
132
|
-
hasHorizontalLine &&
|
|
133
|
-
|
|
183
|
+
"after:absolute after:bg-primary after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-1px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
184
|
+
hasHorizontalLine &&
|
|
185
|
+
cn(
|
|
186
|
+
"group/tab-trigger rounded-none px-0 py-0 hover:bg-transparent active:brightness-100 data-[state=active]:bg-transparent data-[state=active]:hover:bg-transparent",
|
|
187
|
+
framed ? "pb-2" : "pb-1.5",
|
|
188
|
+
),
|
|
189
|
+
className,
|
|
134
190
|
)}
|
|
135
|
-
style={{
|
|
191
|
+
style={{
|
|
192
|
+
...(hasHorizontalLine
|
|
193
|
+
? {
|
|
194
|
+
flex: distribution === "equal" ? "1 1 0%" : "none",
|
|
195
|
+
height: "auto",
|
|
196
|
+
minWidth: distribution === "equal" ? 0 : undefined,
|
|
197
|
+
}
|
|
198
|
+
: {}),
|
|
199
|
+
...style,
|
|
200
|
+
}}
|
|
136
201
|
{...props}
|
|
137
202
|
>
|
|
138
|
-
{
|
|
203
|
+
{hasHorizontalLine ? (
|
|
139
204
|
<span
|
|
140
|
-
data-slot="tabs-trigger-
|
|
141
|
-
|
|
142
|
-
|
|
205
|
+
data-slot="tabs-trigger-content"
|
|
206
|
+
className={cn(
|
|
207
|
+
"inline-flex items-center justify-center gap-1.5 rounded-md px-3 transition-colors hover:bg-accent hover:text-accent-foreground active:brightness-90 group-data-[state=active]/tab-trigger:text-foreground",
|
|
208
|
+
distribution === "equal" && "w-full",
|
|
209
|
+
controlHeight[size],
|
|
210
|
+
)}
|
|
143
211
|
>
|
|
144
|
-
{
|
|
212
|
+
{content}
|
|
145
213
|
</span>
|
|
214
|
+
) : (
|
|
215
|
+
content
|
|
146
216
|
)}
|
|
147
|
-
{children}
|
|
148
217
|
</TabsPrimitive.Trigger>
|
|
149
|
-
)
|
|
218
|
+
);
|
|
150
219
|
}
|
|
151
220
|
|
|
152
221
|
function TabsContent({
|
|
@@ -159,7 +228,7 @@ function TabsContent({
|
|
|
159
228
|
className={cn("flex-1 outline-none", className)}
|
|
160
229
|
{...props}
|
|
161
230
|
/>
|
|
162
|
-
)
|
|
231
|
+
);
|
|
163
232
|
}
|
|
164
233
|
|
|
165
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
234
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
## Contexto e variante
|
|
2
2
|
|
|
3
|
-
Use `Button` para iniciar uma ação. `context` declara a hierarquia ou o risco da ação;
|
|
4
|
-
escolhe o tratamento visual. Use `primary` para a ação principal, `neutral` para ações de
|
|
5
|
-
`
|
|
3
|
+
Use `Button` para iniciar uma ação. `context` declara a hierarquia, a cautela ou o risco da ação;
|
|
4
|
+
`variant` escolhe o tratamento visual. Use `primary` para a ação principal, `neutral` para ações de
|
|
5
|
+
apoio, `warning` quando a pessoa puder prosseguir mas o efeito merecer atenção e `danger` quando a
|
|
6
|
+
ação tiver uma consequência perigosa. A variante `outline` mantém o fundo
|
|
6
7
|
transparente em repouso e usa uma superfície apenas no hover, preservando o fundo da região onde o
|
|
7
8
|
botão está inserido. `subtle` preenche o botão com um tom suave do contexto, para ações que precisam
|
|
8
9
|
de presença sem competir com a principal.
|
|
@@ -13,6 +14,7 @@ de presença sem competir com a principal.
|
|
|
13
14
|
<Button variant="subtle">Convidar pessoa</Button>
|
|
14
15
|
<Button context="neutral" variant="outline">Ver prévia</Button>
|
|
15
16
|
<Button context="neutral" variant="ghost">Cancelar</Button>
|
|
17
|
+
<Button context="warning" variant="ghost">Registrar tentativa sem sucesso</Button>
|
|
16
18
|
<Button context="danger" variant="solid">Excluir</Button>
|
|
17
19
|
<Button context="primary" variant="link">Ver documentação</Button>
|
|
18
20
|
```
|
|
@@ -27,16 +29,16 @@ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em
|
|
|
27
29
|
`icon-xs`, `icon-sm` e `icon`; 1rem em `default`; 1.25rem em `lg` e `icon-lg`), a menos que o
|
|
28
30
|
ícone traga um `size-*` próprio. O quadrado `icon` mantém a área de 2.25rem com o glifo discreto.
|
|
29
31
|
|
|
30
|
-
| Nome | Medida | Uso
|
|
31
|
-
| --------- | ------- |
|
|
32
|
-
| `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`).
|
|
33
|
-
| `sm` | 2rem | Operação densa em toolbar ou dentro de uma seção, ao lado de `Select` e `Tabs` `sm`.
|
|
32
|
+
| Nome | Medida | Uso |
|
|
33
|
+
| --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`). |
|
|
35
|
+
| `sm` | 2rem | Operação densa em toolbar ou dentro de uma seção, ao lado de `Select` e `Tabs` `sm`. |
|
|
34
36
|
| `default` | 2.25rem | Decisão da superfície: header de Page, criação no cabeçalho de uma coleção e footer de Dialog ou Drawer; é também a linha de `Input`. |
|
|
35
|
-
| `lg` | 2.5rem | Chamada principal com mais área de toque.
|
|
36
|
-
| `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo ou de uma linha densa; é o quadrado de `ActionTrigger` com `icon`.
|
|
37
|
-
| `icon-sm` | 1.75rem | Ação só de ícone em composições compactas que precisam de mais presença; também é o quadrado das setas do pager de `ActionList`.
|
|
38
|
-
| `icon` | 2.25rem | Ação só de ícone na linha padrão.
|
|
39
|
-
| `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`.
|
|
37
|
+
| `lg` | 2.5rem | Chamada principal com mais área de toque. |
|
|
38
|
+
| `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo ou de uma linha densa; é o quadrado de `ActionTrigger` com `icon`. |
|
|
39
|
+
| `icon-sm` | 1.75rem | Ação só de ícone em composições compactas que precisam de mais presença; também é o quadrado das setas do pager de `ActionList`. |
|
|
40
|
+
| `icon` | 2.25rem | Ação só de ícone na linha padrão. |
|
|
41
|
+
| `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`. |
|
|
40
42
|
|
|
41
43
|
Escolha o tamanho pela região, não pela importância visual: `variant` e `context` resolvem a
|
|
42
44
|
hierarquia da ação. Headers de Page, a criação no cabeçalho de uma coleção e footers de
|
|
@@ -102,15 +104,15 @@ estilos, use `buttonVariants` para obter as classes.
|
|
|
102
104
|
|
|
103
105
|
## Propriedades de Button
|
|
104
106
|
|
|
105
|
-
| Propriedade | Tipo | Padrão
|
|
106
|
-
| ----------- | ------------------------------------------------------------------------------------ |
|
|
107
|
-
| `context` | `'neutral' \| 'primary' \| 'danger'`
|
|
108
|
-
| `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'solid'`
|
|
109
|
-
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'default'`
|
|
110
|
-
| `shape` | `'default' \| 'pill'`
|
|
111
|
-
| `asChild` | `boolean` | `false`
|
|
112
|
-
| `busy` | `boolean` | `false`
|
|
113
|
-
| `icon` | `React.ReactNode` |
|
|
107
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
108
|
+
| ----------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
109
|
+
| `context` | `'neutral' \| 'primary' \| 'warning' \| 'danger'` | `'primary'` em `solid` e `subtle`; `'neutral'` nas demais variantes | A hierarquia, a cautela ou o risco comunicado pela ação. |
|
|
110
|
+
| `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'solid'` | O tratamento visual aplicado ao contexto. |
|
|
111
|
+
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'default'` | A medida na escala única dos controles (tabela acima). Os `icon-*` são quadrados para botões só de ícone, com `aria-label`. |
|
|
112
|
+
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria do botão; `pill` arredonda as extremidades por completo. |
|
|
113
|
+
| `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<button>` — para âncoras e afins. |
|
|
114
|
+
| `busy` | `boolean` | `false` | Ação em andamento, depois do clique: mostra o `Spinner` e desabilita o botão. Não indica carregamento de conteúdo; para isso, use `DataState` ou `Skeleton`. |
|
|
115
|
+
| `icon` | `React.ReactNode` | | Ícone à esquerda, como nó (ex.: `icon={<Plus />}`); o glifo segue o `size`. Com `busy`, dá lugar ao `Spinner`; os dois não aparecem juntos. |
|
|
114
116
|
|
|
115
117
|
## ButtonGroup
|
|
116
118
|
|
|
@@ -146,6 +148,27 @@ conectar bordas. Esse modo atende ações só com ícone em barras e linhas de l
|
|
|
146
148
|
</ButtonGroup>
|
|
147
149
|
```
|
|
148
150
|
|
|
151
|
+
### Ações divididas
|
|
152
|
+
|
|
153
|
+
Use `mode="divided"` quando cada controle precisar preservar sua própria interação, mas divisores
|
|
154
|
+
visuais ajudarem a ler o conjunto. O grupo insere os divisores entre os filhos e acompanha a
|
|
155
|
+
orientação automaticamente. Combine `framed` para delimitar o conjunto sem criar um fundo e
|
|
156
|
+
`distribution="equal"` quando as ações precisarem ocupar larguras iguais.
|
|
157
|
+
|
|
158
|
+
```tsx preview col
|
|
159
|
+
<ButtonGroup framed mode="divided" distribution="equal">
|
|
160
|
+
<Button variant="ghost" icon={<Phone />}>
|
|
161
|
+
Ligar
|
|
162
|
+
</Button>
|
|
163
|
+
<Button variant="ghost" icon={<MessageSquare />}>
|
|
164
|
+
Anotar
|
|
165
|
+
</Button>
|
|
166
|
+
<Button context="danger" variant="ghost" icon={<CircleX />}>
|
|
167
|
+
Encerrar
|
|
168
|
+
</Button>
|
|
169
|
+
</ButtonGroup>
|
|
170
|
+
```
|
|
171
|
+
|
|
149
172
|
### No rodapé de Dialog e Drawer
|
|
150
173
|
|
|
151
174
|
`DialogFooter` e `DrawerFooter` cuidam somente da faixa; envolva as decisões em um
|
|
@@ -169,13 +192,15 @@ confirmação. As ações dividem a largura disponível; use `outline` no `Cance
|
|
|
169
192
|
```tsx preview col
|
|
170
193
|
<div className="w-80">
|
|
171
194
|
<ButtonGroup mode="spaced" distribution="equal">
|
|
172
|
-
<Button context="neutral" variant="outline">
|
|
195
|
+
<Button context="neutral" variant="outline">
|
|
196
|
+
Cancelar
|
|
197
|
+
</Button>
|
|
173
198
|
<Button context="danger">Excluir</Button>
|
|
174
199
|
</ButtonGroup>
|
|
175
200
|
</div>
|
|
176
201
|
```
|
|
177
202
|
|
|
178
|
-
### Ação
|
|
203
|
+
### Ação com variações
|
|
179
204
|
|
|
180
205
|
Combine a ação principal, um separador e um botão de ícone quando o mesmo comando oferecer
|
|
181
206
|
variações.
|
|
@@ -227,12 +252,13 @@ semântica de outro elemento, como `label`.
|
|
|
227
252
|
|
|
228
253
|
### Propriedades de ButtonGroup
|
|
229
254
|
|
|
230
|
-
| Propriedade | Tipo
|
|
231
|
-
| -------------- |
|
|
232
|
-
| `orientation` | `'horizontal' \| 'vertical'`
|
|
233
|
-
| `mode` | `'connected' \| 'spaced'`
|
|
234
|
-
| `shape` | `'default' \| 'pill'`
|
|
235
|
-
| `distribution` | `'content' \| 'equal'`
|
|
255
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
256
|
+
| -------------- | -------------------------------------- | -------------- | ----------------------------------------------------------------------------------------- |
|
|
257
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção do bloco e do colapso das bordas. |
|
|
258
|
+
| `mode` | `'connected' \| 'spaced' \| 'divided'` | `'connected'` | Conecta as bordas, separa por espaço ou insere divisores entre os controles. |
|
|
259
|
+
| `shape` | `'default' \| 'pill'` | `'default'` | Geometria das extremidades externas do grupo. |
|
|
260
|
+
| `distribution` | `'content' \| 'equal'` | `'content'` | Mantém a largura do conteúdo ou divide igualmente o espaço disponível entre os controles. |
|
|
261
|
+
| `framed` | `boolean` | `false` | Aplica borda, raio e padding ao grupo sem definir uma nova superfície. |
|
|
236
262
|
|
|
237
263
|
### Propriedades de ButtonGroupSeparator
|
|
238
264
|
|
|
@@ -4,11 +4,11 @@ Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `
|
|
|
4
4
|
`DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
|
|
5
5
|
espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
|
|
6
6
|
|
|
7
|
-
O `Content` é
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
O `Content` é uma região nomeada dentro de outra superfície. Sem moldura, ele mantém a anatomia
|
|
8
|
+
contínua. Com `framed`, pode permanecer contínuo ou separar header e body com
|
|
9
|
+
`layout="sectioned"`. O layout organiza as regiões internas; não transforma `Content` em
|
|
10
|
+
[`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) ou [`Pane`](/ui/pane). Pelo mesmo
|
|
11
|
+
motivo o `Content` não tem rodapé: um rodapé pertence à superfície que o hospeda.
|
|
12
12
|
|
|
13
13
|
No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
|
|
14
14
|
|
|
@@ -36,11 +36,7 @@ do recuo da região.
|
|
|
36
36
|
|
|
37
37
|
```tsx preview col
|
|
38
38
|
render(
|
|
39
|
-
<Content
|
|
40
|
-
framed
|
|
41
|
-
title="Cliente"
|
|
42
|
-
media={<UserRound aria-hidden="true" />}
|
|
43
|
-
>
|
|
39
|
+
<Content framed title="Cliente" media={<UserRound aria-hidden="true" />}>
|
|
44
40
|
<DetailGroup columns={2}>
|
|
45
41
|
<DetailField label="Nome" value="Carla Souza" />
|
|
46
42
|
<DetailField label="Telefone" value="+55 11 97333-4455" />
|
|
@@ -93,6 +89,34 @@ render(
|
|
|
93
89
|
);
|
|
94
90
|
```
|
|
95
91
|
|
|
92
|
+
## Header e body seccionados
|
|
93
|
+
|
|
94
|
+
Use `layout="sectioned"` quando o filho do body já controlar o recuo de suas células, linhas ou
|
|
95
|
+
itens. O header recebe `p-3` e divisor inferior; o body fica sem padding para que a estrutura interna
|
|
96
|
+
alcance a moldura. Esse layout exige `framed`.
|
|
97
|
+
|
|
98
|
+
```tsx preview col
|
|
99
|
+
render(
|
|
100
|
+
<Content
|
|
101
|
+
framed
|
|
102
|
+
layout="sectioned"
|
|
103
|
+
title="Veículo"
|
|
104
|
+
media={<CarFront aria-hidden="true" />}
|
|
105
|
+
>
|
|
106
|
+
<DetailGroup dividers columns={2} orientation="horizontal">
|
|
107
|
+
<DetailField label="Marca" value="Toyota" />
|
|
108
|
+
<DetailField label="Modelo" value="Yaris XLS" />
|
|
109
|
+
<DetailField label="Ano/modelo" value="2021" />
|
|
110
|
+
<DetailField label="Placa" value="EVT0012" />
|
|
111
|
+
</DetailGroup>
|
|
112
|
+
</Content>,
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Conteúdo livre que não possui recuo próprio continua no layout `continuous`. Não combine
|
|
117
|
+
`layout="sectioned"` com `frameVariant="legend"`: `sectioned` coloca o header dentro da moldura;
|
|
118
|
+
`legend` integra o header ao traço externo.
|
|
119
|
+
|
|
96
120
|
## Grupo de campos
|
|
97
121
|
|
|
98
122
|
Quando a região nomear um conjunto de campos relacionados, use `as="fieldset"`. O `Content`
|
|
@@ -129,12 +153,7 @@ decisão semântica, e não uma forma de obter esse desenho.
|
|
|
129
153
|
|
|
130
154
|
```tsx preview col
|
|
131
155
|
render(
|
|
132
|
-
<Content
|
|
133
|
-
as="fieldset"
|
|
134
|
-
framed
|
|
135
|
-
frameVariant="legend"
|
|
136
|
-
title="Dados do veículo"
|
|
137
|
-
>
|
|
156
|
+
<Content as="fieldset" framed frameVariant="legend" title="Dados do veículo">
|
|
138
157
|
<FieldGroup>
|
|
139
158
|
<Field>
|
|
140
159
|
<FieldLabel htmlFor="vehicle-plate">Placa</FieldLabel>
|
|
@@ -192,7 +211,12 @@ extremo oposto ao título, como em qualquer variante.
|
|
|
192
211
|
|
|
193
212
|
```tsx preview col
|
|
194
213
|
render(
|
|
195
|
-
<Content
|
|
214
|
+
<Content
|
|
215
|
+
level={1}
|
|
216
|
+
variant="page"
|
|
217
|
+
title="Clientes"
|
|
218
|
+
actions={<Button>Criar cliente</Button>}
|
|
219
|
+
>
|
|
196
220
|
<div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
|
|
197
221
|
A lista de clientes ocupa o corpo.
|
|
198
222
|
</div>
|
|
@@ -211,19 +235,20 @@ classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio
|
|
|
211
235
|
|
|
212
236
|
## Propriedades de Content
|
|
213
237
|
|
|
214
|
-
| Propriedade | Tipo
|
|
215
|
-
| -------------- |
|
|
216
|
-
| `title` | `ReactNode`
|
|
217
|
-
| `media` | `ReactNode`
|
|
218
|
-
| `count` | `number`
|
|
219
|
-
| `description` | `ReactNode`
|
|
220
|
-
| `actions` | `ReactNode`
|
|
221
|
-
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6`
|
|
222
|
-
| `variant` | `'page' \| 'section'`
|
|
223
|
-
| `as` | `'section' \| 'fieldset'`
|
|
224
|
-
| `framed` | `boolean`
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
238
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
239
|
+
| -------------- | ----------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
240
|
+
| `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
|
|
241
|
+
| `media` | `ReactNode` | | Forma curta: ícone ou imagem que identifica a região antes do título. |
|
|
242
|
+
| `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
|
|
243
|
+
| `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
|
|
244
|
+
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
|
|
245
|
+
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
|
|
246
|
+
| `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
|
|
247
|
+
| `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
|
|
248
|
+
| `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
|
|
249
|
+
| `layout` | `'continuous' \| 'sectioned'` | `'continuous'` | Mantém header e body contínuos ou os separa com divisor e body sem recuo. `sectioned` exige `framed`. |
|
|
250
|
+
| `frameVariant` | `'default' \| 'legend'` | `'default'` | Mantém a borda contínua ou integra visualmente o header à moldura. `legend` não combina com `layout="sectioned"`. |
|
|
251
|
+
| `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
|
|
227
252
|
|
|
228
253
|
As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
|
|
229
254
|
|
|
@@ -235,12 +260,12 @@ hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Conte
|
|
|
235
260
|
|
|
236
261
|
## Propriedades de ContentMedia
|
|
237
262
|
|
|
238
|
-
| Propriedade | Tipo | Padrão | Descrição
|
|
239
|
-
| ----------- | ------------------------------------------------------------------------ | ----------- |
|
|
240
|
-
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'xs'` | Tamanho da moldura e do glifo na escala compartilhada dos controles.
|
|
241
|
-
| `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor.
|
|
242
|
-
| `variant` | `'subtle' \| 'outline'`
|
|
243
|
-
| `className` | `string` | | Ajusta a moldura sem substituir a escala.
|
|
263
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
264
|
+
| ----------- | ------------------------------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
265
|
+
| `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'xs'` | Tamanho da moldura e do glifo na escala compartilhada dos controles. |
|
|
266
|
+
| `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Declara o significado da cor. |
|
|
267
|
+
| `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
|
|
268
|
+
| `className` | `string` | | Ajusta a moldura sem substituir a escala. |
|
|
244
269
|
| `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. |
|
|
245
270
|
|
|
246
271
|
`ContentMedia` já é a moldura. Passe só o glifo e escolha `size`, `context` e `variant` no slot. Um
|