Redesign p1
This commit is contained in:
211
frontend/src/components/ui/GlassCard.tsx
Normal file
211
frontend/src/components/ui/GlassCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user