refactor(style): apply new design

This commit is contained in:
2026-04-22 11:30:33 -04:00
parent 4d84669f9f
commit 138183f3a8
13 changed files with 74 additions and 48 deletions
+30 -4
View File
@@ -3,11 +3,12 @@
import { Fragment } from 'react';
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react';
import { ChevronDownIcon } from '@zen/core/shared/icons';
import { useRouter } from 'next/navigation';
import { useRouter, usePathname } from 'next/navigation';
import { useTheme, getThemeIcon } from '@zen/core/themes';
const AdminHeader = ({ isMobileMenuOpen, setIsMobileMenuOpen, user, onLogout, appName = 'ZEN' }) => {
const AdminHeader = ({ isMobileMenuOpen, setIsMobileMenuOpen, user, onLogout, appName = 'ZEN', navigationSections = [] }) => {
const router = useRouter();
const pathname = usePathname();
const getImageUrl = (imageKey) => {
if (!imageKey) return null;
@@ -47,14 +48,28 @@ const AdminHeader = ({ isMobileMenuOpen, setIsMobileMenuOpen, user, onLogout, ap
const ThemeIcon = getThemeIcon(theme, systemIsDark);
const themeLabel = theme === 'light' ? 'Mode clair' : theme === 'dark' ? 'Mode sombre' : 'Thème système';
const currentPageName = (() => {
for (const section of navigationSections) {
for (const item of section.items) {
if (pathname === item.href || pathname.startsWith(item.href + '/')) {
return item.name;
}
}
if (section.items.length === 1 && (pathname === section.items[0].href || pathname.startsWith(section.items[0].href + '/'))) {
return section.title;
}
}
return null;
})();
const quickLinks = [];
const imageUrl = getImageUrl(user?.image);
const userInitials = getUserInitials(user?.name);
return (
<header className="bg-white dark:bg-black border-b border-neutral-200 dark:border-neutral-800/70 sticky top-0 z-30 h-12 flex items-center w-full">
<div className="flex items-center justify-between lg:justify-end px-4 lg:px-6 py-2 w-full">
{/* Left section — Mobile menu button + Logo */}
<div className="flex items-center justify-between px-4 lg:px-6 py-2 w-full">
{/* Left section — Mobile menu button + Logo / Desktop breadcrumb */}
<div className="flex items-center space-x-3 lg:hidden">
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
@@ -68,6 +83,17 @@ const AdminHeader = ({ isMobileMenuOpen, setIsMobileMenuOpen, user, onLogout, ap
<h1 className="text-neutral-900 dark:text-white font-semibold text-lg">{appName}</h1>
</div>
{/* Desktop breadcrumb */}
{currentPageName && (
<div className="hidden lg:flex items-center gap-1.5 text-[13px]">
<span className="text-neutral-500 dark:text-neutral-400">{appName}</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-neutral-400 dark:text-neutral-600 flex-shrink-0">
<polyline points="9 18 15 12 9 6" />
</svg>
<span className="text-neutral-900 dark:text-white font-medium">{currentPageName}</span>
</div>
)}
{/* Right section — Quick links + Profile */}
<div className="flex items-center gap-3 sm:gap-4">
<nav className="hidden sm:flex items-center gap-4 lg:gap-6">
@@ -17,7 +17,7 @@ export default function AdminPagesLayout({ children, user, onLogout, appName, en
navigationSections={navigationSections}
/>
<div className="flex-1 flex flex-col min-w-0">
<AdminHeader isMobileMenuOpen={isMobileMenuOpen} setIsMobileMenuOpen={setIsMobileMenuOpen} user={user} onLogout={onLogout} appName={appName} />
<AdminHeader isMobileMenuOpen={isMobileMenuOpen} setIsMobileMenuOpen={setIsMobileMenuOpen} user={user} onLogout={onLogout} appName={appName} navigationSections={navigationSections} />
<main className="flex-1 overflow-y-auto bg-neutral-50 dark:bg-black">
<div className="px-8 py-7 pb-32 max-w-[1400px] mx-auto">
{children}
@@ -201,7 +201,7 @@ const AdminSidebar = ({ isMobileMenuOpen, setIsMobileMenuOpen, appName, enabledM
{/* Logo */}
<Link href="/admin" className="px-4 h-12 flex items-center justify-start gap-2 border-b border-neutral-200 dark:border-neutral-800/70">
<h1 className="text-neutral-900 dark:text-white font-semibold">{appName}</h1>
<span className="bg-red-700/10 border border-red-600/20 text-red-600 uppercase text-[10px] leading-none px-2 py-1 rounded-lg font-semibold">
<span className="bg-red-700/10 border border-red-600/20 text-red-600 uppercase text-[10px] leading-none px-2 py-1 rounded font-semibold">
Admin
</span>
</Link>
@@ -85,10 +85,10 @@ export default function LoginPage({ onSubmit, onNavigate, onSetSessionCookie, re
}
};
const inputClasses = 'w-full px-3 py-2.5 rounded-xl text-sm focus:outline-none transition-all duration-[120ms] ease-out disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900 dark:border-neutral-700/50 dark:text-white dark:placeholder-neutral-500 dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20';
const inputClasses = 'w-full px-[10px] py-[7px] rounded text-[13px] focus:outline-none transition-all duration-[120ms] ease-out disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900 dark:border-neutral-700/50 dark:text-white dark:placeholder-neutral-500 dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20';
return (
<div className="bg-white dark:bg-neutral-900/40 border border-neutral-200 dark:border-neutral-800/50 rounded-xl px-4 py-6 md:px-6 md:py-8 w-full max-w-md">
<div className="bg-white dark:bg-neutral-900/40 border border-neutral-200 dark:border-neutral-800/50 rounded-md px-4 py-6 md:px-6 md:py-8 w-full max-w-md">
{/* Header */}
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white mb-2">
@@ -190,7 +190,7 @@ export default function LoginPage({ onSubmit, onNavigate, onSetSessionCookie, re
type="button"
onClick={handleSubmit}
disabled={isLoading || success || currentUser}
className="cursor-pointer w-full bg-neutral-900 text-white mt-2 py-2.5 px-4 rounded-xl text-sm font-medium hover:bg-neutral-800 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-[120ms] ease-out focus:outline-none focus:ring-1 focus:ring-neutral-900/20 dark:bg-white dark:text-black dark:hover:bg-neutral-100 dark:focus:ring-white/20"
className="cursor-pointer w-full bg-neutral-900 text-white mt-2 py-[7px] px-4 rounded text-[13px] font-medium hover:bg-neutral-800 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-[120ms] ease-out focus:outline-none focus:ring-1 focus:ring-neutral-900/20 dark:bg-white dark:text-black dark:hover:bg-neutral-100 dark:focus:ring-white/20"
>
{isLoading ? (
<div className="flex items-center justify-center space-x-2">
+3 -3
View File
@@ -24,9 +24,9 @@ const Badge = ({
};
const sizes = {
sm: 'px-2 py-0.5 rounded-lg text-[11px]',
md: 'px-2.5 py-0.5 rounded-lg text-[11px]',
lg: 'px-3 py-1 rounded-lg text-xs'
sm: 'px-2 py-0.5 rounded text-[12px]',
md: 'px-2.5 py-0.5 rounded text-[12px]',
lg: 'px-3 py-1 rounded text-xs'
};
return (
+6 -6
View File
@@ -15,20 +15,20 @@ const Button = ({
className = '',
...props
}) => {
const baseClassName = 'cursor-pointer inline-flex items-center justify-center font-medium rounded-xl transition-all duration-[120ms] ease-out focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed';
const baseClassName = 'cursor-pointer inline-flex items-center justify-center font-medium rounded transition-all duration-[120ms] ease-out focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-neutral-900 text-white hover:bg-neutral-800 focus:ring-neutral-900/20 dark:bg-white dark:text-black dark:hover:bg-neutral-100 dark:focus:ring-white/20',
secondary: 'bg-transparent border border-neutral-300 text-neutral-700 hover:bg-neutral-100 focus:ring-neutral-500/20 dark:bg-neutral-800/60 dark:border-neutral-700/50 dark:text-white dark:hover:bg-neutral-800/80 dark:focus:ring-neutral-600/20 dark:backdrop-blur-sm',
secondary: 'bg-transparent border border-neutral-300 text-neutral-700 hover:bg-neutral-100 focus:ring-neutral-500/20 dark:bg-neutral-800/60 dark:border-neutral-700/50 dark:text-white dark:hover:bg-neutral-800/80 dark:focus:ring-neutral-600/20',
danger: 'bg-red-50 border border-red-200 text-red-700 hover:bg-red-100 focus:ring-red-500/20 dark:bg-red-500/20 dark:border-red-500/30 dark:text-red-400 dark:hover:bg-red-500/30 dark:focus:ring-red-500/20',
ghost: 'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100 focus:ring-neutral-500/20 dark:text-neutral-400 dark:hover:text-white dark:hover:bg-neutral-700/30 dark:focus:ring-neutral-600/20',
success: 'bg-green-50 border border-green-200 text-green-700 hover:bg-green-100 focus:ring-green-500/20 dark:bg-green-500/20 dark:border-green-500/30 dark:text-green-400 dark:hover:bg-green-500/30 dark:focus:ring-green-500/20',
warning: 'bg-yellow-50 border border-yellow-200 text-yellow-700 hover:bg-yellow-100 focus:ring-yellow-500/20 dark:bg-yellow-500/20 dark:border-yellow-500/30 dark:text-yellow-400 dark:hover:bg-yellow-500/30 dark:focus:ring-yellow-500/20'
};
const sizes = {
sm: 'px-3 py-1.5 text-xs gap-1.5',
md: 'px-4 py-2.5 text-sm gap-2',
sm: 'px-[10px] py-[4px] text-[12px] gap-1.5',
md: 'px-[14px] py-[7px] text-[13px] gap-2',
lg: 'px-6 py-3 text-base gap-2.5'
};
+9 -9
View File
@@ -19,15 +19,15 @@ const Card = ({
const isLightDark = variant === 'lightDark';
const variants = {
default: 'rounded-xl bg-white dark:bg-neutral-800/30 border-neutral-200 dark:border-neutral-700/30',
elevated: 'rounded-xl bg-neutral-50/80 dark:bg-neutral-900/40 border-neutral-200 dark:border-neutral-800/50',
outline: 'rounded-xl bg-transparent border-neutral-300 dark:border-neutral-700/50',
solid: 'rounded-xl bg-neutral-100 dark:bg-neutral-800 border-neutral-200 dark:border-neutral-700',
lightDark: 'rounded-xl bg-white dark:bg-neutral-900/40 border-neutral-200 dark:border-neutral-800/50',
success: 'rounded-xl bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-900/50',
info: 'rounded-xl bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-900/50',
warning: 'rounded-xl bg-yellow-50 dark:bg-yellow-900/30 border-yellow-200 dark:border-yellow-900/50',
danger: 'rounded-xl bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-900/50'
default: 'rounded-md bg-white dark:bg-neutral-800/30 border-neutral-200 dark:border-neutral-700/30',
elevated: 'rounded-md bg-neutral-50/80 dark:bg-neutral-900/40 border-neutral-200 dark:border-neutral-800/50',
outline: 'rounded-md bg-transparent border-neutral-300 dark:border-neutral-700/50',
solid: 'rounded-md bg-neutral-100 dark:bg-neutral-800 border-neutral-200 dark:border-neutral-700',
lightDark: 'rounded-md bg-white dark:bg-neutral-900/40 border-neutral-200 dark:border-neutral-800/50',
success: 'rounded-md bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-900/50',
info: 'rounded-md bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-900/50',
warning: 'rounded-md bg-yellow-50 dark:bg-yellow-900/30 border-yellow-200 dark:border-yellow-900/50',
danger: 'rounded-md bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-900/50'
};
const variantsHover = {
+2 -2
View File
@@ -18,7 +18,7 @@ const Input = ({
step,
...props
}) => {
const baseInputClassName = `w-full px-3 py-2.5 rounded-xl text-sm focus:outline-none transition-all duration-[120ms] ease-out disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900/60 dark:border-neutral-700/50 dark:text-white dark:placeholder-neutral-500 dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20 dark:hover:bg-neutral-900/80 ${
const baseInputClassName = `w-full px-[10px] py-[7px] rounded text-[13px] focus:outline-none transition-all duration-[120ms] ease-out disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900/60 dark:border-neutral-700/50 dark:text-white dark:placeholder-neutral-500 dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20 ${
error ? 'border-red-500/50 dark:border-red-500/50' : ''
} ${className}`;
@@ -56,7 +56,7 @@ const Input = ({
{...props}
/>
<div
className={`w-12 h-10 border rounded-xl cursor-pointer transition-all duration-200 ${
className={`w-12 h-10 border rounded cursor-pointer transition-all duration-[120ms] ease-out ${
error ? 'border-red-500/50' : 'border-neutral-300 dark:border-neutral-700/50 dark:hover:border-neutral-600'
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
style={{ backgroundColor: value || '#000000' }}
+3 -3
View File
@@ -27,16 +27,16 @@ const Modal = ({
className="relative z-50"
>
{/* Backdrop */}
<div className="fixed inset-0 bg-black/40" aria-hidden="true" />
<div className="fixed inset-0 bg-black/35 backdrop-blur-[2px]" aria-hidden="true" />
{/* Container */}
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel
className={`
w-full ${sizeClasses[size]}
w-full ${sizeClasses[size]}
bg-white dark:bg-neutral-900
border border-neutral-200 dark:border-neutral-800
rounded-xl
rounded-lg
shadow-xl
max-h-[90vh]
overflow-hidden
+2 -2
View File
@@ -39,9 +39,9 @@ const Pagination = ({
<button
onClick={onClick}
disabled={disabled || loading}
className={`px-2 py-1 text-xs font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
className={`px-2 py-1 text-xs font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
isActive
? 'bg-green-500/20 text-green-600 dark:text-green-400 border border-green-500/30 dark:border-green-500/20'
? 'bg-neutral-900 text-white border border-neutral-900 dark:bg-white dark:text-black dark:border-white'
: 'text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-700/30 hover:text-neutral-900 dark:hover:text-white'
}`}
>
+1 -1
View File
@@ -15,7 +15,7 @@ const Select = ({
description,
...props
}) => {
const baseSelectClassName = `w-full cursor-pointer px-3 py-2.5 rounded-xl text-sm focus:outline-none transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900/60 dark:border-neutral-700/50 dark:text-white dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20 dark:hover:bg-neutral-900/80 dark:backdrop-blur-sm ${
const baseSelectClassName = `w-full cursor-pointer px-[10px] py-[7px] rounded text-[13px] focus:outline-none transition-all duration-[120ms] ease-out disabled:opacity-50 disabled:cursor-not-allowed bg-white border border-neutral-300 text-neutral-900 focus:border-neutral-500 focus:ring-1 focus:ring-neutral-500/20 dark:bg-neutral-900/60 dark:border-neutral-700/50 dark:text-white dark:focus:border-neutral-600 dark:focus:ring-neutral-600/20 ${
error ? 'border-red-500/50 dark:border-red-500/50' : ''
} ${className}`;
+7 -7
View File
@@ -30,16 +30,16 @@ const StatCard = ({
return (
<div
className={`bg-white dark:bg-neutral-900/40 border border-neutral-200 dark:border-neutral-800/50 rounded-xl p-4 sm:p-6 transition-all duration-[120ms] ease-out ${className}`}
className={`bg-white dark:bg-neutral-900/40 border border-neutral-200 dark:border-neutral-800/50 rounded-md px-5 py-4 transition-all duration-[120ms] ease-out ${className}`}
{...props}
>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider mb-2 sm:mb-3 truncate">
<p className="text-[11px] font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-[0.04em] mb-2 truncate">
{title}
</p>
<div className="text-xl sm:text-2xl font-bold text-neutral-900 dark:text-white mb-1 sm:mb-2 truncate">
<div className="text-[22px] font-semibold tracking-tight text-neutral-900 dark:text-white mb-1 truncate">
{loading ? (
<Skeleton height="h-6" width="60%" />
) : (
@@ -65,11 +65,11 @@ const StatCard = ({
)}
</div>
<div className={`${bgColor} ${color} p-2.5 sm:p-3 rounded-xl flex-shrink-0 ml-3`}>
<div className={`${bgColor} ${color} w-8 h-8 p-2 rounded flex-shrink-0 ml-3 flex items-center justify-center`}>
{loading ? (
<Skeleton height="h-5 sm:h-6" width="w-5 sm:w-6" />
<Skeleton height="h-4" width="w-4" />
) : (
Icon && <Icon className="h-5 w-5 sm:h-6 sm:w-6" />
Icon && <Icon className="h-4 w-4" />
)}
</div>
</div>
+6 -6
View File
@@ -5,9 +5,9 @@ import Badge from './Badge';
import { TorriGateIcon } from '../Icons';
const ROW_SIZE = {
sm: { cell: 'px-4 py-3', header: 'px-6 py-4', mobile: 'p-4' },
md: { cell: 'px-6 py-4', header: 'px-6 py-4', mobile: 'p-6' },
lg: { cell: 'px-6 py-5', header: 'px-6 py-5', mobile: 'p-8' },
sm: { cell: 'px-4 py-[11px]', header: 'px-4 py-[9px]', mobile: 'p-4' },
md: { cell: 'px-4 py-[14px]', header: 'px-4 py-[12px]', mobile: 'p-5' },
lg: { cell: 'px-4 py-[18px]', header: 'px-4 py-[14px]', mobile: 'p-6' },
};
const Table = ({
@@ -21,7 +21,7 @@ const Table = ({
getRowProps,
emptyMessage = 'Aucune donnée',
emptyDescription = 'Aucun élément à afficher',
size = 'md',
size = 'sm',
className = '',
...props
}) => {
@@ -172,7 +172,7 @@ const Table = ({
{columns.map((column) => (
<th
key={column.key}
className={`${sizeClasses.header} ${column.headerAlign === 'right' ? 'text-right' : 'text-left'} text-xs font-medium text-neutral-600 dark:text-neutral-300 uppercase tracking-wider ${
className={`${sizeClasses.header} ${column.headerAlign === 'right' ? 'text-right' : 'text-left'} text-[11px] font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-[0.04em] ${
column.sortable ? 'cursor-pointer hover:text-neutral-900 dark:hover:text-white transition-colors' : ''
}`}
onClick={column.sortable && onSort ? () => onSort(column.key) : undefined}
@@ -185,7 +185,7 @@ const Table = ({
))}
</tr>
</thead>
<tbody className="divide-y divide-neutral-200 dark:divide-neutral-700/30">
<tbody className="divide-y divide-neutral-200 dark:divide-neutral-700/30 text-[13px]">
{loading ? (
Array.from({ length: 5 }).map((_, index) => (
<SkeletonRow key={index} />