@softize/opus 23.0.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 +81 -0
- package/bin/cli.mjs +2 -0
- package/bin/lib/check.mjs +241 -42
- package/bin/lib/copy.mjs +16 -34
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +6 -0
- package/docs/adr/0039-filter-presentation-is-independent-from-filter-type.md +3 -3
- package/docs/adr/0040-tabs-use-a-single-navigation-presentation.md +5 -6
- 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/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/templates/app/src/App.tsx +24 -12
- 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 +15 -19
- 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 +90 -73
- 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 +4 -2
- 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 +76 -70
- package/src/ui/docs/content/action-view.md +33 -18
- 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/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 +20 -20
- package/src/ui/docs/content/toggle.md +117 -65
- package/src/ui/docs/content/tokens.md +124 -40
- package/src/ui/docs/registry.tsx +4 -6
- package/src/ui/index.ts +30 -27
- package/src/ui/meta.ts +15 -27
- package/src/ui/react.tsx +40 -43
- package/src/ui/theme.css +311 -64
- package/src/ui/components/patterns/content-header.tsx +0 -528
- 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 -275
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Switch as SwitchPrimitive } from "radix-ui";
|
|
3
3
|
|
|
4
|
-
import { cn } from
|
|
5
|
-
import type { ControlSize } from
|
|
4
|
+
import { cn } from "../../lib/cn.ts";
|
|
5
|
+
import type { ControlSize } from "./control.ts";
|
|
6
6
|
|
|
7
7
|
/** `size` usa os nomes da escala de control.ts; a chave é um trilho (não uma linha), então a
|
|
8
8
|
* medida é a do glifo daquele tamanho: `sm` acompanha o svg de um controle sm (0.875rem). */
|
|
@@ -11,15 +11,15 @@ function Switch({
|
|
|
11
11
|
size = "default",
|
|
12
12
|
...props
|
|
13
13
|
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
|
14
|
-
size?: Extract<ControlSize, "sm" | "default"
|
|
14
|
+
size?: Extract<ControlSize, "sm" | "default">;
|
|
15
15
|
}) {
|
|
16
16
|
return (
|
|
17
17
|
<SwitchPrimitive.Root
|
|
18
18
|
data-slot="switch"
|
|
19
19
|
data-size={size}
|
|
20
20
|
className={cn(
|
|
21
|
-
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-
|
|
22
|
-
className
|
|
21
|
+
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-interaction-selected",
|
|
22
|
+
className,
|
|
23
23
|
)}
|
|
24
24
|
{...props}
|
|
25
25
|
>
|
|
@@ -27,11 +27,11 @@ function Switch({
|
|
|
27
27
|
data-slot="switch-thumb"
|
|
28
28
|
// px: o percurso do thumb desconta a borda (um pixel de cada lado) no calc do translate.
|
|
29
29
|
className={cn(
|
|
30
|
-
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
|
|
30
|
+
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground",
|
|
31
31
|
)}
|
|
32
32
|
/>
|
|
33
33
|
</SwitchPrimitive.Root>
|
|
34
|
-
)
|
|
34
|
+
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export { Switch }
|
|
37
|
+
export { Switch };
|
|
@@ -1,18 +1,23 @@
|
|
|
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
|
-
type TableVariant =
|
|
6
|
-
const TableVariantContext = React.createContext<TableVariant>(
|
|
5
|
+
type TableVariant = "plain" | "framed";
|
|
6
|
+
const TableVariantContext = React.createContext<TableVariant>("plain");
|
|
7
7
|
|
|
8
8
|
export interface TableProps extends React.ComponentProps<"table"> {
|
|
9
9
|
/** `framed` aplica a moldura canônica de datagrid no contêiner da tabela. */
|
|
10
|
-
variant?: TableVariant
|
|
10
|
+
variant?: TableVariant;
|
|
11
11
|
/** Classes do contêiner responsável pela rolagem e pela moldura da tabela. */
|
|
12
|
-
containerClassName?: string
|
|
12
|
+
containerClassName?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function Table({
|
|
15
|
+
function Table({
|
|
16
|
+
className,
|
|
17
|
+
containerClassName,
|
|
18
|
+
variant = "plain",
|
|
19
|
+
...props
|
|
20
|
+
}: TableProps) {
|
|
16
21
|
return (
|
|
17
22
|
<TableVariantContext.Provider value={variant}>
|
|
18
23
|
<div
|
|
@@ -20,7 +25,7 @@ function Table({ className, containerClassName, variant = 'plain', ...props }: T
|
|
|
20
25
|
data-variant={variant}
|
|
21
26
|
className={cn(
|
|
22
27
|
"relative w-full overflow-x-auto",
|
|
23
|
-
variant ===
|
|
28
|
+
variant === "framed" && "rounded-lg border border-border",
|
|
24
29
|
containerClassName,
|
|
25
30
|
)}
|
|
26
31
|
>
|
|
@@ -31,22 +36,22 @@ function Table({ className, containerClassName, variant = 'plain', ...props }: T
|
|
|
31
36
|
/>
|
|
32
37
|
</div>
|
|
33
38
|
</TableVariantContext.Provider>
|
|
34
|
-
)
|
|
39
|
+
);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
38
|
-
const variant = React.useContext(TableVariantContext)
|
|
43
|
+
const variant = React.useContext(TableVariantContext);
|
|
39
44
|
return (
|
|
40
45
|
<thead
|
|
41
46
|
data-slot="table-header"
|
|
42
47
|
className={cn(
|
|
43
48
|
"[&_tr]:border-b [&_tr:hover]:bg-transparent",
|
|
44
|
-
variant ===
|
|
49
|
+
variant === "framed" && "bg-control",
|
|
45
50
|
className,
|
|
46
51
|
)}
|
|
47
52
|
{...props}
|
|
48
53
|
/>
|
|
49
|
-
)
|
|
54
|
+
);
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
@@ -56,7 +61,7 @@ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
|
56
61
|
className={cn("[&_tr:last-child]:border-0", className)}
|
|
57
62
|
{...props}
|
|
58
63
|
/>
|
|
59
|
-
)
|
|
64
|
+
);
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
@@ -65,11 +70,11 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
|
65
70
|
data-slot="table-footer"
|
|
66
71
|
className={cn(
|
|
67
72
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
68
|
-
className
|
|
73
|
+
className,
|
|
69
74
|
)}
|
|
70
75
|
{...props}
|
|
71
76
|
/>
|
|
72
|
-
)
|
|
77
|
+
);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
@@ -77,12 +82,12 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
|
77
82
|
<tr
|
|
78
83
|
data-slot="table-row"
|
|
79
84
|
className={cn(
|
|
80
|
-
"border-b transition-colors hover:bg-
|
|
81
|
-
className
|
|
85
|
+
"border-b transition-colors hover:bg-interaction-hover has-aria-expanded:bg-interaction-selected data-[state=selected]:bg-interaction-selected",
|
|
86
|
+
className,
|
|
82
87
|
)}
|
|
83
88
|
{...props}
|
|
84
89
|
/>
|
|
85
|
-
)
|
|
90
|
+
);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
@@ -91,11 +96,11 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
91
96
|
data-slot="table-head"
|
|
92
97
|
className={cn(
|
|
93
98
|
"h-10 px-3 text-left align-middle font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
94
|
-
className
|
|
99
|
+
className,
|
|
95
100
|
)}
|
|
96
101
|
{...props}
|
|
97
102
|
/>
|
|
98
|
-
)
|
|
103
|
+
);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
@@ -104,11 +109,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
|
104
109
|
data-slot="table-cell"
|
|
105
110
|
className={cn(
|
|
106
111
|
"px-3 py-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[0.125rem]",
|
|
107
|
-
className
|
|
112
|
+
className,
|
|
108
113
|
)}
|
|
109
114
|
{...props}
|
|
110
115
|
/>
|
|
111
|
-
)
|
|
116
|
+
);
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
function TableCaption({
|
|
@@ -121,7 +126,7 @@ function TableCaption({
|
|
|
121
126
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
122
127
|
{...props}
|
|
123
128
|
/>
|
|
124
|
-
)
|
|
129
|
+
);
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
export {
|
|
@@ -133,4 +138,4 @@ export {
|
|
|
133
138
|
TableRow,
|
|
134
139
|
TableCell,
|
|
135
140
|
TableCaption,
|
|
136
|
-
}
|
|
141
|
+
};
|
|
@@ -68,6 +68,7 @@ function TabsList({
|
|
|
68
68
|
const size = React.useContext(TabsSizeContext);
|
|
69
69
|
const orientation = React.useContext(TabsOrientationContext);
|
|
70
70
|
const distribution = React.useContext(TabsDistributionContext);
|
|
71
|
+
const framed = React.useContext(TabsFramedContext);
|
|
71
72
|
const hasHorizontalLine = orientation === "horizontal";
|
|
72
73
|
return (
|
|
73
74
|
<TabsPrimitive.List
|
|
@@ -78,6 +79,7 @@ function TabsList({
|
|
|
78
79
|
"group/tabs-list inline-flex w-fit items-center justify-center gap-1 rounded-none bg-transparent p-0 text-muted-foreground group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
|
79
80
|
controlHeight[size],
|
|
80
81
|
distribution === "equal" && orientation === "horizontal" && "w-full",
|
|
82
|
+
framed && orientation === "horizontal" && "px-1 pt-1",
|
|
81
83
|
className,
|
|
82
84
|
)}
|
|
83
85
|
style={{
|
|
@@ -152,7 +154,7 @@ function TabsListItem({
|
|
|
152
154
|
hasHorizontalLine &&
|
|
153
155
|
cn(
|
|
154
156
|
"group/tab-list-item rounded-none px-0 py-0 hover:bg-transparent active:brightness-100 data-[state=active]:bg-transparent data-[state=active]:hover:bg-transparent",
|
|
155
|
-
framed ? "pb-
|
|
157
|
+
framed ? "pb-1" : "pb-1.5",
|
|
156
158
|
),
|
|
157
159
|
className,
|
|
158
160
|
)}
|
|
@@ -172,7 +174,7 @@ function TabsListItem({
|
|
|
172
174
|
<span
|
|
173
175
|
data-slot="tabs-list-item-content"
|
|
174
176
|
className={cn(
|
|
175
|
-
"inline-flex items-center justify-center gap-1.5 rounded-md px-3 transition-colors hover:bg-
|
|
177
|
+
"inline-flex items-center justify-center gap-1.5 rounded-md px-3 transition-colors hover:bg-interaction-hover hover:text-accent-foreground active:bg-interaction-pressed group-data-[state=active]/tab-list-item:text-foreground",
|
|
176
178
|
distribution === "equal" && "w-full",
|
|
177
179
|
controlHeight[size],
|
|
178
180
|
)}
|
|
@@ -1,18 +1,18 @@
|
|
|
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
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
6
|
return (
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-
|
|
11
|
-
className
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-control px-3 py-2 text-sm transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:aria-invalid:ring-context-danger/40",
|
|
11
|
+
className,
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
14
14
|
/>
|
|
15
|
-
)
|
|
15
|
+
);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export { Textarea }
|
|
18
|
+
export { Textarea };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import type { ControlShape } from "./control.ts";
|
|
4
|
+
|
|
5
|
+
type ToggleGroupMode = "connected" | "spaced" | "divided" | "segmented";
|
|
6
|
+
type ToggleGroupDistribution = "content" | "equal";
|
|
7
|
+
|
|
8
|
+
type ToggleGroupContextValue = {
|
|
9
|
+
mode: ToggleGroupMode;
|
|
10
|
+
orientation: "horizontal" | "vertical";
|
|
11
|
+
variant?: "default" | "outline" | null;
|
|
12
|
+
size?: "default" | "sm" | "lg" | null;
|
|
13
|
+
shape?: ControlShape | null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ToggleGroupContext = React.createContext<ToggleGroupContextValue | null>(
|
|
17
|
+
null,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
ToggleGroupContext,
|
|
22
|
+
type ToggleGroupDistribution,
|
|
23
|
+
type ToggleGroupMode,
|
|
24
|
+
};
|
|
@@ -1,99 +1,141 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { type VariantProps } from "class-variance-authority"
|
|
3
|
-
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
|
-
import { cn } from
|
|
6
|
-
import type { ControlShape } from
|
|
7
|
-
import {
|
|
5
|
+
import { cn } from "../../lib/cn.ts";
|
|
6
|
+
import type { ControlShape } from "./control.ts";
|
|
7
|
+
import { Separator } from "./separator.tsx";
|
|
8
|
+
import {
|
|
9
|
+
ToggleGroupContext,
|
|
10
|
+
type ToggleGroupDistribution,
|
|
11
|
+
type ToggleGroupMode,
|
|
12
|
+
} from "./toggle-group-context.ts";
|
|
13
|
+
import { toggleVariants } from "./toggle.tsx";
|
|
8
14
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const horizontalInactiveSegmentDivider = [
|
|
16
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:relative",
|
|
17
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:ml-0.5",
|
|
18
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:absolute",
|
|
19
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:-left-0.5",
|
|
20
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:top-1/2",
|
|
21
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:h-4",
|
|
22
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:w-[0.0625rem]",
|
|
23
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:-translate-y-1/2",
|
|
24
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:bg-muted-foreground/35",
|
|
25
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:content-['']",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const verticalInactiveSegmentDivider = [
|
|
29
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:relative",
|
|
30
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:mt-0.5",
|
|
31
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:absolute",
|
|
32
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:-top-0.5",
|
|
33
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:left-1/2",
|
|
34
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:h-[0.0625rem]",
|
|
35
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:w-4",
|
|
36
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:-translate-x-1/2",
|
|
37
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:bg-muted-foreground/35",
|
|
38
|
+
"[&>[data-slot=toggle-group-item][data-state=off]+[data-slot=toggle-group-item][data-state=off]]:before:content-['']",
|
|
39
|
+
];
|
|
20
40
|
|
|
21
|
-
type
|
|
41
|
+
type ToggleGroupProps = React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
|
22
42
|
VariantProps<typeof toggleVariants> & {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
mode?: ToggleGroupMode;
|
|
44
|
+
distribution?: ToggleGroupDistribution;
|
|
45
|
+
framed?: boolean;
|
|
46
|
+
shape?: ControlShape;
|
|
47
|
+
};
|
|
26
48
|
|
|
27
49
|
function ToggleGroup({
|
|
28
50
|
className,
|
|
29
51
|
variant,
|
|
30
52
|
size,
|
|
31
|
-
|
|
53
|
+
mode = "connected",
|
|
54
|
+
distribution = "content",
|
|
55
|
+
framed = false,
|
|
56
|
+
orientation = "horizontal",
|
|
32
57
|
shape = "default",
|
|
33
58
|
children,
|
|
34
59
|
...props
|
|
35
|
-
}:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
}: ToggleGroupProps) {
|
|
61
|
+
const effectiveSize = size ?? "default";
|
|
62
|
+
const controls = React.Children.toArray(children);
|
|
63
|
+
const content =
|
|
64
|
+
mode === "divided"
|
|
65
|
+
? controls.flatMap((child, index) => {
|
|
66
|
+
const childKey = React.isValidElement(child) ? child.key : index;
|
|
67
|
+
return [
|
|
68
|
+
...(index === 0
|
|
69
|
+
? []
|
|
70
|
+
: [
|
|
71
|
+
<Separator
|
|
72
|
+
key={`toggle-group-separator-${childKey ?? index}`}
|
|
73
|
+
data-slot="toggle-group-separator"
|
|
74
|
+
orientation={
|
|
75
|
+
orientation === "horizontal" ? "vertical" : "horizontal"
|
|
76
|
+
}
|
|
77
|
+
className="m-0! self-center bg-input"
|
|
78
|
+
style={
|
|
79
|
+
orientation === "horizontal"
|
|
80
|
+
? {
|
|
81
|
+
flex: "none",
|
|
82
|
+
width: "0.0625rem",
|
|
83
|
+
height: "1.25rem",
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
flex: "none",
|
|
87
|
+
width: "1.25rem",
|
|
88
|
+
height: "0.0625rem",
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/>,
|
|
92
|
+
]),
|
|
93
|
+
child,
|
|
94
|
+
];
|
|
95
|
+
})
|
|
96
|
+
: children;
|
|
97
|
+
|
|
40
98
|
return (
|
|
41
99
|
<ToggleGroupPrimitive.Root
|
|
42
100
|
data-slot="toggle-group"
|
|
43
101
|
data-variant={variant}
|
|
44
|
-
data-size={
|
|
45
|
-
data-
|
|
102
|
+
data-size={effectiveSize}
|
|
103
|
+
data-mode={mode}
|
|
46
104
|
data-shape={shape}
|
|
47
|
-
|
|
105
|
+
data-distribution={distribution}
|
|
106
|
+
data-framed={framed}
|
|
107
|
+
orientation={orientation}
|
|
48
108
|
className={cn(
|
|
49
|
-
"group/toggle-group flex w-fit items-
|
|
50
|
-
|
|
109
|
+
"group/toggle-group flex w-fit items-stretch",
|
|
110
|
+
orientation === "vertical" && "flex-col",
|
|
111
|
+
mode === "connected" && "gap-0",
|
|
112
|
+
(mode === "spaced" || mode === "divided") && "gap-1",
|
|
113
|
+
mode === "segmented" &&
|
|
114
|
+
"gap-0.5 rounded-lg bg-interaction-hover p-0.5 text-muted-foreground",
|
|
115
|
+
mode === "segmented" &&
|
|
116
|
+
(orientation === "horizontal"
|
|
117
|
+
? horizontalInactiveSegmentDivider
|
|
118
|
+
: verticalInactiveSegmentDivider),
|
|
119
|
+
distribution === "equal" &&
|
|
120
|
+
"w-full [&>[data-slot=toggle-group-item]]:min-w-0 [&>[data-slot=toggle-group-item]]:w-full [&>[data-slot=toggle-group-item]]:flex-1",
|
|
121
|
+
framed && "rounded-xl border p-1",
|
|
122
|
+
shape === "pill" && "rounded-full",
|
|
123
|
+
className,
|
|
51
124
|
)}
|
|
52
125
|
{...props}
|
|
53
126
|
>
|
|
54
|
-
<ToggleGroupContext.Provider
|
|
55
|
-
{
|
|
127
|
+
<ToggleGroupContext.Provider
|
|
128
|
+
value={{ mode, orientation, variant, size: effectiveSize, shape }}
|
|
129
|
+
>
|
|
130
|
+
{content}
|
|
56
131
|
</ToggleGroupContext.Provider>
|
|
57
132
|
</ToggleGroupPrimitive.Root>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function ToggleGroupItem({
|
|
62
|
-
className,
|
|
63
|
-
children,
|
|
64
|
-
variant,
|
|
65
|
-
size,
|
|
66
|
-
shape,
|
|
67
|
-
'data-slot': _composedSlot,
|
|
68
|
-
'data-state': _composedState,
|
|
69
|
-
...props
|
|
70
|
-
}: ToggleGroupItemProps) {
|
|
71
|
-
const context = React.useContext(ToggleGroupContext)
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<ToggleGroupPrimitive.Item
|
|
75
|
-
data-slot="toggle-group-item"
|
|
76
|
-
data-variant={context.variant || variant}
|
|
77
|
-
data-size={context.size || size}
|
|
78
|
-
data-spacing={context.spacing}
|
|
79
|
-
data-item-shape={shape}
|
|
80
|
-
className={cn(
|
|
81
|
-
toggleVariants({
|
|
82
|
-
variant: context.variant || variant,
|
|
83
|
-
size: context.size || size,
|
|
84
|
-
shape: shape ?? context.shape,
|
|
85
|
-
}),
|
|
86
|
-
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
87
|
-
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
|
88
|
-
"group-data-[shape=pill]/toggle-group:data-[spacing=0]:first:rounded-l-full group-data-[shape=pill]/toggle-group:data-[spacing=0]:last:rounded-r-full",
|
|
89
|
-
"data-[item-shape=pill]:data-[spacing=0]:rounded-full",
|
|
90
|
-
className
|
|
91
|
-
)}
|
|
92
|
-
{...props}
|
|
93
|
-
>
|
|
94
|
-
{children}
|
|
95
|
-
</ToggleGroupPrimitive.Item>
|
|
96
|
-
)
|
|
133
|
+
);
|
|
97
134
|
}
|
|
98
135
|
|
|
99
|
-
export {
|
|
136
|
+
export {
|
|
137
|
+
ToggleGroup,
|
|
138
|
+
type ToggleGroupDistribution,
|
|
139
|
+
type ToggleGroupMode,
|
|
140
|
+
type ToggleGroupProps,
|
|
141
|
+
};
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
import {
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import {
|
|
4
|
+
Toggle as TogglePrimitive,
|
|
5
|
+
ToggleGroup as ToggleGroupPrimitive,
|
|
6
|
+
} from "radix-ui";
|
|
4
7
|
|
|
5
|
-
import { cn } from
|
|
6
|
-
import { controlHeight, focusRing } from
|
|
8
|
+
import { cn } from "../../lib/cn.ts";
|
|
9
|
+
import { controlHeight, focusRing } from "./control.ts";
|
|
10
|
+
import { ToggleGroupContext } from "./toggle-group-context.ts";
|
|
7
11
|
|
|
8
12
|
const toggleVariants = cva(
|
|
9
13
|
[
|
|
10
|
-
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-
|
|
14
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-interaction-hover hover:text-muted-foreground active:bg-interaction-pressed disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 data-[state=on]:bg-interaction-selected data-[state=on]:text-accent-foreground dark:aria-invalid:ring-context-danger/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
11
15
|
focusRing,
|
|
12
16
|
],
|
|
13
17
|
{
|
|
@@ -15,7 +19,7 @@ const toggleVariants = cva(
|
|
|
15
19
|
variant: {
|
|
16
20
|
default: "bg-transparent",
|
|
17
21
|
outline:
|
|
18
|
-
"border border-input bg-transparent text-muted-foreground hover:bg-
|
|
22
|
+
"border border-input bg-transparent text-muted-foreground hover:bg-interaction-hover hover:text-foreground active:bg-interaction-pressed data-[state=on]:bg-interaction-selected data-[state=on]:text-foreground",
|
|
19
23
|
},
|
|
20
24
|
// Alturas da escala de control.ts (`sm` = o mesmo h-8 de Button e Select).
|
|
21
25
|
size: {
|
|
@@ -24,8 +28,8 @@ const toggleVariants = cva(
|
|
|
24
28
|
lg: `${controlHeight.lg} min-w-10 px-2.5`,
|
|
25
29
|
},
|
|
26
30
|
shape: {
|
|
27
|
-
default:
|
|
28
|
-
pill:
|
|
31
|
+
default: "",
|
|
32
|
+
pill: "rounded-full",
|
|
29
33
|
},
|
|
30
34
|
},
|
|
31
35
|
defaultVariants: {
|
|
@@ -33,25 +37,87 @@ const toggleVariants = cva(
|
|
|
33
37
|
size: "default",
|
|
34
38
|
shape: "default",
|
|
35
39
|
},
|
|
36
|
-
}
|
|
37
|
-
)
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
type ToggleProps = (
|
|
44
|
+
| React.ComponentProps<typeof TogglePrimitive.Root>
|
|
45
|
+
| React.ComponentProps<typeof ToggleGroupPrimitive.Item>
|
|
46
|
+
) &
|
|
47
|
+
VariantProps<typeof toggleVariants> & {
|
|
48
|
+
"data-slot"?: string;
|
|
49
|
+
"data-state"?: string;
|
|
50
|
+
};
|
|
38
51
|
|
|
39
52
|
function Toggle({
|
|
40
53
|
className,
|
|
41
54
|
variant,
|
|
42
55
|
size,
|
|
43
56
|
shape,
|
|
57
|
+
"data-slot": _composedSlot,
|
|
58
|
+
"data-state": _composedState,
|
|
44
59
|
...props
|
|
45
|
-
}:
|
|
46
|
-
|
|
60
|
+
}: ToggleProps) {
|
|
61
|
+
const group = React.useContext(ToggleGroupContext);
|
|
62
|
+
const effectiveVariant = group?.variant ?? variant;
|
|
63
|
+
const effectiveSize = group?.size ?? size;
|
|
64
|
+
const effectiveShape = shape ?? group?.shape;
|
|
65
|
+
|
|
66
|
+
if (group !== null) {
|
|
67
|
+
const itemProps = props as React.ComponentProps<
|
|
68
|
+
typeof ToggleGroupPrimitive.Item
|
|
69
|
+
>;
|
|
70
|
+
if (typeof itemProps.value !== "string" || itemProps.value.length === 0) {
|
|
71
|
+
throw new Error("Toggle dentro de ToggleGroup exige value.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<ToggleGroupPrimitive.Item
|
|
76
|
+
data-slot="toggle-group-item"
|
|
77
|
+
data-variant={effectiveVariant}
|
|
78
|
+
data-size={effectiveSize}
|
|
79
|
+
data-mode={group.mode}
|
|
80
|
+
data-item-shape={shape}
|
|
81
|
+
className={cn(
|
|
82
|
+
toggleVariants({
|
|
83
|
+
variant: effectiveVariant,
|
|
84
|
+
size: effectiveSize,
|
|
85
|
+
shape: effectiveShape,
|
|
86
|
+
}),
|
|
87
|
+
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
88
|
+
group.mode === "connected" &&
|
|
89
|
+
group.orientation === "horizontal" && [
|
|
90
|
+
"rounded-none shadow-none first:rounded-l-md last:rounded-r-md data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
|
91
|
+
"group-data-[shape=pill]/toggle-group:first:rounded-l-full group-data-[shape=pill]/toggle-group:last:rounded-r-full",
|
|
92
|
+
"data-[item-shape=pill]:rounded-full",
|
|
93
|
+
],
|
|
94
|
+
group.mode === "connected" &&
|
|
95
|
+
group.orientation === "vertical" && [
|
|
96
|
+
"rounded-none shadow-none first:rounded-t-md last:rounded-b-md data-[variant=outline]:border-t-0 data-[variant=outline]:first:border-t",
|
|
97
|
+
"group-data-[shape=pill]/toggle-group:first:rounded-t-full group-data-[shape=pill]/toggle-group:last:rounded-b-full",
|
|
98
|
+
],
|
|
99
|
+
(group.mode === "spaced" || group.mode === "divided") &&
|
|
100
|
+
"group-data-[shape=pill]/toggle-group:rounded-full",
|
|
101
|
+
group.mode === "segmented" &&
|
|
102
|
+
"rounded-md text-muted-foreground hover:bg-interaction-selected hover:text-foreground active:bg-interaction-pressed data-[state=on]:bg-card data-[state=on]:text-card-foreground data-[state=on]:shadow-sm group-data-[shape=pill]/toggle-group:rounded-full",
|
|
103
|
+
group.mode === "segmented" && effectiveSize === "sm" && "h-7",
|
|
104
|
+
group.mode === "segmented" && effectiveSize === "default" && "h-8",
|
|
105
|
+
group.mode === "segmented" && effectiveSize === "lg" && "h-9",
|
|
106
|
+
className,
|
|
107
|
+
)}
|
|
108
|
+
{...itemProps}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
47
113
|
return (
|
|
48
114
|
<TogglePrimitive.Root
|
|
49
115
|
data-slot="toggle"
|
|
50
116
|
data-shape={shape}
|
|
51
117
|
className={cn(toggleVariants({ variant, size, shape, className }))}
|
|
52
|
-
{...props}
|
|
118
|
+
{...(props as React.ComponentProps<typeof TogglePrimitive.Root>)}
|
|
53
119
|
/>
|
|
54
|
-
)
|
|
120
|
+
);
|
|
55
121
|
}
|
|
56
122
|
|
|
57
|
-
export { Toggle, toggleVariants }
|
|
123
|
+
export { Toggle, type ToggleProps, toggleVariants };
|