@softize/opus 22.2.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/bin/cli.mjs +3 -9
  3. package/bin/lib/check.mjs +242 -49
  4. package/bin/lib/copy.mjs +17 -35
  5. package/bin/lib/gen-runner.mjs +2 -0
  6. package/bin/lib/gen-stubs.mjs +3 -1
  7. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  9. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
  10. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
  11. package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
  12. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  13. package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
  14. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
  15. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
  16. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  17. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  18. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  19. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  20. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  21. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  22. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  23. package/docs/code-style.md +1 -1
  24. package/package.json +5 -1
  25. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  26. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  27. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  28. package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
  29. package/registry/templates/app/src/App.tsx +24 -12
  30. package/src/core/contracts.ts +1 -1
  31. package/src/core/runtime.ts +4 -37
  32. package/src/core/types.ts +5 -23
  33. package/src/design/index.ts +14 -3
  34. package/src/testing/index.ts +6 -26
  35. package/src/ui/appearance.ts +149 -0
  36. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  37. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  38. package/src/ui/components/patterns/confirm.tsx +3 -1
  39. package/src/ui/components/patterns/fill-region.ts +8 -7
  40. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  41. package/src/ui/components/patterns/form.tsx +8 -7
  42. package/src/ui/components/patterns/list.tsx +64 -14
  43. package/src/ui/components/patterns/page.tsx +15 -11
  44. package/src/ui/components/patterns/pane.tsx +9 -240
  45. package/src/ui/components/patterns/presentation.tsx +14 -14
  46. package/src/ui/components/patterns/sidebar.tsx +7 -17
  47. package/src/ui/components/patterns/split.tsx +111 -59
  48. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  49. package/src/ui/components/patterns/structural-media.tsx +26 -0
  50. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  51. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  52. package/src/ui/components/patterns/surface.tsx +620 -0
  53. package/src/ui/components/patterns/trigger.tsx +11 -9
  54. package/src/ui/components/primitives/ask.tsx +155 -83
  55. package/src/ui/components/primitives/button.tsx +11 -9
  56. package/src/ui/components/primitives/checkbox.tsx +8 -8
  57. package/src/ui/components/primitives/command.tsx +1 -1
  58. package/src/ui/components/primitives/composer.tsx +47 -45
  59. package/src/ui/components/primitives/detail.tsx +9 -2
  60. package/src/ui/components/primitives/dialog.tsx +60 -26
  61. package/src/ui/components/primitives/drawer.tsx +58 -12
  62. package/src/ui/components/primitives/icon-picker.tsx +93 -70
  63. package/src/ui/components/primitives/input-group.tsx +35 -28
  64. package/src/ui/components/primitives/input-otp.tsx +16 -16
  65. package/src/ui/components/primitives/input.tsx +20 -10
  66. package/src/ui/components/primitives/item.tsx +9 -12
  67. package/src/ui/components/primitives/menu.tsx +44 -65
  68. package/src/ui/components/primitives/metric-card.tsx +75 -59
  69. package/src/ui/components/primitives/radio-group.tsx +9 -9
  70. package/src/ui/components/primitives/select.tsx +344 -209
  71. package/src/ui/components/primitives/switch.tsx +10 -10
  72. package/src/ui/components/primitives/table.tsx +29 -24
  73. package/src/ui/components/primitives/tabs.tsx +58 -88
  74. package/src/ui/components/primitives/textarea.tsx +6 -6
  75. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  76. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  77. package/src/ui/components/primitives/toggle.tsx +82 -16
  78. package/src/ui/docs/DocBrowser.tsx +77 -69
  79. package/src/ui/docs/content/action-form-card.md +9 -9
  80. package/src/ui/docs/content/action-list.md +85 -70
  81. package/src/ui/docs/content/action-view.md +33 -18
  82. package/src/ui/docs/content/actions.md +6 -7
  83. package/src/ui/docs/content/button.md +1 -1
  84. package/src/ui/docs/content/communication.md +1 -1
  85. package/src/ui/docs/content/customization.md +64 -11
  86. package/src/ui/docs/content/detail.md +8 -6
  87. package/src/ui/docs/content/dialog.md +100 -58
  88. package/src/ui/docs/content/drawer.md +63 -19
  89. package/src/ui/docs/content/field.md +14 -15
  90. package/src/ui/docs/content/icon-tile.md +14 -13
  91. package/src/ui/docs/content/page.md +34 -26
  92. package/src/ui/docs/content/pane.md +41 -290
  93. package/src/ui/docs/content/presentation.md +3 -3
  94. package/src/ui/docs/content/semantic-context.md +0 -1
  95. package/src/ui/docs/content/separator.md +6 -6
  96. package/src/ui/docs/content/sidebar.md +68 -266
  97. package/src/ui/docs/content/skeleton.md +20 -18
  98. package/src/ui/docs/content/split.md +14 -14
  99. package/src/ui/docs/content/surface.md +513 -0
  100. package/src/ui/docs/content/tabs.md +82 -83
  101. package/src/ui/docs/content/testing.md +2 -3
  102. package/src/ui/docs/content/toggle.md +120 -63
  103. package/src/ui/docs/content/tokens.md +124 -40
  104. package/src/ui/docs/registry.tsx +4 -6
  105. package/src/ui/drivers/react.tsx +4 -3
  106. package/src/ui/index.ts +30 -27
  107. package/src/ui/meta.ts +15 -27
  108. package/src/ui/react.tsx +42 -46
  109. package/src/ui/theme.css +311 -64
  110. package/src/vite/design.ts +23 -21
  111. package/src/ui/components/patterns/content-header.tsx +0 -532
  112. package/src/ui/components/patterns/surface-media.tsx +0 -69
  113. package/src/ui/components/primitives/card.tsx +0 -191
  114. package/src/ui/docs/content/card.md +0 -292
  115. package/src/ui/docs/content/content.md +0 -276
