@opencode-ai/ui 1.18.9 → 1.18.11
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/dist/v2/components/toast-v2.d.ts +19 -10
- package/package.json +2 -1
- package/src/v2/components/toast-v2.css +132 -122
- package/src/v2/components/toast-v2.tsx +204 -73
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ToastRootProps, ToastCloseButtonProps, ToastTitleProps, ToastDescriptionProps } from "@kobalte/core/toast";
|
|
1
|
+
import { type ToasterProps } from "solid-sonner";
|
|
3
2
|
import type { ComponentProps, JSX } from "solid-js";
|
|
3
|
+
import "./button-v2.css";
|
|
4
4
|
import "./toast-v2.css";
|
|
5
|
-
export interface ToastV2RegionProps extends
|
|
5
|
+
export interface ToastV2RegionProps extends ToasterProps {
|
|
6
6
|
}
|
|
7
7
|
declare function ToastV2Region(props: ToastV2RegionProps): JSX.Element;
|
|
8
|
-
export interface ToastV2RootComponentProps
|
|
9
|
-
|
|
10
|
-
classList?: ComponentProps<"li">["classList"];
|
|
8
|
+
export interface ToastV2RootComponentProps {
|
|
9
|
+
toastId: number;
|
|
11
10
|
children?: JSX.Element;
|
|
12
11
|
}
|
|
13
12
|
declare function ToastV2Root(props: ToastV2RootComponentProps): JSX.Element;
|
|
14
13
|
declare function ToastV2Icon(props: ComponentProps<"div">): JSX.Element;
|
|
15
14
|
declare function ToastV2Content(props: ComponentProps<"div">): JSX.Element;
|
|
16
|
-
declare function ToastV2Title(props:
|
|
17
|
-
declare function ToastV2Description(props:
|
|
15
|
+
declare function ToastV2Title(props: ComponentProps<"div">): JSX.Element;
|
|
16
|
+
declare function ToastV2Description(props: ComponentProps<"div">): JSX.Element;
|
|
18
17
|
declare function ToastV2Actions(props: ComponentProps<"div">): JSX.Element;
|
|
19
|
-
declare function ToastV2CloseButton(props:
|
|
18
|
+
declare function ToastV2CloseButton(props: ComponentProps<"button">): JSX.Element;
|
|
20
19
|
export declare const ToastV2: typeof ToastV2Root & {
|
|
21
20
|
Region: typeof ToastV2Region;
|
|
22
21
|
Icon: typeof ToastV2Icon;
|
|
@@ -26,7 +25,15 @@ export declare const ToastV2: typeof ToastV2Root & {
|
|
|
26
25
|
Actions: typeof ToastV2Actions;
|
|
27
26
|
CloseButton: typeof ToastV2CloseButton;
|
|
28
27
|
};
|
|
29
|
-
export
|
|
28
|
+
export declare const toasterV2: {
|
|
29
|
+
show(render: (props: {
|
|
30
|
+
toastId: number;
|
|
31
|
+
}) => JSX.Element, options?: {
|
|
32
|
+
duration?: number;
|
|
33
|
+
persistent?: boolean;
|
|
34
|
+
}): number;
|
|
35
|
+
dismiss(toastId?: number): string | number | undefined;
|
|
36
|
+
};
|
|
30
37
|
export interface ToastV2Action {
|
|
31
38
|
label: string;
|
|
32
39
|
variant?: "primary" | "secondary";
|
|
@@ -36,6 +43,7 @@ export interface ToastV2Options {
|
|
|
36
43
|
title?: string;
|
|
37
44
|
description?: string;
|
|
38
45
|
icon?: JSX.Element;
|
|
46
|
+
variant?: "default" | "success" | "error" | "loading";
|
|
39
47
|
duration?: number;
|
|
40
48
|
persistent?: boolean;
|
|
41
49
|
actions?: ToastV2Action[];
|
|
@@ -46,3 +54,4 @@ export interface ToastV2PromiseOptions<T, U = unknown> {
|
|
|
46
54
|
success?: (data: T) => JSX.Element;
|
|
47
55
|
error?: (error: U) => JSX.Element;
|
|
48
56
|
}
|
|
57
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode-ai/ui",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
"remend": "1.3.0",
|
|
147
147
|
"shiki": "4.2.0",
|
|
148
148
|
"solid-list": "0.3.0",
|
|
149
|
+
"solid-sonner": "0.3.1",
|
|
149
150
|
"strip-ansi": "7.1.2"
|
|
150
151
|
},
|
|
151
152
|
"peerDependencies": {
|
|
@@ -1,114 +1,133 @@
|
|
|
1
|
-
|
|
2
|
-
position: fixed;
|
|
3
|
-
bottom: 48px;
|
|
4
|
-
right: 32px;
|
|
1
|
+
.toast-v2-region {
|
|
5
2
|
z-index: 1000;
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-direction: column;
|
|
8
|
-
gap: 12px;
|
|
9
3
|
max-width: min(320px, calc(100vw - 64px));
|
|
10
|
-
max-height: none;
|
|
11
|
-
width: 100%;
|
|
12
|
-
overflow: visible;
|
|
13
4
|
pointer-events: none;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
display: flex;
|
|
17
|
-
flex-direction: column;
|
|
18
|
-
gap: 12px;
|
|
19
|
-
list-style: none;
|
|
20
|
-
margin: 0;
|
|
21
|
-
padding: 0;
|
|
22
|
-
max-height: none;
|
|
23
|
-
overflow-y: visible;
|
|
24
|
-
overflow-x: visible;
|
|
25
|
-
scrollbar-width: none;
|
|
26
|
-
|
|
27
|
-
&::-webkit-scrollbar {
|
|
28
|
-
display: none;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
5
|
+
user-select: none;
|
|
6
|
+
-webkit-user-select: none;
|
|
31
7
|
}
|
|
32
8
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
gap: 12px;
|
|
9
|
+
.toast-v2 {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-template-columns: minmax(0, 1fr) 20px;
|
|
12
|
+
column-gap: 12px;
|
|
13
|
+
row-gap: 12px;
|
|
38
14
|
width: 320px;
|
|
15
|
+
max-width: 100%;
|
|
39
16
|
padding: 12px;
|
|
40
17
|
max-height: min(420px, calc(100dvh - 96px));
|
|
41
18
|
overflow: hidden;
|
|
42
19
|
pointer-events: auto;
|
|
43
|
-
|
|
44
|
-
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
outline: none;
|
|
22
|
+
user-select: none;
|
|
23
|
+
-webkit-user-select: none;
|
|
45
24
|
border-radius: 8px;
|
|
46
25
|
color: var(--v2-text-text-base);
|
|
47
26
|
background: var(--v2-background-bg-layer-01);
|
|
48
27
|
box-shadow: var(--v2-elevation-floating);
|
|
28
|
+
transition:
|
|
29
|
+
transform 280ms cubic-bezier(0.2, 0, 0, 1),
|
|
30
|
+
opacity 160ms ease-out,
|
|
31
|
+
height 280ms cubic-bezier(0.2, 0, 0, 1),
|
|
32
|
+
box-shadow 160ms ease-out;
|
|
33
|
+
|
|
34
|
+
&:has(> [data-icon]) {
|
|
35
|
+
grid-template-columns: 16px minmax(0, 1fr) 20px;
|
|
36
|
+
}
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
&:focus-visible {
|
|
39
|
+
box-shadow:
|
|
40
|
+
var(--v2-elevation-floating),
|
|
41
|
+
0 0 0 2px var(--v2-border-border-focus);
|
|
52
42
|
}
|
|
53
43
|
|
|
54
|
-
&[data-
|
|
55
|
-
|
|
44
|
+
&[data-expanded="true"] {
|
|
45
|
+
overflow: visible;
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
[data-
|
|
59
|
-
|
|
60
|
-
align-items: flex-start;
|
|
61
|
-
gap: 12px;
|
|
62
|
-
width: 100%;
|
|
48
|
+
&[data-expanded="false"][data-front="false"] > * {
|
|
49
|
+
opacity: 0;
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
[data-
|
|
66
|
-
|
|
52
|
+
&[data-expanded="false"][data-front="false"] {
|
|
53
|
+
--y: translateY(calc(-1 * var(--toasts-before) * 9.5px)) scale(calc(1 - var(--toasts-before) * 0.05));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
> [data-icon] {
|
|
57
|
+
grid-column: 1;
|
|
58
|
+
grid-row: 1;
|
|
59
|
+
align-self: start;
|
|
67
60
|
display: flex;
|
|
68
61
|
align-items: center;
|
|
69
62
|
justify-content: center;
|
|
70
63
|
width: 16px;
|
|
71
64
|
height: 20px;
|
|
72
|
-
min-width: 16px;
|
|
73
|
-
min-height: 20px;
|
|
74
65
|
color: var(--v2-icon-icon-base);
|
|
75
|
-
|
|
76
|
-
[data-component="icon"] {
|
|
77
|
-
color: var(--v2-icon-icon-base);
|
|
78
|
-
width: 16px;
|
|
79
|
-
height: 16px;
|
|
80
|
-
display: inline-flex;
|
|
81
|
-
align-items: center;
|
|
82
|
-
justify-content: center;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
> * {
|
|
86
|
-
width: 16px;
|
|
87
|
-
height: 16px;
|
|
88
|
-
display: inline-flex;
|
|
89
|
-
align-items: center;
|
|
90
|
-
justify-content: center;
|
|
91
|
-
flex-shrink: 0;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
svg {
|
|
95
|
-
width: 16px;
|
|
96
|
-
height: 16px;
|
|
97
|
-
display: block;
|
|
98
|
-
flex-shrink: 0;
|
|
99
|
-
}
|
|
100
66
|
}
|
|
101
67
|
|
|
102
|
-
[data-
|
|
103
|
-
|
|
68
|
+
> [data-content] {
|
|
69
|
+
grid-column: 1;
|
|
70
|
+
grid-row: 1;
|
|
104
71
|
display: flex;
|
|
105
72
|
flex-direction: column;
|
|
106
73
|
gap: 4px;
|
|
107
|
-
min-height: 0;
|
|
108
74
|
min-width: 0;
|
|
109
75
|
overflow: hidden;
|
|
110
76
|
}
|
|
111
77
|
|
|
78
|
+
&:has(> [data-icon]) > [data-content] {
|
|
79
|
+
grid-column: 2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
> [data-close-button] {
|
|
83
|
+
grid-column: 2;
|
|
84
|
+
grid-row: 1;
|
|
85
|
+
position: static;
|
|
86
|
+
align-self: start;
|
|
87
|
+
width: 20px;
|
|
88
|
+
height: 20px;
|
|
89
|
+
min-width: 20px;
|
|
90
|
+
min-height: 20px;
|
|
91
|
+
padding: 0;
|
|
92
|
+
border: 0;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
transform: none;
|
|
95
|
+
background: transparent;
|
|
96
|
+
color: var(--v2-icon-icon-muted);
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
display: inline-flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&:has(> [data-icon]) > [data-close-button] {
|
|
104
|
+
grid-column: 3;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
> [data-close-button]:hover {
|
|
108
|
+
background: var(--v2-overlay-simple-overlay-hover);
|
|
109
|
+
color: var(--v2-icon-icon-base);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
> [data-close-button]:active {
|
|
113
|
+
background: var(--v2-overlay-simple-overlay-pressed);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
> [data-close-button]:focus-visible {
|
|
117
|
+
outline: 2px solid var(--v2-border-border-focus);
|
|
118
|
+
outline-offset: 2px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
> [data-icon] > *,
|
|
122
|
+
> [data-icon] svg,
|
|
123
|
+
> [data-close-button] svg {
|
|
124
|
+
width: 16px;
|
|
125
|
+
height: 16px;
|
|
126
|
+
display: block;
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[data-title],
|
|
112
131
|
[data-slot="toast-v2-title"] {
|
|
113
132
|
color: var(--v2-text-text-base);
|
|
114
133
|
overflow: hidden;
|
|
@@ -123,6 +142,7 @@
|
|
|
123
142
|
margin: 0;
|
|
124
143
|
}
|
|
125
144
|
|
|
145
|
+
[data-description],
|
|
126
146
|
[data-slot="toast-v2-description"] {
|
|
127
147
|
color: var(--v2-text-text-muted);
|
|
128
148
|
text-wrap-style: pretty;
|
|
@@ -138,10 +158,15 @@
|
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
[data-slot="toast-v2-actions"] {
|
|
161
|
+
grid-column: 1;
|
|
162
|
+
grid-row: 2;
|
|
141
163
|
display: flex;
|
|
142
164
|
flex-wrap: wrap;
|
|
143
165
|
gap: 8px;
|
|
144
|
-
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&:has(> [data-icon]) [data-slot="toast-v2-actions"] {
|
|
169
|
+
grid-column: 2;
|
|
145
170
|
}
|
|
146
171
|
|
|
147
172
|
[data-slot="toast-v2-actions"] [data-component="button-v2"] {
|
|
@@ -154,62 +179,47 @@
|
|
|
154
179
|
font-synthesis: none;
|
|
155
180
|
}
|
|
156
181
|
|
|
157
|
-
[data-slot="toast-v2-
|
|
182
|
+
[data-slot="toast-v2-header"] {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: flex-start;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
width: 100%;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
[data-slot="toast-v2-icon"] {
|
|
158
190
|
flex-shrink: 0;
|
|
159
|
-
|
|
160
|
-
height: 20px;
|
|
161
|
-
min-width: 20px;
|
|
162
|
-
min-height: 20px;
|
|
163
|
-
padding: 0;
|
|
164
|
-
border: 0;
|
|
165
|
-
border-radius: 4px;
|
|
166
|
-
background: transparent;
|
|
167
|
-
color: var(--v2-icon-icon-muted);
|
|
168
|
-
cursor: pointer;
|
|
169
|
-
display: inline-flex;
|
|
191
|
+
display: flex;
|
|
170
192
|
align-items: center;
|
|
171
193
|
justify-content: center;
|
|
194
|
+
width: 16px;
|
|
195
|
+
height: 20px;
|
|
196
|
+
color: var(--v2-icon-icon-base);
|
|
197
|
+
}
|
|
172
198
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
&:focus-visible {
|
|
183
|
-
outline: 2px solid var(--v2-border-border-focus);
|
|
184
|
-
outline-offset: 2px;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
svg {
|
|
188
|
-
width: 16px;
|
|
189
|
-
height: 16px;
|
|
190
|
-
display: block;
|
|
191
|
-
}
|
|
199
|
+
[data-slot="toast-v2-content"] {
|
|
200
|
+
flex: 1;
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
gap: 4px;
|
|
204
|
+
min-width: 0;
|
|
205
|
+
overflow: hidden;
|
|
192
206
|
}
|
|
193
207
|
}
|
|
194
208
|
|
|
195
|
-
@
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
to {
|
|
201
|
-
transform: translateY(0);
|
|
202
|
-
opacity: 1;
|
|
209
|
+
@media (prefers-reduced-motion: reduce) {
|
|
210
|
+
.toast-v2,
|
|
211
|
+
.toast-v2 > * {
|
|
212
|
+
animation: none !important;
|
|
213
|
+
transition: none !important;
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
216
|
|
|
206
|
-
@
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
opacity: 1;
|
|
217
|
+
@media (max-width: 600px) {
|
|
218
|
+
.toast-v2-region {
|
|
219
|
+
max-width: calc(100vw - 32px);
|
|
210
220
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
|
|
222
|
+
.toast-v2 {
|
|
223
|
+
width: 100%;
|
|
214
224
|
}
|
|
215
225
|
}
|
|
@@ -1,40 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ToastRootProps, ToastCloseButtonProps, ToastTitleProps, ToastDescriptionProps } from "@kobalte/core/toast"
|
|
1
|
+
import { Toaster, toast, type ToasterProps } from "solid-sonner"
|
|
3
2
|
import type { ComponentProps, JSX } from "solid-js"
|
|
4
|
-
import {
|
|
3
|
+
import { createContext, onCleanup, onMount, splitProps, useContext } from "solid-js"
|
|
5
4
|
import { Portal } from "solid-js/web"
|
|
6
|
-
import
|
|
5
|
+
import "./button-v2.css"
|
|
7
6
|
import "./toast-v2.css"
|
|
8
7
|
|
|
9
|
-
export interface ToastV2RegionProps extends
|
|
8
|
+
export interface ToastV2RegionProps extends ToasterProps {}
|
|
10
9
|
|
|
11
10
|
function ToastV2Region(props: ToastV2RegionProps) {
|
|
11
|
+
const [local, rest] = splitProps(props, ["class", "className", "style", "toastOptions", "swipeDirections"])
|
|
12
|
+
onMount(() => {
|
|
13
|
+
const sync = () => {
|
|
14
|
+
document.querySelectorAll<HTMLElement>(".toast-v2-region .toast-v2").forEach((element) => {
|
|
15
|
+
const hidden = element.dataset.visible === "false"
|
|
16
|
+
element.inert = hidden
|
|
17
|
+
element.tabIndex = hidden ? -1 : 0
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
let connected = false
|
|
21
|
+
const connect = () => {
|
|
22
|
+
const regions = document.querySelectorAll(".toast-v2-region")
|
|
23
|
+
if (!regions.length) return
|
|
24
|
+
observer.disconnect()
|
|
25
|
+
regions.forEach((region) => {
|
|
26
|
+
observer.observe(region, {
|
|
27
|
+
subtree: true,
|
|
28
|
+
childList: true,
|
|
29
|
+
attributes: true,
|
|
30
|
+
attributeFilter: ["data-visible"],
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
connected = true
|
|
34
|
+
sync()
|
|
35
|
+
}
|
|
36
|
+
const observer = new MutationObserver(() => {
|
|
37
|
+
if (!connected) connect()
|
|
38
|
+
else sync()
|
|
39
|
+
})
|
|
40
|
+
observer.observe(document.body, { subtree: true, childList: true })
|
|
41
|
+
queueMicrotask(connect)
|
|
42
|
+
onCleanup(() => observer.disconnect())
|
|
43
|
+
})
|
|
12
44
|
return (
|
|
13
45
|
<Portal>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
46
|
+
<Toaster
|
|
47
|
+
position="bottom-right"
|
|
48
|
+
offset={{ right: 32, bottom: 48 }}
|
|
49
|
+
mobileOffset={16}
|
|
50
|
+
gap={12}
|
|
51
|
+
duration={5000}
|
|
52
|
+
swipeDirections={["bottom"]}
|
|
53
|
+
className={["toast-v2-region", local.className, local.class].filter(Boolean).join(" ")}
|
|
54
|
+
style={{ "--width": "320px", ...local.style } as JSX.CSSProperties}
|
|
55
|
+
toastOptions={{
|
|
56
|
+
...local.toastOptions,
|
|
57
|
+
unstyled: true,
|
|
58
|
+
closeButton: true,
|
|
59
|
+
closeButtonAriaLabel: local.toastOptions?.closeButtonAriaLabel ?? "Dismiss",
|
|
60
|
+
}}
|
|
61
|
+
{...rest}
|
|
62
|
+
/>
|
|
17
63
|
</Portal>
|
|
18
64
|
)
|
|
19
65
|
}
|
|
20
66
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
67
|
+
const ToastV2Context = createContext<number>()
|
|
68
|
+
|
|
69
|
+
export interface ToastV2RootComponentProps {
|
|
70
|
+
toastId: number
|
|
24
71
|
children?: JSX.Element
|
|
25
72
|
}
|
|
26
73
|
|
|
27
74
|
function ToastV2Root(props: ToastV2RootComponentProps) {
|
|
28
|
-
return
|
|
29
|
-
<Kobalte
|
|
30
|
-
data-component="toast-v2"
|
|
31
|
-
classList={{
|
|
32
|
-
...props.classList,
|
|
33
|
-
[props.class ?? ""]: !!props.class,
|
|
34
|
-
}}
|
|
35
|
-
{...props}
|
|
36
|
-
/>
|
|
37
|
-
)
|
|
75
|
+
return <ToastV2Context.Provider value={props.toastId}>{props.children}</ToastV2Context.Provider>
|
|
38
76
|
}
|
|
39
77
|
|
|
40
78
|
function ToastV2Icon(props: ComponentProps<"div">) {
|
|
@@ -45,26 +83,43 @@ function ToastV2Content(props: ComponentProps<"div">) {
|
|
|
45
83
|
return <div data-slot="toast-v2-content" {...props} />
|
|
46
84
|
}
|
|
47
85
|
|
|
48
|
-
function ToastV2Title(props:
|
|
49
|
-
return <
|
|
86
|
+
function ToastV2Title(props: ComponentProps<"div">) {
|
|
87
|
+
return <div data-slot="toast-v2-title" {...props} />
|
|
50
88
|
}
|
|
51
89
|
|
|
52
|
-
function ToastV2Description(props:
|
|
53
|
-
return <
|
|
90
|
+
function ToastV2Description(props: ComponentProps<"div">) {
|
|
91
|
+
return <div data-slot="toast-v2-description" {...props} />
|
|
54
92
|
}
|
|
55
93
|
|
|
56
94
|
function ToastV2Actions(props: ComponentProps<"div">) {
|
|
57
95
|
return <div data-slot="toast-v2-actions" {...props} />
|
|
58
96
|
}
|
|
59
97
|
|
|
60
|
-
function ToastV2CloseButton(props:
|
|
98
|
+
function ToastV2CloseButton(props: ComponentProps<"button">) {
|
|
99
|
+
const toastId = useContext(ToastV2Context)
|
|
100
|
+
const [local, rest] = splitProps(props, ["children", "onClick"])
|
|
61
101
|
return (
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
data-slot="toast-v2-close-button"
|
|
105
|
+
aria-label="Dismiss"
|
|
106
|
+
{...rest}
|
|
107
|
+
onClick={(event) => {
|
|
108
|
+
if (typeof local.onClick === "function") local.onClick(event)
|
|
109
|
+
if (!event.defaultPrevented && toastId !== undefined) toasterV2.dismiss(toastId)
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{local.children ?? <CloseIcon />}
|
|
113
|
+
</button>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function CloseIcon() {
|
|
118
|
+
return (
|
|
119
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
120
|
+
<path d="M4.25 11.75L11.75 4.25" stroke="currentColor" />
|
|
121
|
+
<path d="M11.75 11.75L4.25 4.25" stroke="currentColor" />
|
|
122
|
+
</svg>
|
|
68
123
|
)
|
|
69
124
|
}
|
|
70
125
|
|
|
@@ -78,7 +133,29 @@ export const ToastV2 = Object.assign(ToastV2Root, {
|
|
|
78
133
|
CloseButton: ToastV2CloseButton,
|
|
79
134
|
})
|
|
80
135
|
|
|
81
|
-
|
|
136
|
+
let toastV2Id = 0
|
|
137
|
+
|
|
138
|
+
export const toasterV2 = {
|
|
139
|
+
show(render: (props: { toastId: number }) => JSX.Element, options?: { duration?: number; persistent?: boolean }) {
|
|
140
|
+
const toastId = --toastV2Id
|
|
141
|
+
toast.custom((id) => render({ toastId: Number(id) }), {
|
|
142
|
+
id: toastId,
|
|
143
|
+
className: "toast-v2",
|
|
144
|
+
duration: options?.persistent ? Number.POSITIVE_INFINITY : options?.duration,
|
|
145
|
+
unstyled: true,
|
|
146
|
+
})
|
|
147
|
+
return toastId
|
|
148
|
+
},
|
|
149
|
+
dismiss(toastId?: number) {
|
|
150
|
+
if (toastId === undefined) {
|
|
151
|
+
activeToastV2ByKey.clear()
|
|
152
|
+
activeToastV2ById.clear()
|
|
153
|
+
} else {
|
|
154
|
+
releaseToastV2(activeToastV2ById.get(toastId))
|
|
155
|
+
}
|
|
156
|
+
return toast.dismiss(toastId)
|
|
157
|
+
},
|
|
158
|
+
}
|
|
82
159
|
|
|
83
160
|
export interface ToastV2Action {
|
|
84
161
|
label: string
|
|
@@ -90,53 +167,107 @@ export interface ToastV2Options {
|
|
|
90
167
|
title?: string
|
|
91
168
|
description?: string
|
|
92
169
|
icon?: JSX.Element
|
|
170
|
+
variant?: "default" | "success" | "error" | "loading"
|
|
93
171
|
duration?: number
|
|
94
172
|
persistent?: boolean
|
|
95
173
|
actions?: ToastV2Action[]
|
|
96
174
|
}
|
|
97
175
|
|
|
176
|
+
interface ActiveToastV2 {
|
|
177
|
+
id: number
|
|
178
|
+
key: string
|
|
179
|
+
options: ToastV2Options
|
|
180
|
+
actions?: JSX.Element
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const activeToastV2ByKey = new Map<string, ActiveToastV2>()
|
|
184
|
+
const activeToastV2ById = new Map<number, ActiveToastV2>()
|
|
185
|
+
|
|
98
186
|
export function showToastV2(options: ToastV2Options | string) {
|
|
99
|
-
const opts = typeof options === "string" ? { description: options } : options
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</Show>
|
|
108
|
-
<ToastV2.Content>
|
|
109
|
-
<Show when={opts.title}>
|
|
110
|
-
<ToastV2.Title>{opts.title}</ToastV2.Title>
|
|
111
|
-
</Show>
|
|
112
|
-
<Show when={opts.description}>
|
|
113
|
-
<ToastV2.Description>{opts.description}</ToastV2.Description>
|
|
114
|
-
</Show>
|
|
115
|
-
</ToastV2.Content>
|
|
116
|
-
<ToastV2.CloseButton />
|
|
117
|
-
</div>
|
|
118
|
-
<Show when={opts.actions?.length}>
|
|
119
|
-
<ToastV2.Actions>
|
|
120
|
-
{opts.actions!.map((action) => (
|
|
121
|
-
<ButtonV2
|
|
122
|
-
variant={action.variant === "secondary" ? "ghost" : "neutral"}
|
|
123
|
-
size="small"
|
|
124
|
-
data-action-variant={action.variant ?? "primary"}
|
|
125
|
-
onClick={() => {
|
|
126
|
-
if (typeof action.onClick === "function") {
|
|
127
|
-
action.onClick()
|
|
128
|
-
}
|
|
129
|
-
toaster.dismiss(props.toastId)
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
{action.label}
|
|
133
|
-
</ButtonV2>
|
|
134
|
-
))}
|
|
135
|
-
</ToastV2.Actions>
|
|
136
|
-
</Show>
|
|
137
|
-
</ToastV2>
|
|
138
|
-
)
|
|
187
|
+
const opts: ToastV2Options = typeof options === "string" ? { description: options } : options
|
|
188
|
+
const key = JSON.stringify({
|
|
189
|
+
title: opts.title,
|
|
190
|
+
description: opts.description,
|
|
191
|
+
variant: opts.variant,
|
|
192
|
+
duration: opts.duration,
|
|
193
|
+
persistent: opts.persistent,
|
|
194
|
+
actions: opts.actions?.map((action) => [action.label, action.variant]),
|
|
139
195
|
})
|
|
196
|
+
const active = activeToastV2ByKey.get(key)
|
|
197
|
+
const toasts = toast.getToasts()
|
|
198
|
+
|
|
199
|
+
if (active && toasts.at(-1)?.id === active.id) {
|
|
200
|
+
active.options = opts
|
|
201
|
+
publishToastV2(active)
|
|
202
|
+
pulseToastV2(active.id)
|
|
203
|
+
return active.id
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (active && toasts.some((item) => item.id === active.id)) toasterV2.dismiss(active.id)
|
|
207
|
+
releaseToastV2(active)
|
|
208
|
+
|
|
209
|
+
const entry: ActiveToastV2 = { id: --toastV2Id, key, options: opts }
|
|
210
|
+
entry.actions = createToastV2Actions(entry)
|
|
211
|
+
activeToastV2ByKey.set(key, entry)
|
|
212
|
+
activeToastV2ById.set(entry.id, entry)
|
|
213
|
+
publishToastV2(entry)
|
|
214
|
+
return entry.id
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function publishToastV2(entry: ActiveToastV2) {
|
|
218
|
+
const release = () => releaseToastV2(entry)
|
|
219
|
+
toast(entry.options.title ?? "", {
|
|
220
|
+
id: entry.id,
|
|
221
|
+
description: entry.options.description,
|
|
222
|
+
icon: entry.options.icon,
|
|
223
|
+
action: entry.actions,
|
|
224
|
+
closeButton: true,
|
|
225
|
+
duration: entry.options.persistent ? Number.POSITIVE_INFINITY : entry.options.duration,
|
|
226
|
+
className: `toast-v2 toast-v2--${entry.options.variant ?? "default"}`,
|
|
227
|
+
testId: `toast-v2-${entry.id}`,
|
|
228
|
+
unstyled: true,
|
|
229
|
+
onDismiss: release,
|
|
230
|
+
onAutoClose: release,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createToastV2Actions(entry: ActiveToastV2) {
|
|
235
|
+
if (!entry.options.actions?.length) return undefined
|
|
236
|
+
return (
|
|
237
|
+
<ToastV2.Actions>
|
|
238
|
+
{entry.options.actions.map((action, index) => (
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
data-component="button-v2"
|
|
242
|
+
data-variant={action.variant === "secondary" ? "ghost" : "neutral"}
|
|
243
|
+
data-size="small"
|
|
244
|
+
data-action-variant={action.variant ?? "primary"}
|
|
245
|
+
onClick={() => {
|
|
246
|
+
const onClick = entry.options.actions?.[index]?.onClick
|
|
247
|
+
if (typeof onClick === "function") onClick()
|
|
248
|
+
toasterV2.dismiss(entry.id)
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
{action.label}
|
|
252
|
+
</button>
|
|
253
|
+
))}
|
|
254
|
+
</ToastV2.Actions>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function pulseToastV2(toastId: number) {
|
|
259
|
+
if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return
|
|
260
|
+
requestAnimationFrame(() => {
|
|
261
|
+
const element = document.querySelector<HTMLElement>(`[data-testid="toast-v2-${toastId}"]`)
|
|
262
|
+
if (!element || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
|
|
263
|
+
element.animate([{ scale: 1 }, { scale: 1.025 }, { scale: 1 }], { duration: 160, easing: "ease-out" })
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function releaseToastV2(entry: ActiveToastV2 | undefined) {
|
|
268
|
+
if (!entry) return
|
|
269
|
+
if (activeToastV2ByKey.get(entry.key) === entry) activeToastV2ByKey.delete(entry.key)
|
|
270
|
+
if (activeToastV2ById.get(entry.id) === entry) activeToastV2ById.delete(entry.id)
|
|
140
271
|
}
|
|
141
272
|
|
|
142
273
|
export interface ToastV2PromiseOptions<T, U = unknown> {
|