@softize/opus 19.0.0 → 20.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 +193 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +15 -3
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/list.tsx +89 -78
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/presentation.tsx +52 -37
- package/src/ui/components/patterns/split.tsx +44 -4
- package/src/ui/components/patterns/surface-anatomy.ts +35 -8
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -1
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/dialog.tsx +41 -10
- package/src/ui/components/primitives/drawer.tsx +9 -1
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +134 -52
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +41 -15
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +31 -7
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +107 -24
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +28 -5
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +12 -4
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +118 -12
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +55 -5
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +3 -2
- package/src/ui/docs/content/skeleton.md +6 -1
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +19 -0
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +31 -31
- package/src/vite/design.ts +16 -4
|
@@ -37,7 +37,12 @@ function InputGroup({ className, shape = 'default', ...props }: React.ComponentP
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const inputGroupAddonVariants = cva(
|
|
40
|
-
|
|
40
|
+
[
|
|
41
|
+
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-0.3125rem)] [&>svg:not([class*='size-'])]:size-4",
|
|
42
|
+
// O addon acompanha o controle desabilitado sem depender do `data-disabled` manual. Só o
|
|
43
|
+
// controle conta: um InputGroupButton desabilitado não apaga o grupo inteiro.
|
|
44
|
+
"group-has-[[data-slot=input-group-control]:disabled]/input-group:opacity-50",
|
|
45
|
+
],
|
|
41
46
|
{
|
|
42
47
|
variants: {
|
|
43
48
|
align: {
|
|
@@ -72,7 +77,9 @@ function InputGroupAddon({
|
|
|
72
77
|
if ((e.target as HTMLElement).closest("button")) {
|
|
73
78
|
return
|
|
74
79
|
}
|
|
75
|
-
e.currentTarget.parentElement
|
|
80
|
+
e.currentTarget.parentElement
|
|
81
|
+
?.querySelector<HTMLElement>("input, textarea")
|
|
82
|
+
?.focus()
|
|
76
83
|
}}
|
|
77
84
|
{...props}
|
|
78
85
|
/>
|
|
@@ -49,7 +49,7 @@ function InputOTPSlot({
|
|
|
49
49
|
data-slot="input-otp-slot"
|
|
50
50
|
data-active={isActive}
|
|
51
51
|
className={cn(
|
|
52
|
-
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
|
|
52
|
+
"relative flex h-9 w-9 items-center justify-center border-y border-r border-input bg-muted/30 text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
|
|
53
53
|
className
|
|
54
54
|
)}
|
|
55
55
|
{...props}
|
|
@@ -15,7 +15,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
|
15
15
|
)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function KbdGroup({ className, ...props }: React.ComponentProps<"
|
|
18
|
+
function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">) {
|
|
19
19
|
return (
|
|
20
20
|
<kbd
|
|
21
21
|
data-slot="kbd-group"
|
|
@@ -12,6 +12,9 @@ function Label({
|
|
|
12
12
|
data-slot="label"
|
|
13
13
|
className={cn(
|
|
14
14
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
|
+
// O Field declara o grupo nomeado `group/field`: sem o sufixo, o Label não enxerga o
|
|
16
|
+
// `data-disabled` dele e continua aceso ao lado de um controle desabilitado.
|
|
17
|
+
"group-data-[disabled=true]/field:pointer-events-none group-data-[disabled=true]/field:opacity-50",
|
|
15
18
|
className
|
|
16
19
|
)}
|
|
17
20
|
{...props}
|
|
@@ -12,6 +12,11 @@ function Menu({
|
|
|
12
12
|
return <MenuPrimitive.Root data-slot="menu" {...props} />
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated `MenuContent` já renderiza o painel num portal próprio, então envolvê-lo com
|
|
17
|
+
* `MenuPortal` não muda onde o painel aparece. Use `MenuContent` diretamente. Sai do pacote numa
|
|
18
|
+
* próxima versão major.
|
|
19
|
+
*/
|
|
15
20
|
function MenuPortal({
|
|
16
21
|
...props
|
|
17
22
|
}: React.ComponentProps<typeof MenuPrimitive.Portal>) {
|
|
@@ -59,7 +59,7 @@ function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function PopoverTitle({ className, ...props }: React.ComponentProps<"
|
|
62
|
+
function PopoverTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
63
63
|
return (
|
|
64
64
|
<div
|
|
65
65
|
data-slot="popover-title"
|
|
@@ -32,13 +32,21 @@ export interface ProgressProps extends React.ComponentProps<typeof ProgressPrimi
|
|
|
32
32
|
function Progress({
|
|
33
33
|
className,
|
|
34
34
|
value,
|
|
35
|
+
max = 100,
|
|
35
36
|
context = 'primary',
|
|
36
37
|
...props
|
|
37
38
|
}: ProgressProps) {
|
|
39
|
+
// A largura é a fração de `max`, não de 100: `value={3} max={4}` enche três quartos. Um `max`
|
|
40
|
+
// não positivo cai em 100, como o Radix faz no Root.
|
|
41
|
+
const scale = max > 0 ? max : 100
|
|
42
|
+
const percent = ((value ?? 0) / scale) * 100
|
|
38
43
|
return (
|
|
39
44
|
<ProgressPrimitive.Root
|
|
40
45
|
data-slot="progress"
|
|
41
46
|
data-context={context}
|
|
47
|
+
// O Root precisa do `value` para anunciar o andamento (aria-valuenow, data-state).
|
|
48
|
+
value={value}
|
|
49
|
+
max={max}
|
|
42
50
|
className={cn(
|
|
43
51
|
"relative h-2 w-full overflow-hidden rounded-full",
|
|
44
52
|
trackClasses[context],
|
|
@@ -49,7 +57,7 @@ function Progress({
|
|
|
49
57
|
<ProgressPrimitive.Indicator
|
|
50
58
|
data-slot="progress-indicator"
|
|
51
59
|
className={cn("h-full w-full flex-1 transition-all", indicatorClasses[context])}
|
|
52
|
-
style={{ transform: `translateX(-${100 -
|
|
60
|
+
style={{ transform: `translateX(-${100 - percent}%)` }}
|
|
53
61
|
/>
|
|
54
62
|
</ProgressPrimitive.Root>
|
|
55
63
|
)
|
|
@@ -4,14 +4,40 @@ import { Slider as SliderPrimitive } from "radix-ui"
|
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
5
|
import { focusRing } from './control.ts'
|
|
6
6
|
|
|
7
|
+
export interface SliderProps extends React.ComponentProps<typeof SliderPrimitive.Root> {
|
|
8
|
+
/**
|
|
9
|
+
* Nome de cada thumb, na ordem de `value`. Use em intervalos, em que cada controle precisa de
|
|
10
|
+
* um nome próprio (`['Custo mínimo', 'Custo máximo']`). Prevalece sobre `aria-label` e
|
|
11
|
+
* `aria-labelledby` no thumb correspondente.
|
|
12
|
+
*/
|
|
13
|
+
thumbLabels?: readonly string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Quem recebe foco e anuncia o valor é o thumb (`role="slider"`), não a raiz. Por isso
|
|
17
|
+
// `aria-label` e `aria-labelledby` vão para os thumbs, como o Select faz com o campo interno;
|
|
18
|
+
// na raiz, sem papel, não nomeariam nada. Em intervalo, `thumbLabels` dá um nome a cada controle.
|
|
19
|
+
// Sem nome declarado, o Radix usaria "Minimum"/"Maximum": o fallback fica em português. Em
|
|
20
|
+
// intervalo sem `thumbLabels`, o nome comum se compõe com o fallback ("Preço, mínimo"), senão os
|
|
21
|
+
// dois thumbs teriam o mesmo nome; com `aria-labelledby`, o fallback entra por um span oculto
|
|
22
|
+
// referenciado junto (`hidden` sai da tela, mas continua valendo como rótulo referenciado).
|
|
23
|
+
function thumbFallback(index: number, total: number): string | undefined {
|
|
24
|
+
if (total > 2) return `Valor ${index + 1} de ${total}`
|
|
25
|
+
if (total === 2) return index === 0 ? 'Mínimo' : 'Máximo'
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
7
29
|
function Slider({
|
|
8
30
|
className,
|
|
9
31
|
defaultValue,
|
|
10
32
|
value,
|
|
11
33
|
min = 0,
|
|
12
34
|
max = 100,
|
|
35
|
+
thumbLabels,
|
|
36
|
+
'aria-label': ariaLabel,
|
|
37
|
+
'aria-labelledby': ariaLabelledby,
|
|
13
38
|
...props
|
|
14
|
-
}:
|
|
39
|
+
}: SliderProps) {
|
|
40
|
+
const fallbackIdBase = React.useId()
|
|
15
41
|
const _values = React.useMemo(
|
|
16
42
|
() =>
|
|
17
43
|
Array.isArray(value)
|
|
@@ -48,16 +74,41 @@ function Slider({
|
|
|
48
74
|
)}
|
|
49
75
|
/>
|
|
50
76
|
</SliderPrimitive.Track>
|
|
51
|
-
{Array.from({ length: _values.length }, (_, index) =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
{Array.from({ length: _values.length }, (_, index) => {
|
|
78
|
+
const thumbLabel = thumbLabels?.[index]
|
|
79
|
+
const fallback = thumbFallback(index, _values.length)
|
|
80
|
+
const fallbackId = `${fallbackIdBase}-thumb-${index}`
|
|
81
|
+
const composeLabelledby = thumbLabel === undefined && ariaLabelledby !== undefined && fallback !== undefined
|
|
82
|
+
const label =
|
|
83
|
+
thumbLabel ??
|
|
84
|
+
(ariaLabel !== undefined && fallback !== undefined
|
|
85
|
+
? `${ariaLabel}, ${fallback.toLowerCase()}`
|
|
86
|
+
: ariaLabel ?? fallback)
|
|
87
|
+
return (
|
|
88
|
+
<React.Fragment key={index}>
|
|
89
|
+
{composeLabelledby && (
|
|
90
|
+
<span id={fallbackId} hidden>
|
|
91
|
+
{fallback}
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
<SliderPrimitive.Thumb
|
|
95
|
+
data-slot="slider-thumb"
|
|
96
|
+
aria-label={composeLabelledby ? undefined : label}
|
|
97
|
+
aria-labelledby={
|
|
98
|
+
thumbLabel !== undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: composeLabelledby
|
|
101
|
+
? `${ariaLabelledby} ${fallbackId}`
|
|
102
|
+
: ariaLabelledby
|
|
103
|
+
}
|
|
104
|
+
className={cn(
|
|
105
|
+
"block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm transition-[color,box-shadow] hover:ring-[0.1875rem] hover:ring-ring/50 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50",
|
|
106
|
+
focusRing,
|
|
107
|
+
)}
|
|
108
|
+
/>
|
|
109
|
+
</React.Fragment>
|
|
110
|
+
)
|
|
111
|
+
})}
|
|
61
112
|
</SliderPrimitive.Root>
|
|
62
113
|
)
|
|
63
114
|
}
|
|
@@ -2,6 +2,7 @@ import * as React from 'react'
|
|
|
2
2
|
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip.tsx'
|
|
3
3
|
import { useOverflowing } from '../../lib/overflow.ts'
|
|
4
4
|
import { cn } from '../../lib/cn.ts'
|
|
5
|
+
import { focusRing } from './control.ts'
|
|
5
6
|
|
|
6
7
|
export interface TruncateProps extends React.ComponentProps<'span'> {
|
|
7
8
|
/** Conteúdo do tooltip quando transborda. Default: os próprios children. */
|
|
@@ -17,13 +18,21 @@ export interface TruncateProps extends React.ComponentProps<'span'> {
|
|
|
17
18
|
* Requer `<TooltipProvider>` na raiz do app (o esqueleto do `opus create` já monta).
|
|
18
19
|
* Com `fade`, a mesma medição decide quando esmaecer o fim da linha em vez de cortar
|
|
19
20
|
* em reticências — sem transbordo, nada é aplicado.
|
|
21
|
+
* Com o texto cortado, o span entra na ordem de tabulação para que o teclado alcance a dica;
|
|
22
|
+
* sem corte, fica fora dela, porque não há nada escondido para revelar.
|
|
20
23
|
*/
|
|
21
24
|
export function Truncate({ tooltip, fade = false, children, className, ...props }: TruncateProps): React.ReactElement {
|
|
22
25
|
const { ref, overflowing } = useOverflowing<HTMLSpanElement>(children)
|
|
23
26
|
const cut = fade ? (overflowing ? 'truncate-fade' : 'overflow-hidden whitespace-nowrap') : 'truncate'
|
|
24
27
|
|
|
25
28
|
const span = (
|
|
26
|
-
<span
|
|
29
|
+
<span
|
|
30
|
+
ref={ref}
|
|
31
|
+
data-slot="truncate"
|
|
32
|
+
tabIndex={overflowing ? 0 : undefined}
|
|
33
|
+
className={cn('block', cut, overflowing && ['rounded-sm outline-none', focusRing], className)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
27
36
|
{children}
|
|
28
37
|
</span>
|
|
29
38
|
)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
## Um painel por vez
|
|
2
2
|
|
|
3
|
+
Use `Accordion` para dividir um conteúdo longo em painéis que a pessoa expande sob demanda, cada um
|
|
4
|
+
com seu título. Para um único bloco recolhível, use [`Collapsible`](/ui/collapsible).
|
|
5
|
+
|
|
3
6
|
Use `type="single"` quando somente um painel deve permanecer aberto. Com `collapsible`, a pessoa
|
|
4
|
-
também pode fechar o painel atual; sem essa propriedade, um item
|
|
7
|
+
também pode fechar o painel atual; sem essa propriedade, depois que um item é aberto, sempre haverá
|
|
8
|
+
um item expandido.
|
|
5
9
|
`defaultValue` define o item inicialmente aberto no modo não controlado.
|
|
6
10
|
|
|
7
11
|
```tsx preview
|
|
@@ -9,41 +13,43 @@ também pode fechar o painel atual; sem essa propriedade, um item permanece expa
|
|
|
9
13
|
<AccordionItem value="overview">
|
|
10
14
|
<AccordionTrigger>Visão geral</AccordionTrigger>
|
|
11
15
|
<AccordionContent>
|
|
12
|
-
<p className="text-muted-foreground">
|
|
16
|
+
<p className="text-muted-foreground">Corolla XEi 2022, placa ABC-1D23.</p>
|
|
13
17
|
</AccordionContent>
|
|
14
18
|
</AccordionItem>
|
|
15
|
-
<AccordionItem value="
|
|
16
|
-
<AccordionTrigger>
|
|
19
|
+
<AccordionItem value="services">
|
|
20
|
+
<AccordionTrigger>Serviços</AccordionTrigger>
|
|
17
21
|
<AccordionContent>
|
|
18
|
-
<p className="text-muted-foreground">
|
|
22
|
+
<p className="text-muted-foreground">Troca de óleo, filtro de ar e alinhamento.</p>
|
|
19
23
|
</AccordionContent>
|
|
20
24
|
</AccordionItem>
|
|
21
|
-
<AccordionItem value="
|
|
22
|
-
<AccordionTrigger>
|
|
25
|
+
<AccordionItem value="parts">
|
|
26
|
+
<AccordionTrigger>Peças</AccordionTrigger>
|
|
23
27
|
<AccordionContent>
|
|
24
|
-
<p className="text-muted-foreground">
|
|
28
|
+
<p className="text-muted-foreground">Filtro de óleo e pastilhas de freio.</p>
|
|
25
29
|
</AccordionContent>
|
|
26
30
|
</AccordionItem>
|
|
27
31
|
</Accordion>
|
|
28
32
|
```
|
|
29
33
|
|
|
34
|
+
O `AccordionTrigger` já traz o ícone que gira conforme o estado; não acrescente outro.
|
|
35
|
+
|
|
30
36
|
## Múltiplos abertos
|
|
31
37
|
|
|
32
38
|
Use `type="multiple"` quando os painéis puderem permanecer abertos ao mesmo tempo. Nesse modo,
|
|
33
39
|
`defaultValue` recebe um array com os itens inicialmente expandidos.
|
|
34
40
|
|
|
35
41
|
```tsx preview
|
|
36
|
-
<Accordion type="multiple" defaultValue={['
|
|
37
|
-
<AccordionItem value="
|
|
38
|
-
<AccordionTrigger>
|
|
42
|
+
<Accordion type="multiple" defaultValue={['notes', 'parts']} className="w-full">
|
|
43
|
+
<AccordionItem value="notes">
|
|
44
|
+
<AccordionTrigger>Observações</AccordionTrigger>
|
|
39
45
|
<AccordionContent>
|
|
40
|
-
<p className="text-muted-foreground">O
|
|
46
|
+
<p className="text-muted-foreground">O cliente relatou ruído ao frear.</p>
|
|
41
47
|
</AccordionContent>
|
|
42
48
|
</AccordionItem>
|
|
43
|
-
<AccordionItem value="
|
|
44
|
-
<AccordionTrigger>
|
|
49
|
+
<AccordionItem value="parts">
|
|
50
|
+
<AccordionTrigger>Peças</AccordionTrigger>
|
|
45
51
|
<AccordionContent>
|
|
46
|
-
<p className="text-muted-foreground">
|
|
52
|
+
<p className="text-muted-foreground">Pastilhas de freio substituídas.</p>
|
|
47
53
|
</AccordionContent>
|
|
48
54
|
</AccordionItem>
|
|
49
55
|
</Accordion>
|
|
@@ -55,38 +61,79 @@ Use `value` e `onValueChange` quando outro elemento ou estado do aplicativo tamb
|
|
|
55
61
|
controlar o painel aberto.
|
|
56
62
|
|
|
57
63
|
```tsx preview
|
|
58
|
-
const [open, setOpen] = React.useState('
|
|
64
|
+
const [open, setOpen] = React.useState('diagnosis')
|
|
59
65
|
|
|
60
66
|
render(
|
|
61
67
|
<Accordion type="single" collapsible value={open} onValueChange={setOpen} className="w-full">
|
|
62
|
-
<AccordionItem value="
|
|
63
|
-
<AccordionTrigger>
|
|
68
|
+
<AccordionItem value="diagnosis">
|
|
69
|
+
<AccordionTrigger>Diagnóstico</AccordionTrigger>
|
|
64
70
|
<AccordionContent>
|
|
65
|
-
<p className="text-muted-foreground">
|
|
71
|
+
<p className="text-muted-foreground">Identifica a causa do ruído relatado pelo cliente.</p>
|
|
66
72
|
</AccordionContent>
|
|
67
73
|
</AccordionItem>
|
|
68
|
-
<AccordionItem value="
|
|
69
|
-
<AccordionTrigger>
|
|
74
|
+
<AccordionItem value="execution">
|
|
75
|
+
<AccordionTrigger>Execução</AccordionTrigger>
|
|
70
76
|
<AccordionContent>
|
|
71
|
-
<p className="text-muted-foreground">
|
|
77
|
+
<p className="text-muted-foreground">Substitui as peças aprovadas no orçamento.</p>
|
|
72
78
|
</AccordionContent>
|
|
73
79
|
</AccordionItem>
|
|
74
80
|
</Accordion>,
|
|
75
81
|
)
|
|
76
82
|
```
|
|
77
83
|
|
|
84
|
+
## Item indisponível
|
|
85
|
+
|
|
86
|
+
Use `disabled` no `AccordionItem` quando um painel existe, mas ainda não pode ser aberto. O gatilho
|
|
87
|
+
fica esmaecido e não responde ao clique nem ao teclado.
|
|
88
|
+
|
|
89
|
+
```tsx preview
|
|
90
|
+
<Accordion type="single" collapsible className="w-full">
|
|
91
|
+
<AccordionItem value="quote">
|
|
92
|
+
<AccordionTrigger>Orçamento</AccordionTrigger>
|
|
93
|
+
<AccordionContent>
|
|
94
|
+
<p className="text-muted-foreground">Aprovado pelo cliente em 12 de maio.</p>
|
|
95
|
+
</AccordionContent>
|
|
96
|
+
</AccordionItem>
|
|
97
|
+
<AccordionItem value="invoice" disabled>
|
|
98
|
+
<AccordionTrigger>Nota fiscal</AccordionTrigger>
|
|
99
|
+
<AccordionContent>
|
|
100
|
+
<p className="text-muted-foreground">Emitida depois da entrega do veículo.</p>
|
|
101
|
+
</AccordionContent>
|
|
102
|
+
</AccordionItem>
|
|
103
|
+
</Accordion>
|
|
104
|
+
```
|
|
105
|
+
|
|
78
106
|
## Propriedades de Accordion
|
|
79
107
|
|
|
80
108
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
81
109
|
|---|---|---|---|
|
|
82
|
-
| `type` | `'single' \| 'multiple'` | | `single` abre um painel por vez; `multiple` permite manter vários abertos. |
|
|
110
|
+
| `type` | `'single' \| 'multiple'` | | Obrigatório. `single` abre um painel por vez; `multiple` permite manter vários abertos. |
|
|
83
111
|
| `collapsible` | `boolean` | `false` | No modo `single`, permite fechar o item aberto. |
|
|
84
|
-
| `defaultValue` | `string
|
|
85
|
-
| `value` | `string
|
|
86
|
-
| `onValueChange` | `(value) => void` | | Chamado quando a pessoa abre ou fecha um item. |
|
|
112
|
+
| `defaultValue` | `string` em `single`; `string[]` em `multiple` | | Item ou itens inicialmente abertos no modo não controlado. |
|
|
113
|
+
| `value` | `string` em `single`; `string[]` em `multiple` | | Item ou itens abertos no modo controlado. Use com `onValueChange`. |
|
|
114
|
+
| `onValueChange` | `(value: string) => void` em `single`; `(value: string[]) => void` em `multiple` | | Chamado quando a pessoa abre ou fecha um item. Em `single`, recebe `''` quando o item aberto é fechado. |
|
|
115
|
+
| `disabled` | `boolean` | `false` | Desabilita todos os itens. |
|
|
116
|
+
| `className` | `string` | | Ajusta o contêiner. |
|
|
87
117
|
|
|
88
118
|
## Propriedades de AccordionItem
|
|
89
119
|
|
|
90
120
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
91
121
|
|---|---|---|---|
|
|
92
|
-
| `value` | `string` | | Identificador usado por `defaultValue` e `value` no `Accordion`. |
|
|
122
|
+
| `value` | `string` | | Obrigatório. Identificador usado por `defaultValue` e `value` no `Accordion`. |
|
|
123
|
+
| `disabled` | `boolean` | `false` | Impede abrir ou fechar este item. |
|
|
124
|
+
| `className` | `string` | | Ajusta o item, que traz um divisor inferior exceto no último. |
|
|
125
|
+
|
|
126
|
+
## Propriedades de AccordionTrigger
|
|
127
|
+
|
|
128
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| `children` | `ReactNode` | | Rótulo do painel. O ícone de estado é acrescentado depois dele. |
|
|
131
|
+
| `className` | `string` | | Ajusta o botão do gatilho. |
|
|
132
|
+
|
|
133
|
+
## Propriedades de AccordionContent
|
|
134
|
+
|
|
135
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
136
|
+
|---|---|---|---|
|
|
137
|
+
| `children` | `ReactNode` | | Conteúdo do painel. |
|
|
138
|
+
| `className` | `string` | | Ajusta o recuo interno do conteúdo, aplicado a um contêiner dentro da região animada. |
|
|
139
|
+
| `forceMount` | `boolean` | `false` | Mantém o conteúdo montado mesmo fechado. |
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
## Formulário em uma seção
|
|
2
2
|
|
|
3
|
-
Use `ActionFormCard`
|
|
4
|
-
aplica a estrutura e o espaçamento de `Card
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Use `ActionFormCard` quando o formulário ocupar uma seção delimitada da página, ao lado de outros
|
|
4
|
+
blocos. O componente aplica a estrutura e o espaçamento de [`Card`](/ui/card) ao
|
|
5
|
+
[`ActionForm`](/ui/action-form), sem a faixa de ações de um modal. O título é opcional e segue a
|
|
6
|
+
mesma regra de um formulário modal: nomeia a operação e o recurso, como `Criar workspace`, enquanto
|
|
7
|
+
a ação principal mantém `Salvar`.
|
|
7
8
|
|
|
8
9
|
```tsx preview col md
|
|
9
10
|
<DocBrowserActionProvider>
|
|
10
|
-
<ActionFormCard
|
|
11
|
-
title="Editar workspace"
|
|
12
|
-
action={docWorkspaceCreate}
|
|
13
|
-
defaultValues={{ name: "Empresa X", status: "active" }}
|
|
14
|
-
/>
|
|
11
|
+
<ActionFormCard title="Criar workspace" action={docWorkspaceCreate} />
|
|
15
12
|
</DocBrowserActionProvider>
|
|
16
13
|
```
|
|
17
14
|
|
|
15
|
+
As ações ficam alinhadas no fim do rodapé e preservam a largura do conteúdo. Quando `onCancel`
|
|
16
|
+
acrescenta `Cancelar`, os dois botões formam um `ButtonGroup` espaçado. Para interromper o fluxo
|
|
17
|
+
com um modal, use [`ActionFormDialog`](/ui/action-form-dialog).
|
|
18
|
+
|
|
19
|
+
O título do card é um heading `h3`, o nível padrão de `CardTitle`, adequado a uma seção dentro de
|
|
20
|
+
uma página que já tem o próprio título. Quando o card ocupar outro lugar na hierarquia — por
|
|
21
|
+
exemplo, a seção principal logo abaixo do `h1` da página —, declare `titleLevel={2}`. O nível muda
|
|
22
|
+
só a estrutura do documento, que leitores de tela usam para navegar; a aparência do título continua
|
|
23
|
+
a mesma.
|
|
24
|
+
|
|
18
25
|
## Propriedades de ActionFormCard
|
|
19
26
|
|
|
20
|
-
| Propriedade
|
|
21
|
-
|
|
|
22
|
-
| `title`
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `cardClassName`
|
|
27
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
28
|
+
| ------------------ | ---------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `title` | `string` | | Título do cabeçalho. Sem ele e sem `description`, o card começa diretamente pelo corpo. |
|
|
30
|
+
| `titleLevel` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `3` | Nível do heading do título. Muda a hierarquia do documento, não a escala visual. |
|
|
31
|
+
| `description` | `string` | | Texto de apoio abaixo do título. |
|
|
32
|
+
| `cardClassName` | `string` | | Classes aplicadas à superfície do card. |
|
|
33
|
+
| `className` | `string` | | Classes aplicadas ao `<form>` interno. |
|
|
34
|
+
| Demais propriedades | Propriedades de `ActionForm` | | `action`, `defaultValues`, `fieldOptions`, `submitLabel`, `cancelLabel`, `cancelVariant`, `onCancel`, `onSuccess`, `disabled`, `onLoadingChange` e `children` mantêm o comportamento de `ActionForm`. `body` e `footer` não são aceitas: o card fornece o corpo e o rodapé. |
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Use `ActionFormDialog` quando o formulário precisar interromper o fluxo atual sem levar a pessoa
|
|
4
4
|
para outra página. O consumidor controla `open`; depois de uma execução bem-sucedida, o componente
|
|
5
|
-
fecha o modal. O cabeçalho e o rodapé permanecem visíveis enquanto os campos
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
fecha o modal. O cabeçalho e o rodapé permanecem visíveis enquanto os campos rolam.
|
|
6
|
+
|
|
7
|
+
O título nomeia a operação e o recurso: `Criar workspace` em uma criação e `Editar workspace` em uma
|
|
8
|
+
edição. Nos dois casos, a ação principal mantém o rótulo padrão `Salvar`, porque o título já
|
|
9
|
+
distingue a operação. `submitLabel` fica para efeitos que `Salvar` não descreve, como `Renomear` ou
|
|
10
|
+
`Criar nova versão`.
|
|
11
11
|
|
|
12
12
|
```tsx preview
|
|
13
13
|
const [open, setOpen] = useState(false);
|
|
@@ -25,12 +25,22 @@ render(
|
|
|
25
25
|
);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## Ações do rodapé
|
|
29
|
+
|
|
30
|
+
O rodapé agrupa as decisões em um `ButtonGroup` cuja largura acompanha o conteúdo: `Cancelar`
|
|
31
|
+
aparece em `ghost` e a ação principal permanece `solid`, ambas no tamanho normal de uma decisão
|
|
32
|
+
modal. `Cancelar` está sempre presente; sem `onCancel` próprio, ele fecha o modal. Com
|
|
33
|
+
`footerDistribution="equal"`, as duas decisões dividem a faixa e `Cancelar` passa a `outline`,
|
|
34
|
+
salvo escolha explícita em `cancelVariant`.
|
|
35
|
+
|
|
36
|
+
Para consultar uma coleção no mesmo formato de modal, use [`ActionListDialog`](/ui/action-list-dialog).
|
|
37
|
+
|
|
28
38
|
## Propriedades de ActionFormDialog
|
|
29
39
|
|
|
30
40
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
31
41
|
| --------------------- | ------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
42
|
| `open / onOpenChange` | `boolean / (open: boolean) => void` | | Estado controlado do modal. `onOpenChange(false)` é chamado no sucesso e, quando não há `onCancel` próprio, ao cancelar. |
|
|
33
|
-
| `title` | `string` | |
|
|
43
|
+
| `title` | `string` | | Operação e recurso exibidos no cabeçalho, como `Criar workspace` ou `Editar workspace`. |
|
|
34
44
|
| `intro` | `ReactNode` | | Contexto relevante no início do corpo, como texto ou `Alert`. |
|
|
35
45
|
| `submitLabel` | `string` | `'Salvar'` | Resultado da ação principal. Preserve o padrão em criação e edição comuns; sobrescreva apenas quando a operação tiver outro efeito, como `Renomear` ou `Criar nova versão`. |
|
|
36
46
|
| `footerDistribution` | `'content' \| 'equal'` | `'content'` | Mantém a largura das ações pelo conteúdo ou divide a faixa igualmente. |
|