Skip to content

Commit 914170f

Browse files
committed
chore(DataTable): update icon, sorting handling and test cases
1 parent fb9f8ce commit 914170f

15 files changed

Lines changed: 478 additions & 189 deletions

File tree

docs/INSTALLATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ This method registers all SGDS elements up front in the Custom Elements Registry
5757
<link href='https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.24.0/css/sgds.css' rel='stylesheet' type='text/css' />
5858

5959
// it is recommended to load a particular version when using cdn e.g. https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@1.0.2
60-
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.24.0" async crossorigin="anonymous" integrity="sha384-+jElDijCtwY8fqtpigO4xW9MKHg6c9eL1PaH1F12llVCcmKup3bKYjfPCFq01VxD"></script>
60+
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.24.0" async crossorigin="anonymous" integrity="sha384-zhdJP6tQnIxGkXmydDp615RiWzR+OaE6QI0xm++AXMr7e2h3xdfnamHxjMkbhPbe"></script>
6161

6262
//or load a single component e.g. Masthead
6363
<script src="https://cdn.jsdelivr.net/npm/@govtechsg/sgds-web-component@3.24.0/components/Masthead/index.umd.min.js" async crossorigin="anonymous" integrity="sha384-vvllpLfJY3jG+vowUWZOJHnvtDTTdaSUOYGbiozHEq4+HESmYu0i/b2r616QoDly"></script>

playground/DataTable.html

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
<link href="../src/themes/night.css" rel="stylesheet" type="text/css" />
44
<link href="../src/css/sgds.css" rel="stylesheet" type="text/css" />
55
<style>
6+
body {
7+
background: var(--sgds-bg-default);
8+
color: var(--sgds-color-default);
9+
}
10+
11+
.theme-toggle {
12+
max-width: 1200px;
13+
margin: 18px auto 0 auto;
14+
padding: 0 18px;
15+
}
16+
617
.container {
718
max-width: 1200px;
819
margin: 0 auto 36px auto;
@@ -19,15 +30,19 @@
1930
}
2031
</style>
2132

33+
<div class="theme-toggle">
34+
<sgds-switch id="night-mode-toggle">Night mode</sgds-switch>
35+
</div>
36+
2237
<!-- Basic table -->
2338
<div class="container">
2439
<h3>Basic</h3>
2540
<sgds-data-table currentPage="10" dataLength="23" itemsPerPage="5">
2641
<sgds-data-table-row>
2742
<sgds-data-table-head>#</sgds-data-table-head>
2843
<sgds-data-table-head>First name</sgds-data-table-head>
29-
<sgds-data-table-head>Last name</sgds-data-table-head>
30-
<sgds-data-table-head>Username</sgds-data-table-head>
44+
<sgds-data-table-head sorting>Last name</sgds-data-table-head>
45+
<sgds-data-table-head sorting>Username</sgds-data-table-head>
3146
</sgds-data-table-row>
3247
<sgds-data-table-row>
3348
<sgds-data-table-cell>1</sgds-data-table-cell>
@@ -373,7 +388,7 @@ <h3>Server Mode (mode="server")</h3>
373388
<sgds-data-table-row>
374389
<sgds-data-table-head sorting sortKey="id">ID</sgds-data-table-head>
375390
<sgds-data-table-head sorting sortKey="name">Name</sgds-data-table-head>
376-
<sgds-data-table-head sorting sortKey="region">Region</sgds-data-table-head>
391+
<sgds-data-table-head>Region</sgds-data-table-head>
377392
</sgds-data-table-row>
378393
</sgds-data-table>
379394
</div>
@@ -385,7 +400,7 @@ <h3>Header and Cell Props (sorting, sortKey, width)</h3>
385400
<sgds-data-table-row>
386401
<sgds-data-table-head width="88">ID</sgds-data-table-head>
387402
<sgds-data-table-head sorting sortKey="name">Name</sgds-data-table-head>
388-
<sgds-data-table-head sorting sortKey="role">Role</sgds-data-table-head>
403+
<sgds-data-table-head>Role</sgds-data-table-head>
389404
<sgds-data-table-head sorting sortKey="score" width="120" textAlign="right">Score</sgds-data-table-head>
390405
</sgds-data-table-row>
391406

