website refactor

This commit is contained in:
2026-01-20 01:22:05 +01:00
parent f8e7ec7948
commit 30a31dc44f
21 changed files with 1242 additions and 393 deletions

View File

@@ -6,48 +6,47 @@ import { Icon } from './Icon';
export interface ButtonProps {
children: ReactNode;
onClick?: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'success' | 'discord' | 'race-final';
size?: 'sm' | 'md' | 'lg';
variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'success' | 'discord' | 'race-final' | any;
size?: 'sm' | 'md' | 'lg' | any;
disabled?: boolean;
isLoading?: boolean;
type?: 'button' | 'submit' | 'reset';
icon?: ReactNode;
fullWidth?: boolean;
as?: 'button' | 'a';
as?: 'button' | 'a' | any;
href?: string;
target?: string;
rel?: string;
title?: string;
style?: React.CSSProperties;
rounded?: boolean | string;
className?: string;
bg?: string;
color?: string;
w?: string | number;
h?: string | number;
px?: number;
borderColor?: string;
mt?: number;
position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
letterSpacing?: string;
hoverBorderColor?: string;
fontSize?: string;
p?: number;
minHeight?: string;
transition?: boolean;
ring?: string;
transform?: string;
hoverScale?: boolean;
overflow?: string;
borderWidth?: string;
aspectRatio?: string;
border?: boolean;
shadow?: string;
display?: string;
center?: boolean;
justifyContent?: string;
style?: React.CSSProperties;
rounded?: boolean | string | any;
mt?: number | any;
p?: number | any;
px?: number | any;
py?: number | any;
w?: string | number | any;
h?: string | number | any;
bg?: string | any;
color?: string | any;
borderColor?: string | any;
hoverBorderColor?: string | any;
letterSpacing?: string | any;
fontSize?: string | any;
transition?: boolean | any;
center?: boolean | any;
justifyContent?: string | any;
shadow?: string | any;
position?: string | any;
borderWidth?: string | any;
aspectRatio?: string | any;
border?: boolean | any;
}
/**
* Button - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage.
*/
export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(({
children,
onClick,
@@ -63,94 +62,80 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
target,
rel,
title,
style: styleProp,
rounded = false,
className,
bg,
color,
style,
rounded,
mt,
p,
px,
py,
w,
h,
px,
bg,
color,
borderColor,
mt,
position,
letterSpacing,
hoverBorderColor,
letterSpacing,
fontSize,
p,
minHeight,
transition,
ring,
transform,
hoverScale,
overflow,
center,
justifyContent,
shadow,
position,
borderWidth,
aspectRatio,
border,
shadow,
display,
center,
justifyContent,
}, ref) => {
const baseClasses = 'inline-flex items-center justify-center focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold';
const transitionClasses = transition !== false ? 'transition-all duration-150 ease-in-out' : '';
const baseClasses = 'inline-flex items-center justify-center focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold transition-all duration-150 ease-in-out';
const variantClasses = {
primary: 'bg-[var(--ui-color-intent-primary)] text-white hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-primary)] shadow-[0_0_15px_rgba(25,140,255,0.3)] hover:shadow-[0_0_25px_rgba(25,140,255,0.5)]',
primary: 'bg-[var(--ui-color-intent-primary)] text-white hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-primary)] shadow-[0_0_15px_rgba(25,140,255,0.2)]',
secondary: 'bg-[var(--ui-color-bg-surface)] text-white border border-[var(--ui-color-border-default)] hover:bg-[var(--ui-color-border-default)] focus-visible:outline-[var(--ui-color-intent-primary)]',
danger: 'bg-[var(--ui-color-intent-critical)] text-white hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-critical)]',
ghost: 'bg-transparent text-[var(--ui-color-text-low)] hover:text-[var(--ui-color-text-high)] hover:bg-white/5 focus-visible:outline-[var(--ui-color-text-low)]',
success: 'bg-[var(--ui-color-intent-success)] text-[var(--ui-color-bg-base)] hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-success)]',
'race-final': 'bg-[var(--ui-color-intent-success)] text-[var(--ui-color-bg-base)] hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-success)]',
discord: 'bg-[#5865F2] text-white hover:bg-[#4752C4] focus-visible:outline-[#5865F2]',
'race-final': 'bg-[var(--ui-color-intent-success)] text-[var(--ui-color-bg-base)] hover:opacity-90 focus-visible:outline-[var(--ui-color-intent-success)]',
};
const sizeClasses = {
sm: 'min-h-[32px] px-3 py-1 text-xs',
md: 'min-h-[40px] px-4 py-2 text-sm',
lg: 'min-h-[48px] px-6 py-3 text-base'
sm: 'min-h-[32px] px-3 py-1 text-[10px]',
md: 'min-h-[40px] px-4 py-2 text-xs',
lg: 'min-h-[48px] px-6 py-3 text-sm'
};
const disabledClasses = (disabled || isLoading) ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer';
const widthClasses = fullWidth ? 'w-full' : '';
const roundedClasses = rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none');
const classes = [
baseClasses,
transitionClasses,
variantClasses[variant],
sizeClasses[size],
variantClasses[variant as keyof typeof variantClasses] || variantClasses.primary,
sizeClasses[size as keyof typeof sizeClasses] || sizeClasses.md,
disabledClasses,
widthClasses,
roundedClasses,
ring,
hoverScale ? 'hover:scale-105' : '',
display === 'flex' ? 'flex' : '',
rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none'),
center ? 'items-center justify-center' : '',
justifyContent ? `justify-${justifyContent}` : '',
className,
].filter(Boolean).join(' ');
const style: React.CSSProperties = {
...styleProp,
const combinedStyle: React.CSSProperties = {
...style,
...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(p !== undefined ? { padding: typeof p === 'number' ? `${p * 0.25}rem` : p } : {}),
...(px !== undefined ? { paddingLeft: typeof px === 'number' ? `${px * 0.25}rem` : px, paddingRight: typeof px === 'number' ? `${px * 0.25}rem` : px } : {}),
...(py !== undefined ? { paddingTop: typeof py === 'number' ? `${py * 0.25}rem` : py, paddingBottom: typeof py === 'number' ? `${py * 0.25}rem` : py } : {}),
...(w !== undefined ? { width: w } : {}),
...(h !== undefined ? { height: h } : {}),
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
...(color ? { color: color.startsWith('text-') ? undefined : color } : {}),
...(w ? { width: w } : {}),
...(h ? { height: h } : {}),
...(px ? { paddingLeft: `${px * 0.25}rem`, paddingRight: `${px * 0.25}rem` } : {}),
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor, borderStyle: 'solid', borderWidth: '1px' } : {}),
...(mt ? { marginTop: `${mt * 0.25}rem` } : {}),
...(position ? { position } : {}),
...(letterSpacing ? { letterSpacing } : {}),
...(fontSize ? { fontSize } : {}),
...(p !== undefined ? { padding: `${p * 0.25}rem` } : {}),
...(minHeight ? { minHeight } : {}),
...(transform ? { transform } : {}),
...(overflow ? { overflow } : {}),
...(justifyContent ? { justifyContent } : {}),
...(shadow ? { boxShadow: shadow } : {}),
...(position ? { position } : {}),
...(borderWidth ? { borderWidth } : {}),
...(aspectRatio ? { aspectRatio } : {}),
...(border ? { border: '1px solid var(--ui-color-border-default)' } : {}),
...(shadow ? { boxShadow: shadow.startsWith('shadow-') ? undefined : shadow } : {}),
};
const content = (
@@ -161,35 +146,23 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
</Box>
);
if (as === 'a') {
return (
<a
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
href={href}
target={target}
rel={rel}
className={classes}
onClick={onClick as MouseEventHandler<HTMLAnchorElement>}
style={style}
title={title}
>
{content}
</a>
);
}
const Tag = as === 'a' ? 'a' : 'button';
return (
<button
ref={ref as React.ForwardedRef<HTMLButtonElement>}
type={type}
<Tag
ref={ref as any}
type={as === 'a' ? undefined : type}
href={as === 'a' ? href : undefined}
target={as === 'a' ? target : undefined}
rel={as === 'a' ? rel : undefined}
className={classes}
onClick={onClick as MouseEventHandler<HTMLButtonElement>}
disabled={disabled || isLoading}
style={style}
onClick={onClick as any}
disabled={as === 'a' ? undefined : (disabled || isLoading)}
style={combinedStyle}
title={title}
>
{content}
</button>
</Tag>
);
});

