@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.
@@ -1,22 +1,21 @@
1
- import { Toast as Kobalte, toaster } from "@kobalte/core/toast";
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 ComponentProps<typeof Kobalte.Region> {
5
+ export interface ToastV2RegionProps extends ToasterProps {
6
6
  }
7
7
  declare function ToastV2Region(props: ToastV2RegionProps): JSX.Element;
8
- export interface ToastV2RootComponentProps extends ToastRootProps {
9
- class?: string;
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: ToastTitleProps & ComponentProps<"div">): JSX.Element;
17
- declare function ToastV2Description(props: ToastDescriptionProps & ComponentProps<"div">): JSX.Element;
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: ToastCloseButtonProps & ComponentProps<"button">): JSX.Element;
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 { toaster as toasterV2 };
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.9",
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
- [data-component="toast-v2-region"] {
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
- [data-slot="toast-v2-list"] {
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
- [data-component="toast-v2"] {
34
- position: relative;
35
- display: flex;
36
- flex-direction: column;
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
- transition: transform 140ms ease-out;
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
- &[data-opened] {
51
- animation: toastV2PopIn 140ms ease-out;
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-closed] {
55
- animation: toastV2PopOut 100ms ease-in forwards;
44
+ &[data-expanded="true"] {
45
+ overflow: visible;
56
46
  }
57
47
 
58
- [data-slot="toast-v2-header"] {
59
- display: flex;
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-slot="toast-v2-icon"] {
66
- flex-shrink: 0;
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-slot="toast-v2-content"] {
103
- flex: 1;
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
- padding-left: 28px;
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-close-button"] {
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
- width: 20px;
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
- &:hover {
174
- background: var(--v2-overlay-simple-overlay-hover);
175
- color: var(--v2-icon-icon-base);
176
- }
177
-
178
- &:active {
179
- background: var(--v2-overlay-simple-overlay-pressed);
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
- @keyframes toastV2PopIn {
196
- from {
197
- opacity: 0.8;
198
- transform: translateY(6px);
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
- @keyframes toastV2PopOut {
207
- from {
208
- transform: translateY(0);
209
- opacity: 1;
217
+ @media (max-width: 600px) {
218
+ .toast-v2-region {
219
+ max-width: calc(100vw - 32px);
210
220
  }
211
- to {
212
- transform: translateY(6px);
213
- opacity: 0.8;
221
+
222
+ .toast-v2 {
223
+ width: 100%;
214
224
  }
215
225
  }
@@ -1,40 +1,78 @@
1
- import { Toast as Kobalte, toaster } from "@kobalte/core/toast"
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 { Show, children } from "solid-js"
3
+ import { createContext, onCleanup, onMount, splitProps, useContext } from "solid-js"
5
4
  import { Portal } from "solid-js/web"
6
- import { ButtonV2 } from "./button-v2"
5
+ import "./button-v2.css"
7
6
  import "./toast-v2.css"
8
7
 
9
- export interface ToastV2RegionProps extends ComponentProps<typeof Kobalte.Region> {}
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
- <Kobalte.Region data-component="toast-v2-region" {...props}>
15
- <Kobalte.List data-slot="toast-v2-list" />
16
- </Kobalte.Region>
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
- export interface ToastV2RootComponentProps extends ToastRootProps {
22
- class?: string
23
- classList?: ComponentProps<"li">["classList"]
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: ToastTitleProps & ComponentProps<"div">) {
49
- return <Kobalte.Title data-slot="toast-v2-title" {...props} />
86
+ function ToastV2Title(props: ComponentProps<"div">) {
87
+ return <div data-slot="toast-v2-title" {...props} />
50
88
  }
51
89
 
52
- function ToastV2Description(props: ToastDescriptionProps & ComponentProps<"div">) {
53
- return <Kobalte.Description data-slot="toast-v2-description" {...props} />
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: ToastCloseButtonProps & ComponentProps<"button">) {
98
+ function ToastV2CloseButton(props: ComponentProps<"button">) {
99
+ const toastId = useContext(ToastV2Context)
100
+ const [local, rest] = splitProps(props, ["children", "onClick"])
61
101
  return (
62
- <Kobalte.CloseButton data-slot="toast-v2-close-button" aria-label="Dismiss" {...props}>
63
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
64
- <path d="M4.25 11.75L11.75 4.25" stroke="currentColor" />
65
- <path d="M11.75 11.75L4.25 4.25" stroke="currentColor" />
66
- </svg>
67
- </Kobalte.CloseButton>
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
- export { toaster as toasterV2 }
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
- return toaster.show((props) => {
101
- const resolvedIcon = children(() => opts.icon)
102
- return (
103
- <ToastV2 toastId={props.toastId} duration={opts.duration} persistent={opts.persistent}>
104
- <div data-slot="toast-v2-header">
105
- <Show when={resolvedIcon()}>
106
- <ToastV2.Icon>{resolvedIcon()}</ToastV2.Icon>
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> {