@@ -410,6 +425,39 @@ <h3>Header and Cell Props (sorting, sortKey, width)</h3>
410425
</sgds-data-table>
411426
</div>
412427

428+
<!-- Default sort -->
429+
<div class="container">
430+
<h3>Default Sort</h3>
431+
<sgds-data-table currentPage="1" dataLength="4" itemsPerPage="4">
432+
<sgds-data-table-row>
433+
<sgds-data-table-head sorting sortKey="id">ID</sgds-data-table-head>
434+
<sgds-data-table-head sorting sortKey="name" ariasort="ascending">Name</sgds-data-table-head>
435+
<sgds-data-table-head>Role</sgds-data-table-head>
436+
</sgds-data-table-row>
437+
438+
<sgds-data-table-row id="default-sort-row-1">
439+
<sgds-data-table-cell>2</sgds-data-table-cell>
440+
<sgds-data-table-cell>Alice</sgds-data-table-cell>
441+
<sgds-data-table-cell>Engineer</sgds-data-table-cell>
442+
</sgds-data-table-row>
443+
<sgds-data-table-row id="default-sort-row-2">
444+
<sgds-data-table-cell>4</sgds-data-table-cell>
445+
<sgds-data-table-cell>Ben</sgds-data-table-cell>
446+
<sgds-data-table-cell>Analyst</sgds-data-table-cell>
447+
</sgds-data-table-row>
448+
<sgds-data-table-row id="default-sort-row-3">
449+
<sgds-data-table-cell>1</sgds-data-table-cell>
450+
<sgds-data-table-cell>Chloe</sgds-data-table-cell>
451+
<sgds-data-table-cell>Manager</sgds-data-table-cell>
452+
</sgds-data-table-row>
453+
<sgds-data-table-row id="default-sort-row-4">
454+
<sgds-data-table-cell>3</sgds-data-table-cell>
455+
<sgds-data-table-cell>Darren</sgds-data-table-cell>
456+
<sgds-data-table-cell>Designer</sgds-data-table-cell>
457+
</sgds-data-table-row>
458+
</sgds-data-table>
459+
</div>
460+
413461
<!-- Row colspan example (5 data rows, 5 columns) -->
414462
<div class="container">
415463
<h3>Row Colspan Example (5 data rows, 5 columns)</h3>
@@ -463,6 +511,13 @@ <h3>Row Colspan Example (5 data rows, 5 columns)</h3>
463511
</div>
464512

465513
<script type="module">
514+
const nightModeToggle = document.getElementById("night-mode-toggle");
515+
516+
nightModeToggle?.addEventListener("sgds-change", event => {
517+
const isNight = event.target?.checked === true;
518+
document.documentElement.classList.toggle("sgds-night-theme", isNight);
519+
});
520+
466521
// Set rowData on multi-select rows
467522
document.getElementById("ms-row-1").rowData = { id: 1, name: "John Doe" };
468523
document.getElementById("ms-row-2").rowData = { id: 2, name: "Jane Doe" };
@@ -478,6 +533,12 @@ <h3>Row Colspan Example (5 data rows, 5 columns)</h3>
478533
document.getElementById("sort-row-2").rowData = { id: 2, name: "Adam", role: "Engineer", score: 82 };
479534
document.getElementById("sort-row-3").rowData = { id: 3, name: "Nora", role: "Manager", score: 75 };
480535

536+
// Set rowData for default sort sample
537+
document.getElementById("default-sort-row-1").rowData = { id: 2, name: "Alice", role: "Engineer" };
538+
document.getElementById("default-sort-row-2").rowData = { id: 4, name: "Ben", role: "Analyst" };
539+
document.getElementById("default-sort-row-3").rowData = { id: 1, name: "Chloe", role: "Manager" };
540+
document.getElementById("default-sort-row-4").rowData = { id: 3, name: "Darren", role: "Designer" };
541+
481542
// Mock API-driven pagination demo for server mode table
482543
const serverTable = document.getElementById("server-table");
483544
const totalRows = 50;

