Files
gridpilot.gg/apps/website/ui/Toast.tsx
2026-01-18 23:24:30 +01:00

71 lines
2.1 KiB
TypeScript

import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { Box } from './Box';
import { IconButton } from './IconButton';
import { Surface } from './Surface';
import { Text } from './Text';
export interface ToastProps {
children: ReactNode;
title?: string;
onClose: () => void;
icon?: ReactNode;
intent?: 'primary' | 'success' | 'warning' | 'critical';
isVisible: boolean;
isExiting: boolean;
progress?: number;
}
export const Toast = ({
children,
title,
onClose,
icon,
intent = 'primary',
isVisible,
isExiting,
progress
}: ToastProps) => {
const intentColors = {
primary: 'var(--ui-color-intent-primary)',
success: 'var(--ui-color-intent-success)',
warning: 'var(--ui-color-intent-warning)',
critical: 'var(--ui-color-intent-critical)',
};
return (
<Box
style={{
transform: isVisible && !isExiting ? 'translateX(0)' : 'translateX(100%)',
opacity: isVisible && !isExiting ? 1 : 0,
transition: 'all 0.3s ease-in-out',
width: '24rem'
}}
>
<Surface variant="muted" rounded="xl" shadow="xl" style={{ border: `1px solid ${intentColors[intent]}33`, overflow: 'hidden' }}>
{progress !== undefined && (
<Box height="1px" bg="rgba(255,255,255,0.1)">
<Box height="100%" bg={intentColors[intent]} style={{ width: `${progress}%`, transition: 'width 0.1s linear' }} />
</Box>
)}
<Box padding={4} display="flex" gap={3}>
{icon && (
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)" flexShrink={0}>
{icon}
</Box>
)}
<Box flex={1} minWidth="0">
<Box display="flex" alignItems="start" justifyContent="between" gap={2}>
{title && <Text size="sm" weight="bold" variant="high" truncate>{title}</Text>}
<IconButton icon={X} size="sm" variant="ghost" onClick={onClose} title="Close" />
</Box>
<Box marginTop={1}>
{children}
</Box>
</Box>
</Box>
</Surface>
</Box>
);
};