website refactor
This commit is contained in:
@@ -12,7 +12,7 @@ import React, { forwardRef, ForwardedRef, ElementType } from 'react';
|
||||
* If you need more complex behavior, create a specific component in apps/website/components.
|
||||
*/
|
||||
|
||||
export 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;
|
||||
export 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 | string;
|
||||
|
||||
interface ResponsiveSpacing {
|
||||
base?: Spacing;
|
||||
@@ -82,7 +82,7 @@ export interface BoxProps<T extends ElementType> {
|
||||
h?: string | number | ResponsiveValue<string | number>;
|
||||
|
||||
// Display
|
||||
display?: 'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string | ResponsiveValue<'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string>;
|
||||
display?: 'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string | ResponsiveValue<'block' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'none' | string | any>;
|
||||
center?: boolean;
|
||||
overflow?: 'auto' | 'hidden' | 'visible' | 'scroll' | string;
|
||||
overflowX?: 'auto' | 'hidden' | 'visible' | 'scroll';
|
||||
@@ -393,7 +393,13 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
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',
|
||||
'auto': 'auto'
|
||||
'auto': 'auto',
|
||||
'none': '0',
|
||||
'xs': '2',
|
||||
'sm': '4',
|
||||
'md': '6',
|
||||
'lg': '8',
|
||||
'xl': '12'
|
||||
};
|
||||
|
||||
const getSpacingClass = (prefix: string, value: Spacing | 'auto' | ResponsiveSpacing | undefined) => {
|
||||
|
||||
@@ -41,6 +41,12 @@ export interface ButtonProps {
|
||||
borderWidth?: string | any;
|
||||
aspectRatio?: string | any;
|
||||
border?: boolean | any;
|
||||
ring?: string | any;
|
||||
overflow?: string | any;
|
||||
display?: string | any;
|
||||
transform?: string | any;
|
||||
hoverScale?: boolean | any;
|
||||
minHeight?: string | any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { ReactNode, forwardRef } from 'react';
|
||||
import { Heading } from './Heading';
|
||||
import { Spacing } from './Box';
|
||||
|
||||
export interface CardProps {
|
||||
children: ReactNode;
|
||||
variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | any;
|
||||
variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | 'precision' | 'bordered' | 'elevated' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary';
|
||||
title?: string | ReactNode;
|
||||
footer?: ReactNode;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | number | any;
|
||||
padding?: Spacing | number | any;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
bg?: string;
|
||||
@@ -28,6 +29,9 @@ export interface CardProps {
|
||||
gap?: number;
|
||||
py?: number;
|
||||
backgroundColor?: string;
|
||||
group?: boolean | any;
|
||||
w?: string | any;
|
||||
justifyContent?: string | any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +72,13 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({
|
||||
outline: 'bg-transparent border-[var(--ui-color-border-default)]',
|
||||
glass: 'bg-white/[0.03] backdrop-blur-md border-white/[0.05]',
|
||||
dark: 'bg-[var(--ui-color-bg-base)] border-[var(--ui-color-border-default)]',
|
||||
precision: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-[inset_0_1px_0_0_rgba(255,255,255,0.02)]',
|
||||
bordered: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)]',
|
||||
elevated: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-md',
|
||||
'rarity-common': 'bg-gray-500/10 border-gray-500/50',
|
||||
'rarity-rare': 'bg-blue-500/10 border-blue-500/50',
|
||||
'rarity-epic': 'bg-purple-500/10 border-purple-500/50',
|
||||
'rarity-legendary': 'bg-orange-500/10 border-orange-500/50',
|
||||
};
|
||||
|
||||
const paddingClasses = {
|
||||
@@ -78,7 +89,7 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({
|
||||
};
|
||||
|
||||
const getPaddingClass = (pad: any) => {
|
||||
if (typeof pad === 'string') return paddingClasses[pad as keyof typeof paddingClasses] || paddingClasses.md;
|
||||
if (typeof pad === 'string') return `p-${pad}`;
|
||||
return ''; // Handled in style
|
||||
};
|
||||
|
||||
@@ -86,8 +97,8 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({
|
||||
...style,
|
||||
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
|
||||
...(backgroundColor ? { backgroundColor } : {}),
|
||||
...(p !== undefined ? { padding: `${p * 0.25}rem` } : {}),
|
||||
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
|
||||
...(p !== undefined ? { padding: typeof p === 'number' ? `${p * 0.25}rem` : undefined } : {}),
|
||||
...(py !== undefined ? { paddingTop: typeof py === 'number' ? `${py * 0.25}rem` : undefined, paddingBottom: typeof py === 'number' ? `${py * 0.25}rem` : undefined } : {}),
|
||||
...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {}),
|
||||
...(responsiveColSpan?.lg ? { gridColumn: `span ${responsiveColSpan.lg} / span ${responsiveColSpan.lg}` } : {}),
|
||||
...(overflow ? { overflow } : {}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, forwardRef } from 'react';
|
||||
import React, { ReactNode, forwardRef, CSSProperties } from 'react';
|
||||
|
||||
export interface HeadingProps {
|
||||
children: ReactNode;
|
||||
@@ -8,7 +8,7 @@ export interface HeadingProps {
|
||||
uppercase?: boolean;
|
||||
intent?: 'primary' | 'telemetry' | 'warning' | 'critical' | 'default' | any;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
style?: CSSProperties;
|
||||
mb?: number | any;
|
||||
marginBottom?: number | any;
|
||||
mt?: number | any;
|
||||
@@ -19,6 +19,10 @@ export interface HeadingProps {
|
||||
truncate?: boolean;
|
||||
size?: string;
|
||||
icon?: ReactNode;
|
||||
id?: string;
|
||||
lineHeight?: string | any;
|
||||
groupHoverColor?: string | any;
|
||||
transition?: boolean | any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,94 +1,100 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Heading } from './Heading';
|
||||
import { Text } from './Text';
|
||||
import React, { ReactNode, CSSProperties } from 'react';
|
||||
import { Spacing } from './Box';
|
||||
|
||||
export interface PanelProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
variant?: 'default' | 'muted' | 'ghost' | 'dark' | any;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg' | number | any;
|
||||
variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | 'precision' | 'bordered' | 'elevated';
|
||||
padding?: Spacing | number;
|
||||
onClick?: () => void;
|
||||
style?: CSSProperties;
|
||||
title?: string | ReactNode;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
description?: string;
|
||||
footer?: ReactNode;
|
||||
border?: boolean;
|
||||
rounded?: string;
|
||||
className?: string;
|
||||
borderColor?: string;
|
||||
bg?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel - Redesigned for "Modern Precision" theme.
|
||||
* Includes compatibility props to prevent app-wide breakage.
|
||||
*/
|
||||
export const Panel = ({
|
||||
title,
|
||||
description,
|
||||
export function Panel({
|
||||
children,
|
||||
footer,
|
||||
variant = 'default',
|
||||
variant = 'default',
|
||||
padding = 'md',
|
||||
actions,
|
||||
className,
|
||||
onClick,
|
||||
style,
|
||||
title,
|
||||
actions,
|
||||
description,
|
||||
footer,
|
||||
border,
|
||||
rounded,
|
||||
borderColor,
|
||||
bg,
|
||||
}: PanelProps) => {
|
||||
className
|
||||
}: PanelProps) {
|
||||
const variantClasses = {
|
||||
default: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)]',
|
||||
muted: 'bg-[var(--ui-color-bg-surface-muted)] border-[var(--ui-color-border-muted)]',
|
||||
ghost: 'bg-transparent border-transparent',
|
||||
dark: 'bg-[var(--ui-color-bg-base)] border-[var(--ui-color-border-default)]',
|
||||
default: 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] shadow-sm',
|
||||
muted: 'bg-[var(--ui-color-bg-surface-muted)] border border-[var(--ui-color-border-muted)]',
|
||||
outline: 'bg-transparent border border-[var(--ui-color-border-default)]',
|
||||
glass: 'bg-white/[0.03] backdrop-blur-md border border-white/[0.05]',
|
||||
dark: 'bg-[var(--ui-color-bg-base)] border border-[var(--ui-color-border-default)]',
|
||||
precision: 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] shadow-[inset_0_1px_0_0_rgba(255,255,255,0.02)]',
|
||||
bordered: 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)]',
|
||||
elevated: 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] shadow-md',
|
||||
};
|
||||
|
||||
const paddingClasses = {
|
||||
none: 'p-0',
|
||||
sm: 'p-2',
|
||||
md: 'p-4',
|
||||
lg: 'p-8',
|
||||
xs: 'p-2',
|
||||
sm: 'p-4',
|
||||
md: 'p-6',
|
||||
lg: 'p-10',
|
||||
};
|
||||
|
||||
const getPaddingClass = (pad: any) => {
|
||||
if (typeof pad === 'string') return paddingClasses[pad as keyof typeof paddingClasses] || paddingClasses.md;
|
||||
return ''; // Handled in style
|
||||
const getPaddingClass = (p: any) => {
|
||||
if (typeof p === 'string') return `p-${p}`;
|
||||
return '';
|
||||
};
|
||||
|
||||
const combinedStyle: React.CSSProperties = {
|
||||
...style,
|
||||
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
|
||||
...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {}),
|
||||
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}),
|
||||
...(border === false ? { border: 'none' } : {}),
|
||||
};
|
||||
const interactiveClasses = onClick
|
||||
? 'cursor-pointer hover:border-[var(--ui-color-border-bright)] transition-all duration-200 ease-in-out active:scale-[0.99]'
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={`border ${variantClasses[variant as keyof typeof variantClasses] || variantClasses.default} ${getPaddingClass(padding)} transition-all duration-200 ${className || ''}`} style={combinedStyle}>
|
||||
{(title || description || actions) && (
|
||||
<div className={`border-b border-[var(--ui-color-border-muted)] flex items-center justify-between ${getPaddingClass(padding)}`}>
|
||||
<div>
|
||||
{title && <Heading level={4} weight="semibold" uppercase>{title}</Heading>}
|
||||
{description && <Text size="xs" variant="low">{description}</Text>}
|
||||
</div>
|
||||
{actions && (
|
||||
<div className="flex items-center gap-2">
|
||||
{actions}
|
||||
<div
|
||||
className={`${variantClasses[variant]} ${getPaddingClass(padding)} ${interactiveClasses} ${rounded ? `rounded-${rounded}` : 'rounded-md'} ${border ? 'border' : ''} ${className || ''}`}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
...style,
|
||||
...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {})
|
||||
}}
|
||||
>
|
||||
{(title || actions) && (
|
||||
<div className="flex items-center justify-between mb-6 border-b border-[var(--ui-color-border-muted)] pb-4">
|
||||
{title && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1 h-4 bg-[var(--ui-color-intent-primary)]" />
|
||||
<h3 className="text-xs font-bold uppercase tracking-widest text-[var(--ui-color-text-high)]">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-[10px] text-[var(--ui-color-text-low)] uppercase mono ml-3">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{actions && <div>{actions}</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={typeof padding === 'number' ? '' : getPaddingClass(padding)}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{footer && (
|
||||
<div className={`border-t border-[var(--ui-color-border-muted)] bg-white/[0.02] ${getPaddingClass(padding)}`}>
|
||||
<div className="mt-6 pt-4 border-t border-[var(--ui-color-border-muted)]">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface SurfaceProps<T extends ElementType = 'div'> extends BoxProps<T>
|
||||
as?: T;
|
||||
children?: ReactNode;
|
||||
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'discord' | 'gradient-blue' | 'gradient-gold' | 'gradient-purple' | 'gradient-green' | 'discord-inner' | 'outline' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary' | 'precision';
|
||||
rounded?: keyof ThemeRadii | 'none' | '2xl';
|
||||
shadow?: keyof ThemeShadows | 'none';
|
||||
rounded?: keyof ThemeRadii | 'none' | '2xl' | string | boolean;
|
||||
shadow?: keyof ThemeShadows | 'none' | string;
|
||||
}
|
||||
|
||||
export const Surface = forwardRef(<T extends ElementType = 'div'>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import React, { ReactNode, CSSProperties } from 'react';
|
||||
import { Surface } from './Surface';
|
||||
|
||||
export interface TableProps {
|
||||
@@ -16,7 +16,14 @@ export const Table = ({ children, className }: TableProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const TableHeader = ({ children, className, textAlign, w }: { children: ReactNode, className?: string, textAlign?: 'left' | 'center' | 'right', w?: string }) => {
|
||||
export interface TableHeaderProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
w?: string;
|
||||
}
|
||||
|
||||
export const TableHeader = ({ children, className, textAlign, w }: TableHeaderProps) => {
|
||||
return (
|
||||
<thead className={`bg-[var(--ui-color-bg-base)] border-b border-[var(--ui-color-border-default)] ${className || ''}`}>
|
||||
<tr>
|
||||
@@ -55,7 +62,14 @@ export const TableRow = ({ children, onClick, className, variant, clickable, bg,
|
||||
);
|
||||
};
|
||||
|
||||
export const TableHeaderCell = ({ children, textAlign, w, className }: { children: ReactNode, textAlign?: 'left' | 'center' | 'right', w?: string, className?: string }) => {
|
||||
export interface TableHeaderCellProps {
|
||||
children: ReactNode;
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
w?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TableHeaderCell = ({ children, textAlign, w, className }: TableHeaderCellProps) => {
|
||||
const alignClass = textAlign === 'center' ? 'text-center' : (textAlign === 'right' ? 'text-right' : 'text-left');
|
||||
return (
|
||||
<th
|
||||
@@ -67,7 +81,18 @@ export const TableHeaderCell = ({ children, textAlign, w, className }: { childre
|
||||
);
|
||||
};
|
||||
|
||||
export const TableCell = ({ children, textAlign, className, py, colSpan, w, position, ...props }: { children: ReactNode, textAlign?: 'left' | 'center' | 'right', className?: string, py?: number, colSpan?: number, w?: string, position?: string, [key: string]: any }) => {
|
||||
export interface TableCellProps {
|
||||
children: ReactNode;
|
||||
textAlign?: 'left' | 'center' | 'right';
|
||||
className?: string;
|
||||
py?: number;
|
||||
colSpan?: number;
|
||||
w?: string;
|
||||
position?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const TableCell = ({ children, textAlign, className, py, colSpan, w, position, ...props }: TableCellProps) => {
|
||||
const alignClass = textAlign === 'center' ? 'text-center' : (textAlign === 'right' ? 'text-right' : 'text-left');
|
||||
return (
|
||||
<td
|
||||
|
||||
@@ -1,81 +1,115 @@
|
||||
import { ChevronRight, Globe, Users } from 'lucide-react';
|
||||
import { ChevronRight, Globe, Users, Zap } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Badge } from './Badge';
|
||||
import { Box } from './Box';
|
||||
import { Card } from './Card';
|
||||
import { Heading } from './Heading';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
import { Badge } from './Badge';
|
||||
|
||||
export interface TeamCardProps {
|
||||
name: string;
|
||||
description?: string;
|
||||
leagueName?: string;
|
||||
logo?: ReactNode;
|
||||
memberCount: number;
|
||||
isRecruiting?: boolean;
|
||||
badges?: ReactNode;
|
||||
rating?: string;
|
||||
wins?: string;
|
||||
races?: string;
|
||||
region?: string;
|
||||
isRecruiting?: boolean;
|
||||
performanceLevel?: string;
|
||||
onClick?: () => void;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const TeamCard = ({
|
||||
name,
|
||||
description,
|
||||
leagueName,
|
||||
logo,
|
||||
memberCount,
|
||||
isRecruiting,
|
||||
badges,
|
||||
region,
|
||||
rating,
|
||||
wins,
|
||||
races,
|
||||
region = 'EU',
|
||||
isRecruiting,
|
||||
performanceLevel,
|
||||
description,
|
||||
onClick
|
||||
}: TeamCardProps) => {
|
||||
return (
|
||||
<Card
|
||||
variant="dark"
|
||||
onClick={onClick}
|
||||
style={{ cursor: onClick ? 'pointer' : 'default', height: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
variant="precision"
|
||||
padding="none"
|
||||
onClick={onClick}
|
||||
transition
|
||||
>
|
||||
<Box display="flex" gap={4} marginBottom={4}>
|
||||
<Box width={16} height={16} rounded="lg" bg="var(--ui-color-bg-surface-muted)" style={{ border: '1px solid var(--ui-color-border-default)', overflow: 'hidden', flexShrink: 0 }}>
|
||||
{logo}
|
||||
</Box>
|
||||
<Box flex={1} minWidth="0">
|
||||
<Box display="flex" alignItems="start" justifyContent="between" gap={2}>
|
||||
<Heading level={4} weight="bold" truncate>{name}</Heading>
|
||||
{isRecruiting && <Badge variant="success" size="sm">RECRUITING</Badge>}
|
||||
</Box>
|
||||
<Box display="flex" gap={2} flexWrap="wrap" marginTop={2}>
|
||||
{badges}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Text size="xs" variant="low" lineClamp={2} style={{ height: '2.5rem', marginBottom: '1rem' }} block leading="relaxed">
|
||||
{description || 'No description available'}
|
||||
</Text>
|
||||
|
||||
{region && (
|
||||
<Box marginBottom={4}>
|
||||
<Badge variant="default" size="sm">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
|
||||
<Icon icon={Globe} size={3} intent="primary" />
|
||||
<Text size="xs" weight="bold">{region}</Text>
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Header: Logo and Identity */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-[var(--ui-color-bg-base)] flex items-center justify-center overflow-hidden border border-[var(--ui-color-border-muted)]">
|
||||
{logo || <Icon icon={Users} size={5} intent="low" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Heading level={4} weight="bold" truncate uppercase>{name}</Heading>
|
||||
{isRecruiting && (
|
||||
<Badge variant="success" size="xs">RECRUITING</Badge>
|
||||
)}
|
||||
</div>
|
||||
</Badge>
|
||||
</Box>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{leagueName && <Text size="xs" variant="low" truncate uppercase mono>{leagueName}</Text>}
|
||||
{performanceLevel && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Icon icon={Zap} size={3} intent="telemetry" />
|
||||
<Text size="xs" variant="telemetry" mono uppercase>{performanceLevel}</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Box marginTop="auto" paddingTop={4} style={{ borderTop: '1px solid var(--ui-color-border-muted)', opacity: 0.5 }} display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Icon icon={Users} size={3} intent="low" />
|
||||
<Text size="xs" variant="low" font="mono">
|
||||
{memberCount} {memberCount === 1 ? 'MEMBER' : 'MEMBERS'}
|
||||
{/* Technical Stats Grid - Engineered Look */}
|
||||
{(rating || wins || races) && (
|
||||
<div className="grid grid-cols-3 gap-px bg-[var(--ui-color-border-muted)] border border-[var(--ui-color-border-muted)]">
|
||||
<div className="p-3 bg-[var(--ui-color-bg-surface)] text-center">
|
||||
<Text size="xs" variant="low" uppercase block mb={1} mono>Rating</Text>
|
||||
<Text size="md" weight="bold" mono variant="primary">{rating || '-'}</Text>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--ui-color-bg-surface)] text-center">
|
||||
<Text size="xs" variant="low" uppercase block mb={1} mono>Wins</Text>
|
||||
<Text size="md" weight="bold" mono variant="telemetry">{wins || '-'}</Text>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--ui-color-bg-surface)] text-center">
|
||||
<Text size="xs" variant="low" uppercase block mb={1} mono>Races</Text>
|
||||
<Text size="md" weight="bold" mono variant="high">{races || '-'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{description && (
|
||||
<Text size="xs" variant="low" lineClamp={2} block leading="relaxed">
|
||||
{description}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase>VIEW</Text>
|
||||
<Icon icon={ChevronRight} size={3} intent="low" />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Footer: Metadata */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-[var(--ui-color-border-muted)]">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon icon={Users} size={3} intent="low" />
|
||||
<Text size="xs" variant="low" mono>{memberCount}</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon icon={Globe} size={3} intent="low" />
|
||||
<Text size="xs" variant="low" mono>{region}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 text-[var(--ui-color-intent-primary)]">
|
||||
<Text size="xs" weight="bold" uppercase mono>Details</Text>
|
||||
<Icon icon={ChevronRight} size={3} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
33
apps/website/ui/TeamsHeader.tsx
Normal file
33
apps/website/ui/TeamsHeader.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Heading } from './Heading';
|
||||
import { Text } from './Text';
|
||||
|
||||
interface TeamsHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
action?: ReactNode;
|
||||
}
|
||||
|
||||
export function TeamsHeader({ title, subtitle, action }: TeamsHeaderProps) {
|
||||
return (
|
||||
<div className="mb-12 flex flex-col md:flex-row md:items-end justify-between gap-6 border-b border-[var(--ui-color-border-muted)] pb-8">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-1 h-8 bg-[var(--ui-color-intent-primary)]" />
|
||||
<Heading level={1} weight="bold" uppercase>{title}</Heading>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<Text variant="low" size="lg" uppercase mono className="tracking-[0.2em]">
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{action && (
|
||||
<div className="flex items-center">
|
||||
{action}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { ElementType, ReactNode, forwardRef } from 'react';
|
||||
import React, { ElementType, ReactNode, forwardRef, CSSProperties } from 'react';
|
||||
import { ResponsiveValue } from './Box';
|
||||
|
||||
export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'base';
|
||||
|
||||
@@ -15,7 +16,7 @@ export interface TextProps {
|
||||
block?: boolean;
|
||||
truncate?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
style?: CSSProperties;
|
||||
mt?: number | any;
|
||||
mb?: number | any;
|
||||
ml?: number | any;
|
||||
@@ -29,7 +30,7 @@ export interface TextProps {
|
||||
flexGrow?: number;
|
||||
flexShrink?: number;
|
||||
lineClamp?: number;
|
||||
display?: string;
|
||||
display?: string | ResponsiveValue<string | any>;
|
||||
opacity?: number;
|
||||
maxWidth?: string | number;
|
||||
mx?: string | number;
|
||||
@@ -170,6 +171,20 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
loose: 'leading-loose',
|
||||
};
|
||||
|
||||
const getResponsiveClasses = (prefix: string, value: any | ResponsiveValue<any> | undefined) => {
|
||||
if (value === undefined) return '';
|
||||
if (typeof value === 'object') {
|
||||
const classes = [];
|
||||
if (value.base !== undefined) classes.push(prefix ? `${prefix}-${value.base}` : String(value.base));
|
||||
if (value.sm !== undefined) classes.push(prefix ? `sm:${prefix}-${value.sm}` : `sm:${value.sm}`);
|
||||
if (value.md !== undefined) classes.push(prefix ? `md:${prefix}-${value.md}` : `md:${value.md}`);
|
||||
if (value.lg !== undefined) classes.push(prefix ? `lg:${prefix}-${value.lg}` : `lg:${value.lg}`);
|
||||
if (value.xl !== undefined) classes.push(prefix ? `xl:${prefix}-${value.xl}` : `xl:${value.xl}`);
|
||||
return classes.join(' ');
|
||||
}
|
||||
return prefix ? `${prefix}-${value}` : String(value);
|
||||
};
|
||||
|
||||
const classes = [
|
||||
variantClasses[variant as keyof typeof variantClasses] || '',
|
||||
getResponsiveSize(size),
|
||||
@@ -180,6 +195,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
leadingClasses[leading as keyof typeof leadingClasses] || '',
|
||||
block ? 'block' : 'inline',
|
||||
truncate ? 'truncate' : '',
|
||||
getResponsiveClasses('display', display),
|
||||
transition ? 'transition-all duration-200' : '',
|
||||
italic ? 'italic' : '',
|
||||
animate === 'pulse' ? 'animate-pulse' : '',
|
||||
@@ -189,7 +205,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
|
||||
const combinedStyle: React.CSSProperties = {
|
||||
...style,
|
||||
...(display ? { display } : {}),
|
||||
...(typeof display === 'string' ? { display } : {}),
|
||||
...(alignItems ? { alignItems } : {}),
|
||||
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
|
||||
...(cursor ? { cursor } : {}),
|
||||
@@ -219,7 +235,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
...(transform ? { textTransform: transform as any } : {}),
|
||||
};
|
||||
|
||||
const Tag = as;
|
||||
const Tag = as || 'p';
|
||||
|
||||
return (
|
||||
<Tag ref={ref} className={classes} style={combinedStyle} aria-label={ariaLabel} htmlFor={htmlFor}>
|
||||
|
||||
Reference in New Issue
Block a user