fix(ui): resolve toast icon inside the toast component (#34874)
This commit is contained in:
parent
4a42caef2c
commit
f52424e05f
2 changed files with 65 additions and 37 deletions
26
packages/app/test-browser/toast-owner.test.ts
Normal file
26
packages/app/test-browser/toast-owner.test.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { createSignal, type JSX } from "solid-js"
|
||||
import { showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
|
||||
|
||||
describe("showToastV2", () => {
|
||||
test("creates no reactive computations at call time", () => {
|
||||
const [tick, setTick] = createSignal(0)
|
||||
let reads = 0
|
||||
const icon = (() => {
|
||||
reads++
|
||||
tick()
|
||||
return undefined
|
||||
}) as unknown as JSX.Element
|
||||
|
||||
const id = showToastV2({ description: "test", icon })
|
||||
|
||||
// Resolving the icon at call time creates an ownerless computation that is
|
||||
// never disposed and tracks its dependencies forever; it must only resolve
|
||||
// once the toast component renders.
|
||||
expect(reads).toBe(0)
|
||||
setTick(1)
|
||||
expect(reads).toBe(0)
|
||||
|
||||
toasterV2.dismiss(id)
|
||||
})
|
||||
})
|
||||
|
|
@ -97,44 +97,46 @@ export interface ToastV2Options {
|
|||
|
||||
export function showToastV2(options: ToastV2Options | string) {
|
||||
const opts = typeof options === "string" ? { description: options } : options
|
||||
const resolvedIcon = children(() => opts.icon)
|
||||
return toaster.show((props) => (
|
||||
<ToastV2 toastId={props.toastId} duration={opts.duration} persistent={opts.persistent}>
|
||||
<div data-slot="toast-v2-header">
|
||||
<Show when={resolvedIcon()}>
|
||||
<ToastV2.Icon>{resolvedIcon()}</ToastV2.Icon>
|
||||
return toaster.show((props) => {
|
||||
const resolvedIcon = children(() => opts.icon)
|
||||
return (
|
||||
<ToastV2 toastId={props.toastId} duration={opts.duration} persistent={opts.persistent}>
|
||||
<div data-slot="toast-v2-header">
|
||||
<Show when={resolvedIcon()}>
|
||||
<ToastV2.Icon>{resolvedIcon()}</ToastV2.Icon>
|
||||
</Show>
|
||||
<ToastV2.Content>
|
||||
<Show when={opts.title}>
|
||||
<ToastV2.Title>{opts.title}</ToastV2.Title>
|
||||
</Show>
|
||||
<Show when={opts.description}>
|
||||
<ToastV2.Description>{opts.description}</ToastV2.Description>
|
||||
</Show>
|
||||
</ToastV2.Content>
|
||||
<ToastV2.CloseButton />
|
||||
</div>
|
||||
<Show when={opts.actions?.length}>
|
||||
<ToastV2.Actions>
|
||||
{opts.actions!.map((action) => (
|
||||
<ButtonV2
|
||||
variant={action.variant === "secondary" ? "ghost" : "neutral"}
|
||||
size="small"
|
||||
data-action-variant={action.variant ?? "primary"}
|
||||
onClick={() => {
|
||||
if (typeof action.onClick === "function") {
|
||||
action.onClick()
|
||||
}
|
||||
toaster.dismiss(props.toastId)
|
||||
}}
|
||||
>
|
||||
{action.label}
|
||||
</ButtonV2>
|
||||
))}
|
||||
</ToastV2.Actions>
|
||||
</Show>
|
||||
<ToastV2.Content>
|
||||
<Show when={opts.title}>
|
||||
<ToastV2.Title>{opts.title}</ToastV2.Title>
|
||||
</Show>
|
||||
<Show when={opts.description}>
|
||||
<ToastV2.Description>{opts.description}</ToastV2.Description>
|
||||
</Show>
|
||||
</ToastV2.Content>
|
||||
<ToastV2.CloseButton />
|
||||
</div>
|
||||
<Show when={opts.actions?.length}>
|
||||
<ToastV2.Actions>
|
||||
{opts.actions!.map((action) => (
|
||||
<ButtonV2
|
||||
variant={action.variant === "secondary" ? "ghost" : "neutral"}
|
||||
size="small"
|
||||
data-action-variant={action.variant ?? "primary"}
|
||||
onClick={() => {
|
||||
if (typeof action.onClick === "function") {
|
||||
action.onClick()
|
||||
}
|
||||
toaster.dismiss(props.toastId)
|
||||
}}
|
||||
>
|
||||
{action.label}
|
||||
</ButtonV2>
|
||||
))}
|
||||
</ToastV2.Actions>
|
||||
</Show>
|
||||
</ToastV2>
|
||||
))
|
||||
</ToastV2>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export interface ToastV2PromiseOptions<T, U = unknown> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue