@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.
Files changed (142) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +15 -3
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/list.tsx +89 -78
  20. package/src/ui/components/patterns/page.tsx +20 -4
  21. package/src/ui/components/patterns/presentation.tsx +52 -37
  22. package/src/ui/components/patterns/split.tsx +44 -4
  23. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  24. package/src/ui/components/patterns/trigger.tsx +12 -2
  25. package/src/ui/components/primitives/alert.tsx +9 -1
  26. package/src/ui/components/primitives/avatar.tsx +7 -3
  27. package/src/ui/components/primitives/carousel.tsx +34 -5
  28. package/src/ui/components/primitives/checkbox.tsx +11 -3
  29. package/src/ui/components/primitives/command.tsx +2 -1
  30. package/src/ui/components/primitives/copyable.tsx +120 -10
  31. package/src/ui/components/primitives/dialog.tsx +41 -10
  32. package/src/ui/components/primitives/drawer.tsx +9 -1
  33. package/src/ui/components/primitives/empty.tsx +1 -1
  34. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  35. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  36. package/src/ui/components/primitives/input-group.tsx +9 -2
  37. package/src/ui/components/primitives/input-otp.tsx +1 -1
  38. package/src/ui/components/primitives/kbd.tsx +1 -1
  39. package/src/ui/components/primitives/label.tsx +3 -0
  40. package/src/ui/components/primitives/menu.tsx +5 -0
  41. package/src/ui/components/primitives/popover.tsx +1 -1
  42. package/src/ui/components/primitives/progress.tsx +9 -1
  43. package/src/ui/components/primitives/slider.tsx +62 -11
  44. package/src/ui/components/primitives/truncate.tsx +10 -1
  45. package/src/ui/docs/content/accordion.md +74 -27
  46. package/src/ui/docs/content/action-form-card.md +24 -15
  47. package/src/ui/docs/content/action-form-dialog.md +17 -7
  48. package/src/ui/docs/content/action-form.md +92 -77
  49. package/src/ui/docs/content/action-list-dialog.md +75 -38
  50. package/src/ui/docs/content/action-list.md +134 -52
  51. package/src/ui/docs/content/action-trigger.md +27 -10
  52. package/src/ui/docs/content/action-view.md +14 -14
  53. package/src/ui/docs/content/actions.md +161 -66
  54. package/src/ui/docs/content/ai.md +43 -44
  55. package/src/ui/docs/content/alert.md +83 -29
  56. package/src/ui/docs/content/ask.md +51 -26
  57. package/src/ui/docs/content/aspect-ratio.md +22 -24
  58. package/src/ui/docs/content/audit.md +23 -19
  59. package/src/ui/docs/content/auth.md +38 -19
  60. package/src/ui/docs/content/avatar.md +54 -5
  61. package/src/ui/docs/content/badge.md +29 -9
  62. package/src/ui/docs/content/breadcrumb.md +21 -8
  63. package/src/ui/docs/content/button.md +73 -43
  64. package/src/ui/docs/content/calendar.md +134 -26
  65. package/src/ui/docs/content/card.md +41 -15
  66. package/src/ui/docs/content/carousel.md +37 -6
  67. package/src/ui/docs/content/chat.md +118 -37
  68. package/src/ui/docs/content/checkbox.md +73 -7
  69. package/src/ui/docs/content/cli.md +94 -55
  70. package/src/ui/docs/content/collapsible.md +43 -19
  71. package/src/ui/docs/content/command.md +30 -8
  72. package/src/ui/docs/content/composer.md +23 -8
  73. package/src/ui/docs/content/content.md +31 -7
  74. package/src/ui/docs/content/copyable.md +39 -8
  75. package/src/ui/docs/content/customization.md +55 -46
  76. package/src/ui/docs/content/cycle.md +33 -18
  77. package/src/ui/docs/content/data-products.md +115 -0
  78. package/src/ui/docs/content/data-state.md +20 -6
  79. package/src/ui/docs/content/data.md +20 -19
  80. package/src/ui/docs/content/detail.md +12 -5
  81. package/src/ui/docs/content/dialog.md +107 -24
  82. package/src/ui/docs/content/dictionary-value.md +4 -4
  83. package/src/ui/docs/content/dock.md +54 -19
  84. package/src/ui/docs/content/dot.md +35 -5
  85. package/src/ui/docs/content/drawer.md +63 -19
  86. package/src/ui/docs/content/empty-value.md +20 -6
  87. package/src/ui/docs/content/empty.md +24 -3
  88. package/src/ui/docs/content/events.md +76 -16
  89. package/src/ui/docs/content/executions.md +9 -9
  90. package/src/ui/docs/content/field.md +21 -6
  91. package/src/ui/docs/content/getting-started.md +44 -34
  92. package/src/ui/docs/content/icon-picker.md +19 -13
  93. package/src/ui/docs/content/icon-tile.md +28 -5
  94. package/src/ui/docs/content/input-otp.md +58 -7
  95. package/src/ui/docs/content/input.md +22 -3
  96. package/src/ui/docs/content/item.md +53 -6
  97. package/src/ui/docs/content/kbd.md +5 -1
  98. package/src/ui/docs/content/label.md +13 -4
  99. package/src/ui/docs/content/log.md +5 -5
  100. package/src/ui/docs/content/markdown.md +5 -4
  101. package/src/ui/docs/content/mcp.md +49 -23
  102. package/src/ui/docs/content/menu.md +95 -10
  103. package/src/ui/docs/content/metric-card.md +12 -4
  104. package/src/ui/docs/content/observability.md +10 -9
  105. package/src/ui/docs/content/page.md +118 -12
  106. package/src/ui/docs/content/pagination.md +13 -5
  107. package/src/ui/docs/content/pane.md +55 -5
  108. package/src/ui/docs/content/popover.md +42 -5
  109. package/src/ui/docs/content/presentation.md +91 -47
  110. package/src/ui/docs/content/progress.md +27 -14
  111. package/src/ui/docs/content/queue.md +39 -21
  112. package/src/ui/docs/content/radio-group.md +26 -4
  113. package/src/ui/docs/content/router.md +29 -10
  114. package/src/ui/docs/content/runtime.md +90 -54
  115. package/src/ui/docs/content/scheduler.md +53 -21
  116. package/src/ui/docs/content/scroll-area.md +42 -35
  117. package/src/ui/docs/content/select.md +78 -33
  118. package/src/ui/docs/content/semantic-context.md +21 -2
  119. package/src/ui/docs/content/separator.md +15 -9
  120. package/src/ui/docs/content/sidebar.md +3 -2
  121. package/src/ui/docs/content/skeleton.md +6 -1
  122. package/src/ui/docs/content/slider.md +37 -9
  123. package/src/ui/docs/content/spinner.md +4 -3
  124. package/src/ui/docs/content/split.md +19 -0
  125. package/src/ui/docs/content/storage.md +8 -9
  126. package/src/ui/docs/content/switch.md +10 -7
  127. package/src/ui/docs/content/table.md +46 -8
  128. package/src/ui/docs/content/tabs.md +14 -4
  129. package/src/ui/docs/content/testing.md +76 -31
  130. package/src/ui/docs/content/textarea.md +5 -4
  131. package/src/ui/docs/content/toast.md +48 -7
  132. package/src/ui/docs/content/toggle.md +3 -3
  133. package/src/ui/docs/content/tokens.md +15 -26
  134. package/src/ui/docs/content/tooltip.md +18 -2
  135. package/src/ui/docs/content/truncate.md +36 -6
  136. package/src/ui/docs/content/ui.md +74 -22
  137. package/src/ui/docs/content/upgrading.md +45 -36
  138. package/src/ui/docs/plugin.ts +36 -2
  139. package/src/ui/docs/registry.tsx +6 -0
  140. package/src/ui/drivers/react.tsx +16 -8
  141. package/src/ui/meta.ts +31 -31
  142. 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