View File

@@ -1,73 +1,151 @@
import React, { ReactNode, forwardRef } from 'react';
import { Box } from './Box';
import { Surface, SurfaceProps } from './Surface';
import { Heading } from './Heading';
export interface CardProps extends Omit<SurfaceProps<'div'>, 'children' | 'title' | 'variant'> {
export interface CardProps {
children: ReactNode;
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'outline' | 'rarity-common' | 'rarity-rare' | 'rarity-epic' | 'rarity-legendary';
title?: ReactNode;
variant?: 'default' | 'muted' | 'outline' | 'glass' | 'dark' | any;
title?: string | ReactNode;
footer?: ReactNode;
padding?: 'none' | 'sm' | 'md' | 'lg' | number | any;
className?: string;
style?: React.CSSProperties;
bg?: string;
p?: number;
onClick?: () => void;
responsiveColSpan?: { lg: number };
overflow?: string;
rounded?: string | boolean;
borderLeft?: boolean;
borderColor?: string;
center?: boolean;
transition?: string | boolean;
hoverBorderColor?: string;
border?: boolean;
position?: string;
mb?: number;
display?: string;
alignItems?: string;
gap?: number;
py?: number;
backgroundColor?: string;
}
/**
* Card - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage.
*/
export const Card = forwardRef<HTMLDivElement, CardProps>(({
children,
variant = 'default',
title,
footer,
...props
padding = 'md',
className,
style,
bg,
p,
onClick,
responsiveColSpan,
overflow,
rounded,
borderLeft,
borderColor,
center,
transition,
hoverBorderColor,
border,
position,
mb,
display,
alignItems,
gap,
py,
backgroundColor,
}, ref) => {
const isOutline = variant === 'outline';
const style: React.CSSProperties = isOutline ? {
backgroundColor: 'transparent',
border: '1px solid var(--ui-color-border-default)',
} : {};
const variantClasses = {
default: 'bg-[var(--ui-color-bg-surface)] border-[var(--ui-color-border-default)] shadow-sm',
muted: 'bg-[var(--ui-color-bg-surface-muted)] border-[var(--ui-color-border-muted)]',
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)]',
};
const paddingClasses = {
none: 'p-0',
sm: 'p-2',
md: 'p-4',
lg: 'p-8',
};
const getPaddingClass = (pad: any) => {
if (typeof pad === 'string') return paddingClasses[pad as keyof typeof paddingClasses] || paddingClasses.md;
return ''; // Handled in style
};
const combinedStyle: React.CSSProperties = {
...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` } : {}),
...(typeof padding === 'number' ? { padding: `${padding * 0.25}rem` } : {}),
...(responsiveColSpan?.lg ? { gridColumn: `span ${responsiveColSpan.lg} / span ${responsiveColSpan.lg}` } : {}),
...(overflow ? { overflow } : {}),
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}),
...(borderLeft ? { borderLeft: `4px solid ${borderColor || 'var(--ui-color-intent-primary)'}` } : {}),
...(center ? { display: 'flex', alignItems: 'center', justifyContent: 'center' } : {}),
...(typeof transition === 'string' ? { transition } : {}),
...(position ? { position: position as any } : {}),
...(mb !== undefined ? { marginBottom: `${mb * 0.25}rem` } : {}),
...(display ? { display } : {}),
...(alignItems ? { alignItems } : {}),
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
...(border === false ? { border: 'none' } : {}),
};
return (
<Surface
<div
ref={ref}
variant={isOutline ? 'default' : variant}
rounded="lg"
shadow="md"
style={style}
{...props}
className={`border ${variantClasses[variant as keyof typeof variantClasses] || variantClasses.default} ${getPaddingClass(padding)} ${onClick ? 'cursor-pointer hover:border-[var(--ui-color-border-bright)]' : ''} ${transition === true ? 'transition-all duration-200' : ''} ${rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none')} ${className || ''}`}
style={combinedStyle}
onClick={onClick}
>
{title && (
<Box padding={4} borderBottom>
<div className={`border-b border-[var(--ui-color-border-muted)] ${getPaddingClass(padding)}`}>
{typeof title === 'string' ? (
<h3 className="text-lg font-bold text-[var(--ui-color-text-high)]">{title}</h3>
<Heading level={5} weight="bold" uppercase>{title}</Heading>
) : title}
</Box>
</div>
)}
<Box padding={4}>
<div className={typeof padding === 'number' || p !== undefined ? '' : getPaddingClass(padding)}>
{children}
</Box>
</div>
{footer && (
<Box padding={4} borderTop bg="rgba(255,255,255,0.02)">
<div className={`border-t border-[var(--ui-color-border-muted)] bg-white/[0.02] ${getPaddingClass(padding)}`}>
{footer}
</Box>
</div>
)}
</Surface>
</div>
);
});
Card.displayName = 'Card';
export const CardHeader = ({ title, children }: { title?: string, children?: ReactNode }) => (
<Box marginBottom={4}>
{title && <h3 className="text-lg font-bold text-[var(--ui-color-text-high)]">{title}</h3>}
<div className="mb-4">
{title && <Heading level={5} weight="bold" uppercase>{title}</Heading>}
{children}
</Box>
</div>
);
export const CardContent = ({ children }: { children: ReactNode }) => (
<Box>{children}</Box>
<div>{children}</div>
);
export const CardFooter = ({ children }: { children: ReactNode }) => (
<Box marginTop={4} paddingTop={4} borderTop>
<div className="mt-4 pt-4 border-t border-[var(--ui-color-border-muted)]">
{children}
</Box>
</div>
);

