Feat/neumorphism/widgets #3

Merged
paradox merged 2 commits from feat/neumorphism/widgets into test 2026-07-31 12:21:12 +02:00
18 changed files with 253 additions and 118 deletions
+1
View File
@@ -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",
+12
View File
@@ -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
+63 -22
View File
@@ -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<SidebarProps> = ({ onLogout }) => {
export const Sidebar: React.FC<SidebarProps> = ({ onLogout, className }) => {
return (
<aside className="w-64 bg-slate-900 flex flex-col shrink-0">
{/* En-tête / Logo */}
<div className="h-16 flex items-center px-6 border-b border-slate-800">
<span className="text-xl font-bold text-white tracking-widest">Aegis<span className="text-blue-500">.</span></span>
<Card className={cn("w-60 flex flex-col p-6 shrink-0", className)}>
{/* 1. EN-TÊTE / LOGO */}
<div className="flex items-center gap-4 mb-10 px-2">
{/* 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>
{/* Menu de navigation URL-based */}
<nav className="flex-1 px-4 py-6 space-y-2">
{NAVIGATION_ITEMS.map((item) => (
{/* 2. NAVIGATION PRINCIPALE (Basée sur l'URL) */}
<nav className="flex flex-col gap-4 flex-1">
{NAVIGATION_ITEMS.map((item) => {
const Icon = item.icon;
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
cn(
"flex items-center px-4 py-3 text-sm font-medium rounded-lg transition-colors duration-200",
"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
? "bg-blue-900/50 text-white border border-blue-800/50"
: "text-slate-400 hover:bg-slate-800 hover:text-slate-200 border border-transparent"
? cn(NEUMORPHISM.insetLight, NEUMORPHISM.insetDark, "!text-blue-600 dark:!text-blue-400 font-black")
: cn(NEUMORPHISM.lightShadow, NEUMORPHISM.darkShadow, "text-slate-500 dark:text-slate-400 font-bold hover:text-slate-700 dark:hover:text-slate-200")
)
}
>
{item.name}
{({ 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>
{/* Zone de déconnexion */}
<div className="p-4 border-t border-slate-800">
{/* 3. ZONE DE DÉCONNEXION */}
<div className="pt-6 mt-6 border-t border-white/40 dark:border-slate-800/50">
<button
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>
</div>
</aside>
</Card>
);
};
+9 -7
View File
@@ -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<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;
/** 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<HTMLButtonElement>) => void;
}
@@ -15,7 +16,7 @@ export const BackButton: React.FC<BackButtonProps> = ({
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<BackButtonProps> = ({
}) => {
const navigate = useNavigate();
// Logique du clic (priorité : onClick perso > destination 'to' > retour historique)
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
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<BackButtonProps> = ({
onClick={handleClick}
leftIcon={
<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"
stroke="currentColor"
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" />
</svg>
}
className={className}
className={cn("group", className)}
{...props}
>
{children || label}
+26 -27
View File
@@ -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<HTMLButtonElement> {
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<HTMLButtonElement, ButtonProps>(
},
ref
) => {
const newLocal = "ml-2 flex-shrink-0";
return (
<button
ref={ref} // Indispensable pour l'accessibilité ou des librairies externes
ref={ref}
type={type}
disabled={disabled || isLoading}
className={cn(
// Styles de base structurels
"inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200",
"focus:outline-none active:scale-[0.98]",
"disabled:opacity-60 disabled:pointer-events-none disabled:active:scale-100",
// Application dynamique des dictionnaires
// Style de base & Neumorphism
"inline-flex items-center justify-center cursor-pointer outline-none transition-all duration-300 ease-in-out",
"bg-[#e8e8e8] dark:bg-[#1e293b]",
"border border-[#e8e8e8] dark:border-[#1e293b]",
"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],
sizeStyles[size],
NEUMORPHISM.sizeStyles[size],
className
)}
{...props}
>
{/* Spinner SVG optimisé pour le chargement */}
{/* Spinner SVG si en chargement */}
{isLoading && (
<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"
fill="none"
viewBox="0 0 24 24"
@@ -81,7 +80,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<span className="truncate">{children}</span>
{/* Icône de droite */}
{rightIcon && <span className={newLocal}>{rightIcon}</span>}
{rightIcon && <span className="ml-2 shrink-0">{rightIcon}</span>}
</button>
);
}
+37 -5
View File
@@ -1,11 +1,35 @@
import React from 'react';
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'|'digged';
}
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
({ className, variant = 'raised', ...props }, ref) => (
<div
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],
variant === 'digged' && [NEUMORPHISM.insetDark, NEUMORPHISM.insetLight],
// 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}
/>
)
@@ -16,7 +40,11 @@ export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<
({ className, ...props }, ref) => (
<div
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}
/>
)
@@ -25,7 +53,11 @@ CardHeader.displayName = "CardHeader";
export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ 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";
+44 -21
View File
@@ -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<HTMLInputElement> {
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, '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<HTMLInputElement, InputProps>(
@@ -20,39 +16,63 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
label,
error,
id,
sizeVariant = 'md',
size = 'md',
leftIcon,
rightIcon,
required,
disabled,
...props
}, ref) => {
const autoId = useId();
const inputId = id || autoId;
return (
<div className="w-full">
<div className="w-full flex flex-col gap-1.5">
{/* Label */}
{label && (
<label htmlFor={inputId} className="block text-sm font-medium text-slate-700 mb-1.5">
{label} {props.required && <span className="text-blue-900 font-bold">*</span>}
<label
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>
)}
<div className="relative flex items-center w-full">
{/* Icône à gauche */}
{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}
</span>
)}
{/* Champ HTML avec les ombres creusées centralisées */}
<input
id={inputId}
ref={ref}
disabled={disabled}
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",
sizeStyles[sizeVariant],
leftIcon && "pl-9", // Décale le texte à droite si icône gauche
rightIcon && "pr-9", // Décale le texte à gauche si icône droite
error && "border-red-500 focus:ring-red-500 focus:border-red-500",
// Base & Couleurs de fond
"w-full transition-all duration-300 ease-in-out outline-none border border-transparent",
"bg-[#e8e8e8] dark:bg-[#1e293b]",
"text-slate-800 dark:text-slate-100 font-medium placeholder:text-slate-400 dark:placeholder:text-slate-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
)}
{...props}
@@ -60,13 +80,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
{/* Icône à droite */}
{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}
</span>
)}
</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>
);
}
+1 -1
View File
@@ -10,7 +10,7 @@ interface PageHeaderProps {
export const PageHeader: React.FC<PageHeaderProps> = ({ title, description, children }) => {
return (
<Card className="border-slate-200 shadow-sm">
<Card>
<CardContent className="p-6 sm:px-8 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">
+7 -7
View File
@@ -1,6 +1,7 @@
import React from 'react';
import type { ManagedContract } from '@/types/ManagedContract';
import { StatusBadge } from '@/components/ui/StatusBadge';
import { Card } from './Card';
interface ServiceObjectProps {
contract: ManagedContract;
@@ -8,16 +9,15 @@ interface ServiceObjectProps {
export const ServiceObject: React.FC<ServiceObjectProps> = ({ contract }) => {
return (
<div className="p-4 bg-slate-50 rounded-lg border border-slate-100 flex justify-between items-center">
<div>
<Card variant= 'digged' className="p-4 rounded-lg border border-slate-100 flex flex-col">
<div className='flex flex-row justify-between'>
<h3 className="text-sm font-medium text-slate-900">{contract.service_name}</h3>
{contract.description && (
<p className="text-xs text-slate-500 mt-1">{contract.description}</p>
)}
</div>
{/* Fonctionne instantanément avec 'Actif', 'En déploiement', 'Suspendu' */}
<StatusBadge status={contract.status} />
</div>
{contract.description && (
<p className="text-xs text-slate-500 mt-1">{contract.description}</p>
)}
</Card>
);
};
+1 -1
View File
@@ -55,7 +55,7 @@ export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, className }) =
return (
<div
className={cn(
"px-2.5 py-1 rounded-full text-xs font-semibold inline-flex items-center border transition-colors",
"px-2.5 py-1 rounded-full text-xs font-semibold inline-flex items-center border transition-colors min-w-fit max-h-fit",
config.bg,
className
)}
@@ -42,7 +42,7 @@ export const EvolutionFilterBar: React.FC<EvolutionFilterBarProps> = ({
<div className="relative min-w-60">
<Input
type="text"
sizeVariant="sm" // Format compact pour la barre de recherche
// Format compact pour la barre de recherche
placeholder="Rechercher une initiative..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
+1 -1
View File
@@ -19,7 +19,7 @@ export default function ServicesWidget() {
<p className="text-sm text-slate-500">Catalogue des contrats d'infogérance actifs</p>
</div>
<div className="flex-1 overflow-y-auto mb-6 space-y-3">
<div className="flex-1 mb-6 space-y-3">
{error && <EmptyState message={error} className="text-red-500" />}
{isLoading && !error && <Loader />}
{!isLoading && !error && contracts.length === 0 && (
+3 -3
View File
@@ -7,15 +7,15 @@ export const SupportCtaWidget: React.FC = () => {
const navigate = useNavigate();
return (
<Card className="bg-blue-900 border-blue-800 text-white shadow-sm">
<Card>
<CardContent className="p-6">
<h3 className="text-lg font-semibold mb-2">Centre de Support</h3>
<p className="text-blue-200 text-sm mb-4 leading-relaxed">
<p className="text-blue-900 text-sm mb-4 leading-relaxed">
Déclarez un incident critique ou demandez une évolution de votre infrastructure.
</p>
<Button
onClick={() => navigate('/support')}
className="w-full bg-white text-blue-900 hover:bg-slate-50 font-medium py-2.5 px-4 rounded-lg transition-colors shadow-sm"
className="w-full text-blue-900"
>
Ouvrir un ticket sécurisé
</Button>
+3 -3
View File
@@ -8,13 +8,13 @@ interface DashboardLayoutProps {
export default function DashboardLayout({ children, onLogout }: DashboardLayoutProps) {
return (
<div className="flex h-screen bg-slate-50 overflow-hidden">
<div className="h-screen w-full flex bg-[#e8e8e8] dark:bg-[#1e293b] text-slate-800 dark:text-slate-100 font-sans transition-all duration-300 ease-in-out">
{/* Intégration de la navbar extraite */}
<Sidebar onLogout={onLogout} />
<Sidebar onLogout={onLogout} className='my-4 ml-4'/>
{/* CONTENU PRINCIPAL */}
<main className="flex-1 overflow-y-auto scrollbar-hide">
<div className="p-8">
<div>
{children}
</div>
</main>
+5 -5
View File
@@ -10,8 +10,8 @@ export default function TrustCenter() {
const navigate = useNavigate();
return (
<div className="bg-slate-50 font-sans text-slate-900 relative min-h-[calc(100vh-4rem)]">
<main className="max-w-7xl mx-auto px-4 sm:px-8 py-8 space-y-6">
<div className="font-sans relative min-h-[calc(100vh-4rem)]">
<main className="max-w-7xl mx-auto px-8 sm:px-8 space-y-6 m-4">
{/* HEADER EXTRAIT */}
<PageHeader
@@ -31,8 +31,8 @@ export default function TrustCenter() {
</PageHeader>
{/* GRILLE DES WIDGETS */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6">
<div className="lg:col-span-3 space-y-6">
<UptimeWidget />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<BackupWidget onClick={() => navigate('/backups')} />
@@ -40,7 +40,7 @@ export default function TrustCenter() {
</div>
</div>
<div className="space-y-6">
<div className="space-y-6 lg:col-span-2">
<ServicesWidget />
<SupportCtaWidget />
</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/*"],
"@types/*": ["./src/types/*"],
"@widgets/*": ["./src/components/widgets/*"],
"@styles/*": ["./src/styles/*"],
},
/* Bundler mode */
+1
View File
@@ -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'),
}
}
})