@softize/opus 22.2.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/bin/cli.mjs +3 -9
  3. package/bin/lib/check.mjs +242 -49
  4. package/bin/lib/copy.mjs +17 -35
  5. package/bin/lib/gen-runner.mjs +2 -0
  6. package/bin/lib/gen-stubs.mjs +3 -1
  7. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
  9. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
  10. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
  11. package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
  12. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  13. package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
  14. package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
  15. package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
  16. package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
  17. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
  18. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
  19. package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
  20. package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
  21. package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
  22. package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
  23. package/docs/code-style.md +1 -1
  24. package/package.json +5 -1
  25. package/registry/skills/build-opus-ui/SKILL.md +11 -11
  26. package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
  27. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
  28. package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
  29. package/registry/templates/app/src/App.tsx +24 -12
  30. package/src/core/contracts.ts +1 -1
  31. package/src/core/runtime.ts +4 -37
  32. package/src/core/types.ts +5 -23
  33. package/src/design/index.ts +14 -3
  34. package/src/testing/index.ts +6 -26
  35. package/src/ui/appearance.ts +149 -0
  36. package/src/ui/components/patterns/action-form-card.tsx +46 -33
  37. package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
  38. package/src/ui/components/patterns/confirm.tsx +3 -1
  39. package/src/ui/components/patterns/fill-region.ts +8 -7
  40. package/src/ui/components/patterns/form-dialog.tsx +1 -3
  41. package/src/ui/components/patterns/form.tsx +8 -7
  42. package/src/ui/components/patterns/list.tsx +64 -14
  43. package/src/ui/components/patterns/page.tsx +15 -11
  44. package/src/ui/components/patterns/pane.tsx +9 -240
  45. package/src/ui/components/patterns/presentation.tsx +14 -14
  46. package/src/ui/components/patterns/sidebar.tsx +7 -17
  47. package/src/ui/components/patterns/split.tsx +111 -59
  48. package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
  49. package/src/ui/components/patterns/structural-media.tsx +26 -0
  50. package/src/ui/components/patterns/surface-anatomy.ts +71 -61
  51. package/src/ui/components/patterns/surface-assistant.tsx +2 -4
  52. package/src/ui/components/patterns/surface.tsx +620 -0
  53. package/src/ui/components/patterns/trigger.tsx +11 -9
  54. package/src/ui/components/primitives/ask.tsx +155 -83
  55. package/src/ui/components/primitives/button.tsx +11 -9
  56. package/src/ui/components/primitives/checkbox.tsx +8 -8
  57. package/src/ui/components/primitives/command.tsx +1 -1
  58. package/src/ui/components/primitives/composer.tsx +47 -45
  59. package/src/ui/components/primitives/detail.tsx +9 -2
  60. package/src/ui/components/primitives/dialog.tsx +60 -26
  61. package/src/ui/components/primitives/drawer.tsx +58 -12
  62. package/src/ui/components/primitives/icon-picker.tsx +93 -70
  63. package/src/ui/components/primitives/input-group.tsx +35 -28
  64. package/src/ui/components/primitives/input-otp.tsx +16 -16
  65. package/src/ui/components/primitives/input.tsx +20 -10
  66. package/src/ui/components/primitives/item.tsx +9 -12
  67. package/src/ui/components/primitives/menu.tsx +44 -65
  68. package/src/ui/components/primitives/metric-card.tsx +75 -59
  69. package/src/ui/components/primitives/radio-group.tsx +9 -9
  70. package/src/ui/components/primitives/select.tsx +344 -209
  71. package/src/ui/components/primitives/switch.tsx +10 -10
  72. package/src/ui/components/primitives/table.tsx +29 -24
  73. package/src/ui/components/primitives/tabs.tsx +58 -88
  74. package/src/ui/components/primitives/textarea.tsx +6 -6
  75. package/src/ui/components/primitives/toggle-group-context.ts +24 -0
  76. package/src/ui/components/primitives/toggle-group.tsx +116 -74
  77. package/src/ui/components/primitives/toggle.tsx +82 -16
  78. package/src/ui/docs/DocBrowser.tsx +77 -69
  79. package/src/ui/docs/content/action-form-card.md +9 -9
  80. package/src/ui/docs/content/action-list.md +85 -70
  81. package/src/ui/docs/content/action-view.md +33 -18
  82. package/src/ui/docs/content/actions.md +6 -7
  83. package/src/ui/docs/content/button.md +1 -1
  84. package/src/ui/docs/content/communication.md +1 -1
  85. package/src/ui/docs/content/customization.md +64 -11
  86. package/src/ui/docs/content/detail.md +8 -6
  87. package/src/ui/docs/content/dialog.md +100 -58
  88. package/src/ui/docs/content/drawer.md +63 -19
  89. package/src/ui/docs/content/field.md +14 -15
  90. package/src/ui/docs/content/icon-tile.md +14 -13
  91. package/src/ui/docs/content/page.md +34 -26
  92. package/src/ui/docs/content/pane.md +41 -290
  93. package/src/ui/docs/content/presentation.md +3 -3
  94. package/src/ui/docs/content/semantic-context.md +0 -1
  95. package/src/ui/docs/content/separator.md +6 -6
  96. package/src/ui/docs/content/sidebar.md +68 -266
  97. package/src/ui/docs/content/skeleton.md +20 -18
  98. package/src/ui/docs/content/split.md +14 -14
  99. package/src/ui/docs/content/surface.md +513 -0
  100. package/src/ui/docs/content/tabs.md +82 -83
  101. package/src/ui/docs/content/testing.md +2 -3
  102. package/src/ui/docs/content/toggle.md +120 -63
  103. package/src/ui/docs/content/tokens.md +124 -40
  104. package/src/ui/docs/registry.tsx +4 -6
  105. package/src/ui/drivers/react.tsx +4 -3
  106. package/src/ui/index.ts +30 -27
  107. package/src/ui/meta.ts +15 -27
  108. package/src/ui/react.tsx +42 -46
  109. package/src/ui/theme.css +311 -64
  110. package/src/vite/design.ts +23 -21
  111. package/src/ui/components/patterns/content-header.tsx +0 -532
  112. package/src/ui/components/patterns/surface-media.tsx +0 -69
  113. package/src/ui/components/primitives/card.tsx +0 -191
  114. package/src/ui/docs/content/card.md +0 -292
  115. package/src/ui/docs/content/content.md +0 -276