View File

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

View File

@@ -0,0 +1,20 @@
import React, { ReactNode } from 'react';
import { Grid } from './Grid';
interface FeatureGridProps {
children: ReactNode;
columns?: number | { base: number; md?: number; lg?: number };
gap?: number;
}
/**
* FeatureGrid - A semantic layout for displaying features or pillars.
* Allowed to use Grid primitive.
*/
export function FeatureGrid({ children, columns = { base: 1, md: 3 }, gap = 8 }: FeatureGridProps) {
return (
<Grid cols={columns} gap={gap}>
{children}
</Grid>
);
}

View File

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

View File

@@ -1,31 +1,49 @@
import { ReactNode, forwardRef } from 'react';
import { Box, BoxProps, ResponsiveValue, Spacing } from './Box';
export interface HeadingProps extends BoxProps<any> {
export interface HeadingProps {
children: ReactNode;
level?: 1 | 2 | 3 | 4 | 5 | 6;
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
align?: 'left' | 'center' | 'right';
fontSize?: string | ResponsiveValue<string>;
icon?: ReactNode;
groupHoverColor?: string;
uppercase?: boolean;
intent?: 'primary' | 'telemetry' | 'warning' | 'critical' | 'default' | any;
className?: string;
style?: React.CSSProperties;
mb?: number | any;
marginBottom?: number | any;
mt?: number | any;
marginTop?: number | any;
color?: string;
fontSize?: string | { base: string; sm?: string; md: string; lg?: string; xl?: string };
letterSpacing?: string;
mb?: Spacing;
truncate?: boolean;
size?: string;
icon?: ReactNode;
}
/**
* Heading - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage.
*/
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
children,
level = 1,
weight = 'bold',
align = 'left',
fontSize,
icon,
groupHoverColor,
uppercase,
letterSpacing,
uppercase = false,
intent = 'default',
className,
style,
mb,
...props
marginBottom,
mt,
marginTop,
color,
fontSize,
letterSpacing,
truncate,
size,
icon,
}, ref) => {
const Tag = `h${level}` as const;
@@ -37,37 +55,62 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
};
const sizeClasses = {
1: 'text-4xl md:text-5xl',
2: 'text-3xl md:text-4xl',
3: 'text-2xl md:text-3xl',
4: 'text-xl md:text-2xl',
5: 'text-lg md:text-xl',
6: 'text-base md:text-lg'
1: 'text-4xl md:text-5xl tracking-tighter leading-none',
2: 'text-3xl md:text-4xl tracking-tight leading-tight',
3: 'text-2xl md:text-3xl tracking-tight leading-snug',
4: 'text-xl md:text-2xl tracking-normal leading-normal',
5: 'text-lg md:text-xl tracking-normal leading-normal',
6: 'text-base md:text-lg tracking-wide leading-normal'
};
const intentClasses = {
default: 'text-[var(--ui-color-text-high)]',
primary: 'text-[var(--ui-color-intent-primary)]',
telemetry: 'text-[var(--ui-color-intent-telemetry)]',
warning: 'text-[var(--ui-color-intent-warning)]',
critical: 'text-[var(--ui-color-intent-critical)]',
};
const getResponsiveFontSize = (fs: HeadingProps['fontSize']) => {
if (!fs) return '';
if (typeof fs === 'string') return ''; // Handled in style
const classes = [];
if (fs.base) classes.push(`text-${fs.base}`);
if (fs.sm) classes.push(`sm:text-${fs.sm}`);
if (fs.md) classes.push(`md:text-${fs.md}`);
if (fs.lg) classes.push(`lg:text-${fs.lg}`);
if (fs.xl) classes.push(`xl:text-${fs.xl}`);
return classes.join(' ');
};
const classes = [
'text-[var(--ui-color-text-high)]',
intentClasses[intent as keyof typeof intentClasses] || intentClasses.default,
weightClasses[weight],
fontSize ? '' : sizeClasses[level],
fontSize ? getResponsiveFontSize(fontSize) : sizeClasses[level],
align === 'center' ? 'text-center' : (align === 'right' ? 'text-right' : 'text-left'),
uppercase ? 'uppercase' : '',
uppercase ? 'uppercase tracking-widest' : '',
truncate ? 'truncate' : '',
className,
].join(' ');
const combinedStyle: React.CSSProperties = {
...style,
...(mb !== undefined ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}),
...(marginBottom !== undefined ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}),
...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(marginTop !== undefined ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}),
...(color ? { color } : {}),
...(letterSpacing ? { letterSpacing } : {}),
...(typeof fontSize === 'string' ? { fontSize } : {}),
};
return (
<Box
as={Tag}
ref={ref}
className={classes}
fontSize={typeof fontSize === 'string' ? fontSize : undefined}
letterSpacing={letterSpacing}
mb={mb}
{...props}
>
<Box display="flex" alignItems="center" gap={2}>
<Tag ref={ref} className={classes} style={combinedStyle}>
<div className="flex items-center gap-2">
{icon}
{children}
</Box>
</Box>
</div>
</Tag>
);
});

