@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,72 +1,115 @@
1
1
  import * as React from 'react'
2
2
  import { Slot } from '@radix-ui/react-slot'
3
- import { cva, type VariantProps } from 'class-variance-authority'
3
+
4
+ import type { UiContext } from '../../../core/ui-context.ts'
4
5
  import { cn } from '../../lib/cn.ts'
5
6
  import type { ControlShape } from './control.ts'
6
7
  import { Spinner } from './spinner.tsx'
7
8
 
9
+ export type ButtonContext = Extract<UiContext, 'neutral' | 'primary' | 'danger'>
10
+ export type ButtonVariant = 'solid' | 'subtle' | 'outline' | 'ghost' | 'link'
11
+ export type LegacyButtonVariant = 'default' | 'secondary' | 'destructive'
12
+ export type ButtonSize = 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm' | 'icon-xs'
13
+
14
+ export interface ButtonStyleOptions {
15
+ context?: ButtonContext | null
16
+ variant?: ButtonVariant | LegacyButtonVariant | null
17
+ size?: ButtonSize | null
18
+ shape?: ControlShape | null
19
+ }
20
+
21
+ const baseClasses =
22
+ 'inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 active:brightness-90 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4'
23
+
24
+ const sizeClasses: Record<ButtonSize, string> = {
25
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
26
+ sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
27
+ lg: 'h-10 px-6 has-[>svg]:px-4',
28
+ icon: 'size-9',
29
+ 'icon-sm': 'size-8',
30
+ 'icon-xs': 'size-6',
31
+ }
32
+
33
+ const solidClasses: Record<ButtonContext, string> = {
34
+ neutral: 'bg-context-neutral text-context-neutral-foreground hover:brightness-95',
35
+ primary: 'bg-context-primary text-context-primary-foreground hover:brightness-95',
36
+ danger: 'bg-context-danger text-context-danger-foreground hover:brightness-95 focus-visible:ring-context-danger/20',
37
+ }
38
+
39
+ const subtleClasses: Record<ButtonContext, string> = {
40
+ neutral: 'bg-context-neutral-subtle text-foreground hover:bg-context-neutral',
41
+ primary: 'bg-context-primary-subtle text-context-primary-emphasis hover:bg-context-primary/15',
42
+ danger: 'bg-context-danger-subtle text-context-danger-emphasis hover:bg-context-danger/15',
43
+ }
44
+
45
+ const outlineClasses: Record<ButtonContext, string> = {
46
+ neutral: 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',
47
+ primary: 'border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground',
48
+ danger: 'border border-context-danger-border bg-background text-context-danger-emphasis hover:bg-context-danger-subtle',
49
+ }
50
+
51
+ const ghostClasses: Record<ButtonContext, string> = {
52
+ neutral: 'text-foreground hover:bg-accent hover:text-accent-foreground',
53
+ primary: 'text-context-primary-emphasis hover:bg-context-primary-subtle',
54
+ danger: 'text-context-danger-emphasis hover:bg-context-danger-subtle',
55
+ }
56
+
57
+ const linkClasses: Record<ButtonContext, string> = {
58
+ neutral: 'text-foreground underline-offset-4 hover:underline',
59
+ primary: 'text-context-primary-emphasis underline-offset-4 hover:underline',
60
+ danger: 'text-context-danger-emphasis underline-offset-4 hover:underline',
61
+ }
62
+
63
+ export function resolveButtonStyle({ context, variant }: ButtonStyleOptions = {}): {
64
+ context: ButtonContext
65
+ variant: ButtonVariant
66
+ } {
67
+ const legacy = variant === 'default' || variant === 'secondary' || variant === 'destructive'
68
+ if (context != null && legacy) {
69
+ throw new Error('Button não permite combinar context com uma variante semântica legada.')
70
+ }
71
+ if (variant === 'default') return { context: 'primary', variant: 'solid' }
72
+ if (variant === 'secondary') return { context: 'neutral', variant: 'solid' }
73
+ if (variant === 'destructive') return { context: 'danger', variant: 'solid' }
74
+ return { context: context ?? 'primary', variant: variant ?? 'solid' }
75
+ }
76
+
8
77
  /**
9
- * Variantes do button (cva). Exportadas pra compor com asChild (ex.: estilizar um
10
- * <a> com a cara de botão: `<a className={buttonVariants({ variant: 'outline' })}>`).
11
- * `active:brightness-90` é toque da casa (feedback de clique) — fora do shadcn de propósito.
12
- * Era `active:scale-95`, mas press GEOMÉTRICO move o rect do gatilho e todo popover
13
- * ancorado persegue a âncora durante a transição (a "dançada" do menu).
78
+ * Classes do Button. `context` seleciona a família semântica e `variant` seleciona
79
+ * o tratamento visual. `default`, `secondary` e `destructive` são aliases temporários.
14
80
  */
