Redesign p1

This commit is contained in:
2025-12-17 02:03:33 +07:00
parent 11f7b59471
commit 332491454d
29 changed files with 5137 additions and 2587 deletions

View File

@@ -15,13 +15,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
ref={ref}
disabled={disabled || isLoading}
className={clsx(
'inline-flex items-center justify-center font-medium rounded-lg transition-colors',
'inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200',
'disabled:opacity-50 disabled:cursor-not-allowed',
{
'bg-primary-600 hover:bg-primary-700 text-white': variant === 'primary',
'bg-gray-700 hover:bg-gray-600 text-white': variant === 'secondary',
'bg-neon-500 hover:bg-neon-400 text-dark-900 font-semibold shadow-[0_0_10px_rgba(0,240,255,0.3)] hover:shadow-[0_0_20px_rgba(0,240,255,0.5)]': variant === 'primary',
'bg-dark-600 hover:bg-dark-500 text-white border border-dark-500': variant === 'secondary',
'bg-red-600 hover:bg-red-700 text-white': variant === 'danger',
'bg-transparent hover:bg-gray-800 text-gray-300': variant === 'ghost',
'bg-transparent hover:bg-dark-700 text-gray-300 hover:text-white': variant === 'ghost',
'px-3 py-1.5 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',

View File

@@ -4,11 +4,18 @@ import { clsx } from 'clsx'
interface CardProps {
children: ReactNode
className?: string
hover?: boolean
}
export function Card({ children, className }: CardProps) {
export function Card({ children, className, hover = false }: CardProps) {
return (
<div className={clsx('bg-gray-800 rounded-xl p-6 shadow-lg', className)}>
<div
className={clsx(
'bg-dark-800 rounded-xl p-6 border border-dark-600',
hover && 'transition-all duration-300 hover:-translate-y-1 hover:border-neon-500/30 hover:shadow-[0_10px_40px_rgba(0,240,255,0.1)]',
className
)}
>
{children}
</div>
)

View File

@@ -0,0 +1,211 @@
import { type ReactNode, type HTMLAttributes } from 'react'
import { clsx } from 'clsx'
interface GlassCardProps extends HTMLAttributes<HTMLDivElement> {
children: ReactNode
variant?: 'default' | 'dark' | 'neon' | 'gradient'
hover?: boolean
glow?: boolean
className?: string
}
export function GlassCard({
children,
variant = 'default',
hover = false,
glow = false,
className,
...props
}: GlassCardProps) {
const variantClasses = {
default: 'glass',
dark: 'glass-dark',
neon: 'glass-neon',
gradient: 'gradient-border',
}
return (
<div
className={clsx(
'rounded-xl p-6',
variantClasses[variant],
hover && 'card-hover cursor-pointer',
glow && 'neon-glow-pulse',
className
)}
{...props}
>
{children}
</div>
)
}
// Stats card variant
interface StatsCardProps {
label: string
value: string | number
icon?: ReactNode
trend?: {
value: number
isPositive: boolean
}
color?: 'neon' | 'purple' | 'pink' | 'default'
className?: string
}
export function StatsCard({
label,
value,
icon,
trend,
color = 'default',
className,
}: StatsCardProps) {
const colorClasses = {
neon: 'border-neon-500/30 hover:border-neon-500/50',
purple: 'border-accent-500/30 hover:border-accent-500/50',
pink: 'border-pink-500/30 hover:border-pink-500/50',
default: 'border-dark-600 hover:border-dark-500',
}
const iconColorClasses = {
neon: 'text-neon-500 bg-neon-500/10',
purple: 'text-accent-500 bg-accent-500/10',
pink: 'text-pink-500 bg-pink-500/10',
default: 'text-gray-400 bg-dark-700',
}
const valueColorClasses = {
neon: 'text-neon-400',
purple: 'text-accent-400',
pink: 'text-pink-400',
default: 'text-white',
}
return (
<div
className={clsx(
'glass rounded-xl p-4 border transition-all duration-300',
colorClasses[color],
'hover:-translate-y-0.5',
className
)}
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400 mb-1">{label}</p>
<p className={clsx('text-2xl font-bold', valueColorClasses[color])}>
{value}
</p>
{trend && (
<p
className={clsx(
'text-xs mt-1',
trend.isPositive ? 'text-green-400' : 'text-red-400'
)}
>
{trend.isPositive ? '+' : ''}{trend.value}%
</p>
)}
</div>
{icon && (
<div
className={clsx(
'w-12 h-12 rounded-lg flex items-center justify-center',
iconColorClasses[color]
)}
>
{icon}
</div>
)}
</div>
</div>
)
}
// Feature card variant
interface FeatureCardProps {
title: string
description: string
icon: ReactNode
color?: 'neon' | 'purple' | 'pink'
className?: string
}
export function FeatureCard({
title,
description,
icon,
color = 'neon',
className,
}: FeatureCardProps) {
const colorClasses = {
neon: {
icon: 'text-neon-500 bg-neon-500/10 group-hover:bg-neon-500/20',
border: 'group-hover:border-neon-500/50',
glow: 'group-hover:shadow-[0_0_30px_rgba(0,240,255,0.15)]',
},
purple: {
icon: 'text-accent-500 bg-accent-500/10 group-hover:bg-accent-500/20',
border: 'group-hover:border-accent-500/50',
glow: 'group-hover:shadow-[0_0_30px_rgba(168,85,247,0.15)]',
},
pink: {
icon: 'text-pink-500 bg-pink-500/10 group-hover:bg-pink-500/20',
border: 'group-hover:border-pink-500/50',
glow: 'group-hover:shadow-[0_0_30px_rgba(236,72,153,0.15)]',
},
}
const colors = colorClasses[color]
return (
<div
className={clsx(
'group glass rounded-xl p-6 border border-dark-600 transition-all duration-300',
'hover:-translate-y-1',
colors.border,
colors.glow,
className
)}
>
<div
className={clsx(
'w-14 h-14 rounded-xl flex items-center justify-center mb-4 transition-colors',
colors.icon
)}
>
{icon}
</div>
<h3 className="text-lg font-semibold text-white mb-2">{title}</h3>
<p className="text-gray-400 text-sm">{description}</p>
</div>
)
}
// Interactive card with animated border
interface AnimatedBorderCardProps extends HTMLAttributes<HTMLDivElement> {
children: ReactNode
className?: string
}
export function AnimatedBorderCard({
children,
className,
...props
}: AnimatedBorderCardProps) {
return (
<div className={clsx('relative group', className)} {...props}>
{/* Animated gradient border */}
<div
className="absolute -inset-0.5 bg-gradient-to-r from-neon-500 via-accent-500 to-pink-500 rounded-xl opacity-30 group-hover:opacity-60 blur transition-opacity duration-300"
style={{
backgroundSize: '200% 200%',
animation: 'gradient-flow 3s linear infinite',
}}
/>
{/* Card content */}
<div className="relative glass-dark rounded-xl p-6">{children}</div>
</div>
)
}

View File

@@ -0,0 +1,116 @@
import { type ReactNode, type HTMLAttributes } from 'react'
import { clsx } from 'clsx'
interface GlitchTextProps extends HTMLAttributes<HTMLSpanElement> {
children: ReactNode
as?: 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'p'
intensity?: 'low' | 'medium' | 'high'
color?: 'neon' | 'purple' | 'pink' | 'white'
hover?: boolean
className?: string
}
export function GlitchText({
children,
as: Component = 'span',
intensity = 'medium',
color = 'neon',
hover = false,
className,
...props
}: GlitchTextProps) {
const text = typeof children === 'string' ? children : ''
const colorClasses = {
neon: 'text-neon-500',
purple: 'text-accent-500',
pink: 'text-pink-500',
white: 'text-white',
}
const glowClasses = {
neon: 'neon-text',
purple: 'neon-text-purple',
pink: '[text-shadow:0_0_10px_#ec4899,0_0_20px_#ec4899,0_0_30px_#ec4899]',
white: '[text-shadow:0_0_10px_rgba(255,255,255,0.5),0_0_20px_rgba(255,255,255,0.3)]',
}
const intensityClasses = {
low: 'animate-[glitch-skew_2s_infinite_linear_alternate-reverse]',
medium: '',
high: 'animate-glitch',
}
if (hover) {
return (
<Component
className={clsx(
colorClasses[color],
'relative inline-block cursor-pointer transition-all duration-300',
'hover:' + glowClasses[color],
'glitch-hover',
className
)}
{...props}
>
{children}
</Component>
)
}
return (
<Component
className={clsx(
'glitch relative inline-block',
colorClasses[color],
glowClasses[color],
intensityClasses[intensity],
className
)}
data-text={text}
{...props}
>
{children}
</Component>
)
}
// Simpler glitch effect for headings
interface GlitchHeadingProps {
children: ReactNode
level?: 1 | 2 | 3 | 4
className?: string
gradient?: boolean
}
export function GlitchHeading({
children,
level = 1,
className,
gradient = false,
}: GlitchHeadingProps) {
const text = typeof children === 'string' ? children : ''
const sizeClasses = {
1: 'text-4xl md:text-5xl lg:text-6xl font-bold',
2: 'text-3xl md:text-4xl font-bold',
3: 'text-2xl md:text-3xl font-semibold',
4: 'text-xl md:text-2xl font-semibold',
}
const Component = `h${level}` as keyof JSX.IntrinsicElements
return (
<Component
className={clsx(
'glitch relative inline-block',
sizeClasses[level],
gradient ? 'gradient-neon-text' : 'text-white neon-text',
className
)}
data-text={text}
>
{children}
</Component>
)
}

View File

@@ -11,7 +11,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
return (
<div className="w-full">
{label && (
<label htmlFor={id} className="block text-sm font-medium text-gray-300 mb-1">
<label htmlFor={id} className="block text-sm font-medium text-gray-300 mb-1.5">
{label}
</label>
)}
@@ -19,15 +19,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
ref={ref}
id={id}
className={clsx(
'w-full px-4 py-2 bg-gray-800 border rounded-lg text-white placeholder-gray-500',
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent',
'transition-colors',
error ? 'border-red-500' : 'border-gray-700',
'w-full px-4 py-3 bg-dark-800 border rounded-lg text-white placeholder-gray-500',
'focus:outline-none focus:border-neon-500',
'focus:shadow-[0_0_0_3px_rgba(0,240,255,0.1),0_0_10px_rgba(0,240,255,0.2)]',
'transition-all duration-200',
error ? 'border-red-500' : 'border-dark-600',
className
)}
{...props}
/>
{error && <p className="mt-1 text-sm text-red-500">{error}</p>}
{error && <p className="mt-1.5 text-sm text-red-400">{error}</p>}
</div>
)
}

View File

@@ -0,0 +1,166 @@
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'
import { clsx } from 'clsx'
import { Loader2 } from 'lucide-react'
interface NeonButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
size?: 'sm' | 'md' | 'lg'
color?: 'neon' | 'purple' | 'pink'
isLoading?: boolean
icon?: ReactNode
iconPosition?: 'left' | 'right'
glow?: boolean
pulse?: boolean
}
export const NeonButton = forwardRef<HTMLButtonElement, NeonButtonProps>(
(
{
className,
variant = 'primary',
size = 'md',
color = 'neon',
isLoading,
icon,
iconPosition = 'left',
glow = true,
pulse = false,
children,
disabled,
...props
},
ref
) => {
const colorMap = {
neon: {
primary: 'bg-neon-500 hover:bg-neon-400 text-dark-900',
secondary: 'bg-dark-600 hover:bg-dark-500 text-neon-400 border border-neon-500/30',
outline: 'bg-transparent border-2 border-neon-500 text-neon-500 hover:bg-neon-500 hover:text-dark-900',
ghost: 'bg-transparent hover:bg-neon-500/10 text-neon-400',
danger: 'bg-red-600 hover:bg-red-700 text-white',
glow: '0 0 20px rgba(0, 240, 255, 0.5)',
glowHover: '0 0 30px rgba(0, 240, 255, 0.7)',
},
purple: {
primary: 'bg-accent-500 hover:bg-accent-400 text-white',
secondary: 'bg-dark-600 hover:bg-dark-500 text-accent-400 border border-accent-500/30',
outline: 'bg-transparent border-2 border-accent-500 text-accent-500 hover:bg-accent-500 hover:text-white',
ghost: 'bg-transparent hover:bg-accent-500/10 text-accent-400',
danger: 'bg-red-600 hover:bg-red-700 text-white',
glow: '0 0 20px rgba(168, 85, 247, 0.5)',
glowHover: '0 0 30px rgba(168, 85, 247, 0.7)',
},
pink: {
primary: 'bg-pink-500 hover:bg-pink-400 text-white',
secondary: 'bg-dark-600 hover:bg-dark-500 text-pink-400 border border-pink-500/30',
outline: 'bg-transparent border-2 border-pink-500 text-pink-500 hover:bg-pink-500 hover:text-white',
ghost: 'bg-transparent hover:bg-pink-500/10 text-pink-400',
danger: 'bg-red-600 hover:bg-red-700 text-white',
glow: '0 0 20px rgba(236, 72, 153, 0.5)',
glowHover: '0 0 30px rgba(236, 72, 153, 0.7)',
},
}
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm gap-1.5',
md: 'px-4 py-2.5 text-base gap-2',
lg: 'px-6 py-3 text-lg gap-2.5',
}
const iconSizes = {
sm: 'w-4 h-4',
md: 'w-5 h-5',
lg: 'w-6 h-6',
}
const colors = colorMap[color]
return (
<button
ref={ref}
disabled={disabled || isLoading}
className={clsx(
'inline-flex items-center justify-center font-semibold rounded-lg',
'transition-all duration-300 ease-out',
'disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none',
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-900',
color === 'neon' && 'focus:ring-neon-500',
color === 'purple' && 'focus:ring-accent-500',
color === 'pink' && 'focus:ring-pink-500',
colors[variant],
sizeClasses[size],
pulse && 'neon-glow-pulse',
className
)}
style={{
boxShadow: glow && !disabled && variant !== 'ghost' ? colors.glow : undefined,
}}
onMouseEnter={(e) => {
if (glow && !disabled && variant !== 'ghost') {
e.currentTarget.style.boxShadow = colors.glowHover
}
props.onMouseEnter?.(e)
}}
onMouseLeave={(e) => {
if (glow && !disabled && variant !== 'ghost') {
e.currentTarget.style.boxShadow = colors.glow
}
props.onMouseLeave?.(e)
}}
{...props}
>
{isLoading && <Loader2 className={clsx(iconSizes[size], 'animate-spin')} />}
{!isLoading && icon && iconPosition === 'left' && (
<span className={iconSizes[size]}>{icon}</span>
)}
{children}
{!isLoading && icon && iconPosition === 'right' && (
<span className={iconSizes[size]}>{icon}</span>
)}
</button>
)
}
)
NeonButton.displayName = 'NeonButton'
// Gradient button variant
interface GradientButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
size?: 'sm' | 'md' | 'lg'
isLoading?: boolean
icon?: ReactNode
}
export const GradientButton = forwardRef<HTMLButtonElement, GradientButtonProps>(
({ className, size = 'md', isLoading, icon, children, disabled, ...props }, ref) => {
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm gap-1.5',
md: 'px-4 py-2.5 text-base gap-2',
lg: 'px-6 py-3 text-lg gap-2.5',
}
return (
<button
ref={ref}
disabled={disabled || isLoading}
className={clsx(
'relative inline-flex items-center justify-center font-semibold rounded-lg',
'bg-gradient-to-r from-neon-500 via-accent-500 to-pink-500',
'text-white transition-all duration-300',
'hover:shadow-[0_0_30px_rgba(168,85,247,0.5)]',
'disabled:opacity-50 disabled:cursor-not-allowed',
'focus:outline-none focus:ring-2 focus:ring-accent-500 focus:ring-offset-2 focus:ring-offset-dark-900',
sizeClasses[size],
className
)}
{...props}
>
{isLoading && <Loader2 className="w-5 h-5 animate-spin" />}
{!isLoading && icon && <span className="w-5 h-5">{icon}</span>}
{children}
</button>
)
}
)
GradientButton.displayName = 'GradientButton'

View File

@@ -4,3 +4,8 @@ export { Card, CardHeader, CardTitle, CardContent } from './Card'
export { ToastContainer } from './Toast'
export { ConfirmModal } from './ConfirmModal'
export { UserAvatar, clearAvatarCache } from './UserAvatar'
// New design system components
export { GlitchText, GlitchHeading } from './GlitchText'
export { NeonButton, GradientButton } from './NeonButton'
export { GlassCard, StatsCard, FeatureCard, AnimatedBorderCard } from './GlassCard'