Tldraw/packages/tldraw/src/lib/ui/components/Toasts.tsx

122 wiersze
3.1 KiB
TypeScript

import * as T from '@radix-ui/react-toast'
import * as React from 'react'
import { AlertSeverity, TLUiToast, useToasts } from '../context/toasts'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { TLUiIconType } from '../icon-types'
import { TldrawUiButton } from './primitives/Button/TldrawUiButton'
import { TldrawUiButtonLabel } from './primitives/Button/TldrawUiButtonLabel'
import { TldrawUiIcon } from './primitives/TldrawUiIcon'
const SEVERITY_TO_ICON: { [msg in AlertSeverity]: TLUiIconType } = {
success: 'check-circle',
warning: 'warning-triangle',
error: 'cross-circle',
info: 'info-circle',
}
function Toast({ toast }: { toast: TLUiToast }) {
const { removeToast } = useToasts()
const msg = useTranslation()
const onOpenChange = (isOpen: boolean) => {
if (!isOpen) {
removeToast(toast.id)
}
}
const hasActions = toast.actions && toast.actions.length > 0
const icon = toast.icon || (toast.severity && SEVERITY_TO_ICON[toast.severity])
return (
<T.Root
onOpenChange={onOpenChange}
className="tlui-toast__container"
duration={toast.keepOpen ? Infinity : 5000}
data-severity={toast.severity}
>
{icon && (
<div className="tlui-toast__icon">
<TldrawUiIcon icon={icon} />
</div>
)}
<div className="tlui-toast__main">
<div className="tlui-toast__content">
{toast.title && <T.Title className="tlui-toast__title">{toast.title}</T.Title>}
{toast.description && (
<T.Description className="tlui-toast__description">{toast.description}</T.Description>
)}
</div>
{toast.actions && (
<div className="tlui-toast__actions">
{toast.actions.map((action, i) => (
<T.Action key={i} altText={action.label} asChild onClick={action.onClick}>
<TldrawUiButton type={action.type}>
<TldrawUiButtonLabel>{action.label}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Action>
))}
<T.Close asChild>
<TldrawUiButton
type="normal"
className="tlui-toast__close"
style={{ marginLeft: 'auto' }}
>
<TldrawUiButtonLabel>{toast.closeLabel ?? msg('toast.close')}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Close>
</div>
)}
</div>
{!hasActions && (
<T.Close asChild>
<TldrawUiButton type="normal" className="tlui-toast__close">
<TldrawUiButtonLabel>{toast.closeLabel ?? msg('toast.close')}</TldrawUiButtonLabel>
</TldrawUiButton>
</T.Close>
)}
</T.Root>
)
}
function _Toasts() {
const { toasts } = useToasts()
return (
<>
{toasts.map((toast) => (
<Toast key={toast.id} toast={toast} />
))}
</>
)
}
export const Toasts = React.memo(_Toasts)
export function ToastViewport() {
const { toasts } = useToasts()
const [hasToasts, setHasToasts] = React.useState(false)
React.useEffect(() => {
let cancelled = false
if (toasts.length) {
setHasToasts(true)
} else {
setTimeout(() => {
if (!cancelled) {
setHasToasts(false)
}
}, 1000)
}
return () => {
cancelled = true
}
}, [toasts.length, setHasToasts])
if (!hasToasts) return null
return <T.ToastViewport className="tlui-toast__viewport" />
}