website refactor
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { ChevronDown, ChevronUp, Minus } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface DeltaChipProps {
|
||||
value: number;
|
||||
@@ -11,40 +12,26 @@ interface DeltaChipProps {
|
||||
export function DeltaChip({ value, type = 'rank' }: DeltaChipProps) {
|
||||
if (value === 0) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-600">
|
||||
<Icon icon={Minus} size={3} />
|
||||
<Text size="xs" font="mono">0</Text>
|
||||
</Box>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
|
||||
<Icon icon={Minus} size={3} intent="low" />
|
||||
<Text size="xs" font="mono" variant="low">0</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isPositive = value > 0;
|
||||
const color = isPositive
|
||||
? (type === 'rank' ? 'text-performance-green' : 'text-performance-green')
|
||||
: (type === 'rank' ? 'text-error-red' : 'text-error-red');
|
||||
|
||||
// For rank, positive delta usually means dropping positions (e.g. +1 rank means 1st -> 2nd)
|
||||
// But usually "Delta" in leaderboards means "positions gained/lost"
|
||||
// Let's assume value is "positions gained" (positive = up, negative = down)
|
||||
|
||||
const variant = isPositive ? 'success' : 'critical';
|
||||
const IconComponent = isPositive ? ChevronUp : ChevronDown;
|
||||
const absoluteValue = Math.abs(value);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={0.5}
|
||||
color={color}
|
||||
bg={`${color.replace('text-', 'bg-')}/10`}
|
||||
px={1.5}
|
||||
py={0.5}
|
||||
rounded="full"
|
||||
>
|
||||
<Icon icon={IconComponent} size={3} />
|
||||
<Text size="xs" font="mono" weight="bold">
|
||||
{absoluteValue}
|
||||
</Text>
|
||||
</Box>
|
||||
<Badge variant={variant} size="sm">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.125rem' }}>
|
||||
<Icon icon={IconComponent} size={3} />
|
||||
<Text size="xs" font="mono" weight="bold">
|
||||
{absoluteValue}
|
||||
</Text>
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { ControlBar } from '@/ui/ControlBar';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Filter, Search } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -18,70 +19,32 @@ export function LeaderboardFiltersBar({
|
||||
children,
|
||||
}: LeaderboardFiltersBarProps) {
|
||||
return (
|
||||
<Stack
|
||||
mb={6}
|
||||
p={3}
|
||||
bg="bg-deep-charcoal/40"
|
||||
border
|
||||
borderColor="border-charcoal-outline/50"
|
||||
rounded="lg"
|
||||
blur="sm"
|
||||
>
|
||||
<Stack direction="row" align="center" justify="between" gap={4}>
|
||||
<Stack position="relative" flexGrow={1} maxWidth="md">
|
||||
<Stack
|
||||
position="absolute"
|
||||
left="3"
|
||||
top="1/2"
|
||||
transform="translateY(-50%)"
|
||||
pointerEvents="none"
|
||||
zIndex={10}
|
||||
>
|
||||
<Icon icon={Search} size={4} color="text-gray-500" />
|
||||
</Stack>
|
||||
<Stack
|
||||
as="input"
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange?.(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
w="full"
|
||||
bg="bg-graphite-black/50"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="md"
|
||||
py={2}
|
||||
pl={10}
|
||||
pr={4}
|
||||
fontSize="0.875rem"
|
||||
color="text-white"
|
||||
transition
|
||||
hoverBorderColor="border-primary-blue/50"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<div style={{ marginBottom: '1.5rem' }}>
|
||||
<ControlBar
|
||||
leftContent={
|
||||
<div style={{ maxWidth: '32rem', width: '100%' }}>
|
||||
<Input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange?.(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
icon={<Icon icon={Search} size={4} intent="low" />}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
{children}
|
||||
<Stack
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
px={3}
|
||||
py={2}
|
||||
bg="bg-graphite-black/30"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="md"
|
||||
cursor="pointer"
|
||||
transition
|
||||
hoverBg="bg-graphite-black/50"
|
||||
hoverBorderColor="border-gray-600"
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon={<Icon icon={Filter} size={3.5} intent="low" />}
|
||||
>
|
||||
<Icon icon={Filter} size={3.5} color="text-gray-400" />
|
||||
<Text size="xs" weight="bold" color="text-gray-400" uppercase letterSpacing="wider">Filters</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
Filters
|
||||
</Button>
|
||||
</div>
|
||||
</ControlBar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Text } from '@/ui/Text';
|
||||
import React from 'react';
|
||||
|
||||
interface RankBadgeProps {
|
||||
rank: number;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
showLabel?: boolean;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
export function RankBadge({ rank, size = 'md', showLabel = true }: RankBadgeProps) {
|
||||
export function RankBadge({ rank, size = 'md' }: RankBadgeProps) {
|
||||
const getVariant = (rank: number): 'warning' | 'primary' | 'info' | 'default' => {
|
||||
if (rank <= 3) return 'warning';
|
||||
if (rank <= 10) return 'primary';
|
||||
if (rank <= 50) return 'info';
|
||||
return 'default';
|
||||
};
|
||||
|
||||
const getMedalEmoji = (rank: number) => {
|
||||
switch (rank) {
|
||||
case 1: return '🥇';
|
||||
@@ -21,32 +26,12 @@ export function RankBadge({ rank, size = 'md', showLabel = true }: RankBadgeProp
|
||||
|
||||
const medal = getMedalEmoji(rank);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'text-sm px-2 py-1',
|
||||
md: 'text-base px-3 py-1.5',
|
||||
lg: 'text-lg px-4 py-2'
|
||||
};
|
||||
|
||||
const getRankColor = (rank: number) => {
|
||||
if (rank <= 3) return 'bg-warning-amber/20 text-warning-amber border-warning-amber/30';
|
||||
if (rank <= 10) return 'bg-primary-blue/20 text-primary-blue border-primary-blue/30';
|
||||
if (rank <= 50) return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
|
||||
return 'bg-charcoal-outline/20 text-gray-300 border-charcoal-outline';
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="span"
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
gap={1.5}
|
||||
rounded="md"
|
||||
border
|
||||
className={`font-medium ${getRankColor(rank)} ${sizeClasses[size]}`}
|
||||
>
|
||||
{medal && <Text>{medal}</Text>}
|
||||
{showLabel && <Text>#{rank}</Text>}
|
||||
{!showLabel && !medal && <Text>#{rank}</Text>}
|
||||
</Box>
|
||||
<Badge variant={getVariant(rank)} size={size}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
|
||||
{medal && <span>{medal}</span>}
|
||||
<Text size="xs" weight="bold">#{rank}</Text>
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Crown, Medal } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface RankMedalProps {
|
||||
rank: number;
|
||||
@@ -12,11 +12,12 @@ interface RankMedalProps {
|
||||
|
||||
export function RankMedal({ rank, size = 'md', showIcon = true }: RankMedalProps) {
|
||||
const isTop3 = rank <= 3;
|
||||
const variant = MedalDisplay.getVariant(rank);
|
||||
|
||||
const sizeMap = {
|
||||
sm: '7',
|
||||
md: '8',
|
||||
lg: '10',
|
||||
const sizePx = {
|
||||
sm: '1.75rem',
|
||||
md: '2rem',
|
||||
lg: '2.5rem',
|
||||
};
|
||||
|
||||
const textSizeMap = {
|
||||
@@ -32,22 +33,23 @@ export function RankMedal({ rank, size = 'md', showIcon = true }: RankMedalProps
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="full"
|
||||
border
|
||||
h={sizeMap[size]}
|
||||
w={sizeMap[size]}
|
||||
bg={MedalDisplay.getBg(rank)}
|
||||
color={MedalDisplay.getColor(rank)}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '9999px',
|
||||
border: '1px solid var(--ui-color-border-default)',
|
||||
height: sizePx[size],
|
||||
width: sizePx[size],
|
||||
backgroundColor: 'var(--ui-color-bg-surface-muted)'
|
||||
}}
|
||||
>
|
||||
{isTop3 && showIcon ? (
|
||||
<Icon icon={rank === 1 ? Crown : Medal} size={iconSize[size]} />
|
||||
<Icon icon={rank === 1 ? Crown : Medal} size={iconSize[size]} intent={variant as any} />
|
||||
) : (
|
||||
<Text weight="bold" size={textSizeMap[size]}>{rank}</Text>
|
||||
<Text weight="bold" size={textSizeMap[size]} variant={variant as any}>{rank}</Text>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { TableCell, TableRow } from '@/ui/Table';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { RankMedal } from './RankMedal';
|
||||
import React from 'react';
|
||||
|
||||
interface TeamRankingRowProps {
|
||||
id: string;
|
||||
@@ -32,70 +32,62 @@ export function TeamRankingRow({
|
||||
<TableRow
|
||||
clickable={!!onClick}
|
||||
onClick={onClick}
|
||||
group
|
||||
>
|
||||
<TableCell>
|
||||
<Box w="8" display="flex" justifyContent="center">
|
||||
<div style={{ width: '2rem', display: 'flex', justifyContent: 'center' }}>
|
||||
<RankMedal rank={rank} size="md" />
|
||||
</Box>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box
|
||||
position="relative"
|
||||
w="10"
|
||||
h="10"
|
||||
rounded="lg"
|
||||
overflow="hidden"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
bg="bg-graphite-black/50"
|
||||
groupHoverBorderColor="purple-400/50"
|
||||
transition
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
width: '2.5rem',
|
||||
height: '2.5rem',
|
||||
borderRadius: '0.5rem',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid var(--ui-color-border-default)',
|
||||
backgroundColor: 'var(--ui-color-bg-surface-muted)'
|
||||
}}>
|
||||
<Image
|
||||
src={logoUrl || getMediaUrl('team-logo', id)}
|
||||
alt={name}
|
||||
width={40}
|
||||
height={40}
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
/>
|
||||
</Box>
|
||||
<Box minWidth="0">
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<Text
|
||||
weight="semibold"
|
||||
color="text-white"
|
||||
variant="high"
|
||||
block
|
||||
truncate
|
||||
groupHoverTextColor="text-purple-400"
|
||||
transition
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block mt={0.5}>
|
||||
<Text size="xs" variant="low" block>
|
||||
{memberCount} Members
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell textAlign="center">
|
||||
<Text font="mono" weight="bold" color="text-purple-400">
|
||||
<Text font="mono" weight="bold" variant="primary">
|
||||
{rating}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
<TableCell textAlign="center">
|
||||
<Text font="mono" weight="bold" color="text-performance-green">
|
||||
<Text font="mono" weight="bold" variant="success">
|
||||
{wins}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
<TableCell textAlign="center">
|
||||
<Text color="text-gray-400" font="mono">{races}</Text>
|
||||
<Text variant="low" font="mono">{races}</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user