- "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",
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?.querySelector("input")?.focus()
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<"div">) {
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<"h2">) {
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 - (value || 0)}%)` }}
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
- }: React.ComponentProps<typeof SliderPrimitive.Root>) {
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
- <SliderPrimitive.Thumb
53
- data-slot="slider-thumb"
54
- key={index}
55
- className={cn(
56
- "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",
57
- focusRing,
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 ref={ref} data-slot="truncate" className={cn('block', cut, className)} {...props}>
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 permanece expandido.
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">Resumo do workspace Empresa X.</p>
16
+ <p className="text-muted-foreground">Corolla XEi 2022, placa ABC-1D23.</p>
13
17
  </AccordionContent>
14
18
  </AccordionItem>
15
- <AccordionItem value="agents">
16
- <AccordionTrigger>Agentes</AccordionTrigger>
19
+ <AccordionItem value="services">
20
+ <AccordionTrigger>Serviços</AccordionTrigger>
17
21
  <AccordionContent>
18
- <p className="text-muted-foreground">developer, reviewer e designer ativos.</p>
22
+ <p className="text-muted-foreground">Troca de óleo, filtro de ar e alinhamento.</p>
19
23
  </AccordionContent>
20
24
  </AccordionItem>
21
- <AccordionItem value="repos">
22
- <AccordionTrigger>Repositórios</AccordionTrigger>
25
+ <AccordionItem value="parts">
26
+ <AccordionTrigger>Peças</AccordionTrigger>
23
27
  <AccordionContent>
24
- <p className="text-muted-foreground">empresa-x-api e empresa-x-web vinculados.</p>
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={['skills', 'prompt']} className="w-full">
37
- <AccordionItem value="prompt">
38
- <AccordionTrigger>Prompt</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 prompt do agente developer.</p>
46
+ <p className="text-muted-foreground">O cliente relatou ruído ao frear.</p>
41
47
  </AccordionContent>
42
48
  </AccordionItem>
43
- <AccordionItem value="skills">
44
- <AccordionTrigger>Skills</AccordionTrigger>
49
+ <AccordionItem value="parts">
50
+ <AccordionTrigger>Peças</AccordionTrigger>
45
51
  <AccordionContent>
46
- <p className="text-muted-foreground">clean-code, code-style, ddd e test vinculadas.</p>
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('developer')
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="developer">
63
- <AccordionTrigger>Agente developer</AccordionTrigger>
68
+ <AccordionItem value="diagnosis">
69
+ <AccordionTrigger>Diagnóstico</AccordionTrigger>
64
70
  <AccordionContent>
65
- <p className="text-muted-foreground">Implementa a task no worktree da sessão.</p>
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="reviewer">
69
- <AccordionTrigger>Agente reviewer</AccordionTrigger>
74
+ <AccordionItem value="execution">
75
+ <AccordionTrigger>Execução</AccordionTrigger>
70
76
  <AccordionContent>
71
- <p className="text-muted-foreground">Revisa o handoff e carimba o diff.</p>
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 \| string[]` | | Item ou itens inicialmente abertos no modo não controlado. |
85
- | `value` | `string \| string[]` | | Item ou itens abertos no modo controlado. Use com `onValueChange`. |
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` para apresentar um `ActionForm` como seção delimitada da página. O componente
4
- aplica a estrutura e o espaçamento de `Card`, sem a faixa de ações de um modal. O título é opcional.
5
- Quando `onCancel` acrescenta uma segunda ação, o componente organiza os botões em um
6
- `ButtonGroup` espaçado sem forçar a divisão 50/50 do modal.
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 | Tipo | Padrão | Descrição |
21
- | ---------------------------------------------------------------------------- | ---------------------------- | ------ | ---------------------------------------------------------------------------------------- |
22
- | `title` | `string` | | Título do cabeçalho. Sem ele, o card começa diretamente pelo corpo. |
23
- | `description` | `string` | | Subtítulo opcional, abaixo do título. |
24
- | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | Propriedades de `ActionForm` | | Mantêm o mesmo comportamento do formulário interno. |
25
- | `cardClassName` | `string` | | Classes aplicadas à superfície do card. `className` continua sendo aplicado ao `<form>`. |
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 podem rolar.
6
- O rodapé agrupa as decisões em um `ButtonGroup` cuja largura acompanha o conteúdo: `Cancelar`
7
- aparece em `ghost` e a ação principal permanece `solid`, ambas no tamanho normal de uma decisão
8
- modal. `Cancelar` está sempre presente; sem `onCancel` próprio, ele fecha o modal.
9
- Com `footerDistribution="equal"`, as duas decisões dividem a faixa e `Cancelar` passa a `outline`,
10
- salvo escolha explícita em `cancelVariant`.
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` | | Nome da tarefa exibido no cabeçalho. |
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. |