@@ -1,20 +1,24 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
3
 
4
- import { cn } from '../../lib/cn.ts'
5
- import type { ControlShape } from './control.ts'
6
- import { Button, type ButtonProps } from './button.tsx'
7
- import { Input } from './input.tsx'
8
- import { Textarea } from './textarea.tsx'
4
+ import { cn } from "../../lib/cn.ts";
5
+ import type { ControlShape } from "./control.ts";
6
+ import { Button, type ButtonProps } from "./button.tsx";
7
+ import { Input } from "./input.tsx";
8
+ import { Textarea } from "./textarea.tsx";
9
9
 
10
- function InputGroup({ className, shape = 'default', ...props }: React.ComponentProps<"div"> & { shape?: ControlShape }) {
10
+ function InputGroup({
11
+ className,
12
+ shape = "default",
13
+ ...props
14
+ }: React.ComponentProps<"div"> & { shape?: ControlShape }) {
11
15
  return (
12
16
  <div
13
17
  data-slot="input-group"
14
18
  data-shape={shape}
15
19
  role="group"
16
20
  className={cn(
17
- "group/input-group relative flex w-full items-center rounded-md border border-input bg-muted/30 transition-[color,box-shadow] outline-none data-[shape=pill]:rounded-full dark:bg-input/30",
21
+ "group/input-group relative flex w-full items-center rounded-md border border-input bg-control transition-[color,box-shadow] outline-none data-[shape=pill]:rounded-full",
18
22
  "h-9 min-w-0 has-[>textarea]:h-auto",
19
23
 
20
24
  // Variants based on alignment.
@@ -29,11 +33,11 @@ function InputGroup({ className, shape = 'default', ...props }: React.ComponentP
29
33
  // Error state.
30
34
  "has-[[data-slot][aria-invalid=true]]:border-context-danger has-[[data-slot][aria-invalid=true]]:ring-context-danger/20 dark:has-[[data-slot][aria-invalid=true]]:ring-context-danger/40",
31
35
 
32
- className
36
+ className,
33
37
  )}
34
38
  {...props}
35
39
  />
36
- )
40
+ );
37
41
  }
38
42
 
39
43
  const inputGroupAddonVariants = cva(
@@ -59,8 +63,8 @@ const inputGroupAddonVariants = cva(
59
63
  defaultVariants: {
60
64
  align: "inline-start",
61
65
  },
62
- }
63
- )
66
+ },
67
+ );
64
68
 
