Skip to content

Commit 65349c4

Browse files
committed
feat(data-table): allow custom expand icon
Closes #2696
1 parent 4bcebe6 commit 65349c4

5 files changed

Lines changed: 137 additions & 14 deletions

File tree

src/DataTable/DataTable.svelte

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* @property {DataTableKey<Row> | (string & {})} key
2727
* @property {DataTableValue} value
2828
* @property {(item: DataTableValue, row: DataTableRow) => DataTableValue} [display]
29+
* @slot {{ expanded: boolean; row: Row | undefined; props: { "aria-hidden": "true" | "false"; class: string; }; }} expandIcon
2930
* @slot {{ row: Row; rowSelected: boolean; }} expanded-row
3031
* @slot {{ header: DataTableNonEmptyHeader; }} cellHeader
3132
* @slot {{ row: Row; cell: DataTableCell<Row>; rowIndex: number; cellIndex: number; rowSelected: boolean; rowExpanded: boolean; }} cell
@@ -278,6 +279,11 @@
278279
tall: 64,
279280
};
280281
282+
const expandIconProps = {
283+
"aria-hidden": "true",
284+
class: "bx--table-expand__svg",
285+
};
286+
281287
let tableBodyScrollTop = 0;
282288
let prevExpandedRowIds = [];
283289
let tableRef = null;
@@ -610,10 +616,9 @@
610616
dispatch("click:header--expand", { expanded });
611617
}}
612618
>
613-
<ChevronRight
614-
aria-hidden="true"
615-
class="bx--table-expand__svg"
616-
/>
619+
<slot name="expandIcon" {expanded} row={undefined} props={expandIconProps}>
620+
<ChevronRight {...expandIconProps} />
621+
</slot>
617622
</button>
618623
{/if}
619624
</th>
@@ -772,10 +777,9 @@
772777
});
773778
}}
774779
>
775-
<ChevronRight
776-
aria-hidden="true"
777-
class="bx--table-expand__svg"
778-
/>
780+
<slot name="expandIcon" expanded={!!expandedRows[row.id]} {row} props={expandIconProps}>
781+
<ChevronRight {...expandIconProps} />
782+
</slot>
779783
</button>
780784
{/if}
781785
</TableCell>
@@ -986,10 +990,9 @@
986990
});
987991
}}
988992
>
989-
<ChevronRight
990-
aria-hidden="true"
991-
class="bx--table-expand__svg"
992-
/>
993+
<slot name="expandIcon" expanded={isExpanded} {row} props={expandIconProps}>
994+
<ChevronRight {...expandIconProps} />
995+
</slot>
993996
</button>
994997
{/if}
995998
</TableCell>

tests-svelte5/svelte5/snippets/Snippets.test.svelte

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,47 @@
11
<script lang="ts">
2-
import { Button, ComboBox, Dropdown, Theme } from "carbon-components-svelte";
2+
import {
3+
Button,
4+
ComboBox,
5+
DataTable,
6+
Dropdown,
7+
Theme,
8+
} from "carbon-components-svelte";
39
410
const items = [
511
{ id: "1", text: "Option 1" },
612
{ id: "2", text: "Option 2" },
713
{ id: "3", text: "Option 3" },
814
] as const;
15+
16+
const dataTableHeaders = [
17+
{ key: "name", value: "Name" },
18+
{ key: "protocol", value: "Protocol" },
19+
] as const;
20+
21+
const dataTableRows = [
22+
{ id: "a", name: "Load Balancer 1", protocol: "HTTP" },
23+
{ id: "b", name: "Load Balancer 2", protocol: "HTTPS" },
24+
];
925
</script>
1026