15
- export const buttonVariants = cva(
16
- "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 active:brightness-90 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
17
- {
18
- variants: {
19
- variant: {
20
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
21
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
22
- outline:
23
- 'border border-input bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:hover:bg-input/50',
24
- ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
25
- destructive:
26
- 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40',
27
- link: 'text-primary underline-offset-4 hover:underline',
28
- },
29
- size: {
30
- default: 'h-9 px-4 py-2 has-[>svg]:px-3',
31
- sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
32
- lg: 'h-10 px-6 has-[>svg]:px-4',
33
- icon: 'size-9',
34
- // Ícone na escala `sm` (2rem) — o par do `sm` para fileiras densas, onde os irmãos
35
- // também usam `sm`. Sem ele, um botão de ícone ao lado desses controles ficava
36
- // 0.25rem mais alto ou virava override local.
37
- 'icon-sm': 'size-8',
38
- // Ícone menor (1.5rem) — a ação dentro de um campo (clear/mostrar senha no
39
- // `trailing` do Input/Select), que precisa caber folgado num h-9. É o mesmo tamanho
40
- // do `icon-xs` do InputGroupButton; sem ele, o exemplo virava `className="size-6"`.
41
- 'icon-xs': 'size-6',
42
- },
43
- shape: {
44
- default: '',
45
- pill: 'rounded-full',
46
- },
47
- },
48
- defaultVariants: { variant: 'default', size: 'default', shape: 'default' },
49
- },
50
- )
81
+ export function buttonVariants(options: ButtonStyleOptions = {}): string {
82
+ const resolved = resolveButtonStyle(options)
83
+ const variantClasses =
84
+ resolved.variant === 'solid'
85
+ ? solidClasses[resolved.context]
86
+ : resolved.variant === 'subtle'
87
+ ? subtleClasses[resolved.context]
88
+ : resolved.variant === 'outline'
89
+ ? outlineClasses[resolved.context]
90
+ : resolved.variant === 'ghost'
91
+ ? ghostClasses[resolved.context]
92
+ : linkClasses[resolved.context]
93
+ return cn(
94
+ baseClasses,
95
+ variantClasses,
96
+ sizeClasses[options.size ?? 'default'],
97
+ options.shape === 'pill' && 'rounded-full',
98
+ )
99
+ }
51
100
 
