@softize/opus 12.10.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/bin/lib/check.mjs +1098 -310
- package/bin/lib/copy.mjs +12 -5
- package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
- package/docs/adr/0004-page-content-state-is-composed.md +65 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +180 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
- package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
- package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
- package/docs/radius-scale.md +1 -1
- package/package.json +1 -1
- package/registry/instructions/opus.md +5 -0
- package/registry/skills/build-opus-ui/SKILL.md +27 -16
- package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
- package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
- package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
- package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
- package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
- package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
- package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
- package/registry/templates/app/src/App.tsx +1 -1
- package/src/core/dictionary.ts +52 -14
- package/src/core/index.ts +10 -0
- package/src/core/ui-context.ts +29 -0
- package/src/schema/drivers/zod.ts +17 -8
- package/src/ui/components/patterns/action-form-card.tsx +18 -12
- package/src/ui/components/patterns/confirm.tsx +163 -40
- package/src/ui/components/patterns/content-header.tsx +335 -61
- package/src/ui/components/patterns/data-state.tsx +23 -10
- package/src/ui/components/patterns/list.tsx +1097 -783
- package/src/ui/components/patterns/page-state.tsx +115 -0
- package/src/ui/components/patterns/page.tsx +231 -41
- package/src/ui/components/patterns/sidebar.tsx +357 -83
- package/src/ui/components/patterns/trigger.tsx +37 -30
- package/src/ui/components/patterns/view.tsx +7 -11
- package/src/ui/components/primitives/alert.tsx +298 -110
- package/src/ui/components/primitives/ask.tsx +2 -1
- package/src/ui/components/primitives/badge.tsx +91 -30
- package/src/ui/components/primitives/button.tsx +99 -60
- package/src/ui/components/primitives/calendar.tsx +39 -39
- package/src/ui/components/primitives/card.tsx +96 -23
- package/src/ui/components/primitives/detail.tsx +2 -2
- package/src/ui/components/primitives/dialog.tsx +196 -39
- package/src/ui/components/primitives/dictionary-value.tsx +9 -14
- package/src/ui/components/primitives/dot.tsx +74 -21
- package/src/ui/components/primitives/drawer.tsx +40 -24
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/item.tsx +135 -79
- package/src/ui/components/primitives/menu.tsx +11 -3
- package/src/ui/components/primitives/metric-card.tsx +133 -0
- package/src/ui/components/primitives/sonner.tsx +187 -8
- package/src/ui/components/primitives/table.tsx +2 -2
- package/src/ui/docs/DocBrowser.tsx +104 -25
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/accordion.md +22 -16
- package/src/ui/docs/content/action-form-card.md +8 -8
- package/src/ui/docs/content/action-form-dialog.md +9 -9
- package/src/ui/docs/content/action-form.md +28 -34
- package/src/ui/docs/content/action-list-dialog.md +11 -6
- package/src/ui/docs/content/action-list.md +64 -39
- package/src/ui/docs/content/action-trigger.md +21 -14
- package/src/ui/docs/content/action-view.md +8 -8
- package/src/ui/docs/content/actions.md +9 -9
- package/src/ui/docs/content/ai.md +3 -3
- package/src/ui/docs/content/alert.md +54 -28
- package/src/ui/docs/content/aspect-ratio.md +4 -4
- package/src/ui/docs/content/audit.md +2 -2
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/avatar.md +34 -14
- package/src/ui/docs/content/badge.md +21 -22
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +93 -15
- package/src/ui/docs/content/calendar.md +5 -5
- package/src/ui/docs/content/card.md +6 -6
- package/src/ui/docs/content/carousel.md +16 -11
- package/src/ui/docs/content/chat.md +3 -3
- package/src/ui/docs/content/checkbox.md +7 -7
- package/src/ui/docs/content/cli.md +5 -5
- package/src/ui/docs/content/collapsible.md +8 -8
- package/src/ui/docs/content/command.md +16 -8
- package/src/ui/docs/content/composer.md +2 -2
- package/src/ui/docs/content/content.md +44 -0
- package/src/ui/docs/content/copyable.md +4 -3
- package/src/ui/docs/content/customization.md +7 -7
- package/src/ui/docs/content/cycle.md +3 -3
- package/src/ui/docs/content/data-state.md +11 -12
- package/src/ui/docs/content/data.md +26 -33
- package/src/ui/docs/content/detail.md +8 -5
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +19 -18
- package/src/ui/docs/content/dock.md +3 -3
- package/src/ui/docs/content/dot.md +7 -7
- package/src/ui/docs/content/drawer.md +32 -16
- package/src/ui/docs/content/empty-value.md +2 -2
- package/src/ui/docs/content/empty.md +19 -12
- package/src/ui/docs/content/events.md +4 -4
- package/src/ui/docs/content/field.md +34 -12
- package/src/ui/docs/content/getting-started.md +1 -1
- package/src/ui/docs/content/icon-picker.md +8 -4
- package/src/ui/docs/content/input-otp.md +20 -12
- package/src/ui/docs/content/input.md +121 -9
- package/src/ui/docs/content/item.md +64 -24
- package/src/ui/docs/content/kbd.md +19 -11
- package/src/ui/docs/content/label.md +5 -3
- package/src/ui/docs/content/log.md +4 -4
- package/src/ui/docs/content/markdown.md +7 -6
- package/src/ui/docs/content/mcp.md +13 -15
- package/src/ui/docs/content/menu.md +36 -17
- package/src/ui/docs/content/metric-card.md +41 -0
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +93 -10
- package/src/ui/docs/content/pagination.md +22 -17
- package/src/ui/docs/content/popover.md +16 -8
- package/src/ui/docs/content/progress.md +7 -5
- package/src/ui/docs/content/queue.md +5 -5
- package/src/ui/docs/content/radio-group.md +20 -12
- package/src/ui/docs/content/router.md +11 -6
- package/src/ui/docs/content/scheduler.md +4 -5
- package/src/ui/docs/content/scroll-area.md +12 -7
- package/src/ui/docs/content/select.md +42 -29
- package/src/ui/docs/content/semantic-context.md +63 -0
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +325 -56
- package/src/ui/docs/content/skeleton.md +5 -4
- package/src/ui/docs/content/slider.md +8 -7
- package/src/ui/docs/content/spinner.md +8 -8
- package/src/ui/docs/content/split.md +8 -5
- package/src/ui/docs/content/storage.md +6 -8
- package/src/ui/docs/content/switch.md +8 -7
- package/src/ui/docs/content/table.md +16 -6
- package/src/ui/docs/content/tabs.md +28 -14
- package/src/ui/docs/content/testing.md +9 -11
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +47 -13
- package/src/ui/docs/content/toggle.md +75 -7
- package/src/ui/docs/content/tokens.md +31 -3
- package/src/ui/docs/content/tooltip.md +19 -11
- package/src/ui/docs/content/truncate.md +7 -8
- package/src/ui/docs/content/ui.md +10 -9
- package/src/ui/docs/content/upgrading.md +7 -8
- package/src/ui/docs/doc-client.tsx +2 -2
- package/src/ui/docs/registry.tsx +580 -229
- package/src/ui/lib/semantic-context.ts +30 -0
- package/src/ui/meta.ts +278 -286
- package/src/ui/react.tsx +377 -111
- package/src/ui/theme.css +116 -0
- package/src/ui/components/primitives/alert-dialog.tsx +0 -190
- package/src/ui/docs/content/alert-dialog.md +0 -73
- package/src/ui/docs/content/button-group.md +0 -71
- package/src/ui/docs/content/confirm.md +0 -120
- package/src/ui/docs/content/input-group.md +0 -78
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -1,35 +1,134 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
3
4
|
import { XIcon } from 'lucide-react'
|
|
4
5
|
import { cn } from '../../lib/cn.ts'
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
7
|
+
export type DialogMode = 'default' | 'alert'
|
|
8
|
+
|
|
9
|
+
interface DialogContextValue {
|
|
10
|
+
mode: DialogMode
|
|
11
|
+
onResult?: (result: string) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DialogContext = React.createContext<DialogContextValue>({ mode: 'default' })
|
|
15
|
+
|
|
16
|
+
function useDialogContext(): DialogContextValue {
|
|
17
|
+
return React.useContext(DialogContext)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DialogProps
|
|
21
|
+
extends React.ComponentProps<typeof DialogPrimitive.Root> {
|
|
22
|
+
/** `alert` exige uma resposta explícita e deriva `role="alertdialog"`. */
|
|
23
|
+
mode?: DialogMode
|
|
24
|
+
/** Recebe o `result` do `DialogClose` acionado. */
|
|
25
|
+
onResult?: (result: string) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Dialog({
|
|
29
|
+
mode = 'default',
|
|
30
|
+
onResult,
|
|
31
|
+
modal,
|
|
32
|
+
...props
|
|
33
|
+
}: DialogProps) {
|
|
34
|
+
const context = React.useMemo(() => ({ mode, onResult }), [mode, onResult])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<DialogContext.Provider value={context}>
|
|
38
|
+
{mode === 'alert' ? (
|
|
39
|
+
<AlertDialogPrimitive.Root data-slot="dialog" {...props} />
|
|
40
|
+
) : (
|
|
41
|
+
<DialogPrimitive.Root data-slot="dialog" modal={modal} {...props} />
|
|
42
|
+
)}
|
|
43
|
+
</DialogContext.Provider>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function DialogTrigger({
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
50
|
+
const { mode } = useDialogContext()
|
|
51
|
+
return mode === 'alert' ? (
|
|
52
|
+
<AlertDialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
53
|
+
) : (
|
|
54
|
+
<DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
55
|
+
)
|
|
8
56
|
}
|
|
9
57
|
|
|
10
|
-
export function
|
|
11
|
-
|
|
58
|
+
export function DialogPortal({
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
61
|
+
const { mode } = useDialogContext()
|
|
62
|
+
return mode === 'alert' ? (
|
|
63
|
+
<AlertDialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
64
|
+
) : (
|
|
65
|
+
<DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
66
|
+
)
|
|
12
67
|
}
|
|
13
68
|
|
|
14
|
-
export
|
|
15
|
-
|
|
69
|
+
export interface DialogCloseProps
|
|
70
|
+
extends React.ComponentProps<typeof DialogPrimitive.Close> {
|
|
71
|
+
/** No modo `alert`, registra este controle como destino inicial seguro do foco. */
|
|
72
|
+
initialFocus?: boolean
|
|
73
|
+
/** Resposta entregue a `Dialog.onResult` antes do fechamento. */
|
|
74
|
+
result?: string
|
|
16
75
|
}
|
|
17
76
|
|
|
18
|
-
export function DialogClose({
|
|
19
|
-
|
|
77
|
+
export function DialogClose({
|
|
78
|
+
initialFocus = false,
|
|
79
|
+
result,
|
|
80
|
+
onClick,
|
|
81
|
+
...props
|
|
82
|
+
}: DialogCloseProps) {
|
|
83
|
+
const root = useDialogContext()
|
|
84
|
+
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
85
|
+
onClick?.(event)
|
|
86
|
+
if (!event.defaultPrevented && result !== undefined) root.onResult?.(result)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (root.mode === 'alert') {
|
|
90
|
+
const ClosePrimitive = initialFocus
|
|
91
|
+
? AlertDialogPrimitive.Cancel
|
|
92
|
+
: AlertDialogPrimitive.Action
|
|
93
|
+
return (
|
|
94
|
+
<ClosePrimitive
|
|
95
|
+
data-slot="dialog-close"
|
|
96
|
+
data-initial-focus={initialFocus || undefined}
|
|
97
|
+
onClick={handleClick}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<DialogPrimitive.Close
|
|
105
|
+
data-slot="dialog-close"
|
|
106
|
+
data-initial-focus={initialFocus || undefined}
|
|
107
|
+
onClick={handleClick}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
20
111
|
}
|
|
21
112
|
|
|
22
113
|
export function DialogOverlay({
|
|
23
114
|
className,
|
|
24
115
|
...props
|
|
25
116
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
26
|
-
|
|
117
|
+
const { mode } = useDialogContext()
|
|
118
|
+
const overlayClasses = cn(
|
|
119
|
+
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
120
|
+
className,
|
|
121
|
+
)
|
|
122
|
+
return mode === 'alert' ? (
|
|
123
|
+
<AlertDialogPrimitive.Overlay
|
|
124
|
+
data-slot="dialog-overlay"
|
|
125
|
+
className={overlayClasses}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
27
129
|
<DialogPrimitive.Overlay
|
|
28
130
|
data-slot="dialog-overlay"
|
|
29
|
-
className={
|
|
30
|
-
'fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
|
|
31
|
-
className,
|
|
32
|
-
)}
|
|
131
|
+
className={overlayClasses}
|
|
33
132
|
{...props}
|
|
34
133
|
/>
|
|
35
134
|
)
|
|
@@ -38,29 +137,48 @@ export function DialogOverlay({
|
|
|
38
137
|
export function DialogContent({
|
|
39
138
|
className,
|
|
40
139
|
children,
|
|
41
|
-
showCloseButton
|
|
140
|
+
showCloseButton,
|
|
42
141
|
...props
|
|
43
142
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
44
|
-
/** Mostra o
|
|
143
|
+
/** Mostra o “X” no modo padrão. O modo `alert` exige uma resposta identificável. */
|
|
45
144
|
showCloseButton?: boolean
|
|
46
145
|
}) {
|
|
146
|
+
const { mode } = useDialogContext()
|
|
147
|
+
const showClose = showCloseButton ?? mode !== 'alert'
|
|
148
|
+
const contentClasses =
|
|
149
|
+
'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-border bg-background text-foreground shadow-lg 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'
|
|
150
|
+
|
|
151
|
+
if (mode === 'alert') {
|
|
152
|
+
return (
|
|
153
|
+
<DialogPortal>
|
|
154
|
+
<DialogOverlay />
|
|
155
|
+
<AlertDialogPrimitive.Content
|
|
156
|
+
data-slot="dialog-content"
|
|
157
|
+
data-mode="alert"
|
|
158
|
+
className={cn(contentClasses, 'grid max-w-xs gap-4 p-6', className)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
{children}
|
|
162
|
+
</AlertDialogPrimitive.Content>
|
|
163
|
+
</DialogPortal>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
47
167
|
return (
|
|
48
168
|
<DialogPortal>
|
|
49
169
|
<DialogOverlay />
|
|
50
|
-
{/* Superfície elevada: mantemos bg-card (divergência da casa; o shadcn usa bg-background). */}
|
|
51
170
|
<DialogPrimitive.Content
|
|
52
171
|
data-slot="dialog-content"
|
|
172
|
+
data-mode="default"
|
|
53
173
|
className={cn(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// precisa negar margem pra escapar de um p-6 embutido.
|
|
57
|
-
"fixed left-[50%] top-[50%] z-50 flex max-h-[85vh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col overflow-clip rounded-xl bg-card text-card-foreground shadow-lg 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 sm:max-w-lg",
|
|
174
|
+
contentClasses,
|
|
175
|
+
'flex max-h-[85vh] flex-col sm:max-w-lg',
|
|
58
176
|
className,
|
|
59
177
|
)}
|
|
60
178
|
{...props}
|
|
61
179
|
>
|
|
62
180
|
{children}
|
|
63
|
-
{
|
|
181
|
+
{showClose && (
|
|
64
182
|
<DialogPrimitive.Close
|
|
65
183
|
data-slot="dialog-close"
|
|
66
184
|
className="absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
@@ -74,17 +192,18 @@ export function DialogContent({
|
|
|
74
192
|
)
|
|
75
193
|
}
|
|
76
194
|
|
|
77
|
-
export function DialogHeader({
|
|
195
|
+
export function DialogHeader({
|
|
196
|
+
className,
|
|
197
|
+
...props
|
|
198
|
+
}: React.ComponentProps<'div'>) {
|
|
199
|
+
const { mode } = useDialogContext()
|
|
78
200
|
return (
|
|
79
201
|
<div
|
|
80
202
|
data-slot="dialog-header"
|
|
81
|
-
// Fixo (shrink-0), com divisor embaixo (border-b). p-5: padding igual nos quatro
|
|
82
|
-
// lados, casando com o body. O :has tira o divisor quando o header é seguido
|
|
83
|
-
// DIRETO pelo footer (confirm sem body) — senão o border-b dele encostaria no
|
|
84
|
-
// A compensação coincide com a soma das hairlines; o divisor fica no footer.
|
|
85
203
|
className={cn(
|
|
86
|
-
|
|
87
|
-
|
|
204
|
+
mode === 'alert'
|
|
205
|
+
? 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=dialog-media]:grid-rows-[auto_auto_1fr]'
|
|
206
|
+
: 'flex shrink-0 flex-col gap-2 border-b p-5 text-center sm:text-left [&:has(+[data-slot=dialog-footer])]:border-b-0',
|
|
88
207
|
className,
|
|
89
208
|
)}
|
|
90
209
|
{...props}
|
|
@@ -92,27 +211,51 @@ export function DialogHeader({ className, ...props }: React.ComponentProps<'div'
|
|
|
92
211
|
)
|
|
93
212
|
}
|
|
94
213
|
|
|
95
|
-
export function
|
|
214
|
+
export function DialogMedia({
|
|
215
|
+
className,
|
|
216
|
+
...props
|
|
217
|
+
}: React.ComponentProps<'div'>) {
|
|
218
|
+
return (
|
|
219
|
+
<div
|
|
220
|
+
data-slot="dialog-media"
|
|
221
|
+
className={cn(
|
|
222
|
+
'mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted *:[svg:not([class*="size-"])]:size-8',
|
|
223
|
+
className,
|
|
224
|
+
)}
|
|
225
|
+
{...props}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function DialogBody({
|
|
231
|
+
className,
|
|
232
|
+
...props
|
|
233
|
+
}: React.ComponentProps<'div'>) {
|
|
234
|
+
const { mode } = useDialogContext()
|
|
96
235
|
return (
|
|
97
236
|
<div
|
|
98
237
|
data-slot="dialog-body"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
238
|
+
className={cn(
|
|
239
|
+
mode === 'alert' ? 'min-w-0' : 'min-h-0 flex-1 overflow-y-auto p-5',
|
|
240
|
+
className,
|
|
241
|
+
)}
|
|
102
242
|
{...props}
|
|
103
243
|
/>
|
|
104
244
|
)
|
|
105
245
|
}
|
|
106
246
|
|
|
107
|
-
export function DialogFooter({
|
|
247
|
+
export function DialogFooter({
|
|
248
|
+
className,
|
|
249
|
+
...props
|
|
250
|
+
}: React.ComponentProps<'div'>) {
|
|
251
|
+
const { mode } = useDialogContext()
|
|
108
252
|
return (
|
|
109
253
|
<div
|
|
110
254
|
data-slot="dialog-footer"
|
|
111
|
-
// Faixa destacada (divergência da casa, ao estilo do site do shadcn): borda em cima
|
|
112
|
-
// + fundo muted. Sangra até as bordas sozinha — o content não tem padding, então não
|
|
113
|
-
// há -mx pra compensar; o recorte do content arredonda o canto de baixo.
|
|
114
255
|
className={cn(
|
|
115
|
-
|
|
256
|
+
mode === 'alert'
|
|
257
|
+
? 'grid grid-cols-2 gap-2 [&>*:only-child]:col-span-full'
|
|
258
|
+
: 'flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end',
|
|
116
259
|
className,
|
|
117
260
|
)}
|
|
118
261
|
{...props}
|
|
@@ -124,7 +267,14 @@ export function DialogTitle({
|
|
|
124
267
|
className,
|
|
125
268
|
...props
|
|
126
269
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
127
|
-
|
|
270
|
+
const { mode } = useDialogContext()
|
|
271
|
+
return mode === 'alert' ? (
|
|
272
|
+
<AlertDialogPrimitive.Title
|
|
273
|
+
data-slot="dialog-title"
|
|
274
|
+
className={cn('text-lg font-semibold', className)}
|
|
275
|
+
{...props}
|
|
276
|
+
/>
|
|
277
|
+
) : (
|
|
128
278
|
<DialogPrimitive.Title
|
|
129
279
|
data-slot="dialog-title"
|
|
130
280
|
className={cn('text-lg font-semibold leading-none', className)}
|
|
@@ -137,7 +287,14 @@ export function DialogDescription({
|
|
|
137
287
|
className,
|
|
138
288
|
...props
|
|
139
289
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
140
|
-
|
|
290
|
+
const { mode } = useDialogContext()
|
|
291
|
+
return mode === 'alert' ? (
|
|
292
|
+
<AlertDialogPrimitive.Description
|
|
293
|
+
data-slot="dialog-description"
|
|
294
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
295
|
+
{...props}
|
|
296
|
+
/>
|
|
297
|
+
) : (
|
|
141
298
|
<DialogPrimitive.Description
|
|
142
299
|
data-slot="dialog-description"
|
|
143
300
|
className={cn('text-sm text-muted-foreground', className)}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
presentDictionaryValue,
|
|
7
7
|
type DictionaryDescriptor,
|
|
8
8
|
type DictPresentation,
|
|
9
|
-
type DictTone,
|
|
10
9
|
} from '../../../core/index.ts'
|
|
11
10
|
import { cn } from '../../lib/cn.ts'
|
|
12
11
|
import { Badge, type BadgeProps } from './badge.tsx'
|
|
@@ -29,7 +28,9 @@ export interface DictionaryValueProps extends Omit<React.ComponentProps<'span'>,
|
|
|
29
28
|
value: string | null | undefined
|
|
30
29
|
/** Sobrepõe o papel declarado no dicionário SÓ nesta ocorrência. */
|
|
31
30
|
presentation?: DictPresentation
|
|
32
|
-
/** Sobrepõe
|
|
31
|
+
/** Sobrepõe o contexto semântico (override explícito). Valor fora do dicionário segue texto. */
|
|
32
|
+
context?: BadgeProps['context']
|
|
33
|
+
/** Sobrepõe o tratamento visual do badge. Valor fora do dicionário segue texto. */
|
|
33
34
|
variant?: BadgeProps['variant']
|
|
34
35
|
/** `false` esconde o ícone declarado; um nó substitui o ícone do catálogo. */
|
|
35
36
|
icon?: boolean | React.ReactNode
|
|
@@ -41,22 +42,14 @@ export interface DictionaryValueProps extends Omit<React.ComponentProps<'span'>,
|
|
|
41
42
|
fallback?: React.ReactNode
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
const toneVariant: Record<DictTone, NonNullable<BadgeProps['variant']>> = {
|
|
45
|
-
neutral: 'secondary',
|
|
46
|
-
info: 'info',
|
|
47
|
-
success: 'success',
|
|
48
|
-
warning: 'warning',
|
|
49
|
-
danger: 'danger',
|
|
50
|
-
}
|
|
51
|
-
|
|
52
45
|
function isDescriptor(source: DictionaryValueSource): source is DictionaryDescriptor {
|
|
53
46
|
return 'entries' in source && 'keys' in source
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
/**
|
|
57
50
|
* Renderiza um valor de dicionário com a apresentação que o DICIONÁRIO declarou (ADR 0003):
|
|
58
|
-
* classificação → Badge `outline`; status/estágio → Badge
|
|
59
|
-
* (`neutral` sem
|
|
51
|
+
* classificação → Badge `neutral + outline`; status/estágio → Badge `context + subtle`
|
|
52
|
+
* (`neutral` sem contexto); `plain` ou sem papel → texto. Ícone só quando a entrada declara `icon`
|
|
60
53
|
* do catálogo; tooltip só quando `description` acrescenta ao rótulo. O rótulo é sempre texto —
|
|
61
54
|
* cor e ícone nunca comunicam sozinhos. Valor fora do dicionário mostra o código como texto.
|
|
62
55
|
*/
|
|
@@ -64,6 +57,7 @@ export function DictionaryValue({
|
|
|
64
57
|
dict,
|
|
65
58
|
value,
|
|
66
59
|
presentation,
|
|
60
|
+
context,
|
|
67
61
|
variant,
|
|
68
62
|
icon = true,
|
|
69
63
|
icons = iconPickerIcons,
|
|
@@ -102,8 +96,8 @@ export function DictionaryValue({
|
|
|
102
96
|
// dicionário é sempre texto — override não promove código desconhecido a badge.
|
|
103
97
|
const badgeVariant: BadgeProps['variant'] = !resolved.known
|
|
104
98
|
? undefined
|
|
105
|
-
: variant ??
|
|
106
|
-
|
|
99
|
+
: variant ?? resolved.variant ?? undefined
|
|
100
|
+
const badgeContext: BadgeProps['context'] = context ?? resolved.context ?? undefined
|
|
107
101
|
|
|
108
102
|
const content =
|
|
109
103
|
badgeVariant === undefined ? (
|
|
@@ -119,6 +113,7 @@ export function DictionaryValue({
|
|
|
119
113
|
) : (
|
|
120
114
|
<Badge
|
|
121
115
|
data-slot="dictionary-value"
|
|
116
|
+
context={badgeContext}
|
|
122
117
|
variant={badgeVariant}
|
|
123
118
|
className={className}
|
|
124
119
|
{...dataProps}
|
|
@@ -1,38 +1,91 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import type { UiContext } from '../../../core/ui-context.ts'
|
|
3
4
|
import { cn } from '../../lib/cn.ts'
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
export type DotContext = UiContext
|
|
7
|
+
export type DotVariant = 'solid' | 'outline'
|
|
8
|
+
export type LegacyDotVariant =
|
|
9
|
+
| 'default'
|
|
10
|
+
| 'secondary'
|
|
11
|
+
| 'destructive'
|
|
12
|
+
| 'success'
|
|
13
|
+
| 'warning'
|
|
14
|
+
| 'info'
|
|
15
|
+
|
|
16
|
+
interface DotStyleOptions {
|
|
17
|
+
context?: DotContext | null
|
|
18
|
+
variant?: DotVariant | LegacyDotVariant | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const solidClasses: Record<DotContext, string> = {
|
|
22
|
+
neutral: 'bg-context-neutral-emphasis',
|
|
23
|
+
primary: 'bg-context-primary',
|
|
24
|
+
info: 'bg-context-info',
|
|
25
|
+
success: 'bg-context-success',
|
|
26
|
+
warning: 'bg-context-warning',
|
|
27
|
+
danger: 'bg-context-danger',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const outlineClasses: Record<DotContext, string> = {
|
|
31
|
+
neutral: 'border border-context-neutral-border bg-transparent',
|
|
32
|
+
primary: 'border border-context-primary bg-transparent',
|
|
33
|
+
info: 'border border-context-info bg-transparent',
|
|
34
|
+
success: 'border border-context-success bg-transparent',
|
|
35
|
+
warning: 'border border-context-warning bg-transparent',
|
|
36
|
+
danger: 'border border-context-danger bg-transparent',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveDotStyle({ context, variant }: DotStyleOptions = {}): {
|
|
40
|
+
context: DotContext
|
|
41
|
+
variant: DotVariant
|
|
42
|
+
} {
|
|
43
|
+
const legacy =
|
|
44
|
+
variant === 'default' ||
|
|
45
|
+
variant === 'secondary' ||
|
|
46
|
+
variant === 'destructive' ||
|
|
47
|
+
variant === 'success' ||
|
|
48
|
+
variant === 'warning' ||
|
|
49
|
+
variant === 'info'
|
|
50
|
+
if (context != null && legacy) {
|
|
51
|
+
throw new Error('Dot não permite combinar context com uma variante semântica legada.')
|
|
52
|
+
}
|
|
53
|
+
if (variant === 'default') return { context: 'primary', variant: 'solid' }
|
|
54
|
+
if (variant === 'secondary') return { context: 'neutral', variant: 'solid' }
|
|
55
|
+
if (variant === 'destructive') return { context: 'danger', variant: 'solid' }
|
|
56
|
+
if (variant === 'success' || variant === 'warning' || variant === 'info') {
|
|
57
|
+
return { context: variant, variant: 'solid' }
|
|
58
|
+
}
|
|
59
|
+
return { context: context ?? 'neutral', variant: variant ?? 'solid' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function dotVariants(options: DotStyleOptions = {}): string {
|
|
63
|
+
const resolved = resolveDotStyle(options)
|
|
64
|
+
return cn(
|
|
65
|
+
'inline-block size-1.5 shrink-0 rounded-full',
|
|
66
|
+
resolved.variant === 'outline'
|
|
67
|
+
? outlineClasses[resolved.context]
|
|
68
|
+
: solidClasses[resolved.context],
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface DotProps extends React.ComponentProps<'span'>, DotStyleOptions {
|
|
23
73
|
/** Nome acessível quando a cor comunica estado; sem label, o ponto é decorativo. */
|
|
24
74
|
label?: string
|
|
25
75
|
}
|
|
26
76
|
|
|
27
77
|
/** Indicador compacto de estado; use Badge quando o texto precisar permanecer visível. */
|
|
28
|
-
export function Dot({ className, variant, label, ...props }: DotProps): React.ReactElement {
|
|
78
|
+
export function Dot({ className, context, variant, label, ...props }: DotProps): React.ReactElement {
|
|
79
|
+
const resolved = resolveDotStyle({ context, variant })
|
|
29
80
|
return (
|
|
30
81
|
<span
|
|
31
82
|
data-slot="dot"
|
|
83
|
+
data-context={resolved.context}
|
|
84
|
+
data-variant={resolved.variant}
|
|
32
85
|
role={label === undefined ? undefined : 'img'}
|
|
33
86
|
aria-label={label}
|
|
34
87
|
aria-hidden={label === undefined ? true : undefined}
|
|
35
|
-
className={cn(dotVariants(
|
|
88
|
+
className={cn(dotVariants(resolved), className)}
|
|
36
89
|
{...props}
|
|
37
90
|
/>
|
|
38
91
|
)
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { XIcon } from "lucide-react"
|
|
3
|
-
import { Dialog as DrawerPrimitive } from "radix-ui"
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { XIcon } from "lucide-react";
|
|
3
|
+
import { Dialog as DrawerPrimitive } from "radix-ui";
|
|
4
4
|
|
|
5
|
-
import { cn } from
|
|
5
|
+
import { cn } from "../../lib/cn.ts";
|
|
6
6
|
|
|
7
|
-
function Drawer({
|
|
8
|
-
|
|
7
|
+
function Drawer({
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
10
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
function DrawerTrigger({
|
|
12
14
|
...props
|
|
13
15
|
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
14
|
-
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props}
|
|
16
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function DrawerClose({
|
|
18
20
|
...props
|
|
19
21
|
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
20
|
-
return <DrawerPrimitive.Close data-slot="drawer-close" {...props}
|
|
22
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
function DrawerPortal({
|
|
24
26
|
...props
|
|
25
27
|
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
26
|
-
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props}
|
|
28
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function DrawerOverlay({
|
|
@@ -35,11 +37,11 @@ function DrawerOverlay({
|
|
|
35
37
|
data-slot="drawer-overlay"
|
|
36
38
|
className={cn(
|
|
37
39
|
"fixed inset-0 z-50 bg-black/30 backdrop-blur-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
38
|
-
className
|
|
40
|
+
className,
|
|
39
41
|
)}
|
|
40
42
|
{...props}
|
|
41
43
|
/>
|
|
42
|
-
)
|
|
44
|
+
);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function DrawerContent({
|
|
@@ -49,8 +51,8 @@ function DrawerContent({
|
|
|
49
51
|
showCloseButton = true,
|
|
50
52
|
...props
|
|
51
53
|
}: React.ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
52
|
-
side?: "top" | "right" | "bottom" | "left"
|
|
53
|
-
showCloseButton?: boolean
|
|
54
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
55
|
+
showCloseButton?: boolean;
|
|
54
56
|
}) {
|
|
55
57
|
return (
|
|
56
58
|
<DrawerPortal>
|
|
@@ -58,7 +60,7 @@ function DrawerContent({
|
|
|
58
60
|
<DrawerPrimitive.Content
|
|
59
61
|
data-slot="drawer-content"
|
|
60
62
|
className={cn(
|
|
61
|
-
"fixed z-50 flex flex-col
|
|
63
|
+
"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",
|
|
62
64
|
side === "right" &&
|
|
63
65
|
"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 sm:max-w-sm",
|
|
64
66
|
side === "left" &&
|
|
@@ -67,7 +69,7 @@ function DrawerContent({
|
|
|
67
69
|
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
68
70
|
side === "bottom" &&
|
|
69
71
|
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
70
|
-
className
|
|
72
|
+
className,
|
|
71
73
|
)}
|
|
72
74
|
{...props}
|
|
73
75
|
>
|
|
@@ -80,27 +82,40 @@ function DrawerContent({
|
|
|
80
82
|
)}
|
|
81
83
|
</DrawerPrimitive.Content>
|
|
82
84
|
</DrawerPortal>
|
|
83
|
-
)
|
|
85
|
+
);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
87
89
|
return (
|
|
88
90
|
<div
|
|
89
91
|
data-slot="drawer-header"
|
|
90
|
-
className={cn("flex flex-col gap-
|
|
92
|
+
className={cn("flex shrink-0 flex-col gap-2 border-b p-5", className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function DrawerBody({ className, ...props }: React.ComponentProps<"div">) {
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
data-slot="drawer-body"
|
|
102
|
+
className={cn("min-h-0 flex-1 overflow-y-auto p-5", className)}
|
|
91
103
|
{...props}
|
|
92
104
|
/>
|
|
93
|
-
)
|
|
105
|
+
);
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
97
109
|
return (
|
|
98
110
|
<div
|
|
99
111
|
data-slot="drawer-footer"
|
|
100
|
-
className={cn(
|
|
112
|
+
className={cn(
|
|
113
|
+
"mt-auto flex shrink-0 flex-col-reverse gap-2 border-t bg-muted/30 px-5 py-4 sm:flex-row sm:justify-end",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
101
116
|
{...props}
|
|
102
117
|
/>
|
|
103
|
-
)
|
|
118
|
+
);
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
function DrawerTitle({
|
|
@@ -110,10 +125,10 @@ function DrawerTitle({
|
|
|
110
125
|
return (
|
|
111
126
|
<DrawerPrimitive.Title
|
|
112
127
|
data-slot="drawer-title"
|
|
113
|
-
className={cn("font-semibold text-foreground", className)}
|
|
128
|
+
className={cn("text-lg font-semibold leading-none text-foreground", className)}
|
|
114
129
|
{...props}
|
|
115
130
|
/>
|
|
116
|
-
)
|
|
131
|
+
);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
function DrawerDescription({
|
|
@@ -126,7 +141,7 @@ function DrawerDescription({
|
|
|
126
141
|
className={cn("text-sm text-muted-foreground", className)}
|
|
127
142
|
{...props}
|
|
128
143
|
/>
|
|
129
|
-
)
|
|
144
|
+
);
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
export {
|
|
@@ -135,7 +150,8 @@ export {
|
|
|
135
150
|
DrawerClose,
|
|
136
151
|
DrawerContent,
|
|
137
152
|
DrawerHeader,
|
|
153
|
+
DrawerBody,
|
|
138
154
|
DrawerFooter,
|
|
139
155
|
DrawerTitle,
|
|
140
156
|
DrawerDescription,
|
|
141
|
-
}
|
|
157
|
+
};
|