veza/apps/web/src/components/ui/table.tsx
senke fa56dfa77e refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
  kodo-void → background, kodo-ink → card, kodo-graphite → muted,
  kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
  kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
  semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 01:51:49 +01:00

305 lines
7.4 KiB
TypeScript

import * as React from 'react';
import { cn } from '@/lib/utils';
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';
/**
* Table - Composant de tableau avec design system Kodo
*
* Composant de tableau pour afficher des données structurées.
* Utilise le design system Kodo avec des styles cohérents et support pour le scroll.
*
* @example
* ```tsx
* <Table>
* <TableHeader>
* <TableRow>
* <TableHead>Nom</TableHead>
* <TableHead>Email</TableHead>
* </TableRow>
* </TableHeader>
* <TableBody>
* <TableRow>
* <TableCell>John Doe</TableCell>
* <TableCell>john@example.com</TableCell>
* </TableRow>
* </TableBody>
* </Table>
* ```
*
* @component
* @param {React.HTMLAttributes<HTMLTableElement>} props - Propriétés HTML standard de table
* @returns {JSX.Element} Élément div contenant un tableau stylisé avec scroll
*/
// CRITIQUE FIX #40: Interface étendue pour supporter aria-label et caption
export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
/**
* Label accessible pour le tableau (aria-label)
* Si non fourni, aria-label sera undefined et il faudra utiliser TableCaption
*/
'aria-label'?: string;
/**
* ID d'un élément qui décrit le tableau (aria-labelledby)
*/
'aria-labelledby'?: string;
}
const Table = React.forwardRef<HTMLTableElement, TableProps>(
(
{
className,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
...props
},
ref,
) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
// CRITIQUE FIX #40: Ajouter aria-label si fourni pour l'accessibilité
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
{...props}
/>
</div>
),
);
Table.displayName = 'Table';
/**
* TableHeader - En-tête du tableau
*
* Conteneur pour les lignes d'en-tête du tableau.
*
* @component
*/
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn(
'[&_tr]:border-b border-border/50 sticky top-0 z-10 bg-background/95 backdrop-blur-sm',
className,
)}
{...props}
/>
));
TableHeader.displayName = 'TableHeader';
/**
* TableBody - Corps du tableau
*
* Conteneur pour les lignes de données du tableau.
*
* @component
*/
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
));
TableBody.displayName = 'TableBody';
/**
* TableFooter - Pied du tableau
*
* Conteneur pour les lignes de pied du tableau (totaux, etc.).
*
* @component
*/
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
'border-t border-border/50 bg-muted/30 font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
/**
* TableRow - Ligne du tableau
*
* Ligne individuelle du tableau avec effet hover et support pour l'état sélectionné.
*
* @component
*/
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
'border-b border-border/50 transition-colors duration-150 hover:bg-muted/50 data-[state=selected]:bg-muted/80',
className,
)}
{...props}
/>
));
TableRow.displayName = 'TableRow';
/**
* TableHead - Cellule d'en-tête
*
* Cellule d'en-tête du tableau avec style en gras et uppercase.
*
* @component
*/
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'px-4 py-2.5 text-left align-middle text-xs font-medium text-muted-foreground uppercase tracking-wider [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';
/**
* TableCell - Cellule de données
*
* Cellule de données du tableau avec padding et alignement.
*
* @component
*/
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
'px-4 py-3 align-middle text-sm [&:has([role=checkbox])]:pr-0 text-foreground',
className,
)}
{...props}
/>
));
TableCell.displayName = 'TableCell';
/**
* TableCaption - Légende du tableau
*
* Légende optionnelle affichée sous le tableau.
*
* @component
*/
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn('mt-4 text-sm text-muted-foreground', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
/**
* SortIndicator - Indicateur visuel de tri pour les colonnes
*
* Affiche une icône de tri correspondant à la direction actuelle.
* Utilisable dans les cellules d'en-tête pour indiquer l'état de tri.
*
* @component
* @param {Object} props
* @param {'asc' | 'desc' | null | undefined} props.direction - Direction de tri actuelle
*/
function SortIndicator({
direction,
}: {
direction?: 'asc' | 'desc' | null;
}) {
if (!direction)
return (
<ChevronsUpDown className="h-3.5 w-3.5 text-muted-foreground/50" />
);
return direction === 'asc' ? (
<ChevronUp className="h-3.5 w-3.5 text-foreground" />
) : (
<ChevronDown className="h-3.5 w-3.5 text-foreground" />
);
}
/**
* SortableTableHead - Cellule d'en-tête triable
*
* Cellule d'en-tête enrichie avec indicateur de tri et interaction.
*
* @component
* @param {Object} props
* @param {boolean} props.sortable - Si la colonne est triable
* @param {'asc' | 'desc' | null} props.sortDirection - Direction de tri actuelle
* @param {() => void} props.onSort - Callback lors du clic de tri
*/
interface SortableTableHeadProps
extends React.ThHTMLAttributes<HTMLTableCellElement> {
sortable?: boolean;
sortDirection?: 'asc' | 'desc' | null;
onSort?: () => void;
}
const SortableTableHead = React.forwardRef<
HTMLTableCellElement,
SortableTableHeadProps
>(
(
{ className, children, sortable, sortDirection, onSort, ...props },
ref,
) => (
<th
ref={ref}
className={cn(
'px-4 py-2.5 text-left align-middle text-xs font-medium text-muted-foreground uppercase tracking-wider [&:has([role=checkbox])]:pr-0',
sortable &&
'cursor-pointer select-none hover:text-foreground transition-colors duration-150',
className,
)}
onClick={sortable ? onSort : undefined}
{...props}
>
<div className="flex items-center gap-1.5">
{children}
{sortable && <SortIndicator direction={sortDirection} />}
</div>
</th>
),
);
SortableTableHead.displayName = 'SortableTableHead';
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
SortIndicator,
SortableTableHead,
};