rollup.test.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import * as fs from "fs";
1010
function getFilesInDirectorySync(directoryPath) {
1111
try {
1212
const files = fs.readdirSync(directoryPath);
13+
const supportedTestExtensions = new Set([".js", ".ts"]);
1314
const result = files.map(file => {
1415
const filePath = path.join(directoryPath, file);
1516
const stats = fs.statSync(filePath);
16-
if (stats.isFile()) {
17+
if (stats.isFile() && supportedTestExtensions.has(path.extname(filePath))) {
1718
return filePath;
1819
}
1920
});

skills/sgds-components/reference/data-table.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,21 @@ It supports client-side pagination, server-driven pagination, sorting headers, r
2424
- Data from API with backend sorting: set `serverSort` and listen to `sgds-sort`.
2525
- Need loading indicator while fetching server data: set `isLoading` to `true`.
2626
- Need numeric columns aligned right: set `textAlign="right"` on the corresponding `sgds-data-table-head`.
27+
- Need only selected columns to sort: set `sorting` only on those headers.
28+
- Need default sort indicator on first render: set `ariasort` on a sortable header.
2729

2830
```html
2931
<sgds-data-table dataLength="3" itemsPerPage="5" currentPage="1">
3032
<sgds-data-table-row>
3133
<sgds-data-table-head sorting sortKey="id">ID</sgds-data-table-head>
32-
<sgds-data-table-head sorting sortKey="name">Name</sgds-data-table-head>
34+
<sgds-data-table-head sorting sortKey="name" ariasort="ascending">Name</sgds-data-table-head>
35+
<sgds-data-table-head>Role</sgds-data-table-head>
3336
<sgds-data-table-head textAlign="right">Amount</sgds-data-table-head>
3437
</sgds-data-table-row>
3538
<sgds-data-table-row>
3639
<sgds-data-table-cell>1</sgds-data-table-cell>
3740
<sgds-data-table-cell>Amy</sgds-data-table-cell>
41+
<sgds-data-table-cell>Analyst</sgds-data-table-cell>
3842
<sgds-data-table-cell>125.00</sgds-data-table-cell>
3943
</sgds-data-table-row>
4044
</sgds-data-table>
@@ -94,6 +98,12 @@ It supports client-side pagination, server-driven pagination, sorting headers, r
9498
| `sgds-hide` | Expandable row starts closing |
9599
| `sgds-after-hide` | Expandable row finishes closing |
96100

101+
## Sorting Behavior Notes
102+
103+
- In client mode, sorting affects only currently visible rows.
104+
- When a sort cycles back to `none`, row order reverts to the initial slotted order captured at first non-server load.
105+
- Sort controls are disabled when there are no body rows.
106+
97107
## Server Mode Example
98108

99109
```html

src/components/DataTable/data-table.css

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,35 +86,63 @@ th {
8686
gap: var(--sgds-gap-2-xs);
8787
}
8888

89-
.data-table-head.align-right {
90-
justify-content: flex-end;
91-
text-align: right;
92-
}
93-
94-
.data-table-cell.align-right {
95-
justify-content: flex-end;
96-
text-align: right;
89+
.sort {
90+
display: inline-flex;
91+
align-items: center;
9792
}
9893

99-
.sort {
100-
width: var(--sgds-dimension-24);
101-
height: var(--sgds-dimension-24);
102-
display: flex;
103-
flex-direction: column;
94+
.sort-button {
95+
display: inline-flex;
10496
align-items: center;
10597
justify-content: center;
98+
width: var(--sgds-dimension-24);
99+
height: var(--sgds-dimension-24);
100+
padding: 0;
101+
border: var(--sgds-border-width-1) solid var(--sgds-border-color-transparent);
102+
border-radius: var(--sgds-form-border-radius-md);
103+
background-color: var(--sgds-bg-transparent);
104+
color: var(--sgds-color-subtle);
105+
cursor: pointer;
106+
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
107+
box-shadow 0.15s ease-in-out;
106108
}
107109

108-
.sort :first-child {
109-
margin-bottom: calc(-1 * var(--sgds-margin-xs));
110+
.sort-button:hover {
111+
background-color: var(--sgds-bg-translucent-subtle);
112+
color: var(--sgds-color-default);
113+
}
114+
115+
.sort-button:focus,
116+
.sort-button:focus-visible {
117+
background-color: var(--sgds-bg-translucent-subtle);
118+
border-color: transparent;
119+
outline: var(--sgds-outline-focus);
120+
outline-offset: var(--sgds-outline-offset-focus);
110121
}
111122

112-
.sort .sort-icon {
123+
.sort-button:disabled {
124+
background-color: var(--sgds-bg-transparent);
113125
color: var(--sgds-color-muted);
126+
cursor: not-allowed;
114127
}
115128

116-
.sort .active.sort-icon {
117-
color: var(--sgds-color-default);
129+
.sort-button.active {
130+
background-color: var(--sgds-primary-surface-translucent);
131+
color: var(--sgds-primary-color-default);
132+
}
133+
134+
.sort-icon {
135+
display: inline-flex;
136+
}
137+
138+
.data-table-head.align-right {
139+
justify-content: flex-end;
140+
text-align: right;
141+
}
142+
143+
.data-table-cell.align-right {
144+
justify-content: flex-end;
145+
text-align: right;
118146
}
119147

120148
/* control cell variants — applied as classes on the host */

src/components/DataTable/sgds-data-table-head.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class SgdsDataTableHead extends SgdsElement {
2525
@property({ type: String, reflect: true }) ariasort: "ascending" | "descending" | "none" | "other" | undefined;
2626

2727
/** When true, clicking this header cycles through ascending → descending → none sort. */
28-
@property({ type: Boolean, reflect: true }) sorting = true;
28+
@property({ type: Boolean, reflect: true }) sorting = false;
2929

3030
/** Column key emitted in the row's internal sort payload, used to identify which column to sort. */
3131
@property({ type: String, reflect: true }) sortKey = "";

src/components/DataTable/sgds-data-table-row.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export class SgdsDataTableRow extends SgdsElement {
5555

5656
/** @internal — per-column alignment injected by `sgds-data-table` from header cells. */
5757
@property({ attribute: false }) columnAlignments: Array<"left" | "right"> = [];
58+
/** @internal — set by `sgds-data-table` to indicate whether body rows exist. */
59+
@property({ type: Boolean }) hasDataRows = false;
5860

5961
/** @internal */
6062
@query(".expandable-body") private _expandableBody!: HTMLElement;
@@ -182,18 +184,20 @@ export class SgdsDataTableRow extends SgdsElement {
182184
<div class="data-table-head ${el.textAlign === "right" ? "align-right" : "align-left"}">
183185
${children}
184186
${el.sorting
185-
? html`<div class="sort">
186-
<sgds-icon
187-
class="sort-icon ${el.ariasort === "ascending" ? "active" : ""}"
188-
name="chevron-up"
189-
size="sm"
190-
></sgds-icon>
191-
<sgds-icon
192-
class="sort-icon ${el.ariasort === "descending" ? "active" : ""}"
193-
name="chevron-down"
194-
size="sm"
195-
></sgds-icon>
196-
</div>`
187+
? html`
188+
<button
189+
type="button"
190+
class="sort-button ${el.ariasort === "none" ? "" : "active"}"
191+
aria-label=${el.ariasort === "descending" ? "Sort descending" : "Sort ascending"}
192+
?disabled=${!this.hasDataRows}
193+
>
194+
<sgds-icon
195+
class="sort-icon"
196+
name=${el.ariasort === "descending" ? "sort-descending" : "sort-ascending"}
197+
size="sm"
198+
></sgds-icon>
199+
</button>
200+
`
197201
: nothing}
198202
</div>
199203
</th>`;
@@ -244,9 +248,10 @@ export class SgdsDataTableRow extends SgdsElement {
244248
render() {
245249
const cells = this._assignedCells ?? [];
246250
const totalCols = cells.length + (this.showCheckbox ? 1 : 0) + (this.expand || this.showExpandPlaceholder ? 1 : 0);
251+
const rowPart = this._isHeaderRow ? "row row-header" : "row row-body";
247252

248253
return html`
249-
<tr ?data-header-row=${this._isHeaderRow} class=${this.open ? "active" : ""}>
254+
<tr part=${rowPart} ?data-header-row=${this._isHeaderRow} class=${this.open ? "active" : ""}>
250255
${this._renderHiddenSlotCell()}
251256
${this.expand
252257
? this._renderExpandCell()

0 commit comments

Comments
 (0)