Refact/cleanup #4

Merged
maxime.daniels merged 13 commits from refact/cleanup into master 2026-08-03 12:56:36 +02:00
10 changed files with 226 additions and 92 deletions
Showing only changes of commit 7be430ce1a - Show all commits
+1
View File
@@ -13,6 +13,7 @@
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
"@zitadel/react-auth": "^1.2.1", "@zitadel/react-auth": "^1.2.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^1.27.0",
"oidc-client-ts": "^3.5.0", "oidc-client-ts": "^3.5.0",
"otpauth": "^9.5.1", "otpauth": "^9.5.1",
"pocketbase": "^0.27.0", "pocketbase": "^0.27.0",
+12
View File
@@ -17,6 +17,9 @@ importers:
clsx: clsx:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
lucide-react:
specifier: ^1.27.0
version: 1.27.0(react@19.2.8)
oidc-client-ts: oidc-client-ts:
specifier: ^3.5.0 specifier: ^3.5.0
version: 3.5.0 version: 3.5.0
@@ -958,6 +961,11 @@ packages:
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@1.27.0:
resolution: {integrity: sha512-rJicGl/3Fly/E0rOH1YmPZ6e49JCnKknh1ox1vpHnkfjujAkKA6sqUZvH3MTAaXXjgexyUwgNwTJzTtYuAFYJw==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
magic-string@0.30.21: magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -1990,6 +1998,10 @@ snapshots:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
lucide-react@1.27.0(react@19.2.8):
dependencies:
react: 19.2.8
magic-string@0.30.21: magic-string@0.30.21:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
+73 -32
View File
@@ -1,56 +1,97 @@
import React from 'react'; import React from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { Shield, ShieldCheck, HelpCircle, Lock, Settings, LogOut } from 'lucide-react';
import { cn } from '@/utils/utils'; import { cn } from '@/utils/utils';
import { Card } from '@/components/ui/Card';
import { NEUMORPHISM } from '@/styles/Neumorphism';
interface SidebarProps { interface SidebarProps {
onLogout: () => void; onLogout: () => void;
className?: string;
} }
// Liste de tes liens réels avec leurs icônes associées
const NAVIGATION_ITEMS = [ const NAVIGATION_ITEMS = [
{ name: 'Trust Center', path: '/dashboard' }, { name: 'Trust Center', path: '/dashboard', icon: ShieldCheck },
{ name: 'Service Desk', path: '/support' }, { name: 'Service Desk', path: '/support', icon: HelpCircle },
{ name: 'Coffre-fort', path: '/vault' }, { name: 'Coffre-fort', path: '/vault', icon: Lock },
{ name: 'Paramètres', path: '/account' }, { name: 'Paramètres', path: '/account', icon: Settings },
]; ];
export const Sidebar: React.FC<SidebarProps> = ({ onLogout }) => {
export const Sidebar: React.FC<SidebarProps> = ({ onLogout, className }) => {
return ( return (
<aside className="w-64 bg-slate-900 flex flex-col shrink-0"> <Card className={cn("w-72 h-full flex flex-col p-6 shrink-0 rounded-none rounded-r-3xl", className)}>
{/* En-tête / Logo */} {/* 1. EN-TÊTE / LOGO */}
<div className="h-16 flex items-center px-6 border-b border-slate-800"> <div className="flex items-center gap-4 mb-10 px-2">
<span className="text-xl font-bold text-white tracking-widest">Aegis<span className="text-blue-500">.</span></span> {/* Petit badge logo avec effet creusé */}
<div className={cn(
"w-12 h-12 rounded-2xl flex items-center justify-center bg-[#e8e8e8] dark:bg-[#1e293b]",
"shadow-[inset_4px_4px_8px_#c5c5c5,inset_-4px_-4px_8px_#ffffff] dark:shadow-[inset_4px_4px_8px_#121926,inset_-4px_-4px_8px_#2a3850]"
)}>
<Shield className="w-6 h-6 text-blue-600 dark:text-blue-500" />
</div>
<div>
<h1 className="text-2xl font-black tracking-widest text-slate-800 dark:text-white leading-none">AEGIS</h1>
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">by GISE</span>
</div>
</div> </div>
{/* Menu de navigation URL-based */} {/* 2. NAVIGATION PRINCIPALE (Basée sur l'URL) */}
<nav className="flex-1 px-4 py-6 space-y-2"> <nav className="flex flex-col gap-4 flex-1">
{NAVIGATION_ITEMS.map((item) => ( {NAVIGATION_ITEMS.map((item) => {
<NavLink const Icon = item.icon;
key={item.path}
to={item.path} return (
className={({ isActive }) => <NavLink
cn( key={item.path}
"flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-colors duration-200", to={item.path}
isActive className={({ isActive }) =>
? "bg-blue-900/50 text-white border border-blue-800/50" cn(
: "text-slate-400 hover:bg-slate-800 hover:text-slate-200 border border-transparent" "w-full flex items-center gap-4 px-5 py-4 rounded-2xl transition-all duration-300 ease-in-out cursor-pointer",
) "bg-[#e8e8e8] dark:bg-[#1e293b]",
} isActive
> ? cn(NEUMORPHISM.insetLight, NEUMORPHISM.insetDark, "!text-blue-600 dark:!text-blue-400 font-black")
{item.name} : cn(NEUMORPHISM.lightShadow, NEUMORPHISM.darkShadow, "text-slate-500 dark:text-slate-400 font-bold hover:text-slate-700 dark:hover:text-slate-200")
</NavLink> )
))} }
>
{({ isActive }) => (
<>
<Icon
className={cn(
"w-5 h-5 transition-transform duration-300 ease-in-out shrink-0",
isActive ? "scale-110" : "scale-100"
)}
strokeWidth={isActive ? 2.5 : 2}
/>
<span className="text-sm">{item.name}</span>
</>
)}
</NavLink>
);
})}
</nav> </nav>
{/* Zone de déconnexion */} {/* 3. ZONE DE DÉCONNEXION */}
<div className="p-4 border-t border-slate-800"> <div className="pt-6 mt-6 border-t border-white/40 dark:border-slate-800/50">
<button <button
onClick={onLogout} onClick={onLogout}
className="w-full flex items-center justify-center px-4 py-2 text-sm font-medium text-slate-400 hover:text-red-400 hover:bg-slate-800 rounded-lg transition-all cursor-pointer" className={cn(
"w-full flex items-center justify-start gap-4 px-5 py-4 rounded-2xl cursor-pointer transition-all duration-300 ease-in-out",
"bg-[#e8e8e8] dark:bg-[#1e293b]",
NEUMORPHISM.lightShadow,
NEUMORPHISM.darkShadow,
"active:shadow-[inset_4px_4px_8px_#c5c5c5,inset_-4px_-4px_8px_#ffffff] dark:active:shadow-[inset_4px_4px_8px_#121926,inset_-4px_-4px_8px_#2a3850]",
"text-rose-500 dark:text-rose-400 font-bold"
)}
> >
Déconnexion <LogOut className="w-5 h-5" />
<span className="text-sm font-bold">Déconnexion</span>
</button> </button>
</div> </div>
</aside> </Card>
); );
}; };
+9 -7
View File
@@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Button, type ButtonProps } from '@/components/ui/Button'; import { Button, type ButtonProps } from '@/components/ui/Button';
import { cn } from '@/utils/utils';
export interface BackButtonProps extends Omit<ButtonProps, 'onClick'> { export interface BackButtonProps extends Omit<ButtonProps, 'onClick'> {
/** Route explicite vers laquelle naviguer (ex: "/support"). Si non spécifié, fait un retour arrière navigateur (-1) */ /** Route explicite (ex: "/support"). Si omis, fait un retour arrière dans l'historique (-1) */
to?: string; to?: string;
/** Texte à afficher à côté de la flèche */ /** Texte par défaut du bouton */
label?: string; label?: string;
/** Callback optionnel si vous souhaitez intercepter le clic */ /** Fonction personnalisée au clic */
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void; onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
} }
@@ -15,7 +16,7 @@ export const BackButton: React.FC<BackButtonProps> = ({
to, to,
label = "Retour", label = "Retour",
onClick, onClick,
variant = "ghost", variant = "ghost", // Style par défaut : ultra discret sur la surface Neumorphic
size = "sm", size = "sm",
children, children,
className, className,
@@ -23,13 +24,14 @@ export const BackButton: React.FC<BackButtonProps> = ({
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
// Logique du clic (priorité : onClick perso > destination 'to' > retour historique)
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
if (onClick) { if (onClick) {
onClick(e); onClick(e);
} else if (to) { } else if (to) {
navigate(to); navigate(to);
} else { } else {
navigate(-1); // Comportement natif "Précédent" dans l'historique navigate(-1);
} }
}; };
@@ -40,7 +42,7 @@ export const BackButton: React.FC<BackButtonProps> = ({
onClick={handleClick} onClick={handleClick}
leftIcon={ leftIcon={
<svg <svg
className="w-4 h-4 transition-transform group-hover:-translate-x-0.5" className="w-4 h-4 transition-transform duration-200 group-hover:-translate-x-1"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -48,7 +50,7 @@ export const BackButton: React.FC<BackButtonProps> = ({
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 19l-7-7 7-7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 19l-7-7 7-7" />
</svg> </svg>
} }
className={className} className={cn("group", className)}
{...props} {...props}
> >
{children || label} {children || label}
+26 -27
View File
@@ -1,27 +1,20 @@
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import { cn } from '@/utils/utils'; import { cn } from '@/utils/utils';
import { NEUMORPHISM } from '@/styles/Neumorphism';
// 1. Dictionnaire des variantes (Sémantique de couleurs GISE) // Couleurs de texte pour chaque variante
const variantStyles = { const variantStyles = {
primary: "bg-blue-900 text-white hover:bg-blue-800 focus:ring-blue-900 shadow-sm border border-transparent", primary: "text-[#090909] dark:text-white font-bold",
secondary: "bg-slate-900 text-white hover:bg-slate-800 focus:ring-slate-900 shadow-sm border border-transparent", secondary: "text-slate-500 dark:text-slate-400 font-bold",
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-600 shadow-sm border border-transparent", danger: "text-rose-600 dark:text-rose-400 font-bold",
success: "bg-emerald-600 text-white hover:bg-emerald-700 focus:ring-emerald-600 shadow-sm border border-transparent", success: "text-emerald-600 dark:text-emerald-400 font-bold",
outline: "bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 focus:ring-slate-900", outline: "text-slate-700 dark:text-slate-200 border-slate-300 dark:border-slate-700",
ghost: "bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 focus:ring-slate-900 border border-transparent", ghost: "text-slate-600 dark:text-slate-400 border-transparent shadow-none dark:shadow-none",
};
// 2. Dictionnaire des tailles
const sizeStyles = {
sm: "px-3 py-1.5 text-xs",
md: "px-4 py-2.5 text-sm",
lg: "px-6 py-3 text-base w-full sm:w-auto", // w-full sur mobile par défaut, auto sur desktop
icon: "p-2", // Format carré optimisé pour les boutons sans texte
}; };
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: keyof typeof variantStyles; variant?: keyof typeof variantStyles;
size?: keyof typeof sizeStyles; size?: keyof typeof NEUMORPHISM.sizeStyles;
isLoading?: boolean; isLoading?: boolean;
leftIcon?: React.ReactNode; leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode; rightIcon?: React.ReactNode;
@@ -43,28 +36,34 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
}, },
ref ref
) => { ) => {
const newLocal = "ml-2 flex-shrink-0";
return ( return (
<button <button
ref={ref} // Indispensable pour l'accessibilité ou des librairies externes ref={ref}
type={type} type={type}
disabled={disabled || isLoading} disabled={disabled || isLoading}
className={cn( className={cn(
// Styles de base structurels // Style de base & Neumorphism
"inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200", "inline-flex items-center justify-center cursor-pointer outline-none transition-all duration-300 ease-in-out",
"focus:outline-none active:scale-[0.98]", "bg-[#e8e8e8] dark:bg-[#1e293b]",
"disabled:opacity-60 disabled:pointer-events-none disabled:active:scale-100", "border border-[#e8e8e8] dark:border-[#1e293b]",
// Application dynamique des dictionnaires "hover:border-white dark:hover:border-[#2a3850]",
NEUMORPHISM.lightShadow,
NEUMORPHISM.lightActive,
NEUMORPHISM.darkShadow,
NEUMORPHISM.darkActive,
"disabled:opacity-50 disabled:pointer-events-none",
// Variantes & Tailles
variantStyles[variant], variantStyles[variant],
sizeStyles[size], NEUMORPHISM.sizeStyles[size],
className className
)} )}
{...props} {...props}
> >
{/* Spinner SVG optimisé pour le chargement */} {/* Spinner SVG si en chargement */}
{isLoading && ( {isLoading && (
<svg <svg
className="animate-spin -ml-1 mr-2 h-4 w-4 text-current" className="animate-spin -ml-1 mr-2 h-5 w-5 text-current"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -81,7 +80,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<span className="truncate">{children}</span> <span className="truncate">{children}</span>
{/* Icône de droite */} {/* Icône de droite */}
{rightIcon && <span className={newLocal}>{rightIcon}</span>} {rightIcon && <span className="ml-2 shrink-0">{rightIcon}</span>}
</button> </button>
); );
} }
+36 -5
View File
@@ -1,11 +1,34 @@
import React from 'react'; import React from 'react';
import { cn } from '@/utils/utils'; import { cn } from '@/utils/utils';
import { NEUMORPHISM } from '@/styles/Neumorphism';
export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( // IMPORTANT — contraste texte :
// Le fond clair (#e8e8e8) est assez lumineux. Dans le contenu de la Card,
// évite text-slate-400/500 en light mode (ratio WCAG insuffisant sur ce fond) :
// utilise plutôt text-slate-600 (ou plus foncé) pour le texte secondaire en light mode.
// Ex: className="text-slate-600 dark:text-slate-400"
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
// 'raised' : relief neumorphique complet — dashboards, widgets isolés, peu nombreux à l'écran.
// 'flat' : bordure fine, sans ombre — listes/tables denses (perf + lisibilité).
variant?: 'raised' | 'flat';
}
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
({ className, variant = 'raised', ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden", className)} className={cn(
// Style de base
"rounded-3xl border-none transition-[background-color,box-shadow] duration-300 ease-in-out flex flex-col overflow-hidden",
"bg-[#e8e8e8] dark:bg-[#1e293b]",
// Relief neumorphique (par défaut)
variant === 'raised' && [NEUMORPHISM.lightShadow, NEUMORPHISM.darkShadow],
// Variante plate : pas d'ombre, juste une bordure discrète — pour tables/listes denses
variant === 'flat' && "border border-black/5 dark:border-white/10",
className
)}
{...props} {...props}
/> />
) )
@@ -16,7 +39,11 @@ export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("px-6 py-5 border-b border-slate-100 flex flex-col space-y-1.5", className)} className={cn(
// Séparation discrète (bordure fine plutôt qu'ombre inset, pour rester léger)
"px-6 py-5 flex flex-col space-y-1.5 border-b border-black/5 dark:border-white/5",
className
)}
{...props} {...props}
/> />
) )
@@ -25,7 +52,11 @@ CardHeader.displayName = "CardHeader";
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 flex-1", className)} {...props} /> <div
ref={ref}
className={cn("p-6 flex-1", className)}
{...props}
/>
) )
); );
CardContent.displayName = "CardContent"; CardContent.displayName = "CardContent";
+44 -21
View File
@@ -1,17 +1,13 @@
import React, { forwardRef, useId } from 'react'; import React, { forwardRef, useId } from 'react';
import { cn } from '@/utils/utils'; import { cn } from '@/utils/utils';
import { NEUMORPHISM } from '@/styles/Neumorphism';
const sizeStyles = { export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
sm: "px-3 py-1.5 text-xs",
md: "px-4 py-2.5 text-sm",
};
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string; label?: string;
error?: string; error?: string;
sizeVariant?: keyof typeof sizeStyles; // Gestion de la taille size?: keyof typeof NEUMORPHISM.sizeStyles;
leftIcon?: React.ReactNode; // Icône optionnelle intégrée à gauche leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode; // Icône optionnelle intégrée à droite rightIcon?: React.ReactNode;
} }
export const Input = forwardRef<HTMLInputElement, InputProps>( export const Input = forwardRef<HTMLInputElement, InputProps>(
@@ -20,39 +16,63 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
label, label,
error, error,
id, id,
sizeVariant = 'md', size = 'md',
leftIcon, leftIcon,
rightIcon, rightIcon,
required,
disabled,
...props ...props
}, ref) => { }, ref) => {
const autoId = useId(); const autoId = useId();
const inputId = id || autoId; const inputId = id || autoId;
return ( return (
<div className="w-full"> <div className="w-full flex flex-col gap-1.5">
{/* Label */}
{label && ( {label && (
<label htmlFor={inputId} className="block text-sm font-medium text-slate-700 mb-1.5"> <label
{label} {props.required && <span className="text-blue-900 font-bold">*</span>} htmlFor={inputId}
className="block text-sm font-bold text-slate-700 dark:text-slate-300"
>
{label} {required && <span className="text-rose-500 font-bold">*</span>}
</label> </label>
)} )}
<div className="relative flex items-center w-full"> <div className="relative flex items-center w-full">
{/* Icône à gauche */} {/* Icône à gauche */}
{leftIcon && ( {leftIcon && (
<span className="absolute left-3 text-slate-400 pointer-events-none flex items-center"> <span className="absolute left-4 text-slate-400 dark:text-slate-500 pointer-events-none flex items-center justify-center">
{leftIcon} {leftIcon}
</span> </span>
)} )}
{/* Champ HTML avec les ombres creusées centralisées */}
<input <input
id={inputId} id={inputId}
ref={ref} ref={ref}
disabled={disabled}
className={cn( className={cn(
"flex w-full rounded-lg border border-slate-300 bg-white text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-1 focus:ring-blue-900 focus:border-blue-900 disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-500 shadow-sm transition-colors", // Base & Couleurs de fond
sizeStyles[sizeVariant], "w-full transition-all duration-300 ease-in-out outline-none border border-transparent",
leftIcon && "pl-9", // Décale le texte à droite si icône gauche "bg-[#e8e8e8] dark:bg-[#1e293b]",
rightIcon && "pr-9", // Décale le texte à gauche si icône droite "text-slate-800 dark:text-slate-100 font-medium placeholder:text-slate-400 dark:placeholder:text-slate-500",
error && "border-red-500 focus:ring-red-500 focus:border-red-500",
// 🎯 Utilisation directe de tes constantes Neumorphic creusées
NEUMORPHISM.insetLight,
NEUMORPHISM.insetDark,
// Focus & État désactivé
"focus:border-blue-500/40 dark:focus:border-blue-400/40",
"disabled:opacity-50 disabled:cursor-not-allowed",
// Tailles & Marges d'icônes
NEUMORPHISM.sizeStyles[size],
leftIcon && "pl-11",
rightIcon && "pr-11",
// Style en cas d'erreur
error && "border-rose-500 focus:border-rose-500 text-rose-600 dark:text-rose-400",
className className
)} )}
{...props} {...props}
@@ -60,13 +80,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
{/* Icône à droite */} {/* Icône à droite */}
{rightIcon && ( {rightIcon && (
<span className="absolute right-3 text-slate-400 pointer-events-none flex items-center"> <span className="absolute right-4 text-slate-400 dark:text-slate-500 pointer-events-none flex items-center justify-center">
{rightIcon} {rightIcon}
</span> </span>
)} )}
</div> </div>
{error && <p className="mt-1.5 text-xs font-medium text-red-500">{error}</p>} {/* Message d'erreur */}
{error && (
<p className="text-xs font-semibold text-rose-500 dark:text-rose-400">{error}</p>
)}
</div> </div>
); );
} }
+23
View File
@@ -0,0 +1,23 @@
// Styles neumorphiques réutilisables.
export const NEUMORPHISM = {
// Reliefs au repos
lightShadow: "shadow-[6px_6px_12px_#c5c5c5,-6px_-6px_12px_#ffffff]",
darkShadow: "dark:shadow-[6px_6px_12px_#121926,-6px_-6px_12px_#2a3850]",
// Ombres Neumorphic quand on clique (enfoncé)
lightActive: "active:shadow-[4px_4px_12px_#c5c5c5,-4px_-4px_12px_#ffffff]",
darkActive: "dark:active:shadow-[4px_4px_12px_#121926,-4px_-4px_12px_#2a3850]",
sizeStyles: {
sm: "px-5 py-2 text-sm rounded-md",
md: "px-[1.7em] py-[0.7em] text-[18px] rounded-[0.5em]",
lg: "px-10 py-4 text-xl rounded-xl",
icon: "p-3 rounded-lg",
},
// Ombres du bouton ACTIF (effet enfoncé/creusé "inset")
insetLight: "!shadow-[inset_4px_4px_8px_#c5c5c5,inset_-4px_-4px_8px_#ffffff]",
insetDark: "dark:!shadow-[inset_4px_4px_8px_#121926,inset_-4px_-4px_8px_#2a3850]",
} as const;
+1
View File
@@ -22,6 +22,7 @@
"@assets/*": ["./src/assets/*"], "@assets/*": ["./src/assets/*"],
"@types/*": ["./src/types/*"], "@types/*": ["./src/types/*"],
"@widgets/*": ["./src/components/widgets/*"], "@widgets/*": ["./src/components/widgets/*"],
"@styles/*": ["./src/styles/*"],
}, },
/* Bundler mode */ /* Bundler mode */
+1
View File
@@ -24,6 +24,7 @@ export default defineConfig({
'@assets': path.resolve(__dirname, './src/assets'), '@assets': path.resolve(__dirname, './src/assets'),
'@data': path.resolve(__dirname, './src/data'), '@data': path.resolve(__dirname, './src/data'),
'@widgets': path.resolve(__dirname, './src/components/widgets'), '@widgets': path.resolve(__dirname, './src/components/widgets'),
'@styles': path.resolve(__dirname, './src/styles'),
} }
} }
}) })