mirror of
https://github.com/langgenius/dify.git
synced 2026-03-26 13:51:05 +08:00
fix: tighten toast typing and restore focus visibility (#33591)
This commit is contained in:
parent
4c5f7efeed
commit
801f8b6e64
@ -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()
|
||||
})
|
||||
|
||||
@ -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<string, never>
|
||||
type ToastType = 'success' | 'error' | 'warning' | 'info'
|
||||
type ToastToneStyle = {
|
||||
gradientClassName: string
|
||||
iconClassName: string
|
||||
}
|
||||
|
||||
type ToastAddOptions = Omit<ToastManagerAddOptions<ToastData>, '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<string, ToastToneStyle>
|
||||
|
||||
export type ToastType = keyof typeof TOAST_TONE_STYLES
|
||||
|
||||
export type ToastAddOptions = Omit<ToastManagerAddOptions<ToastData>, 'data' | 'positionerProps' | 'type'> & {
|
||||
type?: ToastType
|
||||
}
|
||||
|
||||
type ToastUpdateOptions = Omit<ToastManagerUpdateOptions<ToastData>, 'data' | 'positionerProps' | 'type'> & {
|
||||
export type ToastUpdateOptions = Omit<ToastManagerUpdateOptions<ToastData>, 'data' | 'positionerProps' | 'type'> & {
|
||||
type?: ToastType
|
||||
}
|
||||
|
||||
type ToastPromiseOptions<Value> = {
|
||||
type ToastPromiseResultOption<Value> = string | ToastUpdateOptions | ((value: Value) => string | ToastUpdateOptions)
|
||||
|
||||
export type ToastPromiseOptions<Value> = {
|
||||
loading: string | ToastUpdateOptions
|
||||
success: string | ToastUpdateOptions | ((result: Value) => string | ToastUpdateOptions)
|
||||
error: string | ToastUpdateOptions | ((error: unknown) => string | ToastUpdateOptions)
|
||||
success: ToastPromiseResultOption<Value>
|
||||
error: ToastPromiseResultOption<unknown>
|
||||
}
|
||||
|
||||
export type ToastHostProps = {
|
||||
@ -34,6 +59,14 @@ export type ToastHostProps = {
|
||||
|
||||
const toastManager = BaseToast.createToastManager<ToastData>()
|
||||
|
||||
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<Value>(promiseValue: Promise<Value>, options: ToastPromiseOptions<Value>) {
|
||||
return toastManager.promise(promiseValue, options as ToastManagerPromiseOptions<Value, ToastData>)
|
||||
return toastManager.promise(promiseValue, options)
|
||||
},
|
||||
}
|
||||
|
||||
function ToastIcon({ type }: { type?: string }) {
|
||||
if (type === 'success') {
|
||||
return <span aria-hidden="true" className="i-ri-checkbox-circle-fill h-5 w-5 text-text-success" />
|
||||
}
|
||||
|
||||
if (type === 'error') {
|
||||
return <span aria-hidden="true" className="i-ri-error-warning-fill h-5 w-5 text-text-destructive" />
|
||||
}
|
||||
|
||||
if (type === 'warning') {
|
||||
return <span aria-hidden="true" className="i-ri-alert-fill h-5 w-5 text-text-warning-secondary" />
|
||||
}
|
||||
|
||||
if (type === 'info') {
|
||||
return <span aria-hidden="true" className="i-ri-information-2-fill h-5 w-5 text-text-accent" />
|
||||
}
|
||||
|
||||
return null
|
||||
function ToastIcon({ type }: { type?: ToastType }) {
|
||||
return type
|
||||
? <span aria-hidden="true" className={cn('h-5 w-5', TOAST_TONE_STYLES[type].iconClassName)} />
|
||||
: null
|
||||
}
|
||||
|
||||
function getToneGradientClasses(type?: string) {
|
||||
if (type === 'success')
|
||||
return 'from-components-badge-status-light-success-halo to-background-gradient-mask-transparent'
|
||||
|
||||
if (type === 'error')
|
||||
return 'from-components-badge-status-light-error-halo to-background-gradient-mask-transparent'
|
||||
|
||||
if (type === 'warning')
|
||||
return 'from-components-badge-status-light-warning-halo to-background-gradient-mask-transparent'
|
||||
|
||||
if (type === 'info')
|
||||
return 'from-components-badge-status-light-normal-halo to-background-gradient-mask-transparent'
|
||||
|
||||
function getToneGradientClasses(type?: ToastType) {
|
||||
if (type)
|
||||
return TOAST_TONE_STYLES[type].gradientClassName
|
||||
return 'from-background-default-subtle to-background-gradient-mask-transparent'
|
||||
}
|
||||
|
||||
@ -93,12 +102,13 @@ function ToastCard({
|
||||
showHoverBridge?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('common')
|
||||
const toastType = getToastType(toastItem.type)
|
||||
|
||||
return (
|
||||
<BaseToast.Root
|
||||
toast={toastItem}
|
||||
className={cn(
|
||||
'pointer-events-auto absolute right-0 top-0 w-[360px] max-w-[calc(100vw-2rem)] origin-top-right cursor-default select-none outline-none',
|
||||
'pointer-events-auto absolute right-0 top-0 w-[360px] max-w-[calc(100vw-2rem)] origin-top cursor-default select-none',
|
||||
'[--toast-current-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:8px] [--toast-peek:5px] [--toast-scale:calc(1-(var(--toast-index)*0.0225))] [--toast-shrink:calc(1-var(--toast-scale))]',
|
||||
'[height:var(--toast-current-height)] [z-index:calc(100-var(--toast-index))]',
|
||||
'[transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms] motion-reduce:transition-none',
|
||||
@ -110,11 +120,11 @@ function ToastCard({
|
||||
<div className="relative overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn('absolute inset-[-1px] bg-gradient-to-r opacity-40', getToneGradientClasses(toastItem.type))}
|
||||
className={cn('absolute inset-[-1px] bg-gradient-to-r opacity-40', getToneGradientClasses(toastType))}
|
||||
/>
|
||||
<BaseToast.Content className="relative flex items-start gap-1 overflow-hidden p-3 transition-opacity duration-200 data-[behind]:opacity-0 data-[expanded]:opacity-100">
|
||||
<div className="flex shrink-0 items-center justify-center p-0.5">
|
||||
<ToastIcon type={toastItem.type} />
|
||||
<ToastIcon type={toastType} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 p-1">
|
||||
<div className="flex w-full items-center gap-1">
|
||||
|
||||
@ -71,7 +71,7 @@ const LocaleLayout = async ({
|
||||
<SentryInitializer>
|
||||
<TanstackQueryInitializer>
|
||||
<I18nServerProvider>
|
||||
<ToastHost timeout={5000} />
|
||||
<ToastHost timeout={5000} limit={3} />
|
||||
<ToastProvider>
|
||||
<GlobalPublicStoreProvider>
|
||||
<TooltipProvider delay={300} closeDelay={200}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user