@softize/opus 12.10.0 → 13.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 (154) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/bin/lib/check.mjs +1098 -310
  3. package/bin/lib/copy.mjs +12 -5
  4. package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
  5. package/docs/adr/0004-page-content-state-is-composed.md +65 -0
  6. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +180 -0
  7. package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
  8. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  9. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  10. package/docs/radius-scale.md +1 -1
  11. package/package.json +1 -1
  12. package/registry/instructions/opus.md +5 -0
  13. package/registry/skills/build-opus-ui/SKILL.md +27 -16
  14. package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
  15. package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
  16. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  17. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  18. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  19. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  20. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  21. package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
  22. package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
  23. package/registry/templates/app/src/App.tsx +1 -1
  24. package/src/core/dictionary.ts +52 -14
  25. package/src/core/index.ts +10 -0
  26. package/src/core/ui-context.ts +29 -0
  27. package/src/schema/drivers/zod.ts +17 -8
  28. package/src/ui/components/patterns/action-form-card.tsx +18 -12
  29. package/src/ui/components/patterns/confirm.tsx +163 -40
  30. package/src/ui/components/patterns/content-header.tsx +335 -61
  31. package/src/ui/components/patterns/data-state.tsx +23 -10
  32. package/src/ui/components/patterns/list.tsx +1097 -783
  33. package/src/ui/components/patterns/page-state.tsx +115 -0
  34. package/src/ui/components/patterns/page.tsx +231 -41
  35. package/src/ui/components/patterns/sidebar.tsx +357 -83
  36. package/src/ui/components/patterns/trigger.tsx +37 -30
  37. package/src/ui/components/patterns/view.tsx +7 -11
  38. package/src/ui/components/primitives/alert.tsx +298 -110
  39. package/src/ui/components/primitives/ask.tsx +2 -1
  40. package/src/ui/components/primitives/badge.tsx +91 -30
  41. package/src/ui/components/primitives/button.tsx +99 -60
  42. package/src/ui/components/primitives/calendar.tsx +39 -39
  43. package/src/ui/components/primitives/card.tsx +96 -23
  44. package/src/ui/components/primitives/detail.tsx +2 -2
  45. package/src/ui/components/primitives/dialog.tsx +196 -39
  46. package/src/ui/components/primitives/dictionary-value.tsx +9 -14
  47. package/src/ui/components/primitives/dot.tsx +74 -21
  48. package/src/ui/components/primitives/drawer.tsx +40 -24
  49. package/src/ui/components/primitives/empty.tsx +3 -3
  50. package/src/ui/components/primitives/item.tsx +135 -79
  51. package/src/ui/components/primitives/menu.tsx +11 -3
  52. package/src/ui/components/primitives/metric-card.tsx +133 -0
  53. package/src/ui/components/primitives/sonner.tsx +187 -8
  54. package/src/ui/components/primitives/table.tsx +2 -2
  55. package/src/ui/docs/DocBrowser.tsx +104 -25
  56. package/src/ui/docs/changelog.tsx +1 -1
  57. package/src/ui/docs/content/accordion.md +22 -16
  58. package/src/ui/docs/content/action-form-card.md +8 -8
  59. package/src/ui/docs/content/action-form-dialog.md +9 -9
  60. package/src/ui/docs/content/action-form.md +28 -34
  61. package/src/ui/docs/content/action-list-dialog.md +11 -6
  62. package/src/ui/docs/content/action-list.md +64 -39
  63. package/src/ui/docs/content/action-trigger.md +21 -14
  64. package/src/ui/docs/content/action-view.md +8 -8
  65. package/src/ui/docs/content/actions.md +9 -9
  66. package/src/ui/docs/content/ai.md +3 -3
  67. package/src/ui/docs/content/alert.md +54 -28
  68. package/src/ui/docs/content/aspect-ratio.md +4 -4
  69. package/src/ui/docs/content/audit.md +2 -2
  70. package/src/ui/docs/content/auth.md +3 -3
  71. package/src/ui/docs/content/avatar.md +34 -14
  72. package/src/ui/docs/content/badge.md +21 -22
  73. package/src/ui/docs/content/breadcrumb.md +13 -8
  74. package/src/ui/docs/content/button.md +93 -15
  75. package/src/ui/docs/content/calendar.md +5 -5
  76. package/src/ui/docs/content/card.md +6 -6
  77. package/src/ui/docs/content/carousel.md +16 -11
  78. package/src/ui/docs/content/chat.md +3 -3
  79. package/src/ui/docs/content/checkbox.md +7 -7
  80. package/src/ui/docs/content/cli.md +5 -5
  81. package/src/ui/docs/content/collapsible.md +8 -8
  82. package/src/ui/docs/content/command.md +16 -8
  83. package/src/ui/docs/content/composer.md +2 -2
  84. package/src/ui/docs/content/content.md +44 -0
  85. package/src/ui/docs/content/copyable.md +4 -3
  86. package/src/ui/docs/content/customization.md +7 -7
  87. package/src/ui/docs/content/cycle.md +3 -3
  88. package/src/ui/docs/content/data-state.md +11 -12
  89. package/src/ui/docs/content/data.md +26 -33
  90. package/src/ui/docs/content/detail.md +8 -5
  91. package/src/ui/docs/content/dialog.md +339 -31
  92. package/src/ui/docs/content/dictionary-value.md +19 -18
  93. package/src/ui/docs/content/dock.md +3 -3
  94. package/src/ui/docs/content/dot.md +7 -7
  95. package/src/ui/docs/content/drawer.md +32 -16
  96. package/src/ui/docs/content/empty-value.md +2 -2
  97. package/src/ui/docs/content/empty.md +19 -12
  98. package/src/ui/docs/content/events.md +4 -4
  99. package/src/ui/docs/content/field.md +34 -12
  100. package/src/ui/docs/content/getting-started.md +1 -1
  101. package/src/ui/docs/content/icon-picker.md +8 -4
  102. package/src/ui/docs/content/input-otp.md +20 -12
  103. package/src/ui/docs/content/input.md +121 -9
  104. package/src/ui/docs/content/item.md +64 -24
  105. package/src/ui/docs/content/kbd.md +19 -11
  106. package/src/ui/docs/content/label.md +5 -3
  107. package/src/ui/docs/content/log.md +4 -4
  108. package/src/ui/docs/content/markdown.md +7 -6
  109. package/src/ui/docs/content/mcp.md +13 -15
  110. package/src/ui/docs/content/menu.md +36 -17
  111. package/src/ui/docs/content/metric-card.md +41 -0
  112. package/src/ui/docs/content/observability.md +2 -2
  113. package/src/ui/docs/content/page.md +93 -10
  114. package/src/ui/docs/content/pagination.md +22 -17
  115. package/src/ui/docs/content/popover.md +16 -8
  116. package/src/ui/docs/content/progress.md +7 -5
  117. package/src/ui/docs/content/queue.md +5 -5
  118. package/src/ui/docs/content/radio-group.md +20 -12
  119. package/src/ui/docs/content/router.md +11 -6
  120. package/src/ui/docs/content/scheduler.md +4 -5
  121. package/src/ui/docs/content/scroll-area.md +12 -7
  122. package/src/ui/docs/content/select.md +42 -29
  123. package/src/ui/docs/content/semantic-context.md +63 -0
  124. package/src/ui/docs/content/separator.md +5 -5
  125. package/src/ui/docs/content/sidebar.md +325 -56
  126. package/src/ui/docs/content/skeleton.md +5 -4
  127. package/src/ui/docs/content/slider.md +8 -7
  128. package/src/ui/docs/content/spinner.md +8 -8
  129. package/src/ui/docs/content/split.md +8 -5
  130. package/src/ui/docs/content/storage.md +6 -8
  131. package/src/ui/docs/content/switch.md +8 -7
  132. package/src/ui/docs/content/table.md +16 -6
  133. package/src/ui/docs/content/tabs.md +28 -14
  134. package/src/ui/docs/content/testing.md +9 -11
  135. package/src/ui/docs/content/textarea.md +5 -4
  136. package/src/ui/docs/content/toast.md +47 -13
  137. package/src/ui/docs/content/toggle.md +75 -7
  138. package/src/ui/docs/content/tokens.md +31 -3
  139. package/src/ui/docs/content/tooltip.md +19 -11
  140. package/src/ui/docs/content/truncate.md +7 -8
  141. package/src/ui/docs/content/ui.md +10 -9
  142. package/src/ui/docs/content/upgrading.md +7 -8
  143. package/src/ui/docs/doc-client.tsx +2 -2
  144. package/src/ui/docs/registry.tsx +580 -229
  145. package/src/ui/lib/semantic-context.ts +30 -0
  146. package/src/ui/meta.ts +278 -286
  147. package/src/ui/react.tsx +377 -111
  148. package/src/ui/theme.css +116 -0
  149. package/src/ui/components/primitives/alert-dialog.tsx +0 -190
  150. package/src/ui/docs/content/alert-dialog.md +0 -73
  151. package/src/ui/docs/content/button-group.md +0 -71
  152. package/src/ui/docs/content/confirm.md +0 -120
  153. package/src/ui/docs/content/input-group.md +0 -78
  154. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -1,35 +1,134 @@