65
69
  function InputGroupAddon({
66
70
  className,
@@ -75,15 +79,15 @@ function InputGroupAddon({
75
79
  className={cn(inputGroupAddonVariants({ align }), className)}
76
80
  onClick={(e) => {
77
81
  if ((e.target as HTMLElement).closest("button")) {
78
- return
82
+ return;
79
83
  }
80
84
  e.currentTarget.parentElement
81
85
  ?.querySelector<HTMLElement>("input, textarea")
82
- ?.focus()
86
+ ?.focus();
83
87
  }}
84
88
  {...props}
85
89
  />
86
- )
90
+ );
87
91
  }
88
92
 
89
93
  /** Só o acabamento por tamanho: altura, quadrado e glifo vêm da escala de control.ts via Button. */
@@ -101,11 +105,14 @@ const inputGroupButtonVariants = cva(
101
105
  defaultVariants: {
102
106
  size: "xs",
103
107
  },
104
- }
105
- )
108
+ },
109
+ );
106
110
 
107
111
  /** Subconjunto da escala que cabe dentro de um campo: `xs`, `sm`, `icon-xs` e `icon-sm`. */
108
- type InputGroupButtonSize = Extract<NonNullable<ButtonProps['size']>, 'xs' | 'sm' | 'icon-xs' | 'icon-sm'>
112
+ type InputGroupButtonSize = Extract<
113
+ NonNullable<ButtonProps["size"]>,
114
+ "xs" | "sm" | "icon-xs" | "icon-sm"
115
+ >;
109
116
 
110
117
  function InputGroupButton({
111
118
  className,
@@ -113,7 +120,7 @@ function InputGroupButton({
113
120
  variant = "ghost",
114
121
  size = "xs",
115
122
  ...props
116
- }: Omit<ButtonProps, 'size'> & { size?: InputGroupButtonSize }) {
123
+ }: Omit<ButtonProps, "size"> & { size?: InputGroupButtonSize }) {
117
124
  return (
118
125
  <Button
119
126
  type={type}
@@ -123,7 +130,7 @@ function InputGroupButton({
123
130
  className={cn(inputGroupButtonVariants({ size }), className)}
124
131
  {...props}
125
132
  />
126
- )
133
+ );
127
134
  }
128
135
 
129
136
  function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
@@ -131,11 +138,11 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
131
138
  <span
132
139
  className={cn(
133
140
  "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
134
- className
141
+ className,
135
142
  )}
136
143
  {...props}
137
144
  />
138
- )
145
+ );
139
146
  }
140
147
 
141
148
  function InputGroupInput({
@@ -147,11 +154,11 @@ function InputGroupInput({
147
154
  data-slot="input-group-control"
148
155
  className={cn(
149
156
  "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
150
- className
157
+ className,
151
158
  )}
152
159
  {...props}
153
160
  />
154
- )
161
+ );
155
162
  }
156
163
 
157
164
  function InputGroupTextarea({
@@ -163,11 +170,11 @@ function InputGroupTextarea({
163
170
  data-slot="input-group-control"
164
171
  className={cn(
165
172
  "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
166
- className
173
+ className,
167
174
  )}
168
175
  {...props}
169
176
  />
170
- )
177
+ );
171
178
  }
172
179
 
173
180
  export {
@@ -177,4 +184,4 @@ export {
177
184
  InputGroupText,
178
185
  InputGroupInput,
179
186
  InputGroupTextarea,
180
- }
187
+ };
@@ -1,27 +1,27 @@
1
- import * as React from "react"
2
- import { OTPInput, OTPInputContext } from "input-otp"
3
- import { MinusIcon } from "lucide-react"
1
+ import * as React from "react";
2
+ import { OTPInput, OTPInputContext } from "input-otp";
3
+ import { MinusIcon } from "lucide-react";
4
4
 
5
- import { cn } from '../../lib/cn.ts'
5
+ import { cn } from "../../lib/cn.ts";
6
6
 
7
7
  function InputOTP({
8
8
  className,
9
9
  containerClassName,
10
10
  ...props
11
11
  }: React.ComponentProps<typeof OTPInput> & {
12
- containerClassName?: string
12
+ containerClassName?: string;
13
13
  }) {
14
14
  return (
15
15
  <OTPInput
16
16
  data-slot="input-otp"
17
17
  containerClassName={cn(
18
18
  "flex items-center gap-2 has-disabled:opacity-50",
19
- containerClassName
19
+ containerClassName,
20
20
  )}
21
21
  className={cn("disabled:cursor-not-allowed", className)}
22
22
  {...props}
23
23
  />
24
- )
24
+ );
25
25
  }
26
26
 
27
27
  function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
@@ -31,7 +31,7 @@ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
31
31
  className={cn("flex items-center", className)}
32
32
  {...props}
33
33
  />
34
- )
34
+ );
35
35
  }
