diff --git a/package.json b/package.json index 877a244..58d8a47 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@tailwindcss/vite": "^4.3.3", "@zitadel/react-auth": "^1.2.1", "clsx": "^2.1.1", + "lucide-react": "^1.27.0", "oidc-client-ts": "^3.5.0", "otpauth": "^9.5.1", "pocketbase": "^0.27.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f83c59c..b42454a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + lucide-react: + specifier: ^1.27.0 + version: 1.27.0(react@19.2.8) oidc-client-ts: specifier: ^3.5.0 version: 3.5.0 @@ -958,6 +961,11 @@ packages: lru-cache@5.1.1: 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: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1990,6 +1998,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@1.27.0(react@19.2.8): + dependencies: + react: 19.2.8 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index f16aac4..ff4f8ca 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,56 +1,97 @@ import React from 'react'; import { NavLink } from 'react-router-dom'; +import { Shield, ShieldCheck, HelpCircle, Lock, Settings, LogOut } from 'lucide-react'; import { cn } from '@/utils/utils'; +import { Card } from '@/components/ui/Card'; +import { NEUMORPHISM } from '@/styles/Neumorphism'; interface SidebarProps { onLogout: () => void; + className?: string; } +// Liste de tes liens réels avec leurs icônes associées const NAVIGATION_ITEMS = [ - { name: 'Trust Center', path: '/dashboard' }, - { name: 'Service Desk', path: '/support' }, - { name: 'Coffre-fort', path: '/vault' }, - { name: 'Paramètres', path: '/account' }, + { name: 'Trust Center', path: '/dashboard', icon: ShieldCheck }, + { name: 'Service Desk', path: '/support', icon: HelpCircle }, + { name: 'Coffre-fort', path: '/vault', icon: Lock }, + { name: 'Paramètres', path: '/account', icon: Settings }, ]; -export const Sidebar: React.FC = ({ onLogout }) => { + + +export const Sidebar: React.FC = ({ onLogout, className }) => { return ( - + ); }; diff --git a/src/components/ui/BackButton.tsx b/src/components/ui/BackButton.tsx index e38d897..a233618 100644 --- a/src/components/ui/BackButton.tsx +++ b/src/components/ui/BackButton.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, type ButtonProps } from '@/components/ui/Button'; +import { cn } from '@/utils/utils'; export interface BackButtonProps extends Omit { - /** 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; - /** Texte à afficher à côté de la flèche */ + /** Texte par défaut du bouton */ label?: string; - /** Callback optionnel si vous souhaitez intercepter le clic */ + /** Fonction personnalisée au clic */ onClick?: (e: React.MouseEvent) => void; } @@ -15,7 +16,7 @@ export const BackButton: React.FC = ({ to, label = "Retour", onClick, - variant = "ghost", + variant = "ghost", // Style par défaut : ultra discret sur la surface Neumorphic size = "sm", children, className, @@ -23,13 +24,14 @@ export const BackButton: React.FC = ({ }) => { const navigate = useNavigate(); + // Logique du clic (priorité : onClick perso > destination 'to' > retour historique) const handleClick = (e: React.MouseEvent) => { if (onClick) { onClick(e); } else if (to) { navigate(to); } else { - navigate(-1); // Comportement natif "Précédent" dans l'historique + navigate(-1); } }; @@ -40,7 +42,7 @@ export const BackButton: React.FC = ({ onClick={handleClick} leftIcon={ = ({ } - className={className} + className={cn("group", className)} {...props} > {children || label} diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index bd371e9..962e162 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -1,27 +1,20 @@ import React, { forwardRef } from 'react'; 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 = { - primary: "bg-blue-900 text-white hover:bg-blue-800 focus:ring-blue-900 shadow-sm border border-transparent", - secondary: "bg-slate-900 text-white hover:bg-slate-800 focus:ring-slate-900 shadow-sm border border-transparent", - danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-600 shadow-sm border border-transparent", - success: "bg-emerald-600 text-white hover:bg-emerald-700 focus:ring-emerald-600 shadow-sm border border-transparent", - outline: "bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 focus:ring-slate-900", - ghost: "bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 focus:ring-slate-900 border border-transparent", -}; - -// 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 + primary: "text-[#090909] dark:text-white font-bold", + secondary: "text-slate-500 dark:text-slate-400 font-bold", + danger: "text-rose-600 dark:text-rose-400 font-bold", + success: "text-emerald-600 dark:text-emerald-400 font-bold", + outline: "text-slate-700 dark:text-slate-200 border-slate-300 dark:border-slate-700", + ghost: "text-slate-600 dark:text-slate-400 border-transparent shadow-none dark:shadow-none", }; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: keyof typeof variantStyles; - size?: keyof typeof sizeStyles; + size?: keyof typeof NEUMORPHISM.sizeStyles; isLoading?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; @@ -43,28 +36,34 @@ export const Button = forwardRef( }, ref ) => { - const newLocal = "ml-2 flex-shrink-0"; return ( ); } diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx index 2b96f7a..881bc8d 100644 --- a/src/components/ui/Card.tsx +++ b/src/components/ui/Card.tsx @@ -1,11 +1,34 @@ import React from 'react'; import { cn } from '@/utils/utils'; +import { NEUMORPHISM } from '@/styles/Neumorphism'; -export const Card = React.forwardRef>( - ({ 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 { + // '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( + ({ className, variant = 'raised', ...props }, ref) => (
) @@ -16,7 +39,11 @@ export const CardHeader = React.forwardRef (
) @@ -25,7 +52,11 @@ CardHeader.displayName = "CardHeader"; export const CardContent = React.forwardRef>( ({ className, ...props }, ref) => ( -
+
) ); CardContent.displayName = "CardContent"; \ No newline at end of file diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx index 3602422..afc80d2 100644 --- a/src/components/ui/Input.tsx +++ b/src/components/ui/Input.tsx @@ -1,17 +1,13 @@ import React, { forwardRef, useId } from 'react'; import { cn } from '@/utils/utils'; +import { NEUMORPHISM } from '@/styles/Neumorphism'; -const sizeStyles = { - sm: "px-3 py-1.5 text-xs", - md: "px-4 py-2.5 text-sm", -}; - -export interface InputProps extends React.InputHTMLAttributes { +export interface InputProps extends Omit, 'size'> { label?: string; error?: string; - sizeVariant?: keyof typeof sizeStyles; // Gestion de la taille - leftIcon?: React.ReactNode; // Icône optionnelle intégrée à gauche - rightIcon?: React.ReactNode; // Icône optionnelle intégrée à droite + size?: keyof typeof NEUMORPHISM.sizeStyles; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; } export const Input = forwardRef( @@ -20,39 +16,63 @@ export const Input = forwardRef( label, error, id, - sizeVariant = 'md', + size = 'md', leftIcon, rightIcon, + required, + disabled, ...props }, ref) => { const autoId = useId(); const inputId = id || autoId; return ( -
+
+ {/* Label */} {label && ( -
); } diff --git a/src/styles/Neumorphism.ts b/src/styles/Neumorphism.ts new file mode 100644 index 0000000..35cf721 --- /dev/null +++ b/src/styles/Neumorphism.ts @@ -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; \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 29d3302..9d06261 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -22,6 +22,7 @@ "@assets/*": ["./src/assets/*"], "@types/*": ["./src/types/*"], "@widgets/*": ["./src/components/widgets/*"], + "@styles/*": ["./src/styles/*"], }, /* Bundler mode */ diff --git a/vite.config.ts b/vite.config.ts index dc844bf..9055c80 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ '@assets': path.resolve(__dirname, './src/assets'), '@data': path.resolve(__dirname, './src/data'), '@widgets': path.resolve(__dirname, './src/components/widgets'), + '@styles': path.resolve(__dirname, './src/styles'), } } })