Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/lib/components/Table/Table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,58 @@ will be called for each row. This function should return a string which is the c
type.

<Canvas of={Table.RowColoring} />

## ColSpan

**colSpan** merges adjacent cells horizontally within a row. It can be provided as either a static number or a function.

#### Static colSpan

A static number merges the column with the given number of following columns everywhere β€” header, filter row, and body
cells all collapse together. The absorbed columns' header labels and filter inputs are hidden, since they no longer have
a visible cell to render into.

```tsx
const columns = [
{ title: "Personal Info", dataKey: "fullName", colSpan: 2 },
{ title: "Age", dataKey: "age" },
{ title: "City", dataKey: "city" },
];
```

If an absorbed column has a **footer**, its computed value (sum, avg, etc.) is still surfaced inside the spanning footer
cell, so aggregated data is never silently lost.

#### Function colSpan

When **colSpan** is a function, it receives the row's data object and returns the span value for that row. This form
applies **only to body cells** β€” the header and filter row always show all column labels, since they have no row data
to evaluate the function with. This makes function colSpan ideal for row-dependent merging where some rows should be
collapsed and others should not.

<Canvas of={Table.Colspan} />

## RowSpan

**rowSpan** merges a cell downward across consecutive rows. Like colSpan, it can be provided as a static number or a
function that receives the row's data object.

A common pattern is to span grouped rows together: when the first row of a group returns the group size as the span
value, subsequent rows in the group return 1 (or omit the prop), and the first row's cell visually covers all of them.

```tsx
const columns = [
{
title: "Name",
dataKey: "name",
rowSpan: (row) => row.surname === "Surname 1" || row.surname === "Surname 3" ? 2 : 1,
},
{ title: "Surname", dataKey: "surname" },
{ title: "Age", dataKey: "age" },
];
```

The table automatically suppresses the covered cells so the DOM stays valid. RowSpan is clamped to the number of
remaining rows on the current page, so spans never bleed across pagination boundaries.

<Canvas of={Table.Rowspan} />
5 changes: 4 additions & 1 deletion src/lib/components/Table/Table.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ $row-color-variants: "primary", "secondary", "light", "success", "danger", "warn

.bordered {
&.cellBorders {
table:has(thead [colspan], thead [rowspan], tbody [colspan], tbody [rowspan]) {
border-collapse: collapse;
}
td,
th {
border-color: var(--theme-color-border-light-default);
Expand Down Expand Up @@ -203,7 +206,7 @@ $row-color-variants: "primary", "secondary", "light", "success", "danger", "warn
}

&.rowBorders {
table {
table:has(thead [colspan], thead [rowspan], tbody [colspan], tbody [rowspan]) {
border-collapse: collapse;
}
tr {
Expand Down
81 changes: 61 additions & 20 deletions src/lib/components/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,7 @@ export const Filtering: Story = {
{ title: "Full Name", dataKey: "name", filter: true },
{ title: "Age", dataKey: "age", filter: true },
];
return (
<div style={{ width: 600 }}>
<Table data={data} columns={columns} filterableTable />
</div>
);
return <Table data={data} columns={columns} filterableTable />;
},
};

Expand Down Expand Up @@ -448,9 +444,7 @@ export const Selection: Story = {
{ title: "Age", dataKey: "age", sorting: {} },
];
return (
<div style={{ width: 600 }}>
<Table data={data} columns={columns} selectable selectionKey="selected" onSelect={selection => alert(JSON.stringify(selection))} />
</div>
<Table data={data} columns={columns} selectable selectionKey="selected" onSelect={selection => alert(JSON.stringify(selection))} />
);
},
};
Expand Down Expand Up @@ -526,11 +520,7 @@ export const RowNumbers: Story = {
{ title: "Name", dataKey: "name", sorting: {} },
{ title: "Age", dataKey: "age", sorting: {} },
];
return (
<div style={{ width: 600 }}>
<Table data={data} columns={columns} />
</div>
);
return <Table data={data} columns={columns} />;
},
};

Expand Down Expand Up @@ -561,13 +551,64 @@ export const RowColoring: Story = {
{ title: "Age", dataKey: "age" },
];
return (
<div style={{ width: 600 }}>
<Table
data={data}
columns={columns}
rowColorCallback={rowData => (rowData.age > 80 ? "danger" : rowData.age < 30 ? "success" : undefined)}
/>
</div>
<Table
data={data}
columns={columns}
rowColorCallback={rowData => (rowData.age > 80 ? "danger" : rowData.age < 30 ? "success" : undefined)}
/>
);
},
};

export const Colspan: Story = {
render: () => {
type RowData = { fullName: string; age: number; city: string; merged: boolean };
const data: RowData[] = [
{ fullName: "John Doe", age: 28, city: "New York", merged: true },
{ fullName: "Jane Smith", age: 34, city: "Los Angeles", merged: true },
{ fullName: "Alice Johnson", age: 29, city: "Chicago", merged: false },
{ fullName: "Bob Williams", age: 42, city: "Houston", merged: false },
];
const columns = [
{
title: "Personal Info",
dataKey: "fullName",
colSpan: (row: unknown) => ((row as RowData).merged ? 2 : 1),
},
{
title: "Age",
dataKey: "age",
},
{
title: "City",
dataKey: "city",
},
];
return <Table data={data} columns={columns} border="cellBorders" />;
},
};

export const Rowspan: Story = {
render: () => {
type RowData = { name: string; surname: string; age: number };
const data = [
{ name: "Name 1", surname: "Surname 1", age: 25 },
{ name: "Name 1", surname: "Surname 2", age: 30 },
{ name: "Name 2", surname: "Surname 3", age: 22 },
{ name: "Name 2", surname: "Surname 4", age: 28 },
];
const columns = [
{
title: "Name",
dataKey: "name",
rowSpan: (row: object) => {
const rowData = row as RowData;
return rowData.surname === "Surname 1" || rowData.surname === "Surname 3" ? 2 : 1;
},
},
{ title: "Surname", dataKey: "surname" },
{ title: "Age", dataKey: "age" },
];
return <Table data={data} columns={columns} border="cellBorders" />;
},
};
Loading
Loading