fix(ui): fix missing space between rounded-lg and transition-all in Button class

This commit is contained in:
2026-04-22 14:55:19 -04:00
parent 7c85e54b26
commit 7ca818da5a
5 changed files with 153 additions and 64 deletions
+1 -7
View File
@@ -2,7 +2,7 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { Card, Table, StatusBadge, Pagination, Button } from '@zen/core/shared/components';
import { Card, Table, StatusBadge, Button } from '@zen/core/shared/components';
import { PencilEdit01Icon } from '@zen/core/shared/icons';
import { useToast } from '@zen/core/toast';
@@ -185,18 +185,12 @@ const UsersPageClient = () => {
onSort={handleSort}
emptyMessage="Aucun utilisateur trouvé"
emptyDescription="La base de données est vide"
/>
<Pagination
currentPage={pagination.page}
totalPages={pagination.totalPages}
onPageChange={handlePageChange}
onLimitChange={handleLimitChange}
limit={pagination.limit}
total={pagination.total}
loading={loading}
showPerPage={true}
showStats={true}
/>
</Card>
</div>
+3 -3
View File
@@ -24,9 +24,9 @@ const Badge = ({
};
const sizes = {
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'
sm: 'px-2 py-0.5 rounded-lg text-[12px]',
md: 'px-2.5 py-0.5 rounded-lg text-[12px]',
lg: 'px-3 py-1 rounded-lg text-xs'
};
return (
+1 -1
View File
@@ -15,7 +15,7 @@ const Button = ({
className = '',
...props
}) => {
const baseClassName = 'cursor-pointer inline-flex items-center justify-center font-medium rounded-lgtransition-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-lg 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',
+144 -48
View File
@@ -2,6 +2,7 @@
import React from 'react';
import Badge from './Badge';
import Button from './Button';
import { TorriGateIcon } from '../icons/index.js';
const ROW_SIZE = {
@@ -23,13 +24,21 @@ const Table = ({
emptyDescription = 'Aucun élément à afficher',
size = 'sm',
className = '',
// Pagination props
currentPage = 1,
totalPages = 1,
onPageChange,
onLimitChange,
limit = 20,
total = 0,
...props
}) => {
const sizeClasses = ROW_SIZE[size] ?? ROW_SIZE.md;
const showPagination = total > 20;
const SortIcon = ({ column }) => {
const isActive = sortBy === column.key;
const isDesc = isActive && sortOrder === 'desc';
return (
<span className="ml-1">
<svg
@@ -43,9 +52,9 @@ const Table = ({
);
};
const Skeleton = ({ className = "", width = "100%", height = "h-4" }) => (
const Skeleton = ({ className: cx = '', width = '100%', height = 'h-4' }) => (
<div
className={`bg-gradient-to-r from-neutral-200 via-neutral-100 to-neutral-200 dark:from-neutral-700/30 dark:via-neutral-600/30 dark:to-neutral-700/30 bg-[length:200%_100%] animate-shimmer rounded ${height} ${className}`}
className={`bg-gradient-to-r from-neutral-200 via-neutral-100 to-neutral-200 dark:from-neutral-700/30 dark:via-neutral-600/30 dark:to-neutral-700/30 bg-[length:200%_100%] animate-shimmer rounded ${height} ${cx}`}
style={{ width }}
/>
);
@@ -57,22 +66,11 @@ const Table = ({
{column.skeleton ? (
column.skeleton.secondary ? (
<div className="space-y-2">
<Skeleton
className={column.skeleton.className}
height={column.skeleton.height}
width={column.skeleton.width}
/>
<Skeleton
height={column.skeleton.secondary.height}
width={column.skeleton.secondary.width}
/>
<Skeleton cx={column.skeleton.className} height={column.skeleton.height} width={column.skeleton.width} />
<Skeleton height={column.skeleton.secondary.height} width={column.skeleton.secondary.width} />
</div>
) : (
<Skeleton
className={column.skeleton.className}
height={column.skeleton.height}
width={column.skeleton.width}
/>
<Skeleton cx={column.skeleton.className} height={column.skeleton.height} width={column.skeleton.width} />
)
) : (
<Skeleton height="h-4" width="60%" />
@@ -95,24 +93,11 @@ const Table = ({
);
const renderCellContent = (item, column) => {
if (column.render) {
return column.render(item);
}
if (column.render) return column.render(item);
const value = column.key.split('.').reduce((obj, key) => obj?.[key], item);
if (column.type === 'badge') {
return <Badge variant={column.badgeVariant}>{value}</Badge>;
}
if (column.type === 'date') {
return new Date(value).toLocaleDateString();
}
if (column.type === 'currency') {
return `$${parseFloat(value || 0).toFixed(2)}`;
}
if (column.type === 'badge') return <Badge variant={column.badgeVariant}>{value}</Badge>;
if (column.type === 'date') return new Date(value).toLocaleDateString();
if (column.type === 'currency') return `$${parseFloat(value || 0).toFixed(2)}`;
return value || '-';
};
@@ -128,9 +113,7 @@ const Table = ({
</div>
<div className="flex flex-col gap-2 ml-4">
{columns.slice(2, 4).map((column) => (
<div key={column.key}>
{renderCellContent(item, column)}
</div>
<div key={column.key}>{renderCellContent(item, column)}</div>
))}
</div>
</div>
@@ -154,14 +137,131 @@ const Table = ({
<Skeleton height="h-3" width="40%" />
</div>
<div className="flex flex-col gap-2 ml-4">
<Skeleton className="rounded-full" height="h-6" width="80px" />
<Skeleton className="rounded-full" height="h-6" width="90px" />
<Skeleton cx="rounded-full" height="h-6" width="80px" />
<Skeleton cx="rounded-full" height="h-6" width="90px" />
</div>
</div>
<Skeleton height="h-3" width="50%" />
</div>
);
const getPageNumbers = () => {
const pages = [];
const delta = 2;
if (totalPages > 0) pages.push(1);
if (currentPage - delta > 2) pages.push('...');
for (let i = Math.max(2, currentPage - delta); i <= Math.min(totalPages - 1, currentPage + delta); i++) {
pages.push(i);
}
if (currentPage + delta < totalPages - 1) pages.push('...');
if (totalPages > 1) pages.push(totalPages);
return pages;
};
const PageButton = ({ onClick, disabled, children, isActive = false }) => (
<button
onClick={onClick}
disabled={disabled || loading}
className={`px-2 py-1 text-xs font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
isActive
? 'bg-neutral-900 text-white dark:bg-white dark:text-black'
: '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'
}`}
>
{children}
</button>
);
const from = total > 0 ? (currentPage - 1) * limit + 1 : 0;
const to = Math.min(currentPage * limit, total);
const PaginationBar = () => (
<div className="px-4 py-3 border-t border-neutral-200 dark:border-neutral-700/30">
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2 shrink-0">
{loading ? (
<Skeleton height="h-7" width="90px" />
) : (
<>
<span className="text-xs text-neutral-500 dark:text-neutral-400">Afficher</span>
<select
value={limit}
onChange={(e) => onLimitChange?.(Number(e.target.value))}
disabled={loading}
className="h-7 px-2 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700/50 rounded-md text-xs text-neutral-900 dark:text-white focus:outline-none focus:border-neutral-400 dark:focus:border-neutral-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
</>
)}
</div>
<div className="flex items-center gap-1">
{loading ? (
<>
<Skeleton height="h-7" width="64px" />
<div className="hidden sm:flex items-center gap-1">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} height="h-7" width="28px" />
))}
</div>
<Skeleton height="h-7" width="56px" />
</>
) : (
<>
{totalPages > 1 && (
<Button
variant="ghost"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
>
Précédent
</Button>
)}
{totalPages > 1 && (
<div className="hidden sm:flex items-center gap-1">
{getPageNumbers().map((page, index) => (
<React.Fragment key={index}>
{page === '...' ? (
<span className="px-2 text-xs text-neutral-400 dark:text-neutral-500"></span>
) : (
<PageButton onClick={() => onPageChange(page)} isActive={currentPage === page}>
{page}
</PageButton>
)}
</React.Fragment>
))}
</div>
)}
{totalPages > 1 && (
<Button
variant="ghost"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Suivant
</Button>
)}
</>
)}
</div>
<div className="text-xs text-neutral-400 dark:text-neutral-500 shrink-0 text-right">
{loading ? (
<Skeleton height="h-4" width="100px" />
) : (
`${from}${to} sur ${total}`
)}
</div>
</div>
</div>
);
return (
<div className={className} {...props}>
{/* Desktop Table */}
@@ -187,9 +287,7 @@ const Table = ({
</thead>
<tbody className="divide-y divide-neutral-200 dark:divide-neutral-700/30 text-[13px]">
{loading ? (
Array.from({ length: 5 }).map((_, index) => (
<SkeletonRow key={index} />
))
Array.from({ length: 5 }).map((_, index) => <SkeletonRow key={index} />)
) : data.length === 0 ? (
<EmptyState />
) : (
@@ -218,15 +316,11 @@ const Table = ({
{/* Mobile Cards */}
<div className="lg:hidden divide-y divide-neutral-200 dark:divide-neutral-700/30">
{loading ? (
Array.from({ length: 3 }).map((_, index) => (
<MobileSkeletonCard key={index} />
))
Array.from({ length: 3 }).map((_, index) => <MobileSkeletonCard key={index} />)
) : data.length === 0 ? (
<div className={`${sizeClasses.mobile} py-12 text-center`}>
<div className="text-neutral-500 dark:text-neutral-400 mb-2">
<svg className="mx-auto h-12 w-12 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2M4 13h2m13-8V4a1 1 0 00-1-1H7a1 1 0 00-1 1v1m8 0V4.5" />
</svg>
<TorriGateIcon className="mx-auto h-12 w-12 mb-4" />
</div>
<p className="text-lg font-medium text-neutral-700 dark:text-neutral-300 mb-1">{emptyMessage}</p>
<p className="text-sm text-neutral-500 dark:text-neutral-400">{emptyDescription}</p>
@@ -247,6 +341,8 @@ const Table = ({
})
)}
</div>
{showPagination && <PaginationBar />}
</div>
);
};
-1
View File
@@ -16,7 +16,6 @@ export {
} from './LoadingState';
export { default as Loading } from './Loading';
export { default as Modal } from './Modal';
export { default as Pagination } from './Pagination';
export { default as Select } from './Select';
export { default as StatCard } from './StatCard';
export { default as Table } from './Table';