website refactor

This commit is contained in:
2026-01-20 00:41:57 +01:00
parent f5215f9d73
commit b9624db452
8 changed files with 316 additions and 165 deletions

View File

@@ -45,6 +45,7 @@ export interface ButtonProps {
shadow?: string;
display?: string;
center?: boolean;
justifyContent?: string;
}
export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(({
@@ -89,6 +90,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
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' : '';
@@ -125,6 +127,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
hoverScale ? 'hover:scale-105' : '',
display === 'flex' ? 'flex' : '',
center ? 'items-center justify-center' : '',
justifyContent ? `justify-${justifyContent}` : '',
className,
].filter(Boolean).join(' ');

View File

@@ -1,77 +1,100 @@
import React, { forwardRef, InputHTMLAttributes } from 'react';
import { Box } from './Box';
import { Icon } from './Icon';
import { Text } from './Text';
import { LucideIcon } from 'lucide-react';
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
icon?: LucideIcon | React.ReactNode;
rightElement?: React.ReactNode;
variant?: 'default' | 'ghost' | 'search' | 'error';
fullWidth?: boolean;
label?: string;
error?: string;
errorMessage?: string;
errorMessage?: string; // Alias for error
hint?: string;
fullWidth?: boolean;
size?: 'sm' | 'md' | 'lg';
icon?: React.ReactNode;
variant?: 'default' | 'error';
size?: string | number; // Allow size prop
}
export const Input = forwardRef<HTMLInputElement, InputProps>(({
label,
error,
errorMessage,
hint,
icon,
rightElement,
variant = 'default',
fullWidth = false,
size = 'md',
icon,
variant = 'default',
className,
label,
error,
errorMessage,
hint,
id,
size,
...props
}, ref) => {
const finalError = error || errorMessage;
const sizeClasses = {
sm: 'px-3 py-1.5 text-xs',
md: 'px-4 py-2 text-sm',
lg: 'px-4 py-3 text-base'
const variantClasses = {
default: 'bg-surface-charcoal border border-outline-steel focus:border-primary-accent',
ghost: 'bg-transparent border-none',
search: 'bg-surface-charcoal/50 border border-outline-steel focus:border-primary-accent hover:border-text-low/50',
error: 'bg-surface-charcoal border border-critical-red focus:border-critical-red',
};
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 = (finalError || variant === 'error') ? 'border-[var(--ui-color-intent-critical)]' : '';
const widthClasses = fullWidth ? 'w-full' : '';
const classes = [
baseClasses,
sizeClasses[size],
errorClasses,
widthClasses,
icon ? 'pl-10' : '',
].filter(Boolean).join(' ');
const inputId = id || (label ? `input-${label.toLowerCase().replace(/\s+/g, '-')}` : undefined);
const displayError = error || errorMessage;
return (
<Box width={fullWidth ? '100%' : undefined}>
<Box width={fullWidth ? 'full' : 'auto'}>
{label && (
<Box marginBottom={1.5}>
<Text as="label" size="xs" weight="bold" variant="low">
<Text
as="label"
htmlFor={inputId}
size="sm"
weight="medium"
variant="high"
>
{label}
</Text>
</Box>
)}
<Box position="relative">
<Box
display="flex"
alignItems="center"
gap={3}
paddingX={3}
height="9" // h-9
rounded="md"
width="full"
className={`transition-all duration-200 group ${variantClasses[variant]}`}
>
{icon && (
<Box position="absolute" left={3} top="50%" style={{ transform: 'translateY(-50%)' }}>
{icon}
</Box>
React.isValidElement(icon) ? icon : (
<Icon
icon={icon as LucideIcon}
size={3.5}
className="text-text-low group-focus-within:text-text-high transition-colors"
/>
)
)}
<input
ref={ref}
className={classes}
id={inputId}
className="bg-transparent border-none outline-none text-sm w-full text-text-high placeholder:text-text-low/50 h-full"
{...props}
/>
{rightElement}
</Box>
{finalError && (
{displayError && (
<Box marginTop={1}>
<Text size="xs" variant="critical">
{finalError}
{displayError}
</Text>
</Box>
)}
{hint && !finalError && (
{hint && !displayError && (
<Box marginTop={1}>
<Text size="xs" variant="low">
{hint}

View File

@@ -0,0 +1,48 @@
import { ReactNode } from 'react';
import { Stack } from './Stack';
interface ToolbarProps {
children: ReactNode;
minimized?: boolean;
bottom?: number | string;
}
export function Toolbar({ children, minimized = false, bottom = '2rem' }: ToolbarProps) {
if (minimized) {
return (
<Stack
position="fixed"
right={4}
zIndex={1000}
style={{ bottom }}
>
{children}
</Stack>
);
}
return (
<Stack
position="fixed"
right={4}
zIndex={1000}
width="min(420px, calc(100vw - 2rem))"
maxHeight="calc(100vh - 2rem)"
overflow="auto"
border={true}
borderColor="var(--ui-color-border-default)"
rounded="xl"
bg="rgba(20, 22, 25, 0.92)"
padding={3}
style={{
bottom,
boxShadow: '0 18px 40px rgba(0,0,0,0.55)',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
}}
gap={4}
>
{children}
</Stack>
);
}

View File

@@ -0,0 +1,101 @@
import { ReactNode } from 'react';
import { Box } from '../Box';
// --- Shell Sidebar ---
interface ShellSidebarProps {
children: ReactNode;
header?: ReactNode;
footer?: ReactNode;
collapsed?: boolean;
width?: string | number;
collapsedWidth?: string | number;
}
export function ShellSidebar({
children,
header,
footer,
collapsed = false,
width = '16rem',
collapsedWidth = '5rem'
}: ShellSidebarProps) {
return (
<Box
as="aside"
display="flex"
flexDirection="col"
height="full"
className="bg-base-black border-r border-white/10 relative transition-all duration-300 ease-in-out"
style={{ width: collapsed ? collapsedWidth : width }}
>
{header && (
<Box p={6} pb={8} display="flex" alignItems="center" justifyContent={collapsed ? 'center' : 'start'}>
{header}
</Box>
)}
<Box flex={1} px={collapsed ? 2 : 4} className="overflow-y-auto scrollbar-hide transition-all duration-300">
{children}
</Box>
{footer && (
<Box className="h-14 flex items-center px-4 border-t border-white/10">
{footer}
</Box>
)}
</Box>
);
}
// --- Shell Header ---
interface ShellHeaderProps {
children: ReactNode;
collapsed?: boolean;
}
export function ShellHeader({ children, collapsed = false }: ShellHeaderProps) {
return (
<Box
as="header"
className={`
fixed top-0 right-0 z-40
h-14
bg-base-black/80 backdrop-blur-xl
border-b border-white/10
flex items-center justify-between px-6
transition-all duration-300 ease-in-out
left-0 ${collapsed ? 'lg:left-20' : 'lg:left-64'}
`}
>
{children}
</Box>
);
}
// --- Shell Footer ---
interface ShellFooterProps {
children: ReactNode;
collapsed?: boolean;
}
export function ShellFooter({ children, collapsed = false }: ShellFooterProps) {
return (
<Box
as="footer"
className={`
fixed bottom-0 right-0 z-40
h-14
bg-base-black/80 backdrop-blur-xl
border-t border-white/10
flex items-center justify-between px-6
transition-all duration-300 ease-in-out
left-0 ${collapsed ? 'lg:left-20' : 'lg:left-64'}
`}
>
{children}
</Box>
);
}