@spree/dashboard-ui 1.0.0-rc.3 → 1.0.0-rc.5
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/package.json +25 -22
- package/src/index.ts +2 -0
- package/src/spree/address-block.tsx +7 -10
- package/src/spree/address-book-row.tsx +52 -52
- package/src/spree/address-select.tsx +105 -0
- package/src/spree/bulk-price-table.tsx +17 -11
- package/src/spree/bulk-variants-table.tsx +12 -5
- package/src/spree/calculator-summary.tsx +9 -14
- package/src/spree/claim-resolve-dialog.tsx +28 -27
- package/src/spree/data-grid/data-grid.tsx +23 -13
- package/src/spree/data-grid/index.ts +1 -1
- package/src/spree/form-actions.tsx +27 -5
- package/src/spree/fulfillment-item-list.tsx +10 -9
- package/src/spree/icons.ts +3 -0
- package/src/spree/post-sale-fields.tsx +22 -20
- package/src/spree/resource-combobox.tsx +20 -4
- package/src/spree/secret-input.tsx +29 -13
- package/src/spree/tax-identifiers-card.tsx +69 -76
- package/src/spree/wizard.tsx +1 -7
- package/src/ui/badge.tsx +21 -1
- package/src/ui/dialog.tsx +11 -2
- package/src/ui/item.tsx +188 -0
- package/src/ui/toast.tsx +99 -45
package/src/ui/dialog.tsx
CHANGED
|
@@ -95,20 +95,29 @@ function DialogContent({
|
|
|
95
95
|
className,
|
|
96
96
|
children,
|
|
97
97
|
showCloseButton = true,
|
|
98
|
+
fullScreen = false,
|
|
98
99
|
...props
|
|
99
100
|
}: React.ComponentProps<typeof DialogPrimitive.Popup> & {
|
|
100
101
|
showCloseButton?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Fills the window minus a small gutter, over an opaque overlay — for
|
|
104
|
+
* wizards and editors that replace the page rather than sit on top of it.
|
|
105
|
+
* A see-through overlay would let the page's own panel edges show around
|
|
106
|
+
* the dialog as a second border.
|
|
107
|
+
*/
|
|
108
|
+
fullScreen?: boolean
|
|
101
109
|
}) {
|
|
102
110
|
return (
|
|
103
111
|
<DialogPortal>
|
|
104
|
-
<DialogOverlay />
|
|
112
|
+
<DialogOverlay className={cn(fullScreen && 'bg-background dark:bg-background')} />
|
|
105
113
|
<DialogPrimitive.Popup
|
|
106
114
|
data-slot="dialog-content"
|
|
107
115
|
className={cn(
|
|
108
116
|
'fixed top-1/2 left-1/2 z-50 flex w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-card text-foreground text-sm shadow-lg duration-100 outline-none sm:max-w-[min(500px,calc(100%-2rem))] data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform,translate,scale]',
|
|
117
|
+
fullScreen && 'inset-3 w-auto max-w-none translate-x-0 translate-y-0 sm:max-w-none',
|
|
109
118
|
className,
|
|
110
119
|
)}
|
|
111
|
-
style={{ maxHeight: '90vh' }}
|
|
120
|
+
style={fullScreen ? undefined : { maxHeight: '90vh' }}
|
|
112
121
|
{...props}
|
|
113
122
|
>
|
|
114
123
|
{children}
|
package/src/ui/item.tsx
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { mergeProps } from '@base-ui/react/merge-props'
|
|
2
|
+
import { useRender } from '@base-ui/react/use-render'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import { cn } from '../lib/utils'
|
|
6
|
+
|
|
7
|
+
import { Separator } from './separator'
|
|
8
|
+
|
|
9
|
+
function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
role="list"
|
|
13
|
+
data-slot="item-group"
|
|
14
|
+
className={cn(
|
|
15
|
+
'group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2',
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
|
|
24
|
+
return (
|
|
25
|
+
<Separator
|
|
26
|
+
data-slot="item-separator"
|
|
27
|
+
orientation="horizontal"
|
|
28
|
+
className={cn('my-2', className)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const itemVariants = cva(
|
|
35
|
+
'group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted',
|
|
36
|
+
{
|
|
37
|
+
variants: {
|
|
38
|
+
variant: {
|
|
39
|
+
default: 'border-transparent',
|
|
40
|
+
outline: 'border-border',
|
|
41
|
+
muted: 'border-transparent bg-muted/50',
|
|
42
|
+
},
|
|
43
|
+
size: {
|
|
44
|
+
default: 'gap-2.5 px-3 py-2.5',
|
|
45
|
+
sm: 'gap-2.5 px-3 py-2.5',
|
|
46
|
+
xs: 'gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
variant: 'default',
|
|
51
|
+
size: 'default',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
function Item({
|
|
57
|
+
className,
|
|
58
|
+
variant = 'default',
|
|
59
|
+
size = 'default',
|
|
60
|
+
render,
|
|
61
|
+
...props
|
|
62
|
+
}: useRender.ComponentProps<'div'> & VariantProps<typeof itemVariants>) {
|
|
63
|
+
return useRender({
|
|
64
|
+
defaultTagName: 'div',
|
|
65
|
+
props: mergeProps<'div'>(
|
|
66
|
+
{
|
|
67
|
+
className: cn(itemVariants({ variant, size, className })),
|
|
68
|
+
},
|
|
69
|
+
props,
|
|
70
|
+
),
|
|
71
|
+
render,
|
|
72
|
+
state: {
|
|
73
|
+
slot: 'item',
|
|
74
|
+
variant,
|
|
75
|
+
size,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const itemMediaVariants = cva(
|
|
81
|
+
'flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none',
|
|
82
|
+
{
|
|
83
|
+
variants: {
|
|
84
|
+
variant: {
|
|
85
|
+
default: 'bg-transparent',
|
|
86
|
+
icon: "[&_svg:not([class*='size-'])]:size-4",
|
|
87
|
+
image:
|
|
88
|
+
'size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
defaultVariants: {
|
|
92
|
+
variant: 'default',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
function ItemMedia({
|
|
98
|
+
className,
|
|
99
|
+
variant = 'default',
|
|
100
|
+
...props
|
|
101
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
data-slot="item-media"
|
|
105
|
+
data-variant={variant}
|
|
106
|
+
className={cn(itemMediaVariants({ variant, className }))}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
data-slot="item-content"
|
|
116
|
+
className={cn(
|
|
117
|
+
'flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none',
|
|
118
|
+
className,
|
|
119
|
+
)}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
data-slot="item-title"
|
|
129
|
+
className={cn(
|
|
130
|
+
'line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4',
|
|
131
|
+
className,
|
|
132
|
+
)}
|
|
133
|
+
{...props}
|
|
134
|
+
/>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
139
|
+
return (
|
|
140
|
+
<p
|
|
141
|
+
data-slot="item-description"
|
|
142
|
+
className={cn(
|
|
143
|
+
'line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
|
|
144
|
+
className,
|
|
145
|
+
)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function ItemActions({ className, ...props }: React.ComponentProps<'div'>) {
|
|
152
|
+
return (
|
|
153
|
+
<div data-slot="item-actions" className={cn('flex items-center gap-2', className)} {...props} />
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
data-slot="item-header"
|
|
161
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
168
|
+
return (
|
|
169
|
+
<div
|
|
170
|
+
data-slot="item-footer"
|
|
171
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
172
|
+
{...props}
|
|
173
|
+
/>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
Item,
|
|
179
|
+
ItemActions,
|
|
180
|
+
ItemContent,
|
|
181
|
+
ItemDescription,
|
|
182
|
+
ItemFooter,
|
|
183
|
+
ItemGroup,
|
|
184
|
+
ItemHeader,
|
|
185
|
+
ItemMedia,
|
|
186
|
+
ItemSeparator,
|
|
187
|
+
ItemTitle,
|
|
188
|
+
}
|
package/src/ui/toast.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
TriangleAlertIcon,
|
|
13
13
|
XIcon,
|
|
14
14
|
} from '../spree/icons'
|
|
15
|
+
import { Button } from './button'
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* App-wide toast manager. Created outside React so a toast can be raised from
|
|
@@ -26,18 +27,89 @@ import {
|
|
|
26
27
|
export const toastManager = ToastPrimitive.createToastManager()
|
|
27
28
|
|
|
28
29
|
const ICONS: Record<string, React.ReactNode> = {
|
|
29
|
-
success: <CircleCheckIcon className="
|
|
30
|
-
error: <OctagonXIcon className="
|
|
31
|
-
warning: <TriangleAlertIcon className="
|
|
32
|
-
info: <InfoIcon className="
|
|
33
|
-
loading: <Loader2Icon className="
|
|
30
|
+
success: <CircleCheckIcon className="text-emerald-600 dark:text-emerald-400" />,
|
|
31
|
+
error: <OctagonXIcon className="text-destructive" />,
|
|
32
|
+
warning: <TriangleAlertIcon className="text-amber-600 dark:text-amber-400" />,
|
|
33
|
+
info: <InfoIcon className="text-muted-foreground" />,
|
|
34
|
+
loading: <Loader2Icon className="animate-spin text-muted-foreground" />,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ToastViewport({ className, ...props }: ToastPrimitive.Viewport.Props) {
|
|
38
|
+
return (
|
|
39
|
+
<ToastPrimitive.Viewport
|
|
40
|
+
data-slot="toast-viewport"
|
|
41
|
+
className={cn(
|
|
42
|
+
// `z-40`, below the overlay layer (`z-50`) rather than above
|
|
43
|
+
// everything: the stack sits bottom-right, which is exactly where a
|
|
44
|
+
// side sheet's footer buttons are, and a toast drifting over Save is
|
|
45
|
+
// both unclickable and a click the merchant did not intend.
|
|
46
|
+
'pointer-events-none fixed inset-x-4 bottom-[max(1rem,env(safe-area-inset-bottom))] z-40 mx-auto w-auto max-w-sm outline-none sm:right-4 sm:left-auto sm:mx-0 sm:w-full',
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Toast({ className, ...props }: ToastPrimitive.Root.Props) {
|
|
55
|
+
return (
|
|
56
|
+
<ToastPrimitive.Root
|
|
57
|
+
data-slot="toast"
|
|
58
|
+
className={cn(
|
|
59
|
+
'group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom rounded-2xl border bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
60
|
+
'[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]',
|
|
61
|
+
'h-(--height) [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]',
|
|
62
|
+
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
63
|
+
'data-expanded:h-(--toast-height) data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(var(--offset-y))]',
|
|
64
|
+
'data-limited:opacity-0 data-starting-style:[transform:translateY(150%)]',
|
|
65
|
+
'[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]',
|
|
66
|
+
'data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]',
|
|
67
|
+
'data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]',
|
|
68
|
+
'data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]',
|
|
69
|
+
'data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]',
|
|
70
|
+
'data-expanded:data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]',
|
|
71
|
+
'data-expanded:data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]',
|
|
72
|
+
'data-expanded:data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]',
|
|
73
|
+
'data-expanded:data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]',
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ToastContent({ className, ...props }: ToastPrimitive.Content.Props) {
|
|
82
|
+
return (
|
|
83
|
+
<ToastPrimitive.Content
|
|
84
|
+
data-slot="toast-content"
|
|
85
|
+
className={cn(
|
|
86
|
+
'flex items-center gap-3 overflow-hidden p-4 transition-opacity duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] data-behind:opacity-0 data-expanded:opacity-100',
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function ToastIcon({ type }: { type: string | undefined }) {
|
|
95
|
+
const icon = type ? ICONS[type] : null
|
|
96
|
+
if (!icon) return null
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<span
|
|
100
|
+
data-slot="toast-icon"
|
|
101
|
+
className="shrink-0 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4"
|
|
102
|
+
>
|
|
103
|
+
{icon}
|
|
104
|
+
</span>
|
|
105
|
+
)
|
|
34
106
|
}
|
|
35
107
|
|
|
36
108
|
function ToastList() {
|
|
37
109
|
const { toasts } = ToastPrimitive.useToastManager()
|
|
38
110
|
|
|
39
111
|
return toasts.map((toast) => (
|
|
40
|
-
<
|
|
112
|
+
<Toast
|
|
41
113
|
key={toast.id}
|
|
42
114
|
toast={toast}
|
|
43
115
|
// Base UI types a toast as `dialog`, which puts every transient
|
|
@@ -45,37 +117,31 @@ function ToastList() {
|
|
|
45
117
|
// lookup, ours or a screen reader user's, matches whatever toast happens
|
|
46
118
|
// to be on screen. `status` is what a passive notification should expose.
|
|
47
119
|
role={toast.type === 'error' ? 'alert' : 'status'}
|
|
48
|
-
// `--toast-index` / `--toast-offset-y` drive the stacked-card effect:
|
|
49
|
-
// toasts behind the front one scale down and sit slightly higher.
|
|
50
|
-
className={cn(
|
|
51
|
-
// Normal flow inside a bottom-anchored column, not absolute
|
|
52
|
-
// positioning: a zero-height viewport gives an absolutely-positioned
|
|
53
|
-
// child nothing to sit on, and it renders below the fold.
|
|
54
|
-
'pointer-events-auto w-full',
|
|
55
|
-
'rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-lg',
|
|
56
|
-
'transition-all duration-200 ease-out',
|
|
57
|
-
'data-[starting-style]:translate-y-2 data-[starting-style]:opacity-0',
|
|
58
|
-
'data-[ending-style]:translate-y-2 data-[ending-style]:opacity-0',
|
|
59
|
-
)}
|
|
60
120
|
>
|
|
61
|
-
<
|
|
62
|
-
{toast.type
|
|
63
|
-
<span className="mt-0.5 shrink-0">{ICONS[toast.type]}</span>
|
|
64
|
-
)}
|
|
121
|
+
<ToastContent>
|
|
122
|
+
<ToastIcon type={toast.type} />
|
|
65
123
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
66
|
-
<ToastPrimitive.Title className="font-medium text-sm
|
|
67
|
-
<ToastPrimitive.Description
|
|
124
|
+
<ToastPrimitive.Title data-slot="toast-title" className="font-medium text-sm" />
|
|
125
|
+
<ToastPrimitive.Description
|
|
126
|
+
data-slot="toast-description"
|
|
127
|
+
className="text-muted-foreground text-sm"
|
|
128
|
+
/>
|
|
68
129
|
</div>
|
|
130
|
+
<ToastPrimitive.Action
|
|
131
|
+
data-slot="toast-action"
|
|
132
|
+
render={<Button variant="outline" size="sm" />}
|
|
133
|
+
className="shrink-0"
|
|
134
|
+
/>
|
|
69
135
|
<ToastPrimitive.Close
|
|
70
|
-
|
|
71
|
-
// rest of the app follows for icon-only controls.
|
|
72
|
-
className="-mt-1 -mr-1 inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
136
|
+
data-slot="toast-close"
|
|
73
137
|
aria-label={i18n.t('admin.actions.close')}
|
|
138
|
+
render={<Button variant="ghost" size="icon-sm" />}
|
|
139
|
+
className="relative shrink-0 text-muted-foreground after:absolute after:-inset-2 after:content-[''] hover:text-foreground"
|
|
74
140
|
>
|
|
75
|
-
<XIcon
|
|
141
|
+
<XIcon aria-hidden="true" />
|
|
76
142
|
</ToastPrimitive.Close>
|
|
77
|
-
</
|
|
78
|
-
</
|
|
143
|
+
</ToastContent>
|
|
144
|
+
</Toast>
|
|
79
145
|
))
|
|
80
146
|
}
|
|
81
147
|
|
|
@@ -90,22 +156,10 @@ export function Toaster({ children }: { children?: React.ReactNode }) {
|
|
|
90
156
|
return (
|
|
91
157
|
<ToastPrimitive.Provider toastManager={toastManager}>
|
|
92
158
|
{children}
|
|
93
|
-
<ToastPrimitive.Portal>
|
|
94
|
-
<
|
|
95
|
-
className={cn(
|
|
96
|
-
// Below the overlay layer (`z-50`) rather than above everything:
|
|
97
|
-
// the stack sits bottom-right, which is exactly where a side
|
|
98
|
-
// sheet's footer buttons are, and a toast drifting over Save is
|
|
99
|
-
// both unclickable and a click the merchant did not intend.
|
|
100
|
-
'fixed right-4 z-40 flex w-[min(22rem,calc(100vw-2rem))] flex-col gap-2',
|
|
101
|
-
// `pointer-events-none` on the column so the empty space above the
|
|
102
|
-
// stack never blocks the page; each toast takes them back.
|
|
103
|
-
'pointer-events-none',
|
|
104
|
-
'bottom-[max(1rem,env(safe-area-inset-bottom))]',
|
|
105
|
-
)}
|
|
106
|
-
>
|
|
159
|
+
<ToastPrimitive.Portal data-slot="toast-portal">
|
|
160
|
+
<ToastViewport>
|
|
107
161
|
<ToastList />
|
|
108
|
-
</
|
|
162
|
+
</ToastViewport>
|
|
109
163
|
</ToastPrimitive.Portal>
|
|
110
164
|
</ToastPrimitive.Provider>
|
|
111
165
|
)
|