View File

@@ -15,11 +15,11 @@ export const IconButton = forwardRef<HTMLButtonElement | HTMLAnchorElement, Icon
intent,
...props
}, ref) => {
const iconSizeMap = {
const iconSizeMap: Record<string, any> = {
sm: 3,
md: 4,
lg: 5
} as const;
};
return (
<Button

View File

@@ -0,0 +1,24 @@
import React, { ReactNode } from 'react';
interface IconContainerProps {
children: ReactNode;
variant?: 'default' | 'primary' | 'telemetry' | 'warning';
}
/**
* IconContainer - A semantic UI component for wrapping icons.
*/
export function IconContainer({ children, variant = 'default' }: IconContainerProps) {
const variantClasses = {
default: 'bg-[var(--ui-color-bg-base)] border-[var(--ui-color-border-default)]',
primary: 'bg-[var(--ui-color-intent-primary)]/10 border-[var(--ui-color-intent-primary)]/20',
telemetry: 'bg-[var(--ui-color-intent-telemetry)]/10 border-[var(--ui-color-intent-telemetry)]/20',
warning: 'bg-[var(--ui-color-intent-warning)]/10 border-[var(--ui-color-intent-warning)]/20',
};
return (
<div className={`w-10 h-10 flex items-center justify-center border ${variantClasses[variant]}`}>
{children}
</div>
);
}

View File

@@ -1,7 +1,5 @@
import { ReactNode } from 'react';
import { Box, Spacing } from './Box';
import { Heading } from './Heading';
import { Surface } from './Surface';
import { Text } from './Text';
export interface PanelProps {
@@ -9,63 +7,88 @@ export interface PanelProps {
description?: string;
children: ReactNode;
footer?: ReactNode;
variant?: 'default' | 'dark' | 'muted';
padding?: Spacing;
variant?: 'default' | 'muted' | 'ghost' | 'dark' | any;
padding?: 'none' | 'sm' | 'md' | 'lg' | number | any;
actions?: ReactNode;
className?: string;
style?: React.CSSProperties;
border?: boolean;
rounded?: string;
borderColor?: string;
bg?: string;
}
/**
* Panel - Redesigned for "Modern Precision" theme.
* Includes compatibility props to prevent app-wide breakage.
*/
export const Panel = ({
title,
description,
children,
footer,
variant = 'default',
padding = 6,
padding = 'md',
actions,
className,
style,
border,
rounded,
borderColor,
bg
bg,
}: 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)]',
};
const paddingClasses = {
none: 'p-0',
sm: 'p-2',
md: 'p-4',
lg: 'p-8',
};
const getPaddingClass = (pad: any) => {
if (typeof pad === 'string') return paddingClasses[pad as keyof typeof paddingClasses] || paddingClasses.md;
return ''; // Handled in style
};
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' } : {}),
};
return (
<Surface
variant={variant}
rounded={(rounded as any) || "lg"}
style={{
border: border === false ? 'none' : `1px solid ${borderColor || 'var(--ui-color-border-default)'}`,
backgroundColor: bg ? (bg.startsWith('bg-') ? undefined : bg) : undefined
}}
className={className}
>
<div className={`border ${variantClasses[variant as keyof typeof variantClasses] || variantClasses.default} ${getPaddingClass(padding)} transition-all duration-200 ${className || ''}`} style={combinedStyle}>
{(title || description || actions) && (
<Box padding={padding as any} borderBottom display="flex" alignItems="center" justifyContent="between">
<Box>
{title && <Heading level={3} marginBottom={1}>{title}</Heading>}
{description && <Text size="sm" variant="low">{description}</Text>}
</Box>
<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 && (
<Box display="flex" alignItems="center" gap={3}>
<div className="flex items-center gap-2">
{actions}
</Box>
</div>
)}
</Box>
</div>
)}
<Box padding={padding as any}>
<div className={typeof padding === 'number' ? '' : getPaddingClass(padding)}>
{children}
</Box>
</div>
{footer && (
<Box padding={padding as any} borderTop bg="rgba(255,255,255,0.02)">
<div className={`border-t border-[var(--ui-color-border-muted)] bg-white/[0.02] ${getPaddingClass(padding)}`}>
{footer}
</Box>
</div>
)}
</Surface>
</div>
);
};