27+
<DataTable
28+
data-testid="datatable-expand-icon-snippet-container"
29+
expandable
30+
headers={dataTableHeaders}
31+
rows={dataTableRows}
32+
>
33+
{#snippet expandIcon({ expanded, row, props })}
34+
<span
35+
data-testid="datatable-expand-icon-snippet"
36+
{...props}
37+
data-expanded={expanded}
38+
></span>
39+
{/snippet}
40+
<svelte:fragment slot="expanded-row" let:row>
41+
<pre>{JSON.stringify(row, null, 2)}</pre>
42+
</svelte:fragment>
43+
</DataTable>
44+
1145
<Dropdown data-testid="dropdown-snippet" {items} selectedId="1">
1246
{#snippet children({ item, index })}
1347
<span data-testid="dropdown-item-{index}">{item.text} (#{index})</span>

tests-svelte5/svelte5/snippets/Snippets.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
1-
import { render, screen } from "@testing-library/svelte";
1+
import { render, screen, within } from "@testing-library/svelte";
22
import { user } from "../../setup-tests";
33
import Snippets from "./Snippets.test.svelte";
44

55
describe("Svelte 5 Snippets", () => {
6+
describe("DataTable expandIcon", () => {
7+
it("should render custom expand icon via expandIcon snippet with expanded, row, and props", async () => {
8+
const { container } = render(Snippets);
9+
10+
const datatable = screen.getByTestId(
11+
"datatable-expand-icon-snippet-container",
12+
);
13+
expect(datatable).toBeInTheDocument();
14+
15+
const customIcons = screen.getAllByTestId(
16+
"datatable-expand-icon-snippet",
17+
);
18+
expect(customIcons.length).toBeGreaterThanOrEqual(2);
19+
20+
const firstExpandButton = screen.getAllByRole("button", {
21+
name: /expand/i,
22+
})[0];
23+
const iconInButton = within(firstExpandButton).getByTestId(
24+
"datatable-expand-icon-snippet",
25+
);
26+
expect(iconInButton).toHaveAttribute("data-expanded", "false");
27+
28+
await user.click(firstExpandButton);
29+
30+
expect(iconInButton).toHaveAttribute("data-expanded", "true");
31+
32+
const expandedContent = container.querySelector(
33+
".bx--child-row-inner-container",
34+
);
35+
expect(expandedContent).toBeInTheDocument();
36+
});
37+
});
38+
639
describe("Dropdown", () => {
740
it("should render snippet with item and index arguments", async () => {
841
render(Snippets);

tests/DataTable/DataTable.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import DataTable from "./DataTable.test.svelte";
1212
import DataTableCustomBoth from "./DataTableCustomBoth.test.svelte";
1313
import DataTableCustomDescription from "./DataTableCustomDescription.test.svelte";
1414
import DataTableCustomSlots from "./DataTableCustomSlots.test.svelte";
15+
import DataTableExpandIcon from "./DataTableExpandIcon.test.svelte";
1516

1617
describe("DataTable", () => {
1718
beforeEach(() => {
@@ -563,6 +564,31 @@ describe("DataTable", () => {
563564
).toHaveLength(0);
564565
});
565566

567+
it("renders custom expand icon via expandIcon slot", async () => {
568+
const { container } = render(DataTableExpandIcon);
569+
570+
const customIcons = screen.getAllByTestId("custom-expand-icon");
571+
expect(customIcons.length).toBeGreaterThanOrEqual(2);
572+
573+
const firstExpandButton = screen.getAllByRole("button", {
574+
name: /expand/i,
575+
})[0];
576+
expect(
577+
within(firstExpandButton).getByTestId("custom-expand-icon"),
578+
).toHaveAttribute("data-expanded", "false");
579+
580+
await user.click(firstExpandButton);
581+
582+
expect(
583+
within(firstExpandButton).getByTestId("custom-expand-icon"),
584+
).toHaveAttribute("data-expanded", "true");
585+
586+
const expandedContent = container.querySelector(
587+
".bx--child-row-inner-container",
588+
);
589+
expect(expandedContent).toBeInTheDocument();
590+
});
591+
566592
// Styling and layout tests
567593
it("applies zebra stripe styling", () => {
568594
render(DataTable, {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script lang="ts">
2+
import { DataTable } from "carbon-components-svelte";
3+
import ChevronRight from "carbon-icons-svelte/lib/ChevronRight.svelte";
4+
5+
const headers = [
6+
{ key: "name", value: "Name" },
7+
{ key: "protocol", value: "Protocol" },
8+
] as const;
9+
10+
const rows = [
11+
{ id: "a", name: "Load Balancer 1", protocol: "HTTP" },
12+
{ id: "b", name: "Load Balancer 2", protocol: "HTTPS" },
13+
];
14+
</script>
15+
16+
<DataTable expandable {headers} {rows}>
17+
<svelte:fragment slot="expandIcon" let:expanded let:props>
18+
<ChevronRight
19+
{...props}
20+
data-testid="custom-expand-icon"
21+
data-expanded={expanded}
22+
/>
23+
</svelte:fragment>
24+
<svelte:fragment slot="expanded-row" let:row>
25+
<pre>{JSON.stringify(row, null, 2)}</pre>
26+
</svelte:fragment>
27+
</DataTable>

0 commit comments

Comments
 (0)