@softize/opus 29.0.0 → 31.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 +137 -0
- package/bin/lib/check.mjs +178 -94
- package/bin/lib/copy.mjs +2970 -1801
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +52 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +27 -2
- package/docs/adr/0045-surface-replaces-content-and-card.md +3 -0
- package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +142 -0
- package/docs/adr/0062-listas-contidas-e-texto-flexivel.md +68 -0
- package/docs/adr/0063-metadados-das-superficies.md +44 -0
- package/docs/adr/0064-action-list-com-itens-e-fontes-externas.md +47 -0
- package/docs/adr/0065-presentation-composes-settings-sections.md +295 -0
- package/docs/adr/README.md +8 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +21 -19
- package/registry/skills/build-opus-ui/references/evaluations.md +21 -10
- package/registry/skills/build-opus-ui/references/ui-patterns.md +33 -28
- package/src/core/presentation.ts +938 -32
- package/src/core/types.ts +2 -1
- package/src/ui/components/patterns/form.tsx +2 -0
- package/src/ui/components/patterns/grouped-list.ts +2 -2
- package/src/ui/components/patterns/list.tsx +588 -365
- package/src/ui/components/patterns/page-heading-context.tsx +12 -5
- package/src/ui/components/patterns/page.tsx +238 -181
- package/src/ui/components/patterns/presentation-fields.tsx +246 -0
- package/src/ui/components/patterns/presentation-sections.tsx +494 -0
- package/src/ui/components/patterns/presentation.tsx +1142 -229
- package/src/ui/components/patterns/structural-header.tsx +47 -34
- package/src/ui/components/patterns/surface-anatomy.ts +27 -217
- package/src/ui/components/patterns/surface.tsx +55 -37
- package/src/ui/components/patterns/trigger.tsx +10 -2
- package/src/ui/components/primitives/close-button.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -8
- package/src/ui/components/primitives/detail.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +59 -89
- package/src/ui/components/primitives/drawer.tsx +52 -87
- package/src/ui/components/primitives/field.tsx +1 -1
- package/src/ui/components/primitives/pagination.tsx +3 -2
- package/src/ui/components/primitives/table.tsx +3 -3
- package/src/ui/components/primitives/tabs.tsx +13 -2
- package/src/ui/docs/content/action-form.md +2 -1
- package/src/ui/docs/content/action-list.md +146 -73
- package/src/ui/docs/content/action-trigger.md +5 -0
- package/src/ui/docs/content/button.md +5 -6
- package/src/ui/docs/content/command.md +62 -50
- package/src/ui/docs/content/compositions.md +129 -459
- package/src/ui/docs/content/dialog.md +72 -101
- package/src/ui/docs/content/drawer.md +69 -92
- package/src/ui/docs/content/field.md +2 -1
- package/src/ui/docs/content/page.md +207 -386
- package/src/ui/docs/content/presentation.md +371 -23
- package/src/ui/docs/content/separator.md +1 -1
- package/src/ui/docs/content/surface.md +94 -116
- package/src/ui/docs/content/table.md +7 -0
- package/src/ui/docs/content/tokens.md +18 -4
- package/src/ui/drivers/context.tsx +11 -4
- package/src/ui/drivers/react.tsx +27 -7
- package/src/ui/meta.ts +5 -5
- package/src/ui/react.tsx +38 -8
- package/src/ui/theme.css +44 -33
|
@@ -3,20 +3,17 @@ import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
|
3
3
|
|
|
4
4
|
import { cn } from "../../lib/cn.ts";
|
|
5
5
|
import {
|
|
6
|
-
SurfaceLayoutProvider,
|
|
7
6
|
surfaceActionsClasses,
|
|
8
7
|
surfaceDescriptionClasses,
|
|
9
8
|
surfaceHeaderRowClasses,
|
|
10
9
|
surfaceRegionClasses,
|
|
11
10
|
surfaceTitleClasses,
|
|
12
|
-
useSurfaceLayout,
|
|
13
|
-
type SurfaceLayout,
|
|
14
11
|
} from "../patterns/surface-anatomy.ts";
|
|
12
|
+
import { FillBoundary } from "../patterns/fill-region.ts";
|
|
15
13
|
import { headingTags, type HeadingLevel } from "../patterns/heading-level.ts";
|
|
16
14
|
import {
|
|
17
15
|
StructuralHeader,
|
|
18
|
-
|
|
19
|
-
validateSurfaceIntro,
|
|
16
|
+
structuralMetaClasses,
|
|
20
17
|
} from "../patterns/structural-header.tsx";
|
|
21
18
|
import {
|
|
22
19
|
StructuralMedia,
|
|
@@ -47,10 +44,10 @@ interface DrawerContentContextValue {
|
|
|
47
44
|
const DrawerContentContext = React.createContext<DrawerContentContextValue>({
|
|
48
45
|
showCloseButton: false,
|
|
49
46
|
closeDisabled: false,
|
|
50
|
-
closeSize: "
|
|
47
|
+
closeSize: "xs",
|
|
51
48
|
});
|
|
52
49
|
|
|
53
|
-
const
|
|
50
|
+
const DrawerMetaContext = React.createContext(false);
|
|
54
51
|
|
|
55
52
|
/** Estado de aberto que a pilha de modais da camada flutuante acompanha (ADR 0052). */
|
|
56
53
|
const DrawerStateContext = React.createContext({ open: false, modal: true });
|
|
@@ -148,8 +145,7 @@ function DrawerContent({
|
|
|
148
145
|
side = "right",
|
|
149
146
|
showCloseButton = true,
|
|
150
147
|
closeDisabled = false,
|
|
151
|
-
closeSize = "
|
|
152
|
-
layout = "sectioned",
|
|
148
|
+
closeSize = "xs",
|
|
153
149
|
"aria-describedby": ariaDescribedBy,
|
|
154
150
|
onEscapeKeyDown,
|
|
155
151
|
onPointerDownOutside,
|
|
@@ -168,8 +164,6 @@ function DrawerContent({
|
|
|
168
164
|
*/
|
|
169
165
|
closeDisabled?: boolean;
|
|
170
166
|
closeSize?: CloseButtonSize;
|
|
171
|
-
/** Anatomia da superfície: `sectioned` (default) ou `continuous` — ADR 0023. */
|
|
172
|
-
layout?: SurfaceLayout;
|
|
173
167
|
}) {
|
|
174
168
|
const { open, modal } = React.useContext(DrawerStateContext);
|
|
175
169
|
const layer = useLayerPortal();
|
|
@@ -207,7 +201,6 @@ function DrawerContent({
|
|
|
207
201
|
<DrawerPrimitive.Content
|
|
208
202
|
ref={composedRef}
|
|
209
203
|
data-slot="drawer-content"
|
|
210
|
-
data-layout={layout}
|
|
211
204
|
{...{ [LAYER_MODAL_ATTRIBUTE]: internal || undefined }}
|
|
212
205
|
className={cn(
|
|
213
206
|
"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",
|
|
@@ -235,13 +228,13 @@ function DrawerContent({
|
|
|
235
228
|
{...props}
|
|
236
229
|
>
|
|
237
230
|
<LayerPortalProvider value={nestedPortal}>
|
|
238
|
-
<
|
|
231
|
+
<FillBoundary>
|
|
239
232
|
<DrawerContentContext.Provider
|
|
240
233
|
value={{ showCloseButton, closeDisabled, closeSize }}
|
|
241
234
|
>
|
|
242
235
|
{children}
|
|
243
236
|
</DrawerContentContext.Provider>
|
|
244
|
-
</
|
|
237
|
+
</FillBoundary>
|
|
245
238
|
</LayerPortalProvider>
|
|
246
239
|
</DrawerPrimitive.Content>
|
|
247
240
|
</DrawerPortal>
|
|
@@ -251,39 +244,45 @@ function DrawerContent({
|
|
|
251
244
|
function DrawerHeader({
|
|
252
245
|
className,
|
|
253
246
|
children,
|
|
247
|
+
tabs,
|
|
254
248
|
...props
|
|
255
|
-
}: React.ComponentProps<"div">
|
|
256
|
-
|
|
249
|
+
}: React.ComponentProps<"div"> & {
|
|
250
|
+
tabs?: React.ReactNode;
|
|
251
|
+
}) {
|
|
257
252
|
const { showCloseButton, closeDisabled, closeSize } =
|
|
258
253
|
React.useContext(DrawerContentContext);
|
|
259
254
|
return (
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
className
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
255
|
+
<DrawerMetaContext.Provider value>
|
|
256
|
+
<StructuralHeader
|
|
257
|
+
tabs={tabs}
|
|
258
|
+
name="DrawerHeader"
|
|
259
|
+
slot="drawer"
|
|
260
|
+
slots={{
|
|
261
|
+
leading: DrawerNavigation,
|
|
262
|
+
media: DrawerMedia,
|
|
263
|
+
title: DrawerTitle,
|
|
264
|
+
description: DrawerDescription,
|
|
265
|
+
meta: DrawerMeta,
|
|
266
|
+
actions: DrawerActions,
|
|
267
|
+
}}
|
|
268
|
+
leadingPlacement="inline"
|
|
269
|
+
titleRequired
|
|
270
|
+
rowClassName={surfaceHeaderRowClasses("drawer")}
|
|
271
|
+
className={cn(
|
|
272
|
+
"text-left",
|
|
273
|
+
surfaceRegionClasses("drawer", "header"),
|
|
274
|
+
className,
|
|
275
|
+
)}
|
|
276
|
+
trailing={
|
|
277
|
+
showCloseButton ? (
|
|
278
|
+
<DrawerCloseButton disabled={closeDisabled} size={closeSize} />
|
|
279
|
+
) : undefined
|
|
280
|
+
}
|
|
281
|
+
{...props}
|
|
282
|
+
>
|
|
283
|
+
{children}
|
|
284
|
+
</StructuralHeader>
|
|
285
|
+
</DrawerMetaContext.Provider>
|
|
287
286
|
);
|
|
288
287
|
}
|
|
289
288
|
|
|
@@ -317,42 +316,17 @@ function DrawerActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
317
316
|
);
|
|
318
317
|
}
|
|
319
318
|
|
|
320
|
-
/**
|
|
321
|
-
function
|
|
322
|
-
className,
|
|
323
|
-
children,
|
|
324
|
-
...props
|
|
325
|
-
}: React.ComponentProps<"div">) {
|
|
326
|
-
return (
|
|
327
|
-
<DrawerIntroContext.Provider value>
|
|
328
|
-
<StructuralHeader
|
|
329
|
-
name="DrawerIntro"
|
|
330
|
-
slot="drawer-intro"
|
|
331
|
-
slots={{
|
|
332
|
-
title: DrawerTitle,
|
|
333
|
-
description: DrawerDescription,
|
|
334
|
-
actions: DrawerIntroActions,
|
|
335
|
-
}}
|
|
336
|
-
leadingPlacement="inline"
|
|
337
|
-
titleRequired
|
|
338
|
-
className={cn("text-left", className)}
|
|
339
|
-
{...props}
|
|
340
|
-
>
|
|
341
|
-
{children}
|
|
342
|
-
</StructuralHeader>
|
|
343
|
-
</DrawerIntroContext.Provider>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/** Ações relacionadas ao título do conteúdo, separadas das ações de chrome do header. */
|
|
348
|
-
function DrawerIntroActions({
|
|
319
|
+
/** Badges, datas e autoria abaixo do título e da orientação. */
|
|
320
|
+
export function DrawerMeta({
|
|
349
321
|
className,
|
|
350
322
|
...props
|
|
351
323
|
}: React.ComponentProps<"div">) {
|
|
324
|
+
const inside = React.useContext(DrawerMetaContext);
|
|
325
|
+
if (!inside) throw new Error("DrawerMeta deve estar dentro de DrawerHeader.");
|
|
352
326
|
return (
|
|
353
327
|
<div
|
|
354
|
-
data-slot="drawer-
|
|
355
|
-
className={cn(
|
|
328
|
+
data-slot="drawer-meta"
|
|
329
|
+
className={cn(structuralMetaClasses, className)}
|
|
356
330
|
{...props}
|
|
357
331
|
/>
|
|
358
332
|
);
|
|
@@ -364,14 +338,12 @@ function DrawerBody({
|
|
|
364
338
|
style,
|
|
365
339
|
...props
|
|
366
340
|
}: React.ComponentProps<"div"> & { flush?: boolean }) {
|
|
367
|
-
const layout = useSurfaceLayout("drawer");
|
|
368
|
-
validateSurfaceIntro(props.children, DrawerIntro, "DrawerBody");
|
|
369
341
|
return (
|
|
370
342
|
<div
|
|
371
343
|
data-slot="drawer-body"
|
|
372
344
|
className={cn(
|
|
373
345
|
"min-h-0 flex-1 overflow-y-auto",
|
|
374
|
-
surfaceRegionClasses("drawer",
|
|
346
|
+
surfaceRegionClasses("drawer", "body", { flush }),
|
|
375
347
|
className,
|
|
376
348
|
)}
|
|
377
349
|
style={style}
|
|
@@ -382,7 +354,6 @@ function DrawerBody({
|
|
|
382
354
|
}
|
|
383
355
|
|
|
384
356
|
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
385
|
-
const layout = useSurfaceLayout("drawer");
|
|
386
357
|
return (
|
|
387
358
|
<div
|
|
388
359
|
data-slot="drawer-footer"
|
|
@@ -391,7 +362,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
391
362
|
// preenche a altura da janela, e sem isso o rodapé subiria atrás do corpo. No
|
|
392
363
|
// Dialog a altura é do conteúdo, então ele não precisa (ADR 0023).
|
|
393
364
|
"mt-auto flex shrink-0 flex-row justify-end gap-2",
|
|
394
|
-
surfaceRegionClasses("drawer",
|
|
365
|
+
surfaceRegionClasses("drawer", "footer"),
|
|
395
366
|
className,
|
|
396
367
|
)}
|
|
397
368
|
{...props}
|
|
@@ -408,13 +379,9 @@ function DrawerTitle({
|
|
|
408
379
|
/** Hierarquia do documento. Sem ele vale o `h2` que a primitiva emite. */
|
|
409
380
|
level?: HeadingLevel;
|
|
410
381
|
}) {
|
|
411
|
-
const intro = React.useContext(DrawerIntroContext);
|
|
412
|
-
const layout = useSurfaceLayout("drawer");
|
|
413
382
|
const classes = cn(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
? structuralHeaderClasses.page.title
|
|
417
|
-
: surfaceTitleClasses("drawer", layout),
|
|
383
|
+
"min-w-0 truncate",
|
|
384
|
+
surfaceTitleClasses("drawer"),
|
|
418
385
|
className,
|
|
419
386
|
);
|
|
420
387
|
// O nível troca a TAG do mesmo nó, via `asChild` — a escala continua vindo do layout.
|
|
@@ -441,8 +408,6 @@ function DrawerTitle({
|
|
|
441
408
|
|
|
442
409
|
export {
|
|
443
410
|
DrawerActions,
|
|
444
|
-
DrawerIntro,
|
|
445
|
-
DrawerIntroActions,
|
|
446
411
|
DrawerMedia,
|
|
447
412
|
DrawerNavigation,
|
|
448
413
|
Drawer,
|
|
@@ -184,7 +184,7 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
184
184
|
<p
|
|
185
185
|
data-slot="field-description"
|
|
186
186
|
className={cn(
|
|
187
|
-
"text-
|
|
187
|
+
"text-xs leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
|
|
188
188
|
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
|
|
189
189
|
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
190
190
|
className
|
|
@@ -32,7 +32,7 @@ function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface PaginationLinkProps extends Omit<React.ComponentProps<'a'>, 'children'> {
|
|
35
|
-
/** Página atual:
|
|
35
|
+
/** Página atual: usa fundo `subtle` e ganha `aria-current="page"`. */
|
|
36
36
|
isActive?: boolean
|
|
37
37
|
/** A escala de control.ts, via Button. `default` (números: altura fixa, largura mínima
|
|
38
38
|
* quadrada que cresce com os dígitos) ou um `icon*` (setas quadradas, sem padding). */
|
|
@@ -59,7 +59,8 @@ function PaginationLink({ className, isActive, page, disabled, href, size = 'def
|
|
|
59
59
|
'data-slot': 'pagination-link',
|
|
60
60
|
'data-active': isActive,
|
|
61
61
|
className: cn(
|
|
62
|
-
buttonVariants({ variant: isActive ? '
|
|
62
|
+
buttonVariants({ context: 'neutral', variant: isActive ? 'subtle' : 'ghost', size }),
|
|
63
|
+
isActive && 'font-semibold',
|
|
63
64
|
// Número: a largura mínima é quadrada e o conteúdo manda no resto. Escala `icon*`
|
|
64
65
|
// fica quadrada de verdade (sem `has-[>svg]:px-*` do `default`).
|
|
65
66
|
!square && cn('w-auto px-2 tabular-nums', minWidth),
|
|
@@ -24,7 +24,7 @@ function Table({
|
|
|
24
24
|
data-slot="table-container"
|
|
25
25
|
data-variant={variant}
|
|
26
26
|
className={cn(
|
|
27
|
-
"relative w-full overflow-x-auto",
|
|
27
|
+
"relative w-full min-w-0 max-w-full overflow-x-auto",
|
|
28
28
|
variant === "framed" && "rounded-lg border border-border",
|
|
29
29
|
containerClassName,
|
|
30
30
|
)}
|
|
@@ -95,7 +95,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
95
95
|
<th
|
|
96
96
|
data-slot="table-head"
|
|
97
97
|
className={cn(
|
|
98
|
-
"h-10 px-3 text-left align-middle font-medium whitespace-
|
|
98
|
+
"h-10 px-3 py-2 text-left align-middle font-medium whitespace-normal text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
99
99
|
className,
|
|
100
100
|
)}
|
|
101
101
|
{...props}
|
|
@@ -108,7 +108,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
|
108
108
|
<td
|
|
109
109
|
data-slot="table-cell"
|
|
110
110
|
className={cn(
|
|
111
|
-
"px-3 py-2 align-middle whitespace-
|
|
111
|
+
"px-3 py-2 align-middle whitespace-normal wrap-break-word [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
112
112
|
className,
|
|
113
113
|
)}
|
|
114
114
|
{...props}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
structuralRegionGap,
|
|
8
8
|
} from "../patterns/surface-anatomy.ts";
|
|
9
9
|
import { controlHeight, type ControlSize } from "./control.ts";
|
|
10
|
+
import { useFillRegion, fillRegionClassName } from "../patterns/fill-region.ts";
|
|
10
11
|
|
|
11
12
|
// Divergência da casa (ejetado): o Tabs ganhou `size` — Button e Select têm `sm`, o Tabs
|
|
12
13
|
// não tinha, e uma fileira densa ficava com as abas 0.25rem mais altas
|
|
@@ -18,6 +19,9 @@ const TabsSizeContext = React.createContext<TabsSize>("default");
|
|
|
18
19
|
const TabsDistributionContext =
|
|
19
20
|
React.createContext<TabsDistribution>("content");
|
|
20
21
|
const TabsFramedContext = React.createContext(false);
|
|
22
|
+
|
|
23
|
+
/** Uso interno: o header estrutural fornece o divisor da faixa de abas. */
|
|
24
|
+
export const TabsHeaderContext = React.createContext(false);
|
|
21
25
|
type TabsOrientation = "horizontal" | "vertical";
|
|
22
26
|
const TabsOrientationContext =
|
|
23
27
|
React.createContext<TabsOrientation>("horizontal");
|
|
@@ -70,6 +74,7 @@ function TabsList({
|
|
|
70
74
|
style,
|
|
71
75
|
...props
|
|
72
76
|
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
77
|
+
const inHeader = React.useContext(TabsHeaderContext);
|
|
73
78
|
// O size vem do <Tabs> (contexto) — a altura mora aqui, na list.
|
|
74
79
|
const size = React.useContext(TabsSizeContext);
|
|
75
80
|
const orientation = React.useContext(TabsOrientationContext);
|
|
@@ -101,7 +106,7 @@ function TabsList({
|
|
|
101
106
|
}}
|
|
102
107
|
{...props}
|
|
103
108
|
>
|
|
104
|
-
{hasHorizontalLine && (
|
|
109
|
+
{hasHorizontalLine && !inHeader && (
|
|
105
110
|
<span
|
|
106
111
|
aria-hidden="true"
|
|
107
112
|
data-slot="tabs-line"
|
|
@@ -196,21 +201,27 @@ function TabsListItem({
|
|
|
196
201
|
|
|
197
202
|
function TabsPanel({
|
|
198
203
|
className,
|
|
204
|
+
children,
|
|
199
205
|
...props
|
|
200
206
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
201
207
|
const framed = React.useContext(TabsFramedContext);
|
|
202
208
|
const orientation = React.useContext(TabsOrientationContext);
|
|
209
|
+
const fill = useFillRegion();
|
|
203
210
|
return (
|
|
204
211
|
<TabsPrimitive.Content
|
|
205
212
|
data-slot="tabs-panel"
|
|
213
|
+
{...fill.attributes}
|
|
206
214
|
className={cn(
|
|
207
215
|
"flex-1 outline-none data-[state=inactive]:hidden",
|
|
208
216
|
structuralBodyRhythm,
|
|
217
|
+
fill.filling && fillRegionClassName,
|
|
209
218
|
framed && orientation === "horizontal" && "px-4 pb-4",
|
|
210
219
|
className,
|
|
211
220
|
)}
|
|
212
221
|
{...props}
|
|
213
|
-
|
|
222
|
+
>
|
|
223
|
+
{fill.provide(children)}
|
|
224
|
+
</TabsPrimitive.Content>
|
|
214
225
|
);
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -122,7 +122,8 @@ referência; itens estáticos permanecem como declarados no contrato.
|
|
|
122
122
|
|
|
123
123
|
## Widgets declarativos
|
|
124
124
|
|
|
125
|
-
`FieldSpec.widget` aceita `textarea`, `code`, `lines`, `refItems`, `icon` e `toggle-group`.
|
|
125
|
+
`FieldSpec.widget` aceita `password`, `textarea`, `code`, `lines`, `refItems`, `icon` e `toggle-group`.
|
|
126
|
+
`password` usa um input de senha com `autocomplete="new-password"`, adequado a cadastro e redefinição.
|
|
126
127
|
O literal canônico do grupo de escolhas é sempre kebab-case: `widget: 'toggle-group'`. Em enum ele
|
|
127
128
|
renderiza seleção única; em array de enum, seleção múltipla. As opções seguem a mesma precedência e
|
|
128
129
|
o mesmo `SelectOption` rico dos selects. Use esse widget quando cada opção funcionar como uma
|