Files
gridpilot.gg/apps/website/ui/Toast.tsx
2026-01-20 22:31:14 +01:00

69 lines
2.0 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
translateX={isVisible && !isExiting ? '0' : '100%'}
opacity={isVisible && !isExiting ? 1 : 0}
transition="all 0.3s ease-in-out"
width="24rem"
>
<Surface variant="muted" rounded="xl" shadow="xl" borderColor={`${intentColors[intent]}33`} border={true} overflow="hidden">
{progress !== undefined && (
<Box height="1px" bg="rgba(255,255,255,0.1)">
<Box height="100%" bg={intentColors[intent]} 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>
);
};