website refactor
This commit is contained in:
151
apps/website/ui/EmptyState.tsx
Normal file
151
apps/website/ui/EmptyState.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { EmptyStateProps } from '@/ui/state-types';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Activity, Lock, Search } from 'lucide-react';
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user