36
36
 
37
37
  function InputOTPSlot({
@@ -39,18 +39,18 @@ function InputOTPSlot({
39
39
  className,
40
40
  ...props
41
41
  }: React.ComponentProps<"div"> & {
42
- index: number
42
+ index: number;
43
43
  }) {
44
- const inputOTPContext = React.useContext(OTPInputContext)
45
- const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
44
+ const inputOTPContext = React.useContext(OTPInputContext);
45
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
46
46
 
47
47
  return (
48
48
  <div
49
49
  data-slot="input-otp-slot"
50
50
  data-active={isActive}
51
51
  className={cn(
52
- "relative flex h-9 w-9 items-center justify-center border-y border-r border-input bg-muted/30 text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
53
- className
52
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input bg-control text-sm transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-context-danger data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[0.1875rem] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-context-danger data-[active=true]:aria-invalid:ring-context-danger/20 dark:data-[active=true]:aria-invalid:ring-context-danger/40",
53
+ className,
54
54
  )}
55
55
  {...props}
56
56
  >
@@ -61,7 +61,7 @@ function InputOTPSlot({
61
61
  </div>
62
62
  )}
63
63
  </div>
64
- )
64
+ );
65
65
  }
66
66
 
67
67
  function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
@@ -69,7 +69,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
69
69
  <div data-slot="input-otp-separator" role="separator" {...props}>
70
70
  <MinusIcon />
71
71
  </div>
72
- )
72
+ );
73
73
  }
74
74
 
75
- export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
75
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
@@ -1,25 +1,32 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
 
3
- import { cn } from '../../lib/cn.ts'
3
+ import { cn } from "../../lib/cn.ts";
4
4
 
5
5
  export interface InputProps extends React.ComponentProps<"input"> {
6
6
  /** Ícone LEADING dentro do campo (identidade — calendário na data, prédio na filial).
7
7
  * É a MESMA prop do Select e do Alert: adorno de campo é prop, não composição à mão. */
8
- icon?: React.ReactNode
8
+ icon?: React.ReactNode;
9
9
  /** Ação TRAILING dentro do campo, no fim — um botão que age sobre o valor (limpar,
10
10
  * mostrar senha). Par do `icon`, igual ao Select. Pra addon rico (prefixo de texto,
11
11
  * adorno em bloco, múltiplos), use `InputGroup`. */
12
- trailing?: React.ReactNode
12
+ trailing?: React.ReactNode;
13
13
  }
14
14
 
15
15
  // O <input> CRU (sem adorno): ELE é o campo — borda, foco, ref e className moram nele.
16
16
  const bareInput =
17
- "h-9 w-full min-w-0 rounded-md border border-input bg-muted/30 px-3 py-1 text-sm transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40"
17
+ "h-9 w-full min-w-0 rounded-md border border-input bg-control px-3 py-1 text-sm transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40";
18
18
 
19
19
  function Input({ className, type, icon, trailing, ...props }: InputProps) {
20
20
  // Sem adorno: o <input> cru de sempre — retrocompatível (refs, forms, InputGroupInput).
21
21
  if (icon === undefined && trailing === undefined) {
22
- return <input type={type} data-slot="input" className={cn(bareInput, className)} {...props} />
22
+ return (
23
+ <input
24
+ type={type}
25
+ data-slot="input"
26
+ className={cn(bareInput, className)}
27
+ {...props}
28
+ />
29
+ );
23
30
  }
24
31
  // Adornado: o MESMO modelo flex do InputGroup/Select. A borda e o anel moram no WRAPPER
25
32
  // (disparados pelo `:focus-visible` do input interno — foco de teclado); o <input> fica
@@ -31,7 +38,7 @@ function Input({ className, type, icon, trailing, ...props }: InputProps) {
31
38
  <div
32
39
  data-slot="input-wrapper"
33
40
  className={cn(
34
- "flex h-9 w-full min-w-0 items-center gap-1.5 rounded-md border border-input bg-muted/30 px-3 text-sm transition-[color,box-shadow] outline-none dark:bg-input/30",
41
+ "flex h-9 w-full min-w-0 items-center gap-1.5 rounded-md border border-input bg-control px-3 text-sm transition-[color,box-shadow] outline-none",
35
42
  "has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[0.1875rem] has-[input:focus-visible]:ring-ring/50",
36
43
  "has-[input[aria-invalid=true]]:border-context-danger has-[input[aria-invalid=true]]:ring-context-danger/20 dark:has-[input[aria-invalid=true]]:ring-context-danger/40",
37
44
  "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
@@ -60,12 +67,15 @@ function Input({ className, type, icon, trailing, ...props }: InputProps) {
60
67
  // Interativo (AÇÃO, não decoração): sem pointer-events-none. O tom muted (ação
61
68
  // secundária) mora no slot — o botão ghost herda, sem sprinklar no call site. Se o
62
69
  // trailing é botão: aproxima 0.375rem para casar o inset horizontal com o vertical.
63
- <span data-slot="input-trailing" className="flex shrink-0 items-center text-muted-foreground has-[button]:-mr-1.5">
70
+ <span
71
+ data-slot="input-trailing"
72
+ className="flex shrink-0 items-center text-muted-foreground has-[button]:-mr-1.5"
73
+ >
64
74
  {trailing}
65
75
  </span>
66
76
  )}
67
77
  </div>
68
- )
78
+ );
69
79
  }
70
80
 
71
- export { Input }
81
+ export { Input };
@@ -7,19 +7,16 @@ import type { ControlSize } from "./control.ts";
7
7
  import { iconTileVariants } from "./icon-tile.tsx";
8
8
  import { Separator } from "./separator.tsx";
9
9
 
