update
This commit is contained in:
40
frontend/src/components/ui/Badge.tsx
Normal file
40
frontend/src/components/ui/Badge.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
color?: 'accent' | 'green' | 'orange' | 'red';
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
export const Badge: React.FC<BadgeProps> = ({
|
||||
children,
|
||||
color = 'accent',
|
||||
size = 'sm',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const colorStyles = {
|
||||
accent: { bg: theme.gradientAccent, text: '#fff' },
|
||||
green: { bg: theme.green, text: '#fff' },
|
||||
orange: { bg: theme.orange, text: '#fff' },
|
||||
red: { bg: '#ff4444', text: '#fff' },
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
sm: 'px-2 py-0.5 text-xs',
|
||||
md: 'px-3 py-1 text-sm',
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`${sizeStyles[size]} rounded font-mono font-medium`}
|
||||
style={{
|
||||
background: colorStyles[color].bg,
|
||||
color: colorStyles[color].text,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
60
frontend/src/components/ui/Button.tsx
Normal file
60
frontend/src/components/ui/Button.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
children,
|
||||
onClick,
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const baseStyles = `
|
||||
inline-flex items-center justify-center
|
||||
font-semibold rounded-xl cursor-pointer
|
||||
transition-all duration-300 ease
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`;
|
||||
|
||||
const sizeStyles = {
|
||||
sm: 'px-3 py-1.5 text-xs',
|
||||
md: 'px-5 py-3 text-sm',
|
||||
lg: 'px-8 py-5 text-base',
|
||||
};
|
||||
|
||||
const variantStyles = {
|
||||
primary: `
|
||||
bg-gradient-to-r from-t-accent to-t-accent-dark
|
||||
text-white hover:shadow-t-accent hover:-translate-y-0.5
|
||||
hover:from-[#f0208a] hover:to-[#d01070]
|
||||
`,
|
||||
secondary: `
|
||||
bg-t-bg-hover border border-t-border
|
||||
text-t-text2 hover:bg-t-bg-elevated
|
||||
`,
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${className}`}
|
||||
style={{
|
||||
color: variant === 'primary' ? '#fff' : theme.text2,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
54
frontend/src/components/ui/Card.tsx
Normal file
54
frontend/src/components/ui/Card.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface CardProps {
|
||||
accent?: boolean;
|
||||
highlight?: boolean;
|
||||
padding?: 'sm' | 'md' | 'lg';
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const Card: React.FC<CardProps> = ({
|
||||
accent = false,
|
||||
highlight = false,
|
||||
padding = 'md',
|
||||
children,
|
||||
className = '',
|
||||
onClick,
|
||||
}) => {
|
||||
const { theme, isDark } = useTheme();
|
||||
|
||||
const paddingStyles = {
|
||||
sm: 'p-4',
|
||||
md: 'p-5',
|
||||
lg: 'p-8',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`
|
||||
rounded-xl border transition-all duration-200 cursor-default
|
||||
${paddingStyles[padding]}
|
||||
${accent ? 'border-t-accent' : 'border-t-border'}
|
||||
${highlight ? 'border-2 border-t-accent' : ''}
|
||||
${!isDark ? 'shadow-t-card' : ''}
|
||||
${onClick ? 'cursor-pointer hover:border-t-accent' : ''}
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
}}
|
||||
>
|
||||
{accent && (
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-[3px] rounded-t-xl"
|
||||
style={{ background: theme.gradientAccent }}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
45
frontend/src/components/ui/Input.tsx
Normal file
45
frontend/src/components/ui/Input.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface InputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
className?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export const Input: React.FC<InputProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = '',
|
||||
onKeyDown,
|
||||
className = '',
|
||||
type = 'text',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={placeholder}
|
||||
className={`
|
||||
w-full px-4 py-3 text-sm
|
||||
bg-t-bg-card border border-t-border rounded-lg
|
||||
text-t-text outline-none
|
||||
focus:border-t-accent focus:ring-1 focus:ring-t-accent
|
||||
placeholder:text-t-text3
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
borderColor: theme.border,
|
||||
color: theme.text,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
43
frontend/src/components/ui/ProgressBar.tsx
Normal file
43
frontend/src/components/ui/ProgressBar.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface ProgressBarProps {
|
||||
percent: number;
|
||||
color?: 'accent' | 'green' | 'orange' | 'red';
|
||||
showLabel?: boolean;
|
||||
}
|
||||
|
||||
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
||||
percent,
|
||||
color = 'accent',
|
||||
showLabel = false,
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const colorStyles = {
|
||||
accent: theme.gradientAccent,
|
||||
green: `linear-gradient(90deg, ${theme.green}, #00ff88)`,
|
||||
orange: `linear-gradient(90deg, ${theme.orange}, #ffaa00)`,
|
||||
red: '#ff4444',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="h-2 rounded-full flex-1"
|
||||
style={{ backgroundColor: theme.bgHover }}
|
||||
>
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${percent}%`,
|
||||
background: colorStyles[color],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{showLabel && (
|
||||
<span className="font-mono text-xs text-t-accent">{percent}%</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
52
frontend/src/components/ui/ScoreBar.tsx
Normal file
52
frontend/src/components/ui/ScoreBar.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface ScoreBarProps {
|
||||
score: number; // 0-100
|
||||
label?: string;
|
||||
accent?: boolean;
|
||||
}
|
||||
|
||||
export const ScoreBar: React.FC<ScoreBarProps> = ({
|
||||
score,
|
||||
label,
|
||||
accent = false,
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="p-5 rounded-xl border"
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
borderColor: accent ? theme.accent : theme.border,
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<div
|
||||
className="font-mono text-xs mb-2"
|
||||
style={{ color: theme.text3, letterSpacing: '1px' }}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="font-mono text-3xl font-bold"
|
||||
style={{ color: accent ? theme.accent : theme.text }}
|
||||
>
|
||||
{score}
|
||||
</div>
|
||||
<div className="flex gap-1 mt-2">
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-2 h-4 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: i < score / 10 ? theme.accent : theme.bgHover,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
6
frontend/src/components/ui/index.ts
Normal file
6
frontend/src/components/ui/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { Button } from './Button';
|
||||
export { Card } from './Card';
|
||||
export { Input } from './Input';
|
||||
export { Badge } from './Badge';
|
||||
export { ProgressBar } from './ProgressBar';
|
||||
export { ScoreBar } from './ScoreBar';
|
||||
Reference in New Issue
Block a user