52
- export interface ButtonProps
53
- extends React.ComponentProps<'button'>,
54
- VariantProps<typeof buttonVariants> {
101
+ export interface ButtonProps extends React.ComponentProps<'button'>, ButtonStyleOptions {
55
102
  /** Renderiza como o filho (Radix Slot) em vez de <button> — recompõe sem forkar. */
56
103
  asChild?: boolean
57
- /** Estado "processando": ação em andamento DEPOIS do clique (submit, request) mostra
58
- * Spinner à esquerda e desabilita. NÃO é "carregando" (ANTES: conteúdo ainda não chegou
59
- * → Spinner centralizado/Skeleton num nível de página, não no botão). Ignorado com asChild. */
104
+ /** Estado de ação em andamento; mostra Spinner e desabilita o controle. */
60
105
  busy?: boolean
61
- /** Ícone à esquerda do texto (o componente, ex.: `icon={Plus}`). No `busy` é TROCADO
62
- * pelo Spinner — não soma (evita ícone + spinner juntos). Ignorado com asChild. */
106
+ /** Ícone à esquerda. No `busy`, o Spinner ocupa o mesmo espaço. */
63
107
  icon?: React.ElementType
64
- /** Geometria do controle; `pill` não altera cor nem hierarquia da variante. */
65
- shape?: ControlShape
66
108
  }
67
109
 
68
110
  export function Button({
69
111
  className,
112
+ context,
70
113
  variant,
71
114
  size,
72
115
  shape,
@@ -78,13 +121,14 @@ export function Button({
78
121
  ...props
79
122
  }: ButtonProps): React.ReactElement {
80
123
  const Comp = asChild ? Slot : 'button'
124
+ const resolved = resolveButtonStyle({ context, variant })
81
125
  const iconSize = size === 'sm' ? 'size-3' : 'size-4'
82
- // busy implica disabled; o feedback visual vem do disabled:opacity-50 da cva. O slot
83
- // à esquerda é UM só: Spinner no busy, senão o ícone (icon e spinner nunca somam).
84
126
  return (
85
127
  <Comp
86
128
  data-slot="button"
87
- className={cn(buttonVariants({ variant, size, shape }), className)}
129
+ data-context={resolved.context}
130
+ data-variant={resolved.variant}
131
+ className={cn(buttonVariants({ ...resolved, size, shape }), className)}
88
132
  disabled={disabled || busy}
89
133
  {...props}
90
134
  >
@@ -103,8 +147,3 @@ export function Button({
103
147
  </Comp>
104
148
  )
105
149
  }
106
-
107
- /**
108
- * Metadado de descoberta — lido pelo manifesto/MCP em build-time (não vai pro runtime;
109
- * o barrel não re-exporta `meta`). Escrever o componente já o documenta.
110
- */
@@ -1,17 +1,17 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
  import {
3
3
  ChevronDownIcon,
4
4
  ChevronLeftIcon,
5
5
  ChevronRightIcon,
6
- } from "lucide-react"
6
+ } from "lucide-react";
7
7
  import {
8
8
  DayPicker,
9
9
  getDefaultClassNames,
10
10
  type DayButton,
11
- } from "react-day-picker"
11
+ } from "react-day-picker";
12
12
 
13
- import { cn } from '../../lib/cn.ts'
14
- import { Button, buttonVariants } from './button.tsx'
13
+ import { cn } from "../../lib/cn.ts";
14
+ import { Button, buttonVariants } from "./button.tsx";
15
15
 
16
16
  function Calendar({
17
17
  className,
@@ -23,18 +23,18 @@ function Calendar({
23
23
  components,
24
24
  ...props
25
25
  }: React.ComponentProps<typeof DayPicker> & {
26
- buttonVariant?: React.ComponentProps<typeof Button>["variant"]
26
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
27
27
  }) {
28
- const defaultClassNames = getDefaultClassNames()
28
+ const defaultClassNames = getDefaultClassNames();
29
29
 
30
30
  return (
31
31
  <DayPicker
32
32
  showOutsideDays={showOutsideDays}
33
33
  className={cn(
34
- "group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
34
+ "group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-body]_&]:bg-transparent [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
35
35
  String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
36
36
  String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
37
- className
37
+ className,
38
38
  )}
39
39
  captionLayout={captionLayout}
40
40
  formatters={{
@@ -46,85 +46,85 @@ function Calendar({
46
46
  root: cn("w-fit", defaultClassNames.root),
47
47
  months: cn(
48
48
  "relative flex flex-col gap-4 md:flex-row",
49
- defaultClassNames.months
49
+ defaultClassNames.months,
50
50
  ),
51
51
  month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
52
52
  nav: cn(
53
53
  "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
54
- defaultClassNames.nav
54
+ defaultClassNames.nav,
55
55
  ),
56
56
  button_previous: cn(
57
57
  buttonVariants({ variant: buttonVariant }),
58
58
  "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
59
- defaultClassNames.button_previous
59
+ defaultClassNames.button_previous,
60
60
  ),
61
61
  button_next: cn(
62
62
  buttonVariants({ variant: buttonVariant }),
63
63
  "size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
64
- defaultClassNames.button_next
64
+ defaultClassNames.button_next,
65
65
  ),
66
66
  month_caption: cn(
67
67
  "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
68
- defaultClassNames.month_caption
68
+ defaultClassNames.month_caption,
69
69
  ),
70
70
  dropdowns: cn(
71
71
  "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
72
- defaultClassNames.dropdowns
72
+ defaultClassNames.dropdowns,
73
73
  ),
74
74
  dropdown_root: cn(
75
75
  "relative rounded-md border border-input has-focus:border-ring has-focus:ring-[0.1875rem] has-focus:ring-ring/50",
76
- defaultClassNames.dropdown_root
76
+ defaultClassNames.dropdown_root,
77
77
  ),
78
78
  dropdown: cn(
79
79
  "absolute inset-0 bg-popover text-popover-foreground opacity-0",
80
- defaultClassNames.dropdown
80
+ defaultClassNames.dropdown,
81
81
  ),
82
82
  caption_label: cn(
83
83
  "font-medium select-none",
84
84
  captionLayout === "label"
85
85
  ? "text-sm"
86
86
  : "flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
87
- defaultClassNames.caption_label
87
+ defaultClassNames.caption_label,
88
88
  ),
89
89
  month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
90
90
  weekdays: cn("flex", defaultClassNames.weekdays),
91
91
  weekday: cn(
92
92
  "flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none",
93
- defaultClassNames.weekday
93
+ defaultClassNames.weekday,
94
94
  ),
95
95
  week: cn("mt-2 flex w-full", defaultClassNames.week),
96
96
  week_number_header: cn(
97
97
  "w-(--cell-size) select-none",
98
- defaultClassNames.week_number_header
98
+ defaultClassNames.week_number_header,
99
99
  ),
100
100
  week_number: cn(
101
101
  "text-[0.8rem] text-muted-foreground select-none",
102
- defaultClassNames.week_number
102
+ defaultClassNames.week_number,
103
103
  ),
104
104
  day: cn(
105
105
  "group/day relative aspect-square h-full w-full p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-md",
106
106
  props.showWeekNumber
107
107
  ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
108
108
  : "[&:first-child[data-selected=true]_button]:rounded-l-md",
109
- defaultClassNames.day
109
+ defaultClassNames.day,
110
110
  ),
111
111
  range_start: cn(
112
112
  "rounded-l-md bg-accent",
113
- defaultClassNames.range_start
113
+ defaultClassNames.range_start,
114
114
  ),
115
115
  range_middle: cn("rounded-none", defaultClassNames.range_middle),
116
116
  range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
117
117
  today: cn(
118
118
  "rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none",
119
- defaultClassNames.today
119
+ defaultClassNames.today,
120
120
  ),
121
121
  outside: cn(
122
122
  "text-muted-foreground aria-selected:text-muted-foreground",
123
- defaultClassNames.outside
123
+ defaultClassNames.outside,
124
124
  ),
125
125
  disabled: cn(
126
126
  "text-muted-foreground opacity-50",
127
- defaultClassNames.disabled
127
+ defaultClassNames.disabled,
128
128
  ),
129
129
  hidden: cn("invisible", defaultClassNames.hidden),
130
130
  ...classNames,
@@ -138,13 +138,13 @@ function Calendar({
138
138
  className={cn(className)}
139
139
  {...props}
140
140
  />
141
- )
141
+ );
142
142
  },
143
143
  Chevron: ({ className, orientation, ...props }) => {
144
144
  if (orientation === "left") {
145
145
  return (
146
146
  <ChevronLeftIcon className={cn("size-4", className)} {...props} />
147
- )
147
+ );
148
148
  }
149
149
 
150
150
  if (orientation === "right") {
@@ -153,12 +153,12 @@ function Calendar({
153
153
  className={cn("size-4", className)}
154
154
  {...props}
155
155
  />
156
- )
156
+ );
157
157
  }
158
158
 
159
159
  return (
160
160
  <ChevronDownIcon className={cn("size-4", className)} {...props} />
161
- )
161
+ );
162
162
  },
163
163
  DayButton: CalendarDayButton,
164
164
  WeekNumber: ({ children, ...props }) => {
@@ -168,13 +168,13 @@ function Calendar({
168
168
  {children}
169
169
  </div>
170
170
  </td>
171
- )
171
+ );
172
172
  },
173
173
  ...components,
174
174
  }}
175
175
  {...props}
176
176
  />
177
- )
177
+ );
178
178
  }
179
179
 
180
180
  function CalendarDayButton({
@@ -183,12 +183,12 @@ function CalendarDayButton({
183
183
  modifiers,
184
184
  ...props
185
185
  }: React.ComponentProps<typeof DayButton>) {
186
- const defaultClassNames = getDefaultClassNames()
186
+ const defaultClassNames = getDefaultClassNames();
187
187
 
188
- const ref = React.useRef<HTMLButtonElement>(null)
188
+ const ref = React.useRef<HTMLButtonElement>(null);
189
189
  React.useEffect(() => {
190
- if (modifiers.focused) ref.current?.focus()
191
- }, [modifiers.focused])
190
+ if (modifiers.focused) ref.current?.focus();
191
+ }, [modifiers.focused]);
192
192
 
193
193
  return (
194
194
  <Button
@@ -208,11 +208,11 @@ function CalendarDayButton({
208
208
  className={cn(
209
209
  "flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[0.1875rem] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70",
210
210
  defaultClassNames.day,
211
- className
211
+ className,
212
212
  )}
213
213
  {...props}
214
214
  />
215
- )
215
+ );
216
216
  }
217
217
 
218
- export { Calendar, CalendarDayButton }
218
+ export { Calendar, CalendarDayButton };
@@ -1,56 +1,129 @@
1
- import * as React from 'react'
2
- import { Slot } from '@radix-ui/react-slot'
3
- import { cn } from '../../lib/cn.ts'
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cn } from "../../lib/cn.ts";
4
4
 
5
5
  /**
6
6
  * Card — a superfície da casa: SÓ a casca (borda + raio + bg). Divergência DECLARADA vs
7
7
  * shadcn: FLAT (sem `shadow-sm`) e SEM `flex/gap/py` forçados. O upstream embute
8
8
  * `py-6 gap-6` + slots `px-6`, o que briga com card-box
9
9
  * simples (o `gap-6` do Card + o `space-y` do consumidor = espaçamento dobrado) — por isso
10
- * a casa hand-rollava. Aqui o padding mora no SLOT (Header/Content/Footer) ou no `className`
10
+ * a casa hand-rollava. Aqui o padding mora no SLOT (Header/Body/Footer) ou no `className`
11
11
  * do consumidor (box simples) — mesmo modelo do Dialog.
12
12
  *
13
13
  * Box simples: <Card className="p-4">…</Card>
14
- * Estruturado: <Card><CardHeader>…</CardHeader><CardContent>…</CardContent><CardFooter>…</CardFooter></Card>
14
+ * Estruturado: <Card><CardHeader>…</CardHeader><CardBody>…</CardBody><CardFooter>…</CardFooter></Card>
15
15
  */
16
16
  export function Card({
17
17
  className,
18
18
  asChild = false,
19
19
  ...props
20
- }: React.ComponentProps<'div'> & {
20
+ }: React.ComponentProps<"div"> & {
21
21
  /** Renderiza como o filho (Radix Slot) — pra card CLICÁVEL manter a semântica do filho
22
22
  * (ex.: <button>) com a superfície do Card: `<Card asChild><button onClick>…</button></Card>`. */
23
- asChild?: boolean
23
+ asChild?: boolean;
24
24
  }): React.ReactElement {
25
- const Comp = asChild ? Slot : 'div'
25
+ const Comp = asChild ? Slot : "div";
26
26
  return (
27
- <Comp data-slot="card" className={cn('rounded-xl border border-border bg-card text-card-foreground', className)} {...props} />
28
- )
27
+ <Comp
28
+ data-slot="card"
29
+ className={cn(
30
+ "rounded-xl border border-border bg-card text-card-foreground",
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ );
29
36
  }
30
37
 
31
- export function CardHeader({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
38
+ export function CardHeader({
39
+ className,
40
+ ...props
41
+ }: React.ComponentProps<"div">): React.ReactElement {
32
42
  return (
33
- <div data-slot="card-header" className={cn('flex flex-col gap-1.5 px-5 pt-5 pb-3', className)} {...props} />
34
- )
43
+ <div
44
+ data-slot="card-header"
45
+ className={cn("flex flex-col gap-1.5 px-5 pt-5 pb-3", className)}
46
+ {...props}
47
+ />
48
+ );
35
49
  }
36
50
 
37
- export function CardTitle({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
38
- return <div data-slot="card-title" className={cn('font-semibold leading-none', className)} {...props} />
51
+ export function CardTitle({
52
+ className,
53
+ ...props
54
+ }: React.ComponentProps<"div">): React.ReactElement {
55
+ return (
56
+ <div
57
+ data-slot="card-title"
58
+ className={cn("font-semibold leading-none", className)}
59
+ {...props}
60
+ />
61
+ );
39
62
  }
40
63
 
41
- export function CardDescription({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
42
- return <div data-slot="card-description" className={cn('text-sm text-muted-foreground', className)} {...props} />
64
+ export function CardDescription({
65
+ className,
66
+ ...props
67
+ }: React.ComponentProps<"div">): React.ReactElement {
68
+ return (
69
+ <div
70
+ data-slot="card-description"
71
+ className={cn("text-sm text-muted-foreground", className)}
72
+ {...props}
73
+ />
74
+ );
43
75
  }
44
76
 
45
77
  /** Slot de ação no header (botão/menu) — o consumidor posiciona (ex.: header em flex-row). */
46
- export function CardAction({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
47
- return <div data-slot="card-action" className={cn('shrink-0', className)} {...props} />
78
+ export function CardAction({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<"div">): React.ReactElement {
82
+ return (
83
+ <div
84
+ data-slot="card-action"
85
+ className={cn("shrink-0", className)}
86
+ {...props}
87
+ />
88
+ );
48
89
  }
49
90
 
50
- export function CardContent({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
51
- return <div data-slot="card-content" className={cn('px-5 py-3', className)} {...props} />
91
+ export function CardBody({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"div">): React.ReactElement {
95
+ return (
96
+ <div
97
+ data-slot="card-body"
98
+ className={cn("px-5 py-3", className)}
99
+ {...props}
100
+ />
101
+ );
52
102
  }
53
103
 
54
- export function CardFooter({ className, ...props }: React.ComponentProps<'div'>): React.ReactElement {
55
- return <div data-slot="card-footer" className={cn('flex items-center px-5 pt-3 pb-5', className)} {...props} />
104
+ /** @deprecated Use CardBody. Mantido durante a versão 12 para migração gradual. */
105
+ export function CardContent({
106
+ className,
107
+ ...props
108
+ }: React.ComponentProps<"div">): React.ReactElement {
109
+ return (
110
+ <div
111
+ data-slot="card-content"
112
+ className={cn("px-5 py-3", className)}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ export function CardFooter({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<"div">): React.ReactElement {
122
+ return (
123
+ <div
124
+ data-slot="card-footer"
125
+ className={cn("flex items-center px-5 pt-3 pb-5", className)}
126
+ {...props}
127
+ />
128
+ );
56
129
  }
@@ -41,7 +41,7 @@ function DetailGroup({
41
41
  data-dividers={dividers}
42
42
  data-orientation={orientation}
43
43
  className={cn(
44
- 'group/detail-group grid min-w-0',
44
+ 'group/detail-group grid min-w-0 [--detail-label-width:7rem]',
45
45
  columnClasses[columns],
46
46
  variant === 'plain' && !dividers && 'gap-x-6 gap-y-4',
47
47
  dividers && 'gap-px overflow-hidden',
@@ -88,7 +88,7 @@ function DetailField({
88
88
  data-slot="detail-field"
89
89
  className={cn(
90
90
  'grid min-w-0 items-start',
91
- 'group-data-[orientation=vertical]/detail-group:grid-cols-1 group-data-[orientation=horizontal]/detail-group:grid-cols-[minmax(0,auto)_minmax(0,1fr)]',
91
+ 'group-data-[orientation=vertical]/detail-group:grid-cols-1 group-data-[orientation=horizontal]/detail-group:grid-cols-[minmax(0,var(--detail-label-width))_minmax(0,1fr)]',
92
92
  'group-data-[orientation=vertical]/detail-group:gap-y-1 group-data-[orientation=horizontal]/detail-group:items-baseline group-data-[orientation=horizontal]/detail-group:gap-x-3',
93
93
  'group-data-[dividers=true]/detail-group:outline group-data-[dividers=true]/detail-group:outline-1 group-data-[dividers=true]/detail-group:outline-border',
94
94
  'group-data-[variant=framed]/detail-group:bg-card group-data-[variant=framed]/detail-group:text-card-foreground',