@softize/opus 18.0.1 → 18.1.1
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 +105 -0
- package/PROMOTED.md +4 -5
- package/README.md +5 -4
- package/bin/cli.mjs +4 -0
- package/docs/adr/0004-page-content-state-is-composed.md +3 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -2
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +6 -1
- package/docs/adr/0014-structural-headers-do-not-carry-description.md +2 -2
- package/docs/adr/0015-action-size-follows-interaction-density.md +4 -3
- package/docs/adr/0017-ui-assumes-a-fixed-desktop-layout.md +54 -0
- package/docs/adr/{0012-modal-header-only-names-the-surface.md → 0018-modal-header-only-names-the-surface.md} +4 -1
- package/docs/adr/0019-productive-surfaces-use-compact-density.md +65 -0
- package/docs/code-style.md +2 -2
- package/docs/consumer-upgrade-propagation.md +1 -1
- package/docs/data-products.md +5 -3
- package/docs/protocol.md +6 -6
- package/docs/relative-unit-scale.md +9 -2
- package/docs/releasing.md +28 -4
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/references/evaluations.md +3 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +20 -4
- package/src/auth/drivers/jwt.ts +2 -1
- package/src/core/runtime.ts +32 -5
- package/src/core/types.ts +16 -7
- package/src/mcp/index.ts +13 -1
- package/src/ui/components/patterns/action-list-dialog.tsx +2 -2
- package/src/ui/components/patterns/content-header.tsx +2 -2
- package/src/ui/components/patterns/form-dialog.tsx +7 -2
- package/src/ui/components/patterns/form.tsx +1 -1
- package/src/ui/components/patterns/list.tsx +239 -47
- package/src/ui/components/patterns/presentation.tsx +7 -5
- package/src/ui/components/patterns/sidebar.tsx +1 -1
- package/src/ui/components/patterns/state-surface.tsx +2 -2
- package/src/ui/components/patterns/surface-header.tsx +4 -4
- package/src/ui/components/primitives/alert.tsx +2 -2
- package/src/ui/components/primitives/breadcrumb.tsx +1 -1
- package/src/ui/components/primitives/button-group.tsx +1 -1
- package/src/ui/components/primitives/button.tsx +3 -3
- package/src/ui/components/primitives/calendar.tsx +1 -1
- package/src/ui/components/primitives/card.tsx +1 -1
- package/src/ui/components/primitives/close-button.tsx +40 -0
- package/src/ui/components/primitives/detail.tsx +68 -30
- package/src/ui/components/primitives/dialog.tsx +36 -21
- package/src/ui/components/primitives/drawer.tsx +26 -19
- package/src/ui/components/primitives/empty-value.tsx +3 -3
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/field.tsx +12 -12
- package/src/ui/components/primitives/icon-picker.tsx +1 -1
- package/src/ui/components/primitives/input-group.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +5 -5
- package/src/ui/components/primitives/pagination.tsx +4 -4
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/select.tsx +3 -3
- package/src/ui/components/primitives/table.tsx +26 -17
- package/src/ui/components/primitives/tabs.tsx +80 -23
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle-group.tsx +9 -2
- package/src/ui/docs/content/action-form-dialog.md +11 -4
- package/src/ui/docs/content/action-form.md +13 -3
- package/src/ui/docs/content/action-list-dialog.md +5 -7
- package/src/ui/docs/content/action-list.md +53 -5
- package/src/ui/docs/content/action-trigger.md +9 -5
- package/src/ui/docs/content/action-view.md +12 -8
- package/src/ui/docs/content/actions.md +36 -13
- package/src/ui/docs/content/ai.md +26 -7
- package/src/ui/docs/content/alert.md +6 -3
- package/src/ui/docs/content/aspect-ratio.md +2 -2
- package/src/ui/docs/content/auth.md +25 -10
- package/src/ui/docs/content/avatar.md +1 -1
- package/src/ui/docs/content/badge.md +2 -2
- package/src/ui/docs/content/breadcrumb.md +3 -2
- package/src/ui/docs/content/button.md +33 -8
- package/src/ui/docs/content/calendar.md +1 -1
- package/src/ui/docs/content/card.md +1 -1
- package/src/ui/docs/content/carousel.md +14 -3
- package/src/ui/docs/content/chat.md +1 -1
- package/src/ui/docs/content/cli.md +13 -7
- package/src/ui/docs/content/command.md +34 -2
- package/src/ui/docs/content/composer.md +1 -1
- package/src/ui/docs/content/content.md +5 -4
- package/src/ui/docs/content/customization.md +12 -2
- package/src/ui/docs/content/cycle.md +7 -5
- package/src/ui/docs/content/data-state.md +6 -5
- package/src/ui/docs/content/data.md +3 -3
- package/src/ui/docs/content/detail.md +12 -10
- package/src/ui/docs/content/dialog.md +14 -7
- package/src/ui/docs/content/dictionary-value.md +1 -1
- package/src/ui/docs/content/dock.md +23 -2
- package/src/ui/docs/content/dot.md +0 -2
- package/src/ui/docs/content/drawer.md +7 -4
- package/src/ui/docs/content/empty-value.md +4 -4
- package/src/ui/docs/content/empty.md +1 -4
- package/src/ui/docs/content/events.md +1 -1
- package/src/ui/docs/content/field.md +21 -12
- package/src/ui/docs/content/getting-started.md +4 -2
- package/src/ui/docs/content/icon-picker.md +2 -2
- package/src/ui/docs/content/input-otp.md +2 -0
- package/src/ui/docs/content/input.md +2 -3
- package/src/ui/docs/content/item.md +6 -3
- package/src/ui/docs/content/kbd.md +2 -1
- package/src/ui/docs/content/mcp.md +10 -4
- package/src/ui/docs/content/menu.md +27 -0
- package/src/ui/docs/content/page.md +20 -6
- package/src/ui/docs/content/pagination.md +9 -2
- package/src/ui/docs/content/popover.md +2 -2
- package/src/ui/docs/content/presentation.md +48 -47
- package/src/ui/docs/content/progress.md +2 -6
- package/src/ui/docs/content/runtime.md +8 -5
- package/src/ui/docs/content/scheduler.md +1 -1
- package/src/ui/docs/content/select.md +13 -8
- package/src/ui/docs/content/sidebar.md +3 -2
- package/src/ui/docs/content/skeleton.md +1 -1
- package/src/ui/docs/content/slider.md +4 -4
- package/src/ui/docs/content/spinner.md +3 -3
- package/src/ui/docs/content/tabs.md +22 -12
- package/src/ui/docs/content/testing.md +4 -2
- package/src/ui/docs/content/toast.md +5 -6
- package/src/ui/docs/content/toggle.md +37 -0
- package/src/ui/docs/content/tokens.md +45 -2
- package/src/ui/docs/content/tooltip.md +4 -3
- package/src/ui/docs/content/truncate.md +3 -2
- package/src/ui/docs/content/ui.md +3 -1
- package/src/ui/docs/content/upgrading.md +43 -13
- package/src/ui/docs/doc-client.tsx +1 -1
- package/src/ui/docs/registry.tsx +30 -5
- package/src/ui/meta.ts +4 -4
- package/src/ui/react.tsx +1 -0
- package/src/ui/theme.css +3 -0
|
@@ -8,11 +8,11 @@ export type DetailGroupOrientation = 'vertical' | 'horizontal'
|
|
|
8
8
|
export type DetailGroupColumns = 1 | 2 | 3 | 4 | 'auto'
|
|
9
9
|
|
|
10
10
|
export interface DetailGroupProps extends React.ComponentProps<'dl'> {
|
|
11
|
-
/** `framed` aplica a
|
|
11
|
+
/** `framed` aplica a moldura canônica ao conjunto; na orientação horizontal, a coluna de rótulos ganha fundo sutil. */
|
|
12
12
|
variant?: DetailGroupVariant
|
|
13
13
|
/** Exibe hairlines somente entre os campos, sem exigir moldura externa. */
|
|
14
14
|
dividers?: boolean
|
|
15
|
-
/** Número
|
|
15
|
+
/** Número fixo de colunas ou distribuição automática por largura mínima. */
|
|
16
16
|
columns?: DetailGroupColumns
|
|
17
17
|
/** Organiza a chave sobre o valor ou ao lado dele em cada campo. */
|
|
18
18
|
orientation?: DetailGroupOrientation
|
|
@@ -20,39 +20,57 @@ export interface DetailGroupProps extends React.ComponentProps<'dl'> {
|
|
|
20
20
|
|
|
21
21
|
const columnClasses: Record<DetailGroupColumns, string> = {
|
|
22
22
|
1: 'grid-cols-1',
|
|
23
|
-
2: 'grid-cols-
|
|
24
|
-
3: 'grid-cols-
|
|
25
|
-
4: 'grid-cols-
|
|
23
|
+
2: 'grid-cols-2',
|
|
24
|
+
3: 'grid-cols-3',
|
|
25
|
+
4: 'grid-cols-4',
|
|
26
26
|
auto: 'grid-cols-[repeat(auto-fit,minmax(min(100%,14rem),1fr))]',
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const DetailGroupLayoutContext = React.createContext({
|
|
30
|
+
splitHorizontalField: false,
|
|
31
|
+
})
|
|
32
|
+
|
|
29
33
|
function DetailGroup({
|
|
30
34
|
className,
|
|
35
|
+
children,
|
|
36
|
+
style,
|
|
31
37
|
variant = 'plain',
|
|
32
38
|
dividers = false,
|
|
33
39
|
columns = 1,
|
|
34
40
|
orientation = 'vertical',
|
|
35
41
|
...props
|
|
36
42
|
}: DetailGroupProps): React.ReactElement {
|
|
43
|
+
const splitHorizontalField = variant === 'framed' && orientation === 'horizontal'
|
|
44
|
+
|
|
37
45
|
return (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
<DetailGroupLayoutContext.Provider value={{ splitHorizontalField }}>
|
|
47
|
+
<dl
|
|
48
|
+
data-slot="detail-group"
|
|
49
|
+
data-variant={variant}
|
|
50
|
+
data-dividers={dividers}
|
|
51
|
+
data-orientation={orientation}
|
|
52
|
+
className={cn(
|
|
53
|
+
'group/detail-group grid min-w-0 [--detail-label-width:7rem]',
|
|
54
|
+
columnClasses[columns],
|
|
55
|
+
variant === 'plain' && !dividers && 'gap-x-6 gap-y-4',
|
|
56
|
+
dividers && 'gap-px overflow-hidden',
|
|
57
|
+
(variant === 'framed' || dividers) &&
|
|
58
|
+
!splitHorizontalField &&
|
|
59
|
+
'[&>[data-slot=detail-field]]:p-3',
|
|
60
|
+
variant === 'framed' &&
|
|
61
|
+
'overflow-hidden rounded-lg border border-border bg-transparent text-foreground',
|
|
62
|
+
// Default da coluna de rótulos como CLASSE, antes do `className`: assim uma classe do
|
|
63
|
+
// consumidor (`[--detail-label-width:9rem]`) a substitui. Em `style` inline, o default
|
|
64
|
+
// vencia qualquer classe e só `style` conseguia ajustar a largura.
|
|
65
|
+
splitHorizontalField && '[--detail-label-width:8.5rem]',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
style={style}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</dl>
|
|
73
|
+
</DetailGroupLayoutContext.Provider>
|
|
56
74
|
)
|
|
57
75
|
}
|
|
58
76
|
|
|
@@ -62,7 +80,8 @@ export interface DetailFieldProps extends Omit<
|
|
|
62
80
|
> {
|
|
63
81
|
label: React.ReactNode
|
|
64
82
|
/** `null`, `undefined`, string vazia ou só com espaços renderizam a ausência
|
|
65
|
-
*
|
|
83
|
+
* como travessão; “Não informado” ou `empty` preservam o significado para
|
|
84
|
+
* leitura assistiva. `0` e `false` seguem como valores. */
|
|
66
85
|
value: React.ReactNode
|
|
67
86
|
/** Ícone decorativo que identifica o campo antes do par chave/valor. */
|
|
68
87
|
icon?: React.ReactNode
|
|
@@ -76,29 +95,45 @@ function DetailField({
|
|
|
76
95
|
value,
|
|
77
96
|
icon,
|
|
78
97
|
empty,
|
|
98
|
+
style,
|
|
79
99
|
...props
|
|
80
100
|
}: DetailFieldProps): React.ReactElement {
|
|
101
|
+
const { splitHorizontalField } = React.useContext(DetailGroupLayoutContext)
|
|
81
102
|
const content = !isAbsentValue(value)
|
|
82
103
|
? value
|
|
83
104
|
: empty === undefined || typeof empty === 'string'
|
|
84
|
-
? <EmptyValue {...(empty !== undefined ? { label: empty } : {})} />
|
|
105
|
+
? <EmptyValue compact {...(empty !== undefined ? { label: empty } : {})} />
|
|
85
106
|
: empty
|
|
86
107
|
return (
|
|
87
108
|
<div
|
|
88
109
|
data-slot="detail-field"
|
|
89
110
|
className={cn(
|
|
90
111
|
'grid min-w-0 items-start',
|
|
91
|
-
'group-data-[orientation=vertical]/detail-group:grid-cols-1
|
|
92
|
-
|
|
112
|
+
'group-data-[orientation=vertical]/detail-group:grid-cols-1',
|
|
113
|
+
!splitHorizontalField &&
|
|
114
|
+
'group-data-[orientation=horizontal]/detail-group:grid-cols-[minmax(0,var(--detail-label-width))_minmax(0,1fr)] group-data-[orientation=horizontal]/detail-group:items-baseline group-data-[orientation=horizontal]/detail-group:gap-x-3',
|
|
115
|
+
splitHorizontalField && 'items-stretch gap-x-0',
|
|
116
|
+
'group-data-[orientation=vertical]/detail-group:gap-y-1',
|
|
93
117
|
'group-data-[dividers=true]/detail-group:outline group-data-[dividers=true]/detail-group:outline-1 group-data-[dividers=true]/detail-group:outline-border',
|
|
94
|
-
'group-data-[variant=framed]/detail-group:bg-
|
|
118
|
+
'group-data-[variant=framed]/detail-group:bg-transparent group-data-[variant=framed]/detail-group:text-foreground',
|
|
95
119
|
className,
|
|
96
120
|
)}
|
|
121
|
+
style={
|
|
122
|
+
splitHorizontalField
|
|
123
|
+
? {
|
|
124
|
+
gridTemplateColumns: 'minmax(0, var(--detail-label-width)) minmax(0, 1fr)',
|
|
125
|
+
...style,
|
|
126
|
+
}
|
|
127
|
+
: style
|
|
128
|
+
}
|
|
97
129
|
{...props}
|
|
98
130
|
>
|
|
99
131
|
<dt
|
|
100
132
|
data-slot="detail-field-label"
|
|
101
|
-
className=
|
|
133
|
+
className={cn(
|
|
134
|
+
'flex items-center gap-2.5 text-xs leading-snug font-medium text-muted-foreground group-data-[orientation=horizontal]/detail-group:text-sm',
|
|
135
|
+
splitHorizontalField && 'bg-muted/30 p-3 dark:bg-input/30',
|
|
136
|
+
)}
|
|
102
137
|
>
|
|
103
138
|
{icon !== undefined && (
|
|
104
139
|
<span
|
|
@@ -113,7 +148,10 @@ function DetailField({
|
|
|
113
148
|
</dt>
|
|
114
149
|
<dd
|
|
115
150
|
data-slot="detail-field-value"
|
|
116
|
-
className=
|
|
151
|
+
className={cn(
|
|
152
|
+
'min-w-0 text-sm leading-snug font-normal',
|
|
153
|
+
splitHorizontalField && 'flex items-center p-3',
|
|
154
|
+
)}
|
|
117
155
|
>
|
|
118
156
|
{content}
|
|
119
157
|
</dd>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
3
|
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
4
|
-
import { XIcon } from "lucide-react";
|
|
5
4
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CloseButton,
|
|
7
|
+
type CloseButtonSize,
|
|
8
|
+
} from "./close-button.tsx";
|
|
7
9
|
|
|
8
10
|
export type DialogMode = "default" | "alert";
|
|
9
11
|
|
|
@@ -19,11 +21,13 @@ const DialogContext = React.createContext<DialogContextValue>({
|
|
|
19
21
|
interface DialogContentContextValue {
|
|
20
22
|
showCloseButton: boolean;
|
|
21
23
|
closeDisabled: boolean;
|
|
24
|
+
closeSize: CloseButtonSize;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
const DialogContentContext = React.createContext<DialogContentContextValue>({
|
|
25
28
|
showCloseButton: false,
|
|
26
29
|
closeDisabled: false,
|
|
30
|
+
closeSize: "sm",
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
function useDialogContext(): DialogContextValue {
|
|
@@ -126,9 +130,9 @@ export function DialogClose({
|
|
|
126
130
|
);
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
/** Véu compartilhado por Dialog e Drawer: mesmo escurecimento
|
|
133
|
+
/** Véu compartilhado por Dialog e Drawer: mesmo escurecimento e animação. */
|
|
130
134
|
export const overlayClasses =
|
|
131
|
-
"fixed inset-0 z-50 bg-black/30
|
|
135
|
+
"fixed inset-0 z-50 bg-black/30 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0";
|
|
132
136
|
|
|
133
137
|
export function DialogOverlay({
|
|
134
138
|
className,
|
|
@@ -151,18 +155,16 @@ export function DialogOverlay({
|
|
|
151
155
|
);
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
function DialogCloseButton({
|
|
158
|
+
function DialogCloseButton({
|
|
159
|
+
disabled = false,
|
|
160
|
+
size,
|
|
161
|
+
}: {
|
|
162
|
+
disabled?: boolean;
|
|
163
|
+
size: CloseButtonSize;
|
|
164
|
+
}) {
|
|
155
165
|
return (
|
|
156
166
|
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
|
157
|
-
<
|
|
158
|
-
type="button"
|
|
159
|
-
size="icon"
|
|
160
|
-
variant="ghost"
|
|
161
|
-
disabled={disabled}
|
|
162
|
-
aria-label="Fechar"
|
|
163
|
-
>
|
|
164
|
-
<XIcon aria-hidden />
|
|
165
|
-
</Button>
|
|
167
|
+
<CloseButton size={size} disabled={disabled} />
|
|
166
168
|
</DialogPrimitive.Close>
|
|
167
169
|
);
|
|
168
170
|
}
|
|
@@ -172,6 +174,7 @@ export function DialogContent({
|
|
|
172
174
|
children,
|
|
173
175
|
showCloseButton,
|
|
174
176
|
closeDisabled = false,
|
|
177
|
+
closeSize = "sm",
|
|
175
178
|
"aria-describedby": ariaDescribedBy,
|
|
176
179
|
...props
|
|
177
180
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
@@ -179,11 +182,13 @@ export function DialogContent({
|
|
|
179
182
|
showCloseButton?: boolean;
|
|
180
183
|
/** Mantém a ação visível, mas impede fechar enquanto a superfície está bloqueada. */
|
|
181
184
|
closeDisabled?: boolean;
|
|
185
|
+
/** Tamanho da ação circular de fechar no cabeçalho. */
|
|
186
|
+
closeSize?: CloseButtonSize;
|
|
182
187
|
}) {
|
|
183
188
|
const { mode } = useDialogContext();
|
|
184
189
|
const showClose = showCloseButton ?? mode !== "alert";
|
|
185
190
|
const contentClasses =
|
|
186
|
-
"fixed left-[50%] top-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-clip rounded-xl border border-
|
|
191
|
+
"fixed left-[50%] top-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] overflow-clip rounded-xl border border-transparent bg-background text-foreground shadow-xl duration-200 outline-none 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 dark:border-border/70";
|
|
187
192
|
|
|
188
193
|
if (mode === "alert") {
|
|
189
194
|
return (
|
|
@@ -197,7 +202,11 @@ export function DialogContent({
|
|
|
197
202
|
{...props}
|
|
198
203
|
>
|
|
199
204
|
<DialogContentContext.Provider
|
|
200
|
-
value={{
|
|
205
|
+
value={{
|
|
206
|
+
showCloseButton: false,
|
|
207
|
+
closeDisabled: true,
|
|
208
|
+
closeSize,
|
|
209
|
+
}}
|
|
201
210
|
>
|
|
202
211
|
{children}
|
|
203
212
|
</DialogContentContext.Provider>
|
|
@@ -214,14 +223,14 @@ export function DialogContent({
|
|
|
214
223
|
data-mode="default"
|
|
215
224
|
className={cn(
|
|
216
225
|
contentClasses,
|
|
217
|
-
"flex max-h-[85vh]
|
|
226
|
+
"flex max-h-[85vh] max-w-lg flex-col",
|
|
218
227
|
className,
|
|
219
228
|
)}
|
|
220
229
|
aria-describedby={ariaDescribedBy}
|
|
221
230
|
{...props}
|
|
222
231
|
>
|
|
223
232
|
<DialogContentContext.Provider
|
|
224
|
-
value={{ showCloseButton: showClose, closeDisabled }}
|
|
233
|
+
value={{ showCloseButton: showClose, closeDisabled, closeSize }}
|
|
225
234
|
>
|
|
226
235
|
{children}
|
|
227
236
|
</DialogContentContext.Provider>
|
|
@@ -236,7 +245,7 @@ export function DialogHeader({
|
|
|
236
245
|
...props
|
|
237
246
|
}: React.ComponentProps<"div">) {
|
|
238
247
|
const { mode } = useDialogContext();
|
|
239
|
-
const { showCloseButton, closeDisabled } =
|
|
248
|
+
const { showCloseButton, closeDisabled, closeSize } =
|
|
240
249
|
React.useContext(DialogContentContext);
|
|
241
250
|
return (
|
|
242
251
|
<div
|
|
@@ -251,7 +260,7 @@ export function DialogHeader({
|
|
|
251
260
|
>
|
|
252
261
|
{children}
|
|
253
262
|
{mode !== "alert" && showCloseButton && (
|
|
254
|
-
<DialogCloseButton disabled={closeDisabled} />
|
|
263
|
+
<DialogCloseButton disabled={closeDisabled} size={closeSize} />
|
|
255
264
|
)}
|
|
256
265
|
</div>
|
|
257
266
|
);
|
|
@@ -275,6 +284,7 @@ export function DialogMedia({
|
|
|
275
284
|
|
|
276
285
|
export function DialogBody({
|
|
277
286
|
className,
|
|
287
|
+
style,
|
|
278
288
|
...props
|
|
279
289
|
}: React.ComponentProps<"div">) {
|
|
280
290
|
const { mode } = useDialogContext();
|
|
@@ -285,6 +295,11 @@ export function DialogBody({
|
|
|
285
295
|
mode === "alert" ? "min-w-0" : "min-h-0 flex-1 overflow-y-auto p-4",
|
|
286
296
|
className,
|
|
287
297
|
)}
|
|
298
|
+
style={
|
|
299
|
+
mode === "alert"
|
|
300
|
+
? style
|
|
301
|
+
: ({ "--opus-inline-gutter": "1rem", ...style } as React.CSSProperties)
|
|
302
|
+
}
|
|
288
303
|
{...props}
|
|
289
304
|
/>
|
|
290
305
|
);
|
|
@@ -301,7 +316,7 @@ export function DialogFooter({
|
|
|
301
316
|
className={cn(
|
|
302
317
|
mode === "alert"
|
|
303
318
|
? "w-full"
|
|
304
|
-
: "flex shrink-0 flex-
|
|
319
|
+
: "flex shrink-0 flex-row justify-end gap-2 border-t bg-muted/20 p-4 dark:bg-input/20",
|
|
305
320
|
className,
|
|
306
321
|
)}
|
|
307
322
|
{...props}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { XIcon } from "lucide-react";
|
|
3
2
|
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
4
3
|
|
|
5
4
|
import { cn } from "../../lib/cn.ts";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CloseButton,
|
|
7
|
+
type CloseButtonSize,
|
|
8
|
+
} from "./close-button.tsx";
|
|
7
9
|
import { overlayClasses } from "./dialog.tsx";
|
|
8
10
|
|
|
9
11
|
interface DrawerContentContextValue {
|
|
10
12
|
showCloseButton: boolean;
|
|
11
13
|
closeDisabled: boolean;
|
|
14
|
+
closeSize: CloseButtonSize;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
const DrawerContentContext = React.createContext<DrawerContentContextValue>({
|
|
15
18
|
showCloseButton: false,
|
|
16
19
|
closeDisabled: false,
|
|
20
|
+
closeSize: "sm",
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
function Drawer({
|
|
@@ -53,18 +57,16 @@ function DrawerOverlay({
|
|
|
53
57
|
);
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
function DrawerCloseButton({
|
|
60
|
+
function DrawerCloseButton({
|
|
61
|
+
disabled = false,
|
|
62
|
+
size,
|
|
63
|
+
}: {
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
size: CloseButtonSize;
|
|
66
|
+
}) {
|
|
57
67
|
return (
|
|
58
68
|
<DrawerPrimitive.Close data-slot="drawer-close" asChild>
|
|
59
|
-
<
|
|
60
|
-
type="button"
|
|
61
|
-
size="icon"
|
|
62
|
-
variant="ghost"
|
|
63
|
-
disabled={disabled}
|
|
64
|
-
aria-label="Fechar"
|
|
65
|
-
>
|
|
66
|
-
<XIcon aria-hidden />
|
|
67
|
-
</Button>
|
|
69
|
+
<CloseButton size={size} disabled={disabled} />
|
|
68
70
|
</DrawerPrimitive.Close>
|
|
69
71
|
);
|
|
70
72
|
}
|
|
@@ -75,12 +77,14 @@ function DrawerContent({
|
|
|
75
77
|
side = "right",
|
|
76
78
|
showCloseButton = true,
|
|
77
79
|
closeDisabled = false,
|
|
80
|
+
closeSize = "sm",
|
|
78
81
|
"aria-describedby": ariaDescribedBy,
|
|
79
82
|
...props
|
|
80
83
|
}: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
81
84
|
side?: "top" | "right" | "bottom" | "left";
|
|
82
85
|
showCloseButton?: boolean;
|
|
83
86
|
closeDisabled?: boolean;
|
|
87
|
+
closeSize?: CloseButtonSize;
|
|
84
88
|
}) {
|
|
85
89
|
return (
|
|
86
90
|
<DrawerPortal>
|
|
@@ -90,9 +94,9 @@ function DrawerContent({
|
|
|
90
94
|
className={cn(
|
|
91
95
|
"fixed z-50 flex flex-col bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
92
96
|
side === "right" &&
|
|
93
|
-
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right
|
|
97
|
+
"inset-y-0 right-0 h-full w-3/4 max-w-sm border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
|
94
98
|
side === "left" &&
|
|
95
|
-
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left
|
|
99
|
+
"inset-y-0 left-0 h-full w-3/4 max-w-sm border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
|
|
96
100
|
side === "top" &&
|
|
97
101
|
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
98
102
|
side === "bottom" &&
|
|
@@ -103,7 +107,7 @@ function DrawerContent({
|
|
|
103
107
|
{...props}
|
|
104
108
|
>
|
|
105
109
|
<DrawerContentContext.Provider
|
|
106
|
-
value={{ showCloseButton, closeDisabled }}
|
|
110
|
+
value={{ showCloseButton, closeDisabled, closeSize }}
|
|
107
111
|
>
|
|
108
112
|
{children}
|
|
109
113
|
</DrawerContentContext.Provider>
|
|
@@ -117,7 +121,7 @@ function DrawerHeader({
|
|
|
117
121
|
children,
|
|
118
122
|
...props
|
|
119
123
|
}: React.ComponentProps<"div">) {
|
|
120
|
-
const { showCloseButton, closeDisabled } =
|
|
124
|
+
const { showCloseButton, closeDisabled, closeSize } =
|
|
121
125
|
React.useContext(DrawerContentContext);
|
|
122
126
|
return (
|
|
123
127
|
<div
|
|
@@ -126,16 +130,19 @@ function DrawerHeader({
|
|
|
126
130
|
{...props}
|
|
127
131
|
>
|
|
128
132
|
{children}
|
|
129
|
-
{showCloseButton &&
|
|
133
|
+
{showCloseButton && (
|
|
134
|
+
<DrawerCloseButton disabled={closeDisabled} size={closeSize} />
|
|
135
|
+
)}
|
|
130
136
|
</div>
|
|
131
137
|
);
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
function DrawerBody({ className, ...props }: React.ComponentProps<"div">) {
|
|
140
|
+
function DrawerBody({ className, style, ...props }: React.ComponentProps<"div">) {
|
|
135
141
|
return (
|
|
136
142
|
<div
|
|
137
143
|
data-slot="drawer-body"
|
|
138
144
|
className={cn("min-h-0 flex-1 overflow-y-auto p-4", className)}
|
|
145
|
+
style={{ "--opus-inline-gutter": "1rem", ...style } as React.CSSProperties}
|
|
139
146
|
{...props}
|
|
140
147
|
/>
|
|
141
148
|
);
|
|
@@ -146,7 +153,7 @@ function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
146
153
|
<div
|
|
147
154
|
data-slot="drawer-footer"
|
|
148
155
|
className={cn(
|
|
149
|
-
"mt-auto flex shrink-0 flex-
|
|
156
|
+
"mt-auto flex shrink-0 flex-row justify-end gap-2 border-t p-4",
|
|
150
157
|
className,
|
|
151
158
|
)}
|
|
152
159
|
{...props}
|
|
@@ -19,9 +19,9 @@ export interface EmptyValueProps extends Omit<React.ComponentProps<'span'>, 'chi
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Representação padrão de valor ausente.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Representação padrão de valor ausente. Sem `compact`, o rótulo aparece; com `compact`
|
|
23
|
+
* — o default das colunas de ActionList e de DetailField — aparece o travessão e o rótulo
|
|
24
|
+
* vai para o leitor de tela. Um domínio troca o rótulo por `label`
|
|
25
25
|
* (“Nunca enviado”, “Sem vencimento”, “Não se aplica”) em vez de repetir a condicional.
|
|
26
26
|
*/
|
|
27
27
|
export function EmptyValue({
|
|
@@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
7
7
|
<div
|
|
8
8
|
data-slot="empty"
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border border-dashed border-border p-
|
|
10
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border border-dashed border-border p-12 text-center text-balance",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -44,7 +44,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
44
44
|
<div
|
|
45
45
|
data-slot="field-group"
|
|
46
46
|
className={cn(
|
|
47
|
-
"group/field-group
|
|
47
|
+
"group/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
|
|
48
48
|
className
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
|
@@ -52,22 +52,22 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
52
52
|
)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const horizontalFieldClasses = [
|
|
56
|
+
"flex-row items-center gap-3",
|
|
57
|
+
"[&>[data-slot=field-label]]:flex-auto",
|
|
58
|
+
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
59
|
+
];
|
|
60
|
+
|
|
55
61
|
const fieldVariants = cva(
|
|
56
62
|
"group/field flex w-full data-[invalid=true]:text-context-danger-emphasis",
|
|
57
63
|
{
|
|
58
64
|
variants: {
|
|
59
65
|
orientation: {
|
|
60
66
|
vertical: ["flex-col gap-1.5 [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
61
|
-
horizontal:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
],
|
|
66
|
-
responsive: [
|
|
67
|
-
"flex-col gap-1.5 @md/field-group:flex-row @md/field-group:items-center @md/field-group:gap-3 [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
|
|
68
|
-
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
69
|
-
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
|
|
70
|
-
],
|
|
67
|
+
horizontal: horizontalFieldClasses,
|
|
68
|
+
// Compatibilidade da série 18: sem breakpoints, `responsive` resolve para o
|
|
69
|
+
// mesmo layout desktop da orientação horizontal.
|
|
70
|
+
responsive: horizontalFieldClasses,
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
73
|
defaultVariants: {
|
|
@@ -113,7 +113,7 @@ function FieldLabel({
|
|
|
113
113
|
<Label
|
|
114
114
|
data-slot="field-label"
|
|
115
115
|
className={cn(
|
|
116
|
-
"group/field-label peer/field-label flex w-fit gap-2 text-
|
|
116
|
+
"group/field-label peer/field-label flex w-fit gap-2 text-sm leading-snug font-medium text-muted-foreground group-data-[invalid=true]/field:text-context-danger-emphasis group-data-[disabled=true]/field:opacity-50",
|
|
117
117
|
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
|
|
118
118
|
"has-[>[data-slot=field]]:has-data-[state=checked]:border-primary has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/5 dark:has-[>[data-slot=field]]:has-data-[state=checked]:bg-primary/10",
|
|
119
119
|
className
|
|
@@ -142,7 +142,7 @@ export function IconPicker({
|
|
|
142
142
|
aria-describedby={ariaDescribedBy}
|
|
143
143
|
data-slot="icon-picker"
|
|
144
144
|
className={cn(
|
|
145
|
-
'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-
|
|
145
|
+
'flex h-10 w-full items-center gap-2 rounded-md border border-input bg-muted/30 px-3 text-sm outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
|
|
146
146
|
focusRing,
|
|
147
147
|
className,
|
|
148
148
|
)}
|
|
@@ -14,7 +14,7 @@ function InputGroup({ className, shape = 'default', ...props }: React.ComponentP
|
|
|
14
14
|
data-shape={shape}
|
|
15
15
|
role="group"
|
|
16
16
|
className={cn(
|
|
17
|
-
"group/input-group relative flex w-full items-center rounded-md border border-input transition-[color,box-shadow] outline-none data-[shape=pill]:rounded-full dark:bg-input/30",
|
|
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",
|
|
18
18
|
"h-9 min-w-0 has-[>textarea]:h-auto",
|
|
19
19
|
|
|
20
20
|
// Variants based on alignment.
|
|
@@ -14,7 +14,7 @@ export interface InputProps extends React.ComponentProps<"input"> {
|
|
|
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-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"
|
|
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).
|
|
@@ -31,7 +31,7 @@ function Input({ className, type, icon, trailing, ...props }: InputProps) {
|
|
|
31
31
|
<div
|
|
32
32
|
data-slot="input-wrapper"
|
|
33
33
|
className={cn(
|
|
34
|
-
"flex h-9 w-full min-w-0 items-center gap-1.5 rounded-md border border-input bg-
|
|
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",
|
|
35
35
|
"has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[0.1875rem] has-[input:focus-visible]:ring-ring/50",
|
|
36
36
|
"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
37
|
"has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50",
|
|
@@ -7,7 +7,7 @@ import type { ControlSize } from "./control.ts";
|
|
|
7
7
|
import { Separator } from "./separator.tsx";
|
|
8
8
|
|
|
9
9
|
const itemGroupVariants = cva(
|
|
10
|
-
"group/item-group
|
|
10
|
+
"group/item-group flex flex-col",
|
|
11
11
|
{
|
|
12
12
|
variants: {
|
|
13
13
|
variant: {
|
|
@@ -75,8 +75,8 @@ const itemVariants = cva(
|
|
|
75
75
|
},
|
|
76
76
|
// Nomes da escala de control.ts; a linha não tem altura fixa, `sm` aperta o respiro.
|
|
77
77
|
size: {
|
|
78
|
-
default: "gap-
|
|
79
|
-
sm: "gap-2.5 px-
|
|
78
|
+
default: "gap-3 p-3",
|
|
79
|
+
sm: "gap-2.5 px-3 py-2",
|
|
80
80
|
} satisfies Partial<Record<ControlSize, string>>,
|
|
81
81
|
},
|
|
82
82
|
defaultVariants: { variant: "default", size: "default" },
|
|
@@ -223,7 +223,7 @@ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
223
223
|
<div
|
|
224
224
|
data-slot="item-title"
|
|
225
225
|
className={cn(
|
|
226
|
-
"flex w-fit items-center gap-2
|
|
226
|
+
"flex w-fit items-center gap-2 leading-snug font-medium",
|
|
227
227
|
className,
|
|
228
228
|
)}
|
|
229
229
|
{...props}
|
|
@@ -241,7 +241,7 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
241
241
|
<p
|
|
242
242
|
data-slot="item-description"
|
|
243
243
|
className={cn(
|
|
244
|
-
"line-clamp-2
|
|
244
|
+
"line-clamp-2 leading-normal font-normal text-balance text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
245
245
|
className,
|
|
246
246
|
)}
|
|
247
247
|
{...props}
|
|
@@ -104,11 +104,11 @@ function PaginationPrevious({
|
|
|
104
104
|
<PaginationLink
|
|
105
105
|
aria-label={label}
|
|
106
106
|
size={size ?? (iconOnly ? 'icon' : 'default')}
|
|
107
|
-
className={cn(!iconOnly && 'gap-1 px-2.5
|
|
107
|
+
className={cn(!iconOnly && 'gap-1 px-2.5 pl-2.5', className)}
|
|
108
108
|
{...props}
|
|
109
109
|
>
|
|
110
110
|
<ChevronLeftIcon className={iconClassName} />
|
|
111
|
-
{!iconOnly && <span
|
|
111
|
+
{!iconOnly && <span>{label}</span>}
|
|
112
112
|
</PaginationLink>
|
|
113
113
|
)
|
|
114
114
|
}
|
|
@@ -125,10 +125,10 @@ function PaginationNext({
|
|
|
125
125
|
<PaginationLink
|
|
126
126
|
aria-label={label}
|
|
127
127
|
size={size ?? (iconOnly ? 'icon' : 'default')}
|
|
128
|
-
className={cn(!iconOnly && 'gap-1 px-2.5
|
|
128
|
+
className={cn(!iconOnly && 'gap-1 px-2.5 pr-2.5', className)}
|
|
129
129
|
{...props}
|
|
130
130
|
>
|
|
131
|
-
{!iconOnly && <span
|
|
131
|
+
{!iconOnly && <span>{label}</span>}
|
|
132
132
|
<ChevronRightIcon className={iconClassName} />
|
|
133
133
|
</PaginationLink>
|
|
134
134
|
)
|
|
@@ -25,7 +25,7 @@ function RadioGroupItem({
|
|
|
25
25
|
<RadioGroupPrimitive.Item
|
|
26
26
|
data-slot="radio-group-item"
|
|
27
27
|
className={cn(
|
|
28
|
-
"aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none 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:bg-input/30 dark:aria-invalid:ring-context-danger/40",
|
|
28
|
+
"peer aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none 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:bg-input/30 dark:aria-invalid:ring-context-danger/40",
|
|
29
29
|
className
|
|
30
30
|
)}
|
|
31
31
|
{...props}
|