website refactor

This commit is contained in:
2026-01-21 01:27:08 +01:00
parent 5f3712e5ab
commit d30a725fe7
44 changed files with 702 additions and 572 deletions

View File

@@ -2,8 +2,10 @@
import { DriverIdentity } from '@/ui/DriverIdentity'; import { DriverIdentity } from '@/ui/DriverIdentity';
import { ProfileCard } from '@/ui/ProfileCard'; import { ProfileCard } from '@/ui/ProfileCard';
import { StatGrid } from '@/ui/StatGrid';
import { Badge } from '@/ui/Badge'; import { Badge } from '@/ui/Badge';
import { Grid } from '@/ui/Grid';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Flag, Trophy, Medal } from 'lucide-react'; import { Flag, Trophy, Medal } from 'lucide-react';
interface DriverCardProps { interface DriverCardProps {
@@ -23,12 +25,6 @@ interface DriverCardProps {
} }
export function DriverCard({ driver, onClick }: DriverCardProps) { export function DriverCard({ driver, onClick }: DriverCardProps) {
const stats = [
{ label: 'Races', value: driver.racesCompleted, intent: 'low', icon: Flag },
{ label: 'Wins', value: driver.wins, intent: 'primary', icon: Trophy },
{ label: 'Podiums', value: driver.podiums, intent: 'warning', icon: Medal },
];
return ( return (
<ProfileCard <ProfileCard
onClick={() => onClick(driver.id)} onClick={() => onClick(driver.id)}
@@ -50,16 +46,20 @@ export function DriverCard({ driver, onClick }: DriverCardProps) {
</Badge> </Badge>
} }
stats={ stats={
<StatGrid <Grid cols={3} gap="px" style={{ backgroundColor: 'var(--ui-color-border-muted)', border: '1px solid var(--ui-color-border-muted)', borderRadius: 'var(--ui-radius-md)', overflow: 'hidden' }}>
stats={stats.map(s => ({ <Stack padding={2} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
label: s.label, <Text size="xs" variant="low" uppercase block mono>Races</Text>
value: s.value, <Text size="sm" weight="bold" mono variant="high">{driver.racesCompleted}</Text>
intent: s.intent as any, </Stack>
icon: s.icon <Stack padding={2} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
}))} <Text size="xs" variant="low" uppercase block mono>Wins</Text>
columns={3} <Text size="sm" weight="bold" mono variant="primary">{driver.wins}</Text>
variant="box" </Stack>
/> <Stack padding={2} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
<Text size="xs" variant="low" uppercase block mono>Podiums</Text>
<Text size="sm" weight="bold" mono variant="warning">{driver.podiums}</Text>
</Stack>
</Grid>
} }
/> />
); );

View File

@@ -9,6 +9,7 @@ import { Group } from '@/ui/Group';
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
import { Panel } from '@/ui/Panel'; import { Panel } from '@/ui/Panel';
import { Stack } from '@/ui/Stack'; import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { Trophy } from 'lucide-react'; import { Trophy } from 'lucide-react';
/** /**
@@ -17,23 +18,23 @@ import { Trophy } from 'lucide-react';
*/ */
export function CtaSection() { export function CtaSection() {
return ( return (
<Section variant="default" py={32}> <Section variant="default" padding="lg">
<Panel variant="muted" padding="xl"> <Panel variant="muted" padding="xl">
<Stack gap={12} align="center" textAlign="center"> <Stack gap={12} align="center" textAlign="center">
<Box> <Stack gap={6} align="center">
<Box marginBottom={6} display="flex" justifyContent="center"> <Box width="3rem" height="3rem" bg="var(--ui-color-bg-surface)" rounded="full" border={true} display="flex" alignItems="center" justifyContent="center">
<Box width="3rem" height="3rem" bg="var(--ui-color-bg-surface)" rounded="full" border={true} display="flex" alignItems="center" justifyContent="center"> <Icon icon={Trophy} size={6} intent="primary" />
<Trophy size={24} className="text-[var(--ui-color-intent-primary)]" />
</Box>
</Box> </Box>
<Heading level={2} weight="bold" marginBottom={4}> <Stack gap={4}>
Ready to elevate your league? <Heading level={2} weight="bold">
</Heading> Ready to elevate your league?
<Text variant="med" size="lg" maxWidth="36rem" mx="auto"> </Heading>
Join the growing ecosystem of professional sim racing leagues. <Text variant="med" size="lg" maxWidth="36rem">
Start for free and scale as your community grows. Join the growing ecosystem of professional sim racing leagues.
</Text> Start for free and scale as your community grows.
</Box> </Text>
</Stack>
</Stack>
<Group gap={6} justify="center"> <Group gap={6} justify="center">
<Button variant="primary" size="lg"> <Button variant="primary" size="lg">

View File

@@ -10,6 +10,7 @@ import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid'; import { Grid } from '@/ui/Grid';
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
import { StatusBadge } from '@/ui/StatusBadge'; import { StatusBadge } from '@/ui/StatusBadge';
import { Icon } from '@/ui/Icon';
import { Trophy, Globe, Settings2, Palette, ShieldCheck, BarChart3 } from 'lucide-react'; import { Trophy, Globe, Settings2, Palette, ShieldCheck, BarChart3 } from 'lucide-react';
interface LeagueIdentityPreviewProps { interface LeagueIdentityPreviewProps {
@@ -29,15 +30,15 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
const subdomain = league ? `${league.name.toLowerCase().replace(/\s+/g, '')}.gridpilot.racing` : 'apex.gridpilot.racing'; const subdomain = league ? `${league.name.toLowerCase().replace(/\s+/g, '')}.gridpilot.racing` : 'apex.gridpilot.racing';
return ( return (
<Section variant="default" py={32}> <Section variant="default" padding="lg">
<Box> <Stack gap={24}>
<Box marginBottom={24} maxWidth="42rem"> <Stack gap={8} maxWidth="42rem">
<Heading level={2} weight="bold" marginBottom={8}>Your Brand. Your Rules.</Heading> <Heading level={2} weight="bold">Your Brand. Your Rules.</Heading>
<Text variant="med" size="lg" leading="relaxed"> <Text variant="med" size="lg" leading="relaxed">
GridPilot is designed to be invisible where it matters, letting your league's identity take center stage. GridPilot is designed to be invisible where it matters, letting your league's identity take center stage.
Professional tools that respect your community's unique culture. Professional tools that respect your community's unique culture.
</Text> </Text>
</Box> </Stack>
<Grid cols={{ base: 1, md: 2 }} gap={20}> <Grid cols={{ base: 1, md: 2 }} gap={20}>
{/* Your Brand - Visual Identity */} {/* Your Brand - Visual Identity */}
@@ -46,8 +47,8 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
<Stack gap={8}> <Stack gap={8}>
<Stack gap={2}> <Stack gap={2}>
<Group gap={2}> <Group gap={2}>
<Palette size={16} className="text-[var(--ui-color-intent-primary)]" /> <Icon icon={Palette} size={4} intent="primary" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">Your Brand</Text> <Text variant="primary" weight="bold" uppercase size="xs">Your Brand</Text>
</Group> </Group>
<Heading level={3} weight="bold">Professional Presence</Heading> <Heading level={3} weight="bold">Professional Presence</Heading>
<Text variant="low" size="sm">Build prestige with a dedicated home for your competition.</Text> <Text variant="low" size="sm">Build prestige with a dedicated home for your competition.</Text>
@@ -56,7 +57,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
<Stack gap={4}> <Stack gap={4}>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<Globe size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Globe} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Custom Subdomains</Text> <Text weight="bold">Custom Subdomains</Text>
<Text size="xs" variant="low">{subdomain}</Text> <Text size="xs" variant="low">{subdomain}</Text>
@@ -65,7 +66,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
</Panel> </Panel>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<Trophy size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Trophy} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Live Standings</Text> <Text weight="bold">Live Standings</Text>
<Text size="xs" variant="low">Real-time updates across all car classes.</Text> <Text size="xs" variant="low">Real-time updates across all car classes.</Text>
@@ -74,7 +75,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
</Panel> </Panel>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<BarChart3 size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={BarChart3} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Driver Roster</Text> <Text weight="bold">Driver Roster</Text>
<Text size="xs" variant="low">Detailed profiles with lifetime league stats.</Text> <Text size="xs" variant="low">Detailed profiles with lifetime league stats.</Text>
@@ -85,7 +86,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
<Panel variant="elevated" padding="sm"> <Panel variant="elevated" padding="sm">
<Group gap={3}> <Group gap={3}>
<Box width="2.5rem" height="2.5rem" bg="var(--ui-color-bg-surface)" rounded="sm" border={true} display="flex" alignItems="center" justifyContent="center"> <Box width="2.5rem" height="2.5rem" bg="var(--ui-color-bg-surface)" rounded="sm" border={true} display="flex" alignItems="center" justifyContent="center">
<Trophy size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Trophy} size={5} intent="low" />
</Box> </Box>
<Stack gap={0}> <Stack gap={0}>
<Text weight="bold" size="sm">{leagueName}</Text> <Text weight="bold" size="sm">{leagueName}</Text>
@@ -104,8 +105,8 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
<Stack gap={8}> <Stack gap={8}>
<Stack gap={2}> <Stack gap={2}>
<Group gap={2}> <Group gap={2}>
<Settings2 size={16} className="text-[var(--ui-color-intent-primary)]" /> <Icon icon={Settings2} size={4} intent="primary" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">Your Rules</Text> <Text variant="primary" weight="bold" uppercase size="xs">Your Rules</Text>
</Group> </Group>
<Heading level={3} weight="bold">Absolute Control</Heading> <Heading level={3} weight="bold">Absolute Control</Heading>
<Text variant="low" size="sm">The platform adapts to your league, not the other way around.</Text> <Text variant="low" size="sm">The platform adapts to your league, not the other way around.</Text>
@@ -114,7 +115,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
<Stack gap={4}> <Stack gap={4}>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<ShieldCheck size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={ShieldCheck} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Flexible Points Systems</Text> <Text weight="bold">Flexible Points Systems</Text>
<Text size="xs" variant="low">Custom points for positions, fastest laps, and incidents.</Text> <Text size="xs" variant="low">Custom points for positions, fastest laps, and incidents.</Text>
@@ -123,7 +124,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
</Panel> </Panel>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<Settings2 size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Settings2} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Drop Weeks & Playoffs</Text> <Text weight="bold">Drop Weeks & Playoffs</Text>
<Text size="xs" variant="low">Configure complex season structures with ease.</Text> <Text size="xs" variant="low">Configure complex season structures with ease.</Text>
@@ -132,7 +133,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
</Panel> </Panel>
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Group gap={4}> <Group gap={4}>
<Trophy size={20} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Trophy} size={5} intent="low" />
<Stack gap={1}> <Stack gap={1}>
<Text weight="bold">Multi-Class Support</Text> <Text weight="bold">Multi-Class Support</Text>
<Text size="xs" variant="low">Manage GT3, GTP, and more in a single season.</Text> <Text size="xs" variant="low">Manage GT3, GTP, and more in a single season.</Text>
@@ -156,7 +157,7 @@ export function LeagueIdentityPreview({ league }: LeagueIdentityPreviewProps) {
</Panel> </Panel>
</Box> </Box>
</Grid> </Grid>
</Box> </Stack>
</Section> </Section>
); );
} }

View File

@@ -8,6 +8,7 @@ import { Button } from '@/ui/Button';
import { Stack } from '@/ui/Stack'; import { Stack } from '@/ui/Stack';
import { Group } from '@/ui/Group'; import { Group } from '@/ui/Group';
import { Panel } from '@/ui/Panel'; import { Panel } from '@/ui/Panel';
import { Icon } from '@/ui/Icon';
import { ArrowRight, Database } from 'lucide-react'; import { ArrowRight, Database } from 'lucide-react';
import { routes } from '@/lib/routing/RouteConfig'; import { routes } from '@/lib/routing/RouteConfig';
@@ -16,13 +17,13 @@ import { routes } from '@/lib/routing/RouteConfig';
*/ */
export function MigrationSection() { export function MigrationSection() {
return ( return (
<Section variant="default" py={32}> <Section variant="default" padding="lg">
<Panel variant="bordered" padding="xl"> <Panel variant="bordered" padding="xl">
<Group justify="between" align="center" gap={12} wrap> <Group justify="between" align="center" gap={12} wrap>
<Stack gap={6} flex={1} minWidth="20rem"> <Stack gap={6} flex={1} minWidth="20rem">
<Group gap={3}> <Group gap={3}>
<Database size={20} className="text-[var(--ui-color-intent-primary)]" /> <Icon icon={Database} size={5} intent="primary" />
<Text variant="primary" weight="bold" uppercase size="xs" letterSpacing="0.1em">League Migration</Text> <Text variant="primary" weight="bold" uppercase size="xs">League Migration</Text>
</Group> </Group>
<Heading level={2} weight="bold">Moving from Sheets or Discord?</Heading> <Heading level={2} weight="bold">Moving from Sheets or Discord?</Heading>
<Text variant="med" size="lg"> <Text variant="med" size="lg">
@@ -36,7 +37,7 @@ export function MigrationSection() {
href={routes.league.migration} href={routes.league.migration}
variant="primary" variant="primary"
size="lg" size="lg"
icon={<ArrowRight size={18} />} icon={<Icon icon={ArrowRight} size={4} />}
> >
Start Migration Start Migration
</Button> </Button>

View File

@@ -11,6 +11,7 @@ import { Group } from '@/ui/Group';
import { Stack } from '@/ui/Stack'; import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid'; import { Grid } from '@/ui/Grid';
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
import { Icon } from '@/ui/Icon';
import { Gavel, Clock, User, MessageSquare } from 'lucide-react'; import { Gavel, Clock, User, MessageSquare } from 'lucide-react';
interface StewardingPreviewProps { interface StewardingPreviewProps {
@@ -38,15 +39,15 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
const teamName = team?.name || 'Alex Miller'; const teamName = team?.name || 'Alex Miller';
return ( return (
<Section variant="muted" py={32}> <Section variant="muted" padding="lg">
<Box> <Stack gap={16}>
<Box marginBottom={16} maxWidth="42rem"> <Stack gap={6} maxWidth="42rem">
<Heading level={2} weight="bold" marginBottom={6}>Structured Stewarding</Heading> <Heading level={2} weight="bold">Structured Stewarding</Heading>
<Text variant="med" size="lg" leading="relaxed"> <Text variant="med" size="lg" leading="relaxed">
Protests are part of racing. Managing them shouldn't be a second job. Protests are part of racing. Managing them shouldn't be a second job.
GridPilot provides a dedicated workflow for drivers to report incidents and for you to resolve them with precision. GridPilot provides a dedicated workflow for drivers to report incidents and for you to resolve them with precision.
</Text> </Text>
</Box> </Stack>
<Panel variant="elevated" padding="lg"> <Panel variant="elevated" padding="lg">
<Stack gap={8}> <Stack gap={8}>
@@ -66,7 +67,7 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Stack gap={3}> <Stack gap={3}>
<Group gap={2}> <Group gap={2}>
<User size={14} className="text-[var(--ui-color-intent-primary)]" /> <Icon icon={User} size={4} intent="primary" />
<Text size="xs" uppercase weight="bold" variant="low">Protestor</Text> <Text size="xs" uppercase weight="bold" variant="low">Protestor</Text>
</Group> </Group>
<Text weight="bold">{teamName}</Text> <Text weight="bold">{teamName}</Text>
@@ -76,7 +77,7 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Stack gap={3}> <Stack gap={3}>
<Group gap={2}> <Group gap={2}>
<User size={14} className="text-[var(--ui-color-intent-critical)]" /> <Icon icon={User} size={4} intent="critical" />
<Text size="xs" uppercase weight="bold" variant="low">Defendant</Text> <Text size="xs" uppercase weight="bold" variant="low">Defendant</Text>
</Group> </Group>
<Text weight="bold">David Chen</Text> <Text weight="bold">David Chen</Text>
@@ -86,7 +87,7 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
<Panel variant="bordered" padding="md"> <Panel variant="bordered" padding="md">
<Stack gap={3}> <Stack gap={3}>
<Group gap={2}> <Group gap={2}>
<Clock size={14} className="text-[var(--ui-color-text-low)]" /> <Icon icon={Clock} size={4} intent="low" />
<Text size="xs" uppercase weight="bold" variant="low">Session Info</Text> <Text size="xs" uppercase weight="bold" variant="low">Session Info</Text>
</Group> </Group>
<Text weight="bold">Lap 1, 00:42.150</Text> <Text weight="bold">Lap 1, 00:42.150</Text>
@@ -97,7 +98,7 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
<Stack gap={4}> <Stack gap={4}>
<Group gap={2}> <Group gap={2}>
<MessageSquare size={14} className="text-[var(--ui-color-text-low)]" /> <Icon icon={MessageSquare} size={4} intent="low" />
<Text size="xs" uppercase weight="bold" variant="low">Description</Text> <Text size="xs" uppercase weight="bold" variant="low">Description</Text>
</Group> </Group>
<Panel variant="muted" padding="md"> <Panel variant="muted" padding="md">
@@ -110,11 +111,11 @@ export function StewardingPreview({ race, team }: StewardingPreviewProps) {
<Group gap={3} justify="end"> <Group gap={3} justify="end">
<Button variant="secondary" size="md">Dismiss</Button> <Button variant="secondary" size="md">Dismiss</Button>
<Button variant="primary" size="md" icon={<Gavel size={16} />}>Apply Penalty</Button> <Button variant="primary" size="md" icon={<Icon icon={Gavel} size={4} />}>Apply Penalty</Button>
</Group> </Group>
</Stack> </Stack>
</Panel> </Panel>
</Box> </Stack>
</Section> </Section>
); );
} }

View File

@@ -31,19 +31,17 @@ export function ValuePillars() {
]; ];
return ( return (
<Section variant="default" py={32}> <Section variant="default" padding="lg">
<Box> <FeatureGrid columns={{ base: 1, md: 3 }} gap={16}>
<FeatureGrid columns={{ base: 1, md: 3 }} gap={16}> {pillars.map((pillar) => (
{pillars.map((pillar) => ( <FeatureItem
<FeatureItem key={pillar.title}
key={pillar.title} title={pillar.title}
title={pillar.title} description={pillar.description}
description={pillar.description} icon={pillar.icon}
icon={pillar.icon} />
/> ))}
))} </FeatureGrid>
</FeatureGrid>
</Box>
</Section> </Section>
); );
} }

View File

@@ -99,7 +99,7 @@ function FeatureCard({ feature, index }: { feature: typeof features[0], index: n
export function FeatureGrid() { export function FeatureGrid() {
return ( return (
<Box borderBottom borderColor="var(--ui-color-border-low)"> <Box borderBottom borderColor="var(--ui-color-border-low)">
<Section variant="dark" py={32}> <Section variant="dark" padding="xl">
<Container position="relative" zIndex={10}> <Container position="relative" zIndex={10}>
<Stack gap={16}> <Stack gap={16}>
<Stack maxWidth="2xl"> <Stack maxWidth="2xl">

View File

@@ -63,7 +63,7 @@ export function RankingRow({
</Text> </Text>
<Group gap={2}> <Group gap={2}>
<Text size="xs" variant="low" uppercase weight="bold" letterSpacing="wider">{nationality}</Text> <Text size="xs" variant="low" uppercase weight="bold" letterSpacing="wider">{nationality}</Text>
<Text size="xs" weight="bold" color={SkillLevelDisplay.getColor(skillLevel)} uppercase letterSpacing="wider"> <Text size="xs" weight="bold" style={{ color: SkillLevelDisplay.getColor(skillLevel) }} uppercase letterSpacing="wider">
{SkillLevelDisplay.getLabel(skillLevel)} {SkillLevelDisplay.getLabel(skillLevel)}
</Text> </Text>
</Group> </Group>
@@ -76,7 +76,7 @@ export function RankingRow({
<Text variant="low" font="mono" weight="bold" block size="md"> <Text variant="low" font="mono" weight="bold" block size="md">
{racesCompleted} {racesCompleted}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Races Races
</Text> </Text>
</Group> </Group>
@@ -84,7 +84,7 @@ export function RankingRow({
<Text variant="primary" font="mono" weight="bold" block size="md"> <Text variant="primary" font="mono" weight="bold" block size="md">
{RatingDisplay.format(rating)} {RatingDisplay.format(rating)}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Rating Rating
</Text> </Text>
</Group> </Group>
@@ -92,7 +92,7 @@ export function RankingRow({
<Text variant="success" font="mono" weight="bold" block size="md"> <Text variant="success" font="mono" weight="bold" block size="md">
{wins} {wins}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Wins Wins
</Text> </Text>
</Group> </Group>

View File

@@ -61,7 +61,7 @@ export function TeamRankingRow({
<Text variant="low" font="mono" weight="bold" block size="md"> <Text variant="low" font="mono" weight="bold" block size="md">
{races} {races}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Races Races
</Text> </Text>
</Group> </Group>
@@ -69,7 +69,7 @@ export function TeamRankingRow({
<Text variant="primary" font="mono" weight="bold" block size="md"> <Text variant="primary" font="mono" weight="bold" block size="md">
{rating} {rating}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Rating Rating
</Text> </Text>
</Group> </Group>
@@ -77,7 +77,7 @@ export function TeamRankingRow({
<Text variant="success" font="mono" weight="bold" block size="md"> <Text variant="success" font="mono" weight="bold" block size="md">
{wins} {wins}
</Text> </Text>
<Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold" fontSize="9px"> <Text size="xs" variant="low" block uppercase letterSpacing="widest" weight="bold">
Wins Wins
</Text> </Text>
</Group> </Group>

View File

@@ -105,15 +105,15 @@ export function TeamCard({
{/* Technical Stats Grid - Engineered Look */} {/* Technical Stats Grid - Engineered Look */}
<Grid cols={3} gap="px" style={{ backgroundColor: 'var(--ui-color-border-muted)', border: '1px solid var(--ui-color-border-muted)' }}> <Grid cols={3} gap="px" style={{ backgroundColor: 'var(--ui-color-border-muted)', border: '1px solid var(--ui-color-border-muted)' }}>
<Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}> <Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
<Text size="xs" variant="low" uppercase block marginBottom={1} mono>Rating</Text> <Text size="xs" variant="low" uppercase block mono>Rating</Text>
<Text size="md" weight="bold" mono variant="primary">{data.ratingLabel}</Text> <Text size="md" weight="bold" mono variant="primary">{data.ratingLabel}</Text>
</Stack> </Stack>
<Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}> <Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
<Text size="xs" variant="low" uppercase block marginBottom={1} mono>Wins</Text> <Text size="xs" variant="low" uppercase block mono>Wins</Text>
<Text size="md" weight="bold" mono variant="telemetry">{data.winsLabel}</Text> <Text size="md" weight="bold" mono variant="telemetry">{data.winsLabel}</Text>
</Stack> </Stack>
<Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}> <Stack padding={3} align="center" style={{ backgroundColor: 'var(--ui-color-bg-surface)' }}>
<Text size="xs" variant="low" uppercase block marginBottom={1} mono>Races</Text> <Text size="xs" variant="low" uppercase block mono>Races</Text>
<Text size="md" weight="bold" mono variant="high">{data.racesLabel}</Text> <Text size="md" weight="bold" mono variant="high">{data.racesLabel}</Text>
</Stack> </Stack>
</Grid> </Grid>

View File

@@ -50,7 +50,7 @@ export function DriverProfileTemplate({
}: DriverProfileTemplateProps) { }: DriverProfileTemplateProps) {
if (isLoading) { if (isLoading) {
return ( return (
<Container size="lg" py={12}> <Container size="lg" spacing="lg">
<Stack align="center" justify="center" gap={4}> <Stack align="center" justify="center" gap={4}>
<LoadingSpinner size={10} /> <LoadingSpinner size={10} />
<Text color="text-gray-400">Loading driver profile...</Text> <Text color="text-gray-400">Loading driver profile...</Text>
@@ -61,7 +61,7 @@ export function DriverProfileTemplate({
if (error || !viewData?.currentDriver) { if (error || !viewData?.currentDriver) {
return ( return (
<Container size="md" py={12}> <Container size="md" spacing="lg">
<Stack align="center" gap={6}> <Stack align="center" gap={6}>
<Text color="text-warning-amber">{error || 'Driver not found'}</Text> <Text color="text-warning-amber">{error || 'Driver not found'}</Text>
<Button variant="secondary" onClick={onBackClick}> <Button variant="secondary" onClick={onBackClick}>
@@ -84,7 +84,7 @@ export function DriverProfileTemplate({
] : []; ] : [];
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={6}> <Stack gap={6}>
{/* Back Navigation & Breadcrumbs */} {/* Back Navigation & Breadcrumbs */}
<Stack gap={4}> <Stack gap={4}>

View File

@@ -27,7 +27,7 @@ export function DriverRankingsTemplate({
onBackToLeaderboards, onBackToLeaderboards,
}: DriverRankingsTemplateProps): React.ReactElement { }: DriverRankingsTemplateProps): React.ReactElement {
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<PageHeader <PageHeader
title="Driver Leaderboard" title="Driver Leaderboard"
description="Full rankings of all drivers by performance metrics" description="Full rankings of all drivers by performance metrics"

View File

@@ -5,6 +5,8 @@ import { DriverCard } from '@/components/drivers/DriverCard';
import { DriverStatsHeader } from '@/components/drivers/DriverStatsHeader'; import { DriverStatsHeader } from '@/components/drivers/DriverStatsHeader';
import { DriverGrid } from '@/components/drivers/DriverGrid'; import { DriverGrid } from '@/components/drivers/DriverGrid';
import { PageHeader } from '@/ui/PageHeader'; import { PageHeader } from '@/ui/PageHeader';
import { Section } from '@/ui/Section';
import { Stack } from '@/ui/Stack';
import { Input } from '@/ui/Input'; import { Input } from '@/ui/Input';
import { Button } from '@/ui/Button'; import { Button } from '@/ui/Button';
import { Container } from '@/ui/Container'; import { Container } from '@/ui/Container';
@@ -30,54 +32,55 @@ export function DriversTemplate({
}: DriversTemplateProps) { }: DriversTemplateProps) {
return ( return (
<main> <main>
<PageHeader <Section variant="default" padding="md">
title="Drivers" <PageHeader
description="Global driver roster and statistics." title="Drivers"
action={ description="Global driver roster and statistics."
<Button icon={Users}
variant="secondary" action={
onClick={onViewLeaderboard} <Button
> variant="secondary"
Leaderboard onClick={onViewLeaderboard}
</Button> >
} Leaderboard
/> </Button>
}
<Container size="full" padding="none" py={8}>
<DriverStatsHeader
totalDrivers={viewData.totalDriversLabel}
activeDrivers={viewData.activeCountLabel}
totalRaces={viewData.totalRacesLabel}
/> />
</Container>
<Container size="full" padding="none" py={6}> <Stack gap={12}>
<Input <DriverStatsHeader
placeholder="Search drivers by name or nationality..." totalDrivers={viewData.totalDriversLabel}
value={searchQuery} activeDrivers={viewData.activeCountLabel}
onChange={(e) => onSearchChange(e.target.value)} totalRaces={viewData.totalRacesLabel}
icon={Search} />
variant="search"
/>
</Container>
{filteredDrivers.length > 0 ? ( <Input
<DriverGrid> placeholder="Search drivers by name or nationality..."
{filteredDrivers.map(driver => ( value={searchQuery}
<DriverCard onChange={(e) => onSearchChange(e.target.value)}
key={driver.id} icon={Search}
driver={driver} variant="search"
onClick={onDriverClick} />
{filteredDrivers.length > 0 ? (
<DriverGrid>
{filteredDrivers.map(driver => (
<DriverCard
key={driver.id}
driver={driver}
onClick={onDriverClick}
/>
))}
</DriverGrid>
) : (
<EmptyState
title="No drivers found"
description={`No drivers match "${searchQuery}"`}
icon={Search}
/> />
))} )}
</DriverGrid> </Stack>
) : ( </Section>
<EmptyState
title="No drivers found"
description={`No drivers match "${searchQuery}"`}
icon={Search}
/>
)}
</main> </main>
); );
} }

View File

@@ -42,31 +42,33 @@ interface HomeTemplateProps {
export function HomeTemplate({ viewData }: HomeTemplateProps) { export function HomeTemplate({ viewData }: HomeTemplateProps) {
return ( return (
<main> <main>
{/* Hero Section - Admin Focus */} <Stack gap={0}>
<Hero /> {/* Hero Section - Admin Focus */}
<Hero />
{/* Admin Pain/Solution Strip */} {/* Admin Pain/Solution Strip */}
<TelemetryStrip /> <TelemetryStrip />
{/* Core Admin Features */} {/* Core Admin Features */}
<ValuePillars /> <ValuePillars />
{/* Stewarding Workflow Preview */} {/* Stewarding Workflow Preview */}
<StewardingPreview <StewardingPreview
race={viewData.upcomingRaces[0]} race={viewData.upcomingRaces[0]}
team={viewData.teams[0]} team={viewData.teams[0]}
/> />
{/* League Identity Showcase */} {/* League Identity Showcase */}
<LeagueIdentityPreview <LeagueIdentityPreview
league={viewData.topLeagues[0]} league={viewData.topLeagues[0]}
/> />
{/* Migration Offer */} {/* Migration Offer */}
<MigrationSection /> <MigrationSection />
{/* Final CTA */} {/* Final CTA */}
<CtaSection /> <CtaSection />
</Stack>
</main> </main>
); );
} }

View File

@@ -42,71 +42,69 @@ export function LeaderboardsTemplate({
.slice(0, 5); .slice(0, 5);
return ( return (
<Section variant="default" padding="none" py={12}> <Section variant="default" padding="md">
<Container size="full" padding="lg"> <Stack gap={16}>
<Stack gap={16}> <PageHeader
<PageHeader title="Leaderboards"
title="Leaderboards" description="Global Performance Standings"
description="Global Performance Standings" icon={Activity}
icon={Activity} action={
action={ <Group gap={4}>
<Group gap={4}> <Button
<Button variant="secondary"
variant="secondary" onClick={onNavigateToDrivers}
onClick={onNavigateToDrivers} icon={<Icon icon={Trophy} size={4} />}
icon={<Icon icon={Trophy} size={4} />} >
> Drivers
Drivers </Button>
</Button> <Button
<Button variant="secondary"
variant="secondary" onClick={onNavigateToTeams}
onClick={onNavigateToTeams} icon={<Icon icon={Users} size={4} />}
icon={<Icon icon={Users} size={4} />} >
> Teams
Teams </Button>
</Button> </Group>
</Group> }
} />
/>
{/* Top 10 2026 up top */} {/* Top 10 2026 up top */}
<DriverLeaderboardPreview <DriverLeaderboardPreview
title="Top 10 Drivers 2026" title="Top 10 Drivers 2026"
subtitle="Current Season Standings" subtitle="Current Season Standings"
drivers={top10Drivers} drivers={top10Drivers}
onDriverClick={onDriverClick} onDriverClick={onDriverClick}
onNavigateToDrivers={onNavigateToDrivers} onNavigateToDrivers={onNavigateToDrivers}
/> />
<FeatureGrid columns={{ base: 1, lg: 2 }} gap={12}> <FeatureGrid columns={{ base: 1, lg: 2 }} gap={12}>
<TeamLeaderboardPreview <TeamLeaderboardPreview
teams={top5Teams.map(t => ({ teams={top5Teams.map(t => ({
...t, ...t,
logoUrl: t.logoUrl || '' logoUrl: t.logoUrl || ''
}))} }))}
onTeamClick={onTeamClick} onTeamClick={onTeamClick}
onNavigateToTeams={onNavigateToTeams} onNavigateToTeams={onNavigateToTeams}
/>
<Stack gap={12}>
<DriverLeaderboardPreview
title="Top Newcomers"
subtitle="Rising Stars (< 10 Races)"
drivers={topNewcomers}
onDriverClick={onDriverClick}
onNavigateToDrivers={onNavigateToDrivers}
/> />
<DriverLeaderboardPreview
<Stack gap={12}> title="Top All Time"
<DriverLeaderboardPreview subtitle="Most Wins"
title="Top Newcomers" drivers={topAllTime}
subtitle="Rising Stars (< 10 Races)" onDriverClick={onDriverClick}
drivers={topNewcomers} onNavigateToDrivers={onNavigateToDrivers}
onDriverClick={onDriverClick} />
onNavigateToDrivers={onNavigateToDrivers} </Stack>
/> </FeatureGrid>
<DriverLeaderboardPreview </Stack>
title="Top All Time"
subtitle="Most Wins"
drivers={topAllTime}
onDriverClick={onDriverClick}
onNavigateToDrivers={onNavigateToDrivers}
/>
</Stack>
</FeatureGrid>
</Stack>
</Container>
</Section> </Section>
); );
} }

View File

@@ -8,6 +8,8 @@ import { Input } from '@/ui/Input';
import { Button } from '@/ui/Button'; import { Button } from '@/ui/Button';
import { Group } from '@/ui/Group'; import { Group } from '@/ui/Group';
import { Container } from '@/ui/Container'; import { Container } from '@/ui/Container';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text'; import { Text } from '@/ui/Text';
import { Icon } from '@/ui/Icon'; import { Icon } from '@/ui/Icon';
import { Section } from '@/ui/Section'; import { Section } from '@/ui/Section';
@@ -71,7 +73,7 @@ export function LeaguesTemplate({
onClearFilters, onClearFilters,
}: LeaguesTemplateProps) { }: LeaguesTemplateProps) {
return ( return (
<Section variant="default" padding="lg"> <Section variant="default" padding="md">
{/* Header Section */} {/* Header Section */}
<PageHeader <PageHeader
icon={Trophy} icon={Trophy}
@@ -82,15 +84,15 @@ export function LeaguesTemplate({
onClick={onCreateLeague} onClick={onCreateLeague}
variant="primary" variant="primary"
size="lg" size="lg"
icon={<Plus size={16} />} icon={<Icon icon={Plus} size={4} />}
> >
Create League Create League
</Button> </Button>
} }
/> />
{/* Stats Overview */} <Stack gap={12}>
<Container size="full" padding="none" py={8}> {/* Stats Overview */}
<FeatureGrid columns={{ base: 1, md: 3 }} gap={4}> <FeatureGrid columns={{ base: 1, md: 3 }} gap={4}>
<MetricCard <MetricCard
label="Active Leagues" label="Active Leagues"
@@ -111,38 +113,35 @@ export function LeaguesTemplate({
intent="success" intent="success"
/> />
</FeatureGrid> </FeatureGrid>
</Container>
{/* Control Bar */} {/* Control Bar */}
<ControlBar <ControlBar
leftContent={ leftContent={
<Group gap={4} align="center"> <Group gap={4} align="center">
<Icon icon={Filter} size={4} intent="low" /> <Icon icon={Filter} size={4} intent="low" />
<SegmentedControl <SegmentedControl
options={categories.map(c => ({ options={categories.map(c => ({
id: c.id, id: c.id,
label: c.label, label: c.label,
icon: <Icon icon={c.icon} size={3} /> icon: <Icon icon={c.icon} size={3} />
}))} }))}
activeId={activeCategory} activeId={activeCategory}
onChange={(id) => onCategoryChange(id as CategoryId)} onChange={(id) => onCategoryChange(id as CategoryId)}
/> />
</Group> </Group>
} }
> >
<Input <Input
type="text" type="text"
placeholder="Search infrastructure..." placeholder="Search infrastructure..."
value={searchQuery} value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
icon={<Search size={16} />} icon={<Icon icon={Search} size={4} />}
size="sm" size="sm"
width="300px" />
/> </ControlBar>
</ControlBar>
{/* Results */} {/* Results */}
<Container size="full" padding="none" py={6}>
{filteredLeagues.length > 0 ? ( {filteredLeagues.length > 0 ? (
<FeatureGrid columns={{ base: 1, md: 2, lg: 3 }} gap={6}> <FeatureGrid columns={{ base: 1, md: 2, lg: 3 }} gap={6}>
{filteredLeagues.map((league) => ( {filteredLeagues.map((league) => (
@@ -154,23 +153,23 @@ export function LeaguesTemplate({
))} ))}
</FeatureGrid> </FeatureGrid>
) : ( ) : (
<Section variant="dark" padding="lg"> <Surface variant="dark" padding={12} rounded="xl" border>
<Group direction="col" align="center" justify="center" gap={4}> <Stack align="center" justify="center" gap={6}>
<Icon icon={Search} size={12} intent="low" /> <Icon icon={Search} size={12} intent="low" />
<Group direction="col" align="center" gap={1}> <Stack align="center" gap={2}>
<Text size="lg" weight="bold">No results found</Text> <Text size="lg" weight="bold">No results found</Text>
<Text variant="low" size="sm">Adjust filters to find matching infrastructure.</Text> <Text variant="low" size="sm">Adjust filters to find matching infrastructure.</Text>
</Group> </Stack>
<Button <Button
variant="secondary" variant="secondary"
onClick={onClearFilters} onClick={onClearFilters}
> >
Reset Filters Reset Filters
</Button> </Button>
</Group> </Stack>
</Section> </Surface>
)} )}
</Container> </Stack>
</Section> </Section>
); );
} }

View File

@@ -13,7 +13,7 @@ interface ProfileLayoutShellTemplateProps {
export function ProfileLayoutShellTemplate({ viewData, children }: ProfileLayoutShellTemplateProps) { export function ProfileLayoutShellTemplate({ viewData, children }: ProfileLayoutShellTemplateProps) {
return ( return (
<Box minHeight="screen" backgroundColor="#0C0D0F"> <Box minHeight="screen" backgroundColor="#0C0D0F">
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack direction="row" gap={8} alignItems="start"> <Stack direction="row" gap={8} alignItems="start">
<Box as="aside" width="64" flexShrink={0}> <Box as="aside" width="64" flexShrink={0}>
<ProfileSidebarTemplate viewData={viewData} /> <ProfileSidebarTemplate viewData={viewData} />

View File

@@ -117,7 +117,7 @@ export function RaceDetailTemplate({
}: RaceDetailTemplateProps) { }: RaceDetailTemplateProps) {
if (isLoading) { if (isLoading) {
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={6}> <Stack gap={6}>
<Skeleton width="8rem" height="1.5rem" /> <Skeleton width="8rem" height="1.5rem" />
<Skeleton width="100%" height="12rem" /> <Skeleton width="100%" height="12rem" />
@@ -134,7 +134,7 @@ export function RaceDetailTemplate({
if (error || !viewData || !viewData.race) { if (error || !viewData || !viewData.race) {
return ( return (
<Container size="md" py={8}> <Container size="md" spacing="md">
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl"> <Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
<Stack alignItems="center" gap={4}> <Stack alignItems="center" gap={4}>
<Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text> <Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text>
@@ -174,7 +174,7 @@ export function RaceDetailTemplate({
onBack={onBack} onBack={onBack}
/> />
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
{userResult && ( {userResult && (
<RaceUserResult <RaceUserResult

View File

@@ -46,7 +46,7 @@ export function RaceResultsTemplate({
if (isLoading) { if (isLoading) {
return ( return (
<Container size="lg" py={12}> <Container size="lg" spacing="lg">
<Stack alignItems="center"> <Stack alignItems="center">
<Text color="text-gray-400">Loading results...</Text> <Text color="text-gray-400">Loading results...</Text>
</Stack> </Stack>
@@ -56,7 +56,7 @@ export function RaceResultsTemplate({
if (error && !viewData.raceTrack) { if (error && !viewData.raceTrack) {
return ( return (
<Container size="md" py={12}> <Container size="md" spacing="lg">
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl"> <Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
<Stack alignItems="center" gap={4}> <Stack alignItems="center" gap={4}>
<Text color="text-warning-amber">{error?.message || 'Race not found'}</Text> <Text color="text-warning-amber">{error?.message || 'Race not found'}</Text>
@@ -93,7 +93,7 @@ export function RaceResultsTemplate({
onBack={onBack} onBack={onBack}
/> />
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
{importSuccess && ( {importSuccess && (
<Box p={4} bg="bg-success-green" bgOpacity={0.1} border borderColor="border-success-green" rounded> <Box p={4} bg="bg-success-green" bgOpacity={0.1} border borderColor="border-success-green" rounded>

View File

@@ -46,7 +46,7 @@ export function RaceStewardingTemplate({
if (isLoading) { if (isLoading) {
return ( return (
<Container size="lg" py={12}> <Container size="lg" spacing="lg">
<Stack alignItems="center"> <Stack alignItems="center">
<Text color="text-gray-400">Loading stewarding data...</Text> <Text color="text-gray-400">Loading stewarding data...</Text>
</Stack> </Stack>
@@ -56,7 +56,7 @@ export function RaceStewardingTemplate({
if (!viewData?.race) { if (!viewData?.race) {
return ( return (
<Container size="md" py={12}> <Container size="md" spacing="lg">
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl"> <Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
<Stack alignItems="center" gap={4}> <Stack alignItems="center" gap={4}>
<Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text> <Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text>
@@ -94,7 +94,7 @@ export function RaceStewardingTemplate({
onBack={onBack} onBack={onBack}
/> />
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
<Grid cols={12} gap={6}> <Grid cols={12} gap={6}>
<GridItem colSpan={12} lgSpan={8}> <GridItem colSpan={12} lgSpan={8}>

View File

@@ -9,7 +9,9 @@ import { NextUpRacePanel } from '@/components/races/NextUpRacePanel';
import { RacesDayGroup } from '@/components/races/RacesDayGroup'; import { RacesDayGroup } from '@/components/races/RacesDayGroup';
import { RacesEmptyState } from '@/components/races/RacesEmptyState'; import { RacesEmptyState } from '@/components/races/RacesEmptyState';
import { RaceFilterModal } from '@/components/races/RaceFilterModal'; import { RaceFilterModal } from '@/components/races/RaceFilterModal';
import { PageHeader } from '@/components/shared/PageHeader'; import { PageHeader } from '@/ui/PageHeader';
import { Stack } from '@/ui/Stack';
import { Flag } from 'lucide-react';
import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData'; import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData';
export interface RacesIndexTemplateProps { export interface RacesIndexTemplateProps {
@@ -45,22 +47,21 @@ export function RacesIndexTemplate({
const hasRaces = viewData.racesByDate.length > 0; const hasRaces = viewData.racesByDate.length > 0;
return ( return (
<Section variant="default" padding="lg"> <Section variant="default" padding="md">
<PageHeader <PageHeader
title="Races" title="Races"
subtitle="Live Sessions & Upcoming Events" description="Live Sessions & Upcoming Events"
icon={Flag}
/> />
{/* 1. Status Rail: Live sessions first */} <Stack gap={12}>
<Container size="full" padding="none" py={8}> {/* 1. Status Rail: Live sessions first */}
<RacesLiveRail <RacesLiveRail
liveRaces={viewData.liveRaces} liveRaces={viewData.liveRaces}
onRaceClick={onRaceClick} onRaceClick={onRaceClick}
/> />
</Container>
{/* 2. Command Bar: Fast filters */} {/* 2. Command Bar: Fast filters */}
<Container size="full" padding="none" py={4}>
<RacesCommandBar <RacesCommandBar
timeFilter={timeFilter} timeFilter={timeFilter}
setTimeFilter={setTimeFilter} setTimeFilter={setTimeFilter}
@@ -69,34 +70,31 @@ export function RacesIndexTemplate({
leagues={viewData.leagues} leagues={viewData.leagues}
onShowMoreFilters={() => setShowFilterModal(true)} onShowMoreFilters={() => setShowFilterModal(true)}
/> />
</Container>
{/* 3. Next Up: High signal panel */} {/* 3. Next Up: High signal panel */}
{timeFilter === 'upcoming' && viewData.nextUpRace && ( {timeFilter === 'upcoming' && viewData.nextUpRace && (
<Container size="full" padding="none" py={8}>
<NextUpRacePanel <NextUpRacePanel
race={viewData.nextUpRace} race={viewData.nextUpRace}
onRaceClick={onRaceClick} onRaceClick={onRaceClick}
/> />
</Container> )}
)}
{/* 4. Browse by Day: Grouped schedule */} {/* 4. Browse by Day: Grouped schedule */}
{hasRaces ? ( {hasRaces ? (
<Container size="full" padding="none" py={8}> <Stack gap={8}>
{viewData.racesByDate.map((group) => ( {viewData.racesByDate.map((group) => (
<Container key={group.dateKey} size="full" padding="none" py={4}>
<RacesDayGroup <RacesDayGroup
key={group.dateKey}
dateLabel={group.dateLabel} dateLabel={group.dateLabel}
races={group.races} races={group.races}
onRaceClick={onRaceClick} onRaceClick={onRaceClick}
/> />
</Container> ))}
))} </Stack>
</Container> ) : (
) : ( <RacesEmptyState />
<RacesEmptyState /> )}
)} </Stack>
<RaceFilterModal <RaceFilterModal
isOpen={showFilterModal} isOpen={showFilterModal}

View File

@@ -76,7 +76,7 @@ export function SponsorBillingTemplate({
})); }));
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
<SponsorDashboardHeader <SponsorDashboardHeader
sponsorName="Sponsor" sponsorName="Sponsor"

View File

@@ -93,7 +93,7 @@ export function SponsorCampaignsTemplate({
]; ];
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
<SponsorDashboardHeader <SponsorDashboardHeader
sponsorName="Sponsor" sponsorName="Sponsor"

View File

@@ -85,7 +85,7 @@ export function SponsorDashboardTemplate({ viewData }: SponsorDashboardTemplateP
})); }));
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
{/* Header */} {/* Header */}
<SponsorDashboardHeader <SponsorDashboardHeader

View File

@@ -74,7 +74,7 @@ export function SponsorLeaguesTemplate({
const stats = viewData.stats; const stats = viewData.stats;
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<Stack gap={8}> <Stack gap={8}>
{/* Breadcrumb */} {/* Breadcrumb */}
<Box> <Box>

View File

@@ -82,7 +82,7 @@ export function SponsorSettingsTemplate({
saving, saving,
}: SponsorSettingsTemplateProps) { }: SponsorSettingsTemplateProps) {
return ( return (
<Container size="md" py={8}> <Container size="md" spacing="md">
<Stack gap={8}> <Stack gap={8}>
<SponsorDashboardHeader <SponsorDashboardHeader
sponsorName={profile.companyName} sponsorName={profile.companyName}
@@ -187,7 +187,7 @@ export function SponsorSettingsTemplate({
{/* Danger Zone */} {/* Danger Zone */}
<Card border borderColor="border-racing-red/30"> <Card border borderColor="border-racing-red/30">
<Stack gap={6}> <Stack gap={6}>
<Heading level={3} color="text-racing-red" icon={<Icon icon={AlertCircle} size={5} color="text-racing-red" />}> <Heading level={3} intent="critical" icon={<Icon icon={AlertCircle} size={5} />}>
Danger Zone Danger Zone
</Heading> </Heading>
<Box display="flex" alignItems="center" justifyContent="between"> <Box display="flex" alignItems="center" justifyContent="between">

View File

@@ -26,7 +26,7 @@ export function TeamRankingsTemplate({
onBackToLeaderboards, onBackToLeaderboards,
}: TeamRankingsTemplateProps): React.ReactElement { }: TeamRankingsTemplateProps): React.ReactElement {
return ( return (
<Container size="lg" py={8}> <Container size="lg" spacing="md">
<PageHeader <PageHeader
title="Team Leaderboard" title="Team Leaderboard"
description="Global rankings of all teams based on performance and consistency" description="Global rankings of all teams based on performance and consistency"

View File

@@ -8,9 +8,12 @@ import { TeamsDirectoryHeader } from '@/components/teams/TeamsDirectoryHeader';
import { TeamGrid } from '@/components/teams/TeamGrid'; import { TeamGrid } from '@/components/teams/TeamGrid';
import { TeamCard } from '@/components/teams/TeamCard'; import { TeamCard } from '@/components/teams/TeamCard';
import { TeamSearchBar } from '@/components/teams/TeamSearchBar'; import { TeamSearchBar } from '@/components/teams/TeamSearchBar';
import { PageHeader } from '@/ui/PageHeader';
import { Button } from '@/ui/Button';
import { EmptyState } from '@/ui/EmptyState'; import { EmptyState } from '@/ui/EmptyState';
import { Container } from '@/ui/Container'; import { Container } from '@/ui/Container';
import { Section } from '@/ui/Section'; import { Section } from '@/ui/Section';
import { Stack } from '@/ui/Stack';
import { Carousel } from '@/components/shared/Carousel'; import { Carousel } from '@/components/shared/Carousel';
interface TeamsTemplateProps extends TemplateProps<TeamsViewData> { interface TeamsTemplateProps extends TemplateProps<TeamsViewData> {
@@ -63,21 +66,32 @@ export function TeamsTemplate({
}, [teams, filteredTeams, searchQuery]); }, [teams, filteredTeams, searchQuery]);
return ( return (
<Section variant="default" padding="lg"> <Section variant="default" padding="md">
<TeamsDirectoryHeader onCreateTeam={onCreateTeam} /> <PageHeader
title="Teams"
<Container size="full" padding="none" py={12}> description="Professional racing organizations and community teams."
icon={Users}
action={
<Button
variant="secondary"
onClick={onViewFullLeaderboard}
>
Leaderboard
</Button>
}
/>
<Stack gap={12}>
<TeamSearchBar <TeamSearchBar
searchQuery={searchQuery} searchQuery={searchQuery}
onSearchChange={onSearchChange} onSearchChange={onSearchChange}
/> />
</Container>
{clusters.length > 0 ? ( {clusters.length > 0 ? (
<Container size="full" padding="none"> <Stack gap={12}>
{clusters.map((cluster, index) => ( {clusters.map((cluster) => (
<Container key={cluster.title} size="full" padding="none" py={index === 0 ? 0 : 10}>
<Carousel <Carousel
key={cluster.title}
title={cluster.title} title={cluster.title}
count={cluster.teams.length} count={cluster.teams.length}
> >
@@ -89,21 +103,21 @@ export function TeamsTemplate({
/> />
))} ))}
</Carousel> </Carousel>
</Container> ))}
))} </Stack>
</Container> ) : (
) : ( <EmptyState
<EmptyState icon={Users}
icon={Users} title={searchQuery ? "No matching teams" : "No teams yet"}
title={searchQuery ? "No matching teams" : "No teams yet"} description={searchQuery ? "Try adjusting your search filters" : "Get started by creating your first racing team"}
description={searchQuery ? "Try adjusting your search filters" : "Get started by creating your first racing team"} action={{
action={{ label: 'Create Team',
label: 'Create Team', onClick: onCreateTeam,
onClick: onCreateTeam, variant: 'primary'
variant: 'primary' }}
}} />
/> )}
)} </Stack>
</Section> </Section>
); );
} }

View File

@@ -7,37 +7,60 @@ export interface CardProps {
variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | 'precision' | 'bordered' | 'elevated' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary'; variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | 'precision' | 'bordered' | 'elevated' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary';
title?: string | ReactNode; title?: string | ReactNode;
footer?: ReactNode; footer?: ReactNode;
padding?: Spacing | number | any; padding?: 'none' | 'sm' | 'md' | 'lg' | number;
className?: string;
style?: React.CSSProperties;
bg?: string;
p?: number;
onClick?: () => void; onClick?: () => void;
responsiveColSpan?: { lg: number }; fullHeight?: boolean;
overflow?: string; /** @deprecated Use semantic props instead. */
rounded?: string | boolean; className?: string;
borderLeft?: boolean; /** @deprecated Use semantic props instead. */
borderColor?: string; style?: React.CSSProperties;
center?: boolean; /** @deprecated Use semantic props instead. */
transition?: string | boolean; p?: number;
hoverBorderColor?: string; /** @deprecated Use semantic props instead. */
border?: boolean;
position?: string;
mb?: number;
display?: string;
alignItems?: string;
gap?: number;
py?: number; py?: number;
/** @deprecated Use semantic props instead. */
mb?: number;
/** @deprecated Use semantic props instead. */
bg?: string;
/** @deprecated Use semantic props instead. */
borderColor?: string;
/** @deprecated Use semantic props instead. */
hoverBorderColor?: string;
/** @deprecated Use semantic props instead. */
border?: boolean;
/** @deprecated Use semantic props instead. */
position?: string;
/** @deprecated Use semantic props instead. */
overflow?: string;
/** @deprecated Use semantic props instead. */
center?: boolean;
/** @deprecated Use semantic props instead. */
rounded?: string | boolean;
/** @deprecated Use semantic props instead. */
transition?: string | boolean;
/** @deprecated Use semantic props instead. */
group?: boolean;
/** @deprecated Use semantic props instead. */
responsiveColSpan?: { lg: number };
/** @deprecated Use semantic props instead. */
backgroundColor?: string; backgroundColor?: string;
group?: boolean | any; /** @deprecated Use semantic props instead. */
w?: string | any; w?: string | number;
justifyContent?: string | any; /** @deprecated Use semantic props instead. */
fullHeight?: boolean | any; display?: string;
/** @deprecated Use semantic props instead. */
alignItems?: string;
/** @deprecated Use semantic props instead. */
gap?: number;
/** @deprecated Use semantic props instead. */
borderLeft?: boolean;
/** @deprecated Use semantic props instead. */
justifyContent?: string;
} }
/** /**
* Card - Redesigned for "Modern Precision" theme. * Card - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage. * Enforces semantic props.
*/ */
export const Card = forwardRef<HTMLDivElement, CardProps>(({ export const Card = forwardRef<HTMLDivElement, CardProps>(({
children, children,
@@ -45,28 +68,31 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({
title, title,
footer, footer,
padding = 'md', padding = 'md',
className,
style,
bg,
p,
onClick, onClick,
responsiveColSpan, fullHeight,
overflow, className,
rounded, style: styleProp,
borderLeft, p,
py,
mb,
bg,
borderColor, borderColor,
center,
transition,
hoverBorderColor, hoverBorderColor,
border, border,
position, position,
mb, overflow,
center,
rounded,
transition,
group,
responsiveColSpan,
backgroundColor,
w,
display, display,
alignItems, alignItems,
gap, gap,
py, borderLeft,
backgroundColor, justifyContent,
fullHeight,
}, ref) => { }, ref) => {
const variantClasses = { const variantClasses = {
default: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-sm', default: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-sm',
@@ -83,61 +109,69 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({
'rarity-legendary': 'bg-orange-500/10 border-orange-500/50', 'rarity-legendary': 'bg-orange-500/10 border-orange-500/50',
}; };
const paddingClasses = { const paddingClasses: Record<string, string> = {
none: 'p-0', none: 'p-0',
sm: 'p-2', sm: 'p-2',
md: 'p-4', md: 'p-4',
lg: 'p-8', lg: 'p-8',
}; };
const getPaddingClass = (pad: any) => { const classes = [
if (typeof pad === 'string') return `p-${pad}`; 'border',
return ''; // Handled in style variantClasses[variant as keyof typeof variantClasses] || variantClasses.default,
}; typeof padding === 'string' ? (paddingClasses[padding] || paddingClasses.md) : '',
onClick ? 'cursor-pointer hover:border-[var(--ui-color-border-bright)] transition-all duration-200' : '',
fullHeight ? 'h-full flex flex-col' : '',
group ? 'group' : '',
rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none'),
className,
].filter(Boolean).join(' ');
const combinedStyle: React.CSSProperties = { const style: React.CSSProperties = {
...style, ...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {}),
...(p !== undefined ? { padding: `${p * 0.25}rem` } : {}),
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
...(mb !== undefined ? { marginBottom: `${mb * 0.25}rem` } : {}),
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}), ...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
...(backgroundColor ? { backgroundColor } : {}), ...(backgroundColor ? { backgroundColor } : {}),
...(p !== undefined ? { padding: typeof p === 'number' ? `${p * 0.25}rem` : undefined } : {}),
...(py !== undefined ? { paddingTop: typeof py === 'number' ? `${py * 0.25}rem` : undefined, paddingBottom: typeof py === 'number' ? `${py * 0.25}rem` : undefined } : {}),
...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {}),
...(responsiveColSpan?.lg ? { gridColumn: `span ${responsiveColSpan.lg} / span ${responsiveColSpan.lg}` } : {}),
...(overflow ? { overflow } : {}),
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}), ...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}),
...(borderLeft ? { borderLeft: `4px solid ${borderColor || 'var(--ui-color-intent-primary)'}` } : {}), ...(hoverBorderColor ? { '--hover-border-color': hoverBorderColor } as any : {}),
...(border === false ? { border: 'none' } : {}),
...(position ? { position: position as any } : {}),
...(overflow ? { overflow } : {}),
...(center ? { display: 'flex', alignItems: 'center', justifyContent: 'center' } : {}), ...(center ? { display: 'flex', alignItems: 'center', justifyContent: 'center' } : {}),
...(typeof transition === 'string' ? { transition } : {}), ...(typeof transition === 'string' ? { transition } : {}),
...(position ? { position: position as any } : {}), ...(responsiveColSpan?.lg ? { gridColumn: `span ${responsiveColSpan.lg} / span ${responsiveColSpan.lg}` } : {}),
...(mb !== undefined ? { marginBottom: `${mb * 0.25}rem` } : {}), ...(w !== undefined ? { width: w } : {}),
...(display ? { display } : {}), ...(display ? { display } : {}),
...(alignItems ? { alignItems } : {}), ...(alignItems ? { alignItems } : {}),
...(justifyContent ? { justifyContent } : {}),
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}), ...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
...(border === false ? { border: 'none' } : {}), ...(borderLeft ? { borderLeft: `4px solid var(--ui-color-intent-primary)` } : {}),
...(fullHeight ? { height: '100%' } : {}), ...(styleProp || {}),
}; };
return ( return (
<div <div
ref={ref} ref={ref}
className={`border ${variantClasses[variant as keyof typeof variantClasses] || variantClasses.default} ${getPaddingClass(padding)} ${onClick ? 'cursor-pointer hover:border-[var(--ui-color-border-bright)]' : ''} ${transition === true ? 'transition-all duration-200' : ''} ${rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none')} ${className || ''}`} className={classes}
style={combinedStyle}
onClick={onClick} onClick={onClick}
style={Object.keys(style).length > 0 ? style : undefined}
> >
{title && ( {title && (
<div className={`border-b border-[var(--ui-color-border-muted)] ${getPaddingClass(padding)}`}> <div className={`border-b border-[var(--ui-color-border-muted)] ${typeof padding === 'string' ? (paddingClasses[padding] || paddingClasses.md) : ''}`}>
{typeof title === 'string' ? ( {typeof title === 'string' ? (
<Heading level={5} weight="bold" uppercase>{title}</Heading> <Heading level={5} weight="bold" uppercase>{title}</Heading>
) : title} ) : title}
</div> </div>
)} )}
<div className={`${typeof padding === 'number' || p !== undefined ? '' : getPaddingClass(padding)} ${fullHeight ? 'h-full flex flex-col' : ''}`}> <div className={fullHeight ? 'h-full flex flex-col' : ''}>
{children} {children}
</div> </div>
{footer && ( {footer && (
<div className={`border-t border-[var(--ui-color-border-muted)] bg-white/[0.02] ${getPaddingClass(padding)}`}> <div className={`border-t border-[var(--ui-color-border-muted)] bg-white/[0.02] ${typeof padding === 'string' ? (paddingClasses[padding] || paddingClasses.md) : ''}`}>
{footer} {footer}
</div> </div>
)} )}

View File

@@ -2,28 +2,27 @@ import { ReactNode } from 'react';
export interface ContainerProps { export interface ContainerProps {
children: ReactNode; children: ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | any; size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
padding?: 'none' | 'sm' | 'md' | 'lg' | any; padding?: 'none' | 'sm' | 'md' | 'lg';
py?: number | any; spacing?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky' | any; position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
zIndex?: number | any; zIndex?: number;
paddingX?: number | any; /** @deprecated Use semantic props instead. */
fullWidth?: boolean | any; py?: number;
} }
/** /**
* Container - Redesigned for "Modern Precision" theme. * Container - Redesigned for "Modern Precision" theme.
* Includes compatibility props to prevent app-wide breakage. * Enforces semantic props.
*/ */
export const Container = ({ export const Container = ({
children, children,
size = 'lg', size = 'lg',
padding = 'md', padding = 'md',
py, spacing = 'none',
position, position,
zIndex, zIndex,
paddingX, py,
fullWidth,
}: ContainerProps) => { }: ContainerProps) => {
const sizeMap = { const sizeMap = {
sm: 'max-w-[40rem]', sm: 'max-w-[40rem]',
@@ -40,17 +39,23 @@ export const Container = ({
lg: 'px-8', lg: 'px-8',
}; };
const spacingMap = {
none: 'py-0',
sm: 'py-4',
md: 'py-8',
lg: 'py-12',
xl: 'py-16',
};
const combinedStyle: React.CSSProperties = { const combinedStyle: React.CSSProperties = {
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
...(paddingX !== undefined ? { paddingLeft: `${paddingX * 0.25}rem`, paddingRight: `${paddingX * 0.25}rem` } : {}),
...(position ? { position } : {}), ...(position ? { position } : {}),
...(zIndex !== undefined ? { zIndex } : {}), ...(zIndex !== undefined ? { zIndex } : {}),
...(fullWidth ? { width: '100%' } : {}), ...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
}; };
return ( return (
<div <div
className={`mx-auto w-full ${sizeMap[size as keyof typeof sizeMap] || sizeMap.lg} ${paddingMap[padding as keyof typeof paddingMap] || paddingMap.md}`} className={`mx-auto w-full ${sizeMap[size]} ${paddingMap[padding]} ${spacingMap[spacing]}`}
style={combinedStyle} style={combinedStyle}
> >
{children} {children}

View File

@@ -33,7 +33,7 @@ export const DurationField = ({
return ( return (
<Box> <Box>
<Text as="label" size="xs" weight="bold" variant="low" block marginBottom={1.5}> <Text as="label" size="xs" weight="bold" variant="low" block>
{label} {label}
</Text> </Text>
<Box display="flex" alignItems="center" gap={4}> <Box display="flex" alignItems="center" gap={4}>

View File

@@ -6,6 +6,8 @@ import { Text } from './Text';
import { LucideIcon } from 'lucide-react'; import { LucideIcon } from 'lucide-react';
import { IconContainer } from './IconContainer'; import { IconContainer } from './IconContainer';
import { Icon } from './Icon';
interface FeatureItemProps { interface FeatureItemProps {
title: string; title: string;
description: string; description: string;
@@ -16,12 +18,12 @@ interface FeatureItemProps {
* FeatureItem - A semantic UI component for a single feature/pillar. * FeatureItem - A semantic UI component for a single feature/pillar.
* Allowed to use Stack primitive. * Allowed to use Stack primitive.
*/ */
export function FeatureItem({ title, description, icon: Icon }: FeatureItemProps) { export function FeatureItem({ title, description, icon }: FeatureItemProps) {
return ( return (
<Panel variant="default" padding="lg"> <Panel variant="default" padding="lg">
<Stack direction="col" gap={6}> <Stack direction="col" gap={6}>
<IconContainer> <IconContainer>
<Icon className="w-5 h-5 text-[var(--ui-color-intent-primary)]" /> <Icon icon={icon} size={5} intent="primary" />
</IconContainer> </IconContainer>
<Stack direction="col" gap={4}> <Stack direction="col" gap={4}>
<Heading level={3} weight="bold" uppercase> <Heading level={3} weight="bold" uppercase>

View File

@@ -16,7 +16,7 @@ export const FormSection = ({
return ( return (
<Box display="flex" flexDirection="col" gap={6}> <Box display="flex" flexDirection="col" gap={6}>
<Box borderBottom paddingBottom={4}> <Box borderBottom paddingBottom={4}>
<Text weight="bold" variant="high" size="lg" marginBottom={1} block> <Text weight="bold" variant="high" size="lg" block>
{title} {title}
</Text> </Text>
{description && ( {description && (

View File

@@ -6,28 +6,41 @@ export interface HeadingProps {
weight?: 'normal' | 'medium' | 'semibold' | 'bold'; weight?: 'normal' | 'medium' | 'semibold' | 'bold';
align?: 'left' | 'center' | 'right'; align?: 'left' | 'center' | 'right';
uppercase?: boolean; uppercase?: boolean;
intent?: 'primary' | 'telemetry' | 'warning' | 'critical' | 'default' | any; intent?: 'primary' | 'telemetry' | 'warning' | 'critical' | 'default';
className?: string;
style?: CSSProperties;
mb?: number | any;
marginBottom?: number | any;
mt?: number | any;
marginTop?: number | any;
color?: string;
fontSize?: string | { base: string; sm?: string; md: string; lg?: string; xl?: string };
letterSpacing?: string;
truncate?: boolean; truncate?: boolean;
size?: string;
icon?: ReactNode; icon?: ReactNode;
id?: string; id?: string;
lineHeight?: string | any; /** @deprecated Use semantic props instead. */
groupHoverColor?: string | any; className?: string;
transition?: boolean | any; /** @deprecated Use semantic props instead. */
style?: CSSProperties;
/** @deprecated Use semantic props instead. */
mb?: number | string;
/** @deprecated Use semantic props instead. */
marginBottom?: number | string;
/** @deprecated Use semantic props instead. */
mt?: number | string;
/** @deprecated Use semantic props instead. */
marginTop?: number | string;
/** @deprecated Use semantic props instead. */
color?: string;
/** @deprecated Use semantic props instead. */
fontSize?: string | { base: string; sm?: string; md: string; lg?: string; xl?: string };
/** @deprecated Use semantic props instead. */
letterSpacing?: string;
/** @deprecated Use semantic props instead. */
size?: string;
/** @deprecated Use semantic props instead. */
groupHoverColor?: string;
/** @deprecated Use semantic props instead. */
lineHeight?: string | number;
/** @deprecated Use semantic props instead. */
transition?: boolean;
} }
/** /**
* Heading - Redesigned for "Modern Precision" theme. * Heading - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage. * Enforces semantic props.
*/ */
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
children, children,
@@ -36,8 +49,11 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
align = 'left', align = 'left',
uppercase = false, uppercase = false,
intent = 'default', intent = 'default',
truncate,
icon,
id,
className, className,
style, style: styleProp,
mb, mb,
marginBottom, marginBottom,
mt, mt,
@@ -45,9 +61,10 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
color, color,
fontSize, fontSize,
letterSpacing, letterSpacing,
truncate,
size, size,
icon, groupHoverColor,
lineHeight,
transition,
}, ref) => { }, ref) => {
const Tag = `h${level}` as const; const Tag = `h${level}` as const;
@@ -76,8 +93,7 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
}; };
const getResponsiveFontSize = (fs: HeadingProps['fontSize']) => { const getResponsiveFontSize = (fs: HeadingProps['fontSize']) => {
if (!fs) return ''; if (!fs || typeof fs === 'string') return '';
if (typeof fs === 'string') return ''; // Handled in style
const classes = []; const classes = [];
if (fs.base) classes.push(`text-${fs.base}`); if (fs.base) classes.push(`text-${fs.base}`);
if (fs.sm) classes.push(`sm:text-${fs.sm}`); if (fs.sm) classes.push(`sm:text-${fs.sm}`);
@@ -94,22 +110,25 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
align === 'center' ? 'text-center' : (align === 'right' ? 'text-right' : 'text-left'), align === 'center' ? 'text-center' : (align === 'right' ? 'text-right' : 'text-left'),
uppercase ? 'uppercase tracking-widest' : '', uppercase ? 'uppercase tracking-widest' : '',
truncate ? 'truncate' : '', truncate ? 'truncate' : '',
transition ? 'transition-all duration-200' : '',
color?.startsWith('text-') ? color : '',
className, className,
].join(' '); ].join(' ');
const combinedStyle: React.CSSProperties = { const combinedStyle: React.CSSProperties = {
...style,
...(mb !== undefined ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}), ...(mb !== undefined ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}),
...(marginBottom !== undefined ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}), ...(marginBottom !== undefined ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}),
...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}), ...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(marginTop !== undefined ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}), ...(marginTop !== undefined ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}),
...(color ? { color } : {}), ...(color && !color.startsWith('text-') ? { color } : {}),
...(letterSpacing ? { letterSpacing } : {}), ...(letterSpacing ? { letterSpacing } : {}),
...(typeof fontSize === 'string' ? { fontSize } : {}), ...(typeof fontSize === 'string' ? { fontSize } : {}),
...(lineHeight ? { lineHeight } : {}),
...(styleProp || {}),
}; };
return ( return (
<Tag ref={ref} className={classes} style={combinedStyle}> <Tag ref={ref} className={classes} style={Object.keys(combinedStyle).length > 0 ? combinedStyle : undefined} id={id}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{icon} {icon}
{children} {children}

View File

@@ -22,10 +22,10 @@ export const InfoItem = ({
<Icon icon={icon} size={4} intent={intent as any} /> <Icon icon={icon} size={4} intent={intent as any} />
</Box> </Box>
<Box> <Box>
<Text size="xs" variant="low" block marginBottom={0.5} style={{ fontSize: '10px' }}> <Text size="xs" variant="low" block style={{ fontSize: '10px' }}>
{label} {label}
</Text> </Text>
<Text size="xs" weight="medium" variant="high" block className="truncate"> <Text size="xs" weight="medium" variant="high" block truncate>
{value} {value}
</Text> </Text>
</Box> </Box>

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Section } from './Section'; import { Section } from './Section';
import { Container } from './Container'; import { Container } from './Container';
import { Box } from './Box'; import { Stack } from './Stack';
import { Glow } from './Glow'; import { Glow } from './Glow';
import { Heading } from './Heading'; import { Heading } from './Heading';
import { Text } from './Text'; import { Text } from './Text';
@@ -37,7 +37,7 @@ export function LandingHero({
<Glow color="primary" size="xl" position="top-right" opacity={0.1} /> <Glow color="primary" size="xl" position="top-right" opacity={0.1} />
<Container> <Container>
<Box display="flex" flexDirection="col" gap={8} maxWidth="3xl"> <Stack gap={8} maxWidth="3xl">
<Text size="xs" weight="bold" uppercase variant="primary"> <Text size="xs" weight="bold" uppercase variant="primary">
{subtitle} {subtitle}
</Text> </Text>
@@ -68,7 +68,7 @@ export function LandingHero({
{secondaryAction.label} {secondaryAction.label}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</Box> </Stack>
</Container> </Container>
</Section> </Section>
); );

View File

@@ -24,7 +24,7 @@ export const LandingItem = ({
<Icon icon={icon} size={6} intent={intent} /> <Icon icon={icon} size={6} intent={intent} />
</Box> </Box>
<Box> <Box>
<Text weight="bold" variant="high" size="lg" marginBottom={2} block> <Text weight="bold" variant="high" size="lg" block>
{title} {title}
</Text> </Text>
<Text variant="low" leading="relaxed"> <Text variant="low" leading="relaxed">

View File

@@ -21,7 +21,7 @@ export const LeagueCard = ({ children, onClick, coverUrl, logo, badges }: League
<Card <Card
variant="precision" variant="precision"
onClick={onClick} onClick={onClick}
style={{ height: '100%', display: 'flex', flexDirection: 'column' }} fullHeight
padding="none" padding="none"
> >
<Box height="10rem" position="relative" overflow="hidden"> <Box height="10rem" position="relative" overflow="hidden">

View File

@@ -11,7 +11,7 @@ export interface NotificationStatProps {
export const NotificationStat = ({ label, value, intent = 'low' }: NotificationStatProps) => ( export const NotificationStat = ({ label, value, intent = 'low' }: NotificationStatProps) => (
<Box bg="var(--ui-color-bg-base)" padding={4} style={{ border: '1px solid var(--ui-color-border-default)', borderRadius: 'var(--ui-radius-sm)' }}> <Box bg="var(--ui-color-bg-base)" padding={4} style={{ border: '1px solid var(--ui-color-border-default)', borderRadius: 'var(--ui-radius-sm)' }}>
<Text size="xs" variant="low" weight="bold" uppercase block marginBottom={1}>{label}</Text> <Text size="xs" variant="low" weight="bold" uppercase block>{label}</Text>
<Text size="2xl" weight="bold" variant={intent} block>{value}</Text> <Text size="2xl" weight="bold" variant={intent} block>{value}</Text>
</Box> </Box>
); );
@@ -27,7 +27,7 @@ export const NotificationDeadline = ({ label, deadline, icon }: NotificationDead
<Icon icon={icon} size={5} intent="warning" /> <Icon icon={icon} size={5} intent="warning" />
<Box> <Box>
<Text size="sm" weight="bold" variant="warning" block uppercase>{label}</Text> <Text size="sm" weight="bold" variant="warning" block uppercase>{label}</Text>
<Text size="xs" variant="low" block marginTop={0.5}>{deadline}</Text> <Text size="xs" variant="low" block>{deadline}</Text>
</Box> </Box>
</Box> </Box>
); );

View File

@@ -31,26 +31,26 @@ export function PageHeader({
justifyContent="space-between" justifyContent="space-between"
gap={6} gap={6}
borderBottom="1px solid var(--ui-color-border-muted)" borderBottom="1px solid var(--ui-color-border-muted)"
paddingBottom={8} paddingBottom={6}
> >
<Box> <Box>
<Box display="flex" alignItems="center" gap={3} marginBottom={2}> <Stack direction="row" align="center" gap={4} marginBottom={2}>
{icon ? ( {icon ? (
<Icon icon={icon} size={8} intent="primary" /> <Icon icon={icon} size={8} intent="primary" />
) : ( ) : (
<Box width={1} height={8} backgroundColor="var(--ui-color-intent-primary)" /> <Box width={1} height={8} backgroundColor="var(--ui-color-intent-primary)" />
)} )}
<Heading level={1} weight="bold" uppercase>{title}</Heading> <Heading level={1} weight="bold" uppercase letterSpacing="tight">{title}</Heading>
</Box> </Stack>
{description && ( {description && (
<Text variant="low" size="lg" uppercase mono letterSpacing="0.2em"> <Text variant="low" size="lg" uppercase mono letterSpacing="widest">
{description} {description}
</Text> </Text>
)} )}
</Box> </Box>
{action && ( {action && (
<Box display="flex" alignItems="center"> <Box display="flex" alignItems="center" paddingBottom={1}>
{action} {action}
</Box> </Box>
)} )}

View File

@@ -16,7 +16,7 @@ export const ProfileCard = ({ identity, stats, actions, variant = 'default', onC
variant={variant} variant={variant}
padding="md" padding="md"
onClick={onClick} onClick={onClick}
className="h-full flex flex-col gap-6 transition-all duration-200 hover:border-[var(--ui-color-border-bright)]" fullHeight
> >
<Box display="flex" justifyContent="between" alignItems="start" gap={4}> <Box display="flex" justifyContent="between" alignItems="start" gap={4}>
<Box flex={1} minWidth="0"> <Box flex={1} minWidth="0">

View File

@@ -4,13 +4,12 @@ import { Box } from './Box';
export interface SectionProps { export interface SectionProps {
children: ReactNode; children: ReactNode;
variant?: 'default' | 'dark' | 'muted'; variant?: 'default' | 'dark' | 'muted';
padding?: 'none' | 'sm' | 'md' | 'lg'; padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
id?: string; id?: string;
minHeight?: string; minHeight?: string;
py?: number;
fullWidth?: boolean; fullWidth?: boolean;
maxWidth?: string; maxWidth?: string;
/** @deprecated DO NOT USE. Use semantic props instead. */ /** @deprecated Use semantic props instead. */
className?: string; className?: string;
} }
@@ -20,10 +19,9 @@ export const Section = ({
padding = 'md', padding = 'md',
id, id,
minHeight, minHeight,
py,
className,
fullWidth = false, fullWidth = false,
maxWidth = '80rem' maxWidth = '80rem',
className,
}: SectionProps) => { }: SectionProps) => {
const variantClasses = { const variantClasses = {
default: 'bg-[var(--ui-color-bg-base)]', default: 'bg-[var(--ui-color-bg-base)]',
@@ -36,18 +34,18 @@ export const Section = ({
sm: 'py-8', sm: 'py-8',
md: 'py-16', md: 'py-16',
lg: 'py-24', lg: 'py-24',
xl: 'py-32',
}; };
const classes = [ const classes = [
variantClasses[variant], variantClasses[variant],
py !== undefined ? '' : paddingClasses[padding], paddingClasses[padding],
className, className,
].join(' '); ].join(' ');
return ( return (
<section id={id} className={classes} style={{ <section id={id} className={classes} style={{
...(minHeight ? { minHeight } : {}), ...(minHeight ? { minHeight } : {}),
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {})
}}> }}>
{fullWidth ? ( {fullWidth ? (
children children

View File

@@ -16,7 +16,7 @@ export const StatItem = ({
}: StatItemProps) => { }: StatItemProps) => {
return ( return (
<Box textAlign={align}> <Box textAlign={align}>
<Text size="xs" variant="low" block marginBottom={0.5}>{label}</Text> <Text size="xs" variant="low" block>{label}</Text>
<Text size="sm" weight="semibold" variant={intent}>{value}</Text> <Text size="sm" weight="semibold" variant={intent}>{value}</Text>
</Box> </Box>
); );

View File

@@ -5,69 +5,106 @@ export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
export interface TextProps { export interface TextProps {
children: ReactNode; children: ReactNode;
variant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'inherit' | any; variant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'inherit';
size?: TextSize | { base: TextSize; sm?: TextSize; md?: TextSize; lg?: TextSize; xl?: TextSize } | any; size?: TextSize | { base: TextSize; sm?: TextSize; md?: TextSize; lg?: TextSize; xl?: TextSize };
weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | any; weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
as?: ElementType; as?: ElementType;
align?: 'left' | 'center' | 'right' | any; align?: 'left' | 'center' | 'right';
mono?: boolean; mono?: boolean;
font?: 'sans' | 'mono';
uppercase?: boolean; uppercase?: boolean;
leading?: 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose' | any; leading?: 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose';
block?: boolean; block?: boolean;
truncate?: boolean; truncate?: boolean;
mt?: number | any;
mb?: number | any;
ml?: number | any;
mr?: number | any;
marginTop?: number | any;
marginBottom?: number | any;
font?: 'sans' | 'mono' | string;
color?: string;
letterSpacing?: string;
lineHeight?: string | number;
flexGrow?: number;
flexShrink?: number;
lineClamp?: number;
display?: string | ResponsiveValue<string | any>;
opacity?: number;
maxWidth?: string | number; maxWidth?: string | number;
maxHeight?: string | number; lineClamp?: number;
overflow?: string; letterSpacing?: 'tight' | 'normal' | 'wide' | 'widest' | string;
whiteSpace?: 'normal' | 'nowrap' | 'pre' | 'pre-line' | 'pre-wrap'; id?: string;
mx?: string | number; /** @deprecated Use semantic props (variant, intent) instead. */
pl?: number; color?: string;
px?: number; /** @deprecated Use semantic props instead. */
py?: number;
paddingX?: number;
paddingY?: number;
textAlign?: string;
groupHoverTextColor?: string;
transition?: boolean;
borderLeft?: boolean;
borderStyle?: string;
borderColor?: string;
ariaLabel?: string;
hoverVariant?: string;
fontSize?: string | any;
italic?: boolean;
animate?: string;
capitalize?: boolean;
alignItems?: string;
gap?: number;
cursor?: string;
width?: string | number;
height?: string | number;
htmlFor?: string;
transform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | string;
/** @deprecated DO NOT USE. Use semantic props instead. */
className?: string; className?: string;
/** @deprecated DO NOT USE. Use semantic props instead. */ /** @deprecated Use semantic props instead. */
style?: CSSProperties; style?: CSSProperties;
/** @deprecated Use semantic props instead. */
marginBottom?: number | string | ResponsiveValue<number | string>;
/** @deprecated Use semantic props instead. */
marginTop?: number | string | ResponsiveValue<number | string>;
/** @deprecated Use semantic props instead. */
mb?: number | string | ResponsiveValue<number | string>;
/** @deprecated Use semantic props instead. */
mt?: number | string | ResponsiveValue<number | string>;
/** @deprecated Use semantic props instead. */
ml?: number | string;
/** @deprecated Use semantic props instead. */
mr?: number | string;
/** @deprecated Use semantic props instead. */
mx?: string | number;
/** @deprecated Use semantic props instead. */
px?: number | string;
/** @deprecated Use semantic props instead. */
py?: number | string;
/** @deprecated Use semantic props instead. */
textAlign?: 'left' | 'center' | 'right';
/** @deprecated Use semantic props instead. */
flexGrow?: number;
/** @deprecated Use semantic props instead. */
maxHeight?: string | number;
/** @deprecated Use semantic props instead. */
overflow?: string;
/** @deprecated Use semantic props instead. */
opacity?: number;
/** @deprecated Use semantic props instead. */
groupHoverTextColor?: string;
/** @deprecated Use semantic props instead. */
lineHeight?: string | number;
/** @deprecated Use semantic props instead. */
transform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | string;
/** @deprecated Use semantic props instead. */
animate?: string;
/** @deprecated Use semantic props instead. */
transition?: boolean;
/** @deprecated Use semantic props instead. */
display?: string | ResponsiveValue<string>;
/** @deprecated Use semantic props instead. */
alignItems?: string;
/** @deprecated Use semantic props instead. */
gap?: number;
/** @deprecated Use semantic props instead. */
italic?: boolean;
/** @deprecated Use semantic props instead. */
fontSize?: string | any;
/** @deprecated Use semantic props instead. */
borderLeft?: boolean;
/** @deprecated Use semantic props instead. */
borderColor?: string;
/** @deprecated Use semantic props instead. */
pl?: number;
/** @deprecated Use semantic props instead. */
borderStyle?: string;
/** @deprecated Use semantic props instead. */
paddingX?: number;
/** @deprecated Use semantic props instead. */
whiteSpace?: string;
/** @deprecated Use semantic props instead. */
htmlFor?: string;
/** @deprecated Use semantic props instead. */
width?: string | number;
/** @deprecated Use semantic props instead. */
height?: string | number;
/** @deprecated Use semantic props instead. */
flexShrink?: number;
/** @deprecated Use semantic props instead. */
capitalize?: boolean;
/** @deprecated Use semantic props instead. */
hoverVariant?: string;
/** @deprecated Use semantic props instead. */
cursor?: string;
} }
/** /**
* Text - Redesigned for "Modern Precision" theme. * Text - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage. * Enforces semantic props.
*/ */
export const Text = forwardRef<HTMLElement, TextProps>(({ export const Text = forwardRef<HTMLElement, TextProps>(({
children, children,
@@ -77,56 +114,55 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
as = 'p', as = 'p',
align = 'left', align = 'left',
mono = false, mono = false,
font,
uppercase = false, uppercase = false,
leading = 'normal', leading = 'normal',
block = false, block = false,
truncate = false, truncate = false,
maxWidth,
lineClamp,
letterSpacing,
id,
color,
className, className,
style, style: styleProp,
mt, marginBottom,
marginTop,
mb, mb,
mt,
ml, ml,
mr, mr,
marginTop,
marginBottom,
font,
color,
letterSpacing,
lineHeight,
flexGrow,
flexShrink,
lineClamp,
display,
opacity,
maxWidth,
maxHeight,
overflow,
whiteSpace,
mx, mx,
pl,
px, px,
py, py,
paddingX,
paddingY,
textAlign, textAlign,
flexGrow,
maxHeight,
overflow,
opacity,
groupHoverTextColor, groupHoverTextColor,
transition, lineHeight,
borderLeft, transform,
borderStyle,
borderColor,
ariaLabel,
hoverVariant,
fontSize,
italic,
animate, animate,
capitalize, transition,
display,
alignItems, alignItems,
gap, gap,
cursor, italic,
fontSize,
borderLeft,
borderColor,
pl,
borderStyle,
paddingX,
whiteSpace,
htmlFor,
width, width,
height, height,
htmlFor, flexShrink,
transform, capitalize,
hoverVariant,
cursor,
}, ref) => { }, ref) => {
const variantClasses = { const variantClasses = {
high: 'text-[var(--ui-color-text-high)]', high: 'text-[var(--ui-color-text-high)]',
@@ -181,79 +217,99 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
loose: 'leading-loose', loose: 'leading-loose',
}; };
const getResponsiveClasses = (prefix: string, value: any | ResponsiveValue<any> | undefined) => { const letterSpacingClasses: Record<string, string> = {
tight: 'tracking-tight',
normal: 'tracking-normal',
wide: 'tracking-wide',
widest: 'tracking-widest',
};
const getResponsiveSpacing = (prefix: string, value: any) => {
if (value === undefined) return ''; if (value === undefined) return '';
if (typeof value === 'object') { if (typeof value === 'object') {
const classes = []; const classes = [];
if (value.base !== undefined) classes.push(prefix ? `${prefix}-${value.base}` : String(value.base)); if (value.base !== undefined) classes.push(`${prefix}-${value.base}`);
if (value.sm !== undefined) classes.push(prefix ? `sm:${prefix}-${value.sm}` : `sm:${value.sm}`); if (value.sm !== undefined) classes.push(`sm:${prefix}-${value.sm}`);
if (value.md !== undefined) classes.push(prefix ? `md:${prefix}-${value.md}` : `md:${value.md}`); if (value.md !== undefined) classes.push(`md:${prefix}-${value.md}`);
if (value.lg !== undefined) classes.push(prefix ? `lg:${prefix}-${value.lg}` : `lg:${value.lg}`); if (value.lg !== undefined) classes.push(`lg:${prefix}-${value.lg}`);
if (value.xl !== undefined) classes.push(prefix ? `xl:${prefix}-${value.xl}` : `xl:${value.xl}`); if (value.xl !== undefined) classes.push(`xl:${prefix}-${value.xl}`);
return classes.join(' '); return classes.join(' ');
} }
return prefix ? `${prefix}-${value}` : String(value); return ''; // Handled in style
};
const getResponsiveDisplay = (d: any) => {
if (!d) return '';
if (typeof d === 'string') return d.includes(':') ? d : `flex`; // Fallback
const classes = [];
if (d.base) classes.push(d.base);
if (d.sm) classes.push(`sm:${d.sm}`);
if (d.md) classes.push(`md:${d.md}`);
if (d.lg) classes.push(`lg:${d.lg}`);
if (d.xl) classes.push(`xl:${d.xl}`);
return classes.join(' ');
}; };
const classes = [ const classes = [
variantClasses[variant as keyof typeof variantClasses] || '', variantClasses[variant as keyof typeof variantClasses] || '',
getResponsiveSize(size), getResponsiveSize(size),
weightClasses[weight as keyof typeof weightClasses] || '', weightClasses[weight as keyof typeof weightClasses] || '',
align === 'center' || textAlign === 'center' ? 'text-center' : (align === 'right' || textAlign === 'right' ? 'text-right' : 'text-left'), (align === 'center' || textAlign === 'center') ? 'text-center' : ((align === 'right' || textAlign === 'right') ? 'text-right' : 'text-left'),
(mono || font === 'mono') ? 'font-mono' : 'font-sans', (mono || font === 'mono') ? 'font-mono' : 'font-sans',
uppercase ? 'uppercase tracking-widest' : '', uppercase ? 'uppercase' : '',
letterSpacing ? (letterSpacingClasses[letterSpacing] || '') : (uppercase ? 'tracking-widest' : ''),
leadingClasses[leading as keyof typeof leadingClasses] || '', leadingClasses[leading as keyof typeof leadingClasses] || '',
block ? 'block' : 'inline', block ? 'block' : 'inline',
truncate ? 'truncate' : '', truncate ? 'truncate' : '',
getResponsiveClasses('display', display), getResponsiveSpacing('mb', mb || marginBottom),
transition ? 'transition-all duration-200' : '', getResponsiveSpacing('mt', mt || marginTop),
getResponsiveDisplay(display),
italic ? 'italic' : '', italic ? 'italic' : '',
animate === 'pulse' ? 'animate-pulse' : '', animate === 'pulse' ? 'animate-pulse' : '',
transition ? 'transition-all duration-200' : '',
capitalize ? 'capitalize' : '', capitalize ? 'capitalize' : '',
color?.startsWith('text-') ? color : '', color?.startsWith('text-') ? color : '',
className, className,
].filter(Boolean).join(' '); ].filter(Boolean).join(' ');
const combinedStyle: React.CSSProperties = { const style: React.CSSProperties = {
...style,
...(typeof display === 'string' ? { display } : {}),
...(alignItems ? { alignItems } : {}),
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
...(cursor ? { cursor } : {}),
...(width !== undefined ? { width } : {}),
...(height !== undefined ? { height } : {}),
...(opacity !== undefined ? { opacity } : {}),
...(maxWidth !== undefined ? { maxWidth } : {}), ...(maxWidth !== undefined ? { maxWidth } : {}),
...(maxHeight !== undefined ? { maxHeight } : {}), ...(maxHeight !== undefined ? { maxHeight } : {}),
...(overflow !== undefined ? { overflow } : {}), ...(overflow !== undefined ? { overflow } : {}),
...(whiteSpace !== undefined ? { whiteSpace } : {}),
...(mx === 'auto' ? { marginLeft: 'auto', marginRight: 'auto' } : {}),
...(pl !== undefined ? { paddingLeft: `${pl * 0.25}rem` } : {}),
...(px !== undefined ? { paddingLeft: `${px * 0.25}rem`, paddingRight: `${px * 0.25}rem` } : {}),
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
...(paddingX !== undefined ? { paddingLeft: `${paddingX * 0.25}rem`, paddingRight: `${paddingX * 0.25}rem` } : {}),
...(paddingY !== undefined ? { paddingTop: `${paddingY * 0.25}rem`, paddingBottom: `${paddingY * 0.25}rem` } : {}),
...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(mb !== undefined ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}),
...(ml !== undefined ? { marginLeft: typeof ml === 'number' ? `${ml * 0.25}rem` : ml } : {}),
...(mr !== undefined ? { marginRight: typeof mr === 'number' ? `${mr * 0.25}rem` : mr } : {}),
...(marginTop !== undefined ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}),
...(marginBottom !== undefined ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}),
...(color ? { color: color.startsWith('text-') ? undefined : color } : {}),
...(letterSpacing ? { letterSpacing } : {}),
...(lineHeight ? { lineHeight } : {}),
...(flexGrow !== undefined ? { flexGrow } : {}), ...(flexGrow !== undefined ? { flexGrow } : {}),
...(flexShrink !== undefined ? { flexShrink } : {}), ...(flexShrink !== undefined ? { flexShrink } : {}),
...(opacity !== undefined ? { opacity } : {}),
...(lineClamp !== undefined ? { display: '-webkit-box', WebkitLineClamp: lineClamp, WebkitBoxOrient: 'vertical', overflow: 'hidden' } : {}), ...(lineClamp !== undefined ? { display: '-webkit-box', WebkitLineClamp: lineClamp, WebkitBoxOrient: 'vertical', overflow: 'hidden' } : {}),
...(borderLeft ? { borderLeft: `1px solid ${borderColor || 'var(--ui-color-border-default)'}` } : {}), ...(color && !color.startsWith('text-') ? { color } : {}),
...(fontSize ? { fontSize } : {}), ...(typeof mb === 'number' || typeof mb === 'string' ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}),
...(typeof marginBottom === 'number' || typeof marginBottom === 'string' ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}),
...(typeof mt === 'number' || typeof mt === 'string' ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(typeof marginTop === 'number' || typeof marginTop === 'string' ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}),
...(ml !== undefined ? { marginLeft: typeof ml === 'number' ? `${ml * 0.25}rem` : ml } : {}),
...(mr !== undefined ? { marginRight: typeof mr === 'number' ? `${mr * 0.25}rem` : mr } : {}),
...(mx === 'auto' ? { marginLeft: 'auto', marginRight: 'auto' } : {}),
...(px !== undefined ? { paddingLeft: typeof px === 'number' ? `${px * 0.25}rem` : px, paddingRight: typeof px === 'number' ? `${px * 0.25}rem` : px } : {}),
...(py !== undefined ? { paddingTop: typeof py === 'number' ? `${py * 0.25}rem` : py, paddingBottom: typeof py === 'number' ? `${py * 0.25}rem` : py } : {}),
...(pl !== undefined ? { paddingLeft: typeof pl === 'number' ? `${pl * 0.25}rem` : pl } : {}),
...(paddingX !== undefined ? { paddingLeft: typeof paddingX === 'number' ? `${paddingX * 0.25}rem` : paddingX, paddingRight: typeof paddingX === 'number' ? `${paddingX * 0.25}rem` : paddingX } : {}),
...(letterSpacing && !letterSpacingClasses[letterSpacing] ? { letterSpacing } : {}),
...(lineHeight ? { lineHeight } : {}),
...(transform ? { textTransform: transform as any } : {}), ...(transform ? { textTransform: transform as any } : {}),
...(alignItems ? { alignItems } : {}),
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
...(cursor ? { cursor } : {}),
...(fontSize && typeof fontSize === 'string' ? { fontSize } : {}),
...(borderLeft ? { borderLeft: `1px solid ${borderColor || 'var(--ui-color-border-default)'}` } : {}),
...(whiteSpace ? { whiteSpace: whiteSpace as any } : {}),
...(width !== undefined ? { width } : {}),
...(height !== undefined ? { height } : {}),
...(styleProp || {}),
}; };
const Tag = as || 'p'; const Tag = as || 'p';
return ( return (
<Tag ref={ref} className={classes} style={combinedStyle} aria-label={ariaLabel} htmlFor={htmlFor}> <Tag ref={ref} className={classes} style={Object.keys(style).length > 0 ? style : undefined} id={id} htmlFor={htmlFor}>
{children} {children}
</Tag> </Tag>
); );