@@ -1,83 +1,86 @@
1
- import * as React from 'react'
2
- import type { UiContext } from '../../../core/ui-context.ts'
3
- import { cn } from '../../lib/cn.ts'
4
- import { Card } from './card.tsx'
5
- import { Skeleton } from './skeleton.tsx'
1
+ import * as React from "react";
2
+ import type { UiContext } from "../../../core/ui-context.ts";
3
+ import { cn } from "../../lib/cn.ts";
4
+ import { Skeleton } from "./skeleton.tsx";
5
+ import { Surface } from "../patterns/surface.tsx";
6
6
 
7
7
  /** @deprecated Use `MetricCardContext`. */
8
- export type MetricCardTone = 'default' | 'warning'
9
- export type MetricCardContext = Exclude<UiContext, 'primary'>
8
+ export type MetricCardTone = "default" | "warning";
9
+ export type MetricCardContext = Exclude<UiContext, "primary">;
10
10
 
11
11
  export interface MetricCardSecondaryMetric {
12
12
  /** Nome curto da medida complementar. */
13
- label: React.ReactNode
13
+ label: React.ReactNode;
14
14
  /** Valor complementar já formatado pelo consumidor. */
15
- value: React.ReactNode
15
+ value: React.ReactNode;
16
16
  /** Controle complementar próprio, como uma explicação em tooltip. */
17
- labelAction?: React.ReactNode
17
+ labelAction?: React.ReactNode;
18
18
  }
19
19
 
20
20
  const iconContextClasses: Record<MetricCardContext, string> = {
21
- neutral: 'bg-context-neutral-subtle text-context-neutral-emphasis',
22
- info: 'bg-context-info-subtle text-context-info-emphasis',
23
- success: 'bg-context-success-subtle text-context-success-emphasis',
24
- warning: 'bg-context-warning-subtle text-context-warning-emphasis',
25
- danger: 'bg-context-danger-subtle text-context-danger-emphasis',
26
- }
21
+ neutral: "bg-context-neutral-subtle text-context-neutral-emphasis",
22
+ info: "bg-context-info-subtle text-context-info-emphasis",
23
+ success: "bg-context-success-subtle text-context-success-emphasis",
24
+ warning: "bg-context-warning-subtle text-context-warning-emphasis",
25
+ danger: "bg-context-danger-subtle text-context-danger-emphasis",
26
+ };
27
27
 
