diff --git a/apps/apollo-stories/src/components/Table/Table.mdx b/apps/apollo-stories/src/components/Table/Table.mdx
new file mode 100644
index 000000000..774c75648
--- /dev/null
+++ b/apps/apollo-stories/src/components/Table/Table.mdx
@@ -0,0 +1,79 @@
+import { Canvas, Controls, Meta } from "@storybook/addon-docs";
+import * as TableStories from "./Table.stories";
+
+
+
+# Table
+
+To use the table import it like that:
+
+```tsx
+import { Table } from "@axa-fr/canopee-react/prospect";
+
+const MyComponent = () => (
+
+
+
+ Nom
+ Email
+
+
+
+
+ Jean Dupont
+ jean.dupont@example.com
+
+
+
+);
+```
+
+## Usage
+
+The Table component is a compound component with the following sub-components:
+
+- `Table.THead` - Table header with optional `variant` prop
+- `Table.TBody` - Table body with optional `variant` prop
+- `Table.Tr` - Table row with optional `size` and variant props
+- `Table.Th` - Table header cell with optional `onSort`, `onCheck`, `checkboxPosition` props
+- `Table.Td` - Table data cell with optional `position`, `verticalAlign`, `variant` and `size` props
+
+## Examples
+
+### Basic Table
+
+
+
+### Alternate Variants
+
+Use `variant="alternate"` on `Table.TBody` to get zebra-striped rows.
+
+
+
+### With Tags
+
+Tables work great with other components like Tags for status indicators.
+
+
+
+### With Buttons
+
+Tables can include interactive elements like buttons for actions.
+
+
+
+### Different Sizes
+
+Use the `size` prop on `Table.Tr` to control row height ("S", "M", "L").
+
+
+
+### Text Alignment
+
+Use the `position` prop on `Table.Td` to align cell content ("left", "center", "right").
+
+
+
+### Compact Table
+
+
diff --git a/apps/apollo-stories/src/components/Table/Table.stories.tsx b/apps/apollo-stories/src/components/Table/Table.stories.tsx
new file mode 100644
index 000000000..b814fae5b
--- /dev/null
+++ b/apps/apollo-stories/src/components/Table/Table.stories.tsx
@@ -0,0 +1,486 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { action } from "@storybook/addon-actions";
+import {
+ Table,
+ Button,
+ Tag,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "@axa-fr/canopee-react/prospect";
+
+interface TableStoryArgs {
+ theadVariant?: HeadColorVariants;
+ tbodyVariant?: BodyColorVariants;
+ rowSize?: RowSizeVariants;
+ row1Size?: RowSizeVariants;
+ row2Size?: RowSizeVariants;
+ row3Size?: RowSizeVariants;
+ row4Size?: RowSizeVariants;
+}
+
+const meta: Meta = {
+ title: "Components/Table",
+ component: Table,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const BasicTable: Story = {
+ name: "Tableau basique",
+ args: {
+ theadVariant: "blue",
+ tbodyVariant: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Prénom
+ Email
+ Téléphone
+
+
+
+
+ Dupont
+ Jean
+ jean.dupont@example.com
+ 06 12 34 56 78
+
+
+ Martin
+ Marie
+ marie.martin@example.com
+ 06 98 76 54 32
+
+
+ Bernard
+ Pierre
+ pierre.bernard@example.com
+ 06 11 22 33 44
+
+
+ Dubois
+ Sophie
+ sophie.dubois@example.com
+ 06 55 66 77 88
+
+
+
+ ),
+};
+
+export const AlternateVariantTable: Story = {
+ name: "Tableau avec couleurs alternées",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: "alternate",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Produit
+ Catégorie
+ Prix
+ Stock
+
+
+
+
+ Ordinateur Portable
+ Électronique
+ 899,00 €
+ 15
+
+
+ Souris sans fil
+ Accessoires
+ 29,99 €
+ 50
+
+
+ Clavier mécanique
+ Accessoires
+ 89,00 €
+ 23
+
+
+ Écran 27
+ Électronique
+ 299,00 €
+ 8
+
+
+
+ ),
+};
+
+export const TableWithTags: Story = {
+ name: "Tableau avec tags, statuts et tri",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: "alternate",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Référence
+ Statut
+ Client
+ Montant
+
+
+
+
+ REF-001
+
+ Validé
+
+ Jean Dupont
+ 220,00 €
+
+
+ REF-002
+
+ En attente
+
+ Marie Martin
+ 450,00 €
+
+
+ REF-003
+
+ Rejeté
+
+ Pierre Bernard
+ 180,00 €
+
+
+ REF-004
+
+ En cours
+
+ Sophie Dubois
+ 320,00 €
+
+
+
+ ),
+};
+
+export const TableWithButtons: Story = {
+ name: "Tableau avec sélection et actions",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: undefined,
+ rowSize: "M",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ rowSize: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille des lignes",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Utilisateur
+ Email
+ Rôle
+ Actions
+
+
+
+
+ Jean Dupont
+ jean.dupont@example.com
+ Administrateur
+
+ Modifier
+
+
+
+ Marie Martin
+ marie.martin@example.com
+ Éditeur
+
+ Modifier
+
+
+
+ Pierre Bernard
+ pierre.bernard@example.com
+ Lecteur
+
+ Modifier
+
+
+
+ Sophie Dubois
+ sophie.dubois@example.com
+ Éditeur
+
+ Modifier
+
+
+
+
+ ),
+};
+
+export const TableWithDifferentSizes: Story = {
+ name: "Tableau avec tailles de lignes variées",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: "alternate",
+ row1Size: "S",
+ row2Size: "M",
+ row3Size: "L",
+ row4Size: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ row1Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 1",
+ },
+ row2Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 2",
+ },
+ row3Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 3",
+ },
+ row4Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 4",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Description
+ Prix
+ Disponibilité
+
+
+
+
+ Produit A
+ Description courte
+ 49,99 €
+ En stock
+
+
+ Produit B
+ Description de longueur moyenne pour ce produit
+ 79,99 €
+ En stock
+
+
+ Produit C
+
+ Description très détaillée avec beaucoup informations
+
+ 129,99 €
+ Stock limité
+
+
+ Produit D
+ Description standard
+ 99,99 €
+ Sur commande
+
+
+
+ ),
+};
+
+export const TableWithAlignments: Story = {
+ name: "Tableau avec alignements différents",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Article
+ Quantité
+ Prix unitaire
+ Total
+
+
+
+
+ Ordinateur
+ 1
+ 899,00 €
+ 899,00 €
+
+
+ Souris
+ 2
+ 29,99 €
+ 59,98 €
+
+
+ Clavier
+ 1
+ 89,00 €
+ 89,00 €
+
+
+ Câble HDMI
+ 3
+ 15,99 €
+ 47,97 €
+
+
+
+ ),
+};
+
+export const CompactTable: Story = {
+ name: "Tableau compact (3 colonnes)",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Statut
+ Date
+
+
+
+
+ Projet Alpha
+
+ Terminé
+
+ 10/01/2026
+
+
+ Projet Beta
+
+ En cours
+
+ 15/01/2026
+
+
+ Projet Gamma
+
+ Planifié
+
+ 20/01/2026
+
+
+ Projet Delta
+
+ Annulé
+
+ 05/01/2026
+
+
+
+ ),
+};
diff --git a/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.mdx b/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.mdx
new file mode 100644
index 000000000..c4392287a
--- /dev/null
+++ b/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.mdx
@@ -0,0 +1,47 @@
+import { Canvas, Controls, Meta } from "@storybook/addon-docs";
+import * as TableMobileCardStories from "./TableMobileCard.stories";
+
+
+
+# TableMobileCard
+
+> ⚠️ This component should not be used alone. It is the mobile version for a table.
+
+To use the table mobile card import it like that:
+
+```tsx
+import { TableMobileCard } from "@axa-fr/canopee-react/prospect";
+
+const MyComponent = () => (
+
+
+ Produit/Support
+ AB Sustainable Global Thematic A
+
+
+ Code Isin
+ LU0101010101
+
+
+ Status
+ Ouvert
+
+
+);
+```
+
+## Usage
+
+The TableMobileCard component is a compound component with the following sub-components:
+
+- `TableMobileCard.DRow` - TableMobileCard row wrapper with optional `direction` prop
+- `TableMobileCard.Dt` - Table row title
+- `TableMobileCard.Dd` - Table row description
+
+## Examples
+
+### Basic Table
+
+
+
+
diff --git a/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx b/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx
new file mode 100644
index 000000000..ca947cd23
--- /dev/null
+++ b/apps/apollo-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx
@@ -0,0 +1,94 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { action } from "@storybook/addon-actions";
+import { Button, Icon, TableMobileCard } from "@axa-fr/canopee-react/prospect";
+import download from "@material-symbols/svg-400/outlined/download_2-fill.svg";
+
+interface TableMobileCardStoryArgs {
+ variant?: "alternate" | "blue" | "white";
+ direction?: "row" | "column";
+}
+
+const meta: Meta = {
+ title: "Components/TableMobileCard",
+ component: TableMobileCard,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const BasicTableMobileCard: Story = {
+ name: "Table Card basique",
+ args: {
+ variant: "alternate",
+ direction: "row",
+ },
+ argTypes: {
+ variant: {
+ control: { type: "select" },
+ options: ["alternate", "white", "blue"],
+ description: "Variant de la couleur des lignes",
+ },
+ direction: {
+ control: { type: "select" },
+ options: ["row", "column"],
+ description: "Variant de la disposition des éléments d'une ligne",
+ },
+ },
+ render: (args: TableMobileCardStoryArgs) => (
+
+
+ Produit/Support
+
+ AB Sustainable Global Thematic A
+
+
+
+ Code Isin
+ LU0101010101
+
+
+ Status
+ Ouvert
+
+
+ Qualification SFDR
+ 9
+
+
+ DIC/DIS/DICI
+
+ }
+ onClick={action("download_1")}
+ >
+ Télécharger
+
+
+
+
+ Prospectus
+
+ }
+ >
+ Télécharger
+
+
+
+
+ Rapport annuelle
+
+ }
+ >
+ Télécharger
+
+
+
+
+ ),
+};
diff --git a/apps/apollo-stories/src/pages/Table/Table.css b/apps/apollo-stories/src/pages/Table/Table.css
new file mode 100644
index 000000000..92bdd70b0
--- /dev/null
+++ b/apps/apollo-stories/src/pages/Table/Table.css
@@ -0,0 +1,32 @@
+.table-scroll {
+ margin-inline: calc(-1 * var(--margin-inline));
+ padding-inline: var(--margin-inline);
+ overflow: auto hidden;
+}
+
+main.table-page {
+ --font-size-base: 16;
+
+ padding-block: calc(40 / var(--font-size-base) * 1rem);
+
+ @media (width > 1023px) {
+ padding-block: calc(48 / var(--font-size-base) * 1rem);
+ }
+
+ > section.subgrid {
+ row-gap: calc(28 / var(--font-size-base) * 1rem);
+
+ @media (width > 1023px) {
+ --row-gap: calc(48 / var(--font-size-base) * 1rem);
+ }
+
+ .table-page__dropdown {
+ --cols: 4;
+ }
+
+ .table-page__pagination {
+ width: fit-content;
+ justify-self: center;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/apollo-stories/src/pages/Table/Table.stories.tsx b/apps/apollo-stories/src/pages/Table/Table.stories.tsx
new file mode 100644
index 000000000..7096dfaed
--- /dev/null
+++ b/apps/apollo-stories/src/pages/Table/Table.stories.tsx
@@ -0,0 +1,16 @@
+import "./Table.css";
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { TablePage } from "./Table";
+
+const meta: Meta = {
+ title: "Pages/Table",
+ parameters: { layout: "fullscreen" },
+};
+
+export default meta;
+
+export const StepSortie: StoryObj = {
+ name: "Table",
+ render: TablePage,
+};
diff --git a/apps/apollo-stories/src/pages/Table/Table.tsx b/apps/apollo-stories/src/pages/Table/Table.tsx
new file mode 100644
index 000000000..16be11ce8
--- /dev/null
+++ b/apps/apollo-stories/src/pages/Table/Table.tsx
@@ -0,0 +1,450 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions */
+/* eslint-disable-next-line import/no-extraneous-dependencies */
+import React, {
+ useCallback,
+ useMemo,
+ useState,
+ useSyncExternalStore,
+} from "react";
+import {
+ Button,
+ DebugGrid,
+ Dropdown,
+ Pagination,
+ Table,
+ TableMobileCard,
+ Tag,
+ Heading,
+} from "@axa-fr/canopee-react/prospect";
+
+const useIsSmallScreen = (breakPointToCheck: number) => {
+ const subscribe = useCallback((listener: () => void) => {
+ window.addEventListener("resize", listener);
+ return () => {
+ window.removeEventListener("resize", listener);
+ };
+ }, []);
+
+ const getSnapshot = useCallback(() => {
+ return window.innerWidth <= breakPointToCheck;
+ }, [breakPointToCheck]);
+
+ const getServerSnapshot = useCallback(() => false, []);
+
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
+};
+
+interface DataRow {
+ id: number;
+ reference: string;
+ societe: string;
+ contact: string;
+ email: string;
+ telephone: string;
+ montant: number;
+ statut: "Validé" | "En attente" | "Rejeté" | "En cours";
+ date: string;
+}
+
+const ALL_DATA: DataRow[] = [
+ {
+ id: 1,
+ reference: "CON-2024-001",
+ societe: "Société ABC",
+ contact: "Jean Dupont",
+ email: "jean.dupont@abc.fr",
+ telephone: "06 12 34 56 78",
+ montant: 1250.0,
+ statut: "Validé",
+ date: "15/01/2024",
+ },
+ {
+ id: 2,
+ reference: "CON-2024-002",
+ societe: "Entreprise XYZ",
+ contact: "Marie Martin",
+ email: "marie.martin@xyz.fr",
+ telephone: "06 98 76 54 32",
+ montant: 2500.0,
+ statut: "En attente",
+ date: "22/03/2024",
+ },
+ {
+ id: 3,
+ reference: "CON-2024-003",
+ societe: "Groupe DEF",
+ contact: "Pierre Bernard",
+ email: "pierre.bernard@def.fr",
+ telephone: "06 11 22 33 44",
+ montant: 3750.0,
+ statut: "Rejeté",
+ date: "10/06/2024",
+ },
+ {
+ id: 4,
+ reference: "CON-2024-004",
+ societe: "Industries GHI",
+ contact: "Sophie Dubois",
+ email: "sophie.dubois@ghi.fr",
+ telephone: "06 55 66 77 88",
+ montant: 890.0,
+ statut: "En cours",
+ date: "05/09/2024",
+ },
+ {
+ id: 5,
+ reference: "CON-2024-005",
+ societe: "Services JKL",
+ contact: "Luc Moreau",
+ email: "luc.moreau@jkl.fr",
+ telephone: "06 22 33 44 55",
+ montant: 1780.0,
+ statut: "Validé",
+ date: "12/11/2024",
+ },
+ {
+ id: 6,
+ reference: "CON-2024-006",
+ societe: "Tech MNO",
+ contact: "Claire Petit",
+ email: "claire.petit@mno.fr",
+ telephone: "06 77 88 99 00",
+ montant: 4200.0,
+ statut: "En attente",
+ date: "28/12/2024",
+ },
+ {
+ id: 7,
+ reference: "CON-2024-007",
+ societe: "Solutions PQR",
+ contact: "Marc Lefebvre",
+ email: "marc.lefebvre@pqr.fr",
+ telephone: "06 33 44 55 66",
+ montant: 920.0,
+ statut: "Validé",
+ date: "03/01/2025",
+ },
+ {
+ id: 8,
+ reference: "CON-2024-008",
+ societe: "Digital STU",
+ contact: "Anne Rousseau",
+ email: "anne.rousseau@stu.fr",
+ telephone: "06 44 55 66 77",
+ montant: 1560.0,
+ statut: "En cours",
+ date: "18/02/2025",
+ },
+ {
+ id: 9,
+ reference: "CON-2024-009",
+ societe: "Consulting VWX",
+ contact: "Thomas Garnier",
+ email: "thomas.garnier@vwx.fr",
+ telephone: "06 88 99 00 11",
+ montant: 3100.0,
+ statut: "Validé",
+ date: "25/03/2025",
+ },
+ {
+ id: 10,
+ reference: "CON-2024-010",
+ societe: "Innovation YZ",
+ contact: "Julie Faure",
+ email: "julie.faure@yz.fr",
+ telephone: "06 99 00 11 22",
+ montant: 2890.0,
+ statut: "En attente",
+ date: "07/04/2025",
+ },
+ {
+ id: 11,
+ reference: "CON-2024-011",
+ societe: "Partners AB",
+ contact: "Vincent Leroux",
+ email: "vincent.leroux@ab.fr",
+ telephone: "06 00 11 22 33",
+ montant: 1450.0,
+ statut: "Rejeté",
+ date: "14/05/2025",
+ },
+ {
+ id: 12,
+ reference: "CON-2024-012",
+ societe: "Business CD",
+ contact: "Isabelle Simon",
+ email: "isabelle.simon@cd.fr",
+ telephone: "06 11 22 33 44",
+ montant: 3680.0,
+ statut: "Validé",
+ date: "22/06/2025",
+ },
+ {
+ id: 13,
+ reference: "CON-2024-013",
+ societe: "Global EF",
+ contact: "Nicolas Laurent",
+ email: "nicolas.laurent@ef.fr",
+ telephone: "06 22 33 44 55",
+ montant: 2100.0,
+ statut: "En cours",
+ date: "30/07/2025",
+ },
+ {
+ id: 14,
+ reference: "CON-2024-014",
+ societe: "Pro GH",
+ contact: "Sandrine Michel",
+ email: "sandrine.michel@gh.fr",
+ telephone: "06 33 44 55 66",
+ montant: 1890.0,
+ statut: "En attente",
+ date: "08/08/2025",
+ },
+ {
+ id: 15,
+ reference: "CON-2024-015",
+ societe: "Expert IJ",
+ contact: "Julien Leroy",
+ email: "julien.leroy@ij.fr",
+ telephone: "06 44 55 66 77",
+ montant: 4500.0,
+ statut: "Validé",
+ date: "15/09/2025",
+ },
+];
+
+const STATUS_VARIANTS: Record<
+ DataRow["statut"],
+ "success" | "warning" | "error" | "info"
+> = {
+ Validé: "success",
+ "En attente": "warning",
+ Rejeté: "error",
+ "En cours": "info",
+};
+
+const getStatusTag = (statut: DataRow["statut"]) => (
+ {statut}
+);
+
+type SortConfig = { key: keyof DataRow; direction: "asc" | "desc" } | null;
+
+export const TablePage = () => {
+ const [sortConfig, setSortConfig] = useState(null);
+ const [checkedColumns, setCheckedColumns] = useState>(
+ () => new Set(),
+ );
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPage, setItemsPerPage] = useState(5);
+
+ const isMobile = useIsSmallScreen(667);
+
+ const handleSort = useCallback((key: keyof DataRow) => {
+ setSortConfig((prev) => {
+ const direction: "asc" | "desc" =
+ prev?.key === key && prev.direction === "asc" ? "desc" : "asc";
+ return { key, direction };
+ });
+ setCurrentPage(1);
+ }, []);
+
+ const handleColumnCheck = useCallback((columnIndex: number) => {
+ setCheckedColumns((prev) => {
+ const next = new Set(prev);
+ next.has(columnIndex) ? next.delete(columnIndex) : next.add(columnIndex);
+ return next;
+ });
+ }, []);
+
+ const getColumnVariant = useCallback(
+ (columnIndex: number) => {
+ return checkedColumns.has(columnIndex) ? "blue" : undefined;
+ },
+ [checkedColumns],
+ );
+
+ const sortedData = useMemo(() => {
+ if (!sortConfig) return ALL_DATA;
+
+ const { key, direction } = sortConfig;
+
+ return [...ALL_DATA].sort((a, b) => {
+ const aValue = a[key];
+ const bValue = b[key];
+
+ // numbers
+ if (typeof aValue === "number" && typeof bValue === "number") {
+ return direction === "asc" ? aValue - bValue : bValue - aValue;
+ }
+
+ // everything else as string
+ const aStr = String(aValue);
+ const bStr = String(bValue);
+ return direction === "asc"
+ ? aStr.localeCompare(bStr)
+ : bStr.localeCompare(aStr);
+ });
+ }, [sortConfig]);
+
+ const totalPages = useMemo(
+ () => Math.max(1, Math.ceil(sortedData.length / itemsPerPage)),
+ [sortedData.length, itemsPerPage],
+ );
+
+ const currentData = useMemo(() => {
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ return sortedData.slice(startIndex, endIndex);
+ }, [sortedData, currentPage, itemsPerPage]);
+
+ const handlePageChange = useCallback(
+ (page: number) => setCurrentPage(page),
+ [],
+ );
+
+ const handleItemsPerPageChange = useCallback(
+ (event: React.ChangeEvent) => {
+ setItemsPerPage(Number(event.target.value));
+ setCurrentPage(1);
+ },
+ [],
+ );
+
+ return (
+ <>
+
+
+
+ Liste des produits
+ {!isMobile && (
+ <>
+
+
+ 5
+ 10
+ 15
+ 20
+
+
+
+
+
+
+
+ handleColumnCheck(0)}
+ onSort={() => handleSort("reference")}
+ >
+ Référence
+
+ handleColumnCheck(1)}
+ onSort={() => handleSort("societe")}
+ >
+ Société
+
+ handleColumnCheck(2)}
+ onSort={() => handleSort("contact")}
+ >
+ Contact
+
+ handleColumnCheck(3)}
+ onSort={() => handleSort("montant")}
+ >
+ Montant
+
+ handleColumnCheck(4)}
+ onSort={() => handleSort("statut")}
+ >
+ Statut
+
+
+
+
+
+ {currentData.map((row) => (
+
+
+ {row.reference}
+
+
+ {row.societe}
+
+
+ {row.contact}
+
+
+ {row.montant.toFixed(2)} €
+
+
+ {getStatusTag(row.statut)}
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+
+ {isMobile ? (
+ <>
+ {sortedData.map((elem) => (
+
+
+ Référence
+ {elem.reference}
+
+
+
+ Société
+ {elem.societe}
+
+
+
+ Contact
+ {elem.contact}
+
+
+
+ Montant
+
+ {elem.montant.toFixed(2)} €
+
+
+
+
+ Status
+
+ {getStatusTag(elem.statut)}
+
+
+
+ ))}
+ >
+ ) : null}
+
+
+ >
+ );
+};
diff --git a/apps/look-and-feel-stories/src/components/Table/Table.mdx b/apps/look-and-feel-stories/src/components/Table/Table.mdx
new file mode 100644
index 000000000..50a624a3b
--- /dev/null
+++ b/apps/look-and-feel-stories/src/components/Table/Table.mdx
@@ -0,0 +1,79 @@
+import { Canvas, Controls, Meta } from "@storybook/addon-docs";
+import * as TableStories from "./Table.stories.tsx";
+
+
+
+# Table
+
+To use the table import it like that:
+
+```tsx
+import { Table } from "@axa-fr/canopee-react/client";
+
+const MyComponent = () => (
+
+
+
+ Nom
+ Email
+
+
+
+
+ Jean Dupont
+ jean.dupont@example.com
+
+
+
+);
+```
+
+## Usage
+
+The Table component is a compound component with the following sub-components:
+
+- `Table.THead` - Table header with optional `variant` prop
+- `Table.TBody` - Table body with optional `variant` prop
+- `Table.Tr` - Table row with optional `size` and variant props
+- `Table.Th` - Table header cell with optional `onSort`, `onCheck`, `checkboxPosition` props
+- `Table.Td` - Table data cell with optional `position`, `verticalAlign`, `variant` and `size` props
+
+## Examples
+
+### Basic Table
+
+
+
+### Alternate Variants
+
+Use `variant="alternate"` on `Table.TBody` to get zebra-striped rows.
+
+
+
+### With Tags
+
+Tables work great with other components like Tags for status indicators.
+
+
+
+### With Buttons
+
+Tables can include interactive elements like buttons for actions.
+
+
+
+### Different Sizes
+
+Use the `size` prop on `Table.Tr` to control row height ("S", "M", "L").
+
+
+
+### Text Alignment
+
+Use the `position` prop on `Table.Td` to align cell content ("left", "center", "right").
+
+
+
+### Compact Table
+
+
diff --git a/apps/look-and-feel-stories/src/components/Table/Table.stories.tsx b/apps/look-and-feel-stories/src/components/Table/Table.stories.tsx
new file mode 100644
index 000000000..d6f03f03c
--- /dev/null
+++ b/apps/look-and-feel-stories/src/components/Table/Table.stories.tsx
@@ -0,0 +1,486 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { action } from "@storybook/addon-actions";
+import {
+ Table,
+ Button,
+ Tag,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "@axa-fr/canopee-react/client";
+
+interface TableStoryArgs {
+ theadVariant?: HeadColorVariants;
+ tbodyVariant?: BodyColorVariants;
+ rowSize?: RowSizeVariants;
+ row1Size?: RowSizeVariants;
+ row2Size?: RowSizeVariants;
+ row3Size?: RowSizeVariants;
+ row4Size?: RowSizeVariants;
+}
+
+const meta: Meta = {
+ title: "Components/Table",
+ component: Table,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const BasicTable: Story = {
+ name: "Tableau basique",
+ args: {
+ theadVariant: undefined,
+ tbodyVariant: "alternate",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Prénom
+ Email
+ Téléphone
+
+
+
+
+ Dupont
+ Jean
+ jean.dupont@example.com
+ 06 12 34 56 78
+
+
+ Martin
+ Marie
+ marie.martin@example.com
+ 06 98 76 54 32
+
+
+ Bernard
+ Pierre
+ pierre.bernard@example.com
+ 06 11 22 33 44
+
+
+ Dubois
+ Sophie
+ sophie.dubois@example.com
+ 06 55 66 77 88
+
+
+
+ ),
+};
+
+export const AlternateVariantTable: Story = {
+ name: "Tableau avec couleurs alternées",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: "alternate",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Produit
+ Catégorie
+ Prix
+ Stock
+
+
+
+
+ Ordinateur Portable
+ Électronique
+ 899,00 €
+ 15
+
+
+ Souris sans fil
+ Accessoires
+ 29,99 €
+ 50
+
+
+ Clavier mécanique
+ Accessoires
+ 89,00 €
+ 23
+
+
+ Écran 27
+ Électronique
+ 299,00 €
+ 8
+
+
+
+ ),
+};
+
+export const TableWithTags: Story = {
+ name: "Tableau avec tags, statuts et tri",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: "alternate",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Référence
+ Statut
+ Client
+ Montant
+
+
+
+
+ REF-001
+
+ Validé
+
+ Jean Dupont
+ 220,00 €
+
+
+ REF-002
+
+ En attente
+
+ Marie Martin
+ 450,00 €
+
+
+ REF-003
+
+ Rejeté
+
+ Pierre Bernard
+ 180,00 €
+
+
+ REF-004
+
+ En cours
+
+ Sophie Dubois
+ 320,00 €
+
+
+
+ ),
+};
+
+export const TableWithButtons: Story = {
+ name: "Tableau avec sélection et actions",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: undefined,
+ rowSize: "M",
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ rowSize: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille des lignes",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Utilisateur
+ Email
+ Rôle
+ Actions
+
+
+
+
+ Jean Dupont
+ jean.dupont@example.com
+ Administrateur
+
+ Modifier
+
+
+
+ Marie Martin
+ marie.martin@example.com
+ Éditeur
+
+ Modifier
+
+
+
+ Pierre Bernard
+ pierre.bernard@example.com
+ Lecteur
+
+ Modifier
+
+
+
+ Sophie Dubois
+ sophie.dubois@example.com
+ Éditeur
+
+ Modifier
+
+
+
+
+ ),
+};
+
+export const TableWithDifferentSizes: Story = {
+ name: "Tableau avec tailles de lignes variées",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: "alternate",
+ row1Size: "S",
+ row2Size: "M",
+ row3Size: "L",
+ row4Size: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ row1Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 1",
+ },
+ row2Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 2",
+ },
+ row3Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 3",
+ },
+ row4Size: {
+ control: { type: "select" },
+ options: ["S", "M", "L"],
+ description: "Taille de la ligne 4",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Description
+ Prix
+ Disponibilité
+
+
+
+
+ Produit A
+ Description courte
+ 49,99 €
+ En stock
+
+
+ Produit B
+ Description de longueur moyenne pour ce produit
+ 79,99 €
+ En stock
+
+
+ Produit C
+
+ Description très détaillée avec beaucoup informations
+
+ 129,99 €
+ Stock limité
+
+
+ Produit D
+ Description standard
+ 99,99 €
+ Sur commande
+
+
+
+ ),
+};
+
+export const TableWithAlignments: Story = {
+ name: "Tableau avec alignements différents",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Article
+ Quantité
+ Prix unitaire
+ Total
+
+
+
+
+ Ordinateur
+ 1
+ 899,00 €
+ 899,00 €
+
+
+ Souris
+ 2
+ 29,99 €
+ 59,98 €
+
+
+ Clavier
+ 1
+ 89,00 €
+ 89,00 €
+
+
+ Câble HDMI
+ 3
+ 15,99 €
+ 47,97 €
+
+
+
+ ),
+};
+
+export const CompactTable: Story = {
+ name: "Tableau compact (3 colonnes)",
+ args: {
+ theadVariant: "gray",
+ tbodyVariant: undefined,
+ },
+ argTypes: {
+ theadVariant: {
+ control: { type: "select" },
+ options: ["gray", "blue"],
+ description: "Variant de l'en-tête du tableau",
+ },
+ tbodyVariant: {
+ control: { type: "select" },
+ options: ["white", "blue", "alternate"],
+ description: "Variant du corps du tableau",
+ },
+ },
+ render: (args: TableStoryArgs) => (
+
+
+
+ Nom
+ Statut
+ Date
+
+
+
+
+ Projet Alpha
+
+ Terminé
+
+ 10/01/2026
+
+
+ Projet Beta
+
+ En cours
+
+ 15/01/2026
+
+
+ Projet Gamma
+
+ Planifié
+
+ 20/01/2026
+
+
+ Projet Delta
+
+ Annulé
+
+ 05/01/2026
+
+
+
+ ),
+};
diff --git a/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.mdx b/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.mdx
new file mode 100644
index 000000000..7dc240506
--- /dev/null
+++ b/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.mdx
@@ -0,0 +1,47 @@
+import { Canvas, Controls, Meta } from "@storybook/addon-docs";
+import * as TableMobileCardStories from "./TableMobileCard.stories";
+
+
+
+# TableMobileCard
+
+> ⚠️ This component should not be used alone. It is the mobile version for a table.
+
+To use the table mobile card import it like that:
+
+```tsx
+import { TableMobileCard } from "@axa-fr/canopee-react/client";
+
+const MyComponent = () => (
+
+
+ Produit/Support
+ AB Sustainable Global Thematic A
+
+
+ Code Isin
+ LU0101010101
+
+
+ Status
+ Ouvert
+
+
+);
+```
+
+## Usage
+
+The TableMobileCard component is a compound component with the following sub-components:
+
+- `TableMobileCard.DRow` - TableMobileCard row wrapper with optional `direction` prop
+- `TableMobileCard.Dt` - Table row title
+- `TableMobileCard.Dd` - Table row description
+
+## Examples
+
+### Basic Table
+
+
+
+
diff --git a/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx b/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx
new file mode 100644
index 000000000..d64301dd8
--- /dev/null
+++ b/apps/look-and-feel-stories/src/components/TableMobileCard/TableMobileCard.stories.tsx
@@ -0,0 +1,94 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { action } from "@storybook/addon-actions";
+import { Button, Icon, TableMobileCard } from "@axa-fr/canopee-react/client";
+import download from "@material-symbols/svg-400/outlined/download_2-fill.svg";
+
+interface TableMobileCardStoryArgs {
+ variant?: "alternate" | "blue" | "white";
+ direction?: "row" | "column";
+}
+
+const meta: Meta = {
+ title: "Components/TableMobileCard",
+ component: TableMobileCard,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const BasicTableMobileCard: Story = {
+ name: "Table Card basique",
+ args: {
+ variant: "alternate",
+ direction: "row",
+ },
+ argTypes: {
+ variant: {
+ control: { type: "select" },
+ options: ["alternate", "white", "blue"],
+ description: "Variant de la couleur des lignes",
+ },
+ direction: {
+ control: { type: "select" },
+ options: ["row", "column"],
+ description: "Variant de la disposition des éléments d'une ligne",
+ },
+ },
+ render: (args: TableMobileCardStoryArgs) => (
+
+
+ Produit/Support
+
+ AB Sustainable Global Thematic A
+
+
+
+ Code Isin
+ LU0101010101
+
+
+ Status
+ Ouvert
+
+
+ Qualification SFDR
+ 9
+
+
+ DIC/DIS/DICI
+
+ }
+ >
+ Télécharger
+
+
+
+
+ Prospectus
+
+ }
+ onClick={action("download_1")}
+ >
+ Télécharger
+
+
+
+
+ Rapport annuelle
+
+ }
+ >
+ Télécharger
+
+
+
+
+ ),
+};
diff --git a/apps/look-and-feel-stories/src/pages/Table/Table.css b/apps/look-and-feel-stories/src/pages/Table/Table.css
new file mode 100644
index 000000000..92bdd70b0
--- /dev/null
+++ b/apps/look-and-feel-stories/src/pages/Table/Table.css
@@ -0,0 +1,32 @@
+.table-scroll {
+ margin-inline: calc(-1 * var(--margin-inline));
+ padding-inline: var(--margin-inline);
+ overflow: auto hidden;
+}
+
+main.table-page {
+ --font-size-base: 16;
+
+ padding-block: calc(40 / var(--font-size-base) * 1rem);
+
+ @media (width > 1023px) {
+ padding-block: calc(48 / var(--font-size-base) * 1rem);
+ }
+
+ > section.subgrid {
+ row-gap: calc(28 / var(--font-size-base) * 1rem);
+
+ @media (width > 1023px) {
+ --row-gap: calc(48 / var(--font-size-base) * 1rem);
+ }
+
+ .table-page__dropdown {
+ --cols: 4;
+ }
+
+ .table-page__pagination {
+ width: fit-content;
+ justify-self: center;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/look-and-feel-stories/src/pages/Table/Table.stories.tsx b/apps/look-and-feel-stories/src/pages/Table/Table.stories.tsx
new file mode 100644
index 000000000..7096dfaed
--- /dev/null
+++ b/apps/look-and-feel-stories/src/pages/Table/Table.stories.tsx
@@ -0,0 +1,16 @@
+import "./Table.css";
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { TablePage } from "./Table";
+
+const meta: Meta = {
+ title: "Pages/Table",
+ parameters: { layout: "fullscreen" },
+};
+
+export default meta;
+
+export const StepSortie: StoryObj = {
+ name: "Table",
+ render: TablePage,
+};
diff --git a/apps/look-and-feel-stories/src/pages/Table/Table.tsx b/apps/look-and-feel-stories/src/pages/Table/Table.tsx
new file mode 100644
index 000000000..c10b7fd2e
--- /dev/null
+++ b/apps/look-and-feel-stories/src/pages/Table/Table.tsx
@@ -0,0 +1,450 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions */
+/* eslint-disable-next-line import/no-extraneous-dependencies */
+import React, {
+ useCallback,
+ useMemo,
+ useState,
+ useSyncExternalStore,
+} from "react";
+import {
+ Button,
+ DebugGrid,
+ Dropdown,
+ Pagination,
+ Table,
+ TableMobileCard,
+ Tag,
+ Heading,
+} from "@axa-fr/canopee-react/client";
+
+const useIsSmallScreen = (breakPointToCheck: number) => {
+ const subscribe = useCallback((listener: () => void) => {
+ window.addEventListener("resize", listener);
+ return () => {
+ window.removeEventListener("resize", listener);
+ };
+ }, []);
+
+ const getSnapshot = useCallback(() => {
+ return window.innerWidth <= breakPointToCheck;
+ }, [breakPointToCheck]);
+
+ const getServerSnapshot = useCallback(() => false, []);
+
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
+};
+
+interface DataRow {
+ id: number;
+ reference: string;
+ societe: string;
+ contact: string;
+ email: string;
+ telephone: string;
+ montant: number;
+ statut: "Validé" | "En attente" | "Rejeté" | "En cours";
+ date: string;
+}
+
+const ALL_DATA: DataRow[] = [
+ {
+ id: 1,
+ reference: "CON-2024-001",
+ societe: "Société ABC",
+ contact: "Jean Dupont",
+ email: "jean.dupont@abc.fr",
+ telephone: "06 12 34 56 78",
+ montant: 1250.0,
+ statut: "Validé",
+ date: "15/01/2024",
+ },
+ {
+ id: 2,
+ reference: "CON-2024-002",
+ societe: "Entreprise XYZ",
+ contact: "Marie Martin",
+ email: "marie.martin@xyz.fr",
+ telephone: "06 98 76 54 32",
+ montant: 2500.0,
+ statut: "En attente",
+ date: "22/03/2024",
+ },
+ {
+ id: 3,
+ reference: "CON-2024-003",
+ societe: "Groupe DEF",
+ contact: "Pierre Bernard",
+ email: "pierre.bernard@def.fr",
+ telephone: "06 11 22 33 44",
+ montant: 3750.0,
+ statut: "Rejeté",
+ date: "10/06/2024",
+ },
+ {
+ id: 4,
+ reference: "CON-2024-004",
+ societe: "Industries GHI",
+ contact: "Sophie Dubois",
+ email: "sophie.dubois@ghi.fr",
+ telephone: "06 55 66 77 88",
+ montant: 890.0,
+ statut: "En cours",
+ date: "05/09/2024",
+ },
+ {
+ id: 5,
+ reference: "CON-2024-005",
+ societe: "Services JKL",
+ contact: "Luc Moreau",
+ email: "luc.moreau@jkl.fr",
+ telephone: "06 22 33 44 55",
+ montant: 1780.0,
+ statut: "Validé",
+ date: "12/11/2024",
+ },
+ {
+ id: 6,
+ reference: "CON-2024-006",
+ societe: "Tech MNO",
+ contact: "Claire Petit",
+ email: "claire.petit@mno.fr",
+ telephone: "06 77 88 99 00",
+ montant: 4200.0,
+ statut: "En attente",
+ date: "28/12/2024",
+ },
+ {
+ id: 7,
+ reference: "CON-2024-007",
+ societe: "Solutions PQR",
+ contact: "Marc Lefebvre",
+ email: "marc.lefebvre@pqr.fr",
+ telephone: "06 33 44 55 66",
+ montant: 920.0,
+ statut: "Validé",
+ date: "03/01/2025",
+ },
+ {
+ id: 8,
+ reference: "CON-2024-008",
+ societe: "Digital STU",
+ contact: "Anne Rousseau",
+ email: "anne.rousseau@stu.fr",
+ telephone: "06 44 55 66 77",
+ montant: 1560.0,
+ statut: "En cours",
+ date: "18/02/2025",
+ },
+ {
+ id: 9,
+ reference: "CON-2024-009",
+ societe: "Consulting VWX",
+ contact: "Thomas Garnier",
+ email: "thomas.garnier@vwx.fr",
+ telephone: "06 88 99 00 11",
+ montant: 3100.0,
+ statut: "Validé",
+ date: "25/03/2025",
+ },
+ {
+ id: 10,
+ reference: "CON-2024-010",
+ societe: "Innovation YZ",
+ contact: "Julie Faure",
+ email: "julie.faure@yz.fr",
+ telephone: "06 99 00 11 22",
+ montant: 2890.0,
+ statut: "En attente",
+ date: "07/04/2025",
+ },
+ {
+ id: 11,
+ reference: "CON-2024-011",
+ societe: "Partners AB",
+ contact: "Vincent Leroux",
+ email: "vincent.leroux@ab.fr",
+ telephone: "06 00 11 22 33",
+ montant: 1450.0,
+ statut: "Rejeté",
+ date: "14/05/2025",
+ },
+ {
+ id: 12,
+ reference: "CON-2024-012",
+ societe: "Business CD",
+ contact: "Isabelle Simon",
+ email: "isabelle.simon@cd.fr",
+ telephone: "06 11 22 33 44",
+ montant: 3680.0,
+ statut: "Validé",
+ date: "22/06/2025",
+ },
+ {
+ id: 13,
+ reference: "CON-2024-013",
+ societe: "Global EF",
+ contact: "Nicolas Laurent",
+ email: "nicolas.laurent@ef.fr",
+ telephone: "06 22 33 44 55",
+ montant: 2100.0,
+ statut: "En cours",
+ date: "30/07/2025",
+ },
+ {
+ id: 14,
+ reference: "CON-2024-014",
+ societe: "Pro GH",
+ contact: "Sandrine Michel",
+ email: "sandrine.michel@gh.fr",
+ telephone: "06 33 44 55 66",
+ montant: 1890.0,
+ statut: "En attente",
+ date: "08/08/2025",
+ },
+ {
+ id: 15,
+ reference: "CON-2024-015",
+ societe: "Expert IJ",
+ contact: "Julien Leroy",
+ email: "julien.leroy@ij.fr",
+ telephone: "06 44 55 66 77",
+ montant: 4500.0,
+ statut: "Validé",
+ date: "15/09/2025",
+ },
+];
+
+const STATUS_VARIANTS: Record<
+ DataRow["statut"],
+ "success" | "warning" | "error" | "info"
+> = {
+ Validé: "success",
+ "En attente": "warning",
+ Rejeté: "error",
+ "En cours": "info",
+};
+
+const getStatusTag = (statut: DataRow["statut"]) => (
+ {statut}
+);
+
+type SortConfig = { key: keyof DataRow; direction: "asc" | "desc" } | null;
+
+export const TablePage = () => {
+ const [sortConfig, setSortConfig] = useState(null);
+ const [checkedColumns, setCheckedColumns] = useState>(
+ () => new Set(),
+ );
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPage, setItemsPerPage] = useState(5);
+
+ const isMobile = useIsSmallScreen(667);
+
+ const handleSort = useCallback((key: keyof DataRow) => {
+ setSortConfig((prev) => {
+ const direction: "asc" | "desc" =
+ prev?.key === key && prev.direction === "asc" ? "desc" : "asc";
+ return { key, direction };
+ });
+ setCurrentPage(1);
+ }, []);
+
+ const handleColumnCheck = useCallback((columnIndex: number) => {
+ setCheckedColumns((prev) => {
+ const next = new Set(prev);
+ next.has(columnIndex) ? next.delete(columnIndex) : next.add(columnIndex);
+ return next;
+ });
+ }, []);
+
+ const getColumnVariant = useCallback(
+ (columnIndex: number) => {
+ return checkedColumns.has(columnIndex) ? "blue" : undefined;
+ },
+ [checkedColumns],
+ );
+
+ const sortedData = useMemo(() => {
+ if (!sortConfig) return ALL_DATA;
+
+ const { key, direction } = sortConfig;
+
+ return [...ALL_DATA].sort((a, b) => {
+ const aValue = a[key];
+ const bValue = b[key];
+
+ // numbers
+ if (typeof aValue === "number" && typeof bValue === "number") {
+ return direction === "asc" ? aValue - bValue : bValue - aValue;
+ }
+
+ // everything else as string
+ const aStr = String(aValue);
+ const bStr = String(bValue);
+ return direction === "asc"
+ ? aStr.localeCompare(bStr)
+ : bStr.localeCompare(aStr);
+ });
+ }, [sortConfig]);
+
+ const totalPages = useMemo(
+ () => Math.max(1, Math.ceil(sortedData.length / itemsPerPage)),
+ [sortedData.length, itemsPerPage],
+ );
+
+ const currentData = useMemo(() => {
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ return sortedData.slice(startIndex, endIndex);
+ }, [sortedData, currentPage, itemsPerPage]);
+
+ const handlePageChange = useCallback(
+ (page: number) => setCurrentPage(page),
+ [],
+ );
+
+ const handleItemsPerPageChange = useCallback(
+ (event: React.ChangeEvent) => {
+ setItemsPerPage(Number(event.target.value));
+ setCurrentPage(1);
+ },
+ [],
+ );
+
+ return (
+ <>
+
+
+
+ Liste des produits
+ {!isMobile && (
+ <>
+
+
+ 5
+ 10
+ 15
+ 20
+
+
+
+
+
+
+
+ handleColumnCheck(0)}
+ onSort={() => handleSort("reference")}
+ >
+ Référence
+
+ handleColumnCheck(1)}
+ onSort={() => handleSort("societe")}
+ >
+ Société
+
+ handleColumnCheck(2)}
+ onSort={() => handleSort("contact")}
+ >
+ Contact
+
+ handleColumnCheck(3)}
+ onSort={() => handleSort("montant")}
+ >
+ Montant
+
+ handleColumnCheck(4)}
+ onSort={() => handleSort("statut")}
+ >
+ Statut
+
+
+
+
+
+ {currentData.map((row) => (
+
+
+ {row.reference}
+
+
+ {row.societe}
+
+
+ {row.contact}
+
+
+ {row.montant.toFixed(2)} €
+
+
+ {getStatusTag(row.statut)}
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+
+ {isMobile ? (
+ <>
+ {sortedData.map((elem) => (
+
+
+ Référence
+ {elem.reference}
+
+
+
+ Société
+ {elem.societe}
+
+
+
+ Contact
+ {elem.contact}
+
+
+
+ Montant
+
+ {elem.montant.toFixed(2)} €
+
+
+
+
+ Status
+
+ {getStatusTag(elem.statut)}
+
+
+
+ ))}
+ >
+ ) : null}
+
+
+ >
+ );
+};
diff --git a/package-lock.json b/package-lock.json
index a7eca8a3c..3cef398ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4905,10 +4905,12 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.2.2",
+ "version": "19.2.13",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz",
+ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
"license": "MIT",
"dependencies": {
- "csstype": "^3.0.2"
+ "csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
@@ -7048,7 +7050,9 @@
"license": "MIT"
},
"node_modules/csstype": {
- "version": "3.1.3",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
diff --git a/package.json b/package.json
index 4daf6e124..1c3eb5808 100644
--- a/package.json
+++ b/package.json
@@ -61,4 +61,4 @@
"node": "22.21.0",
"npm": "11.6.4"
}
-}
\ No newline at end of file
+}
diff --git a/packages/canopee-css/src/prospect-client/Table/TableApollo.css b/packages/canopee-css/src/prospect-client/Table/TableApollo.css
new file mode 100644
index 000000000..5fa17ad59
--- /dev/null
+++ b/packages/canopee-css/src/prospect-client/Table/TableApollo.css
@@ -0,0 +1,5 @@
+@import "./TableCommon.css";
+
+.af-table {
+ --table-header-bg-blue: var(--blue-080);
+}
diff --git a/packages/canopee-css/src/prospect-client/Table/TableCommon.css b/packages/canopee-css/src/prospect-client/Table/TableCommon.css
new file mode 100644
index 000000000..7418818c8
--- /dev/null
+++ b/packages/canopee-css/src/prospect-client/Table/TableCommon.css
@@ -0,0 +1,157 @@
+.af-table {
+ all: unset;
+ display: table;
+ box-sizing: border-box;
+ width: 100%;
+ border-collapse: collapse;
+ line-height: var(--rem-20);
+
+ .af-table__th {
+ padding: 0;
+
+ .af-table__th-wrapper {
+ display: flex;
+ box-sizing: border-box;
+ height: 100%;
+ padding: var(--rem-16);
+ place-items: center;
+
+ .af-table__th-sort-icon {
+ width: 40px;
+ cursor: pointer;
+ }
+ }
+
+ .af-table__th-content {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ font-size: var(--rem-16ptable);
+ font-weight: 600;
+ text-align: left;
+ }
+
+ &.af-table__th--center .af-table__th-content {
+ justify-content: center;
+ text-align: center;
+ }
+
+ &.af-table__th--right .af-table__th-content {
+ justify-content: flex-end;
+ text-align: right;
+ }
+
+ &:has(input[type="checkbox"]) {
+ .af-table__th-content {
+ padding-left: var(--rem-16);
+ }
+
+ &.af-table__th--checkbox-right {
+ .af-table__th-wrapper {
+ flex-direction: row-reverse;
+ }
+
+ .af-table__th-content {
+ padding-right: var(--rem-16);
+ padding-left: 0;
+ }
+ }
+ }
+ }
+
+ .af-table__td {
+ box-sizing: border-box;
+ padding-inline: var(--rem-16);
+ white-space: normal;
+
+ &.af-table__td--left {
+ text-align: left;
+ }
+
+ &.af-table__td--center {
+ text-align: center;
+ }
+
+ &.af-table__td--right {
+ text-align: right;
+ }
+
+ &.af-table__td--top {
+ padding-top: var(--rem-16);
+ vertical-align: top;
+ }
+
+ &.af-table__td--middle {
+ vertical-align: middle;
+ }
+
+ &.af-table__td--small {
+ padding-block: calc(10 / var(--font-size-base) * 1rem);
+ }
+
+ &.af-table__td--medium {
+ padding-block: calc(18 / var(--font-size-base) * 1rem);
+ }
+
+ &.af-table__td--large {
+ padding-block: calc(30 / var(--font-size-base) * 1rem);
+ }
+ }
+
+ .af-table__tr {
+ border-bottom: 1px solid var(--blue-200);
+
+ .af-table__td {
+ padding-block: calc(10 / var(--font-size-base) * 1rem);
+ }
+
+ &.af-table__tr--medium {
+ .af-table__td {
+ padding-block: calc(18 / var(--font-size-base) * 1rem);
+ }
+ }
+
+ &.af-table__tr--large {
+ .af-table__td {
+ padding-block: calc(30 / var(--font-size-base) * 1rem);
+ }
+ }
+ }
+
+ .af-table__thead {
+ &.af-table__thead--gray .af-table__th {
+ color: var(--gray-1000);
+ background-color: var(--gray-050);
+ }
+
+ &.af-table__thead--blue .af-table__th {
+ color: var(--blue-1000);
+ background-color: var(--table-header-bg-blue);
+ }
+ }
+
+ .af-table__tbody {
+ font-weight: 600;
+ color: var(--gray-1000);
+
+ &.af-table__tbody--white .af-table__tr,
+ & .af-table__td--white {
+ background: var(--white-1000);
+ }
+
+ &.af-table__tbody--blue .af-table__tr,
+ & .af-table__td--blue {
+ background: var(--blue-040);
+ }
+
+ &.af-table__tbody--alternate .af-table__tr {
+ &:nth-child(even) {
+ background: var(--blue-040);
+ }
+
+ &:nth-child(odd) {
+ background: var(--white-1000);
+ }
+ }
+ }
+}
diff --git a/packages/canopee-css/src/prospect-client/Table/TableLF.css b/packages/canopee-css/src/prospect-client/Table/TableLF.css
new file mode 100644
index 000000000..e99ca9d00
--- /dev/null
+++ b/packages/canopee-css/src/prospect-client/Table/TableLF.css
@@ -0,0 +1,5 @@
+@import "./TableCommon.css";
+
+.af-table {
+ --table-header-bg-blue: var(--blue-100);
+}
diff --git a/packages/canopee-css/src/prospect-client/TableMobileCard/TableMobileCardAll.css b/packages/canopee-css/src/prospect-client/TableMobileCard/TableMobileCardAll.css
new file mode 100644
index 000000000..cb25f88e4
--- /dev/null
+++ b/packages/canopee-css/src/prospect-client/TableMobileCard/TableMobileCardAll.css
@@ -0,0 +1,64 @@
+.af-table-mobile-card {
+ border: 1px solid var(--gray-250);
+ border-radius: var(--radius-8);
+ overflow: hidden;
+ font-size: calc(16 / var(--font-size-base) * 1rem);
+ line-height: calc(20 / var(--font-size-base) * 1rem);
+ color: var(--gray-1000);
+
+ .af-table-mobile-card__drow {
+ display: flex;
+ padding: var(--rem-16);
+ border-bottom: 1px solid var(--blue-200);
+ justify-content: space-between;
+ gap: var(--rem-16);
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ & dt.af-table-mobile-card__dt {
+ width: 50%;
+ font-weight: 400;
+ }
+
+ & dd.af-table-mobile-card__dd {
+ width: 50%;
+ justify-items: end;
+ font-weight: 600;
+ text-align: right;
+ }
+
+ &.af-table-mobile-card__drow--column {
+ flex-direction: column;
+
+ & dt.af-table-mobile-card__dt {
+ width: 100%;
+ }
+
+ & dd.af-table-mobile-card__dd {
+ width: 100%;
+ justify-items: start;
+ text-align: left;
+ }
+ }
+ }
+
+ &.af-table-mobile-card--white .af-table-mobile-card__drow {
+ background-color: var(--white-1000);
+ }
+
+ &.af-table-mobile-card--blue .af-table-mobile-card__drow {
+ background-color: var(--blue-040);
+ }
+
+ &.af-table-mobile-card--alternate {
+ .af-table-mobile-card__drow:nth-child(even) {
+ background-color: var(--blue-040);
+ }
+
+ .af-table-mobile-card__drow:nth-child(odd) {
+ background-color: var(--white-1000);
+ }
+ }
+}
diff --git a/packages/canopee-css/src/prospect-client/client.css b/packages/canopee-css/src/prospect-client/client.css
index 0dadffd29..4cf4177a1 100644
--- a/packages/canopee-css/src/prospect-client/client.css
+++ b/packages/canopee-css/src/prospect-client/client.css
@@ -54,4 +54,6 @@
@import "./List/List/ListLF.css";
@import "./LevelSelector/LevelSelectorLF.css";
@import "./Skeleton/SkeletonLF.css";
+@import "./Table/TableLF.css";
@import "./Fieldset/FieldsetLF.css";
+@import "./TableMobileCard/TableMobileCardAll.css";
diff --git a/packages/canopee-css/src/prospect-client/prospect.css b/packages/canopee-css/src/prospect-client/prospect.css
index c7b4d8a13..4ff48d26b 100644
--- a/packages/canopee-css/src/prospect-client/prospect.css
+++ b/packages/canopee-css/src/prospect-client/prospect.css
@@ -54,4 +54,6 @@
@import "./List/List/ListApollo.css";
@import "./LevelSelector/LevelSelectorApollo.css";
@import "./Skeleton/SkeletonApollo.css";
+@import "./Table/TableApollo.css";
@import "./Fieldset/FieldsetApollo.css";
+@import "./TableMobileCard/TableMobileCardAll.css";
diff --git a/packages/canopee-react/src/client.ts b/packages/canopee-react/src/client.ts
index 8a4287d6e..d5a5e1dcd 100644
--- a/packages/canopee-react/src/client.ts
+++ b/packages/canopee-react/src/client.ts
@@ -162,3 +162,14 @@ export {
} from "./prospect-client/Tag/TagLF";
export { TimelineVertical } from "./prospect-client/TimelineVertical/TimelineVerticalLF";
export { Toggle } from "./prospect-client/Toggle/ToggleLF";
+export {
+ Table,
+ type TableProps,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "./prospect-client/Table/TableLF";
+export {
+ TableMobileCard,
+ type TableMobileCardProps,
+} from "./prospect-client/TableMobileCard/TableMobileCard";
diff --git a/packages/canopee-react/src/prospect-client/Table/TBody.tsx b/packages/canopee-react/src/prospect-client/Table/TBody.tsx
new file mode 100644
index 000000000..19e06e290
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/TBody.tsx
@@ -0,0 +1,26 @@
+import { ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type BodyColorVariants = "white" | "blue" | "alternate";
+
+export type TBodyProps = ComponentPropsWithRef<"tbody"> & {
+ variant?: BodyColorVariants;
+};
+
+export const TBody = ({
+ variant = "white",
+ className,
+ children,
+ ...tableBodyProps
+}: TBodyProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table__tbody",
+ className,
+ modifiers: [variant],
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/Table/THead.tsx b/packages/canopee-react/src/prospect-client/Table/THead.tsx
new file mode 100644
index 000000000..2292d2f05
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/THead.tsx
@@ -0,0 +1,26 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type HeadColorVariants = "gray" | "blue";
+
+export type THeadProps = ComponentPropsWithRef<"thead"> & {
+ variant?: HeadColorVariants;
+};
+
+export const THead = ({
+ variant = "blue",
+ className,
+ children,
+ ...tableHeadProps
+}: THeadProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table__thead",
+ className,
+ modifiers: [variant],
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/Table/TableApollo.tsx b/packages/canopee-react/src/prospect-client/Table/TableApollo.tsx
new file mode 100644
index 000000000..034b0d80d
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/TableApollo.tsx
@@ -0,0 +1,9 @@
+import "@axa-fr/canopee-css/prospect/Table/TableApollo.css";
+
+export {
+ Table,
+ type TableProps,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "./TableCommon";
diff --git a/packages/canopee-react/src/prospect-client/Table/TableCommon.tsx b/packages/canopee-react/src/prospect-client/Table/TableCommon.tsx
new file mode 100644
index 000000000..9edfee9e7
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/TableCommon.tsx
@@ -0,0 +1,28 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+import { Td } from "./Td";
+import { Tr, type RowSizeVariants } from "./Tr";
+import { Th } from "./Th";
+import { TBody, type BodyColorVariants } from "./TBody";
+import { THead, type HeadColorVariants } from "./THead";
+
+export type { HeadColorVariants, BodyColorVariants, RowSizeVariants };
+export type TableProps = ComponentPropsWithRef<"table">;
+
+export const Table = ({ className, children, ...tableProps }: TableProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table",
+ className,
+ });
+ return (
+
+ );
+};
+
+Table.THead = THead;
+Table.TBody = TBody;
+Table.Th = Th;
+Table.Tr = Tr;
+Table.Td = Td;
diff --git a/packages/canopee-react/src/prospect-client/Table/TableLF.tsx b/packages/canopee-react/src/prospect-client/Table/TableLF.tsx
new file mode 100644
index 000000000..0f4dae886
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/TableLF.tsx
@@ -0,0 +1,9 @@
+import "@axa-fr/canopee-css/client/Table/TableLF.css";
+
+export {
+ Table,
+ type TableProps,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "./TableCommon";
diff --git a/packages/canopee-react/src/prospect-client/Table/Td.tsx b/packages/canopee-react/src/prospect-client/Table/Td.tsx
new file mode 100644
index 000000000..c2b03c7dc
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/Td.tsx
@@ -0,0 +1,40 @@
+import { ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type CellContentPositionVariants = "left" | "center" | "right";
+export type CellContentVerticalAlignVariants = "top" | "middle";
+export const tdSizeVariants = {
+ L: "large",
+ M: "medium",
+ S: "small",
+} as const;
+export type TdSizeVariants = keyof typeof tdSizeVariants;
+export type CellColorVariant = "white" | "blue";
+
+export type TdProps = ComponentPropsWithRef<"td"> & {
+ position?: CellContentPositionVariants;
+ verticalAlign?: CellContentVerticalAlignVariants;
+ size?: TdSizeVariants;
+ variant?: CellColorVariant;
+};
+
+export const Td = ({
+ position = "left",
+ verticalAlign = "middle",
+ size,
+ variant,
+ className,
+ children,
+ ...tableCellProps
+}: TdProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table__td",
+ className,
+ modifiers: [position, verticalAlign, size && tdSizeVariants[size], variant],
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/Table/Th.tsx b/packages/canopee-react/src/prospect-client/Table/Th.tsx
new file mode 100644
index 000000000..dd7aa07e7
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/Th.tsx
@@ -0,0 +1,46 @@
+import { type ComponentPropsWithRef } from "react";
+import unfoldMore from "@material-symbols/svg-400/rounded/unfold_more-fill.svg";
+import { getClassName } from "../utilities/getClassName";
+import { Checkbox } from "../Form/Checkbox/Checkbox/CheckboxCommon";
+import { ClickIcon } from "../ClickIcon/ClickIconCommon";
+
+export type HeaderCellPositionVariants = "left" | "center" | "right";
+
+export type ThProps = ComponentPropsWithRef<"th"> & {
+ position?: HeaderCellPositionVariants;
+ checkboxPosition?: HeaderCellPositionVariants;
+ onCheck?: () => void;
+ onSort?: () => void;
+};
+
+export const Th = ({
+ position = "left",
+ onCheck,
+ checkboxPosition = "left",
+ onSort,
+ className,
+ children,
+ ...tableHeaderProps
+}: ThProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table__th",
+ className,
+ modifiers: [position, checkboxPosition && `checkbox-${checkboxPosition}`],
+ });
+ return (
+
+
+ {onCheck ? : null}
+ {children}
+ {onSort ? (
+
+ ) : null}
+
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/Table/Tr.tsx b/packages/canopee-react/src/prospect-client/Table/Tr.tsx
new file mode 100644
index 000000000..3d49ed79b
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/Table/Tr.tsx
@@ -0,0 +1,31 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export const rowSizeVariants = {
+ L: "large",
+ M: "medium",
+ S: "small",
+} as const;
+export type RowSizeVariants = keyof typeof rowSizeVariants;
+
+export type TrProps = ComponentPropsWithRef<"tr"> & {
+ size?: RowSizeVariants;
+};
+
+export const Tr = ({
+ size = "S",
+ className,
+ children,
+ ...tableRowProps
+}: TrProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table__tr",
+ className,
+ modifiers: [rowSizeVariants[size]],
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/TableMobileCard/DRow.tsx b/packages/canopee-react/src/prospect-client/TableMobileCard/DRow.tsx
new file mode 100644
index 000000000..1e1180948
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/TableMobileCard/DRow.tsx
@@ -0,0 +1,26 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type DRowDirectionVariants = "row" | "column";
+
+export type TrProps = ComponentPropsWithRef<"div"> & {
+ direction?: DRowDirectionVariants;
+};
+
+export const DRow = ({
+ className,
+ children,
+ direction = "row",
+ ...dRowProps
+}: TrProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table-mobile-card__drow",
+ className,
+ modifiers: [direction],
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/TableMobileCard/Dd.tsx b/packages/canopee-react/src/prospect-client/TableMobileCard/Dd.tsx
new file mode 100644
index 000000000..d73caf456
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/TableMobileCard/Dd.tsx
@@ -0,0 +1,16 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type TrProps = ComponentPropsWithRef<"dd">;
+
+export const Dd = ({ className, children, ...ddProps }: TrProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table-mobile-card__dd",
+ className,
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/TableMobileCard/Dt.tsx b/packages/canopee-react/src/prospect-client/TableMobileCard/Dt.tsx
new file mode 100644
index 000000000..58c44989b
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/TableMobileCard/Dt.tsx
@@ -0,0 +1,16 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+
+export type TrProps = ComponentPropsWithRef<"dt">;
+
+export const Dt = ({ className, children, ...dtProps }: TrProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table-mobile-card__dt",
+ className,
+ });
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/canopee-react/src/prospect-client/TableMobileCard/TableMobileCard.tsx b/packages/canopee-react/src/prospect-client/TableMobileCard/TableMobileCard.tsx
new file mode 100644
index 000000000..243d72c97
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/TableMobileCard/TableMobileCard.tsx
@@ -0,0 +1,36 @@
+import { type ComponentPropsWithRef } from "react";
+import { getClassName } from "../utilities/getClassName";
+import { DRow } from "./DRow";
+import { Dt } from "./Dt";
+import { Dd } from "./Dd";
+
+import "@axa-fr/canopee-css/prospect/TableMobileCard/TableMobileCardAll.css";
+
+export type TableMobileCardVariants = "white" | "blue" | "alternate";
+
+export type TableMobileCardProps = ComponentPropsWithRef<"dl"> & {
+ variant?: TableMobileCardVariants;
+};
+
+export const TableMobileCard = ({
+ className,
+ children,
+ variant = "alternate",
+ ...tableCardProps
+}: TableMobileCardProps) => {
+ const componentClassName = getClassName({
+ baseClassName: "af-table-mobile-card",
+ className,
+ modifiers: [variant],
+ });
+
+ return (
+
+ {children}
+
+ );
+};
+
+TableMobileCard.DRow = DRow;
+TableMobileCard.Dt = Dt;
+TableMobileCard.Dd = Dd;
diff --git a/packages/canopee-react/src/prospect-client/TableMobileCard/__test__/TableMobileCard.test.tsx b/packages/canopee-react/src/prospect-client/TableMobileCard/__test__/TableMobileCard.test.tsx
new file mode 100644
index 000000000..9c6ad4b51
--- /dev/null
+++ b/packages/canopee-react/src/prospect-client/TableMobileCard/__test__/TableMobileCard.test.tsx
@@ -0,0 +1,40 @@
+import { render, screen } from "@testing-library/react";
+import { TableMobileCard } from "../TableMobileCard";
+
+describe("TableMobileCard", () => {
+ it("should render correctly", () => {
+ render(
+
+
+ Nom
+ Dupont
+
+
+ Nom
+ Dupont
+
+ ,
+ );
+
+ const dt = screen.getByTestId("firstDRow");
+ const dds = screen.getAllByRole("definition");
+
+ expect(dt).toHaveClass("af-table-mobile-card__drow--row");
+ expect(dds).toHaveLength(2);
+ });
+
+ it("should render vertical row correctly", () => {
+ render(
+
+
+ Nom
+ Dupont
+
+ ,
+ );
+
+ const dt = screen.getByTestId("firstDRow");
+
+ expect(dt).toHaveClass("af-table-mobile-card__drow--column");
+ });
+});
diff --git a/packages/canopee-react/src/prospect.ts b/packages/canopee-react/src/prospect.ts
index ba9471a1b..019a08149 100644
--- a/packages/canopee-react/src/prospect.ts
+++ b/packages/canopee-react/src/prospect.ts
@@ -154,3 +154,11 @@ export {
} from "./prospect-client/Tag/TagApollo";
export { TimelineVertical } from "./prospect-client/TimelineVertical/TimelineVerticalApollo";
export { Toggle } from "./prospect-client/Toggle/ToggleApollo";
+export {
+ Table,
+ type TableProps,
+ type HeadColorVariants,
+ type BodyColorVariants,
+ type RowSizeVariants,
+} from "./prospect-client/Table/TableApollo";
+export { TableMobileCard } from "./prospect-client/TableMobileCard/TableMobileCard";