155 lines
5.3 KiB
TypeScript
155 lines
5.3 KiB
TypeScript
import { Box } from '@/ui/Box';
|
|
import { Button } from '@/ui/Button';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Link } from '@/ui/Link';
|
|
import { EmptyStateProps } from '@/ui/state-types';
|
|
import { Text } from '@/ui/Text';
|
|
import React from 'react';
|
|
|
|
// Illustration components (simple SVG representations)
|
|
const Illustrations = {
|
|
racing: () => (
|
|
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M20 70 L80 70 L85 50 L80 30 L20 30 L15 50 Z" fill="currentColor" opacity="0.2"/>
|
|
<path d="M30 60 L70 60 L75 50 L70 40 L30 40 L25 50 Z" fill="currentColor" opacity="0.4"/>
|
|
<circle cx="35" cy="65" r="3" fill="currentColor"/>
|
|
<circle cx="65" cy="65" r="3" fill="currentColor"/>
|
|
<path d="M50 30 L50 20 M45 25 L50 20 L55 25" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
|
</svg>
|
|
),
|
|
league: () => (
|
|
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="50" cy="35" r="15" fill="currentColor" opacity="0.3"/>
|
|
<path d="M35 50 L50 45 L65 50 L65 70 L35 70 Z" fill="currentColor" opacity="0.2"/>
|
|
<path d="M40 55 L50 52 L60 55" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
|
<path d="M40 62 L50 59 L60 62" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
|
</svg>
|
|
),
|
|
team: () => (
|
|
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="35" cy="35" r="8" fill="currentColor" opacity="0.3"/>
|
|
<circle cx="65" cy="35" r="8" fill="currentColor" opacity="0.3"/>
|
|
<circle cx="50" cy="55" r="10" fill="currentColor" opacity="0.2"/>
|
|
<path d="M35 45 L35 60 M65 45 L65 60 M50 65 L50 80" stroke="currentColor" strokeWidth="3" strokeLinecap="round"/>
|
|
</svg>
|
|
),
|
|
sponsor: () => (
|
|
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="25" y="25" width="50" height="50" rx="8" fill="currentColor" opacity="0.2"/>
|
|
<path d="M35 50 L45 60 L65 40" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
|
|
<path d="M50 35 L50 65 M40 50 L60 50" stroke="currentColor" strokeWidth="2" opacity="0.5"/>
|
|
</svg>
|
|
),
|
|
driver: () => (
|
|
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="50" cy="30" r="8" fill="currentColor" opacity="0.3"/>
|
|
<path d="M42 38 L58 38 L55 55 L45 55 Z" fill="currentColor" opacity="0.2"/>
|
|
<path d="M45 55 L40 70 M55 55 L60 70" stroke="currentColor" strokeWidth="3" strokeLinecap="round"/>
|
|
<circle cx="40" cy="72" r="3" fill="currentColor"/>
|
|
<circle cx="60" cy="72" r="3" fill="currentColor"/>
|
|
</svg>
|
|
),
|
|
} as const;
|
|
|
|
export function EmptyState({
|
|
icon: Icon,
|
|
title,
|
|
description,
|
|
action,
|
|
variant = 'default',
|
|
illustration,
|
|
ariaLabel = 'Empty state',
|
|
children,
|
|
}: EmptyStateProps & { children?: React.ReactNode }) {
|
|
const IllustrationComponent = illustration ? Illustrations[illustration] : null;
|
|
|
|
const content = (
|
|
<Box display="flex" flexDirection="col" alignItems="center" gap={4} textAlign="center">
|
|
<Box>
|
|
{IllustrationComponent ? (
|
|
<Box color="var(--ui-color-text-low)">
|
|
<IllustrationComponent />
|
|
</Box>
|
|
) : Icon ? (
|
|
<Box padding={4} rounded="xl" bg="var(--ui-color-bg-surface-muted)" style={{ border: '1px solid var(--ui-color-border-default)' }}>
|
|
<Icon size={32} color="var(--ui-color-text-low)" />
|
|
</Box>
|
|
) : null}
|
|
</Box>
|
|
|
|
<Heading level={3} weight="semibold">{title}</Heading>
|
|
|
|
{description && (
|
|
<Text variant="low" leading="relaxed">
|
|
{description}
|
|
</Text>
|
|
)}
|
|
|
|
{action && (
|
|
<Box marginTop={2}>
|
|
<Button
|
|
variant={action.variant || 'primary'}
|
|
onClick={action.onClick}
|
|
style={{ minWidth: '140px' }}
|
|
>
|
|
{action.label}
|
|
</Button>
|
|
</Box>
|
|
)}
|
|
|
|
{children && (
|
|
<Box marginTop={4}>
|
|
{children}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
|
|
if (variant === 'full-page') {
|
|
return (
|
|
<Box
|
|
position="fixed"
|
|
inset={0}
|
|
bg="var(--ui-color-bg-base)"
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
padding={6}
|
|
role="status"
|
|
aria-label={ariaLabel}
|
|
>
|
|
<Box maxWidth="32rem" fullWidth>
|
|
{content}
|
|
<Box marginTop={8} textAlign="center">
|
|
<Text size="sm" variant="low">
|
|
Need help? Contact us at{' '}
|
|
<Link href="mailto:support@gridpilot.com" variant="primary">
|
|
support@gridpilot.com
|
|
</Link>
|
|
</Text>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
paddingY={variant === 'minimal' ? 8 : 12}
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
role="status"
|
|
aria-label={ariaLabel}
|
|
>
|
|
<Box maxWidth={variant === 'minimal' ? '24rem' : '32rem'} fullWidth>
|
|
{content}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export function MinimalEmptyState(props: Omit<EmptyStateProps, 'variant'>) {
|
|
return <EmptyState {...props} variant="minimal" />;
|
|
}
|