From 801f8b6e64a63d9d16d89a316c25fad1bed9073e Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:17:53 +0800 Subject: [PATCH] fix: tighten toast typing and restore focus visibility (#33591) --- .../base/ui/toast/__tests__/index.spec.tsx | 1 + web/app/components/base/ui/toast/index.tsx | 94 ++++++++++--------- web/app/layout.tsx | 2 +- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/web/app/components/base/ui/toast/__tests__/index.spec.tsx b/web/app/components/base/ui/toast/__tests__/index.spec.tsx index 212a11bea8f..75364117c3f 100644 --- a/web/app/components/base/ui/toast/__tests__/index.spec.tsx +++ b/web/app/components/base/ui/toast/__tests__/index.spec.tsx @@ -37,6 +37,7 @@ describe('base/ui/toast', () => { expect(viewport).toHaveAttribute('aria-live', 'polite') expect(viewport).toHaveClass('z-[1101]') expect(viewport.firstElementChild).toHaveClass('top-4') + expect(screen.getByRole('dialog')).not.toHaveClass('outline-none') expect(document.body.querySelector('[aria-hidden="true"].i-ri-checkbox-circle-fill')).toBeInTheDocument() expect(document.body.querySelector('button[aria-label="common.toast.close"][aria-hidden="true"]')).toBeInTheDocument() }) diff --git a/web/app/components/base/ui/toast/index.tsx b/web/app/components/base/ui/toast/index.tsx index aed0c59b166..2374ac29555 100644 --- a/web/app/components/base/ui/toast/index.tsx +++ b/web/app/components/base/ui/toast/index.tsx @@ -2,7 +2,6 @@ import type { ToastManagerAddOptions, - ToastManagerPromiseOptions, ToastManagerUpdateOptions, ToastObject, } from '@base-ui/react/toast' @@ -11,20 +10,46 @@ import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' type ToastData = Record -type ToastType = 'success' | 'error' | 'warning' | 'info' +type ToastToneStyle = { + gradientClassName: string + iconClassName: string +} -type ToastAddOptions = Omit, 'data' | 'positionerProps' | 'type'> & { +const TOAST_TONE_STYLES = { + success: { + iconClassName: 'i-ri-checkbox-circle-fill text-text-success', + gradientClassName: 'from-components-badge-status-light-success-halo to-background-gradient-mask-transparent', + }, + error: { + iconClassName: 'i-ri-error-warning-fill text-text-destructive', + gradientClassName: 'from-components-badge-status-light-error-halo to-background-gradient-mask-transparent', + }, + warning: { + iconClassName: 'i-ri-alert-fill text-text-warning-secondary', + gradientClassName: 'from-components-badge-status-light-warning-halo to-background-gradient-mask-transparent', + }, + info: { + iconClassName: 'i-ri-information-2-fill text-text-accent', + gradientClassName: 'from-components-badge-status-light-normal-halo to-background-gradient-mask-transparent', + }, +} satisfies Record + +export type ToastType = keyof typeof TOAST_TONE_STYLES + +export type ToastAddOptions = Omit, 'data' | 'positionerProps' | 'type'> & { type?: ToastType } -type ToastUpdateOptions = Omit, 'data' | 'positionerProps' | 'type'> & { +export type ToastUpdateOptions = Omit, 'data' | 'positionerProps' | 'type'> & { type?: ToastType } -type ToastPromiseOptions = { +type ToastPromiseResultOption = string | ToastUpdateOptions | ((value: Value) => string | ToastUpdateOptions) + +export type ToastPromiseOptions = { loading: string | ToastUpdateOptions - success: string | ToastUpdateOptions | ((result: Value) => string | ToastUpdateOptions) - error: string | ToastUpdateOptions | ((error: unknown) => string | ToastUpdateOptions) + success: ToastPromiseResultOption + error: ToastPromiseResultOption } export type ToastHostProps = { @@ -34,6 +59,14 @@ export type ToastHostProps = { const toastManager = BaseToast.createToastManager() +function isToastType(type: string): type is ToastType { + return Object.prototype.hasOwnProperty.call(TOAST_TONE_STYLES, type) +} + +function getToastType(type?: string): ToastType | undefined { + return type && isToastType(type) ? type : undefined +} + export const toast = { add(options: ToastAddOptions) { return toastManager.add(options) @@ -45,43 +78,19 @@ export const toast = { toastManager.update(toastId, options) }, promise(promiseValue: Promise, options: ToastPromiseOptions) { - return toastManager.promise(promiseValue, options as ToastManagerPromiseOptions) + return toastManager.promise(promiseValue, options) }, } -function ToastIcon({ type }: { type?: string }) { - if (type === 'success') { - return