10
- const itemGroupVariants = cva(
11
- "group/item-group flex flex-col",
12
- {
13
- variants: {
14
- variant: {
15
- plain: "",
16
- framed:
17
- "overflow-hidden rounded-xl border border-border bg-card text-card-foreground [&>[data-slot=item]]:rounded-none [&>[data-slot=item-container]>[data-slot=item]]:rounded-none",
18
- },
10
+ const itemGroupVariants = cva("group/item-group flex flex-col", {
11
+ variants: {
12
+ variant: {
13
+ plain: "",
14
+ framed:
15
+ "overflow-hidden rounded-xl border border-border bg-card text-card-foreground [&>[data-slot=item]]:rounded-none [&>[data-slot=item-container]>[data-slot=item]]:rounded-none",
19
16
  },
20
- defaultVariants: { variant: "plain" },
21
17
  },
22
- );
18
+ defaultVariants: { variant: "plain" },
19
+ });
23
20
 
24
21
  const ItemGroupContext = React.createContext(false);
25
22
  const ItemContext = React.createContext(false);
@@ -66,7 +63,7 @@ function ItemSeparator({
66
63
  }
67
64
 
68
65
  const itemVariants = cva(
69
- "group/item flex flex-wrap items-center rounded-md border border-transparent text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-accent/50",
66
+ "group/item flex flex-wrap items-center rounded-md border border-transparent text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-interaction-hover",
70
67
  {
71
68
  variants: {
72
69
  variant: {
@@ -1,15 +1,13 @@
1
- import * as React from "react"
2
- import type { UiContext } from '../../../core/ui-context.ts'
3
- import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
4
- import { DropdownMenu as MenuPrimitive } from "radix-ui"
1
+ import * as React from "react";
2
+ import type { UiContext } from "../../../core/ui-context.ts";
3
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
4
+ import { DropdownMenu as MenuPrimitive } from "radix-ui";
5
5
 
6
- import { useDismissOnIframeFocus } from "../../lib/dismiss-on-iframe.ts"
7
- import { cn } from '../../lib/cn.ts'
6
+ import { useDismissOnIframeFocus } from "../../lib/dismiss-on-iframe.ts";
7
+ import { cn } from "../../lib/cn.ts";
8
8
 
9
- function Menu({
10
- ...props
11
- }: React.ComponentProps<typeof MenuPrimitive.Root>) {
12
- return <MenuPrimitive.Root data-slot="menu" {...props} />
9
+ function Menu({ ...props }: React.ComponentProps<typeof MenuPrimitive.Root>) {
10
+ return <MenuPrimitive.Root data-slot="menu" {...props} />;
13
11
  }
14
12
 
15
13
  /**
@@ -20,20 +18,13 @@ function Menu({
20
18
  function MenuPortal({
21
19
  ...props
22
20
  }: React.ComponentProps<typeof MenuPrimitive.Portal>) {
23
- return (
24
- <MenuPrimitive.Portal data-slot="menu-portal" {...props} />
25
- )
21
+ return <MenuPrimitive.Portal data-slot="menu-portal" {...props} />;
26
22
  }
27
23
 
28
24
  function MenuTrigger({
29
25
  ...props
30
26
  }: React.ComponentProps<typeof MenuPrimitive.Trigger>) {
31
- return (
32
- <MenuPrimitive.Trigger
33
- data-slot="menu-trigger"
34
- {...props}
35
- />
36
- )
27
+ return <MenuPrimitive.Trigger data-slot="menu-trigger" {...props} />;
37
28
  }
38
29
 
39
30
  function MenuContent({
@@ -42,8 +33,8 @@ function MenuContent({
42
33
  ...props
43
34
  }: React.ComponentProps<typeof MenuPrimitive.Content>) {
44
35
  // Mesma razão do Popover: clique dentro de iframe não chega aqui fora.
45
- const ref = React.useRef<HTMLDivElement>(null)
46
- useDismissOnIframeFocus(ref)
36
+ const ref = React.useRef<HTMLDivElement>(null);
37
+ useDismissOnIframeFocus(ref);
47
38
  return (
48
39
  <MenuPrimitive.Portal>
49
40
  <MenuPrimitive.Content
@@ -52,20 +43,18 @@ function MenuContent({
52
43
  sideOffset={sideOffset}
53
44
  className={cn(
54
45
  "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md will-change-[transform,opacity] 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",
55
- className
46
+ className,
56
47
  )}
57
48
  {...props}
58
49
  />
59
50
  </MenuPrimitive.Portal>
60
- )
51
+ );
61
52
  }
62
53
 
63
54
  function MenuGroup({
64
55
  ...props
65
56
  }: React.ComponentProps<typeof MenuPrimitive.Group>) {
66
- return (
67
- <MenuPrimitive.Group data-slot="menu-group" {...props} />
68
- )
57
+ return <MenuPrimitive.Group data-slot="menu-group" {...props} />;
69
58
  }
70
59
 
71
60
  function MenuItem({
@@ -74,22 +63,22 @@ function MenuItem({
74
63
  context,
75
64
  ...props
76
65
  }: React.ComponentProps<typeof MenuPrimitive.Item> & {
77
- inset?: boolean
78
- context?: Extract<UiContext, 'neutral' | 'danger'>
66
+ inset?: boolean;
67
+ context?: Extract<UiContext, "neutral" | "danger">;
79
68
  }) {
80
- const resolvedContext = context ?? 'neutral'
69
+ const resolvedContext = context ?? "neutral";
81
70
  return (
82
71
  <MenuPrimitive.Item
83
72
  data-slot="menu-item"
84
73
  data-inset={inset}
85
74
  data-context={resolvedContext}
86
75
  className={cn(
87
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[context=danger]:focus:bg-context-danger-subtle data-[context=danger]:focus:text-context-danger-emphasis [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[context=danger]:focus:*:[svg]:text-context-danger-emphasis!",
88
- className
76
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-interaction-hover focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[context=danger]:focus:bg-context-danger-subtle data-[context=danger]:focus:text-context-danger-emphasis [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[context=danger]:focus:*:[svg]:text-context-danger-emphasis!",
77
+ className,
89
78
  )}
90
79
  {...props}
91
80
  />
92
- )
81
+ );
93
82
  }
94
83
 
95
84
  function MenuCheckboxItem({
@@ -102,8 +91,8 @@ function MenuCheckboxItem({
102
91
  <MenuPrimitive.CheckboxItem
103
92
  data-slot="menu-checkbox-item"
104
93
  className={cn(
105
- "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
106
- className
94
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-interaction-hover focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
95
+ className,
107
96
  )}
108
97
  checked={checked}
109
98
  {...props}
@@ -115,18 +104,13 @@ function MenuCheckboxItem({
115
104
  </span>
116
105
  {children}
117
106
  </MenuPrimitive.CheckboxItem>
118
- )
107
+ );
119
108
  }
120
109
 
121
110
  function MenuRadioGroup({
122
111
  ...props
123
112
  }: React.ComponentProps<typeof MenuPrimitive.RadioGroup>) {
124
- return (
125
- <MenuPrimitive.RadioGroup
126
- data-slot="menu-radio-group"
127
- {...props}
128
- />
129
- )
113
+ return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
130
114
  }
131
115
 
132
116
  function MenuRadioItem({
@@ -138,8 +122,8 @@ function MenuRadioItem({
138
122
  <MenuPrimitive.RadioItem
139
123
  data-slot="menu-radio-item"
140
124
  className={cn(
141
- "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
142
- className
125
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-interaction-hover focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
126
+ className,
143
127
  )}
144
128
  {...props}
145
129
  >
@@ -150,7 +134,7 @@ function MenuRadioItem({
150
134
  </span>
151
135
  {children}
152
136
  </MenuPrimitive.RadioItem>
153
- )
137
+ );
154
138
  }
155
139
 
156
140
  function MenuLabel({
@@ -158,7 +142,7 @@ function MenuLabel({
158
142
  inset,
159
143
  ...props
160
144
  }: React.ComponentProps<typeof MenuPrimitive.Label> & {
161
- inset?: boolean
145
+ inset?: boolean;
162
146
  }) {
163
147
  return (
164
148
  <MenuPrimitive.Label
@@ -166,11 +150,11 @@ function MenuLabel({
166
150
  data-inset={inset}
167
151
  className={cn(
168
152
  "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
169
- className
153
+ className,
170
154
  )}
171
155
  {...props}
172
156
  />
173
- )
157
+ );
174
158
  }
175
159
 
176
160
  function MenuSeparator({
@@ -183,29 +167,24 @@ function MenuSeparator({
183
167
  className={cn("-mx-1 my-1 h-px bg-border", className)}
184
168
  {...props}
185
169
  />
186
- )
170
+ );
187
171
  }
188
172
 
189
- function MenuShortcut({
190
- className,
191
- ...props
192
- }: React.ComponentProps<"span">) {
173
+ function MenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
193
174
  return (
194
175
  <span
195
176
  data-slot="menu-shortcut"
196
177
  className={cn(
197
178
  "ml-auto text-xs tracking-widest text-muted-foreground",
198
- className
179
+ className,
199
180
  )}
200
181
  {...props}
201
182
  />
202
- )
183
+ );
203
184
  }
204
185
 
205
- function MenuSub({
206
- ...props
207
- }: React.ComponentProps<typeof MenuPrimitive.Sub>) {
208
- return <MenuPrimitive.Sub data-slot="menu-sub" {...props} />
186
+ function MenuSub({ ...props }: React.ComponentProps<typeof MenuPrimitive.Sub>) {
187
+ return <MenuPrimitive.Sub data-slot="menu-sub" {...props} />;
209
188
  }
210
189
 
211
190
  function MenuSubTrigger({
@@ -214,22 +193,22 @@ function MenuSubTrigger({
214
193
  children,
215
194
  ...props
216
195
  }: React.ComponentProps<typeof MenuPrimitive.SubTrigger> & {
217
- inset?: boolean
196
+ inset?: boolean;
218
197
  }) {
219
198
  return (
220
199
  <MenuPrimitive.SubTrigger
221
200
  data-slot="menu-sub-trigger"
222
201
  data-inset={inset}
223
202
  className={cn(
224
- "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
225
- className
203
+ "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-interaction-hover focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-interaction-selected data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
204
+ className,
226
205
  )}
227
206
  {...props}
228
207
  >
229
208
  {children}
230
209
  <ChevronRightIcon className="ml-auto size-4" />
231
210
  </MenuPrimitive.SubTrigger>
232
- )
211
+ );
233
212
  }
234
213
 
235
214
  function MenuSubContent({
@@ -241,11 +220,11 @@ function MenuSubContent({
241
220
  data-slot="menu-sub-content"
242
221
  className={cn(
243
222
  "z-50 min-w-[10rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md will-change-[transform,opacity] 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",
244
- className
223
+ className,
245
224
  )}
246
225
  {...props}
247
226
  />
248
- )
227
+ );
249
228
  }
250
229
 
251
230
  export {
@@ -264,4 +243,4 @@ export {
264
243
  MenuSub,
265
244
  MenuSubTrigger,
266
245
  MenuSubContent,
267
- }
246
+ };