website refactor
This commit is contained in:
@@ -9,19 +9,35 @@ export interface AccordionProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
isOpen?: boolean;
|
||||
onToggle?: () => void;
|
||||
}
|
||||
|
||||
export const Accordion = ({
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false
|
||||
defaultOpen = false,
|
||||
isOpen: controlledIsOpen,
|
||||
onToggle
|
||||
}: AccordionProps) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const [internalIsOpen, setInternalIsOpen] = useState(defaultOpen);
|
||||
|
||||
const isControlled = controlledIsOpen !== undefined;
|
||||
const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
|
||||
|
||||
const handleToggle = () => {
|
||||
if (onToggle) {
|
||||
onToggle();
|
||||
}
|
||||
if (!isControlled) {
|
||||
setInternalIsOpen(!internalIsOpen);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Surface variant="muted" rounded="lg" style={{ border: '1px solid var(--ui-color-border-default)', overflow: 'hidden' }}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
onClick={handleToggle}
|
||||
className="w-full flex items-center justify-between px-4 py-3 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<Text weight="bold" size="sm" variant="high">
|
||||
|
||||
@@ -6,37 +6,43 @@ import { Surface } from './Surface';
|
||||
export interface AvatarProps {
|
||||
src?: string;
|
||||
alt?: string;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl' | number;
|
||||
fallback?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Avatar = ({
|
||||
src,
|
||||
alt,
|
||||
size = 'md',
|
||||
fallback
|
||||
fallback,
|
||||
className
|
||||
}: AvatarProps) => {
|
||||
const sizeMap = {
|
||||
const sizeMap: Record<string, string> = {
|
||||
sm: '2rem',
|
||||
md: '3rem',
|
||||
lg: '4rem',
|
||||
xl: '6rem'
|
||||
};
|
||||
|
||||
const iconSizeMap = {
|
||||
const iconSizeMap: Record<string, number> = {
|
||||
sm: 3,
|
||||
md: 5,
|
||||
lg: 8,
|
||||
xl: 12
|
||||
} as const;
|
||||
};
|
||||
|
||||
const finalSize = typeof size === 'number' ? `${size / 4}rem` : sizeMap[size];
|
||||
const finalIconSize = typeof size === 'number' ? Math.round(size / 8) : iconSizeMap[size];
|
||||
|
||||
return (
|
||||
<Surface
|
||||
variant="muted"
|
||||
rounded="full"
|
||||
className={className}
|
||||
style={{
|
||||
width: sizeMap[size],
|
||||
height: sizeMap[size],
|
||||
width: finalSize,
|
||||
height: finalSize,
|
||||
overflow: 'hidden',
|
||||
border: '2px solid var(--ui-color-border-default)'
|
||||
}}
|
||||
@@ -52,7 +58,7 @@ export const Avatar = ({
|
||||
{fallback ? (
|
||||
<span className="text-sm font-bold text-[var(--ui-color-text-med)]">{fallback}</span>
|
||||
) : (
|
||||
<Icon icon={User} size={iconSizeMap[size]} intent="low" />
|
||||
<Icon icon={User} size={finalIconSize as any} intent="low" />
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -7,17 +7,25 @@ import { Stack } from './Stack';
|
||||
export interface BadgeProps {
|
||||
children: ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'critical' | 'info' | 'outline' | 'default' | 'danger';
|
||||
size?: 'sm' | 'md';
|
||||
size?: 'xs' | 'sm' | 'md';
|
||||
style?: React.CSSProperties;
|
||||
icon?: LucideIcon;
|
||||
rounded?: string;
|
||||
bg?: string;
|
||||
color?: string;
|
||||
borderColor?: string;
|
||||
}
|
||||
|
||||
export const Badge = ({
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
style,
|
||||
icon
|
||||
style: styleProp,
|
||||
icon,
|
||||
rounded,
|
||||
bg,
|
||||
color,
|
||||
borderColor
|
||||
}: BadgeProps) => {
|
||||
const variantClasses = {
|
||||
primary: 'bg-[var(--ui-color-intent-primary)] text-white',
|
||||
@@ -32,16 +40,25 @@ export const Badge = ({
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
xs: 'px-1 py-0 text-[9px]',
|
||||
sm: 'px-1.5 py-0.5 text-[10px]',
|
||||
md: 'px-2 py-0.5 text-xs',
|
||||
};
|
||||
|
||||
const classes = [
|
||||
'inline-flex items-center justify-center font-bold uppercase tracking-wider rounded-none',
|
||||
'inline-flex items-center justify-center font-bold uppercase tracking-wider',
|
||||
variantClasses[variant],
|
||||
sizeClasses[size],
|
||||
rounded ? (rounded === 'full' ? 'rounded-full' : `rounded-${rounded}`) : 'rounded-none',
|
||||
].join(' ');
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
...styleProp,
|
||||
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
|
||||
...(color ? { color: color.startsWith('text-') ? undefined : color } : {}),
|
||||
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor, borderStyle: 'solid', borderWidth: '1px' } : {}),
|
||||
};
|
||||
|
||||
const content = icon ? (
|
||||
<Stack direction="row" align="center" gap={1}>
|
||||
<Icon icon={icon} size={3} />
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
import { Box } from './Box';
|
||||
|
||||
|
||||
export interface TabOption {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface BorderTabsProps {
|
||||
tabs: TabOption[];
|
||||
activeTabId: string;
|
||||
activeTabId?: string;
|
||||
onTabChange: (id: string) => void;
|
||||
activeTab?: string;
|
||||
}
|
||||
|
||||
export const BorderTabs = ({
|
||||
tabs,
|
||||
activeTabId,
|
||||
onTabChange
|
||||
onTabChange,
|
||||
activeTab
|
||||
}: BorderTabsProps) => {
|
||||
const finalActiveTabId = activeTabId || activeTab || '';
|
||||
|
||||
return (
|
||||
<Box display="flex" borderBottom>
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.id === activeTabId;
|
||||
const isActive = tab.id === finalActiveTabId;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -29,6 +40,11 @@ export const BorderTabs = ({
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
{tab.count !== undefined && (
|
||||
<Box as="span" px={1.5} py={0.5} rounded="full" bg="white/10" fontSize="10px">
|
||||
{tab.count}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{isActive && (
|
||||
<Box
|
||||
|
||||
@@ -52,12 +52,12 @@ export interface BoxProps<T extends ElementType> {
|
||||
|
||||
// Aliases (Deprecated - use full names)
|
||||
m?: Spacing | ResponsiveSpacing;
|
||||
mt?: Spacing | ResponsiveSpacing;
|
||||
mb?: Spacing | ResponsiveSpacing;
|
||||
mt?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
mb?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
ml?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
mr?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
mx?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
my?: Spacing | ResponsiveSpacing;
|
||||
my?: Spacing | 'auto' | ResponsiveSpacing;
|
||||
p?: Spacing | ResponsiveSpacing;
|
||||
pt?: Spacing | ResponsiveSpacing;
|
||||
pb?: Spacing | ResponsiveSpacing;
|
||||
@@ -216,6 +216,19 @@ export interface BoxProps<T extends ElementType> {
|
||||
backgroundSize?: string;
|
||||
backgroundPosition?: string;
|
||||
backgroundImage?: string;
|
||||
htmlFor?: string;
|
||||
minH?: string | number | ResponsiveValue<string | number>;
|
||||
borderOpacity?: number;
|
||||
rows?: number;
|
||||
backgroundColor?: string;
|
||||
outline?: string;
|
||||
focusBorderColor?: string;
|
||||
href?: string;
|
||||
name?: string;
|
||||
borderTopColor?: string;
|
||||
onPointerDown?: React.PointerEventHandler<any>;
|
||||
onPointerMove?: React.PointerEventHandler<any>;
|
||||
onPointerUp?: React.PointerEventHandler<any>;
|
||||
}
|
||||
|
||||
export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
@@ -354,6 +367,19 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
backgroundSize,
|
||||
backgroundPosition,
|
||||
backgroundImage,
|
||||
htmlFor,
|
||||
minH,
|
||||
borderOpacity,
|
||||
rows,
|
||||
backgroundColor,
|
||||
outline,
|
||||
focusBorderColor,
|
||||
href,
|
||||
name,
|
||||
borderTopColor,
|
||||
onPointerDown,
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
...props
|
||||
}: BoxProps<T>,
|
||||
ref: ForwardedRef<HTMLElement>
|
||||
@@ -417,7 +443,7 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
getResponsiveClasses('max-w', maxWidth),
|
||||
getResponsiveClasses('min-w', minWidth),
|
||||
getResponsiveClasses('max-h', maxHeight),
|
||||
getResponsiveClasses('min-h', minHeight),
|
||||
getResponsiveClasses('min-h', minHeight || minH),
|
||||
getResponsiveClasses('', display),
|
||||
center ? 'flex items-center justify-center' : '',
|
||||
overflow ? (overflow.includes(':') ? overflow : `overflow-${overflow}`) : '',
|
||||
@@ -464,6 +490,7 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
...(typeof minWidth === 'string' ? { minWidth } : {}),
|
||||
...(typeof maxHeight === 'string' ? { maxHeight } : {}),
|
||||
...(typeof minHeight === 'string' ? { minHeight } : {}),
|
||||
...(typeof minH === 'string' || typeof minH === 'number' ? { minHeight: minH } : {}),
|
||||
...(aspectRatio ? { aspectRatio } : {}),
|
||||
...(typeof top === 'string' || typeof top === 'number' ? { top } : {}),
|
||||
...(typeof right === 'string' || typeof right === 'number' ? { right } : {}),
|
||||
@@ -473,7 +500,9 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
...(typeof borderBottom === 'string' ? { borderBottom } : (borderBottom === true ? { borderBottom: '1px solid var(--ui-color-border-default)' } : {})),
|
||||
...(typeof borderLeft === 'string' ? { borderLeft } : (borderLeft === true ? { borderLeft: '1px solid var(--ui-color-border-default)' } : {})),
|
||||
...(typeof borderRight === 'string' ? { borderRight } : (borderRight === true ? { borderRight: '1px solid var(--ui-color-border-default)' } : {})),
|
||||
...(bg ? { background: bg.startsWith('bg-') ? undefined : bg } : {}),
|
||||
...(borderTopColor ? { borderTopColor: borderTopColor.startsWith('border-') ? undefined : borderTopColor } : {}),
|
||||
...(borderOpacity !== undefined ? { borderOpacity } : {}),
|
||||
...(bg || backgroundColor ? { background: (bg || backgroundColor)!.startsWith('bg-') ? undefined : (bg || backgroundColor) } : {}),
|
||||
...(rounded === true ? { borderRadius: 'var(--ui-radius-md)' } : (typeof rounded === 'string' ? { borderRadius: rounded.includes('rem') || rounded.includes('px') ? rounded : `var(--ui-radius-${rounded})` } : {})),
|
||||
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}),
|
||||
...(border === true ? { border: '1px solid var(--ui-color-border-default)' } : (typeof border === 'string' ? { border } : {})),
|
||||
@@ -531,6 +560,13 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
|
||||
muted={muted}
|
||||
playsInline={playsInline}
|
||||
viewBox={viewBox}
|
||||
htmlFor={htmlFor}
|
||||
rows={rows}
|
||||
href={href}
|
||||
name={name}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -19,7 +19,32 @@ export interface ButtonProps {
|
||||
rel?: string;
|
||||
title?: string;
|
||||
style?: React.CSSProperties;
|
||||
rounded?: boolean;
|
||||
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;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(({
|
||||
@@ -39,8 +64,34 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
||||
title,
|
||||
style: styleProp,
|
||||
rounded = false,
|
||||
className,
|
||||
bg,
|
||||
color,
|
||||
w,
|
||||
h,
|
||||
px,
|
||||
borderColor,
|
||||
mt,
|
||||
position,
|
||||
letterSpacing,
|
||||
hoverBorderColor,
|
||||
fontSize,
|
||||
p,
|
||||
minHeight,
|
||||
transition,
|
||||
ring,
|
||||
transform,
|
||||
hoverScale,
|
||||
overflow,
|
||||
borderWidth,
|
||||
aspectRatio,
|
||||
border,
|
||||
shadow,
|
||||
display,
|
||||
center,
|
||||
}, ref) => {
|
||||
const baseClasses = 'inline-flex items-center justify-center transition-all duration-150 ease-in-out focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 active:opacity-80 uppercase tracking-widest font-bold';
|
||||
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 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)]',
|
||||
@@ -60,17 +111,45 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
||||
|
||||
const disabledClasses = (disabled || isLoading) ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer';
|
||||
const widthClasses = fullWidth ? 'w-full' : '';
|
||||
const roundedClasses = rounded ? 'rounded-full' : 'rounded-none';
|
||||
const roundedClasses = rounded === true ? 'rounded-full' : (typeof rounded === 'string' ? `rounded-${rounded}` : 'rounded-none');
|
||||
|
||||
const classes = [
|
||||
baseClasses,
|
||||
transitionClasses,
|
||||
variantClasses[variant],
|
||||
sizeClasses[size],
|
||||
disabledClasses,
|
||||
widthClasses,
|
||||
roundedClasses,
|
||||
ring,
|
||||
hoverScale ? 'hover:scale-105' : '',
|
||||
display === 'flex' ? 'flex' : '',
|
||||
center ? 'items-center justify-center' : '',
|
||||
className,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
...styleProp,
|
||||
...(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 } : {}),
|
||||
...(borderWidth ? { borderWidth } : {}),
|
||||
...(aspectRatio ? { aspectRatio } : {}),
|
||||
...(border ? { border: '1px solid var(--ui-color-border-default)' } : {}),
|
||||
...(shadow ? { boxShadow: shadow.startsWith('shadow-') ? undefined : shadow } : {}),
|
||||
};
|
||||
|
||||
const content = (
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{isLoading && <Icon icon={Loader2} size={size === 'sm' ? 3 : 4} animate="spin" />}
|
||||
@@ -88,7 +167,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
||||
rel={rel}
|
||||
className={classes}
|
||||
onClick={onClick as MouseEventHandler<HTMLAnchorElement>}
|
||||
style={styleProp}
|
||||
style={style}
|
||||
title={title}
|
||||
>
|
||||
{content}
|
||||
@@ -103,7 +182,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
|
||||
className={classes}
|
||||
onClick={onClick as MouseEventHandler<HTMLButtonElement>}
|
||||
disabled={disabled || isLoading}
|
||||
style={styleProp}
|
||||
style={style}
|
||||
title={title}
|
||||
>
|
||||
{content}
|
||||
|
||||
@@ -8,7 +8,9 @@ export interface CategoryIconProps {
|
||||
}
|
||||
|
||||
export const CategoryIcon = ({
|
||||
category}: CategoryIconProps) => {
|
||||
category,
|
||||
size = 24
|
||||
}: CategoryIconProps) => {
|
||||
// Map categories to icons if needed, for now just use Tag
|
||||
return (
|
||||
<Box
|
||||
|
||||
@@ -4,11 +4,17 @@ 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;
|
||||
}
|
||||
|
||||
export const Container = ({
|
||||
children,
|
||||
size = 'lg'
|
||||
size = 'lg',
|
||||
py,
|
||||
position,
|
||||
zIndex
|
||||
}: ContainerProps) => {
|
||||
const sizeMap = {
|
||||
sm: '40rem',
|
||||
@@ -19,7 +25,7 @@ export const Container = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box marginX="auto" maxWidth={sizeMap[size]} paddingX={4} fullWidth>
|
||||
<Box marginX="auto" maxWidth={sizeMap[size]} paddingX={4} py={py as any} fullWidth position={position} zIndex={zIndex}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,13 @@ import { Container } from './Container';
|
||||
export interface ContentViewportProps {
|
||||
children: ReactNode;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export const ContentViewport = ({
|
||||
children,
|
||||
padding = 'md'
|
||||
padding = 'md',
|
||||
fullWidth = false
|
||||
}: ContentViewportProps) => {
|
||||
const paddingMap: Record<string, any> = {
|
||||
none: 0,
|
||||
@@ -20,7 +22,7 @@ export const ContentViewport = ({
|
||||
|
||||
return (
|
||||
<Box as="main" flex={1} overflow="auto">
|
||||
<Container size="xl">
|
||||
<Container size={fullWidth ? 'full' : 'xl'}>
|
||||
<Box paddingY={paddingMap[padding]}>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function DashboardHero({
|
||||
borderColor="border-[#23272B]"
|
||||
>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
src={avatarUrl || undefined}
|
||||
alt={driverName}
|
||||
size={120}
|
||||
className="rounded-xl"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Copy, RefreshCw, Terminal, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface DevErrorPanelProps {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
@@ -148,3 +148,7 @@ export function EmptyState({
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function MinimalEmptyState(props: Omit<EmptyStateProps, 'variant'>) {
|
||||
return <EmptyState {...props} variant="minimal" />;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export interface FilterOption {
|
||||
export interface FilterGroupProps {
|
||||
options: FilterOption[];
|
||||
activeId: string;
|
||||
onChange: (id: string) => void;
|
||||
onChange?: (id: string) => void;
|
||||
onSelect?: (id: string) => void;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
@@ -20,8 +21,11 @@ export const FilterGroup = ({
|
||||
options,
|
||||
activeId,
|
||||
onChange,
|
||||
onSelect,
|
||||
label
|
||||
}: FilterGroupProps) => {
|
||||
const finalOnChange = onChange || onSelect || (() => {});
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="col" gap={2}>
|
||||
{label && (
|
||||
@@ -37,7 +41,7 @@ export const FilterGroup = ({
|
||||
key={option.id}
|
||||
variant={isActive ? 'primary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => onChange(option.id)}
|
||||
onClick={() => finalOnChange(option.id)}
|
||||
fullWidth
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box } from './Box';
|
||||
import { Button, ButtonProps } from './Button';
|
||||
|
||||
export interface FloatingActionProps extends ButtonProps {
|
||||
export interface FloatingActionProps extends Omit<ButtonProps, 'position'> {
|
||||
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ import { Text } from './Text';
|
||||
|
||||
export interface GoalCardProps {
|
||||
title: string;
|
||||
current: number;
|
||||
target: number;
|
||||
unit: string;
|
||||
current?: number;
|
||||
target?: number;
|
||||
unit?: string;
|
||||
icon: string;
|
||||
goalLabel?: string;
|
||||
currentValue?: number;
|
||||
maxValue?: number;
|
||||
}
|
||||
|
||||
export const GoalCard = ({
|
||||
@@ -15,9 +18,15 @@ export const GoalCard = ({
|
||||
current,
|
||||
target,
|
||||
unit,
|
||||
icon
|
||||
icon,
|
||||
goalLabel,
|
||||
currentValue,
|
||||
maxValue
|
||||
}: GoalCardProps) => {
|
||||
const percentage = Math.min(Math.max((current / target) * 100, 0), 100);
|
||||
const finalCurrent = currentValue ?? current ?? 0;
|
||||
const finalTarget = maxValue ?? target ?? 100;
|
||||
const finalUnit = goalLabel ?? unit ?? '';
|
||||
const percentage = Math.min(Math.max((finalCurrent / finalTarget) * 100, 0), 100);
|
||||
|
||||
return (
|
||||
<Card variant="default">
|
||||
@@ -25,7 +34,7 @@ export const GoalCard = ({
|
||||
<Text size="2xl">{icon}</Text>
|
||||
<Box>
|
||||
<Text weight="bold" variant="high">{title}</Text>
|
||||
<Text size="sm" variant="low">{unit}</Text>
|
||||
<Text size="sm" variant="low">{finalUnit}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -40,7 +49,7 @@ export const GoalCard = ({
|
||||
</Box>
|
||||
|
||||
<Text size="xs" variant="low">
|
||||
{current} / {target} {unit}
|
||||
{finalCurrent} / {finalTarget} {finalUnit}
|
||||
</Text>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface GridProps<T extends ElementType> extends BoxProps<T> {
|
||||
children?: ReactNode;
|
||||
cols?: number | ResponsiveValue<number>;
|
||||
gap?: number | string | ResponsiveValue<number | string>;
|
||||
mdCols?: number;
|
||||
lgCols?: number;
|
||||
}
|
||||
|
||||
export const Grid = forwardRef(<T extends ElementType = 'div'>(
|
||||
@@ -25,16 +27,22 @@ export const Grid = forwardRef(<T extends ElementType = 'div'>(
|
||||
cols = 1,
|
||||
gap = 4,
|
||||
as,
|
||||
mdCols,
|
||||
lgCols,
|
||||
...props
|
||||
}: GridProps<T>,
|
||||
ref: ForwardedRef<HTMLElement>
|
||||
) => {
|
||||
const finalCols = typeof cols === 'object' ? { ...cols } : { base: cols };
|
||||
if (mdCols) finalCols.md = mdCols;
|
||||
if (lgCols) finalCols.lg = lgCols;
|
||||
|
||||
return (
|
||||
<Box
|
||||
as={as}
|
||||
ref={ref}
|
||||
display="grid"
|
||||
gridCols={cols}
|
||||
gridCols={finalCols}
|
||||
gap={gap}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,8 @@ export interface GridItemProps<T extends ElementType> extends BoxProps<T> {
|
||||
children?: ReactNode;
|
||||
colSpan?: number | ResponsiveValue<number>;
|
||||
rowSpan?: number | ResponsiveValue<number>;
|
||||
mdSpan?: number;
|
||||
lgSpan?: number;
|
||||
}
|
||||
|
||||
export const GridItem = forwardRef(<T extends ElementType = 'div'>(
|
||||
@@ -14,15 +16,21 @@ export const GridItem = forwardRef(<T extends ElementType = 'div'>(
|
||||
colSpan,
|
||||
rowSpan,
|
||||
as,
|
||||
mdSpan,
|
||||
lgSpan,
|
||||
...props
|
||||
}: GridItemProps<T>,
|
||||
ref: ForwardedRef<HTMLElement>
|
||||
) => {
|
||||
const finalColSpan = typeof colSpan === 'object' ? { ...colSpan } : { base: colSpan };
|
||||
if (mdSpan) finalColSpan.md = mdSpan;
|
||||
if (lgSpan) finalColSpan.lg = lgSpan;
|
||||
|
||||
return (
|
||||
<Box
|
||||
as={as}
|
||||
ref={ref}
|
||||
colSpan={colSpan}
|
||||
colSpan={finalColSpan as any}
|
||||
// rowSpan is not directly supported by Box yet, but we can add it if needed
|
||||
// or use style
|
||||
{...props}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box } from './Box';
|
||||
|
||||
export interface GroupProps {
|
||||
children: ReactNode;
|
||||
direction?: 'row' | 'col';
|
||||
direction?: 'row' | 'col' | 'column';
|
||||
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
|
||||
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
||||
gap?: number;
|
||||
@@ -20,10 +20,12 @@ export const Group = ({
|
||||
wrap = false,
|
||||
fullWidth = false,
|
||||
}: GroupProps) => {
|
||||
const finalDirection = direction === 'column' ? 'col' : direction;
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={direction}
|
||||
flexDirection={finalDirection}
|
||||
alignItems={align}
|
||||
justifyContent={justify}
|
||||
gap={gap}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode, forwardRef } from 'react';
|
||||
import { Box, BoxProps, ResponsiveValue } from './Box';
|
||||
import { Box, BoxProps, ResponsiveValue, Spacing } from './Box';
|
||||
|
||||
export interface HeadingProps extends BoxProps<any> {
|
||||
children: ReactNode;
|
||||
@@ -7,6 +7,11 @@ export interface HeadingProps extends BoxProps<any> {
|
||||
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
||||
align?: 'left' | 'center' | 'right';
|
||||
fontSize?: string | ResponsiveValue<string>;
|
||||
icon?: ReactNode;
|
||||
groupHoverColor?: string;
|
||||
uppercase?: boolean;
|
||||
letterSpacing?: string;
|
||||
mb?: Spacing;
|
||||
}
|
||||
|
||||
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
|
||||
@@ -15,6 +20,11 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
|
||||
weight = 'bold',
|
||||
align = 'left',
|
||||
fontSize,
|
||||
icon,
|
||||
groupHoverColor,
|
||||
uppercase,
|
||||
letterSpacing,
|
||||
mb,
|
||||
...props
|
||||
}, ref) => {
|
||||
const Tag = `h${level}` as const;
|
||||
@@ -40,11 +50,23 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(({
|
||||
weightClasses[weight],
|
||||
fontSize ? '' : sizeClasses[level],
|
||||
align === 'center' ? 'text-center' : (align === 'right' ? 'text-right' : 'text-left'),
|
||||
uppercase ? 'uppercase' : '',
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<Box as={Tag} ref={ref} className={classes} fontSize={typeof fontSize === 'string' ? fontSize : undefined} {...props}>
|
||||
{children}
|
||||
<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}>
|
||||
{icon}
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,21 +7,31 @@ import { Text } from './Text';
|
||||
export interface HorizontalStatCardProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
icon: LucideIcon;
|
||||
icon: LucideIcon | React.ReactNode;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry';
|
||||
subValue?: string;
|
||||
iconBgColor?: string;
|
||||
}
|
||||
|
||||
export const HorizontalStatCard = ({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
intent = 'primary'
|
||||
intent = 'primary',
|
||||
subValue,
|
||||
iconBgColor
|
||||
}: HorizontalStatCardProps) => {
|
||||
const isLucideIcon = typeof icon === 'function' || (typeof icon === 'object' && icon !== null && 'render' in icon);
|
||||
|
||||
return (
|
||||
<Card variant="default">
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box padding={3} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
<Box padding={3} rounded="lg" bg={iconBgColor || "var(--ui-color-bg-surface-muted)"}>
|
||||
{isLucideIcon ? (
|
||||
<Icon icon={icon as LucideIcon} size={5} intent={intent} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>
|
||||
@@ -30,6 +40,11 @@ export const HorizontalStatCard = ({
|
||||
<Text size="xl" weight="bold" variant="high" block marginTop={0.5}>
|
||||
{value}
|
||||
</Text>
|
||||
{subValue && (
|
||||
<Text size="xs" variant="low" block marginTop={0.5}>
|
||||
{subValue}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
@@ -5,17 +5,19 @@ export interface HorizontalStatItemProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'high' | 'med' | 'low';
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export const HorizontalStatItem = ({
|
||||
label,
|
||||
value,
|
||||
intent = 'high'
|
||||
intent = 'high',
|
||||
color
|
||||
}: HorizontalStatItemProps) => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="between" paddingY={2}>
|
||||
<Text size="sm" variant="low">{label}</Text>
|
||||
<Text weight="semibold" variant={intent}>{value}</Text>
|
||||
<Text weight="semibold" variant={color ? undefined : intent} style={color ? { color } : undefined}>{value}</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ImagePlaceholderProps {
|
||||
aspectRatio?: string;
|
||||
variant?: 'default' | 'loading' | 'error';
|
||||
message?: string;
|
||||
rounded?: string;
|
||||
}
|
||||
|
||||
export const ImagePlaceholder = ({
|
||||
@@ -18,7 +19,8 @@ export const ImagePlaceholder = ({
|
||||
animate = 'pulse',
|
||||
aspectRatio,
|
||||
variant = 'default',
|
||||
message
|
||||
message,
|
||||
rounded
|
||||
}: ImagePlaceholderProps) => {
|
||||
const icons = {
|
||||
default: ImageIcon,
|
||||
@@ -36,7 +38,7 @@ export const ImagePlaceholder = ({
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg="var(--ui-color-bg-surface-muted)"
|
||||
style={{ borderRadius: 'var(--ui-radius-md)' }}
|
||||
style={{ borderRadius: rounded === 'none' ? '0' : (rounded ? `var(--ui-radius-${rounded})` : 'var(--ui-radius-md)') }}
|
||||
gap={2}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HelpCircle, X } from 'lucide-react';
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Box } from './Box';
|
||||
import { Heading } from './Heading';
|
||||
import { Icon } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
import { Surface } from './Surface';
|
||||
|
||||
@@ -5,21 +5,26 @@ import { Text } from './Text';
|
||||
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
errorMessage?: string;
|
||||
hint?: string;
|
||||
fullWidth?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
icon?: React.ReactNode;
|
||||
variant?: 'default' | 'error';
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(({
|
||||
label,
|
||||
error,
|
||||
errorMessage,
|
||||
hint,
|
||||
fullWidth = false,
|
||||
size = 'md',
|
||||
icon,
|
||||
variant = 'default',
|
||||
...props
|
||||
}, ref) => {
|
||||
const finalError = error || errorMessage;
|
||||
const sizeClasses = {
|
||||
sm: 'px-3 py-1.5 text-xs',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
@@ -27,7 +32,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(({
|
||||
};
|
||||
|
||||
const baseClasses = 'bg-[var(--ui-color-bg-surface)] border border-[var(--ui-color-border-default)] text-[var(--ui-color-text-high)] placeholder-[var(--ui-color-text-low)] focus:outline-none focus:border-[var(--ui-color-intent-primary)] transition-colors';
|
||||
const errorClasses = error ? 'border-[var(--ui-color-intent-critical)]' : '';
|
||||
const errorClasses = (finalError || variant === 'error') ? 'border-[var(--ui-color-intent-critical)]' : '';
|
||||
const widthClasses = fullWidth ? 'w-full' : '';
|
||||
|
||||
const classes = [
|
||||
@@ -59,14 +64,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(({
|
||||
{...props}
|
||||
/>
|
||||
</Box>
|
||||
{error && (
|
||||
{finalError && (
|
||||
<Box marginTop={1}>
|
||||
<Text size="xs" variant="critical">
|
||||
{error}
|
||||
{finalError}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{hint && !error && (
|
||||
{hint && !finalError && (
|
||||
<Box marginTop={1}>
|
||||
<Text size="xs" variant="low">
|
||||
{hint}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Button } from './Button';
|
||||
import { Icon } from './Icon';
|
||||
import { Surface } from './Surface';
|
||||
import { Text } from './Text';
|
||||
@@ -12,6 +13,10 @@ export interface LeaderboardPreviewShellProps {
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry';
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
onViewFull?: () => void;
|
||||
iconColor?: string;
|
||||
iconBgGradient?: string;
|
||||
viewFullLabel?: string;
|
||||
}
|
||||
|
||||
export const LeaderboardPreviewShell = ({
|
||||
@@ -20,15 +25,19 @@ export const LeaderboardPreviewShell = ({
|
||||
icon,
|
||||
intent = 'primary',
|
||||
children,
|
||||
footer
|
||||
footer,
|
||||
onViewFull,
|
||||
iconColor,
|
||||
iconBgGradient,
|
||||
viewFullLabel
|
||||
}: LeaderboardPreviewShellProps) => {
|
||||
return (
|
||||
<Surface variant="default" rounded="xl" style={{ border: '1px solid var(--ui-color-border-default)', overflow: 'hidden' }}>
|
||||
<Box padding={6} borderBottom>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" marginBottom={4}>
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
<Box padding={2} rounded="lg" bg={iconBgGradient || "var(--ui-color-bg-surface-muted)"} style={iconColor ? { color: iconColor } : undefined}>
|
||||
<Icon icon={icon} size={5} intent={iconColor ? undefined : intent} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="lg" weight="bold" variant="high" block>
|
||||
@@ -41,6 +50,11 @@ export const LeaderboardPreviewShell = ({
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{onViewFull && (
|
||||
<Button variant="ghost" size="sm" onClick={onViewFull}>
|
||||
{viewFullLabel || 'View Full'}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Surface } from './Surface';
|
||||
|
||||
export interface LeaderboardTableShellProps {
|
||||
children: ReactNode;
|
||||
columns?: any[];
|
||||
}
|
||||
|
||||
export const LeaderboardTableShell = ({ children }: LeaderboardTableShellProps) => {
|
||||
export const LeaderboardTableShell = ({ children, columns }: LeaderboardTableShellProps) => {
|
||||
return (
|
||||
<Surface variant="default" rounded="xl" style={{ border: '1px solid var(--ui-color-border-default)', overflow: 'hidden' }}>
|
||||
<Box>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChevronRight } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Card } from './Card';
|
||||
import { Icon } from './Icon';
|
||||
import { Image } from './Image';
|
||||
import { Text } from './Text';
|
||||
|
||||
|
||||
@@ -10,6 +10,20 @@ export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
block?: boolean;
|
||||
hoverColor?: string;
|
||||
transition?: boolean;
|
||||
pb?: number;
|
||||
truncate?: boolean;
|
||||
hoverTextColor?: string;
|
||||
display?: string;
|
||||
alignItems?: string;
|
||||
gap?: number;
|
||||
rounded?: string;
|
||||
bg?: string;
|
||||
px?: number;
|
||||
py?: number;
|
||||
border?: boolean;
|
||||
borderColor?: string;
|
||||
shadow?: string;
|
||||
hoverBorderColor?: string;
|
||||
}
|
||||
|
||||
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(({
|
||||
@@ -22,6 +36,20 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(({
|
||||
block = false,
|
||||
hoverColor,
|
||||
transition = true,
|
||||
pb,
|
||||
truncate,
|
||||
hoverTextColor,
|
||||
display,
|
||||
alignItems,
|
||||
gap,
|
||||
rounded,
|
||||
bg,
|
||||
px,
|
||||
py,
|
||||
border,
|
||||
borderColor,
|
||||
shadow,
|
||||
hoverBorderColor,
|
||||
...props
|
||||
}, ref) => {
|
||||
const variantClasses = {
|
||||
@@ -43,12 +71,24 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(({
|
||||
block ? 'block' : 'inline',
|
||||
variantClasses[variant],
|
||||
underlineClasses[underline],
|
||||
truncate ? 'truncate' : '',
|
||||
].join(' ');
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
...(size ? { fontSize: size } : {}),
|
||||
...(weight ? { fontWeight: weight } : {}),
|
||||
...(letterSpacing ? { letterSpacing } : {}),
|
||||
...(pb ? { paddingBottom: `${pb * 0.25}rem` } : {}),
|
||||
...(display ? { display } : {}),
|
||||
...(alignItems ? { alignItems } : {}),
|
||||
...(gap ? { gap: `${gap * 0.25}rem` } : {}),
|
||||
...(rounded ? { borderRadius: rounded === 'full' ? '9999px' : `var(--ui-radius-${rounded})` } : {}),
|
||||
...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}),
|
||||
...(px ? { paddingLeft: `${px * 0.25}rem`, paddingRight: `${px * 0.25}rem` } : {}),
|
||||
...(py ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}),
|
||||
...(border ? { border: '1px solid var(--ui-color-border-default)' } : {}),
|
||||
...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}),
|
||||
...(shadow ? { boxShadow: shadow.startsWith('shadow-') ? undefined : shadow } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,16 +5,18 @@ export interface MiniStatProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'high' | 'med' | 'low';
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export const MiniStat = ({
|
||||
label,
|
||||
value,
|
||||
intent = 'high'
|
||||
intent = 'high',
|
||||
color
|
||||
}: MiniStatProps) => {
|
||||
return (
|
||||
<Box textAlign="center" padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Text size="lg" weight="bold" variant={intent} block>{value}</Text>
|
||||
<Text size="lg" weight="bold" variant={color ? undefined : intent} style={color ? { color } : undefined} block>{value}</Text>
|
||||
<Text size="xs" variant="low" block style={{ fontSize: '10px' }}>{label}</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Button } from './Button';
|
||||
import { Heading } from './Heading';
|
||||
import { Icon } from './Icon';
|
||||
import { Surface } from './Surface';
|
||||
import { Text } from './Text';
|
||||
|
||||
@@ -9,13 +12,22 @@ export interface PageHeroProps {
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
image?: ReactNode;
|
||||
icon?: LucideIcon;
|
||||
actions?: Array<{
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
variant?: 'primary' | 'secondary';
|
||||
icon?: LucideIcon;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const PageHero = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
image
|
||||
image,
|
||||
icon,
|
||||
actions
|
||||
}: PageHeroProps) => {
|
||||
return (
|
||||
<Surface
|
||||
@@ -26,12 +38,33 @@ export const PageHero = ({
|
||||
>
|
||||
<Box display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems="center" gap={8}>
|
||||
<Box flex={1}>
|
||||
<Heading level={1} marginBottom={4}>{title}</Heading>
|
||||
<Box display="flex" alignItems="center" gap={4} marginBottom={4}>
|
||||
{icon && (
|
||||
<Box padding={3} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={8} intent="primary" />
|
||||
</Box>
|
||||
)}
|
||||
<Heading level={1}>{title}</Heading>
|
||||
</Box>
|
||||
{description && (
|
||||
<Text size="lg" variant="low" marginBottom={6} block>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{actions && (
|
||||
<Box display="flex" gap={4} marginBottom={children ? 6 : 0}>
|
||||
{actions.map((action) => (
|
||||
<Button
|
||||
key={action.label}
|
||||
variant={action.variant || 'primary'}
|
||||
onClick={action.onClick}
|
||||
icon={action.icon ? <Icon icon={action.icon} size={4} /> : undefined}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{children}
|
||||
</Box>
|
||||
{image && (
|
||||
|
||||
@@ -12,6 +12,11 @@ export interface PanelProps {
|
||||
variant?: 'default' | 'dark' | 'muted';
|
||||
padding?: Spacing;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
border?: boolean;
|
||||
rounded?: string;
|
||||
borderColor?: string;
|
||||
bg?: string;
|
||||
}
|
||||
|
||||
export const Panel = ({
|
||||
@@ -21,10 +26,23 @@ export const Panel = ({
|
||||
footer,
|
||||
variant = 'default',
|
||||
padding = 6,
|
||||
actions
|
||||
actions,
|
||||
className,
|
||||
border,
|
||||
rounded,
|
||||
borderColor,
|
||||
bg
|
||||
}: PanelProps) => {
|
||||
return (
|
||||
<Surface variant={variant} rounded="lg" style={{ border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<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}
|
||||
>
|
||||
{(title || description || actions) && (
|
||||
<Box padding={padding as any} borderBottom display="flex" alignItems="center" justifyContent="between">
|
||||
<Box>
|
||||
|
||||
@@ -4,10 +4,29 @@ import { Box } from './Box';
|
||||
import { IconButton } from './IconButton';
|
||||
import { Input, InputProps } from './Input';
|
||||
|
||||
export interface PasswordFieldProps extends InputProps {}
|
||||
export interface PasswordFieldProps extends InputProps {
|
||||
showPassword?: boolean;
|
||||
onTogglePassword?: () => void;
|
||||
}
|
||||
|
||||
export const PasswordField = (props: PasswordFieldProps) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
export const PasswordField = ({
|
||||
showPassword: controlledShowPassword,
|
||||
onTogglePassword,
|
||||
...props
|
||||
}: PasswordFieldProps) => {
|
||||
const [internalShowPassword, setInternalShowPassword] = useState(false);
|
||||
|
||||
const isControlled = controlledShowPassword !== undefined;
|
||||
const showPassword = isControlled ? controlledShowPassword : internalShowPassword;
|
||||
|
||||
const handleToggle = () => {
|
||||
if (onTogglePassword) {
|
||||
onTogglePassword();
|
||||
}
|
||||
if (!isControlled) {
|
||||
setInternalShowPassword(!internalShowPassword);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
@@ -24,7 +43,7 @@ export const PasswordField = (props: PasswordFieldProps) => {
|
||||
>
|
||||
<IconButton
|
||||
icon={showPassword ? EyeOff : Eye}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
onClick={handleToggle}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
title={showPassword ? 'Hide password' : 'Show password'}
|
||||
|
||||
@@ -5,15 +5,19 @@ import { Link } from './Link';
|
||||
import { Text } from './Text';
|
||||
|
||||
export interface QuickActionLinkProps {
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
label?: string;
|
||||
icon?: LucideIcon;
|
||||
href: string;
|
||||
children?: React.ReactNode;
|
||||
variant?: string;
|
||||
}
|
||||
|
||||
export const QuickActionLink = ({
|
||||
label,
|
||||
icon,
|
||||
href
|
||||
href,
|
||||
children,
|
||||
variant
|
||||
}: QuickActionLinkProps) => {
|
||||
return (
|
||||
<Link href={href} underline="none">
|
||||
@@ -24,11 +28,15 @@ export const QuickActionLink = ({
|
||||
paddingY={2}
|
||||
className="group"
|
||||
>
|
||||
<Icon icon={icon} size={4} intent="low" className="group-hover:text-[var(--ui-color-intent-primary)] transition-colors" />
|
||||
<Text size="sm" variant="med" className="group-hover:text-[var(--ui-color-text-high)] transition-colors">
|
||||
{label}
|
||||
</Text>
|
||||
<Icon icon={ArrowRight} size={3} intent="low" className="opacity-0 group-hover:opacity-100 group-hover:translate-x-1 transition-all" />
|
||||
{children ? children : (
|
||||
<>
|
||||
{icon && <Icon icon={icon} size={4} intent="low" className="group-hover:text-[var(--ui-color-intent-primary)] transition-colors" />}
|
||||
<Text size="sm" variant="med" className="group-hover:text-[var(--ui-color-text-high)] transition-colors">
|
||||
{label}
|
||||
</Text>
|
||||
<Icon icon={ArrowRight} size={3} intent="low" className="opacity-0 group-hover:opacity-100 group-hover:translate-x-1 transition-all" />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -6,13 +6,19 @@ export interface SectionProps {
|
||||
variant?: 'default' | 'dark' | 'muted';
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
id?: string;
|
||||
minHeight?: string;
|
||||
py?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Section = ({
|
||||
children,
|
||||
variant = 'default',
|
||||
padding = 'md',
|
||||
id
|
||||
id,
|
||||
minHeight,
|
||||
py,
|
||||
className
|
||||
}: SectionProps) => {
|
||||
const variantClasses = {
|
||||
default: 'bg-[var(--ui-color-bg-base)]',
|
||||
@@ -29,11 +35,15 @@ export const Section = ({
|
||||
|
||||
const classes = [
|
||||
variantClasses[variant],
|
||||
paddingClasses[padding],
|
||||
py !== undefined ? '' : paddingClasses[padding],
|
||||
className,
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<section id={id} className={classes}>
|
||||
<section id={id} className={classes} style={{
|
||||
...(minHeight ? { minHeight } : {}),
|
||||
...(py !== undefined ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {})
|
||||
}}>
|
||||
<Box marginX="auto" maxWidth="80rem" paddingX={4}>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Surface } from './Surface';
|
||||
|
||||
export interface SegmentedControlOption {
|
||||
id: string;
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement
|
||||
hint?: string;
|
||||
fullWidth?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
pl?: number;
|
||||
}
|
||||
|
||||
export const Select = forwardRef<HTMLSelectElement, SelectProps>(({
|
||||
@@ -23,6 +24,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(({
|
||||
hint,
|
||||
fullWidth = false,
|
||||
size = 'md',
|
||||
pl,
|
||||
...props
|
||||
}, ref) => {
|
||||
const sizeClasses = {
|
||||
@@ -42,6 +44,10 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(({
|
||||
widthClasses,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
...(pl !== undefined ? { paddingLeft: `${pl * 0.25}rem` } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width={fullWidth ? '100%' : undefined}>
|
||||
{label && (
|
||||
@@ -55,6 +61,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(({
|
||||
<select
|
||||
ref={ref}
|
||||
className={classes}
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
{options.map((option) => (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export interface SidebarItemProps {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ChangeEvent, forwardRef } from 'react';
|
||||
|
||||
export interface SimpleCheckboxProps {
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const SimpleCheckbox = forwardRef<HTMLInputElement, SimpleCheckboxProps>(({
|
||||
checked,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DollarSign } from 'lucide-react';
|
||||
import { Badge } from './Badge';
|
||||
import { Box } from './Box';
|
||||
import { Card } from './Card';
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface StackProps<T extends ElementType> extends BoxProps<T> {
|
||||
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline' | ResponsiveValue<'start' | 'center' | 'end' | 'stretch' | 'baseline'>;
|
||||
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | ResponsiveValue<'start' | 'center' | 'end' | 'between' | 'around'>;
|
||||
wrap?: boolean;
|
||||
hoverColor?: string;
|
||||
}
|
||||
|
||||
export const Stack = forwardRef(<T extends ElementType = 'div'>(
|
||||
@@ -31,6 +32,7 @@ export const Stack = forwardRef(<T extends ElementType = 'div'>(
|
||||
justify,
|
||||
wrap = false,
|
||||
as,
|
||||
hoverColor,
|
||||
...props
|
||||
}: StackProps<T>,
|
||||
ref: ForwardedRef<HTMLElement>
|
||||
@@ -45,6 +47,7 @@ export const Stack = forwardRef(<T extends ElementType = 'div'>(
|
||||
alignItems={align}
|
||||
justifyContent={justify}
|
||||
flexWrap={wrap ? 'wrap' : 'nowrap'}
|
||||
hoverTextColor={hoverColor}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -9,19 +9,21 @@ export interface StatBoxProps {
|
||||
value: string | number;
|
||||
icon: LucideIcon;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'low';
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export const StatBox = ({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
intent = 'primary'
|
||||
intent = 'primary',
|
||||
color
|
||||
}: StatBoxProps) => {
|
||||
return (
|
||||
<Surface variant="muted" rounded="xl" padding={4} style={{ border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)" style={color ? { color } : undefined}>
|
||||
<Icon icon={icon} size={5} intent={color ? undefined : intent} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>
|
||||
|
||||
@@ -10,39 +10,55 @@ export interface StatCardProps {
|
||||
value: string | number;
|
||||
icon?: LucideIcon;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'low' | 'high' | 'med';
|
||||
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'outline';
|
||||
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'outline' | 'blue' | 'green' | 'orange';
|
||||
font?: 'sans' | 'mono';
|
||||
trend?: {
|
||||
value: number;
|
||||
isPositive: boolean;
|
||||
};
|
||||
footer?: ReactNode;
|
||||
suffix?: string;
|
||||
prefix?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export const StatCard = ({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
intent = 'primary',
|
||||
intent: intentProp,
|
||||
variant = 'default',
|
||||
font = 'sans',
|
||||
trend,
|
||||
footer
|
||||
footer,
|
||||
suffix,
|
||||
prefix,
|
||||
delay
|
||||
}: StatCardProps) => {
|
||||
const variantMap: Record<string, { variant: any, intent: any }> = {
|
||||
blue: { variant: 'default', intent: 'primary' },
|
||||
green: { variant: 'default', intent: 'success' },
|
||||
orange: { variant: 'default', intent: 'warning' },
|
||||
};
|
||||
|
||||
const mapped = variantMap[variant as string] || { variant, intent: intentProp || 'primary' };
|
||||
const finalVariant = mapped.variant;
|
||||
const finalIntent = mapped.intent;
|
||||
|
||||
return (
|
||||
<Card variant={variant}>
|
||||
<Card variant={finalVariant}>
|
||||
<Box display="flex" alignItems="start" justifyContent="between" marginBottom={4}>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="2xl" weight="bold" variant={intent as any || 'high'} font={font} block marginTop={1}>
|
||||
{value}
|
||||
<Text size="2xl" weight="bold" variant={finalIntent as any || 'high'} font={font} block marginTop={1}>
|
||||
{prefix}{value}{suffix}
|
||||
</Text>
|
||||
</Box>
|
||||
{icon && (
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
<Icon icon={icon} size={5} intent={finalIntent} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
import { Stack } from './Stack';
|
||||
|
||||
export interface StatGridItemProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
icon?: React.ReactNode;
|
||||
icon?: React.ReactNode | LucideIcon;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'high' | 'med' | 'low';
|
||||
color?: string; // Alias for intent
|
||||
}
|
||||
@@ -18,11 +20,17 @@ export const StatGridItem = ({
|
||||
intent = 'high',
|
||||
color
|
||||
}: StatGridItemProps) => {
|
||||
const isLucideIcon = typeof icon === 'function' || (typeof icon === 'object' && icon !== null && 'render' in icon);
|
||||
|
||||
return (
|
||||
<Box padding={4} textAlign="center">
|
||||
{icon && (
|
||||
<Stack direction="row" align="center" justify="center" gap={2} marginBottom={1}>
|
||||
{icon}
|
||||
{isLucideIcon ? (
|
||||
<Icon icon={icon as LucideIcon} size={4} intent={intent} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
<Text size="2xl" weight="bold" variant={intent} color={color} block>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Surface } from './Surface';
|
||||
import { Text } from './Text';
|
||||
|
||||
export interface SummaryItemProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
icon: LucideIcon;
|
||||
label?: string;
|
||||
value?: string | number;
|
||||
icon?: LucideIcon;
|
||||
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry';
|
||||
onClick?: () => void;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
rightContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SummaryItem = ({
|
||||
@@ -15,8 +20,14 @@ export const SummaryItem = ({
|
||||
value,
|
||||
icon,
|
||||
intent = 'primary',
|
||||
onClick
|
||||
onClick,
|
||||
title,
|
||||
subtitle,
|
||||
rightContent
|
||||
}: SummaryItemProps) => {
|
||||
const finalTitle = title || label || '';
|
||||
const finalValue = value || subtitle || '';
|
||||
|
||||
return (
|
||||
<Surface
|
||||
variant="muted"
|
||||
@@ -25,18 +36,23 @@ export const SummaryItem = ({
|
||||
onClick={onClick}
|
||||
style={{ cursor: onClick ? 'pointer' : 'default' }}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="lg" weight="bold" variant="high" block marginTop={0.5}>
|
||||
{value}
|
||||
</Text>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
{icon && (
|
||||
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
||||
<Icon icon={icon} size={5} intent={intent} />
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" variant="low" uppercase>
|
||||
{finalTitle}
|
||||
</Text>
|
||||
<Text size="lg" weight="bold" variant="high" block marginTop={0.5}>
|
||||
{finalValue}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
{rightContent}
|
||||
</Box>
|
||||
</Surface>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ 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';
|
||||
rounded?: keyof ThemeRadii | 'none';
|
||||
rounded?: keyof ThemeRadii | 'none' | '2xl';
|
||||
shadow?: keyof ThemeShadows | 'none';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Surface } from './Surface';
|
||||
|
||||
export interface TabNavigationOption {
|
||||
id: string;
|
||||
@@ -8,24 +9,34 @@ export interface TabNavigationOption {
|
||||
}
|
||||
|
||||
export interface TabNavigationProps {
|
||||
options: TabNavigationOption[];
|
||||
activeId: string;
|
||||
onChange: (id: string) => void;
|
||||
options?: TabNavigationOption[];
|
||||
activeId?: string;
|
||||
onChange?: (id: string) => void;
|
||||
tabs?: TabNavigationOption[];
|
||||
activeTab?: string;
|
||||
onTabChange?: (id: string) => void;
|
||||
}
|
||||
|
||||
export const TabNavigation = ({
|
||||
options,
|
||||
activeId,
|
||||
onChange
|
||||
onChange,
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange
|
||||
}: TabNavigationProps) => {
|
||||
const finalOptions = options || tabs || [];
|
||||
const finalActiveId = activeId || activeTab || '';
|
||||
const finalOnChange = onChange || onTabChange || (() => {});
|
||||
|
||||
return (
|
||||
<Surface variant="muted" rounded="xl" padding={1} display="inline-flex">
|
||||
{options.map((option) => {
|
||||
const isActive = option.id === activeId;
|
||||
{finalOptions.map((option) => {
|
||||
const isActive = option.id === finalActiveId;
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
onClick={() => onChange(option.id)}
|
||||
onClick={() => finalOnChange(option.id)}
|
||||
className={`px-4 py-2 text-xs font-bold uppercase tracking-widest transition-all rounded-lg ${
|
||||
isActive
|
||||
? 'bg-[var(--ui-color-bg-surface)] text-[var(--ui-color-intent-primary)] shadow-sm'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Surface } from './Surface';
|
||||
|
||||
export interface TableProps {
|
||||
children: ReactNode;
|
||||
@@ -32,9 +33,9 @@ export const TableHeader = ({ children, className, textAlign, w }: { children: R
|
||||
|
||||
export const TableHead = TableHeader;
|
||||
|
||||
export const TableBody = ({ children }: { children: ReactNode }) => {
|
||||
export const TableBody = ({ children, className }: { children: ReactNode, className?: string }) => {
|
||||
return (
|
||||
<tbody className="divide-y divide-[var(--ui-color-border-muted)]">
|
||||
<tbody className={`divide-y divide-[var(--ui-color-border-muted)] ${className || ''}`}>
|
||||
{children}
|
||||
</tbody>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Badge } from './Badge';
|
||||
import { Box } from './Box';
|
||||
import { Card } from './Card';
|
||||
import { Heading } from './Heading';
|
||||
import { Icon } from './Icon';
|
||||
import { Text } from './Text';
|
||||
|
||||
export interface TeamCardProps {
|
||||
|
||||
@@ -14,12 +14,15 @@ export interface TextProps extends BoxProps<any> {
|
||||
mono?: boolean;
|
||||
block?: boolean;
|
||||
uppercase?: boolean;
|
||||
capitalize?: boolean;
|
||||
letterSpacing?: string;
|
||||
leading?: 'none' | 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose';
|
||||
truncate?: boolean;
|
||||
lineHeight?: string | number;
|
||||
font?: 'sans' | 'mono';
|
||||
hoverVariant?: 'high' | 'med' | 'low' | 'primary' | 'success' | 'warning' | 'critical';
|
||||
htmlFor?: string;
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
@@ -33,12 +36,15 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
mono = false,
|
||||
block = false,
|
||||
uppercase = false,
|
||||
capitalize = false,
|
||||
letterSpacing,
|
||||
leading,
|
||||
truncate = false,
|
||||
lineHeight,
|
||||
font,
|
||||
hoverVariant,
|
||||
htmlFor,
|
||||
cursor,
|
||||
...props
|
||||
}, ref) => {
|
||||
const variantClasses = {
|
||||
@@ -112,6 +118,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
(mono || font === 'mono') ? 'font-mono' : 'font-sans',
|
||||
block ? 'block' : 'inline',
|
||||
uppercase ? 'uppercase tracking-wider' : '',
|
||||
capitalize ? 'capitalize' : '',
|
||||
leading ? leadingClasses[leading] : '',
|
||||
truncate ? 'truncate' : '',
|
||||
hoverVariant ? hoverVariantClasses[hoverVariant] : '',
|
||||
@@ -120,10 +127,11 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
||||
const style: React.CSSProperties = {
|
||||
...(letterSpacing ? { letterSpacing } : {}),
|
||||
...(lineHeight ? { lineHeight } : {}),
|
||||
...(cursor ? { cursor } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<Box as={as} ref={ref} className={classes} style={style} {...props}>
|
||||
<Box as={as} ref={ref} className={classes} style={style} htmlFor={htmlFor} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { ListItem, ListItemActions, ListItemInfo } from '@/ui/ListItem';
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ThemeRadii {
|
||||
md: string;
|
||||
lg: string;
|
||||
xl: string;
|
||||
'2xl': string;
|
||||
full: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export const defaultTheme: Theme = {
|
||||
md: '0.375rem',
|
||||
lg: '0.5rem',
|
||||
xl: '0.75rem',
|
||||
'2xl': '1rem',
|
||||
full: '9999px',
|
||||
},
|
||||
shadows: {
|
||||
|
||||
Reference in New Issue
Block a user