28
- interface MetricCardBaseProps extends Omit<React.ComponentProps<typeof Card>, 'children' | 'asChild'> {
28
+ interface MetricCardBaseProps extends Omit<
29
+ React.ComponentProps<"div">,
30
+ "children" | "title"
31
+ > {
29
32
  /** Controle complementar exibido ao lado do rótulo, como uma explicação em tooltip. */
30
- labelAction?: React.ReactNode
33
+ labelAction?: React.ReactNode;
31
34
  /** Contexto que explica recorte, proporção ou significado do valor. */
32
- description?: React.ReactNode
35
+ description?: React.ReactNode;
33
36
  /** Ícone decorativo que identifica a natureza da medida. */
34
- icon?: React.ReactNode
37
+ icon?: React.ReactNode;
35
38
  /** Contexto semântico do ícone; não altera a superfície do card. */
36
- context?: MetricCardContext
39
+ context?: MetricCardContext;
37
40
  /** @deprecated Use `context`. Compatibilidade temporária da ADR 0006. */
38
- tone?: MetricCardTone
41
+ tone?: MetricCardTone;
39
42
  /** Ação relacionada diretamente à medida. */
40
- action?: React.ReactNode
43
+ action?: React.ReactNode;
41
44
  /** Medida complementar exibida como uma lâmina posterior sob o card principal. */
42
- secondaryMetric?: MetricCardSecondaryMetric
45
+ secondaryMetric?: MetricCardSecondaryMetric;
43
46
  }
44
47
 
45
48
  type MetricCardActivationProps =
46
49
  | {
47
50
  /** Indica se a medida controla o recorte atualmente aplicado. */
48
- active: boolean
51
+ active: boolean;
49
52
  /** Ativa a medida como recorte ou seleção. */
50
- onActivate: () => void
53
+ onActivate: () => void;
51
54
  /** Nome acessível da ativação, como “Filtrar por Agendados”. */
52
- activationLabel: string
55
+ activationLabel: string;
53
56
  /** Use `onActivate`; o clique genérico conflita com os controles internos do card. */
54
- onClick?: never
57
+ onClick?: never;
55
58
  }
56
59
  | {
57
- active?: never
58
- onActivate?: never
59
- activationLabel?: never
60
- }
60
+ active?: never;
61
+ onActivate?: never;
62
+ activationLabel?: never;
63
+ };
61
64
 
62
65
  export type MetricCardProps = MetricCardBaseProps &
63
66
  MetricCardActivationProps &
64
67
  (
65
68
  | {
66
69
  /** Nome curto da medida apresentada. */
67
- label: React.ReactNode
70
+ label: React.ReactNode;
68
71
  /** Valor já formatado pelo consumidor. */
69
- value: React.ReactNode
70
- loading?: false
72
+ value: React.ReactNode;
73
+ loading?: false;
71
74
  }
72
75
  | {
73
76
  /** O rótulo pode ser omitido porque o placeholder ocupa toda a composição. */
74
- label?: React.ReactNode
77
+ label?: React.ReactNode;
75
78
  /** O valor ainda não está disponível durante o carregamento. */
76
- value?: React.ReactNode
79
+ value?: React.ReactNode;
77
80
  /** Substitui o conteúdo por placeholders preservando a geometria do card. */
78
- loading: true
81
+ loading: true;
79
82
  }
80
- )
83
+ );
81
84
 
