diff --git a/apps/website/app/leagues/LeaguesPageClient.tsx b/apps/website/app/leagues/LeaguesPageClient.tsx index 546214b59..6e129379c 100644 --- a/apps/website/app/leagues/LeaguesPageClient.tsx +++ b/apps/website/app/leagues/LeaguesPageClient.tsx @@ -8,7 +8,7 @@ import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; import { Input } from '@/ui/Input'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Award, diff --git a/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx b/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx index 78930c367..944714277 100644 --- a/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx +++ b/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx @@ -20,7 +20,7 @@ import { LeagueAdminScheduleTemplate } from '@/templates/LeagueAdminScheduleTemp import { Card } from '@/ui/Card'; import { Heading } from '@/ui/Heading'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; diff --git a/apps/website/app/leagues/[id]/stewarding/StewardingPageClient.tsx b/apps/website/app/leagues/[id]/stewarding/StewardingPageClient.tsx index 8b0c26b4b..8448caa09 100644 --- a/apps/website/app/leagues/[id]/stewarding/StewardingPageClient.tsx +++ b/apps/website/app/leagues/[id]/stewarding/StewardingPageClient.tsx @@ -14,7 +14,7 @@ import { RaceViewModel } from '@/lib/view-models/RaceViewModel'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { useMemo, useState } from 'react'; diff --git a/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/ProtestDetailPageClient.tsx b/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/ProtestDetailPageClient.tsx index 470e8693b..043f23b39 100644 --- a/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/ProtestDetailPageClient.tsx +++ b/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/ProtestDetailPageClient.tsx @@ -38,13 +38,13 @@ import { StateContainer } from '@/components/shared/state/StateContainer'; import { useLeagueAdminStatus } from "@/hooks/league/useLeagueAdminStatus"; import { useProtestDetail } from "@/hooks/league/useProtestDetail"; import { routes } from '@/lib/routing/RouteConfig'; -import { GridItem } from '@/ui/GridItem'; +import { GridItem } from '@/ui/primitives/GridItem'; import { Heading } from '@/ui/Heading'; import { Icon as UIIcon } from '@/ui/Icon'; import { Link as UILink } from '@/ui/Link'; import { Box } from '@/ui/primitives/Box'; import { Grid } from '@/ui/primitives/Grid'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; type PenaltyUiConfig = { diff --git a/apps/website/app/leagues/[id]/wallet/LeagueWalletPageClient.tsx b/apps/website/app/leagues/[id]/wallet/LeagueWalletPageClient.tsx index 47a53fb22..b440b6083 100644 --- a/apps/website/app/leagues/[id]/wallet/LeagueWalletPageClient.tsx +++ b/apps/website/app/leagues/[id]/wallet/LeagueWalletPageClient.tsx @@ -7,7 +7,7 @@ import { Container } from '@/ui/Container'; import { Heading } from '@/ui/Heading'; import { Icon as UIIcon } from '@/ui/Icon'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Download diff --git a/apps/website/app/leagues/create/CreateLeagueWizard.tsx b/apps/website/app/leagues/create/CreateLeagueWizard.tsx index abc1c0f94..6d7aeb2bc 100644 --- a/apps/website/app/leagues/create/CreateLeagueWizard.tsx +++ b/apps/website/app/leagues/create/CreateLeagueWizard.tsx @@ -8,7 +8,7 @@ import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { Input } from '@/ui/Input'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { AlertCircle, diff --git a/apps/website/app/sponsor/billing/page.tsx b/apps/website/app/sponsor/billing/page.tsx index 071272192..5ceb57ee0 100644 --- a/apps/website/app/sponsor/billing/page.tsx +++ b/apps/website/app/sponsor/billing/page.tsx @@ -2,7 +2,7 @@ import { useSponsorBilling } from "@/hooks/sponsor/useSponsorBilling"; import { SponsorBillingTemplate } from "@/templates/SponsorBillingTemplate"; -import { Box } from "@/ui/Box"; +import { Box } from "@/ui/primitives/Box"; import { Text } from "@/ui/Text"; import { Button } from "@/ui/Button"; import { DollarSign, AlertTriangle, Calendar, TrendingUp } from "lucide-react"; diff --git a/apps/website/app/sponsor/campaigns/page.tsx b/apps/website/app/sponsor/campaigns/page.tsx index b24030593..9b6e9b17b 100644 --- a/apps/website/app/sponsor/campaigns/page.tsx +++ b/apps/website/app/sponsor/campaigns/page.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { useSponsorSponsorships } from "@/hooks/sponsor/useSponsorSponsorships"; import { SponsorCampaignsTemplate, SponsorshipType } from "@/templates/SponsorCampaignsTemplate"; -import { Box } from "@/ui/Box"; +import { Box } from "@/ui/primitives/Box"; import { Text } from "@/ui/Text"; import { Button } from "@/ui/Button"; diff --git a/apps/website/app/sponsor/signup/page.tsx b/apps/website/app/sponsor/signup/page.tsx index 445569922..fda0cef9b 100644 --- a/apps/website/app/sponsor/signup/page.tsx +++ b/apps/website/app/sponsor/signup/page.tsx @@ -10,7 +10,7 @@ import { Card } from '@/ui/Card'; import { Heading } from '@/ui/Heading'; import { Input } from '@/ui/Input'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { motion, useReducedMotion } from 'framer-motion'; import { diff --git a/apps/website/components/achievements/AchievementGrid.tsx b/apps/website/components/achievements/AchievementGrid.tsx index 7b1cd50de..c47c526fa 100644 --- a/apps/website/components/achievements/AchievementGrid.tsx +++ b/apps/website/components/achievements/AchievementGrid.tsx @@ -1,9 +1,9 @@ import { AchievementDisplay } from '@/lib/display-objects/AchievementDisplay'; import { Card } from '@/ui/Card'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Award, Crown, Medal, Star, Target, Trophy, Zap } from 'lucide-react'; diff --git a/apps/website/components/dashboard/DashboardKpiRow.tsx b/apps/website/components/dashboard/DashboardKpiRow.tsx index 620974e32..f371fa4b9 100644 --- a/apps/website/components/dashboard/DashboardKpiRow.tsx +++ b/apps/website/components/dashboard/DashboardKpiRow.tsx @@ -1,4 +1,4 @@ -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; diff --git a/apps/website/components/drivers/CareerHighlights.tsx b/apps/website/components/drivers/CareerHighlights.tsx index c7b981934..8b08e2db2 100644 --- a/apps/website/components/drivers/CareerHighlights.tsx +++ b/apps/website/components/drivers/CareerHighlights.tsx @@ -3,8 +3,8 @@ import { Card } from '@/ui/Card'; import { GoalCard } from '@/ui/GoalCard'; import { Heading } from '@/ui/Heading'; import { MilestoneItem } from '@/components/achievements/MilestoneItem'; -import { Stack } from '@/ui/Stack'; -import { Grid } from '@/ui/Grid'; +import { Stack } from '@/ui/primitives/Stack'; +import { Grid } from '@/ui/primitives/Grid'; interface Achievement { id: string; diff --git a/apps/website/components/home/HomeFeatureSection.tsx b/apps/website/components/home/HomeFeatureSection.tsx index 83f40bb54..8e98b454d 100644 --- a/apps/website/components/home/HomeFeatureSection.tsx +++ b/apps/website/components/home/HomeFeatureSection.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { Panel } from '@/ui/Panel'; import { Glow } from '@/ui/Glow'; -import { Stack } from '@/ui/Stack'; -import { Grid } from '@/ui/Grid'; +import { Stack } from '@/ui/primitives/Stack'; +import { Grid } from '@/ui/primitives/Grid'; import { Container } from '@/ui/Container'; import { Heading } from '@/ui/Heading'; import { Section } from '@/ui/Section'; diff --git a/apps/website/components/home/HomeFooterCTA.tsx b/apps/website/components/home/HomeFooterCTA.tsx index a895bc68b..384fb6a35 100644 --- a/apps/website/components/home/HomeFooterCTA.tsx +++ b/apps/website/components/home/HomeFooterCTA.tsx @@ -8,8 +8,8 @@ import { DiscordIcon } from '@/ui/icons/DiscordIcon'; import { Code, Lightbulb, LucideIcon, MessageSquare, Users } from 'lucide-react'; import { Heading } from '@/ui/Heading'; import { Text } from '@/ui/Text'; -import { Stack } from '@/ui/Stack'; -import { Grid } from '@/ui/Grid'; +import { Stack } from '@/ui/primitives/Stack'; +import { Grid } from '@/ui/primitives/Grid'; import { Card } from '@/ui/Card'; import { Section } from '@/ui/Section'; import { Container } from '@/ui/Container'; diff --git a/apps/website/components/home/HomeStatsStrip.tsx b/apps/website/components/home/HomeStatsStrip.tsx index 819b5f36c..c0450d207 100644 --- a/apps/website/components/home/HomeStatsStrip.tsx +++ b/apps/website/components/home/HomeStatsStrip.tsx @@ -4,8 +4,8 @@ import React from 'react'; import { MetricCard } from '@/ui/MetricCard'; import { Activity, Users, Trophy, Calendar } from 'lucide-react'; import { Container } from '@/ui/Container'; -import { Grid } from '@/ui/Grid'; -import { Stack } from '@/ui/Stack'; +import { Grid } from '@/ui/primitives/Grid'; +import { Stack } from '@/ui/primitives/Stack'; /** * HomeStatsStrip - A thin strip showing some status or quick info. diff --git a/apps/website/components/landing/DiscoverySection.tsx b/apps/website/components/landing/DiscoverySection.tsx index 42a376dde..f633dcc8f 100644 --- a/apps/website/components/landing/DiscoverySection.tsx +++ b/apps/website/components/landing/DiscoverySection.tsx @@ -1,10 +1,10 @@ 'use client'; import { routes } from '@/lib/routing/RouteConfig'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Button } from '@/ui/Button'; import { Container } from '@/ui/Container'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Heading } from '@/ui/Heading'; import { Link } from '@/ui/Link'; import { Text } from '@/ui/Text'; diff --git a/apps/website/components/leagues/LeagueBasicsSection.tsx b/apps/website/components/leagues/LeagueBasicsSection.tsx index 21103114f..f64addd52 100644 --- a/apps/website/components/leagues/LeagueBasicsSection.tsx +++ b/apps/website/components/leagues/LeagueBasicsSection.tsx @@ -2,7 +2,7 @@ import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel'; import { Button } from '@/ui/Button'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { Input } from '@/ui/Input'; diff --git a/apps/website/components/leagues/LeagueReviewSummary.tsx b/apps/website/components/leagues/LeagueReviewSummary.tsx index 4de605070..bef37554a 100644 --- a/apps/website/components/leagues/LeagueReviewSummary.tsx +++ b/apps/website/components/leagues/LeagueReviewSummary.tsx @@ -20,12 +20,12 @@ import { } from 'lucide-react'; import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel'; import type { LeagueScoringPresetViewModel } from '@/lib/view-models/LeagueScoringPresetViewModel'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { Card } from '@/ui/Card'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; interface LeagueReviewSummaryProps { form: LeagueConfigFormModel; diff --git a/apps/website/components/leagues/LeagueScoringSection.tsx b/apps/website/components/leagues/LeagueScoringSection.tsx index 41e40d03a..04b86937f 100644 --- a/apps/website/components/leagues/LeagueScoringSection.tsx +++ b/apps/website/components/leagues/LeagueScoringSection.tsx @@ -4,7 +4,7 @@ import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel'; import type { LeagueScoringPresetViewModel } from '@/lib/view-models/LeagueScoringPresetViewModel'; import type { CustomPointsConfig } from '@/lib/view-models/ScoringConfigurationViewModel'; import { Button } from '@/ui/Button'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/primitives/Stack'; diff --git a/apps/website/components/leagues/LeagueSummaryCard.tsx b/apps/website/components/leagues/LeagueSummaryCard.tsx index 44f97ab69..4feeb93af 100644 --- a/apps/website/components/leagues/LeagueSummaryCard.tsx +++ b/apps/website/components/leagues/LeagueSummaryCard.tsx @@ -1,13 +1,13 @@ import { ArrowRight } from 'lucide-react'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { LeagueLogo } from './LeagueLogo'; import { Link } from '@/ui/Link'; import { Text } from '@/ui/Text'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; interface LeagueSummaryCardProps { id: string; diff --git a/apps/website/components/leagues/PenaltyHistoryList.tsx b/apps/website/components/leagues/PenaltyHistoryList.tsx index 84d2c1085..304d49d37 100644 --- a/apps/website/components/leagues/PenaltyHistoryList.tsx +++ b/apps/website/components/leagues/PenaltyHistoryList.tsx @@ -5,7 +5,7 @@ import { ProtestViewModel } from "@/lib/view-models/ProtestViewModel"; import { RaceViewModel } from "@/lib/view-models/RaceViewModel"; import { DriverViewModel } from "@/lib/view-models/DriverViewModel"; import { Card } from "@/ui/Card"; -import { Stack } from "@/ui/Stack"; +import { Stack } from "@/ui/primitives/Stack"; import { Text } from "@/ui/Text"; import { Heading } from "@/ui/Heading"; import { Icon } from "@/ui/Icon"; diff --git a/apps/website/components/leagues/PendingProtestsList.tsx b/apps/website/components/leagues/PendingProtestsList.tsx index 64402c75e..4ed8ca062 100644 --- a/apps/website/components/leagues/PendingProtestsList.tsx +++ b/apps/website/components/leagues/PendingProtestsList.tsx @@ -1,7 +1,7 @@ import { DriverViewModel } from "@/lib/view-models/DriverViewModel"; import { ProtestViewModel } from "@/lib/view-models/ProtestViewModel"; import { RaceViewModel } from "@/lib/view-models/RaceViewModel"; -import { Stack } from "@/ui/Stack"; +import { Stack } from "@/ui/primitives/Stack"; import { Card } from "@/ui/Card"; import { ProtestListItem } from "./ProtestListItem"; import { Text } from "@/ui/Text"; diff --git a/apps/website/components/leagues/ReviewProtestModal.tsx b/apps/website/components/leagues/ReviewProtestModal.tsx index d9aa8059f..5424f128f 100644 --- a/apps/website/components/leagues/ReviewProtestModal.tsx +++ b/apps/website/components/leagues/ReviewProtestModal.tsx @@ -7,13 +7,13 @@ import { ProtestViewModel } from "../../lib/view-models/ProtestViewModel"; import { Modal } from "@/ui/Modal"; import { Button } from "@/ui/Button"; import { Card } from "@/ui/Card"; -import { Stack } from "@/ui/Stack"; +import { Stack } from "@/ui/primitives/Stack"; import { Text } from "@/ui/Text"; import { Heading } from "@/ui/Heading"; import { Icon } from "@/ui/Icon"; import { TextArea } from "@/ui/TextArea"; import { Input } from "@/ui/Input"; -import { Grid } from "@/ui/Grid"; +import { Grid } from "@/ui/primitives/Grid"; import { AlertCircle, Video, diff --git a/apps/website/components/leagues/ScheduleRaceCard.tsx b/apps/website/components/leagues/ScheduleRaceCard.tsx index 84898bad6..f2ef3a37c 100644 --- a/apps/website/components/leagues/ScheduleRaceCard.tsx +++ b/apps/website/components/leagues/ScheduleRaceCard.tsx @@ -3,11 +3,11 @@ import React from 'react'; import { Calendar, Clock, MapPin, Car, Trophy } from 'lucide-react'; import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Heading } from '@/ui/Heading'; import { Badge } from '@/ui/Badge'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Icon } from '@/ui/Icon'; interface Race { diff --git a/apps/website/components/sponsors/BillingSummaryPanel.tsx b/apps/website/components/sponsors/BillingSummaryPanel.tsx index 78e73562f..db6ab169c 100644 --- a/apps/website/components/sponsors/BillingSummaryPanel.tsx +++ b/apps/website/components/sponsors/BillingSummaryPanel.tsx @@ -1,4 +1,4 @@ -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; diff --git a/apps/website/components/sponsors/TransactionTable.tsx b/apps/website/components/sponsors/TransactionTable.tsx index e0bce0c82..abd71be41 100644 --- a/apps/website/components/sponsors/TransactionTable.tsx +++ b/apps/website/components/sponsors/TransactionTable.tsx @@ -1,5 +1,5 @@ import { Button } from '@/ui/Button'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; diff --git a/apps/website/templates/AdminDashboardTemplate.tsx b/apps/website/templates/AdminDashboardTemplate.tsx index ed0e75299..9bb5727f0 100644 --- a/apps/website/templates/AdminDashboardTemplate.tsx +++ b/apps/website/templates/AdminDashboardTemplate.tsx @@ -9,11 +9,11 @@ import { AdminDashboardViewData } from '@/lib/view-data/AdminDashboardViewData'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; import { Container } from '@/ui/Container'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { Icon } from '@/ui/Icon'; import { Box } from '@/ui/primitives/Box'; import { QuickActionLink } from '@/ui/QuickActionLink'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { StatusBadge } from '@/ui/StatusBadge'; import { Text } from '@/ui/Text'; import { diff --git a/apps/website/templates/DashboardTemplate.tsx b/apps/website/templates/DashboardTemplate.tsx index 67f1c2108..a0a565f3f 100644 --- a/apps/website/templates/DashboardTemplate.tsx +++ b/apps/website/templates/DashboardTemplate.tsx @@ -10,10 +10,10 @@ import { routes } from '@/lib/routing/RouteConfig'; import type { DashboardViewData } from '@/lib/view-data/DashboardViewData'; import { Avatar } from '@/ui/Avatar'; import { Button } from '@/ui/Button'; -import { Grid } from '@/ui/Grid'; +import { Grid } from '@/ui/primitives/Grid'; import { IconButton } from '@/ui/IconButton'; import { Box } from '@/ui/primitives/Box'; -import { Stack } from '@/ui/Stack'; +import { Stack } from '@/ui/primitives/Stack'; import { Text } from '@/ui/Text'; import { Bell, Calendar, LayoutDashboard, Search, Settings, Trophy, Users } from 'lucide-react'; import { useRouter } from 'next/navigation'; diff --git a/apps/website/templates/LeaderboardsTemplate.tsx b/apps/website/templates/LeaderboardsTemplate.tsx index 81227654c..fc69624ca 100644 --- a/apps/website/templates/LeaderboardsTemplate.tsx +++ b/apps/website/templates/LeaderboardsTemplate.tsx @@ -4,7 +4,7 @@ import { DriverLeaderboardPreview } from '@/components/leaderboards/DriverLeader import { TeamLeaderboardPreview } from '@/components/teams/TeamLeaderboardPreviewWrapper'; import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData'; import { Container } from '@/ui/Container'; -import { GridItem } from '@/ui/GridItem'; +import { GridItem } from '@/ui/primitives/GridItem'; import { PageHero } from '@/ui/PageHero'; import { Grid } from '@/ui/primitives/Grid'; import { Trophy, Users } from 'lucide-react'; diff --git a/apps/website/ui/ActivityItem.tsx b/apps/website/ui/ActivityItem.tsx index 795fce903..789b3c384 100644 --- a/apps/website/ui/ActivityItem.tsx +++ b/apps/website/ui/ActivityItem.tsx @@ -1,66 +1,67 @@ - - +import React from 'react'; import { Box } from './primitives/Box'; -import { Link } from './Link'; import { Text } from './Text'; import { Surface } from './primitives/Surface'; +import { Link } from './Link'; interface ActivityItemProps { - headline: string; + title?: string; + description?: string; + timeAgo?: string; + color?: string; + headline?: string; body?: string; - formattedTime: string; + formattedTime?: string; ctaHref?: string; ctaLabel?: string; - typeColor?: string; } -export function ActivityItem({ +export function ActivityItem({ + title, + description, + timeAgo, + color = 'bg-primary-blue', headline, body, formattedTime, ctaHref, - ctaLabel, - typeColor, + ctaLabel }: ActivityItemProps) { return ( - {typeColor && ( - - )} - + + - {headline} + {title || headline} - {body && ( - - {body} - + + {description || body} + + + {timeAgo || formattedTime} + + {ctaHref && ctaLabel && ( + + + {ctaLabel} + + )} - - {formattedTime} - - {ctaHref && ctaLabel && ( - - - {ctaLabel} - - - )} ); } diff --git a/apps/website/ui/AuthLoading.tsx b/apps/website/ui/AuthLoading.tsx index 77580ad8e..b32c4763c 100644 --- a/apps/website/ui/AuthLoading.tsx +++ b/apps/website/ui/AuthLoading.tsx @@ -1,8 +1,7 @@ - - +import React from 'react'; import { Box } from './primitives/Box'; -import { LoadingSpinner } from './LoadingSpinner'; import { Stack } from './primitives/Stack'; +import { LoadingSpinner } from './LoadingSpinner'; import { Text } from './Text'; interface AuthLoadingProps { @@ -11,10 +10,18 @@ interface AuthLoadingProps { export function AuthLoading({ message = 'Authenticating...' }: AuthLoadingProps) { return ( - + - - {message} + + + {message} + ); diff --git a/apps/website/ui/Avatar.tsx b/apps/website/ui/Avatar.tsx index 3664cbb9d..99a6ecd37 100644 --- a/apps/website/ui/Avatar.tsx +++ b/apps/website/ui/Avatar.tsx @@ -1,51 +1,44 @@ import React from 'react'; import { Box } from './primitives/Box'; import { Image } from './Image'; -import { User } from 'lucide-react'; -import { Icon } from './Icon'; +import { Surface } from './primitives/Surface'; -export interface AvatarProps { - driverId?: string; - src?: string; +interface AvatarProps { + src?: string | null; alt: string; size?: number; className?: string; - border?: boolean; } -export function Avatar({ - driverId, - src, - alt, - size = 40, - className = '', - border = true, -}: AvatarProps) { - const avatarSrc = src || (driverId ? `/media/avatar/${driverId}` : undefined); - +export function Avatar({ src, alt, size = 40, className = '' }: AvatarProps) { return ( - - {avatarSrc ? ( + {src ? ( {alt} ) : ( - 32 ? 5 : 4} color="text-gray-500" /> + + + {alt.charAt(0).toUpperCase()} + + )} - + ); } diff --git a/apps/website/ui/Badge.tsx b/apps/website/ui/Badge.tsx index 9464f5df0..8c1aec76c 100644 --- a/apps/website/ui/Badge.tsx +++ b/apps/website/ui/Badge.tsx @@ -1,22 +1,16 @@ import React, { ReactNode } from 'react'; -import { Box } from './primitives/Box'; +import { Box, BoxProps } from './primitives/Box'; import { Icon } from './Icon'; import { LucideIcon } from 'lucide-react'; -interface BadgeProps { +interface BadgeProps extends Omit, 'children'> { children: ReactNode; - className?: string; variant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'; size?: 'xs' | 'sm' | 'md'; icon?: LucideIcon; - style?: React.CSSProperties; - bg?: string; - color?: string; - borderColor?: string; - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'; } -export function Badge({ children, className = '', variant = 'default', size = 'sm', icon, style, bg, color, borderColor, rounded = 'none' }: BadgeProps) { +export function Badge({ children, className = '', variant = 'default', size = 'sm', icon, rounded = 'none', ...props }: BadgeProps) { const baseClasses = 'flex items-center gap-1.5 border font-bold uppercase tracking-widest'; const sizeClasses = { @@ -47,16 +41,13 @@ export function Badge({ children, className = '', variant = 'default', size = 's const classes = [ baseClasses, sizeClasses[size], - roundedClasses[rounded], - !bg && !color && !borderColor ? variantClasses[variant] : '', - bg, - color, - borderColor, + typeof rounded === 'string' && roundedClasses[rounded as keyof typeof roundedClasses] ? roundedClasses[rounded as keyof typeof roundedClasses] : '', + !props.bg && !props.color && !props.borderColor ? variantClasses[variant] : '', className ].filter(Boolean).join(' '); return ( - + {icon && } {children} diff --git a/apps/website/ui/BorderTabs.tsx b/apps/website/ui/BorderTabs.tsx index 3f59c4f7c..d775667a6 100644 --- a/apps/website/ui/BorderTabs.tsx +++ b/apps/website/ui/BorderTabs.tsx @@ -1,64 +1,63 @@ - - -import { Badge } from './Badge'; +import React from 'react'; import { Box } from './primitives/Box'; +import { Stack } from './primitives/Stack'; +import { Surface } from './primitives/Surface'; import { Text } from './Text'; interface Tab { id: string; label: string; - count?: number; - countVariant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'; + icon?: React.ReactNode; } interface BorderTabsProps { tabs: Tab[]; activeTab: string; onTabChange: (tabId: string) => void; + className?: string; } -export function BorderTabs({ tabs, activeTab, onTabChange }: BorderTabsProps) { +export function BorderTabs({ tabs, activeTab, onTabChange, className = '' }: BorderTabsProps) { return ( - - + + {tabs.map((tab) => { const isActive = activeTab === tab.id; return ( - onTabChange(tab.id)} - pb={3} + variant="ghost" px={1} - cursor="pointer" - transition - borderBottom={isActive} + py={4} + position="relative" borderColor={isActive ? 'border-primary-blue' : ''} - style={{ - borderBottomWidth: isActive ? '2px' : '0', - marginBottom: '-1px' - }} + borderBottom={isActive} + borderWidth={isActive ? '2px' : '0'} + mb="-1px" + transition="all 0.2s" + group > - + + {tab.icon && ( + + {tab.icon} + + )} {tab.label} - {tab.count !== undefined && tab.count > 0 && ( - - {tab.count} - - )} - - + + ); })} - + ); } diff --git a/apps/website/ui/Button.tsx b/apps/website/ui/Button.tsx index 0efab6a31..950ee3d87 100644 --- a/apps/website/ui/Button.tsx +++ b/apps/website/ui/Button.tsx @@ -4,7 +4,7 @@ import { Box, BoxProps } from './primitives/Box'; import { Loader2 } from 'lucide-react'; import { Icon } from './Icon'; -interface ButtonProps extends Omit, 'as' | 'onMouseEnter' | 'onMouseLeave' | 'onSubmit'>, Omit, 'as' | 'onClick' | 'onSubmit'> { +interface ButtonProps extends Omit, 'as' | 'onMouseEnter' | 'onMouseLeave' | 'onSubmit' | 'role' | 'translate' | 'onScroll' | 'draggable' | 'onChange' | 'onMouseDown' | 'onMouseUp' | 'onMouseMove' | 'value' | 'onBlur' | 'onKeyDown'>, Omit, 'as' | 'onClick' | 'onSubmit'> { children: ReactNode; onClick?: MouseEventHandler; className?: string; @@ -19,6 +19,8 @@ interface ButtonProps extends Omit, 'as' href?: string; target?: string; rel?: string; + fontSize?: string; + backgroundColor?: string; } export const Button = forwardRef(({ @@ -36,6 +38,8 @@ export const Button = forwardRef(({ href, target, rel, + fontSize, + backgroundColor, ...props }, ref) => { const baseClasses = 'inline-flex items-center justify-center rounded-none transition-all duration-150 ease-smooth focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold'; @@ -83,6 +87,8 @@ export const Button = forwardRef(({ target={target} rel={rel} className={classes} + fontSize={fontSize} + backgroundColor={backgroundColor} {...props} > {content} @@ -98,6 +104,8 @@ export const Button = forwardRef(({ className={classes} onClick={onClick} disabled={disabled || isLoading} + fontSize={fontSize} + backgroundColor={backgroundColor} {...props} > {content} diff --git a/apps/website/ui/Card.tsx b/apps/website/ui/Card.tsx index 7a01ad32a..9440b1b31 100644 --- a/apps/website/ui/Card.tsx +++ b/apps/website/ui/Card.tsx @@ -1,26 +1,10 @@ import React, { ReactNode, MouseEventHandler } from 'react'; import { Box, BoxProps } from './primitives/Box'; -type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96; - -interface ResponsiveSpacing { - base?: Spacing; - md?: Spacing; - lg?: Spacing; -} - -interface CardProps extends Omit, 'children' | 'className' | 'onClick'> { +export interface CardProps extends Omit, 'children' | 'onClick'> { children: ReactNode; - className?: string; onClick?: MouseEventHandler; - variant?: 'default' | 'outline' | 'ghost'; - p?: Spacing | ResponsiveSpacing; - px?: Spacing | ResponsiveSpacing; - py?: Spacing | ResponsiveSpacing; - pt?: Spacing | ResponsiveSpacing; - pb?: Spacing | ResponsiveSpacing; - pl?: Spacing | ResponsiveSpacing; - pr?: Spacing | ResponsiveSpacing; + variant?: 'default' | 'outline' | 'ghost' | 'muted' | 'dark' | 'glass'; } export function Card({ @@ -35,7 +19,10 @@ export function Card({ const variantClasses = { default: 'bg-panel-gray border border-border-gray shadow-card', outline: 'bg-transparent border border-border-gray', - ghost: 'bg-transparent border-none' + ghost: 'bg-transparent border-none', + muted: 'bg-panel-gray/40 border border-border-gray', + dark: 'bg-graphite-black border border-border-gray', + glass: 'bg-graphite-black/60 backdrop-blur-md border border-border-gray' }; const classes = [ diff --git a/apps/website/ui/CategoryDistributionCard.tsx b/apps/website/ui/CategoryDistributionCard.tsx index 8e105cdb5..a33b4c6bc 100644 --- a/apps/website/ui/CategoryDistributionCard.tsx +++ b/apps/website/ui/CategoryDistributionCard.tsx @@ -1,35 +1,49 @@ import React from 'react'; import { Box } from './primitives/Box'; import { Text } from './Text'; -import { ProgressBar } from './ProgressBar'; +import { Icon } from './Icon'; +import { LucideIcon } from 'lucide-react'; interface CategoryDistributionCardProps { label: string; count: number; percentage: number; + icon: LucideIcon; color: string; bgColor: string; borderColor: string; - progressColor: string; } export function CategoryDistributionCard({ label, count, percentage, + icon, color, bgColor, borderColor, - progressColor, }: CategoryDistributionCardProps) { return ( - + - {count} + {count} + + + - {label} - - {percentage}% of drivers + + {label} + + + + + + {percentage.toFixed(1)}% of total + ); } diff --git a/apps/website/ui/Checkbox.tsx b/apps/website/ui/Checkbox.tsx index d14c7a3af..5b9acd3ba 100644 --- a/apps/website/ui/Checkbox.tsx +++ b/apps/website/ui/Checkbox.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { Box } from './primitives/Box'; import { Text } from './Text'; @@ -26,7 +24,8 @@ export function Checkbox({ label, checked, onChange, disabled }: CheckboxProps) border borderColor="border-charcoal-outline" rounded="sm" - className="text-primary-blue focus:ring-primary-blue" + ring="primary-blue" + color="text-primary-blue" /> {label} diff --git a/apps/website/ui/Container.tsx b/apps/website/ui/Container.tsx index b94a6fbbd..9ba3bfa9d 100644 --- a/apps/website/ui/Container.tsx +++ b/apps/website/ui/Container.tsx @@ -3,7 +3,7 @@ import { Box, BoxProps } from './primitives/Box'; type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96; -interface ContainerProps extends BoxProps<'div'> { +interface ContainerProps extends Omit, 'size' | 'padding'> { children: ReactNode; size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; padding?: boolean; diff --git a/apps/website/ui/DashboardHero.tsx b/apps/website/ui/DashboardHero.tsx index 5a169b92f..aa75355a9 100644 --- a/apps/website/ui/DashboardHero.tsx +++ b/apps/website/ui/DashboardHero.tsx @@ -1,142 +1,146 @@ -import React, { ReactNode } from 'react'; +import React from 'react'; import { Box } from './primitives/Box'; -import { Heading } from './Heading'; -import { Image } from './Image'; +import { Stack } from './primitives/Stack'; import { Text } from './Text'; -import { Glow } from './Glow'; +import { Heading } from './Heading'; +import { Avatar } from './Avatar'; +import { Badge } from './Badge'; +import { Trophy, Flag, Users, Star } from 'lucide-react'; +import { Icon } from './Icon'; interface DashboardHeroProps { driverName: string; - avatarUrl: string; - country: string; - rating: string | number; - rank: string | number; - totalRaces: string | number; - actions?: ReactNode; - stats?: ReactNode; + avatarUrl?: string | null; + rating: number; + rank: number; + totalRaces: number; + winRate: number; className?: string; } -/** - * DashboardHero - * - * Redesigned for "Precision Racing Minimal" theme. - * Uses subtle accent glows and crisp separators. - */ export function DashboardHero({ driverName, avatarUrl, - country, rating, rank, totalRaces, - actions, - stats, + winRate, className = '', }: DashboardHeroProps) { return ( - - {/* Subtle Accent Glow */} - - - - - {/* Driver Identity */} - - - - {driverName} - - - - - - - - Driver Profile - - - / - - - {country} - - - - {driverName} - - - - Rating - {rating} - - - Rank - #{rank} - - - Starts - {totalRaces} - - - + + + {/* Avatar Section */} + + + + + + - - {/* Actions */} - {actions && ( - - {actions} - - )} - {/* Stats Grid */} - {stats && ( - - {stats} + {/* Info Section */} + + + + {driverName} + + + + Rating + {rating} + + + Rank + #{rank} + + + Starts + {totalRaces} + + - )} - + + + + {winRate}% Win Rate + + + Pro License + + + Team Redline + + + + + {/* Quick Stats */} + + + 12 + Podiums + + + + 4 + Wins + + + ); diff --git a/apps/website/ui/ErrorBanner.tsx b/apps/website/ui/ErrorBanner.tsx index 81b529748..453791dda 100644 --- a/apps/website/ui/ErrorBanner.tsx +++ b/apps/website/ui/ErrorBanner.tsx @@ -1,36 +1,63 @@ - - +import React from 'react'; import { Box } from './primitives/Box'; -import { Surface } from './primitives/Surface'; +import { Stack } from './primitives/Stack'; import { Text } from './Text'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; +import { AlertCircle, XCircle, Info, AlertTriangle } from 'lucide-react'; -export interface ErrorBannerProps { - message: string; +interface ErrorBannerProps { title?: string; - variant?: 'error' | 'warning' | 'info'; + message: string; + variant?: 'error' | 'warning' | 'info' | 'success'; } -export function ErrorBanner({ message, title, variant = 'error' }: ErrorBannerProps) { - const variantColors = { - error: { bg: 'rgba(239, 68, 68, 0.1)', border: '#ef4444', text: '#ef4444' }, - warning: { bg: 'rgba(245, 158, 11, 0.1)', border: '#f59e0b', text: '#fcd34d' }, - info: { bg: 'rgba(59, 130, 246, 0.1)', border: '#3b82f6', text: '#3b82f6' }, +export function ErrorBanner({ title, message, variant = 'error' }: ErrorBannerProps) { + const configs = { + error: { + bg: 'rgba(239, 68, 68, 0.1)', + border: 'rgba(239, 68, 68, 0.2)', + text: 'rgb(248, 113, 113)', + icon: XCircle + }, + warning: { + bg: 'rgba(245, 158, 11, 0.1)', + border: 'rgba(245, 158, 11, 0.2)', + text: 'rgb(251, 191, 36)', + icon: AlertTriangle + }, + info: { + bg: 'rgba(59, 130, 246, 0.1)', + border: 'rgba(59, 130, 246, 0.2)', + text: 'rgb(96, 165, 250)', + icon: Info + }, + success: { + bg: 'rgba(16, 185, 129, 0.1)', + border: 'rgba(16, 185, 129, 0.2)', + text: 'rgb(52, 211, 153)', + icon: AlertCircle + } }; - const colors = variantColors[variant]; + const colors = configs[variant]; return ( - - {title && {title}} - {message} - + + + + {title && {title}} + {message} + + ); } diff --git a/apps/website/ui/FormSection.tsx b/apps/website/ui/FormSection.tsx index 0169927a9..3f685fe08 100644 --- a/apps/website/ui/FormSection.tsx +++ b/apps/website/ui/FormSection.tsx @@ -20,7 +20,11 @@ export function FormSection({ children, title }: FormSectionProps) { size="xs" weight="bold" color="text-gray-500" - className="uppercase tracking-widest border-b border-border-gray pb-1" + uppercase + letterSpacing="widest" + borderBottom + borderColor="border-border-gray" + pb={1} > {title} diff --git a/apps/website/ui/Heading.tsx b/apps/website/ui/Heading.tsx index f7881e22c..433296ef4 100644 --- a/apps/website/ui/Heading.tsx +++ b/apps/website/ui/Heading.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, ElementType } from 'react'; import { Stack } from './primitives/Stack'; -import { Box, BoxProps } from './primitives/Box'; +import { Box, BoxProps, ResponsiveValue } from './primitives/Box'; interface ResponsiveFontSize { base?: string; @@ -18,11 +18,13 @@ interface HeadingProps extends Omit, 'children' | 'as' | 'fontSiz id?: string; groupHoverColor?: string; truncate?: boolean; + uppercase?: boolean; fontSize?: string | ResponsiveFontSize; - weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold'; + weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | string; + letterSpacing?: string; } -export function Heading({ level, children, icon, groupHoverColor, truncate, fontSize, weight, ...props }: HeadingProps) { +export function Heading({ level, children, icon, groupHoverColor, truncate, uppercase, fontSize, weight, letterSpacing, ...props }: HeadingProps) { const Tag = `h${level}` as ElementType; const levelClasses = { @@ -34,7 +36,7 @@ export function Heading({ level, children, icon, groupHoverColor, truncate, font 6: 'text-xs font-bold text-white tracking-tight uppercase tracking-widest', }; - const weightClasses = { + const weightClasses: Record = { light: 'font-light', normal: 'font-normal', medium: 'font-medium', @@ -67,14 +69,24 @@ export function Heading({ level, children, icon, groupHoverColor, truncate, font const classes = [ levelClasses[level], getFontSizeClasses(fontSize), - weight ? weightClasses[weight] : '', + weight && weightClasses[weight as keyof typeof weightClasses] ? weightClasses[weight as keyof typeof weightClasses] : '', + letterSpacing ? `tracking-${letterSpacing}` : '', + uppercase ? 'uppercase' : '', groupHoverColor ? `group-hover:text-${groupHoverColor}` : '', truncate ? 'truncate' : '', props.className ].filter(Boolean).join(' '); return ( - + {content} ); diff --git a/apps/website/ui/HorizontalStatCard.tsx b/apps/website/ui/HorizontalStatCard.tsx index f224a9130..acba3fec6 100644 --- a/apps/website/ui/HorizontalStatCard.tsx +++ b/apps/website/ui/HorizontalStatCard.tsx @@ -1,52 +1,46 @@ - - -import { ReactNode } from 'react'; +import React from 'react'; import { Box } from './primitives/Box'; -import { Card } from './Card'; import { Stack } from './primitives/Stack'; -import { Surface } from './primitives/Surface'; import { Text } from './Text'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; +import { LucideIcon } from 'lucide-react'; interface HorizontalStatCardProps { label: string; value: string | number; - subValue?: string; - icon: ReactNode; + icon: LucideIcon; + iconColor?: string; iconBgColor?: string; } export function HorizontalStatCard({ label, value, - subValue, icon, - iconBgColor, + iconColor = 'text-primary-blue', + iconBgColor = 'rgba(59, 130, 246, 0.1)', }: HorizontalStatCardProps) { return ( - - - + + - {icon} + - + {label} - + {value} - {subValue && ( - - {subValue} - - )} - + ); } diff --git a/apps/website/ui/Icon.tsx b/apps/website/ui/Icon.tsx index e56d6c8f9..75a2d7d28 100644 --- a/apps/website/ui/Icon.tsx +++ b/apps/website/ui/Icon.tsx @@ -2,14 +2,29 @@ import React from 'react'; import { LucideIcon } from 'lucide-react'; import { Box, BoxProps } from './primitives/Box'; -interface IconProps extends Omit, 'children' | 'as'> { - icon: LucideIcon; +export interface IconProps extends Omit, 'children'> { + icon: LucideIcon | React.ReactNode; size?: number | string; color?: string; strokeWidth?: number; + animate?: string; + transition?: boolean; + groupHoverTextColor?: string; + groupHoverScale?: boolean; } -export function Icon({ icon: LucideIcon, size = 4, color, className = '', style, ...props }: IconProps) { +export function Icon({ + icon: IconProp, + size = 4, + color, + className = '', + style, + animate, + transition, + groupHoverTextColor, + groupHoverScale, + ...props +}: IconProps) { const sizeMap: Record = { 3: 'w-3 h-3', 3.5: 'w-3.5 h-3.5', @@ -31,13 +46,35 @@ export function Icon({ icon: LucideIcon, size = 4, color, className = '', style, const combinedStyle = color && !isTailwindColor ? { color, ...style } : style; const boxColor = isTailwindColor ? color : undefined; + const classes = [ + sizeClass, + animate === 'spin' ? 'animate-spin' : '', + transition ? 'transition-all duration-150' : '', + groupHoverTextColor ? `group-hover:text-${groupHoverTextColor}` : '', + groupHoverScale ? 'group-hover:scale-110 transition-transform' : '', + className + ].filter(Boolean).join(' '); + + const renderIcon = () => { + if (!IconProp) return null; + if (typeof IconProp === 'function' || (typeof IconProp === 'object' && 'render' in IconProp)) { + const LucideIconComponent = IconProp as LucideIcon; + return ; + } + return IconProp; + }; + return ( + > + {renderIcon()} + ); } diff --git a/apps/website/ui/IconButton.tsx b/apps/website/ui/IconButton.tsx index b898b4e8f..40bdc82e9 100644 --- a/apps/website/ui/IconButton.tsx +++ b/apps/website/ui/IconButton.tsx @@ -1,5 +1,3 @@ - - import React from 'react'; import { LucideIcon } from 'lucide-react'; import { Button } from './Button'; @@ -29,9 +27,9 @@ export function IconButton({ backgroundColor, }: IconButtonProps) { const sizeMap = { - sm: { btn: 'w-8 h-8 p-0', icon: 4 }, - md: { btn: 'w-10 h-10 p-0', icon: 5 }, - lg: { btn: 'w-12 h-12 p-0', icon: 6 }, + sm: { w: '8', h: '8', icon: 4 }, + md: { w: '10', h: '10', icon: 5 }, + lg: { w: '12', h: '12', icon: 6 }, }; return ( @@ -40,7 +38,14 @@ export function IconButton({ onClick={onClick} title={title} disabled={disabled} - className={`${sizeMap[size].btn} rounded-full flex items-center justify-center min-h-0 ${className}`} + w={sizeMap[size].w} + h={sizeMap[size].h} + p={0} + rounded="full" + display="flex" + center + minHeight="0" + className={className} backgroundColor={backgroundColor} > diff --git a/apps/website/ui/InfoBanner.tsx b/apps/website/ui/InfoBanner.tsx index 312cc61e6..4a636c1af 100644 --- a/apps/website/ui/InfoBanner.tsx +++ b/apps/website/ui/InfoBanner.tsx @@ -1,83 +1,75 @@ - - -import { AlertTriangle, CheckCircle, Info, LucideIcon, XCircle } from 'lucide-react'; import React from 'react'; import { Box } from './primitives/Box'; -import { Icon } from './Icon'; import { Stack } from './primitives/Stack'; -import { Surface } from './primitives/Surface'; import { Text } from './Text'; - -type BannerType = 'info' | 'warning' | 'success' | 'error'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; +import { Info, AlertTriangle, AlertCircle, CheckCircle, LucideIcon } from 'lucide-react'; interface InfoBannerProps { - type?: BannerType; title?: string; - children: React.ReactNode; + message?: string; + children?: React.ReactNode; + variant?: 'info' | 'warning' | 'error' | 'success'; + type?: 'info' | 'warning' | 'error' | 'success'; icon?: LucideIcon; } -export function InfoBanner({ - type = 'info', - title, - children, - icon: CustomIcon, -}: InfoBannerProps) { - const bannerConfig: Record = { +export function InfoBanner({ title, message, children, variant = 'info', type, icon }: InfoBannerProps) { + const configs = { info: { - icon: Info, - bg: 'rgba(38, 38, 38, 0.3)', - border: 'rgba(38, 38, 38, 0.5)', - titleColor: 'text-gray-300', - iconColor: '#9ca3af', + bg: 'rgba(59, 130, 246, 0.1)', + border: 'rgba(59, 130, 246, 0.2)', + iconColor: 'rgb(96, 165, 250)', + icon: Info }, warning: { - icon: AlertTriangle, bg: 'rgba(245, 158, 11, 0.1)', - border: 'rgba(245, 158, 11, 0.3)', - titleColor: 'text-warning-amber', - iconColor: '#f59e0b', - }, - success: { - icon: CheckCircle, - bg: 'rgba(16, 185, 129, 0.1)', - border: 'rgba(16, 185, 129, 0.3)', - titleColor: 'text-performance-green', - iconColor: '#10b981', + border: 'rgba(245, 158, 11, 0.2)', + iconColor: 'rgb(251, 191, 36)', + icon: AlertTriangle }, error: { - icon: XCircle, bg: 'rgba(239, 68, 68, 0.1)', - border: 'rgba(239, 68, 68, 0.3)', - titleColor: 'text-error-red', - iconColor: '#ef4444', + border: 'rgba(239, 68, 68, 0.2)', + iconColor: 'rgb(248, 113, 113)', + icon: AlertCircle }, + success: { + bg: 'rgba(16, 185, 129, 0.1)', + border: 'rgba(16, 185, 129, 0.2)', + iconColor: 'rgb(52, 211, 153)', + icon: CheckCircle + } }; - const config = bannerConfig[type]; - const BannerIcon = CustomIcon || config.icon; - + const activeVariant = type || variant; + const config = configs[activeVariant as keyof typeof configs] || configs.info; + const BannerIcon = icon || config.icon; + return ( - + {title && ( - {title} + + {title} + )} - {children} + {message && ( + + {message} + + )} + {children} diff --git a/apps/website/ui/InfoBox.tsx b/apps/website/ui/InfoBox.tsx index 3055527b6..6bedfd120 100644 --- a/apps/website/ui/InfoBox.tsx +++ b/apps/website/ui/InfoBox.tsx @@ -1,62 +1,63 @@ import React from 'react'; -import { Surface } from './primitives/Surface'; -import { Stack } from './primitives/Stack'; import { Box } from './primitives/Box'; -import { Icon } from './Icon'; +import { Stack } from './primitives/Stack'; import { Text } from './Text'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; import { LucideIcon } from 'lucide-react'; interface InfoBoxProps { - icon: LucideIcon; title: string; description: string; - variant?: 'primary' | 'success' | 'warning' | 'default'; + icon: LucideIcon; + variant?: 'info' | 'warning' | 'error' | 'success'; } -export function InfoBox({ icon, title, description, variant = 'default' }: InfoBoxProps) { - const variantColors = { - primary: { +export function InfoBox({ title, description, icon, variant = 'info' }: InfoBoxProps) { + const configs = { + info: { bg: 'rgba(59, 130, 246, 0.1)', - border: '#3b82f6', - text: '#3b82f6', - icon: '#3b82f6' - }, - success: { - bg: 'rgba(16, 185, 129, 0.1)', - border: '#10b981', - text: '#10b981', - icon: '#10b981' + border: 'rgba(59, 130, 246, 0.2)', + icon: 'rgb(96, 165, 250)', + text: 'text-white' }, warning: { bg: 'rgba(245, 158, 11, 0.1)', - border: '#f59e0b', - text: '#f59e0b', - icon: '#f59e0b' + border: 'rgba(245, 158, 11, 0.2)', + icon: 'rgb(251, 191, 36)', + text: 'text-white' }, - default: { - bg: 'rgba(38, 38, 38, 0.3)', - border: '#262626', - text: 'white', - icon: '#9ca3af' + error: { + bg: 'rgba(239, 68, 68, 0.1)', + border: 'rgba(239, 68, 68, 0.2)', + icon: 'rgb(248, 113, 113)', + text: 'text-white' + }, + success: { + bg: 'rgba(16, 185, 129, 0.1)', + border: 'rgba(16, 185, 129, 0.2)', + icon: 'rgb(52, 211, 153)', + text: 'text-white' } }; - const colors = variantColors[variant]; + const colors = configs[variant]; return ( - + - {title} + {title} {description} diff --git a/apps/website/ui/Input.tsx b/apps/website/ui/Input.tsx index 74d6e549e..30c088ca9 100644 --- a/apps/website/ui/Input.tsx +++ b/apps/website/ui/Input.tsx @@ -1,26 +1,30 @@ -import React, { forwardRef, InputHTMLAttributes } from 'react'; -import { Text } from './Text'; +import React, { forwardRef, ReactNode } from 'react'; import { Box } from './primitives/Box'; import { Stack } from './primitives/Stack'; +import { Text } from './Text'; -interface InputProps extends InputHTMLAttributes { - variant?: 'default' | 'error'; +interface InputProps extends React.InputHTMLAttributes { + label?: string; + icon?: ReactNode; errorMessage?: string; - icon?: React.ReactNode; - label?: React.ReactNode; + variant?: 'default' | 'error'; } export const Input = forwardRef( - ({ className = '', variant = 'default', errorMessage, icon, label, ...props }, ref) => { - const baseClasses = 'px-3 py-2 border rounded-sm text-white bg-graphite-black focus:outline-none focus:border-primary-accent transition-all duration-150 ease-smooth w-full text-sm placeholder:text-gray-600'; - const variantClasses = (variant === 'error' || errorMessage) ? 'border-critical-red' : 'border-border-gray'; - const iconClasses = icon ? 'pl-10' : ''; - const classes = `${baseClasses} ${variantClasses} ${iconClasses} ${className}`; + ({ label, icon, errorMessage, variant = 'default', className = '', ...props }, ref) => { + const isError = variant === 'error' || !!errorMessage; + + const baseClasses = 'w-full px-4 py-2 bg-deep-graphite border rounded-lg text-white placeholder:text-gray-500 focus:outline-none transition-all duration-150 sm:text-sm'; + const variantClasses = isError + ? 'border-warning-amber focus:border-warning-amber focus:ring-1 focus:ring-warning-amber' + : 'border-charcoal-outline focus:border-primary-blue focus:ring-1 focus:ring-primary-blue'; + + const classes = `${baseClasses} ${variantClasses} ${icon ? 'pl-11' : ''} ${className}`; return ( {label && ( - + {label} )} @@ -28,20 +32,21 @@ export const Input = forwardRef( {icon && ( {icon} )} {errorMessage && ( - + {errorMessage} )} diff --git a/apps/website/ui/Link.tsx b/apps/website/ui/Link.tsx index eb546b496..b3c5814e9 100644 --- a/apps/website/ui/Link.tsx +++ b/apps/website/ui/Link.tsx @@ -1,19 +1,19 @@ import React, { ReactNode } from 'react'; import { Box, BoxProps } from './primitives/Box'; -interface LinkProps extends Omit, 'children' | 'className' | 'onClick'> { +export interface LinkProps extends Omit, 'children' | 'onClick'> { href: string; children: ReactNode; - className?: string; variant?: 'primary' | 'secondary' | 'ghost'; size?: 'xs' | 'sm' | 'md' | 'lg'; target?: '_blank' | '_self' | '_parent' | '_top'; rel?: string; onClick?: React.MouseEventHandler; - style?: React.CSSProperties; block?: boolean; - weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold'; + weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | string; truncate?: boolean; + hoverColor?: string; + transition?: boolean; } export function Link({ @@ -25,10 +25,11 @@ export function Link({ target = '_self', rel = '', onClick, - style, block = false, weight, truncate, + hoverColor, + transition, ...props }: LinkProps) { const baseClasses = 'inline-flex items-center transition-colors'; @@ -46,7 +47,7 @@ export function Link({ lg: 'text-lg' }; - const weightClasses = { + const weightClasses: Record = { light: 'font-light', normal: 'font-normal', medium: 'font-medium', @@ -58,8 +59,10 @@ export function Link({ block ? 'flex' : baseClasses, variantClasses[variant], sizeClasses[size], - weight ? weightClasses[weight] : '', + weight && weightClasses[weight] ? weightClasses[weight] : '', truncate ? 'truncate' : '', + hoverColor ? `hover:${hoverColor}` : '', + transition ? 'transition-all duration-150' : '', className ].filter(Boolean).join(' '); @@ -71,7 +74,10 @@ export function Link({ target={target} rel={rel} onClick={onClick} - style={style} + style={{ + ...(weight && !weightClasses[weight] ? { fontWeight: weight } : {}), + ...(props.style || {}) + }} {...props} > {children} diff --git a/apps/website/ui/LoadingSpinner.tsx b/apps/website/ui/LoadingSpinner.tsx index 4542ea786..f90541c96 100644 --- a/apps/website/ui/LoadingSpinner.tsx +++ b/apps/website/ui/LoadingSpinner.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Box } from './primitives/Box'; interface LoadingSpinnerProps { size?: number; @@ -7,19 +8,17 @@ interface LoadingSpinnerProps { } export function LoadingSpinner({ size = 8, color = '#3b82f6', className = '' }: LoadingSpinnerProps) { - const style: React.CSSProperties = { - width: `${size * 0.25}rem`, - height: `${size * 0.25}rem`, - border: '2px solid transparent', - borderTopColor: color, - borderLeftColor: color, - borderRadius: '9999px', - }; - return ( -
diff --git a/apps/website/ui/MediaPreviewCard.tsx b/apps/website/ui/MediaPreviewCard.tsx index d1a6fb0a2..488aa618c 100644 --- a/apps/website/ui/MediaPreviewCard.tsx +++ b/apps/website/ui/MediaPreviewCard.tsx @@ -1,93 +1,100 @@ import React from 'react'; import { Box } from './primitives/Box'; -import { Text } from './Text'; -import { ImagePlaceholder } from './ImagePlaceholder'; import { Image } from './Image'; +import { Surface } from './primitives/Surface'; +import { Text } from './Text'; +import { Play, Image as ImageIcon } from 'lucide-react'; +import { Icon } from './Icon'; -export interface MediaPreviewCardProps { - src?: string; - alt?: string; +interface MediaPreviewCardProps { + type: 'image' | 'video'; + src: string; + alt: string; title?: string; subtitle?: string; + onClick?: () => void; aspectRatio?: string; isLoading?: boolean; - error?: string; - onClick?: () => void; className?: string; - actions?: React.ReactNode; } export function MediaPreviewCard({ + type, src, - alt = 'Media preview', + alt, title, - subtitle, - aspectRatio = '16/9', - isLoading, - error, onClick, + aspectRatio = '16/9', + isLoading = false, className = '', - actions, }: MediaPreviewCardProps) { return ( - - + {isLoading ? ( - - ) : error ? ( - - ) : src ? ( + + ) : ( {alt} - ) : ( - )} - - {actions && ( + + {/* Overlay */} + + + + + + + {title && ( - {actions} + + {title} + )} - - {(title || subtitle) && ( - - {title && ( - - {title} - - )} - {subtitle && ( - - {subtitle} - - )} - - )} - + ); } diff --git a/apps/website/ui/Modal.tsx b/apps/website/ui/Modal.tsx index 12cb2c91a..c2fae7030 100644 --- a/apps/website/ui/Modal.tsx +++ b/apps/website/ui/Modal.tsx @@ -1,138 +1,144 @@ - - -import React, { - type KeyboardEvent as ReactKeyboardEvent, - type ReactNode, -} from 'react'; +import React, { ReactNode } from 'react'; import { Box } from './primitives/Box'; -import { Button } from './Button'; -import { Heading } from './Heading'; import { Stack } from './primitives/Stack'; +import { Button } from './Button'; import { Text } from './Text'; +import { X } from 'lucide-react'; +import { IconButton } from './IconButton'; interface ModalProps { - title: string; - description?: string; - icon?: ReactNode; - children?: ReactNode; - primaryActionLabel?: string; - secondaryActionLabel?: string; - onPrimaryAction?: () => void | Promise; - onSecondaryAction?: () => void; - onOpenChange?: (open: boolean) => void; isOpen: boolean; + onClose?: () => void; + onOpenChange?: (open: boolean) => void; + title?: string; + description?: string; + icon?: React.ReactNode; + children: ReactNode; footer?: ReactNode; + primaryActionLabel?: string; + onPrimaryAction?: () => void; + secondaryActionLabel?: string; + onSecondaryAction?: () => void; + isLoading?: boolean; + size?: 'sm' | 'md' | 'lg' | 'xl'; } export function Modal({ + isOpen, + onClose, + onOpenChange, title, description, icon, children, - primaryActionLabel, - secondaryActionLabel, - onPrimaryAction, - onSecondaryAction, - onOpenChange, - isOpen, footer, + primaryActionLabel, + onPrimaryAction, + secondaryActionLabel, + onSecondaryAction, + isLoading = false, + size = 'md', }: ModalProps) { - const handleKeyDown = (event: ReactKeyboardEvent) => { - if (event.key === 'Escape') { - if (onOpenChange) { - onOpenChange(false); - } - return; - } + if (!isOpen) return null; + + const sizeMap = { + sm: 'max-w-md', + md: 'max-w-lg', + lg: 'max-w-2xl', + xl: 'max-w-4xl', }; - const handleBackdropClick = (event: React.MouseEvent) => { - if (event.target === event.currentTarget && onOpenChange) { - onOpenChange(false); - } + const handleClose = () => { + if (onClose) onClose(); + if (onOpenChange) onOpenChange(false); }; - if (!isOpen) { - return null; - } - return ( + {/* Backdrop click to close */} + + - - - {icon && {icon}} - - {title} - {description && ( - - {description} - - )} - + {/* Header */} + + + + {icon && {icon}} + + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + + + - + {/* Content */} + {children} + {/* Footer */} {(primaryActionLabel || secondaryActionLabel || footer) && ( - - {(primaryActionLabel || secondaryActionLabel) && ( - + + {footer || ( + {secondaryActionLabel && ( )} {primaryActionLabel && ( )} - - )} - {footer && ( - - {footer} - + )} )} diff --git a/apps/website/ui/OnboardingError.tsx b/apps/website/ui/OnboardingError.tsx index 613d56da8..470ce6a5c 100644 --- a/apps/website/ui/OnboardingError.tsx +++ b/apps/website/ui/OnboardingError.tsx @@ -1,12 +1,26 @@ +import React from 'react'; +import { Box } from './primitives/Box'; +import { Text } from './Text'; + interface OnboardingErrorProps { message: string; } export function OnboardingError({ message }: OnboardingErrorProps) { return ( -
- âš  -

{message}

-
+ + âš  + {message} + ); -} \ No newline at end of file +} diff --git a/apps/website/ui/OnboardingForm.tsx b/apps/website/ui/OnboardingForm.tsx index 20bc2890e..2f0429aff 100644 --- a/apps/website/ui/OnboardingForm.tsx +++ b/apps/website/ui/OnboardingForm.tsx @@ -1,12 +1,15 @@ +import React, { ReactNode, FormEvent } from 'react'; +import { Box } from './primitives/Box'; + interface OnboardingFormProps { - children: React.ReactNode; - onSubmit: (e: React.FormEvent) => void | Promise; + children: ReactNode; + onSubmit: (e: FormEvent) => void; } export function OnboardingForm({ children, onSubmit }: OnboardingFormProps) { return ( -
+ {children} - +
); -} \ No newline at end of file +} diff --git a/apps/website/ui/OnboardingStepPanel.tsx b/apps/website/ui/OnboardingStepPanel.tsx index b3e299ebd..13972f861 100644 --- a/apps/website/ui/OnboardingStepPanel.tsx +++ b/apps/website/ui/OnboardingStepPanel.tsx @@ -1,18 +1,20 @@ -import { Surface } from '@/ui/primitives/Surface'; +import React, { ReactNode } from 'react'; +import { Surface } from './primitives/Surface'; interface OnboardingStepPanelProps { - children: React.ReactNode; + children: ReactNode; className?: string; } export function OnboardingStepPanel({ children, className = '' }: OnboardingStepPanelProps) { return ( {children} diff --git a/apps/website/ui/ProfileStatGrid.tsx b/apps/website/ui/ProfileStatGrid.tsx index 58d882745..d47ef1ebb 100644 --- a/apps/website/ui/ProfileStatGrid.tsx +++ b/apps/website/ui/ProfileStatGrid.tsx @@ -1,8 +1,7 @@ - - -import { Box } from '@/ui/primitives/Box'; -import { Grid } from '@/ui/primitives/Grid'; -import { Text } from '@/ui/Text'; +import React from 'react'; +import { Box } from './primitives/Box'; +import { Text } from './Text'; +import { Grid } from './primitives/Grid'; interface Stat { label: string; @@ -16,11 +15,19 @@ interface ProfileStatGridProps { export function ProfileStatGrid({ stats }: ProfileStatGridProps) { return ( - + {stats.map((stat, idx) => ( - + {stat.value} - {stat.label} + {stat.label} ))} diff --git a/apps/website/ui/SectionHeader.tsx b/apps/website/ui/SectionHeader.tsx index 124b333ce..8c8ca2552 100644 --- a/apps/website/ui/SectionHeader.tsx +++ b/apps/website/ui/SectionHeader.tsx @@ -1,46 +1,50 @@ - - -import { LucideIcon } from 'lucide-react'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { Box } from './primitives/Box'; -import { Heading } from './Heading'; -import { Icon } from './Icon'; import { Stack } from './primitives/Stack'; -import { Surface } from './primitives/Surface'; import { Text } from './Text'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; +import { LucideIcon } from 'lucide-react'; interface SectionHeaderProps { - icon: LucideIcon; title: string; description?: string; - action?: React.ReactNode; + icon?: LucideIcon; color?: string; + actions?: ReactNode; } -export function SectionHeader({ - icon, - title, - description, - action, - color = '#3b82f6' -}: SectionHeaderProps) { +export function SectionHeader({ title, description, icon, color = 'text-primary-blue', actions }: SectionHeaderProps) { return ( - + - - - + + {icon && ( + - - {title} - {description && ( - {description} - )} - - - - {action && {action}} + )} + + + {title} + + {description && ( + + {description} + + )} + + + {actions && ( + + {actions} + + )} ); diff --git a/apps/website/ui/SegmentedControl.tsx b/apps/website/ui/SegmentedControl.tsx index 51c92a299..5c4794671 100644 --- a/apps/website/ui/SegmentedControl.tsx +++ b/apps/website/ui/SegmentedControl.tsx @@ -1,5 +1,3 @@ - - import { Box } from './primitives/Box'; import { Stack } from './primitives/Stack'; import { Text } from './Text'; @@ -29,7 +27,16 @@ export function SegmentedControl({ }; return ( - + {options.map((option) => { const isSelected = option.value === value; @@ -41,24 +48,28 @@ export function SegmentedControl({ onClick={() => handleSelect(option.value, option.disabled)} aria-pressed={isSelected} disabled={option.disabled} - style={{ - flex: 1, - minWidth: '140px', - padding: '0.375rem 0.75rem', - borderRadius: '9999px', - transition: 'all 0.2s', - textAlign: 'left', - backgroundColor: isSelected ? '#3b82f6' : 'transparent', - color: isSelected ? 'white' : '#d1d5db', - opacity: option.disabled ? 0.5 : 1, - cursor: option.disabled ? 'not-allowed' : 'pointer', - border: 'none' - }} + flex={1} + minWidth="140px" + px={3} + py={1.5} + rounded="full" + transition="all 0.2s" + textAlign="left" + bg={isSelected ? 'bg-primary-blue' : 'transparent'} + color={isSelected ? 'text-white' : 'text-gray-400'} + opacity={option.disabled ? 0.5 : 1} + cursor={option.disabled ? 'not-allowed' : 'pointer'} + border="none" > {option.label} {option.description && ( - + {option.description} )} @@ -66,6 +77,6 @@ export function SegmentedControl({ ); })} - + ); } diff --git a/apps/website/ui/Select.tsx b/apps/website/ui/Select.tsx index 57d222e8f..3026f54c5 100644 --- a/apps/website/ui/Select.tsx +++ b/apps/website/ui/Select.tsx @@ -1,4 +1,5 @@ -import React, { ChangeEvent, SelectHTMLAttributes } from 'react'; +import React, { forwardRef, ReactNode } from 'react'; +import { Box } from './primitives/Box'; import { Stack } from './primitives/Stack'; import { Text } from './Text'; @@ -7,64 +8,57 @@ interface SelectOption { label: string; } -interface SelectProps extends SelectHTMLAttributes { - id?: string; - 'aria-label'?: string; - value?: string; - onChange?: (e: ChangeEvent) => void; - options: SelectOption[]; - className?: string; - style?: React.CSSProperties; +interface SelectProps extends React.SelectHTMLAttributes { label?: string; fullWidth?: boolean; pl?: number; + errorMessage?: string; + variant?: 'default' | 'error'; + options?: SelectOption[]; } -export function Select({ - id, - 'aria-label': ariaLabel, - value, - onChange, - options, - className = '', - style, - label, - fullWidth = true, - pl, - ...props -}: SelectProps) { - const spacingMap: Record = { - 10: 'pl-10' - }; - const defaultClasses = `${fullWidth ? 'w-full' : 'w-auto'} px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:outline-none focus:border-primary-blue transition-colors`; - const classes = [ - defaultClasses, - pl !== undefined ? spacingMap[pl] : '', - className - ].filter(Boolean).join(' '); +export const Select = forwardRef( + ({ label, fullWidth = true, pl, errorMessage, variant = 'default', options, children, className = '', style, ...props }, ref) => { + const isError = variant === 'error' || !!errorMessage; + + const variantClasses = isError + ? 'border-warning-amber focus:border-warning-amber' + : 'border-charcoal-outline focus:border-primary-blue'; + + const defaultClasses = `${fullWidth ? 'w-full' : 'w-auto'} px-3 py-2 bg-deep-graphite border rounded-lg text-white focus:outline-none transition-colors`; + const classes = [ + defaultClasses, + variantClasses, + pl ? `pl-${pl}` : '', + className + ].filter(Boolean).join(' '); - return ( - - {label && ( - - {label} - - )} - - - ); -} + return ( + + {label && ( + + {label} + + )} + + {options ? options.map(opt => ( + + )) : children} + + {errorMessage && ( + + {errorMessage} + + )} + + ); + } +); + +Select.displayName = 'Select'; diff --git a/apps/website/ui/SimpleCheckbox.tsx b/apps/website/ui/SimpleCheckbox.tsx index 7e04690c9..75eda2cd2 100644 --- a/apps/website/ui/SimpleCheckbox.tsx +++ b/apps/website/ui/SimpleCheckbox.tsx @@ -28,7 +28,8 @@ export function SimpleCheckbox({ checked, onChange, disabled, 'aria-label': aria borderColor="border-charcoal-outline" rounded="sm" aria-label={ariaLabel} - className="text-primary-blue focus:ring-primary-blue" + ring="primary-blue" + color="text-primary-blue" /> ); } diff --git a/apps/website/ui/Skeleton.tsx b/apps/website/ui/Skeleton.tsx index 97c145f17..c4a449a96 100644 --- a/apps/website/ui/Skeleton.tsx +++ b/apps/website/ui/Skeleton.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Box } from './primitives/Box'; interface SkeletonProps { width?: string | number; @@ -8,17 +9,13 @@ interface SkeletonProps { } export function Skeleton({ width, height, circle, className = '' }: SkeletonProps) { - const style: React.CSSProperties = { - width: width, - height: height, - borderRadius: circle ? '9999px' : '0.375rem', - backgroundColor: 'rgba(38, 38, 38, 0.4)', - }; - return ( -
diff --git a/apps/website/ui/StatCard.tsx b/apps/website/ui/StatCard.tsx index 48b2d2de0..266f55473 100644 --- a/apps/website/ui/StatCard.tsx +++ b/apps/website/ui/StatCard.tsx @@ -1,115 +1,115 @@ - - -import { motion, useReducedMotion } from 'framer-motion'; -import { ArrowDownRight, ArrowUpRight, LucideIcon } from 'lucide-react'; +import React, { ReactNode } from 'react'; import { Box } from './primitives/Box'; -import { Card } from './Card'; -import { Icon } from './Icon'; import { Stack } from './primitives/Stack'; import { Text } from './Text'; +import { Card } from './Card'; +import { Icon } from './Icon'; +import { LucideIcon } from 'lucide-react'; interface StatCardProps { label: string; value: string | number; - subValue?: string; icon?: LucideIcon; - variant?: 'blue' | 'purple' | 'green' | 'orange'; - className?: string; trend?: { value: number; isPositive: boolean; }; + variant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info'; + className?: string; + onClick?: () => void; prefix?: string; suffix?: string; delay?: number; } -export function StatCard({ - label, - value, - subValue, +export function StatCard({ + label, + value, icon, - variant = 'blue', - className = '', trend, - prefix = '', - suffix = '', - delay = 0, + variant = 'default', + className = '', + onClick, + prefix, + suffix, + delay, }: StatCardProps) { - const shouldReduceMotion = useReducedMotion(); - const variantClasses = { - blue: 'bg-gradient-to-br from-blue-900/20 to-blue-700/10 border-blue-500/30', - purple: 'bg-gradient-to-br from-purple-900/20 to-purple-700/10 border-purple-500/30', - green: 'bg-gradient-to-br from-green-900/20 to-green-700/10 border-green-500/30', - orange: 'bg-gradient-to-br from-orange-900/20 to-orange-700/10 border-orange-500/30' + default: 'bg-panel-gray border-border-gray', + primary: 'bg-primary-accent/5 border-primary-accent/20', + success: 'bg-success-green/5 border-success-green/20', + warning: 'bg-warning-amber/5 border-warning-amber/20', + danger: 'bg-critical-red/5 border-critical-red/20', + info: 'bg-telemetry-aqua/5 border-telemetry-aqua/20', }; - + + const iconBgClasses = { + default: 'bg-white/5', + primary: 'bg-primary-accent/10', + success: 'bg-success-green/10', + warning: 'bg-warning-amber/10', + danger: 'bg-critical-red/10', + info: 'bg-telemetry-aqua/10', + }; + const iconColorClasses = { - blue: 'text-primary-blue', - purple: 'text-purple-400', - green: 'text-performance-green', - orange: 'text-warning-amber' + default: 'text-gray-400', + primary: 'text-primary-accent', + success: 'text-success-green', + warning: 'text-warning-amber', + danger: 'text-critical-red', + info: 'text-telemetry-aqua', }; - + const cardContent = ( - + - + + + {label} + {icon && ( - + )} + + + + + {prefix}{value}{suffix} + {trend && ( - - - {Math.abs(trend.value)}% + + + {trend.isPositive ? '+' : ''}{trend.value}% + + + vs last period + )} - - - {prefix}{typeof value === 'number' ? value.toLocaleString() : value}{suffix} - - {label} - {subValue && ( - - {subValue} - - )} - ); - if (shouldReduceMotion) { - return {cardContent}; + if (onClick) { + return ( + + {cardContent} + + ); } - return ( - - {cardContent} - - ); + return cardContent; } diff --git a/apps/website/ui/StatGridItem.tsx b/apps/website/ui/StatGridItem.tsx index e6583aa03..9a86a91b9 100644 --- a/apps/website/ui/StatGridItem.tsx +++ b/apps/website/ui/StatGridItem.tsx @@ -1,37 +1,36 @@ -import React from 'react'; -import { LucideIcon } from 'lucide-react'; +import React, { ReactNode } from 'react'; import { Box } from './primitives/Box'; +import { Stack } from './primitives/Stack'; import { Text } from './Text'; import { Icon } from './Icon'; -import { Stack } from './primitives/Stack'; +import { LucideIcon } from 'lucide-react'; interface StatGridItemProps { label: string; value: string | number; - color?: string; icon?: LucideIcon; + color?: string; } -export function StatGridItem({ label, value, color = 'text-white', icon }: StatGridItemProps) { +/** + * StatGridItem + * + * A simple stat display for use in a grid. + */ +export function StatGridItem({ label, value, icon, color = 'text-primary-blue' }: StatGridItemProps) { return ( - + {icon && ( - + - {label} )} - {!icon && ( - {label} - )} - {value} + + {value} + + + {label} + ); } diff --git a/apps/website/ui/SummaryItem.tsx b/apps/website/ui/SummaryItem.tsx index 21a2dd23d..605f06dc7 100644 --- a/apps/website/ui/SummaryItem.tsx +++ b/apps/website/ui/SummaryItem.tsx @@ -1,43 +1,48 @@ - - -import { ReactNode } from 'react'; +import React from 'react'; import { Box } from './primitives/Box'; -import { Surface } from './primitives/Surface'; import { Text } from './Text'; +import { Surface } from './primitives/Surface'; +import { Icon } from './Icon'; +import { LucideIcon } from 'lucide-react'; interface SummaryItemProps { - title: string; - subtitle?: string; - rightContent?: ReactNode; + label?: string; + value?: string | number; + icon?: LucideIcon; onClick?: () => void; + title?: string; + subtitle?: string; + rightContent?: React.ReactNode; } -export function SummaryItem({ - title, - subtitle, - rightContent, - onClick, -}: SummaryItemProps) { +export function SummaryItem({ label, value, icon, onClick, title, subtitle, rightContent }: SummaryItemProps) { return ( - - - {title} - - {subtitle && ( - - {subtitle} + {icon && ( + + + + )} + + {(label || title) && ( + + {label || title} + + )} + {(value || subtitle) && ( + + {value || subtitle} )} diff --git a/apps/website/ui/TabNavigation.tsx b/apps/website/ui/TabNavigation.tsx index c1b1cbe84..f0a7ffb8f 100644 --- a/apps/website/ui/TabNavigation.tsx +++ b/apps/website/ui/TabNavigation.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { Box } from './primitives/Box'; +import { Stack } from './primitives/Stack'; +import { Surface } from './primitives/Surface'; import { Text } from './Text'; -import { Icon } from './Icon'; -import { LucideIcon } from 'lucide-react'; interface Tab { id: string; label: string; - icon?: LucideIcon; + icon?: React.ReactNode; } interface TabNavigationProps { @@ -19,51 +19,50 @@ interface TabNavigationProps { export function TabNavigation({ tabs, activeTab, onTabChange, className = '' }: TabNavigationProps) { return ( - - {tabs.map((tab) => { - const isActive = activeTab === tab.id; - return ( - onTabChange(tab.id)} - display="flex" - alignItems="center" - gap={2} - px={5} - py={2.5} - rounded="lg" - cursor="pointer" - transition - bg={isActive ? 'bg-primary-blue' : ''} - className={`select-none ${isActive ? 'shadow-lg shadow-primary-blue/25' : 'hover:bg-iron-gray/80'}`} - > - {tab.icon && } - + {tabs.map((tab) => { + const isActive = activeTab === tab.id; + return ( + onTabChange(tab.id)} + variant={isActive ? 'default' : 'ghost'} + bg={isActive ? 'bg-primary-blue' : ''} + rounded="lg" + px={4} + py={2} + transition="all 0.2s" + group + className={`select-none ${isActive ? 'shadow-lg shadow-primary-blue/25' : 'hover:bg-iron-gray/80'}`} > - {tab.label} - - - ); - })} - + + {tab.icon && ( + + {tab.icon} + + )} + + {tab.label} + + + + ); + })} + + ); } diff --git a/apps/website/ui/Table.tsx b/apps/website/ui/Table.tsx index 6a8a8b9c4..c465fd51c 100644 --- a/apps/website/ui/Table.tsx +++ b/apps/website/ui/Table.tsx @@ -1,58 +1,59 @@ -import React, { ReactNode, HTMLAttributes } from 'react'; +import React, { ReactNode, ElementType } from 'react'; import { Box, BoxProps } from './primitives/Box'; -interface TableProps extends HTMLAttributes { +interface TableProps extends BoxProps<'table'> { children: ReactNode; - className?: string; } export function Table({ children, className = '', ...props }: TableProps) { + const { border, translate, ...rest } = props; return ( - - + +
{children}
); } -interface TableHeadProps extends HTMLAttributes { +interface TableHeaderProps extends BoxProps<'thead'> { children: ReactNode; } -export function TableHead({ children, ...props }: TableHeadProps) { +export function TableHeader({ children, className = '', ...props }: TableHeaderProps) { return ( - + {children} - + ); } -interface TableBodyProps extends HTMLAttributes { +export const TableHead = TableHeader; + +interface TableBodyProps extends BoxProps<'tbody'> { children: ReactNode; } -export function TableBody({ children, ...props }: TableBodyProps) { +export function TableBody({ children, className = '', ...props }: TableBodyProps) { return ( - + {children} - + ); } interface TableRowProps extends BoxProps<'tr'> { children: ReactNode; + hoverBg?: string; clickable?: boolean; - variant?: 'default' | 'highlight'; + variant?: string; } -export function TableRow({ children, className = '', clickable = false, variant = 'default', ...props }: TableRowProps) { - const baseClasses = 'transition-colors duration-150 ease-smooth'; - const variantClasses = variant === 'highlight' ? 'bg-primary-accent/5' : 'hover:bg-white/[0.02]'; +export function TableRow({ children, className = '', hoverBg, clickable, variant, ...props }: TableRowProps) { const classes = [ - baseClasses, - variantClasses, - clickable ? 'cursor-pointer' : '', + 'transition-colors', + clickable || props.onClick ? 'cursor-pointer' : '', + hoverBg ? `hover:${hoverBg}` : (clickable || props.onClick ? 'hover:bg-white/5' : ''), className ].filter(Boolean).join(' '); @@ -63,13 +64,15 @@ export function TableRow({ children, className = '', clickable = false, variant ); } -interface TableHeaderProps extends BoxProps<'th'> { +interface TableCellProps extends BoxProps<'td'> { children: ReactNode; } -export function TableHeader({ children, className = '', ...props }: TableHeaderProps) { - const baseClasses = 'py-2.5 px-4 text-[11px] font-bold text-gray-500 uppercase tracking-wider'; - const classes = [baseClasses, className].filter(Boolean).join(' '); +export function TableHeaderCell({ children, className = '', ...props }: TableCellProps) { + const classes = [ + 'px-4 py-3 text-xs font-bold text-gray-400 uppercase tracking-wider', + className + ].filter(Boolean).join(' '); return ( @@ -78,13 +81,11 @@ export function TableHeader({ children, className = '', ...props }: TableHeaderP ); } -interface TableCellProps extends BoxProps<'td'> { - children: ReactNode; -} - export function TableCell({ children, className = '', ...props }: TableCellProps) { - const baseClasses = 'py-3 px-4 text-sm text-gray-300'; - const classes = [baseClasses, className].filter(Boolean).join(' '); + const classes = [ + 'px-4 py-4 text-sm text-gray-300', + className + ].filter(Boolean).join(' '); return ( diff --git a/apps/website/ui/Text.tsx b/apps/website/ui/Text.tsx index 2d600da97..407486d15 100644 --- a/apps/website/ui/Text.tsx +++ b/apps/website/ui/Text.tsx @@ -25,14 +25,14 @@ interface ResponsiveTextAlign { '2xl'?: TextAlign; } -interface TextProps extends Omit, 'children' | 'className'> { +interface TextProps extends Omit, 'children' | 'className' | 'size'> { as?: T; children: ReactNode; className?: string; size?: TextSize | ResponsiveTextSize; - weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold'; + weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | string; color?: string; - font?: 'mono' | 'sans'; + font?: 'mono' | 'sans' | string; align?: TextAlign | ResponsiveTextAlign; truncate?: boolean; uppercase?: boolean; @@ -43,6 +43,7 @@ interface TextProps extends Omit, 'c style?: React.CSSProperties; block?: boolean; italic?: boolean; + lineClamp?: number; ml?: Spacing | ResponsiveSpacing; mr?: Spacing | ResponsiveSpacing; mt?: Spacing | ResponsiveSpacing; @@ -76,6 +77,7 @@ export function Text({ style, block = false, italic = false, + lineClamp, ml, mr, mt, mb, ...props }: TextProps & ComponentPropsWithoutRef) { @@ -115,7 +117,7 @@ export function Text({ bold: 'font-bold' }; - const fontClasses = { + const fontClasses: Record = { mono: 'font-mono', sans: 'font-sans' }; @@ -175,8 +177,8 @@ export function Text({ const classes = [ block ? 'block' : 'inline', getSizeClasses(size), - weightClasses[weight], - fontClasses[font], + weightClasses[weight] || '', + fontClasses[font] || '', getAlignClasses(align), leading ? leadingClasses[leading] : '', color, @@ -184,6 +186,7 @@ export function Text({ uppercase ? 'uppercase' : '', capitalize ? 'capitalize' : '', italic ? 'italic' : '', + lineClamp ? `line-clamp-${lineClamp}` : '', letterSpacing === '0.05em' ? 'tracking-wider' : letterSpacing ? `tracking-${letterSpacing}` : '', getSpacingClass('ml', ml), getSpacingClass('mr', mr), @@ -194,6 +197,8 @@ export function Text({ const combinedStyle = { ...(fontSize ? { fontSize } : {}), + ...(weight && !weightClasses[weight] ? { fontWeight: weight } : {}), + ...(font && !fontClasses[font] ? { fontFamily: font } : {}), ...style }; diff --git a/apps/website/ui/TextArea.tsx b/apps/website/ui/TextArea.tsx index f5425bbba..6135e16f3 100644 --- a/apps/website/ui/TextArea.tsx +++ b/apps/website/ui/TextArea.tsx @@ -1,56 +1,49 @@ - - -import React, { TextareaHTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; import { Box } from './primitives/Box'; import { Stack } from './primitives/Stack'; import { Text } from './Text'; -interface TextAreaProps extends TextareaHTMLAttributes { - label?: React.ReactNode; +interface TextAreaProps extends React.TextareaHTMLAttributes { + label?: string; errorMessage?: string; variant?: 'default' | 'error'; fullWidth?: boolean; } -export function TextArea({ - label, - errorMessage, - variant = 'default', - fullWidth = true, - className = '', - ...props -}: TextAreaProps) { - const isError = variant === 'error' || !!errorMessage; +export const TextArea = forwardRef( + ({ label, errorMessage, variant = 'default', fullWidth = true, className = '', ...props }, ref) => { + const isError = variant === 'error' || !!errorMessage; + + return ( + + {label && ( + + {label} + + )} + + + {errorMessage && ( + + {errorMessage} + + )} + + + ); + } +); - return ( - - {label && ( - - {label} - - )} - - - - {errorMessage && ( - - {errorMessage} - - )} - - ); -} +TextArea.displayName = 'TextArea'; diff --git a/apps/website/ui/Toggle.tsx b/apps/website/ui/Toggle.tsx index 962bd8ee1..7c1a133df 100644 --- a/apps/website/ui/Toggle.tsx +++ b/apps/website/ui/Toggle.tsx @@ -1,68 +1,74 @@ -import { motion } from 'framer-motion'; +import React from 'react'; import { Box } from './primitives/Box'; import { Text } from './Text'; +import { motion } from 'framer-motion'; interface ToggleProps { - checked: boolean; - onChange: (checked: boolean) => void; label: string; description?: string; + checked: boolean; + onChange: (checked: boolean) => void; disabled?: boolean; } -export function Toggle({ - checked, - onChange, - label, - description, - disabled = false, -}: ToggleProps) { +export function Toggle({ label, description, checked, onChange, disabled }: ToggleProps) { return ( - + + ); } diff --git a/apps/website/ui/primitives/Box.tsx b/apps/website/ui/primitives/Box.tsx index 5a1701e47..f7aa51aba 100644 --- a/apps/website/ui/primitives/Box.tsx +++ b/apps/website/ui/primitives/Box.tsx @@ -16,8 +16,10 @@ type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 1 interface ResponsiveSpacing { base?: Spacing; + sm?: Spacing; md?: Spacing; lg?: Spacing; + xl?: Spacing; } export type ResponsiveValue = { @@ -49,38 +51,151 @@ export interface BoxProps { px?: Spacing | ResponsiveSpacing; py?: Spacing | ResponsiveSpacing; // Sizing - w?: string | ResponsiveValue; - h?: string | ResponsiveValue; - width?: string; - height?: string; + w?: string | number | ResponsiveValue; + h?: string | number | ResponsiveValue; + width?: string | number; + height?: string | number; maxWidth?: string | ResponsiveValue; minWidth?: string | ResponsiveValue; maxHeight?: string | ResponsiveValue; minHeight?: string | ResponsiveValue; + fullWidth?: boolean; + fullHeight?: boolean; + aspectRatio?: string; // Display - display?: 'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | ResponsiveValue<'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none'>; + display?: 'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string | ResponsiveValue<'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string>; + center?: boolean; + overflow?: 'auto' | 'hidden' | 'visible' | 'scroll' | string; + overflowX?: 'auto' | 'hidden' | 'visible' | 'scroll'; + overflowY?: 'auto' | 'hidden' | 'visible' | 'scroll'; + textAlign?: 'left' | 'center' | 'right' | 'justify' | string; + visibility?: 'visible' | 'hidden' | 'collapse'; + // Positioning + position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'; + top?: string | number | ResponsiveValue; + right?: string | number | ResponsiveValue; + bottom?: string | number | ResponsiveValue; + left?: string | number | ResponsiveValue; + inset?: string | number; + insetY?: string | number; + insetX?: string | number; + zIndex?: number; // Basic Styling - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'; - border?: boolean; + rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | string | boolean; + border?: boolean | string; + borderTop?: boolean | string; + borderBottom?: boolean | string; + borderLeft?: boolean | string; + borderRight?: boolean | string; + borderWidth?: string | number; + borderStyle?: 'solid' | 'dashed' | 'dotted' | 'none' | string; borderColor?: string; + borderOpacity?: number; bg?: string; + backgroundColor?: string; + backgroundImage?: string; + backgroundSize?: string; + backgroundPosition?: string; + bgOpacity?: number; color?: string; shadow?: string; opacity?: number; + blur?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | string; + pointerEvents?: 'auto' | 'none' | string; // Flex/Grid Item props flex?: number | string; flexShrink?: number; flexGrow?: number; + flexDirection?: 'row' | 'row-reverse' | 'col' | 'col-reverse' | string | ResponsiveValue; + flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | string; + alignItems?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' | string | ResponsiveValue; + justifyContent?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly' | string | ResponsiveValue; alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'stretch' | 'baseline'; + gap?: number | string | ResponsiveValue; + gridCols?: number | ResponsiveValue; + responsiveGridCols?: number | ResponsiveValue; + colSpan?: number | ResponsiveValue; + responsiveColSpan?: number | ResponsiveValue; order?: number | string | ResponsiveValue; + // Transform + transform?: string | boolean; + translate?: string; + translateX?: string; + translateY?: string; + // Animation (Framer Motion support) + initial?: any; + animate?: any; + exit?: any; + transition?: any; + variants?: any; + whileHover?: any; + whileTap?: any; + onHoverStart?: any; + onHoverEnd?: any; + whileInView?: any; + viewport?: any; + custom?: any; + // Interaction + group?: boolean; + groupHoverTextColor?: string; + groupHoverScale?: boolean; + groupHoverOpacity?: number; + groupHoverBorderColor?: string; + hoverBorderColor?: string; + hoverBg?: string; + hoverTextColor?: string; + hoverScale?: boolean | number; + clickable?: boolean; // Events - onMouseEnter?: React.MouseEventHandler; - onMouseLeave?: React.MouseEventHandler; - onClick?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; + onClick?: React.MouseEventHandler; + onMouseDown?: React.MouseEventHandler; + onMouseUp?: React.MouseEventHandler; + onMouseMove?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; + onBlur?: React.FocusEventHandler; + onSubmit?: React.FormEventHandler; + onScroll?: React.UIEventHandler; style?: React.CSSProperties; id?: string; - role?: string; + role?: React.AriaRole; tabIndex?: number; + // Other + type?: 'button' | 'submit' | 'reset' | string; + disabled?: boolean; + cursor?: string; + fontSize?: string | ResponsiveValue; + weight?: string; + fontWeight?: string | number; + letterSpacing?: string; + lineHeight?: string | number; + font?: string; + ring?: string; + hideScrollbar?: boolean; + truncate?: boolean; + src?: string; + alt?: string; + draggable?: boolean; + min?: string | number; + max?: string | number; + step?: string | number; + value?: string | number; + onChange?: React.ChangeEventHandler; + placeholder?: string; + title?: string; + padding?: Spacing | ResponsiveSpacing; + paddingLeft?: Spacing | ResponsiveSpacing; + paddingRight?: Spacing | ResponsiveSpacing; + paddingTop?: Spacing | ResponsiveSpacing; + paddingBottom?: Spacing | ResponsiveSpacing; + size?: string | number | ResponsiveValue; + accept?: string; + autoPlay?: boolean; + loop?: boolean; + muted?: boolean; + playsInline?: boolean; + objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'; } export const Box = forwardRef(( @@ -92,26 +207,126 @@ export const Box = forwardRef(( p, pt, pb, pl, pr, px, py, w, h, width, height, maxWidth, minWidth, maxHeight, minHeight, + fullWidth, fullHeight, + aspectRatio, display, + center, + overflow, overflowX, overflowY, + textAlign, + visibility, + position, + top, right, bottom, left, + inset, insetY, insetX, + zIndex, rounded, border, + borderTop, + borderBottom, + borderLeft, + borderRight, + borderWidth, + borderStyle, borderColor, + borderOpacity, bg, + backgroundColor, + backgroundImage, + backgroundSize, + backgroundPosition, + bgOpacity, color, shadow, opacity, + blur, + pointerEvents, flex, flexShrink, flexGrow, + flexDirection, + flexWrap, + alignItems, + justifyContent, alignSelf, + gap, + gridCols, + responsiveGridCols, + colSpan, + responsiveColSpan, order, + transform, + translate, + translateX, + translateY, + initial, + animate, + exit, + transition, + variants, + whileHover, + whileTap, + onHoverStart, + onHoverEnd, + whileInView, + viewport, + custom, + group, + groupHoverTextColor, + groupHoverScale, + groupHoverOpacity, + groupHoverBorderColor, + hoverBorderColor, + hoverBg, + hoverTextColor, + hoverScale, + clickable, onMouseEnter, onMouseLeave, onClick, + onMouseDown, + onMouseUp, + onMouseMove, + onKeyDown, + onBlur, + onSubmit, + onScroll, style: styleProp, id, role, tabIndex, + type, + disabled, + cursor, + fontSize, + weight, + fontWeight, + letterSpacing, + lineHeight, + font, + ring, + hideScrollbar, + truncate, + src, + alt, + draggable, + min, + max, + step, + value, + onChange, + placeholder, + title, + padding, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, + size, + accept, + autoPlay, + loop, + muted, + playsInline, + objectFit, ...props }: BoxProps & ComponentPropsWithoutRef, ref: ForwardedRef @@ -131,14 +346,16 @@ export const Box = forwardRef(( if (typeof value === 'object') { const classes = []; if (value.base !== undefined) classes.push(`${prefix}-${spacingMap[value.base]}`); + if (value.sm !== undefined) classes.push(`sm:${prefix}-${spacingMap[value.sm]}`); if (value.md !== undefined) classes.push(`md:${prefix}-${spacingMap[value.md]}`); if (value.lg !== undefined) classes.push(`lg:${prefix}-${spacingMap[value.lg]}`); + if (value.xl !== undefined) classes.push(`xl:${prefix}-${spacingMap[value.xl]}`); return classes.join(' '); } return `${prefix}-${spacingMap[value]}`; }; - const getResponsiveClasses = (prefix: string, value: string | number | ResponsiveValue | undefined) => { + const getResponsiveClasses = (prefix: string, value: any | ResponsiveValue | undefined) => { if (value === undefined) return ''; if (typeof value === 'object') { const classes = []; @@ -161,42 +378,111 @@ export const Box = forwardRef(( getSpacingClass('mr', mr), getSpacingClass('mx', mx), getSpacingClass('my', my), - getSpacingClass('p', p), - getSpacingClass('pt', pt), - getSpacingClass('pb', pb), - getSpacingClass('pl', pl), - getSpacingClass('pr', pr), + getSpacingClass('p', p || padding), + getSpacingClass('pt', pt || paddingTop), + getSpacingClass('pb', pb || paddingBottom), + getSpacingClass('pl', pl || paddingLeft), + getSpacingClass('pr', pr || paddingRight), getSpacingClass('px', px), getSpacingClass('py', py), - getResponsiveClasses('w', w), - getResponsiveClasses('h', h), + fullWidth ? 'w-full' : getResponsiveClasses('w', w), + fullHeight ? 'h-full' : getResponsiveClasses('h', h), getResponsiveClasses('max-w', maxWidth), getResponsiveClasses('min-w', minWidth), getResponsiveClasses('max-h', maxHeight), getResponsiveClasses('min-h', minHeight), getResponsiveClasses('', display), - rounded ? `rounded-${rounded}` : '', - border ? 'border' : '', + center ? 'flex items-center justify-center' : '', + overflow ? (overflow.includes(':') ? overflow : `overflow-${overflow}`) : '', + overflowX ? `overflow-x-${overflowX}` : '', + overflowY ? `overflow-y-${overflowY}` : '', + textAlign ? `text-${textAlign}` : '', + visibility ? visibility : '', + position ? position : '', + getResponsiveClasses('top', top), + getResponsiveClasses('right', right), + getResponsiveClasses('bottom', bottom), + getResponsiveClasses('left', left), + inset !== undefined ? `inset-${inset}` : '', + insetY !== undefined ? `inset-y-${insetY}` : '', + insetX !== undefined ? `inset-x-${insetX}` : '', + zIndex !== undefined ? `z-${zIndex}` : '', + rounded === true ? 'rounded' : (rounded === false ? 'rounded-none' : (typeof rounded === 'string' ? (rounded.includes('-') ? rounded : `rounded-${rounded}`) : '')), + border === true ? 'border' : (typeof border === 'string' ? (border === 'none' ? 'border-none' : border) : ''), + borderTop === true ? 'border-t' : (typeof borderTop === 'string' ? borderTop : ''), + borderBottom === true ? 'border-b' : (typeof borderBottom === 'string' ? borderBottom : ''), + borderLeft === true ? 'border-l' : (typeof borderLeft === 'string' ? borderLeft : ''), + borderRight === true ? 'border-r' : (typeof borderRight === 'string' ? borderRight : ''), + borderStyle ? `border-${borderStyle}` : '', borderColor ? borderColor : '', + borderOpacity !== undefined ? `border-opacity-${borderOpacity * 100}` : '', bg ? bg : '', + backgroundColor ? backgroundColor : '', + bgOpacity !== undefined ? `bg-opacity-${bgOpacity * 100}` : '', color ? color : '', shadow ? shadow : '', + opacity !== undefined ? `opacity-${opacity * 100}` : '', + blur ? (blur === 'none' ? 'blur-none' : `blur-${blur}`) : '', + pointerEvents ? `pointer-events-${pointerEvents}` : '', flex !== undefined ? `flex-${flex}` : '', flexShrink !== undefined ? `flex-shrink-${flexShrink}` : '', flexGrow !== undefined ? `flex-grow-${flexGrow}` : '', + getResponsiveClasses('flex', flexDirection), + flexWrap ? `flex-${flexWrap}` : '', + getResponsiveClasses('items', alignItems), + getResponsiveClasses('justify', justifyContent), alignSelf !== undefined ? `self-${alignSelf}` : '', - opacity !== undefined ? `opacity-${opacity * 100}` : '', + getResponsiveClasses('gap', gap), + getResponsiveClasses('grid-cols', gridCols || responsiveGridCols), + getResponsiveClasses('col-span', colSpan || responsiveColSpan), getResponsiveClasses('order', order), + getResponsiveClasses('text', fontSize), + group ? 'group' : '', + groupHoverTextColor ? `group-hover:text-${groupHoverTextColor}` : '', + groupHoverScale ? 'group-hover:scale-105 transition-transform' : '', + groupHoverOpacity !== undefined ? `group-hover:opacity-${groupHoverOpacity * 100}` : '', + groupHoverBorderColor ? `group-hover:border-${groupHoverBorderColor}` : '', + hoverBorderColor ? `hover:border-${hoverBorderColor}` : '', + hoverBg ? `hover:bg-${hoverBg}` : '', + hoverTextColor ? `hover:text-${hoverTextColor}` : '', + hoverScale === true ? 'hover:scale-105 transition-transform' : (typeof hoverScale === 'number' ? `hover:scale-${hoverScale} transition-transform` : ''), + clickable ? 'cursor-pointer active:opacity-80 transition-all' : '', + ring ? `ring-${ring}` : '', + hideScrollbar ? 'scrollbar-hide' : '', + truncate ? 'truncate' : '', + transform === true ? 'transform' : (transform === false ? 'transform-none' : ''), className ].filter(Boolean).join(' '); const style: React.CSSProperties = { - ...(width ? { width } : {}), - ...(height ? { height } : {}), + ...(typeof width === 'string' || typeof width === 'number' ? { width } : {}), + ...(typeof height === 'string' || typeof height === 'number' ? { height } : {}), ...(typeof maxWidth === 'string' ? { maxWidth } : {}), ...(typeof minWidth === 'string' ? { minWidth } : {}), ...(typeof maxHeight === 'string' ? { maxHeight } : {}), ...(typeof minHeight === 'string' ? { minHeight } : {}), + ...(aspectRatio ? { aspectRatio } : {}), + ...(typeof top === 'string' || typeof top === 'number' ? { top } : {}), + ...(typeof right === 'string' || typeof right === 'number' ? { right } : {}), + ...(typeof bottom === 'string' || typeof bottom === 'number' ? { bottom } : {}), + ...(typeof left === 'string' || typeof left === 'number' ? { left } : {}), + ...(borderWidth !== undefined ? { borderWidth } : {}), + ...(typeof transform === 'string' ? { transform } : {}), + ...(translate ? { translate } : {}), + ...(translateX ? { transform: `translateX(${translateX})` } : {}), + ...(translateY ? { transform: `translateY(${translateY})` } : {}), + ...(cursor ? { cursor } : {}), + ...(fontSize && typeof fontSize === 'string' && !fontSize.includes(':') ? { fontSize } : {}), + ...(weight ? { fontWeight: weight } : {}), + ...(fontWeight ? { fontWeight } : {}), + ...(letterSpacing ? { letterSpacing } : {}), + ...(lineHeight ? { lineHeight } : {}), + ...(font ? { fontFamily: font } : {}), + ...(typeof size === 'string' || typeof size === 'number' ? { width: size, height: size } : {}), + ...(backgroundImage ? { backgroundImage } : {}), + ...(backgroundSize ? { backgroundSize } : {}), + ...(backgroundPosition ? { backgroundPosition } : {}), + ...(objectFit ? { objectFit } : {}), ...(styleProp || {}) }; @@ -205,12 +491,35 @@ export const Box = forwardRef(( ref={ref as React.ForwardedRef} className={classes} onClick={onClick} + onMouseDown={onMouseDown} + onMouseUp={onMouseUp} + onMouseMove={onMouseMove} + onKeyDown={onKeyDown} + onBlur={onBlur} + onSubmit={onSubmit} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} + onScroll={onScroll} style={style} id={id} role={role} tabIndex={tabIndex} + type={type} + disabled={disabled} + src={src} + alt={alt} + draggable={draggable} + min={min} + max={max} + step={step} + value={value} + onChange={onChange} + placeholder={placeholder} + title={title} + autoPlay={autoPlay} + loop={loop} + muted={muted} + playsInline={playsInline} {...props} > {children} diff --git a/apps/website/ui/primitives/Grid.tsx b/apps/website/ui/primitives/Grid.tsx index 02ea4780e..3e4f0dbe6 100644 --- a/apps/website/ui/primitives/Grid.tsx +++ b/apps/website/ui/primitives/Grid.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, ElementType } from 'react'; import { Box, BoxProps, ResponsiveValue } from './Box'; /** @@ -13,32 +13,16 @@ import { Box, BoxProps, ResponsiveValue } from './Box'; * If you need a more specific layout, create a new component in apps/website/components. */ -export interface GridProps { - children: ReactNode; +export interface GridProps extends Omit, 'children'> { + children?: ReactNode; cols?: 1 | 2 | 3 | 4 | 5 | 6 | 12; mdCols?: 1 | 2 | 3 | 4 | 5 | 6 | 12; lgCols?: 1 | 2 | 3 | 4 | 5 | 6 | 12; gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12 | 16; className?: string; - // Spacing - m?: number; - mt?: number; - mb?: number; - ml?: number; - mr?: number; - p?: number; - pt?: number; - pb?: number; - pl?: number; - pr?: number; - px?: number; - py?: number; - // Sizing - w?: string | ResponsiveValue; - h?: string | ResponsiveValue; } -export function Grid({ +export function Grid({ children, cols = 1, mdCols, @@ -46,7 +30,7 @@ export function Grid({ gap = 4, className = '', ...props -}: GridProps) { +}: GridProps) { const colClasses: Record = { 1: 'grid-cols-1', 2: 'grid-cols-1 md:grid-cols-2', diff --git a/apps/website/ui/primitives/GridItem.tsx b/apps/website/ui/primitives/GridItem.tsx index cb0809c69..89c6d64f5 100644 --- a/apps/website/ui/primitives/GridItem.tsx +++ b/apps/website/ui/primitives/GridItem.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { Box } from './Box'; +import React, { ElementType } from 'react'; +import { Box, BoxProps } from './Box'; /** * WARNING: DO NOT VIOLATE THE PURPOSE OF THIS PRIMITIVE. @@ -12,15 +12,15 @@ import { Box } from './Box'; * If you need a more specific layout, create a new component in apps/website/components. */ -export interface GridItemProps { - children: React.ReactNode; +export interface GridItemProps extends Omit, 'children'> { + children?: React.ReactNode; colSpan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; mdSpan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; lgSpan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; className?: string; } -export function GridItem({ children, colSpan, mdSpan, lgSpan, className = '' }: GridItemProps) { +export function GridItem({ children, colSpan, mdSpan, lgSpan, className = '', ...props }: GridItemProps) { const spanClasses = [ colSpan ? `col-span-${colSpan}` : '', mdSpan ? `md:col-span-${mdSpan}` : '', @@ -29,7 +29,7 @@ export function GridItem({ children, colSpan, mdSpan, lgSpan, className = '' }: ].filter(Boolean).join(' '); return ( - + {children} ); diff --git a/apps/website/ui/primitives/Stack.tsx b/apps/website/ui/primitives/Stack.tsx index ff0db3bf2..eb51d072e 100644 --- a/apps/website/ui/primitives/Stack.tsx +++ b/apps/website/ui/primitives/Stack.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, ElementType } from 'react'; +import React, { ReactNode, ElementType, forwardRef, ForwardedRef } from 'react'; import { Box, BoxProps, ResponsiveValue } from './Box'; /** @@ -13,8 +13,6 @@ import { Box, BoxProps, ResponsiveValue } from './Box'; * If you need a more specific layout, create a new component in apps/website/components. */ -type Spacing = 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 64 | 72 | 80 | 96; - interface ResponsiveGap { base?: number; sm?: number; @@ -23,73 +21,31 @@ interface ResponsiveGap { xl?: number; } -interface ResponsiveSpacing { - base?: Spacing; - sm?: Spacing; - md?: Spacing; - lg?: Spacing; - xl?: Spacing; - '2xl'?: Spacing; -} - -export interface StackProps { +export interface StackProps extends Omit, 'children'> { as?: T; - children: ReactNode; + children?: ReactNode; className?: string; direction?: 'row' | 'col' | { base?: 'row' | 'col'; md?: 'row' | 'col'; lg?: 'row' | 'col' }; - gap?: number | ResponsiveGap; + gap?: number | string | ResponsiveGap; align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' | ResponsiveValue<'start' | 'center' | 'end' | 'stretch' | 'baseline'>; justify?: 'start' | 'center' | 'end' | 'between' | 'around' | ResponsiveValue<'start' | 'center' | 'end' | 'between' | 'around'>; wrap?: boolean; - // Spacing (allowed for layout) - m?: Spacing | ResponsiveSpacing; - mt?: Spacing | ResponsiveSpacing; - mb?: Spacing | ResponsiveSpacing; - ml?: Spacing | ResponsiveSpacing; - mr?: Spacing | ResponsiveSpacing; - p?: Spacing | ResponsiveSpacing; - pt?: Spacing | ResponsiveSpacing; - pb?: Spacing | ResponsiveSpacing; - pl?: Spacing | ResponsiveSpacing; - pr?: Spacing | ResponsiveSpacing; - px?: Spacing | ResponsiveSpacing; - py?: Spacing | ResponsiveSpacing; - // Sizing (allowed for layout) - w?: string | ResponsiveValue; - h?: string | ResponsiveValue; - minWidth?: string | ResponsiveValue; - maxWidth?: string | ResponsiveValue; - minHeight?: string | ResponsiveValue; - maxHeight?: string | ResponsiveValue; - // Basic styling (sometimes needed for containers) - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'; - // Flex item props - flex?: number | string; - flexGrow?: number; - flexShrink?: number; - alignSelf?: 'auto' | 'start' | 'end' | 'center' | 'stretch' | 'baseline'; - style?: React.CSSProperties; } -export function Stack({ - children, - className = '', - direction = 'col', - gap = 4, - align, - justify, - wrap = false, - m, mt, mb, ml, mr, - p, pt, pb, pl, pr, px, py, - w, h, minWidth, maxWidth, minHeight, maxHeight, - rounded, - flex, - flexGrow, - flexShrink, - alignSelf, - as, - ...props -}: StackProps) { +export const Stack = forwardRef(( + { + children, + className = '', + direction = 'col', + gap = 4, + align, + justify, + wrap = false, + as, + ...props + }: StackProps, + ref: ForwardedRef +) => { const gapClasses: Record = { 0: 'gap-0', 1: 'gap-1', @@ -104,50 +60,19 @@ export function Stack({ 16: 'gap-16' }; - const getGapClasses = (value: number | ResponsiveGap | undefined) => { + const getGapClasses = (value: number | string | ResponsiveGap | undefined) => { if (value === undefined) return ''; if (typeof value === 'object') { const classes = []; - if (value.base !== undefined) classes.push(gapClasses[value.base]); - if (value.sm !== undefined) classes.push(`sm:${gapClasses[value.sm]}`); - if (value.md !== undefined) classes.push(`md:${gapClasses[value.md]}`); - if (value.lg !== undefined) classes.push(`lg:${gapClasses[value.lg]}`); - if (value.xl !== undefined) classes.push(`xl:${gapClasses[value.xl]}`); + if (value.base !== undefined) classes.push(typeof value.base === 'number' ? gapClasses[value.base] : `gap-${value.base}`); + if (value.sm !== undefined) classes.push(typeof value.sm === 'number' ? `sm:${gapClasses[value.sm]}` : `sm:gap-${value.sm}`); + if (value.md !== undefined) classes.push(typeof value.md === 'number' ? `md:${gapClasses[value.md]}` : `md:gap-${value.md}`); + if (value.lg !== undefined) classes.push(typeof value.lg === 'number' ? `lg:${gapClasses[value.lg]}` : `lg:gap-${value.lg}`); + if (value.xl !== undefined) classes.push(typeof value.xl === 'number' ? `xl:${gapClasses[value.xl]}` : `xl:gap-${value.xl}`); return classes.join(' '); } - return gapClasses[value]; - }; - - const spacingMap: Record = { - 0: '0', 0.5: '0.5', 1: '1', 1.5: '1.5', 2: '2', 2.5: '2.5', 3: '3', 3.5: '3.5', 4: '4', - 5: '5', 6: '6', 7: '7', 8: '8', 9: '9', 10: '10', 11: '11', 12: '12', 14: '14', - 16: '16', 20: '20', 24: '24', 28: '28', 32: '32', 36: '36', 40: '40', 44: '44', - 48: '48', 52: '52', 56: '56', 60: '60', 64: '64', 72: '72', 80: '80', 96: '96' - }; - - const roundedClasses = { - none: 'rounded-none', - sm: 'rounded-sm', - md: 'rounded-md', - lg: 'rounded-lg', - xl: 'rounded-xl', - '2xl': 'rounded-2xl', - full: 'rounded-full' - }; - - const getSpacingClass = (prefix: string, value: Spacing | ResponsiveSpacing | undefined) => { - if (value === undefined) return ''; - if (typeof value === 'object') { - const classes = []; - if (value.base !== undefined) classes.push(`${prefix}-${spacingMap[value.base]}`); - if (value.sm !== undefined) classes.push(`sm:${prefix}-${spacingMap[value.sm]}`); - if (value.md !== undefined) classes.push(`md:${prefix}-${spacingMap[value.md]}`); - if (value.lg !== undefined) classes.push(`lg:${prefix}-${spacingMap[value.lg]}`); - if (value.xl !== undefined) classes.push(`xl:${prefix}-${spacingMap[value.xl]}`); - if (value['2xl'] !== undefined) classes.push(`2xl:${prefix}-${spacingMap[value['2xl']]}`); - return classes.join(' '); - } - return `${prefix}-${spacingMap[value]}`; + if (typeof value === 'number') return gapClasses[value]; + return `gap-${value}`; }; const classes = [ @@ -161,19 +86,6 @@ export function Stack({ ].filter(Boolean).join(' '), getGapClasses(gap) || 'gap-4', wrap ? 'flex-wrap' : '', - getSpacingClass('m', m), - getSpacingClass('mt', mt), - getSpacingClass('mb', mb), - getSpacingClass('ml', ml), - getSpacingClass('mr', mr), - getSpacingClass('p', p), - getSpacingClass('pt', pt), - getSpacingClass('pb', pb), - getSpacingClass('pl', pl), - getSpacingClass('pr', pr), - getSpacingClass('px', px), - getSpacingClass('py', py), - rounded ? roundedClasses[rounded] : '', className ].filter(Boolean).join(' '); @@ -217,20 +129,13 @@ export function Stack({ return ( {children} ); -} +}); + +Stack.displayName = 'Stack'; diff --git a/apps/website/ui/primitives/Surface.tsx b/apps/website/ui/primitives/Surface.tsx index 37c2e4e44..868aa4ae4 100644 --- a/apps/website/ui/primitives/Surface.tsx +++ b/apps/website/ui/primitives/Surface.tsx @@ -1,5 +1,5 @@ -import React, { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react'; -import { Box, BoxProps, ResponsiveValue } from './Box'; +import React, { ReactNode, ElementType, ComponentPropsWithoutRef, forwardRef, ForwardedRef } from 'react'; +import { Box, BoxProps } from './Box'; /** * WARNING: DO NOT VIOLATE THE PURPOSE OF THIS PRIMITIVE. @@ -12,33 +12,31 @@ import { Box, BoxProps, ResponsiveValue } from './Box'; * If you need a more specific layout, create a new component in apps/website/components. */ -export interface SurfaceProps { +export interface SurfaceProps extends Omit, 'children' | 'padding'> { as?: T; - children: ReactNode; + children?: ReactNode; variant?: 'default' | 'muted' | 'dark' | 'glass' | 'gradient-blue' | 'gradient-gold' | 'gradient-purple' | 'gradient-green' | 'discord' | 'discord-inner'; - rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'; - border?: boolean; + rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | string | boolean; + border?: boolean | string; padding?: number; className?: string; shadow?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'discord' | string; - // Sizing - w?: string | ResponsiveValue; - h?: string | ResponsiveValue; - maxWidth?: string | ResponsiveValue; } -export function Surface({ - as, - children, - variant = 'default', - rounded = 'none', - border = false, - padding = 0, - className = '', - shadow = 'none', - w, h, maxWidth, - ...props -}: SurfaceProps & ComponentPropsWithoutRef) { +export const Surface = forwardRef(( + { + as, + children, + variant = 'default', + rounded = 'none', + border = false, + padding = 0, + className = '', + shadow = 'none', + ...props + }: SurfaceProps & ComponentPropsWithoutRef, + ref: ForwardedRef +) => { const variantClasses: Record = { default: 'bg-panel-gray', muted: 'bg-panel-gray/40', @@ -85,7 +83,7 @@ export function Surface({ const classes = [ variantClasses[variant], - roundedClasses[rounded], + typeof rounded === 'string' && roundedClasses[rounded] ? roundedClasses[rounded] : '', border ? 'border border-border-gray' : '', paddingClasses[padding] || 'p-0', shadowClasses[shadow], @@ -93,8 +91,10 @@ export function Surface({ ].filter(Boolean).join(' '); return ( - + {children} ); -} +}); + +Surface.displayName = 'Surface';