Enhance theming and UI components: Introduce a new dark cyan theme in globals.css, update layout to utilize ThemeProvider for consistent theming, and refactor button and card components to use CSS variables for styling. Improve navigation and alert components with dynamic styles based on theme variables, ensuring a cohesive user experience across the application.
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 49s
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 49s
This commit is contained in:
@@ -8,10 +8,10 @@ interface AlertProps extends HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
const variantClasses = {
|
||||
success: "bg-green-900/50 border-green-500/50 text-green-400",
|
||||
error: "bg-red-900/50 border-red-500/50 text-red-400",
|
||||
warning: "bg-yellow-900/50 border-yellow-500/50 text-yellow-400",
|
||||
info: "bg-blue-900/50 border-blue-500/50 text-blue-400",
|
||||
success: "border",
|
||||
error: "border",
|
||||
warning: "border",
|
||||
info: "border",
|
||||
};
|
||||
|
||||
export default function Alert({
|
||||
@@ -20,9 +20,33 @@ export default function Alert({
|
||||
className = "",
|
||||
...props
|
||||
}: AlertProps) {
|
||||
const variantStyles = {
|
||||
success: {
|
||||
backgroundColor: "color-mix(in srgb, var(--success) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--success) 50%, transparent)",
|
||||
color: "var(--success)",
|
||||
},
|
||||
error: {
|
||||
backgroundColor: "color-mix(in srgb, var(--destructive) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--destructive) 50%, transparent)",
|
||||
color: "var(--destructive)",
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: "color-mix(in srgb, var(--yellow) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--yellow) 50%, transparent)",
|
||||
color: "var(--yellow)",
|
||||
},
|
||||
info: {
|
||||
backgroundColor: "color-mix(in srgb, var(--blue) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--blue) 50%, transparent)",
|
||||
color: "var(--blue)",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`border rounded px-4 py-3 text-sm ${variantClasses[variant]} ${className}`}
|
||||
style={variantStyles[variant]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -49,7 +49,11 @@ export default function Avatar({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${sizeClass} rounded-full border overflow-hidden bg-black/60 flex items-center justify-center relative ${className} ${borderClassName}`}
|
||||
className={`${sizeClass} rounded-full border overflow-hidden flex items-center justify-center relative ${className} ${borderClassName}`}
|
||||
style={{
|
||||
backgroundColor: "var(--card)",
|
||||
borderColor: "var(--border)",
|
||||
}}
|
||||
>
|
||||
{displaySrc ? (
|
||||
<img
|
||||
@@ -61,7 +65,8 @@ export default function Avatar({
|
||||
/>
|
||||
) : null}
|
||||
<span
|
||||
className={`text-pixel-gold font-bold ${displaySrc ? "hidden" : ""}`}
|
||||
className={`font-bold ${displaySrc ? "hidden" : ""}`}
|
||||
style={{ color: "var(--accent-color)" }}
|
||||
>
|
||||
{initial}
|
||||
</span>
|
||||
|
||||
@@ -29,7 +29,16 @@ export default function BackgroundSection({
|
||||
>
|
||||
{/* Dark overlay for readability */}
|
||||
{overlay && (
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-b"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom,
|
||||
color-mix(in srgb, var(--background) 70%, transparent),
|
||||
color-mix(in srgb, var(--background) 60%, transparent),
|
||||
color-mix(in srgb, var(--background) 80%, transparent)
|
||||
)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
}
|
||||
|
||||
const variantClasses = {
|
||||
default: "bg-pixel-gold/20 border-pixel-gold/50 text-pixel-gold",
|
||||
success: "bg-green-900/50 border-green-500/50 text-green-400",
|
||||
warning: "bg-yellow-900/50 border-yellow-500/50 text-yellow-400",
|
||||
danger: "bg-red-900/50 border-red-500/50 text-red-400",
|
||||
info: "bg-blue-900/30 border-blue-500/50 text-blue-400",
|
||||
default: "border",
|
||||
success: "border",
|
||||
warning: "border",
|
||||
danger: "border",
|
||||
info: "border",
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
@@ -28,9 +28,38 @@ export default function Badge({
|
||||
className = "",
|
||||
...props
|
||||
}: BadgeProps) {
|
||||
const variantStyles = {
|
||||
default: {
|
||||
backgroundColor: "color-mix(in srgb, var(--accent-color) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--accent-color) 50%, transparent)",
|
||||
color: "var(--accent-color)",
|
||||
},
|
||||
success: {
|
||||
backgroundColor: "color-mix(in srgb, var(--success) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--success) 50%, transparent)",
|
||||
color: "var(--success)",
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: "color-mix(in srgb, var(--yellow) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--yellow) 50%, transparent)",
|
||||
color: "var(--yellow)",
|
||||
},
|
||||
danger: {
|
||||
backgroundColor: "color-mix(in srgb, var(--destructive) 20%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--destructive) 50%, transparent)",
|
||||
color: "var(--destructive)",
|
||||
},
|
||||
info: {
|
||||
backgroundColor: "color-mix(in srgb, var(--blue) 15%, transparent)",
|
||||
borderColor: "color-mix(in srgb, var(--blue) 50%, transparent)",
|
||||
color: "var(--blue)",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-block border uppercase rounded whitespace-nowrap flex-shrink-0 ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
|
||||
style={variantStyles[variant]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -10,14 +10,11 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
}
|
||||
|
||||
const variantClasses = {
|
||||
primary:
|
||||
"border-pixel-gold/50 bg-black/60 text-white hover:bg-pixel-gold/10 hover:border-pixel-gold",
|
||||
secondary:
|
||||
"border-gray-600/50 bg-gray-900/20 text-gray-400 hover:bg-gray-900/30 hover:border-gray-500",
|
||||
success:
|
||||
"border-green-500/50 bg-green-900/20 text-green-400 hover:bg-green-900/30",
|
||||
danger: "border-red-500/50 bg-red-900/20 text-red-400 hover:bg-red-900/30",
|
||||
ghost: "border-transparent bg-transparent text-white hover:text-pixel-gold",
|
||||
primary: "btn-primary border transition-colors",
|
||||
secondary: "btn-secondary border transition-colors",
|
||||
success: "btn-success border transition-colors",
|
||||
danger: "btn-danger border transition-colors",
|
||||
ghost: "btn-ghost border-transparent transition-colors",
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
|
||||
@@ -8,8 +8,8 @@ interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
const variantClasses = {
|
||||
default: "bg-black/60 border border-pixel-gold/30",
|
||||
dark: "bg-black/80 border border-pixel-gold/30",
|
||||
default: "border",
|
||||
dark: "border",
|
||||
};
|
||||
|
||||
export default function Card({
|
||||
@@ -18,9 +18,21 @@ export default function Card({
|
||||
className = "",
|
||||
...props
|
||||
}: CardProps) {
|
||||
const variantStyles = {
|
||||
default: {
|
||||
backgroundColor: "var(--card)",
|
||||
borderColor: "var(--border)",
|
||||
},
|
||||
dark: {
|
||||
backgroundColor: "var(--card-hover)",
|
||||
borderColor: "var(--border)",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg backdrop-blur-sm ${variantClasses[variant]} ${className}`}
|
||||
style={variantStyles[variant]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -21,7 +21,18 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
className={`w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition ${className}`}
|
||||
className={`w-full px-4 py-3 border rounded focus:outline-none transition ${className}`}
|
||||
style={{
|
||||
backgroundColor: "var(--input)",
|
||||
borderColor: "var(--border)",
|
||||
color: "var(--foreground)",
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.borderColor = "var(--accent-color)";
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = "var(--border)";
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
|
||||
@@ -39,11 +39,18 @@ export default function Modal({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
|
||||
className="fixed inset-0 z-[200] flex items-center justify-center p-4 backdrop-blur-sm"
|
||||
style={{
|
||||
backgroundColor: "color-mix(in srgb, var(--background) 80%, transparent)",
|
||||
}}
|
||||
onClick={closeOnOverlayClick ? onClose : undefined}
|
||||
>
|
||||
<div
|
||||
className={`bg-black border-2 border-pixel-gold/70 rounded-lg w-full ${sizeClasses[size]} max-h-[90vh] overflow-y-auto shadow-2xl`}
|
||||
className={`border-2 rounded-lg w-full ${sizeClasses[size]} max-h-[90vh] overflow-y-auto shadow-2xl`}
|
||||
style={{
|
||||
backgroundColor: "var(--card-hover)",
|
||||
borderColor: "color-mix(in srgb, var(--accent-color) 70%, transparent)",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -10,14 +10,30 @@ interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const variantClasses = {
|
||||
hp: {
|
||||
high: "from-green-600 to-green-700",
|
||||
medium: "from-yellow-600 to-orange-700",
|
||||
low: "from-red-700 to-red-900",
|
||||
},
|
||||
xp: "from-pixel-gold/80 via-pixel-gold/70 to-pixel-gold/80",
|
||||
default: "from-pixel-gold/80 via-pixel-gold/70 to-pixel-gold/80",
|
||||
const getGradientStyle = (variant: "hp" | "xp" | "default", percentage: number) => {
|
||||
if (variant === "hp") {
|
||||
if (percentage > 60) {
|
||||
return {
|
||||
background: `linear-gradient(to right, var(--success), color-mix(in srgb, var(--success) 90%, transparent))`,
|
||||
};
|
||||
} else if (percentage > 30) {
|
||||
return {
|
||||
background: `linear-gradient(to right, var(--yellow), color-mix(in srgb, var(--accent) 90%, transparent))`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
background: `linear-gradient(to right, var(--destructive), color-mix(in srgb, var(--destructive) 90%, transparent))`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
background: `linear-gradient(to right,
|
||||
color-mix(in srgb, var(--accent-color) 80%, transparent),
|
||||
color-mix(in srgb, var(--accent-color) 70%, transparent),
|
||||
color-mix(in srgb, var(--accent-color) 80%, transparent)
|
||||
)`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default function ProgressBar({
|
||||
@@ -31,40 +47,42 @@ export default function ProgressBar({
|
||||
}: ProgressBarProps) {
|
||||
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
||||
|
||||
let gradientClass = "";
|
||||
if (variant === "hp") {
|
||||
if (percentage > 60) {
|
||||
gradientClass = variantClasses.hp.high;
|
||||
} else if (percentage > 30) {
|
||||
gradientClass = variantClasses.hp.medium;
|
||||
} else {
|
||||
gradientClass = variantClasses.hp.low;
|
||||
}
|
||||
} else if (variant === "xp") {
|
||||
gradientClass = variantClasses.xp;
|
||||
} else {
|
||||
gradientClass = variantClasses.default;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} {...props}>
|
||||
{showLabel && (
|
||||
<div className="flex justify-between text-xs text-gray-400 mb-1">
|
||||
<div className="flex justify-between text-xs mb-1" style={{ color: "var(--gray-400)" }}>
|
||||
<span>{label || variant.toUpperCase()}</span>
|
||||
<span>
|
||||
{value} / {max}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative h-2 sm:h-3 bg-gray-900 border border-gray-700 rounded overflow-hidden">
|
||||
<div
|
||||
className="relative h-2 sm:h-3 border rounded overflow-hidden"
|
||||
style={{
|
||||
backgroundColor: "var(--gray-900)",
|
||||
borderColor: "var(--gray-700)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`absolute inset-0 bg-gradient-to-r ${gradientClass} transition-all duration-1000 ease-out`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
className="absolute inset-0 transition-all duration-1000 ease-out"
|
||||
style={{
|
||||
width: `${percentage}%`,
|
||||
...getGradientStyle(variant, percentage),
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: "linear-gradient(to right, transparent, color-mix(in srgb, var(--foreground) 10%, transparent), transparent)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{variant === "hp" && percentage < 30 && (
|
||||
<div className="absolute inset-0 border border-red-500 rounded animate-pulse"></div>
|
||||
<div
|
||||
className="absolute inset-0 border rounded animate-pulse"
|
||||
style={{ borderColor: "var(--destructive)" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,21 +28,28 @@ export default function SectionTitle({
|
||||
|
||||
let titleClasses = `${baseClasses} ${sizeClasses[size]} ${className}`;
|
||||
|
||||
const titleStyles: React.CSSProperties & {
|
||||
WebkitBackgroundClip?: string;
|
||||
WebkitTextFillColor?: string;
|
||||
} = {};
|
||||
if (variant === "gradient") {
|
||||
titleClasses += " bg-gradient-to-r from-pixel-gold via-orange-400 to-pixel-gold bg-clip-text text-transparent";
|
||||
titleClasses += " bg-clip-text text-transparent";
|
||||
titleStyles.background = `linear-gradient(to right, var(--accent-color), var(--accent), var(--accent-color))`;
|
||||
titleStyles.WebkitBackgroundClip = "text";
|
||||
titleStyles.WebkitTextFillColor = "transparent";
|
||||
} else if (variant === "gold") {
|
||||
titleClasses += " text-pixel-gold";
|
||||
titleStyles.color = "var(--accent-color)";
|
||||
} else {
|
||||
titleClasses += " text-white";
|
||||
titleStyles.color = "var(--foreground)";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<h1 className={titleClasses} {...props}>
|
||||
<h1 className={titleClasses} style={titleStyles} {...props}>
|
||||
{variant === "gradient" ? (
|
||||
<span
|
||||
style={{
|
||||
textShadow: "0 0 30px rgba(218, 165, 32, 0.5)",
|
||||
textShadow: `0 0 30px color-mix(in srgb, var(--accent-color) 50%, transparent)`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -52,7 +59,10 @@ export default function SectionTitle({
|
||||
)}
|
||||
</h1>
|
||||
{subtitle && (
|
||||
<div className="text-pixel-gold text-lg md:text-xl font-gaming-subtitle font-semibold flex items-center justify-center gap-2 tracking-wide">
|
||||
<div
|
||||
className="text-lg md:text-xl font-gaming-subtitle font-semibold flex items-center justify-center gap-2 tracking-wide"
|
||||
style={{ color: "var(--accent-color)" }}
|
||||
>
|
||||
<span>✦</span>
|
||||
<span>{subtitle}</span>
|
||||
<span>✦</span>
|
||||
@@ -61,4 +71,3 @@ export default function SectionTitle({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,20 @@ export default function StarRating({
|
||||
disabled={disabled}
|
||||
onMouseEnter={() => !disabled && setHoverValue(star)}
|
||||
onMouseLeave={() => !disabled && setHoverValue(0)}
|
||||
className={`transition-transform hover:scale-110 disabled:hover:scale-100 disabled:cursor-not-allowed ${
|
||||
star <= displayValue
|
||||
? "text-pixel-gold"
|
||||
: "text-gray-600 hover:text-gray-500"
|
||||
} ${sizeClasses[size]}`}
|
||||
className={`transition-transform hover:scale-110 disabled:hover:scale-100 disabled:cursor-not-allowed ${sizeClasses[size]}`}
|
||||
style={{
|
||||
color: star <= displayValue ? "var(--accent-color)" : "var(--gray-500)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!disabled && star > displayValue) {
|
||||
e.currentTarget.style.color = "var(--gray-400)";
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!disabled && star > displayValue) {
|
||||
e.currentTarget.style.color = "var(--gray-500)";
|
||||
}
|
||||
}}
|
||||
aria-label={`Noter ${star} étoile${star > 1 ? "s" : ""}`}
|
||||
>
|
||||
★
|
||||
|
||||
@@ -27,7 +27,18 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
ref={ref}
|
||||
maxLength={maxLength}
|
||||
value={value}
|
||||
className={`w-full px-4 py-3 bg-black/60 border border-pixel-gold/30 rounded text-white placeholder-gray-500 focus:outline-none focus:border-pixel-gold transition resize-none ${className}`}
|
||||
className={`w-full px-4 py-3 border rounded focus:outline-none transition resize-none ${className}`}
|
||||
style={{
|
||||
backgroundColor: "var(--input)",
|
||||
borderColor: "var(--border)",
|
||||
color: "var(--foreground)",
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = "var(--accent-color)";
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = "var(--border)";
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
{showCharCount && maxLength && (
|
||||
|
||||
31
components/ui/ThemeToggle.tsx
Normal file
31
components/ui/ThemeToggle.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
export default function ThemeToggle() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="px-3 py-1 border rounded text-xs uppercase tracking-widest transition"
|
||||
style={{
|
||||
borderColor: "var(--border)",
|
||||
backgroundColor: "var(--card)",
|
||||
color: "var(--foreground)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = "var(--accent-color)";
|
||||
e.currentTarget.style.backgroundColor = "color-mix(in srgb, var(--accent-color) 10%, transparent)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = "var(--border)";
|
||||
e.currentTarget.style.backgroundColor = "var(--card)";
|
||||
}}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{theme === "dark" ? "🌙" : "💎"} {theme === "dark" ? "Gold" : "Cyan"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ export { default as SectionTitle } from "./SectionTitle";
|
||||
export { default as BackgroundSection } from "./BackgroundSection";
|
||||
export { default as Alert } from "./Alert";
|
||||
export { default as CloseButton } from "./CloseButton";
|
||||
export { default as ThemeToggle } from "./ThemeToggle";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user