website refactor
This commit is contained in:
@@ -1,70 +1,66 @@
|
||||
import { DriverIdentity } from '@/ui/DriverIdentity';
|
||||
import { DriverStats } from '@/components/drivers/DriverStats';
|
||||
import { RankBadge } from '@/components/leaderboards/RankBadge';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
'use client';
|
||||
|
||||
export interface DriverCardProps {
|
||||
id: string;
|
||||
name: string;
|
||||
rating: number;
|
||||
skillLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
nationality: string;
|
||||
racesCompleted: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
rank: number;
|
||||
onClick?: () => void;
|
||||
import { DriverIdentity } from '@/ui/DriverIdentity';
|
||||
import { ProfileCard } from '@/ui/ProfileCard';
|
||||
import { StatGrid } from '@/ui/StatGrid';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Flag, Trophy, Medal } from 'lucide-react';
|
||||
|
||||
interface DriverCardProps {
|
||||
driver: {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
rating: number;
|
||||
ratingLabel: string;
|
||||
nationality: string;
|
||||
racesCompleted: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
rank: number;
|
||||
};
|
||||
onClick: (id: string) => void;
|
||||
}
|
||||
|
||||
export function DriverCard(props: DriverCardProps) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
rating,
|
||||
nationality,
|
||||
racesCompleted,
|
||||
wins,
|
||||
podiums,
|
||||
rank,
|
||||
onClick,
|
||||
} = props;
|
||||
|
||||
// Create a proper DriverViewModel instance
|
||||
const driverViewModel = new DriverViewModel({
|
||||
id,
|
||||
name,
|
||||
avatarUrl: null,
|
||||
});
|
||||
|
||||
const winRate = racesCompleted > 0 ? ((wins / racesCompleted) * 100).toFixed(0) : '0';
|
||||
export function DriverCard({ driver, onClick }: DriverCardProps) {
|
||||
const stats = [
|
||||
{ label: 'Races', value: driver.racesCompleted, intent: 'low', icon: Flag },
|
||||
{ label: 'Wins', value: driver.wins, intent: 'primary', icon: Trophy },
|
||||
{ label: 'Podiums', value: driver.podiums, intent: 'warning', icon: Medal },
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
transition
|
||||
>
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Stack direction="row" align="center" gap={4} flexGrow={1}>
|
||||
<RankBadge rank={rank} size="lg" />
|
||||
|
||||
<DriverIdentity
|
||||
driver={driverViewModel}
|
||||
href={routes.driver.detail(id)}
|
||||
meta={`${nationality} • ${racesCompleted} races`}
|
||||
size="md"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<DriverStats
|
||||
rating={rating}
|
||||
wins={wins}
|
||||
podiums={podiums}
|
||||
winRate={winRate}
|
||||
<ProfileCard
|
||||
onClick={() => onClick(driver.id)}
|
||||
variant="muted"
|
||||
identity={
|
||||
<DriverIdentity
|
||||
driver={{
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
avatarUrl: driver.avatarUrl || null,
|
||||
}}
|
||||
contextLabel={`Rank #${driver.rank}`}
|
||||
meta={driver.nationality}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
}
|
||||
actions={
|
||||
<Badge variant="outline" size="sm">
|
||||
{driver.ratingLabel}
|
||||
</Badge>
|
||||
}
|
||||
stats={
|
||||
<StatGrid
|
||||
stats={stats.map(s => ({
|
||||
label: s.label,
|
||||
value: s.value,
|
||||
intent: s.intent as any,
|
||||
icon: s.icon
|
||||
}))}
|
||||
columns={3}
|
||||
variant="box"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
@@ -11,13 +11,14 @@ interface DriverSearchBarProps {
|
||||
|
||||
export function DriverSearchBar({ query, onChange }: DriverSearchBarProps) {
|
||||
return (
|
||||
<Group fullWidth>
|
||||
<Box as="div" width="full" maxWidth="400px">
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="Search drivers by name or nationality..."
|
||||
icon={<Search size={20} />}
|
||||
placeholder="Search competitors..."
|
||||
icon={<Search size={16} />}
|
||||
size="sm"
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
25
apps/website/components/drivers/DriverStatsHeader.tsx
Normal file
25
apps/website/components/drivers/DriverStatsHeader.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import { StatGrid } from '@/ui/StatGrid';
|
||||
import { Users, Trophy, Activity } from 'lucide-react';
|
||||
|
||||
interface DriverStatsHeaderProps {
|
||||
totalDrivers: string;
|
||||
activeDrivers: string;
|
||||
totalRaces: string;
|
||||
}
|
||||
|
||||
export function DriverStatsHeader({ totalDrivers, activeDrivers, totalRaces }: DriverStatsHeaderProps) {
|
||||
return (
|
||||
<StatGrid
|
||||
columns={{ base: 1, md: 3 }}
|
||||
variant="card"
|
||||
cardVariant="muted"
|
||||
stats={[
|
||||
{ label: 'Total Drivers', value: totalDrivers, icon: Users, intent: 'primary' },
|
||||
{ label: 'Active Drivers', value: activeDrivers, icon: Activity, intent: 'success' },
|
||||
{ label: 'Total Races', value: totalRaces, icon: Trophy, intent: 'warning' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Table, TableHead, TableBody, TableRow, TableHeaderCell } from '@/ui/Table';
|
||||
import { TrendingUp } from 'lucide-react';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import React from 'react';
|
||||
|
||||
interface DriverTableProps {
|
||||
@@ -15,31 +16,31 @@ interface DriverTableProps {
|
||||
|
||||
export function DriverTable({ children }: DriverTableProps) {
|
||||
return (
|
||||
<Group direction="column" gap={4} fullWidth>
|
||||
<Group direction="row" align="center" gap={3}>
|
||||
<Card variant="dark">
|
||||
<Stack direction="col" gap="md">
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Surface variant="precision" rounded="md" padding="sm">
|
||||
<Icon icon={TrendingUp} size={5} intent="primary" />
|
||||
</Card>
|
||||
<Group direction="column">
|
||||
<Heading level={2}>Driver Rankings</Heading>
|
||||
<Text size="xs" variant="low">Top performers by skill rating</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Surface>
|
||||
<Stack direction="col" gap="none">
|
||||
<Heading level={2} weight="bold">Driver Rankings</Heading>
|
||||
<Text size="xs" variant="low">Performance metrics based on sanctioned race results</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell textAlign="center" w="60px">#</TableHeaderCell>
|
||||
<TableHeaderCell>Driver</TableHeaderCell>
|
||||
<TableHeaderCell w="150px">Nationality</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="100px">Rating</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="80px">Wins</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="center" w="60px">Rank</TableHeaderCell>
|
||||
<TableHeaderCell>Competitor</TableHeaderCell>
|
||||
<TableHeaderCell w="180px">Nationality</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="120px">Skill Rating</TableHeaderCell>
|
||||
<TableHeaderCell textAlign="right" w="100px">Victories</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{children}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import { RatingBadge } from '@/components/drivers/RatingBadge';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { TableRow, TableCell } from '@/ui/Table';
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
import { CountryFlag } from '@/ui/CountryFlag';
|
||||
|
||||
interface DriverTableRowProps {
|
||||
rank: number;
|
||||
@@ -28,19 +29,19 @@ export function DriverTableRow({
|
||||
onClick,
|
||||
}: DriverTableRowProps) {
|
||||
return (
|
||||
<TableRow onClick={onClick} clickable>
|
||||
<TableRow onClick={onClick}>
|
||||
<TableCell textAlign="center">
|
||||
<Text
|
||||
size="sm"
|
||||
weight="bold"
|
||||
font="mono"
|
||||
mono
|
||||
variant={rank <= 3 ? 'warning' : 'low'}
|
||||
>
|
||||
{rank}
|
||||
{rank.toString().padStart(2, '0')}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Group direction="row" align="center" gap={3}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Avatar
|
||||
src={avatarUrl || undefined}
|
||||
alt={name}
|
||||
@@ -53,16 +54,22 @@ export function DriverTableRow({
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text size="xs" variant="low">{nationality}</Text>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<CountryFlag countryCode={nationality} size="sm" />
|
||||
<Text size="xs" variant="low" uppercase>{nationality}</Text>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<RatingBadge rating={rating} ratingLabel={ratingLabel} size="sm" />
|
||||
<Box display="flex" alignItems="center" justifyContent="end" gap={2}>
|
||||
<Text size="xs" variant="low" mono>{rating}</Text>
|
||||
<RatingBadge rating={rating} ratingLabel={ratingLabel} size="sm" />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell textAlign="right">
|
||||
<Text size="sm" weight="semibold" font="mono" variant="success">
|
||||
<Text size="sm" weight="bold" mono variant="success">
|
||||
{winsLabel}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
@@ -2,19 +2,15 @@
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Section } from '@/ui/Section';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Trophy, Users } from 'lucide-react';
|
||||
|
||||
interface DriverStat {
|
||||
label: string;
|
||||
value: string | number;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'telemetry';
|
||||
}
|
||||
import { MetricCard } from '@/ui/MetricCard';
|
||||
import { Trophy, Users, Zap, Flag } from 'lucide-react';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
|
||||
interface DriversDirectoryHeaderProps {
|
||||
totalDriversLabel: string;
|
||||
@@ -31,53 +27,68 @@ export function DriversDirectoryHeader({
|
||||
totalRacesLabel,
|
||||
onViewLeaderboard,
|
||||
}: DriversDirectoryHeaderProps) {
|
||||
const stats: DriverStat[] = [
|
||||
{ label: 'drivers', value: totalDriversLabel, intent: 'primary' },
|
||||
{ label: 'active', value: activeDriversLabel, intent: 'success' },
|
||||
{ label: 'total wins', value: totalWinsLabel, intent: 'warning' },
|
||||
{ label: 'races', value: totalRacesLabel, intent: 'telemetry' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Section variant="dark" padding="md">
|
||||
<Section variant="dark" padding="lg">
|
||||
<Container>
|
||||
<Group direction="row" align="center" justify="between" gap={8} fullWidth>
|
||||
<Group direction="column" gap={6}>
|
||||
<Group direction="row" align="center" gap={3}>
|
||||
<Card variant="dark">
|
||||
<Stack direction="col" gap="lg">
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Stack direction="col" gap="xs">
|
||||
<Stack direction="row" align="center" gap="md">
|
||||
<Icon icon={Users} size={6} intent="primary" />
|
||||
</Card>
|
||||
<Heading level={1}>Drivers</Heading>
|
||||
</Group>
|
||||
|
||||
<Text size="lg" variant="low">
|
||||
Meet the racers who make every lap count. From rookies to champions, track their journey and see who's dominating the grid.
|
||||
</Text>
|
||||
<Heading level={1} weight="bold">
|
||||
Driver Directory
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Text size="lg" variant="low">
|
||||
The official registry of GridPilot competitors. Tracking performance,
|
||||
reliability, and championship progress across all sanctioned events.
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Group direction="row" gap={6} wrap>
|
||||
{stats.map((stat, index) => (
|
||||
<Group key={index} direction="row" align="center" gap={2}>
|
||||
<Text size="sm" variant="low">
|
||||
<Text as="span" weight="semibold" variant="high">{stat.value}</Text> {stat.label}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Group direction="column" gap={2} align="center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onViewLeaderboard}
|
||||
icon={<Icon icon={Trophy} size={5} />}
|
||||
icon={<Icon icon={Trophy} size={4} />}
|
||||
>
|
||||
View Leaderboard
|
||||
Global Rankings
|
||||
</Button>
|
||||
<Text size="xs" variant="low" align="center">
|
||||
See full driver rankings
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<Grid cols={4} gap="md">
|
||||
<Box>
|
||||
<MetricCard
|
||||
label="Total Registered"
|
||||
value={totalDriversLabel}
|
||||
icon={Users}
|
||||
intent="primary"
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<MetricCard
|
||||
label="Active Competitors"
|
||||
value={activeDriversLabel}
|
||||
icon={Zap}
|
||||
intent="success"
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<MetricCard
|
||||
label="Career Victories"
|
||||
value={totalWinsLabel}
|
||||
icon={Trophy}
|
||||
intent="warning"
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<MetricCard
|
||||
label="Total Race Starts"
|
||||
value={totalRacesLabel}
|
||||
icon={Flag}
|
||||
intent="telemetry"
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { DriversDirectoryHeader } from '@/components/drivers/DriversDirectoryHeader';
|
||||
import { DriverSearchBar } from '@/components/drivers/DriverSearchBar';
|
||||
import { DriverTable } from '@/components/drivers/DriverTable';
|
||||
import { DriverTableRow } from '@/components/drivers/DriverTableRow';
|
||||
import { DriversViewData } from '@/lib/types/view-data/DriversViewData';
|
||||
import { DriverCard } from '@/components/drivers/DriverCard';
|
||||
import { DriverStatsHeader } from '@/components/drivers/DriverStatsHeader';
|
||||
import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Search, Users } from 'lucide-react';
|
||||
import { EmptyState } from '@/ui/EmptyState';
|
||||
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Search } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface DriversTemplateProps {
|
||||
viewData: DriversViewData | null;
|
||||
viewData: DriversViewData;
|
||||
searchQuery: string;
|
||||
onSearchChange: (query: string) => void;
|
||||
filteredDrivers: DriversViewData['drivers'];
|
||||
@@ -20,7 +18,7 @@ interface DriversTemplateProps {
|
||||
onViewLeaderboard: () => void;
|
||||
}
|
||||
|
||||
export function DriversTemplate({
|
||||
export function DriversTemplate({
|
||||
viewData,
|
||||
searchQuery,
|
||||
onSearchChange,
|
||||
@@ -29,47 +27,59 @@ export function DriversTemplate({
|
||||
onViewLeaderboard
|
||||
}: DriversTemplateProps) {
|
||||
return (
|
||||
<Container size="lg" py={8}>
|
||||
<Group direction="column" gap={10} fullWidth>
|
||||
<DriversDirectoryHeader
|
||||
totalDriversLabel={viewData?.totalDriversLabel || '0'}
|
||||
activeDriversLabel={viewData?.activeCountLabel || '0'}
|
||||
totalWinsLabel={viewData?.totalWinsLabel || '0'}
|
||||
totalRacesLabel={viewData?.totalRacesLabel || '0'}
|
||||
onViewLeaderboard={onViewLeaderboard}
|
||||
<main>
|
||||
<Box marginBottom={8}>
|
||||
<PageHeader
|
||||
icon={Users}
|
||||
title="Drivers"
|
||||
description="Global driver roster and statistics."
|
||||
action={
|
||||
<Box
|
||||
as="button"
|
||||
onClick={onViewLeaderboard}
|
||||
className="px-4 py-2 rounded-md bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] text-sm font-medium hover:bg-[var(--ui-color-bg-surface-muted)] transition-colors"
|
||||
>
|
||||
Leaderboard
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<DriverSearchBar query={searchQuery} onChange={onSearchChange} />
|
||||
<Box marginBottom={8}>
|
||||
<DriverStatsHeader
|
||||
totalDrivers={viewData.totalDriversLabel}
|
||||
activeDrivers={viewData.activeCountLabel}
|
||||
totalRaces={viewData.totalRacesLabel}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<DriverTable>
|
||||
{filteredDrivers.map((driver, index) => (
|
||||
<DriverTableRow
|
||||
<Box marginBottom={6} className="w-full">
|
||||
<Input
|
||||
placeholder="Search drivers by name or nationality..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
icon={Search}
|
||||
variant="search"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{filteredDrivers.length > 0 ? (
|
||||
<Box display="grid" gap={4} className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{filteredDrivers.map(driver => (
|
||||
<DriverCard
|
||||
key={driver.id}
|
||||
rank={index + 1}
|
||||
name={driver.name}
|
||||
avatarUrl={driver.avatarUrl}
|
||||
nationality={driver.nationality}
|
||||
rating={driver.rating}
|
||||
ratingLabel={driver.ratingLabel}
|
||||
winsLabel={String(driver.wins)}
|
||||
onClick={() => onDriverClick(driver.id)}
|
||||
driver={driver}
|
||||
onClick={onDriverClick}
|
||||
/>
|
||||
))}
|
||||
</DriverTable>
|
||||
|
||||
{filteredDrivers.length === 0 && (
|
||||
<EmptyState
|
||||
icon={Search}
|
||||
title="No drivers found"
|
||||
description={`No drivers found matching "${searchQuery}"`}
|
||||
action={{
|
||||
label: 'Clear search',
|
||||
onClick: () => onSearchChange(''),
|
||||
variant: 'secondary'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Container>
|
||||
</Box>
|
||||
) : (
|
||||
<EmptyState
|
||||
title="No drivers found"
|
||||
description={`No drivers match "${searchQuery}"`}
|
||||
icon={Search}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
38
apps/website/ui/ProfileCard.tsx
Normal file
38
apps/website/ui/ProfileCard.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Card } from './Card';
|
||||
import { Box } from './Box';
|
||||
|
||||
export interface ProfileCardProps {
|
||||
identity: ReactNode;
|
||||
stats?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
variant?: 'default' | 'muted' | 'outline' | 'glass';
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const ProfileCard = ({ identity, stats, actions, variant = 'default', onClick }: ProfileCardProps) => {
|
||||
return (
|
||||
<Card
|
||||
variant={variant}
|
||||
padding="md"
|
||||
onClick={onClick}
|
||||
className="h-full flex flex-col gap-6 transition-all duration-200 hover:border-[var(--ui-color-border-bright)]"
|
||||
>
|
||||
<Box display="flex" justifyContent="between" alignItems="start" gap={4}>
|
||||
<Box flex={1} minWidth="0">
|
||||
{identity}
|
||||
</Box>
|
||||
{actions && (
|
||||
<Box flexShrink={0}>
|
||||
{actions}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{stats && (
|
||||
<Box marginTop="auto" paddingTop={4} borderTop="1px solid var(--ui-color-border-muted)">
|
||||
{stats}
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user