82
85
  /**
83
86
  * MetricCard apresenta uma medida resumida. Formatação, cálculo e carregamento dos dados
@@ -101,19 +104,24 @@ export function MetricCard({
101
104
  ...props
102
105
  }: MetricCardProps): React.ReactElement {
103
106
  if (context !== undefined && tone !== undefined) {
104
- throw new Error('MetricCard não permite combinar context com tone legado.')
107
+ throw new Error("MetricCard não permite combinar context com tone legado.");
105
108
  }
106
- const hasIcon = icon !== undefined && icon !== null
107
- const resolvedContext = context ?? (tone === 'warning' ? 'warning' : 'neutral')
108
- const interactive = onActivate !== undefined
109
+ const hasIcon = icon !== undefined && icon !== null;
110
+ const resolvedContext =
111
+ context ?? (tone === "warning" ? "warning" : "neutral");
112
+ const interactive = onActivate !== undefined;
109
113
 
110
114
  const primaryCard = (
111
- <Card
115
+ <Surface
116
+ framed
117
+ elevation="raised"
112
118
  data-slot="metric-card"
113
- aria-busy={secondaryMetric === undefined ? loading || undefined : undefined}
119
+ aria-busy={
120
+ secondaryMetric === undefined ? loading || undefined : undefined
121
+ }
114
122
  className={cn(
115
- 'min-w-0 p-4',
116
- secondaryMetric !== undefined && 'relative z-10',
123
+ "min-w-0 p-4",
124
+ secondaryMetric !== undefined && "relative z-10",
117
125
  secondaryMetric === undefined && !interactive && className,
118
126
  )}
119
127
  {...(secondaryMetric === undefined && !interactive ? props : {})}
@@ -146,7 +154,7 @@ export function MetricCard({
146
154
  data-context={resolvedContext}
147
155
  aria-hidden="true"
148
156
  className={cn(
149
- 'flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4',
157
+ "flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4",
150
158
  iconContextClasses[resolvedContext],
151
159
  )}
152
160
  >
@@ -161,7 +169,10 @@ export function MetricCard({
161
169
  {value}
162
170
  </div>
163
171
  {description !== undefined && (
164
- <div data-slot="metric-card-description" className="mt-1 text-xs text-muted-foreground">
172
+ <div
173
+ data-slot="metric-card-description"
174
+ className="mt-1 text-xs text-muted-foreground"
175
+ >
165
176
  {description}
166
177
  </div>
167
178
  )}
@@ -172,8 +183,8 @@ export function MetricCard({
172
183
  )}
173
184
  </>
174
185
  )}
175
- </Card>
176
- )
186
+ </Surface>
187
+ );
177
188
 
178
189
  const metric =
179
190
  secondaryMetric === undefined ? (
@@ -183,10 +194,12 @@ export function MetricCard({
183
194
  {...(!interactive ? props : {})}
184
195
  data-slot="metric-card-stack"
185
196
  aria-busy={loading || undefined}
186
- className={cn('relative isolate min-w-0', !interactive && className)}
197
+ className={cn("relative isolate min-w-0", !interactive && className)}
187
198
  >
188
199
  {primaryCard}
189
- <Card
200
+ <Surface
201
+ framed
202
+ elevation="raised"
190
203
  data-slot="metric-card-secondary"
191
204
  className="relative z-0 -mt-8 flex w-full min-w-0 items-center gap-1.5 bg-[color-mix(in_oklab,var(--card)_88%,black)] px-4 pt-9 pb-1"
192
205
  >
@@ -208,18 +221,21 @@ export function MetricCard({
208
221
  {secondaryMetric.label}
209
222
  </div>
210
223
  {secondaryMetric.labelAction !== undefined && (
211
- <div data-slot="metric-card-secondary-label-action" className="shrink-0">
224
+ <div
225
+ data-slot="metric-card-secondary-label-action"
226
+ className="shrink-0"
227
+ >
212
228
  {secondaryMetric.labelAction}
213
229
  </div>
214
230
  )}
215
231
  </div>
216
232
  </>
217
233
  )}
218
- </Card>
234
+ </Surface>
219
235
  </div>
220
- )
236
+ );
221
237
 
222
- if (!interactive) return metric
238
+ if (!interactive) return metric;
223
239
 
224
240
  return (
225
241
  <div
@@ -230,7 +246,7 @@ export function MetricCard({
230
246
  // Ativa = a borda na cor primária, e só. Anel somaria uma segunda linha de
231
247
  // outra cor à borda e, pintado para fora, sumiria no primeiro ancestral com
232
248
  // `overflow-hidden`.
233
- 'relative isolate min-w-0 data-[active=true]:[&_[data-slot=metric-card]]:border-primary',
249
+ "relative isolate min-w-0 data-[active=true]:[&_[data-slot=metric-card]]:border-primary",
234
250
  className,
235
251
  )}
236
252
  >
@@ -247,5 +263,5 @@ export function MetricCard({
247
263
  {metric}
248
264
  </div>
249
265
  </div>
250
- )
266
+ );
251
267
  }
@@ -1,8 +1,8 @@
1
- import * as React from "react"
2
- import { CircleIcon } from "lucide-react"
3
- import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
1
+ import * as React from "react";
2
+ import { CircleIcon } from "lucide-react";
3
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
4
4
 
5
- import { cn } from '../../lib/cn.ts'
5
+ import { cn } from "../../lib/cn.ts";
6
6
 
7
7
  function RadioGroup({
8
8
  className,
@@ -14,7 +14,7 @@ function RadioGroup({
14
14
  className={cn("grid gap-3", className)}
15
15
  {...props}
16
16
  />
17
- )
17
+ );
18
18
  }
19
19
 
20
20
  function RadioGroupItem({
@@ -25,8 +25,8 @@ function RadioGroupItem({
25
25
  <RadioGroupPrimitive.Item
26
26
  data-slot="radio-group-item"
27
27
  className={cn(
28
- "peer aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
29
- className
28
+ "peer aspect-square size-4 shrink-0 rounded-full border border-input bg-control text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40",
29
+ className,
30
30
  )}
31
31
  {...props}
32
32
  >
@@ -37,7 +37,7 @@ function RadioGroupItem({
37
37
  <CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-primary" />
38
38
  </RadioGroupPrimitive.Indicator>
39
39
  </RadioGroupPrimitive.Item>
40
- )
40
+ );
41
41
  }
42
42
 
43
- export { RadioGroup, RadioGroupItem }
43
+ export { RadioGroup, RadioGroupItem };