View File

@@ -1,30 +1,67 @@
import React, { ElementType, ReactNode, forwardRef } from 'react';
import { Box, BoxProps, ResponsiveValue } from './Box';
export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'base';
export interface TextProps extends BoxProps<any> {
export interface TextProps {
children: ReactNode;
variant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'inherit';
size?: TextSize | ResponsiveValue<TextSize>;
weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
variant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'inherit' | any;
size?: TextSize | { base: TextSize; sm?: TextSize; md?: TextSize; lg?: TextSize; xl?: TextSize } | any;
weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | any;
as?: ElementType;
align?: 'left' | 'center' | 'right';
italic?: boolean;
align?: 'left' | 'center' | 'right' | any;
mono?: boolean;
block?: boolean;
uppercase?: boolean;
capitalize?: boolean;
letterSpacing?: string;
leading?: 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose';
leading?: 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose' | any;
block?: boolean;
truncate?: boolean;
className?: string;
style?: React.CSSProperties;
mt?: number | any;
mb?: number | any;
ml?: number | any;
mr?: number | any;
marginTop?: number | any;
marginBottom?: number | any;
font?: 'sans' | 'mono' | string;
color?: string;
letterSpacing?: string;
lineHeight?: string | number;
font?: 'sans' | 'mono';
hoverVariant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical';
htmlFor?: string;
flexGrow?: number;
flexShrink?: number;
lineClamp?: number;
display?: string;
opacity?: number;
maxWidth?: string | number;
mx?: string | number;
pl?: number;
px?: number;
py?: number;
paddingX?: number;
paddingY?: number;
textAlign?: string;
groupHoverTextColor?: string;
transition?: boolean;
borderLeft?: boolean;
borderStyle?: string;
borderColor?: string;
ariaLabel?: string;
hoverVariant?: string;
fontSize?: string | any;
italic?: boolean;
animate?: string;
capitalize?: boolean;
alignItems?: string;
gap?: number;
cursor?: string;
width?: string | number;
htmlFor?: string;
transform?: string;
}
/**
* Text - Redesigned for "Modern Precision" theme.
* Includes extensive compatibility props to prevent app-wide breakage.
*/
export const Text = forwardRef<HTMLElement, TextProps>(({
children,
variant = 'med',
@@ -32,20 +69,53 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
weight = 'normal',
as = 'p',
align = 'left',
italic = false,
mono = false,
block = false,
uppercase = false,
capitalize = false,
letterSpacing,
leading,
leading = 'normal',
block = false,
truncate = false,
lineHeight,
className,
style,
mt,
mb,
ml,
mr,
marginTop,
marginBottom,
font,
color,
letterSpacing,
lineHeight,
flexGrow,
flexShrink,
lineClamp,
display,
opacity,
maxWidth,
mx,
pl,
px,
py,
paddingX,
paddingY,
textAlign,
groupHoverTextColor,
transition,
borderLeft,
borderStyle,
borderColor,
ariaLabel,
hoverVariant,
htmlFor,
fontSize,
italic,
animate,
capitalize,
alignItems,
gap,
cursor,
...props
width,
htmlFor,
transform,
}, ref) => {
const variantClasses = {
high: 'text-[var(--ui-color-text-high)]',
@@ -58,40 +128,31 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
telemetry: 'text-[var(--ui-color-intent-telemetry)]',
inherit: 'text-inherit',
};
const hoverVariantClasses = {
high: 'hover:text-[var(--ui-color-text-high)]',
med: 'hover:text-[var(--ui-color-text-med)]',
low: 'hover:text-[var(--ui-color-text-low)]',
primary: 'hover:text-[var(--ui-color-intent-primary)]',
success: 'hover:text-[var(--ui-color-intent-success)]',
warning: 'hover:text-[var(--ui-color-intent-warning)]',
critical: 'hover:text-[var(--ui-color-intent-critical)]',
};
const sizeMap: Record<TextSize, string> = {
xs: 'text-xs',
sm: 'text-sm',
base: 'text-base',
md: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
'2xl': 'text-2xl',
'3xl': 'text-3xl',
'4xl': 'text-4xl',
const sizeMap: Record<string, string> = {
xs: 'text-[10px]',
sm: 'text-xs',
base: 'text-sm',
md: 'text-sm',
lg: 'text-base',
xl: 'text-lg',
'2xl': 'text-xl',
'3xl': 'text-2xl',
'4xl': 'text-3xl',
};
const getResponsiveSizeClasses = (value: TextSize | ResponsiveValue<TextSize>) => {
if (typeof value === 'string') return sizeMap[value];
const getResponsiveSize = (s: any) => {
if (!s) return sizeMap['md'];
if (typeof s === 'string') return sizeMap[s] || sizeMap['md'];
const classes = [];
if (value.base) classes.push(sizeMap[value.base]);
if (value.sm) classes.push(`sm:${sizeMap[value.sm]}`);
if (value.md) classes.push(`md:${sizeMap[value.md]}`);
if (value.lg) classes.push(`lg:${sizeMap[value.lg]}`);
if (value.xl) classes.push(`xl:${sizeMap[value.xl]}`);
if (s.base) classes.push(sizeMap[s.base]);
if (s.sm) classes.push(`sm:${sizeMap[s.sm]}`);
if (s.md) classes.push(`md:${sizeMap[s.md]}`);
if (s.lg) classes.push(`lg:${sizeMap[s.lg]}`);
if (s.xl) classes.push(`xl:${sizeMap[s.xl]}`);
return classes.join(' ');
};
const weightClasses = {
light: 'font-light',
normal: 'font-normal',
@@ -110,30 +171,60 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
};
const classes = [
variantClasses[variant],
getResponsiveSizeClasses(size),
weightClasses[weight],
align === 'center' ? 'text-center' : (align === 'right' ? 'text-right' : 'text-left'),
italic ? 'italic' : '',
variantClasses[variant as keyof typeof variantClasses] || '',
getResponsiveSize(size),
weightClasses[weight as keyof typeof weightClasses] || '',
align === 'center' || textAlign === 'center' ? 'text-center' : (align === 'right' || textAlign === 'right' ? 'text-right' : 'text-left'),
(mono || font === 'mono') ? 'font-mono' : 'font-sans',
uppercase ? 'uppercase tracking-widest' : '',
leadingClasses[leading as keyof typeof leadingClasses] || '',
block ? 'block' : 'inline',
uppercase ? 'uppercase tracking-wider' : '',
capitalize ? 'capitalize' : '',
leading ? leadingClasses[leading] : '',
truncate ? 'truncate' : '',
hoverVariant ? hoverVariantClasses[hoverVariant] : '',
].join(' ');
transition ? 'transition-all duration-200' : '',
italic ? 'italic' : '',
animate === 'pulse' ? 'animate-pulse' : '',
capitalize ? 'capitalize' : '',
className,
].filter(Boolean).join(' ');
const style: React.CSSProperties = {
const combinedStyle: React.CSSProperties = {
...style,
...(display ? { display } : {}),
...(alignItems ? { alignItems } : {}),
...(gap !== undefined ? { gap: `${gap * 0.25}rem` } : {}),
...(cursor ? { cursor } : {}),
...(width !== undefined ? { width } : {}),
...(opacity !== undefined ? { opacity } : {}),
...(maxWidth !== undefined ? { maxWidth } : {}),
...(mx === 'auto' ? { marginLeft: 'auto', marginRight: 'auto' } : {}),
...(pl !== undefined ? { paddingLeft: `${pl * 0.25}rem` } : {}),
...(px !== undefined ? { paddingLeft: `${px * 0.25}rem`, paddingRight: `${px * 0.25}rem` } : {}),
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
...(paddingX !== undefined ? { paddingLeft: `${paddingX * 0.25}rem`, paddingRight: `${paddingX * 0.25}rem` } : {}),
...(paddingY !== undefined ? { paddingTop: `${paddingY * 0.25}rem`, paddingBottom: `${paddingY * 0.25}rem` } : {}),
...(mt !== undefined ? { marginTop: typeof mt === 'number' ? `${mt * 0.25}rem` : mt } : {}),
...(mb !== undefined ? { marginBottom: typeof mb === 'number' ? `${mb * 0.25}rem` : mb } : {}),
...(ml !== undefined ? { marginLeft: typeof ml === 'number' ? `${ml * 0.25}rem` : ml } : {}),
...(mr !== undefined ? { marginRight: typeof mr === 'number' ? `${mr * 0.25}rem` : mr } : {}),
...(marginTop !== undefined ? { marginTop: typeof marginTop === 'number' ? `${marginTop * 0.25}rem` : marginTop } : {}),
...(marginBottom !== undefined ? { marginBottom: typeof marginBottom === 'number' ? `${marginBottom * 0.25}rem` : marginBottom } : {}),
...(color ? { color } : {}),
...(letterSpacing ? { letterSpacing } : {}),
...(lineHeight ? { lineHeight } : {}),
...(cursor ? { cursor } : {}),
...(flexGrow !== undefined ? { flexGrow } : {}),
...(flexShrink !== undefined ? { flexShrink } : {}),
...(lineClamp !== undefined ? { display: '-webkit-box', WebkitLineClamp: lineClamp, WebkitBoxOrient: 'vertical', overflow: 'hidden' } : {}),
...(borderLeft ? { borderLeft: `1px solid ${borderColor || 'var(--ui-color-border-default)'}` } : {}),
...(fontSize ? { fontSize } : {}),
...(transform ? { textTransform: transform as any } : {}),
};
const Tag = as;
return (
<Box as={as} ref={ref} className={classes} style={style} htmlFor={htmlFor} {...props}>
<Tag ref={ref} className={classes} style={combinedStyle} aria-label={ariaLabel} htmlFor={htmlFor}>
{children}
</Box>
</Tag>
);
});