@open-mercato/ui 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/backend/DataTable.js +28 -62
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
- package/dist/primitives/activity-feed.js +196 -0
- package/dist/primitives/activity-feed.js.map +7 -0
- package/dist/primitives/avatar.js +140 -14
- package/dist/primitives/avatar.js.map +3 -3
- package/dist/primitives/badge.js +94 -5
- package/dist/primitives/badge.js.map +2 -2
- package/dist/primitives/button-group.js +52 -0
- package/dist/primitives/button-group.js.map +7 -0
- package/dist/primitives/color-picker.js +520 -0
- package/dist/primitives/color-picker.js.map +7 -0
- package/dist/primitives/command-menu.js +355 -0
- package/dist/primitives/command-menu.js.map +7 -0
- package/dist/primitives/dialog.js +210 -35
- package/dist/primitives/dialog.js.map +2 -2
- package/dist/primitives/drawer.js +219 -0
- package/dist/primitives/drawer.js.map +7 -0
- package/dist/primitives/notification-feed.js +213 -0
- package/dist/primitives/notification-feed.js.map +7 -0
- package/dist/primitives/notification.js +23 -2
- package/dist/primitives/notification.js.map +2 -2
- package/dist/primitives/pagination.js +245 -0
- package/dist/primitives/pagination.js.map +7 -0
- package/dist/primitives/progress.js +178 -5
- package/dist/primitives/progress.js.map +2 -2
- package/dist/primitives/rating.js +174 -0
- package/dist/primitives/rating.js.map +7 -0
- package/dist/primitives/scroll-area.js +82 -0
- package/dist/primitives/scroll-area.js.map +7 -0
- package/dist/primitives/segmented-control.js +101 -0
- package/dist/primitives/segmented-control.js.map +7 -0
- package/dist/primitives/separator.js +103 -4
- package/dist/primitives/separator.js.map +2 -2
- package/dist/primitives/slider.js +115 -0
- package/dist/primitives/slider.js.map +7 -0
- package/dist/primitives/step-indicator.js +187 -0
- package/dist/primitives/step-indicator.js.map +7 -0
- package/dist/primitives/table.js +102 -10
- package/dist/primitives/table.js.map +2 -2
- package/dist/primitives/tabs.js +122 -14
- package/dist/primitives/tabs.js.map +2 -2
- package/dist/primitives/time-picker.js +9 -11
- package/dist/primitives/time-picker.js.map +2 -2
- package/package.json +8 -4
- package/src/backend/DataTable.tsx +43 -60
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/primitives/__tests__/activity-feed.test.tsx +216 -0
- package/src/primitives/__tests__/avatar.test.tsx +131 -0
- package/src/primitives/__tests__/badge.test.tsx +189 -0
- package/src/primitives/__tests__/button-group.test.tsx +117 -0
- package/src/primitives/__tests__/color-picker.test.tsx +589 -0
- package/src/primitives/__tests__/command-menu.test.tsx +231 -0
- package/src/primitives/__tests__/dialog.test.tsx +290 -0
- package/src/primitives/__tests__/drawer.test.tsx +264 -0
- package/src/primitives/__tests__/notification-feed.test.tsx +303 -0
- package/src/primitives/__tests__/notification.test.tsx +102 -2
- package/src/primitives/__tests__/pagination.test.tsx +268 -0
- package/src/primitives/__tests__/progress.test.tsx +222 -0
- package/src/primitives/__tests__/rating.test.tsx +164 -0
- package/src/primitives/__tests__/scroll-area.test.tsx +109 -0
- package/src/primitives/__tests__/segmented-control.test.tsx +129 -0
- package/src/primitives/__tests__/separator.test.tsx +143 -0
- package/src/primitives/__tests__/slider.test.tsx +86 -0
- package/src/primitives/__tests__/step-indicator.test.tsx +205 -0
- package/src/primitives/__tests__/table.test.tsx +199 -0
- package/src/primitives/__tests__/tabs.test.tsx +139 -0
- package/src/primitives/activity-feed.tsx +326 -0
- package/src/primitives/avatar.tsx +225 -22
- package/src/primitives/badge.tsx +141 -6
- package/src/primitives/button-group.tsx +100 -0
- package/src/primitives/color-picker.tsx +693 -0
- package/src/primitives/command-menu.tsx +503 -0
- package/src/primitives/dialog.tsx +315 -51
- package/src/primitives/drawer.tsx +338 -0
- package/src/primitives/notification-feed.tsx +358 -0
- package/src/primitives/notification.tsx +42 -2
- package/src/primitives/pagination.tsx +376 -0
- package/src/primitives/progress.tsx +267 -6
- package/src/primitives/rating.tsx +245 -0
- package/src/primitives/scroll-area.tsx +169 -0
- package/src/primitives/segmented-control.tsx +162 -0
- package/src/primitives/separator.tsx +163 -3
- package/src/primitives/slider.tsx +160 -0
- package/src/primitives/step-indicator.tsx +287 -0
- package/src/primitives/table.tsx +142 -11
- package/src/primitives/tabs.tsx +174 -12
- package/src/primitives/time-picker.tsx +9 -11
|
@@ -3,9 +3,44 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
5
5
|
import { X } from 'lucide-react'
|
|
6
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
7
|
+
|
|
6
8
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
9
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Modal dialog primitive. Phase B.7 rewrite per Figma `Modals` page
|
|
13
|
+
* (`466:4630`) — `Modal Header [1.1]` (`466:4778`, title + optional
|
|
14
|
+
* leading icon badge + optional description), `Modal Footer [1.1]`
|
|
15
|
+
* (`472:566`, right-aligned buttons or 50/50 stretched layout),
|
|
16
|
+
* `Status Modals [1.1]` (`480:1372`, status-icon hero variant).
|
|
17
|
+
*
|
|
18
|
+
* Backward compatibility (40 import sites — every dialog in the
|
|
19
|
+
* product). All existing exports (`Dialog`, `DialogTrigger`,
|
|
20
|
+
* `DialogPortal`, `DialogOverlay`, `DialogClose`, `DialogContent`,
|
|
21
|
+
* `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`)
|
|
22
|
+
* stay callable verbatim. Default `size="default"` reproduces the
|
|
23
|
+
* original `sm:max-w-lg` width; default header has no leading badge;
|
|
24
|
+
* default footer is right-aligned (current look).
|
|
25
|
+
*
|
|
26
|
+
* New (additive):
|
|
27
|
+
* DialogContent:
|
|
28
|
+
* - `size: 'sm' | 'default' | 'lg' | 'xl'` — max-width breakpoints
|
|
29
|
+
* - `dismissible: boolean` (default `true`) — render the auto X
|
|
30
|
+
* top-right close button
|
|
31
|
+
*
|
|
32
|
+
* DialogHeader:
|
|
33
|
+
* - `leading?: ReactNode` — `size-10 rounded-full border` icon
|
|
34
|
+
* badge to the left of the title block (matches Figma `Modal
|
|
35
|
+
* Header [1.1]` icon-prefixed variants — same chrome as the
|
|
36
|
+
* Drawer header leading slot)
|
|
37
|
+
*
|
|
38
|
+
* DialogFooter:
|
|
39
|
+
* - `layout: 'default' | 'equal'` (default `default`) — `equal`
|
|
40
|
+
* stretches children flex-1 for 50/50 confirmation footers
|
|
41
|
+
* (Figma `Modal Footer [1.1]` variant 1)
|
|
42
|
+
*/
|
|
43
|
+
|
|
9
44
|
const Dialog = DialogPrimitive.Root
|
|
10
45
|
|
|
11
46
|
const DialogTrigger = DialogPrimitive.Trigger
|
|
@@ -24,80 +59,307 @@ const DialogOverlay = React.forwardRef<
|
|
|
24
59
|
>(({ className, elevated, ...props }, ref) => (
|
|
25
60
|
<DialogPrimitive.Overlay
|
|
26
61
|
ref={ref}
|
|
62
|
+
data-slot="dialog-overlay"
|
|
27
63
|
className={cn(
|
|
28
64
|
'fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
29
65
|
elevated ? 'z-modal-elevated' : 'z-modal',
|
|
30
|
-
className
|
|
66
|
+
className,
|
|
31
67
|
)}
|
|
32
68
|
{...props}
|
|
33
69
|
/>
|
|
34
70
|
))
|
|
35
71
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
36
72
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
73
|
+
const dialogContentVariants = cva(
|
|
74
|
+
'fixed inset-x-0 bottom-0 flex max-h-[90vh] w-full translate-x-0 translate-y-0 flex-col gap-4 overflow-y-auto rounded-t-xl border-t bg-card p-6 shadow-lg sm:inset-auto sm:left-1/2 sm:top-1/2 sm:min-h-0 sm:h-auto sm:w-full sm:max-h-[90vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:border focus-visible:outline-none data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
75
|
+
{
|
|
76
|
+
variants: {
|
|
77
|
+
size: {
|
|
78
|
+
sm: 'sm:max-w-sm',
|
|
79
|
+
default: 'sm:max-w-lg',
|
|
80
|
+
lg: 'sm:max-w-2xl',
|
|
81
|
+
xl: 'sm:max-w-4xl',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
size: 'default',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
export type DialogContentProps = React.ComponentPropsWithoutRef<
|
|
91
|
+
typeof DialogPrimitive.Content
|
|
92
|
+
> &
|
|
93
|
+
VariantProps<typeof dialogContentVariants> & {
|
|
40
94
|
/** Render above popovers (z-modal-elevated, 55) instead of the default z-modal (40).
|
|
41
95
|
* Set on dialogs that open from inside another popover (e.g. the SaveFilterDialog
|
|
42
96
|
* inside the AdvancedFilterPanel popover) so they aren't hidden behind the popover. */
|
|
43
97
|
elevated?: boolean
|
|
98
|
+
/** Render the auto X top-right close button. @default true */
|
|
99
|
+
dismissible?: boolean
|
|
100
|
+
/** Aria label for the auto X close button. Defaults to the
|
|
101
|
+
* `ui.dialog.close.ariaLabel` translation (`"Close"`). */
|
|
102
|
+
closeAriaLabel?: string
|
|
44
103
|
}
|
|
45
|
-
>(({ className, children, elevated, ...props }, ref) => {
|
|
46
|
-
const t = useT()
|
|
47
|
-
|
|
48
|
-
React.useEffect(() => {
|
|
49
|
-
return () => {
|
|
50
|
-
if (typeof window === 'undefined') return
|
|
51
|
-
window.setTimeout(() => {
|
|
52
|
-
if (document.querySelector('[data-dialog-content][data-state="open"]')) return
|
|
53
|
-
document.body.style.removeProperty('overflow')
|
|
54
|
-
document.body.style.removeProperty('pointer-events')
|
|
55
|
-
}, 0)
|
|
56
|
-
}
|
|
57
|
-
}, [])
|
|
58
104
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
const DialogContent = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
107
|
+
DialogContentProps
|
|
108
|
+
>(
|
|
109
|
+
(
|
|
110
|
+
{
|
|
111
|
+
className,
|
|
112
|
+
children,
|
|
113
|
+
elevated,
|
|
114
|
+
size,
|
|
115
|
+
dismissible = true,
|
|
116
|
+
closeAriaLabel,
|
|
117
|
+
...props
|
|
118
|
+
},
|
|
119
|
+
ref,
|
|
120
|
+
) => {
|
|
121
|
+
const t = useT()
|
|
122
|
+
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
return () => {
|
|
125
|
+
if (typeof window === 'undefined') return
|
|
126
|
+
window.setTimeout(() => {
|
|
127
|
+
if (document.querySelector('[data-dialog-content][data-state="open"]')) return
|
|
128
|
+
document.body.style.removeProperty('overflow')
|
|
129
|
+
document.body.style.removeProperty('pointer-events')
|
|
130
|
+
}, 0)
|
|
131
|
+
}
|
|
132
|
+
}, [])
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<DialogPortal>
|
|
136
|
+
<DialogOverlay elevated={elevated} />
|
|
137
|
+
<DialogPrimitive.Content
|
|
138
|
+
ref={ref}
|
|
139
|
+
data-dialog-content=""
|
|
140
|
+
data-slot="dialog-content"
|
|
141
|
+
data-size={size ?? 'default'}
|
|
142
|
+
className={cn(
|
|
143
|
+
dialogContentVariants({ size }),
|
|
144
|
+
elevated ? 'z-modal-elevated' : 'z-modal',
|
|
145
|
+
className,
|
|
146
|
+
)}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
{dismissible ? (
|
|
150
|
+
<DialogClose
|
|
151
|
+
data-dialog-close=""
|
|
152
|
+
data-slot="dialog-close-button"
|
|
153
|
+
className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
154
|
+
aria-label={closeAriaLabel ?? t('ui.dialog.close.ariaLabel', 'Close')}
|
|
155
|
+
>
|
|
156
|
+
<X className="h-4 w-4" />
|
|
157
|
+
</DialogClose>
|
|
158
|
+
) : null}
|
|
159
|
+
{children}
|
|
160
|
+
</DialogPrimitive.Content>
|
|
161
|
+
</DialogPortal>
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
166
|
+
|
|
167
|
+
export type DialogHeaderTone =
|
|
168
|
+
| 'default'
|
|
169
|
+
| 'accent'
|
|
170
|
+
| 'success'
|
|
171
|
+
| 'warning'
|
|
172
|
+
| 'error'
|
|
173
|
+
| 'info'
|
|
174
|
+
|
|
175
|
+
// Soft-tinted status leading badges per canonical Figma `Status
|
|
176
|
+
// Modals [1.1]` (`480:1372`). Each status tone uses the soft tint
|
|
177
|
+
// background paired with a saturated colored icon (red `!` on light
|
|
178
|
+
// pink, amber triangle on light amber, green check on light green,
|
|
179
|
+
// indigo `i` on light indigo). The bordered `default` tone keeps
|
|
180
|
+
// the white-bg + border-input shell for generic settings icons per
|
|
181
|
+
// `Modal Header [1.1]` (`466:4778`).
|
|
182
|
+
const DIALOG_HEADER_TONE_CLASS: Record<DialogHeaderTone, string> = {
|
|
183
|
+
default: 'border border-input bg-background text-muted-foreground',
|
|
184
|
+
accent: 'bg-accent-indigo/10 text-accent-indigo',
|
|
185
|
+
success: 'bg-status-success-bg text-status-success-icon',
|
|
186
|
+
warning: 'bg-status-warning-bg text-status-warning-icon',
|
|
187
|
+
error: 'bg-status-error-bg text-status-error-icon',
|
|
188
|
+
info: 'bg-status-info-bg text-status-info-icon',
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type DialogHeaderProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
192
|
+
/** Optional leading icon — rendered inside a size-10 rounded-full
|
|
193
|
+
* badge to the left of the title block. Matches Figma `Modal
|
|
194
|
+
* Header [1.1]` icon-prefixed variants and `Status Modals [1.1]`
|
|
195
|
+
* status-tinted badges. */
|
|
196
|
+
leading?: React.ReactNode
|
|
197
|
+
/** Visual tone for the leading badge. `default` keeps the
|
|
198
|
+
* bordered white badge (generic settings icons); status tones
|
|
199
|
+
* (`success`/`warning`/`error`/`info`) use the matching
|
|
200
|
+
* `bg-status-*-bg text-status-*-icon` tint per Figma `Status
|
|
201
|
+
* Modals [1.1]`. Use `error` / `warning` to signal destructive
|
|
202
|
+
* flows via the badge instead of a red CTA button.
|
|
203
|
+
* @default 'default' */
|
|
204
|
+
leadingTone?: DialogHeaderTone
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const DialogHeader = ({
|
|
208
|
+
className,
|
|
209
|
+
leading,
|
|
210
|
+
leadingTone = 'default',
|
|
211
|
+
children,
|
|
212
|
+
...props
|
|
213
|
+
}: DialogHeaderProps) => (
|
|
214
|
+
<div
|
|
215
|
+
data-slot="dialog-header"
|
|
216
|
+
className={cn(
|
|
217
|
+
leading ? 'flex items-start gap-3 text-left' : 'flex flex-col space-y-1.5 text-center sm:text-left',
|
|
218
|
+
className,
|
|
219
|
+
)}
|
|
220
|
+
{...props}
|
|
221
|
+
>
|
|
222
|
+
{leading ? (
|
|
223
|
+
<span
|
|
224
|
+
data-slot="dialog-header-leading"
|
|
225
|
+
data-tone={leadingTone}
|
|
226
|
+
aria-hidden="true"
|
|
227
|
+
className={cn(
|
|
228
|
+
'inline-flex size-10 shrink-0 items-center justify-center rounded-full',
|
|
229
|
+
DIALOG_HEADER_TONE_CLASS[leadingTone],
|
|
230
|
+
)}
|
|
231
|
+
>
|
|
232
|
+
{leading}
|
|
233
|
+
</span>
|
|
234
|
+
) : null}
|
|
235
|
+
{leading ? (
|
|
236
|
+
<div
|
|
237
|
+
data-slot="dialog-header-text"
|
|
238
|
+
className="flex min-w-0 flex-1 flex-col gap-1 text-left"
|
|
239
|
+
>
|
|
240
|
+
{children}
|
|
241
|
+
</div>
|
|
242
|
+
) : (
|
|
243
|
+
children
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
DialogHeader.displayName = 'DialogHeader'
|
|
248
|
+
|
|
249
|
+
export type DialogFooterProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
250
|
+
/** Footer layout. `default` reproduces the original
|
|
251
|
+
* `flex flex-col-reverse gap-2 sm:flex-row sm:justify-end` look.
|
|
252
|
+
* `equal` stretches children flex-1 for 50/50 confirmation footers
|
|
253
|
+
* per Figma `Modal Footer [1.1]` variant 1. */
|
|
254
|
+
layout?: 'default' | 'equal'
|
|
255
|
+
/** Render the canonical `border-t` separator above the button row.
|
|
256
|
+
* Per Figma `Modal Footer [1.1]` every variant ships the rule.
|
|
257
|
+
* @default true */
|
|
258
|
+
bordered?: boolean
|
|
259
|
+
/** Optional left-side slot — typically a "Don't show it again"
|
|
260
|
+
* CheckboxField, a "Remember me" Switch, a left link button, or a
|
|
261
|
+
* step-indicator. Per Figma `Modal Footer [1.1]` variants 2–6.
|
|
262
|
+
* When provided, children stay right-aligned and the leading slot
|
|
263
|
+
* anchors left. Mutually exclusive with `layout="equal"` (the
|
|
264
|
+
* 50/50 split is a confirmation-flow shape that doesn't combine
|
|
265
|
+
* with the leading-content footers per Figma). */
|
|
266
|
+
leading?: React.ReactNode
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const DialogFooter = ({
|
|
270
|
+
className,
|
|
271
|
+
layout = 'default',
|
|
272
|
+
bordered = true,
|
|
273
|
+
leading,
|
|
274
|
+
children,
|
|
275
|
+
...props
|
|
276
|
+
}: DialogFooterProps) => {
|
|
277
|
+
if (layout === 'equal') {
|
|
278
|
+
return (
|
|
279
|
+
<div
|
|
280
|
+
data-slot="dialog-footer"
|
|
281
|
+
data-layout="equal"
|
|
282
|
+
data-bordered={bordered ? 'true' : undefined}
|
|
70
283
|
className={cn(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'
|
|
284
|
+
// Per Figma: thin border-t separator above the footer in
|
|
285
|
+
// every Modal Footer [1.1] variant. Rule is inset inside
|
|
286
|
+
// the DialogContent's `p-6` padding — true edge-to-edge
|
|
287
|
+
// ran the footer's layout box past the parent's
|
|
288
|
+
// overflow-y-auto scrollbar gutter, pushing the scrollbar
|
|
289
|
+
// outside the rounded card (caught in QuickDeal modal
|
|
290
|
+
// testing). Edge-to-edge polish deferred to a v6 dialog
|
|
291
|
+
// restructure that splits scrolling body from sticky
|
|
292
|
+
// header/footer.
|
|
293
|
+
bordered ? 'border-t border-input pt-4' : '',
|
|
294
|
+
'flex flex-row gap-2 [&>*]:flex-1',
|
|
75
295
|
className,
|
|
76
296
|
)}
|
|
77
297
|
{...props}
|
|
78
298
|
>
|
|
79
|
-
<DialogClose
|
|
80
|
-
data-dialog-close=""
|
|
81
|
-
className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
82
|
-
aria-label={t('ui.dialog.close.ariaLabel', 'Close')}
|
|
83
|
-
>
|
|
84
|
-
<X className="h-4 w-4" />
|
|
85
|
-
</DialogClose>
|
|
86
299
|
{children}
|
|
87
|
-
</
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})
|
|
91
|
-
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
92
303
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
304
|
+
if (leading) {
|
|
305
|
+
return (
|
|
306
|
+
<div
|
|
307
|
+
data-slot="dialog-footer"
|
|
308
|
+
data-layout="default"
|
|
309
|
+
data-bordered={bordered ? 'true' : undefined}
|
|
310
|
+
className={cn(
|
|
311
|
+
// Per Figma: thin border-t separator above the footer in
|
|
312
|
+
// every Modal Footer [1.1] variant. Rule is inset inside
|
|
313
|
+
// the DialogContent's `p-6` padding — true edge-to-edge
|
|
314
|
+
// ran the footer's layout box past the parent's
|
|
315
|
+
// overflow-y-auto scrollbar gutter, pushing the scrollbar
|
|
316
|
+
// outside the rounded card (caught in QuickDeal modal
|
|
317
|
+
// testing). Edge-to-edge polish deferred to a v6 dialog
|
|
318
|
+
// restructure that splits scrolling body from sticky
|
|
319
|
+
// header/footer.
|
|
320
|
+
bordered ? 'border-t border-input pt-4' : '',
|
|
321
|
+
'flex flex-col gap-3 sm:flex-row sm:items-center',
|
|
322
|
+
className,
|
|
323
|
+
)}
|
|
324
|
+
{...props}
|
|
325
|
+
>
|
|
326
|
+
<div
|
|
327
|
+
data-slot="dialog-footer-leading"
|
|
328
|
+
className="inline-flex items-center gap-2 sm:mr-auto"
|
|
329
|
+
>
|
|
330
|
+
{leading}
|
|
331
|
+
</div>
|
|
332
|
+
<div
|
|
333
|
+
data-slot="dialog-footer-trailing"
|
|
334
|
+
className="flex flex-col-reverse gap-2 sm:flex-row sm:items-center"
|
|
335
|
+
>
|
|
336
|
+
{children}
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
97
341
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
342
|
+
return (
|
|
343
|
+
<div
|
|
344
|
+
data-slot="dialog-footer"
|
|
345
|
+
data-layout="default"
|
|
346
|
+
data-bordered={bordered ? 'true' : undefined}
|
|
347
|
+
className={cn(
|
|
348
|
+
// Per Figma: the border-t separator runs edge-to-edge across
|
|
349
|
+
// the modal card (terminates at the rounded corners), NOT
|
|
350
|
+
// inset inside the DialogContent's `p-6` padding. We escape
|
|
351
|
+
// the parent's horizontal padding with `-mx-6` and re-apply
|
|
352
|
+
// `px-6` so the button row keeps its inset position.
|
|
353
|
+
bordered ? '-mx-6 border-t border-input px-6 pt-4' : '',
|
|
354
|
+
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
|
355
|
+
className,
|
|
356
|
+
)}
|
|
357
|
+
{...props}
|
|
358
|
+
>
|
|
359
|
+
{children}
|
|
360
|
+
</div>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
101
363
|
DialogFooter.displayName = 'DialogFooter'
|
|
102
364
|
|
|
103
365
|
const DialogTitle = React.forwardRef<
|
|
@@ -106,6 +368,7 @@ const DialogTitle = React.forwardRef<
|
|
|
106
368
|
>(({ className, ...props }, ref) => (
|
|
107
369
|
<DialogPrimitive.Title
|
|
108
370
|
ref={ref}
|
|
371
|
+
data-slot="dialog-title"
|
|
109
372
|
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
110
373
|
{...props}
|
|
111
374
|
/>
|
|
@@ -118,6 +381,7 @@ const DialogDescription = React.forwardRef<
|
|
|
118
381
|
>(({ className, ...props }, ref) => (
|
|
119
382
|
<DialogPrimitive.Description
|
|
120
383
|
ref={ref}
|
|
384
|
+
data-slot="dialog-description"
|
|
121
385
|
className={cn('text-sm text-muted-foreground', className)}
|
|
122
386
|
{...props}
|
|
123
387
|
/>
|