1
1
  import * as React from 'react'
2
2
  import * as DialogPrimitive from '@radix-ui/react-dialog'
3
+ import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
3
4
  import { XIcon } from 'lucide-react'
4
5
  import { cn } from '../../lib/cn.ts'
5
6
 
6
- export function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
7
- return <DialogPrimitive.Root data-slot="dialog" {...props} />
7
+ export type DialogMode = 'default' | 'alert'
8
+
9
+ interface DialogContextValue {
10
+ mode: DialogMode
11
+ onResult?: (result: string) => void
12
+ }
13
+
14
+ const DialogContext = React.createContext<DialogContextValue>({ mode: 'default' })
15
+
16
+ function useDialogContext(): DialogContextValue {
17
+ return React.useContext(DialogContext)
18
+ }
19
+
20
+ export interface DialogProps
21
+ extends React.ComponentProps<typeof DialogPrimitive.Root> {
22
+ /** `alert` exige uma resposta explícita e deriva `role="alertdialog"`. */
23
+ mode?: DialogMode
24
+ /** Recebe o `result` do `DialogClose` acionado. */
25
+ onResult?: (result: string) => void
26
+ }
27
+
28
+ export function Dialog({
29
+ mode = 'default',
30
+ onResult,
31
+ modal,
32
+ ...props
33
+ }: DialogProps) {
34
+ const context = React.useMemo(() => ({ mode, onResult }), [mode, onResult])
35
+
36
+ return (
37
+ <DialogContext.Provider value={context}>
38
+ {mode === 'alert' ? (
39
+ <AlertDialogPrimitive.Root data-slot="dialog" {...props} />
40
+ ) : (
41
+ <DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />
42
+ )}
43
+ </DialogContext.Provider>
44
+ )
45
+ }
46
+
47
+ export function DialogTrigger({
48
+ ...props
49
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
50
+ const { mode } = useDialogContext()
51
+ return mode === 'alert' ? (
52
+ <AlertDialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
53
+ ) : (
54
+ <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
55
+ )
8
56
  }
9
57
 
10
- export function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
11
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
58
+ export function DialogPortal({
59
+ ...props
60
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
61
+ const { mode } = useDialogContext()
62
+ return mode === 'alert' ? (
63
+ <AlertDialogPrimitive.Portal data-slot="dialog-portal" {...props} />
64
+ ) : (
65
+ <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
66
+ )
12
67
  }
13
68
 
14
- export function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
15
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
69
+ export interface DialogCloseProps
70
+ extends React.ComponentProps<typeof DialogPrimitive.Close> {
71
+ /** No modo `alert`, registra este controle como destino inicial seguro do foco. */
72
+ initialFocus?: boolean
73
+ /** Resposta entregue a `Dialog.onResult` antes do fechamento. */
74
+ result?: string
16
75
  }
17
76
 
18
- export function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
19
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
77
+ export function DialogClose({
78
+ initialFocus = false,
79
+ result,
80
+ onClick,
81
+ ...props
82
+ }: DialogCloseProps) {
83
+ const root = useDialogContext()
84
+ const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
85
+ onClick?.(event)
86
+ if (!event.defaultPrevented && result !== undefined) root.onResult?.(result)
87
+ }
88
+
89
+ if (root.mode === 'alert') {
90
+ const ClosePrimitive = initialFocus
91
+ ? AlertDialogPrimitive.Cancel
92
+ : AlertDialogPrimitive.Action
93
+ return (
94
+ <ClosePrimitive
95
+ data-slot="dialog-close"
96
+ data-initial-focus={initialFocus || undefined}
97
+ onClick={handleClick}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ return (
104
+ <DialogPrimitive.Close
105
+ data-slot="dialog-close"
106
+ data-initial-focus={initialFocus || undefined}
107
+ onClick={handleClick}
108
+ {...props}
109
+ />
110
+ )
20
111
  }
21
112
 
22
113
  export function DialogOverlay({
23
114
  className,
24
115
  ...props
25
116
  }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
26
- return (
117
+ const { mode } = useDialogContext()
118
+ const overlayClasses = cn(
119
+ 'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
120
+ className,
121
+ )
122
+ return mode === 'alert' ? (
123
+ <AlertDialogPrimitive.Overlay
124
+ data-slot="dialog-overlay"
125
+ className={overlayClasses}
126
+ {...props}
127
+ />
128
+ ) : (
27
129
  <DialogPrimitive.Overlay
28
130
  data-slot="dialog-overlay"
29
- className={cn(
30
- 'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
31
- className,
32
- )}
131
+ className={overlayClasses}
33
132
  {...props}
34
133
  />
35
134
  )
@@ -38,29 +137,48 @@ export function DialogOverlay({
38
137
  export function DialogContent({
39
138
  className,
40
139
  children,
41
- showCloseButton = true,
140
+ showCloseButton,
42
141
  ...props
43
142
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
44
- /** Mostra o "X" de fechar no canto. Desligue pra um modal que exige ação explícita. */
143
+ /** Mostra o X no modo padrão. O modo `alert` exige uma resposta identificável. */
45
144
  showCloseButton?: boolean
46
145
  }) {
146
+ const { mode } = useDialogContext()
147
+ const showClose = showCloseButton ?? mode !== 'alert'
148
+ const contentClasses =
149
+ 'fixed left-[50%] top-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-clip rounded-xl border border-border bg-background text-foreground shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95'
150
+
151
+ if (mode === 'alert') {
152
+ return (
153
+ <DialogPortal>
154
+ <DialogOverlay />
155
+ <AlertDialogPrimitive.Content
156
+ data-slot="dialog-content"
157
+ data-mode="alert"
158
+ className={cn(contentClasses, 'grid max-w-xs gap-4 p-6', className)}
159
+ {...props}
160
+ >
161
+ {children}
162
+ </AlertDialogPrimitive.Content>
163
+ </DialogPortal>
164
+ )
165
+ }
166
+
47
167
  return (
48
168
  <DialogPortal>
49
169
  <DialogOverlay />
50
- {/* Superfície elevada: mantemos bg-card (divergência da casa; o shadcn usa bg-background). */}
51
170
  <DialogPrimitive.Content
52
171
  data-slot="dialog-content"
172
+ data-mode="default"
53
173
  className={cn(
54
- // Superfície PURA: só a casca (bg/raio/sombra) + coluna flex com teto de
55
- // altura. O padding mora nos SLOTS (Header/Body/Footer), não aqui — assim ninguém
56
- // precisa negar margem pra escapar de um p-6 embutido.
57
- "fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-clip rounded-xl bg-card text-card-foreground shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
174
+ contentClasses,
175
+ 'flex max-h-[85vh] flex-col sm:max-w-lg',
58
176
  className,
59
177
  )}
60
178
  {...props}
61
179
  >
62
180
  {children}
63
- {showCloseButton && (
181
+ {showClose && (
64
182
  <DialogPrimitive.Close
65
183
  data-slot="dialog-close"
66
184
  className="absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
@@ -74,17 +192,18 @@ export function DialogContent({
74
192
  )
75
193
  }
76
194
 
77
- export function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
195
+ export function DialogHeader({
196
+ className,
197
+ ...props
198
+ }: React.ComponentProps<'div'>) {
199
+ const { mode } = useDialogContext()
78
200
  return (
79
201
  <div
80
202
  data-slot="dialog-header"
81
- // Fixo (shrink-0), com divisor embaixo (border-b). p-5: padding igual nos quatro
82
- // lados, casando com o body. O :has tira o divisor quando o header é seguido
83
- // DIRETO pelo footer (confirm sem body) — senão o border-b dele encostaria no
84
- // A compensação coincide com a soma das hairlines; o divisor fica no footer.
85
203
  className={cn(
86
- 'flex shrink-0 flex-col gap-2 border-b p-5 text-center sm:text-left',
87
- '[&:has(+[data-slot=dialog-footer])]:border-b-0',
204
+ mode === 'alert'
205
+ ? 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=dialog-media]:grid-rows-[auto_auto_1fr]'
206
+ : 'flex shrink-0 flex-col gap-2 border-b p-5 text-center sm:text-left [&:has(+[data-slot=dialog-footer])]:border-b-0',
88
207
  className,
89
208
  )}
90
209
  {...props}
@@ -92,27 +211,51 @@ export function DialogHeader({ className, ...props }: React.ComponentProps<'div'
92
211
  )
93
212
  }
94
213
 
95
- export function DialogBody({ className, ...props }: React.ComponentProps<'div'>) {
214
+ export function DialogMedia({
215
+ className,
216
+ ...props
217
+ }: React.ComponentProps<'div'>) {
218
+ return (
219
+ <div
220
+ data-slot="dialog-media"
221
+ className={cn(
222
+ 'mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted *:[svg:not([class*="size-"])]:size-8',
223
+ className,
224
+ )}
225
+ {...props}
226
+ />
227
+ )
228
+ }
229
+
230
+ export function DialogBody({
231
+ className,
232
+ ...props
233
+ }: React.ComponentProps<'div'>) {
234
+ const { mode } = useDialogContext()
96
235
  return (
97
236
  <div
98
237
  data-slot="dialog-body"
99
- // O miolo que ROLA: cresce, scrolla e padda a si mesmo. Pra corpo edge-to-edge
100
- // (cmdk, iframe de lente) passe `className` zerando o padding.
101
- className={cn('min-h-0 flex-1 overflow-y-auto p-5', className)}
238
+ className={cn(
239
+ mode === 'alert' ? 'min-w-0' : 'min-h-0 flex-1 overflow-y-auto p-5',
240
+ className,
241
+ )}
102
242
  {...props}
103
243
  />
104
244
  )
105
245
  }
106
246
 
107
- export function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
247
+ export function DialogFooter({
248
+ className,
249
+ ...props
250
+ }: React.ComponentProps<'div'>) {
251
+ const { mode } = useDialogContext()
108
252
  return (
109
253
  <div
110
254
  data-slot="dialog-footer"
111
- // Faixa destacada (divergência da casa, ao estilo do site do shadcn): borda em cima
112
- // + fundo muted. Sangra até as bordas sozinha — o content não tem padding, então não
113
- // há -mx pra compensar; o recorte do content arredonda o canto de baixo.
114
255
  className={cn(
115
- 'flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end',
256
+ mode === 'alert'
257
+ ? 'grid grid-cols-2 gap-2 [&>*:only-child]:col-span-full'
258
+ : 'flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end',
116
259
  className,
117
260
  )}
118
261
  {...props}
@@ -124,7 +267,14 @@ export function DialogTitle({
124
267
  className,
125
268
  ...props
126
269
  }: React.ComponentProps<typeof DialogPrimitive.Title>) {
127
- return (
270
+ const { mode } = useDialogContext()
271
+ return mode === 'alert' ? (
272
+ <AlertDialogPrimitive.Title
273
+ data-slot="dialog-title"
274
+ className={cn('text-lg font-semibold', className)}
275
+ {...props}
276
+ />
277
+ ) : (
128
278
  <DialogPrimitive.Title
129
279
  data-slot="dialog-title"
130
280
  className={cn('text-lg font-semibold leading-none', className)}
@@ -137,7 +287,14 @@ export function DialogDescription({
137
287
  className,
138
288
  ...props
139
289
  }: React.ComponentProps<typeof DialogPrimitive.Description>) {
140
- return (
290
+ const { mode } = useDialogContext()
291
+ return mode === 'alert' ? (
292
+ <AlertDialogPrimitive.Description
293
+ data-slot="dialog-description"
294
+ className={cn('text-sm text-muted-foreground', className)}
295
+ {...props}
296
+ />
297
+ ) : (
141
298
  <DialogPrimitive.Description
142
299
  data-slot="dialog-description"
143
300
  className={cn('text-sm text-muted-foreground', className)}
@@ -6,7 +6,6 @@ import {
6
6
  presentDictionaryValue,
7
7
  type DictionaryDescriptor,
8
8
  type DictPresentation,
9
- type DictTone,
10
9
  } from '../../../core/index.ts'
11
10
  import { cn } from '../../lib/cn.ts'
12
11
  import { Badge, type BadgeProps } from './badge.tsx'
@@ -29,7 +28,9 @@ export interface DictionaryValueProps extends Omit<React.ComponentProps<'span'>,
29
28
  value: string | null | undefined
30
29
  /** Sobrepõe o papel declarado no dicionário SÓ nesta ocorrência. */
31
30
  presentation?: DictPresentation
32
- /** Sobrepõe a variante do badge (override explícito). Valor fora do dicionário segue texto. */
31
+ /** Sobrepõe o contexto semântico (override explícito). Valor fora do dicionário segue texto. */
32
+ context?: BadgeProps['context']
33
+ /** Sobrepõe o tratamento visual do badge. Valor fora do dicionário segue texto. */
33
34
  variant?: BadgeProps['variant']
34
35
  /** `false` esconde o ícone declarado; um nó substitui o ícone do catálogo. */
35
36
  icon?: boolean | React.ReactNode
@@ -41,22 +42,14 @@ export interface DictionaryValueProps extends Omit<React.ComponentProps<'span'>,
41
42
  fallback?: React.ReactNode
42
43
  }
43
44
 
44
- const toneVariant: Record<DictTone, NonNullable<BadgeProps['variant']>> = {
45
- neutral: 'secondary',
46
- info: 'info',
47
- success: 'success',
48
- warning: 'warning',
49
- danger: 'danger',
50
- }
51
-
52
45
  function isDescriptor(source: DictionaryValueSource): source is DictionaryDescriptor {
53
46
  return 'entries' in source && 'keys' in source
54
47
  }
55
48
 
56
49
  /**
57
50
  * Renderiza um valor de dicionário com a apresentação que o DICIONÁRIO declarou (ADR 0003):
58
- * classificação → Badge `outline`; status/estágio → Badge tonal pelo `tone` da entrada
59
- * (`neutral` sem tom); `plain` ou sem papel → texto. Ícone só quando a entrada declara `icon`
51
+ * classificação → Badge `neutral + outline`; status/estágio → Badge `context + subtle`
52
+ * (`neutral` sem contexto); `plain` ou sem papel → texto. Ícone só quando a entrada declara `icon`
60
53
  * do catálogo; tooltip só quando `description` acrescenta ao rótulo. O rótulo é sempre texto —
61
54
  * cor e ícone nunca comunicam sozinhos. Valor fora do dicionário mostra o código como texto.
62
55
  */
@@ -64,6 +57,7 @@ export function DictionaryValue({
64
57
  dict,
65
58
  value,
66
59
  presentation,
60
+ context,
67
61
  variant,
68
62
  icon = true,
69
63
  icons = iconPickerIcons,
@@ -102,8 +96,8 @@ export function DictionaryValue({
102
96
  // dicionário é sempre texto — override não promove código desconhecido a badge.
103
97
  const badgeVariant: BadgeProps['variant'] = !resolved.known
104
98
  ? undefined
105
- : variant ??
106
- (resolved.badge === null ? undefined : resolved.badge === 'outline' ? 'outline' : toneVariant[resolved.badge])
99
+ : variant ?? resolved.variant ?? undefined
100
+ const badgeContext: BadgeProps['context'] = context ?? resolved.context ?? undefined
107
101
 
108
102
  const content =
109
103
  badgeVariant === undefined ? (
@@ -119,6 +113,7 @@ export function DictionaryValue({
119
113
  ) : (
120
114
  <Badge
121
115
  data-slot="dictionary-value"
116
+ context={badgeContext}
122
117
  variant={badgeVariant}
123
118
  className={className}
124
119
  {...dataProps}
@@ -1,38 +1,91 @@
1
1
  import * as React from 'react'
2
- import { cva, type VariantProps } from 'class-variance-authority'
2
+
3
+ import type { UiContext } from '../../../core/ui-context.ts'
3
4
  import { cn } from '../../lib/cn.ts'
4
5
 
5
- export const dotVariants = cva('inline-block size-1.5 shrink-0 rounded-full', {
6
- variants: {
7
- variant: {
8
- default: 'bg-primary',
9
- secondary: 'bg-muted-foreground/45',
10
- destructive: 'bg-destructive',
11
- outline: 'border border-border bg-transparent',
12
- success: 'bg-emerald-500',
13
- warning: 'bg-amber-500',
14
- info: 'bg-blue-500',
15
- },
16
- },
17
- defaultVariants: {
18
- variant: 'default',
19
- },
20
- })
21
-
22
- export interface DotProps extends React.ComponentProps<'span'>, VariantProps<typeof dotVariants> {
6
+ export type DotContext = UiContext
7
+ export type DotVariant = 'solid' | 'outline'
8
+ export type LegacyDotVariant =
9
+ | 'default'
10
+ | 'secondary'
11
+ | 'destructive'
12
+ | 'success'
13
+ | 'warning'
14
+ | 'info'
15
+
16
+ interface DotStyleOptions {
17
+ context?: DotContext | null
18
+ variant?: DotVariant | LegacyDotVariant | null
19
+ }
20
+
21
+ const solidClasses: Record<DotContext, string> = {
22
+ neutral: 'bg-context-neutral-emphasis',
23
+ primary: 'bg-context-primary',
24
+ info: 'bg-context-info',
25
+ success: 'bg-context-success',
26
+ warning: 'bg-context-warning',
27
+ danger: 'bg-context-danger',
28
+ }
29
+
30
+ const outlineClasses: Record<DotContext, string> = {
31
+ neutral: 'border border-context-neutral-border bg-transparent',
32
+ primary: 'border border-context-primary bg-transparent',
33
+ info: 'border border-context-info bg-transparent',
34
+ success: 'border border-context-success bg-transparent',
35
+ warning: 'border border-context-warning bg-transparent',
36
+ danger: 'border border-context-danger bg-transparent',
37
+ }
38
+
39
+ export function resolveDotStyle({ context, variant }: DotStyleOptions = {}): {
40
+ context: DotContext
41
+ variant: DotVariant
42
+ } {
43
+ const legacy =
44
+ variant === 'default' ||
45
+ variant === 'secondary' ||
46
+ variant === 'destructive' ||
47
+ variant === 'success' ||
48
+ variant === 'warning' ||
49
+ variant === 'info'
50
+ if (context != null && legacy) {
51
+ throw new Error('Dot não permite combinar context com uma variante semântica legada.')
52
+ }
53
+ if (variant === 'default') return { context: 'primary', variant: 'solid' }
54
+ if (variant === 'secondary') return { context: 'neutral', variant: 'solid' }
55
+ if (variant === 'destructive') return { context: 'danger', variant: 'solid' }
56
+ if (variant === 'success' || variant === 'warning' || variant === 'info') {
57
+ return { context: variant, variant: 'solid' }
58
+ }
59
+ return { context: context ?? 'neutral', variant: variant ?? 'solid' }
60
+ }
61
+
62
+ export function dotVariants(options: DotStyleOptions = {}): string {
63
+ const resolved = resolveDotStyle(options)
64
+ return cn(
65
+ 'inline-block size-1.5 shrink-0 rounded-full',
66
+ resolved.variant === 'outline'
67
+ ? outlineClasses[resolved.context]
68
+ : solidClasses[resolved.context],
69
+ )
70
+ }
71
+
72
+ export interface DotProps extends React.ComponentProps<'span'>, DotStyleOptions {
23
73
  /** Nome acessível quando a cor comunica estado; sem label, o ponto é decorativo. */
24
74
  label?: string
25
75
  }
26
76
 
27
77
  /** Indicador compacto de estado; use Badge quando o texto precisar permanecer visível. */
28
- export function Dot({ className, variant, label, ...props }: DotProps): React.ReactElement {
78
+ export function Dot({ className, context, variant, label, ...props }: DotProps): React.ReactElement {
79
+ const resolved = resolveDotStyle({ context, variant })
29
80
  return (
30
81
  <span
31
82
  data-slot="dot"
83
+ data-context={resolved.context}
84
+ data-variant={resolved.variant}
32
85
  role={label === undefined ? undefined : 'img'}
33
86
  aria-label={label}
34
87
  aria-hidden={label === undefined ? true : undefined}
35
- className={cn(dotVariants({ variant }), className)}
88
+ className={cn(dotVariants(resolved), className)}
36
89
  {...props}
37
90
  />
38
91
  )
@@ -1,29 +1,31 @@
1
- import * as React from "react"
2
- import { XIcon } from "lucide-react"
3
- import { Dialog as DrawerPrimitive } from "radix-ui"
1
+ import * as React from "react";
2
+ import { XIcon } from "lucide-react";
3
+ import { Dialog as DrawerPrimitive } from "radix-ui";
4
4
 
5
- import { cn } from '../../lib/cn.ts'
5
+ import { cn } from "../../lib/cn.ts";
6
6
 
7
- function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
8
- return <DrawerPrimitive.Root data-slot="drawer" {...props} />
7
+ function Drawer({
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
10
+ return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
9
11
  }
10
12
 
11
13
  function DrawerTrigger({
12
14
  ...props
13
15
  }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
14
- return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
16
+ return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
15
17
  }
16
18
 
17
19
  function DrawerClose({
18
20
  ...props
19
21
  }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
20
- return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
22
+ return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
21
23
  }
22
24
 
23
25
  function DrawerPortal({
24
26
  ...props
25
27
  }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
26
- return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
28
+ return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
27
29
  }
28
30
 
29
31
  function DrawerOverlay({
@@ -35,11 +37,11 @@ function DrawerOverlay({
35
37
  data-slot="drawer-overlay"
36
38
  className={cn(
37
39
  "fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
38
- className
40
+ className,
39
41
  )}
40
42
  {...props}
41
43
  />
42
- )
44
+ );
43
45
  }
44
46
 
45
47
  function DrawerContent({
@@ -49,8 +51,8 @@ function DrawerContent({
49
51
  showCloseButton = true,
50
52
  ...props
51
53
  }: React.ComponentProps<typeof DrawerPrimitive.Content> & {
52
- side?: "top" | "right" | "bottom" | "left"
53
- showCloseButton?: boolean
54
+ side?: "top" | "right" | "bottom" | "left";
55
+ showCloseButton?: boolean;
54
56
  }) {
55
57
  return (
56
58
  <DrawerPortal>
@@ -58,7 +60,7 @@ function DrawerContent({
58
60
  <DrawerPrimitive.Content
59
61
  data-slot="drawer-content"
60
62
  className={cn(
61
- "fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
63
+ "fixed z-50 flex flex-col bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
62
64
  side === "right" &&
63
65
  "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
64
66
  side === "left" &&
@@ -67,7 +69,7 @@ function DrawerContent({
67
69
  "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
68
70
  side === "bottom" &&
69
71
  "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
70
- className
72
+ className,
71
73
  )}
72
74
  {...props}
73
75
  >
@@ -80,27 +82,40 @@ function DrawerContent({
80
82
  )}
81
83
  </DrawerPrimitive.Content>
82
84
  </DrawerPortal>
83
- )
85
+ );
84
86
  }
85
87
 
86
88
  function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
87
89
  return (
88
90
  <div
89
91
  data-slot="drawer-header"
90
- className={cn("flex flex-col gap-1.5 p-4", className)}
92
+ className={cn("flex shrink-0 flex-col gap-2 border-b p-5", className)}
93
+ {...props}
94
+ />
95
+ );
96
+ }
97
+
98
+ function DrawerBody({ className, ...props }: React.ComponentProps<"div">) {
99
+ return (
100
+ <div
101
+ data-slot="drawer-body"
102
+ className={cn("min-h-0 flex-1 overflow-y-auto p-5", className)}
91
103
  {...props}
92
104
  />
93
- )
105
+ );
94
106
  }
95
107
 
96
108
  function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
97
109
  return (
98
110
  <div
99
111
  data-slot="drawer-footer"
100
- className={cn("mt-auto flex flex-col gap-2 p-4", className)}
112
+ className={cn(
113
+ "mt-auto flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end",
114
+ className,
115
+ )}
101
116
  {...props}
102
117
  />
103
- )
118
+ );
104
119
  }
105
120
 
106
121
  function DrawerTitle({
@@ -110,10 +125,10 @@ function DrawerTitle({
110
125
  return (
111
126
  <DrawerPrimitive.Title
112
127
  data-slot="drawer-title"
113
- className={cn("font-semibold text-foreground", className)}
128
+ className={cn("text-lg font-semibold leading-none text-foreground", className)}
114
129
  {...props}
115
130
  />
116
- )
131
+ );
117
132
  }
118
133
 
119
134
  function DrawerDescription({
@@ -126,7 +141,7 @@ function DrawerDescription({
126
141
  className={cn("text-sm text-muted-foreground", className)}
127
142
  {...props}
128
143
  />
129
- )
144
+ );
130
145
  }
131
146
 
132
147
  export {
@@ -135,7 +150,8 @@ export {
135
150
  DrawerClose,
136
151
  DrawerContent,
137
152
  DrawerHeader,
153
+ DrawerBody,
138
154
  DrawerFooter,
139
155
  DrawerTitle,
140
156
  DrawerDescription,
141
- }
157
+ };