@fluid-topics/ftds-table 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ The design system table component
2
+
3
+ ## Install
4
+
5
+ ```shell
6
+ npm install @fluid-topics/ftds-table
7
+ yarn add @fluid-topics/ftds-table
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ ```typescript
13
+ import { html } from "lit"
14
+ import "@fluid-topics/ftds-table"
15
+
16
+ function render() {
17
+ return html` <ftds-table foo="bar"></ftds-table> `
18
+ }
19
+ ```
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { customElements } from "@fluid-topics/ft-wc-utils";
2
+ import definitions from "./definitions";
3
+ customElements(definitions);
@@ -0,0 +1,5 @@
1
+ import { FtdsTable } from "./ftds-table";
2
+ declare const _default: {
3
+ "ftds-table": typeof FtdsTable;
4
+ };
5
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { FtdsTable } from "./ftds-table";
2
+ export default {
3
+ "ftds-table": FtdsTable,
4
+ };
@@ -0,0 +1,46 @@
1
+ import { nothing, PropertyValues, TemplateResult } from "lit";
2
+ import "@fluid-topics/ftds-button";
3
+ import "@fluid-topics/ftds-typography";
4
+ import { FtdsTypographyVariants } from "@fluid-topics/ftds-typography";
5
+ import { ColumnConfiguration, EmptyStateConfiguration, FtdsTableProperties, Sort } from "./ftds-table.properties";
6
+ import { FtLitElement } from "@fluid-topics/ft-wc-utils";
7
+ import { FtdsIcons } from "@fluid-topics/ftds-icon";
8
+ export declare class FtdsTable<T extends Record<string, any>> extends FtLitElement implements FtdsTableProperties<T> {
9
+ static styles: import("lit").CSSResult;
10
+ data: Array<T>;
11
+ columns: Array<ColumnConfiguration<T>>;
12
+ sort?: Sort;
13
+ emptyState?: EmptyStateConfiguration;
14
+ stickyHeaders: boolean;
15
+ disableDataManipulation: boolean;
16
+ currentSort?: Sort;
17
+ ariaLabel: string | null;
18
+ clickableRows: boolean;
19
+ highlightedRowIndex: number;
20
+ private activeRowIndex;
21
+ private rows?;
22
+ static DEFAULT_RENDER: (v: any) => TemplateResult<1>;
23
+ static DEFAULT_COMPARATOR: (a: any, b: any) => number;
24
+ focusRow(rowIndex: number): void;
25
+ protected render(): TemplateResult<1>;
26
+ protected optWrapWithFtTypography(toRender: TemplateResult | string, variant: FtdsTypographyVariants): TemplateResult;
27
+ protected renderHeader(column: ColumnConfiguration<T>, index: number): TemplateResult<1>;
28
+ protected renderHeaderRow(): TemplateResult<1>;
29
+ protected renderRow(row: T, rowIndex: number): TemplateResult<1>;
30
+ protected renderSortButton(sortIcon: FtdsIcons, label: string, sort: () => void, index: number): TemplateResult<1>;
31
+ protected renderCell(column: ColumnConfiguration<T>, columnIndex: number, row: T, rowIndex: number): TemplateResult<1>;
32
+ protected renderColumnSort(column: ColumnConfiguration<T>, index: number): typeof nothing | TemplateResult<1>;
33
+ private renderEmptyStateTable;
34
+ protected getSortIcon(isSorted: boolean | undefined): FtdsIcons;
35
+ protected update(props: PropertyValues): void;
36
+ protected columnPart(prefix: string, index: number): string;
37
+ protected getValue(column: ColumnConfiguration<T>, row: T): any;
38
+ private sortData;
39
+ private getOppositeSortOrder;
40
+ private getSortOrderText;
41
+ private dispatchChangeEvent;
42
+ private handleRowKeyDown;
43
+ private dispatchRowClickEvent;
44
+ private handleRowMouseDown;
45
+ private handleRowMouseUp;
46
+ }
@@ -0,0 +1,291 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing, } from "lit";
8
+ import { repeat } from "lit/directives/repeat.js";
9
+ import "@fluid-topics/ftds-button";
10
+ import "@fluid-topics/ftds-typography";
11
+ import { FtdsTypographyVariants } from "@fluid-topics/ftds-typography";
12
+ import { FtdsTableChangeEvent, RowClickEvent, } from "./ftds-table.properties";
13
+ import { property, queryAll, state, } from "lit/decorators.js";
14
+ import { DesignSystemFamily } from "@fluid-topics/design-system-variables";
15
+ import { designSystemStyles } from "./ftds-table.styles";
16
+ import { classMap } from "lit/directives/class-map.js";
17
+ import { styleMap } from "lit/directives/style-map.js";
18
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
19
+ import { FtLitElement, jsonProperty, numberProperty, } from "@fluid-topics/ft-wc-utils";
20
+ import { cactusSvg } from "@fluid-topics/ft-assets";
21
+ import { FtdsIcons, FtdsIconVariants, } from "@fluid-topics/ftds-icon";
22
+ export class FtdsTable extends FtLitElement {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.data = [];
26
+ this.columns = [];
27
+ this.stickyHeaders = false;
28
+ this.disableDataManipulation = false;
29
+ this.ariaLabel = null;
30
+ this.clickableRows = false;
31
+ this.highlightedRowIndex = -1;
32
+ this.activeRowIndex = -1;
33
+ }
34
+ focusRow(rowIndex) {
35
+ if (this.rows) {
36
+ this.rows[rowIndex].focus();
37
+ }
38
+ }
39
+ render() {
40
+ var _a;
41
+ const data = this.sortData(this.data);
42
+ return html `
43
+ <table class="table"
44
+ role="table"
45
+ aria-label="${(_a = this.ariaLabel) !== null && _a !== void 0 ? _a : nothing}"
46
+ >
47
+ ${this.renderHeaderRow()}
48
+ <tbody>
49
+ ${data.length > 0 ? html `
50
+ ${repeat(data, (_, rowIndex) => "row-" + rowIndex, (row, rowIndex) => this.renderRow(row, rowIndex))}` : this.renderEmptyStateTable()}
51
+ </tbody>
52
+ </table>
53
+ `;
54
+ }
55
+ optWrapWithFtTypography(toRender, variant) {
56
+ if (typeof toRender === "string") {
57
+ return html `
58
+ <ftds-typography variant="${variant}">
59
+ ${toRender}
60
+ </ftds-typography>
61
+ `;
62
+ }
63
+ return toRender;
64
+ }
65
+ renderHeader(column, index) {
66
+ var _a, _b, _c, _d;
67
+ const headerCellClasses = {
68
+ "header-cell": true,
69
+ "header-cell--sticky": this.stickyHeaders,
70
+ };
71
+ const columnTitleContainerClasses = {
72
+ "column-title-container": true,
73
+ ["column-title-container--text-align-" + column.textAlign]: true,
74
+ };
75
+ const styles = (_b = (_a = column.styles) === null || _a === void 0 ? void 0 : _a.headerCell) !== null && _b !== void 0 ? _b : {};
76
+ let ariaSort;
77
+ if ((_c = column.sortable) !== null && _c !== void 0 ? _c : true) {
78
+ if (((_d = this.currentSort) === null || _d === void 0 ? void 0 : _d.column) === index) {
79
+ ariaSort = this.currentSort.order === "asc" ? "ascending" : "descending";
80
+ }
81
+ else {
82
+ ariaSort = "none";
83
+ }
84
+ }
85
+ return html `
86
+ <th class="${classMap(headerCellClasses)}" style="${styleMap(styles)}" role="columnheader" scope="col" aria-sort="${ariaSort !== null && ariaSort !== void 0 ? ariaSort : nothing}"
87
+ part="${this.columnPart("header-cell", index)}">
88
+ <div class="${classMap(columnTitleContainerClasses)}"
89
+ part="${this.columnPart("title-container", index)}">
90
+ <span class="column-title"
91
+ part="${this.columnPart("title", index)}">
92
+ ${this.optWrapWithFtTypography(column.title, FtdsTypographyVariants.caption1semibold)}
93
+ </span>
94
+ ${this.renderColumnSort(column, index)}
95
+ </div>
96
+ </th>
97
+ `;
98
+ }
99
+ renderHeaderRow() {
100
+ const classes = {
101
+ "header": true,
102
+ "header--sticky": this.stickyHeaders,
103
+ };
104
+ return html `
105
+ <thead>
106
+ <tr class="${classMap(classes)}" role="row">
107
+ ${repeat(this.columns, (_, index) => "header-cell-" + index, (column, index) => this.renderHeader(column, index))}
108
+ </tr>
109
+ </thead>
110
+ `;
111
+ }
112
+ renderRow(row, rowIndex) {
113
+ const classes = {
114
+ "row": true,
115
+ "row--clickable": this.clickableRows,
116
+ "row--active": rowIndex === this.activeRowIndex,
117
+ "row--highlighted": rowIndex === this.highlightedRowIndex,
118
+ };
119
+ return html `
120
+ <tr class="${classMap(classes)}" role="row" tabindex="${this.clickableRows ? "0" : nothing}"
121
+ part="table-row-${rowIndex}"
122
+ @keydown=${this.clickableRows ? (e) => this.handleRowKeyDown(e, row) : nothing}
123
+ @click=${this.clickableRows ? () => this.dispatchRowClickEvent(row) : nothing}
124
+ @mousedown=${this.clickableRows ? () => this.handleRowMouseDown(rowIndex) : nothing}
125
+ @mouseup=${this.clickableRows ? this.handleRowMouseUp : nothing}
126
+ @mouseleave=${this.clickableRows ? this.handleRowMouseUp : nothing}
127
+ >
128
+ ${repeat(this.columns, (_, columnIndex) => "cell-" + columnIndex + "-" + rowIndex, (column, columnIndex) => this.renderCell(column, columnIndex, row, rowIndex))}
129
+ </tr>
130
+ `;
131
+ }
132
+ renderSortButton(sortIcon, label, sort, index) {
133
+ return html `
134
+ <ftds-button
135
+ family="${DesignSystemFamily.neutral}"
136
+ icon="${sortIcon}"
137
+ iconVariant="${FtdsIconVariants.solid}"
138
+ label="${label}"
139
+ @click=${sort}
140
+ part="${this.columnPart("sort-button", index)}"
141
+ ></ftds-button>
142
+ `;
143
+ }
144
+ renderCell(column, columnIndex, row, rowIndex) {
145
+ var _a;
146
+ const classes = {
147
+ "cell": true,
148
+ ["cell--text-align-" + column.textAlign]: true,
149
+ };
150
+ const renderer = (_a = column.render) !== null && _a !== void 0 ? _a : FtdsTable.DEFAULT_RENDER;
151
+ const render = (value) => {
152
+ const rendered = renderer(value, rowIndex);
153
+ return typeof rendered === "string" ? unsafeHTML(rendered) : rendered;
154
+ };
155
+ return html `
156
+ <td class="${classMap(classes)}" role="cell"
157
+ part="${this.columnPart("cell", columnIndex)} cell-row-${rowIndex} cell-column-${columnIndex}-row-${rowIndex}">
158
+ ${render(this.getValue(column, row))}
159
+ </td>
160
+ `;
161
+ }
162
+ renderColumnSort(column, index) {
163
+ var _a, _b, _c;
164
+ const isSorted = ((_a = this.currentSort) === null || _a === void 0 ? void 0 : _a.column) === index;
165
+ const sortIcon = this.getSortIcon(isSorted);
166
+ const sort = () => {
167
+ var _a, _b;
168
+ const defaultSort = (_a = column.defaultSortOrder) !== null && _a !== void 0 ? _a : "asc";
169
+ this.currentSort = {
170
+ column: index,
171
+ order: isSorted && ((_b = this.currentSort) === null || _b === void 0 ? void 0 : _b.order) === defaultSort ? this.getOppositeSortOrder(defaultSort) : defaultSort,
172
+ };
173
+ this.dispatchChangeEvent();
174
+ };
175
+ const sortOrderText = this.getSortOrderText(isSorted, column);
176
+ const titleText = (_b = column.titleText) !== null && _b !== void 0 ? _b : column.title;
177
+ const label = `Sort ${titleText} in ${sortOrderText} order`;
178
+ return ((_c = column.sortable) !== null && _c !== void 0 ? _c : true) ? this.renderSortButton(sortIcon, label, sort, index) : nothing;
179
+ }
180
+ renderEmptyStateTable() {
181
+ var _a, _b;
182
+ return html `
183
+ <tr class="row" role="row">
184
+ <td colspan="${this.columns.length}" class="cell no-content" role="cell">
185
+ ${((_a = this.emptyState) === null || _a === void 0 ? void 0 : _a.noImage) ? nothing : cactusSvg}
186
+ <ftds-typography variant="${FtdsTypographyVariants.body2medium}">
187
+ ${(_b = this.emptyState) === null || _b === void 0 ? void 0 : _b.content}
188
+ </ftds-typography>
189
+ </td>
190
+ </tr>
191
+ `;
192
+ }
193
+ getSortIcon(isSorted) {
194
+ return isSorted ? (this.currentSort.order === "asc" ? FtdsIcons.SORT_UP : FtdsIcons.SORT_DOWN) : FtdsIcons.SORT;
195
+ }
196
+ update(props) {
197
+ super.update(props);
198
+ if (props.has("sort")) {
199
+ this.currentSort = this.sort;
200
+ }
201
+ }
202
+ columnPart(prefix, index) {
203
+ return `${prefix} ${prefix}-column-${index}`;
204
+ }
205
+ getValue(column, row) {
206
+ return typeof column.getter === "string" ? row[column.getter] : column.getter(row);
207
+ }
208
+ sortData(data) {
209
+ var _a;
210
+ if (this.currentSort && !this.disableDataManipulation) {
211
+ const column = this.columns[this.currentSort.column];
212
+ const ascComparator = (_a = column.comparator) !== null && _a !== void 0 ? _a : FtdsTable.DEFAULT_COMPARATOR;
213
+ const comparator = this.currentSort.order === "asc" ? ascComparator : (a, b) => -ascComparator(a, b);
214
+ return [...data].sort((a, b) => comparator(this.getValue(column, a), this.getValue(column, b)));
215
+ }
216
+ return data;
217
+ }
218
+ getOppositeSortOrder(sortOrder) {
219
+ return sortOrder === "asc" ? "desc" : "asc";
220
+ }
221
+ getSortOrderText(isSorted, column) {
222
+ if (isSorted) {
223
+ return this.currentSort.order === "asc" ? "descending" : "ascending";
224
+ }
225
+ else {
226
+ return column.defaultSortOrder === "desc" ? "descending" : "ascending";
227
+ }
228
+ }
229
+ dispatchChangeEvent() {
230
+ this.dispatchEvent(new FtdsTableChangeEvent({
231
+ sort: this.currentSort,
232
+ }));
233
+ }
234
+ handleRowKeyDown(e, row) {
235
+ if (e.key === "Enter" || e.key === " ") {
236
+ this.dispatchRowClickEvent(row);
237
+ }
238
+ }
239
+ dispatchRowClickEvent(row) {
240
+ this.dispatchEvent(new RowClickEvent(row));
241
+ }
242
+ handleRowMouseDown(rowIndex) {
243
+ this.activeRowIndex = rowIndex;
244
+ }
245
+ handleRowMouseUp() {
246
+ this.activeRowIndex = -1;
247
+ }
248
+ }
249
+ FtdsTable.styles = designSystemStyles;
250
+ FtdsTable.DEFAULT_RENDER = (v) => html `
251
+ <ftds-typography variant="${FtdsTypographyVariants.caption1medium}">
252
+ ${v}
253
+ </ftds-typography>
254
+ `;
255
+ FtdsTable.DEFAULT_COMPARATOR = (a, b) => a - b;
256
+ __decorate([
257
+ jsonProperty([])
258
+ ], FtdsTable.prototype, "data", void 0);
259
+ __decorate([
260
+ jsonProperty([])
261
+ ], FtdsTable.prototype, "columns", void 0);
262
+ __decorate([
263
+ jsonProperty(undefined)
264
+ ], FtdsTable.prototype, "sort", void 0);
265
+ __decorate([
266
+ jsonProperty([])
267
+ ], FtdsTable.prototype, "emptyState", void 0);
268
+ __decorate([
269
+ property({ type: Boolean })
270
+ ], FtdsTable.prototype, "stickyHeaders", void 0);
271
+ __decorate([
272
+ property({ type: Boolean })
273
+ ], FtdsTable.prototype, "disableDataManipulation", void 0);
274
+ __decorate([
275
+ state()
276
+ ], FtdsTable.prototype, "currentSort", void 0);
277
+ __decorate([
278
+ property({ attribute: "aria-label" })
279
+ ], FtdsTable.prototype, "ariaLabel", void 0);
280
+ __decorate([
281
+ property({ type: Boolean })
282
+ ], FtdsTable.prototype, "clickableRows", void 0);
283
+ __decorate([
284
+ numberProperty()
285
+ ], FtdsTable.prototype, "highlightedRowIndex", void 0);
286
+ __decorate([
287
+ numberProperty()
288
+ ], FtdsTable.prototype, "activeRowIndex", void 0);
289
+ __decorate([
290
+ queryAll("tbody .row")
291
+ ], FtdsTable.prototype, "rows", void 0);