@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,88 +1,362 @@
1
- import type { ReactNode } from 'react'
2
- import { cn } from '../../lib/cn.ts'
1
+ import {
2
+ Children,
3
+ Fragment,
4
+ createContext,
5
+ isValidElement,
6
+ useContext,
7
+ useId,
8
+ type HTMLAttributes,
9
+ type ReactElement,
10
+ type ReactNode,
11
+ } from "react";
12
+ import { cn } from "../../lib/cn.ts";
3
13
 
4
- export type ContentHeaderLevel = 1 | 2 | 3 | 4 | 5 | 6
5
- export type ContentHeaderVariant = 'page' | 'section'
14
+ export type ContentHeaderLevel = 1 | 2 | 3 | 4 | 5 | 6;
15
+ export type ContentHeaderVariant = "page" | "section";
6
16
 
7
- export interface ContentHeaderProps {
8
- title: ReactNode
9
- /** Conteúdo complementar exibido ao lado do título, como uma contagem. */
10
- meta?: ReactNode
11
- description?: ReactNode
12
- actions?: ReactNode
17
+ interface ContentContextValue {
18
+ level: ContentHeaderLevel;
19
+ titleId: string;
20
+ variant: ContentHeaderVariant;
21
+ }
22
+
23
+ const ContentContext = createContext<ContentContextValue | null>(null);
24
+ const ContentHeaderContext = createContext<ContentContextValue | null>(null);
25
+
26
+ function requiredContext<T>(
27
+ value: T | null,
28
+ component: string,
29
+ parent: string,
30
+ ): T {
31
+ if (value === null)
32
+ throw new Error(`${component} deve estar dentro de ${parent}.`);
33
+ return value;
34
+ }
35
+
36
+ interface ContentBaseProps extends Omit<
37
+ HTMLAttributes<HTMLElement>,
38
+ "children" | "title"
39
+ > {
13
40
  /** Nível semântico do heading, independente de seu destaque visual. */
14
- level?: ContentHeaderLevel
15
- /** Hierarquia visual: destaque principal ou cabeçalho interno de seção. */
16
- variant?: ContentHeaderVariant
17
- className?: string
41
+ level?: ContentHeaderLevel;
42
+ /** Hierarquia visual. `page` permanece somente para compatibilidade durante a versão 12. */
43
+ variant?: ContentHeaderVariant;
18
44
  }
19
45
 
20
- interface ContentHeaderSlots {
21
- root?: string
22
- heading: string
23
- actions: string
46
+ interface ContentShorthandProps extends ContentBaseProps {
47
+ title: ReactNode;
48
+ /** Conteúdo complementar exibido ao lado do título, como uma contagem. */
49
+ meta?: ReactNode;
50
+ description?: ReactNode;
51
+ actions?: ReactNode;
52
+ children: ReactNode;
24
53
  }
25
54
 
26
- interface ContentHeaderFrameProps extends ContentHeaderProps {
27
- slots: ContentHeaderSlots
55
+ interface ContentComposedProps extends ContentBaseProps {
56
+ title?: never;
57
+ meta?: never;
58
+ description?: never;
59
+ actions?: never;
60
+ children: ReactNode;
28
61
  }
29
62
 
30
- const headingTags = {
31
- 1: 'h1',
32
- 2: 'h2',
33
- 3: 'h3',
34
- 4: 'h4',
35
- 5: 'h5',
36
- 6: 'h6',
37
- } as const
38
-
39
- /** Layout compartilhado com Page; mantém os slots históricos sem expô-los na API pública. */
40
- export function ContentHeaderFrame({
63
+ export type ContentProps = ContentShorthandProps | ContentComposedProps;
64
+
65
+ /** Região de conteúdo nomeada. A forma curta e a explícita produzem a mesma anatomia. */
66
+ export function Content({
41
67
  title,
42
68
  meta,
43
69
  description,
44
70
  actions,
45
71
  level = 2,
46
- variant = 'section',
72
+ variant = "section",
73
+ children,
47
74
  className,
48
- slots,
49
- }: ContentHeaderFrameProps): React.ReactElement {
50
- const Heading = headingTags[level]
51
- const page = variant === 'page'
75
+ ...props
76
+ }: ContentProps): ReactElement {
77
+ const titleId = useId();
78
+ const context = { level, titleId, variant };
79
+ const shorthand = title !== undefined;
80
+ const nodes = Children.toArray(children);
81
+
82
+ if (
83
+ shorthand &&
84
+ nodes.some((node) => isValidElement(node) && node.type === ContentHeader)
85
+ ) {
86
+ throw new Error(
87
+ "Content não permite misturar propriedades de shorthand com ContentHeader explícito.",
88
+ );
89
+ }
90
+ if (!shorthand) {
91
+ const headers = nodes.filter(
92
+ (node) => isValidElement(node) && node.type === ContentHeader,
93
+ );
94
+ const bodies = nodes.filter(
95
+ (node) => isValidElement(node) && node.type === ContentBody,
96
+ );
97
+ if (
98
+ headers.length !== 1 ||
99
+ bodies.length !== 1 ||
100
+ headers.length + bodies.length !== nodes.length
101
+ ) {
102
+ throw new Error(
103
+ "Content explícito exige exatamente um ContentHeader e um ContentBody como filhos diretos.",
104
+ );
105
+ }
106
+ }
52
107
 
53
108
  return (
54
- <div
55
- {...(slots.root === undefined ? {} : { 'data-slot': slots.root })}
56
- className={cn(actions !== undefined && 'flex items-end justify-between gap-4', className)}
57
- >
58
- <div data-slot={slots.heading} className="min-w-0">
59
- <Heading
60
- className={cn(
61
- 'flex items-baseline gap-2 font-semibold',
62
- page ? 'text-2xl tracking-tight' : 'text-sm',
63
- )}
64
- >
65
- {title}
66
- {meta}
67
- </Heading>
109
+ <ContentContext.Provider value={context}>
110
+ <section
111
+ data-slot="content"
112
+ aria-labelledby={
113
+ props["aria-label"] === undefined ? titleId : undefined
114
+ }
115
+ className={cn("space-y-3", className)}
116
+ {...props}
117
+ >
118
+ {shorthand ? (
119
+ <>
120
+ <ContentHeader>
121
+ <ContentTitle>{title}</ContentTitle>
122
+ {meta !== undefined && <ContentMeta>{meta}</ContentMeta>}
123
+ {description !== undefined && (
124
+ <ContentDescription>{description}</ContentDescription>
125
+ )}
126
+ {actions !== undefined && (
127
+ <ContentActions>{actions}</ContentActions>
128
+ )}
129
+ </ContentHeader>
130
+ <ContentBody>{children}</ContentBody>
131
+ </>
132
+ ) : (
133
+ children
134
+ )}
135
+ </section>
136
+ </ContentContext.Provider>
137
+ );
138
+ }
139
+
140
+ interface ContentHeaderShorthandProps extends Omit<
141
+ HTMLAttributes<HTMLDivElement>,
142
+ "children" | "title"
143
+ > {
144
+ /** @deprecated Prefira os slots explícitos ou o shorthand de Content. */
145
+ title: ReactNode;
146
+ meta?: ReactNode;
147
+ description?: ReactNode;
148
+ actions?: ReactNode;
149
+ /** @deprecated Declare o nível em Content. Compatibilidade da série 12. */
150
+ level?: ContentHeaderLevel;
151
+ /** @deprecated Declare a hierarquia visual em Content. Compatibilidade da série 12. */
152
+ variant?: ContentHeaderVariant;
153
+ children?: never;
154
+ }
155
+
156
+ interface ContentHeaderComposedProps extends Omit<
157
+ HTMLAttributes<HTMLDivElement>,
158
+ "title"
159
+ > {
160
+ /** @deprecated Declare o nível em Content. Compatibilidade da série 12. */
161
+ level?: ContentHeaderLevel;
162
+ /** @deprecated Declare a hierarquia visual em Content. Compatibilidade da série 12. */
163
+ variant?: ContentHeaderVariant;
164
+ title?: never;
165
+ meta?: never;
166
+ description?: never;
167
+ actions?: never;
168
+ children: ReactNode;
169
+ }
170
+
171
+ export type ContentHeaderProps =
172
+ ContentHeaderShorthandProps | ContentHeaderComposedProps;
173
+
174
+ /** Header estrutural de Content; não é uma região autônoma. */
175
+ export function ContentHeader({
176
+ title,
177
+ meta,
178
+ description,
179
+ actions,
180
+ level,
181
+ variant,
182
+ children,
183
+ className,
184
+ ...props
185
+ }: ContentHeaderProps): ReactElement {
186
+ const parent = useContext(ContentContext);
187
+ const standaloneTitleId = useId();
188
+ const shorthand = title !== undefined;
189
+ if (parent === null && !shorthand) {
190
+ throw new Error("ContentHeader deve estar dentro de Content.");
191
+ }
192
+ const content: ContentContextValue = {
193
+ level: level ?? parent?.level ?? 2,
194
+ titleId: parent?.titleId ?? standaloneTitleId,
195
+ variant: variant ?? parent?.variant ?? "section",
196
+ };
197
+ const nodes = Children.toArray(
198
+ shorthand ? (
199
+ <>
200
+ <ContentTitle>{title}</ContentTitle>
201
+ {meta !== undefined && <ContentMeta>{meta}</ContentMeta>}
68
202
  {description !== undefined && (
69
- <p className={cn('truncate text-muted-foreground', page ? 'mt-1 text-sm' : 'mt-0.5 text-xs')}>{description}</p>
203
+ <ContentDescription>{description}</ContentDescription>
70
204
  )}
71
- </div>
72
- {actions !== undefined && (
73
- <div data-slot={slots.actions} className={cn('flex shrink-0 items-center', page ? 'gap-4' : 'gap-2')}>
74
- {actions}
205
+ {actions !== undefined && <ContentActions>{actions}</ContentActions>}
206
+ </>
207
+ ) : (
208
+ children
209
+ ),
210
+ ).flatMap((node) =>
211
+ isValidElement(node) && node.type === Fragment
212
+ ? Children.toArray((node.props as { children?: ReactNode }).children)
213
+ : [node],
214
+ );
215
+ const titles = nodes.filter(
216
+ (node) => isValidElement(node) && node.type === ContentTitle,
217
+ );
218
+ const metas = nodes.filter(
219
+ (node) => isValidElement(node) && node.type === ContentMeta,
220
+ );
221
+ const descriptions = nodes.filter(
222
+ (node) => isValidElement(node) && node.type === ContentDescription,
223
+ );
224
+ const actionSlots = nodes.filter(
225
+ (node) => isValidElement(node) && node.type === ContentActions,
226
+ );
227
+ const recognized =
228
+ titles.length + metas.length + descriptions.length + actionSlots.length;
229
+
230
+ if (
231
+ titles.length !== 1 ||
232
+ metas.length > 1 ||
233
+ descriptions.length > 1 ||
234
+ actionSlots.length > 1 ||
235
+ recognized !== nodes.length
236
+ ) {
237
+ throw new Error(
238
+ "ContentHeader exige um ContentTitle e aceita no máximo um ContentDescription, ContentMeta e ContentActions como filhos diretos.",
239
+ );
240
+ }
241
+
242
+ return (
243
+ <ContentHeaderContext.Provider value={content}>
244
+ <div
245
+ data-slot="content-header"
246
+ className={cn(
247
+ actionSlots.length > 0 &&
248
+ "flex flex-col items-start gap-4 sm:flex-row sm:items-end sm:justify-between",
249
+ className,
250
+ )}
251
+ {...props}
252
+ >
253
+ <div data-slot="content-header-heading" className="min-w-0">
254
+ <div className="flex items-baseline gap-2">
255
+ {titles}
256
+ {metas}
257
+ </div>
258
+ {descriptions}
75
259
  </div>
260
+ {actionSlots}
261
+ </div>
262
+ </ContentHeaderContext.Provider>
263
+ );
264
+ }
265
+
266
+ const headingTags = {
267
+ 1: "h1",
268
+ 2: "h2",
269
+ 3: "h3",
270
+ 4: "h4",
271
+ 5: "h5",
272
+ 6: "h6",
273
+ } as const;
274
+
275
+ export function ContentTitle({
276
+ className,
277
+ id,
278
+ ...props
279
+ }: HTMLAttributes<HTMLHeadingElement>): ReactElement {
280
+ const context = requiredContext(
281
+ useContext(ContentHeaderContext),
282
+ "ContentTitle",
283
+ "ContentHeader",
284
+ );
285
+ const Heading = headingTags[context.level];
286
+ return (
287
+ <Heading
288
+ id={id ?? context.titleId}
289
+ data-slot="content-title"
290
+ className={cn(
291
+ "font-semibold",
292
+ context.variant === "page" ? "text-2xl tracking-tight" : "text-sm",
293
+ className,
76
294
  )}
77
- </div>
78
- )
295
+ {...props}
296
+ />
297
+ );
79
298
  }
80
299
 
81
- export function ContentHeader(props: ContentHeaderProps): React.ReactElement {
300
+ export function ContentDescription({
301
+ className,
302
+ ...props
303
+ }: HTMLAttributes<HTMLParagraphElement>): ReactElement {
304
+ const context = requiredContext(
305
+ useContext(ContentHeaderContext),
306
+ "ContentDescription",
307
+ "ContentHeader",
308
+ );
82
309
  return (
83
- <ContentHeaderFrame
310
+ <p
311
+ data-slot="content-description"
312
+ className={cn(
313
+ "truncate text-muted-foreground",
314
+ context.variant === "page" ? "mt-1 text-sm" : "mt-0.5 text-xs",
315
+ className,
316
+ )}
84
317
  {...props}
85
- slots={{ root: 'content-header', heading: 'content-header-heading', actions: 'content-header-actions' }}
86
318
  />
87
- )
319
+ );
320
+ }
321
+
322
+ export function ContentMeta({
323
+ className,
324
+ ...props
325
+ }: HTMLAttributes<HTMLSpanElement>): ReactElement {
326
+ requiredContext(
327
+ useContext(ContentHeaderContext),
328
+ "ContentMeta",
329
+ "ContentHeader",
330
+ );
331
+ return <span data-slot="content-meta" className={className} {...props} />;
332
+ }
333
+
334
+ export function ContentActions({
335
+ className,
336
+ ...props
337
+ }: HTMLAttributes<HTMLDivElement>): ReactElement {
338
+ const context = requiredContext(
339
+ useContext(ContentHeaderContext),
340
+ "ContentActions",
341
+ "ContentHeader",
342
+ );
343
+ return (
344
+ <div
345
+ data-slot="content-actions"
346
+ className={cn(
347
+ "flex w-full shrink-0 items-center sm:w-auto",
348
+ context.variant === "page" ? "gap-4" : "gap-2",
349
+ className,
350
+ )}
351
+ {...props}
352
+ />
353
+ );
354
+ }
355
+
356
+ export function ContentBody({
357
+ className,
358
+ ...props
359
+ }: HTMLAttributes<HTMLDivElement>): ReactElement {
360
+ requiredContext(useContext(ContentContext), "ContentBody", "Content");
361
+ return <div data-slot="content-body" className={className} {...props} />;
88
362
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import * as React from 'react'
11
11
  import { cn } from '../../lib/cn.ts'
12
+ import { Alert } from '../primitives/alert.tsx'
12
13
  import { Button } from '../primitives/button.tsx'
13
14
  import { Spinner } from '../primitives/spinner.tsx'
14
15
 
@@ -50,8 +51,9 @@ export function DataState({
50
51
 
51
52
  // Modo TABELA: o estado vira UMA linha de largura cheia (<td colSpan>), pra caber no <tbody>.
52
53
  if (colSpan !== undefined) {
54
+ const status = error ? 'error' : loading ? 'loading' : 'empty'
53
55
  return (
54
- <tr>
56
+ <tr data-slot="data-state" data-status={status}>
55
57
  <td
56
58
  colSpan={colSpan}
57
59
  className={cn('px-4 py-10 text-center text-sm', error ? 'text-destructive' : 'text-muted-foreground/60')}
@@ -78,25 +80,36 @@ export function DataState({
78
80
  // Modo BLOCO (default).
79
81
  if (error) {
80
82
  return (
81
- <div className="flex flex-col items-center gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 text-center text-sm text-destructive">
82
- <span>{errorText ?? 'Não foi possível carregar.'}</span>
83
- {onRetry !== undefined && (
84
- <Button variant="outline" size="sm" onClick={() => void onRetry()}>
85
- {retryText}
86
- </Button>
87
- )}
83
+ <div data-slot="data-state" data-status="error">
84
+ <Alert context="danger" description={errorText ?? 'Não foi possível carregar.'}>
85
+ {onRetry !== undefined && (
86
+ <Button variant="outline" size="sm" onClick={() => void onRetry()}>
87
+ {retryText}
88
+ </Button>
89
+ )}
90
+ </Alert>
88
91
  </div>
89
92
  )
90
93
  }
91
94
  if (loading) {
92
95
  return (
93
- <div className="py-10 text-center text-muted-foreground">
96
+ <div
97
+ data-slot="data-state"
98
+ data-status="loading"
99
+ role="status"
100
+ aria-live="polite"
101
+ className="py-10 text-center text-muted-foreground"
102
+ >
94
103
  <Spinner className="mx-auto" />
95
104
  </div>
96
105
  )
97
106
  }
98
107
  return (
99
- <p className="rounded-lg border border-border py-10 text-center text-sm text-muted-foreground/60">
108
+ <p
109
+ data-slot="data-state"
110
+ data-status="empty"
111
+ className="rounded-lg border border-border py-10 text-center text-sm text-muted-foreground/60"
112
+ >
100
113
  {emptyText}
101
114
  </p>
102
115
  )