@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.
- package/CHANGELOG.md +120 -1
- package/bin/cli.mjs +3 -9
- package/bin/lib/check.mjs +242 -49
- package/bin/lib/copy.mjs +17 -35
- package/bin/lib/gen-runner.mjs +2 -0
- package/bin/lib/gen-stubs.mjs +3 -1
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +16 -12
- package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +8 -6
- package/docs/adr/0036-tabs-declares-trigger-distribution.md +11 -8
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0038-content-level-defines-primary-presentation.md +62 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +62 -0
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +59 -0
- package/docs/adr/0042-toggle-group-shares-the-grouped-control-grammar.md +79 -0
- package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +73 -0
- package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +133 -0
- package/docs/adr/0045-surface-replaces-content-and-card.md +230 -0
- package/docs/adr/0046-structural-body-owns-content-rhythm.md +103 -0
- package/docs/adr/0047-pane-is-only-a-split-layout-unit.md +71 -0
- package/docs/adr/0048-modal-intro-separates-content-from-chrome.md +34 -0
- package/docs/code-style.md +1 -1
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/SKILL.md +11 -11
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -9
- package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -16
- package/registry/skills/create-opus-action/references/contract-and-binding.md +3 -3
- package/registry/templates/app/src/App.tsx +24 -12
- package/src/core/contracts.ts +1 -1
- package/src/core/runtime.ts +4 -37
- package/src/core/types.ts +5 -23
- package/src/design/index.ts +14 -3
- package/src/testing/index.ts +6 -26
- package/src/ui/appearance.ts +149 -0
- package/src/ui/components/patterns/action-form-card.tsx +46 -33
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -4
- package/src/ui/components/patterns/confirm.tsx +3 -1
- package/src/ui/components/patterns/fill-region.ts +8 -7
- package/src/ui/components/patterns/form-dialog.tsx +1 -3
- package/src/ui/components/patterns/form.tsx +8 -7
- package/src/ui/components/patterns/list.tsx +64 -14
- package/src/ui/components/patterns/page.tsx +15 -11
- package/src/ui/components/patterns/pane.tsx +9 -240
- package/src/ui/components/patterns/presentation.tsx +14 -14
- package/src/ui/components/patterns/sidebar.tsx +7 -17
- package/src/ui/components/patterns/split.tsx +111 -59
- package/src/ui/components/patterns/{surface-header.tsx → structural-header.tsx} +50 -19
- package/src/ui/components/patterns/structural-media.tsx +26 -0
- package/src/ui/components/patterns/surface-anatomy.ts +71 -61
- package/src/ui/components/patterns/surface-assistant.tsx +2 -4
- package/src/ui/components/patterns/surface.tsx +620 -0
- package/src/ui/components/patterns/trigger.tsx +11 -9
- package/src/ui/components/primitives/ask.tsx +155 -83
- package/src/ui/components/primitives/button.tsx +11 -9
- package/src/ui/components/primitives/checkbox.tsx +8 -8
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/composer.tsx +47 -45
- package/src/ui/components/primitives/detail.tsx +9 -2
- package/src/ui/components/primitives/dialog.tsx +60 -26
- package/src/ui/components/primitives/drawer.tsx +58 -12
- package/src/ui/components/primitives/icon-picker.tsx +93 -70
- package/src/ui/components/primitives/input-group.tsx +35 -28
- package/src/ui/components/primitives/input-otp.tsx +16 -16
- package/src/ui/components/primitives/input.tsx +20 -10
- package/src/ui/components/primitives/item.tsx +9 -12
- package/src/ui/components/primitives/menu.tsx +44 -65
- package/src/ui/components/primitives/metric-card.tsx +75 -59
- package/src/ui/components/primitives/radio-group.tsx +9 -9
- package/src/ui/components/primitives/select.tsx +344 -209
- package/src/ui/components/primitives/switch.tsx +10 -10
- package/src/ui/components/primitives/table.tsx +29 -24
- package/src/ui/components/primitives/tabs.tsx +58 -88
- package/src/ui/components/primitives/textarea.tsx +6 -6
- package/src/ui/components/primitives/toggle-group-context.ts +24 -0
- package/src/ui/components/primitives/toggle-group.tsx +116 -74
- package/src/ui/components/primitives/toggle.tsx +82 -16
- package/src/ui/docs/DocBrowser.tsx +77 -69
- package/src/ui/docs/content/action-form-card.md +9 -9
- package/src/ui/docs/content/action-list.md +85 -70
- package/src/ui/docs/content/action-view.md +33 -18
- package/src/ui/docs/content/actions.md +6 -7
- package/src/ui/docs/content/button.md +1 -1
- package/src/ui/docs/content/communication.md +1 -1
- package/src/ui/docs/content/customization.md +64 -11
- package/src/ui/docs/content/detail.md +8 -6
- package/src/ui/docs/content/dialog.md +100 -58
- package/src/ui/docs/content/drawer.md +63 -19
- package/src/ui/docs/content/field.md +14 -15
- package/src/ui/docs/content/icon-tile.md +14 -13
- package/src/ui/docs/content/page.md +34 -26
- package/src/ui/docs/content/pane.md +41 -290
- package/src/ui/docs/content/presentation.md +3 -3
- package/src/ui/docs/content/semantic-context.md +0 -1
- package/src/ui/docs/content/separator.md +6 -6
- package/src/ui/docs/content/sidebar.md +68 -266
- package/src/ui/docs/content/skeleton.md +20 -18
- package/src/ui/docs/content/split.md +14 -14
- package/src/ui/docs/content/surface.md +513 -0
- package/src/ui/docs/content/tabs.md +82 -83
- package/src/ui/docs/content/testing.md +2 -3
- package/src/ui/docs/content/toggle.md +120 -63
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +4 -6
- package/src/ui/drivers/react.tsx +4 -3
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +42 -46
- package/src/ui/theme.css +311 -64
- package/src/vite/design.ts +23 -21
- package/src/ui/components/patterns/content-header.tsx +0 -532
- package/src/ui/components/patterns/surface-media.tsx +0 -69
- package/src/ui/components/primitives/card.tsx +0 -191
- package/src/ui/docs/content/card.md +0 -292
- 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
|
|
5
|
-
import type { ControlShape } from
|
|
6
|
-
import { Button, type ButtonProps } from
|
|
7
|
-
import { Input } from
|
|
8
|
-
import { Textarea } from
|
|
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({
|
|
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-
|
|
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<
|
|
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,
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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-
|
|
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
|
|
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
|
|
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,
|
|
66
|
+
inset?: boolean;
|
|
67
|
+
context?: Extract<UiContext, "neutral" | "danger">;
|
|
79
68
|
}) {
|
|
80
|
-
const resolvedContext = context ??
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
+
};
|