From 4fc94a6e9bc223cb5ebe4cd6d854fe3a14406b59 Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Tue, 29 Jul 2025 14:53:45 -0400 Subject: [PATCH 01/12] HDS-5076 Feat: Convert Table showcase pages to TS --- .../app/controllers/page-components/table.js | 522 -------------- .../app/controllers/page-components/table.ts | 649 ++++++++++++++++++ showcase/app/routes/page-components/table.js | 39 -- showcase/app/routes/page-components/table.ts | 137 ++++ .../app/templates/page-components/table.hbs | 333 ++++++--- 5 files changed, 1033 insertions(+), 647 deletions(-) delete mode 100644 showcase/app/controllers/page-components/table.js create mode 100644 showcase/app/controllers/page-components/table.ts delete mode 100644 showcase/app/routes/page-components/table.js create mode 100644 showcase/app/routes/page-components/table.ts diff --git a/showcase/app/controllers/page-components/table.js b/showcase/app/controllers/page-components/table.js deleted file mode 100644 index d7ec77db1df..00000000000 --- a/showcase/app/controllers/page-components/table.js +++ /dev/null @@ -1,522 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Controller from '@ember/controller'; -import { action } from '@ember/object'; -import { tracked } from '@glimmer/tracking'; -import { deepTracked } from 'ember-deep-tracked'; -import { later } from '@ember/runloop'; - -// we use an array to declare the custom sorting order for the clusters' status -const customSortingCriteriaArray = [ - 'failing', - 'active', - 'establishing', - 'pending', -]; - -const updateModelWithSelectAllState = (modelData, selectAllState) => { - modelData.forEach((modelRow) => { - modelRow.isSelected = selectAllState; - }); -}; - -const updateModelWithSelectableRowsStates = ( - modelData, - selectableRowsStates, -) => { - const modelDataMap = new Map( - modelData.map((modelRow) => [modelRow.id, modelRow]), - ); - selectableRowsStates.forEach((row) => { - // safe to assume that there is always a record for the "selectionKey" since it's coming from the model (the selectable "rows" are a subset of the model dataset) - modelDataMap.get(row.selectionKey).isSelected = row.isSelected; - }); -}; - -export default class PageComponentsTableController extends Controller { - // custom sorting - @tracked customSortOrder_demo2 = 'asc'; - @tracked customSortBy_demo3 = undefined; - @tracked customSortOrder_demo3 = 'asc'; - - // multi-select - @tracked multiSelectFilterRows__demo1 = 'all'; - @tracked multiSelectToggleScope__demo1 = false; - @tracked multiSelectToggleDebug__demo1 = false; - @deepTracked multiSelectModelData__demo1 = [ - ...this.model.selectableDataDemo1, - ]; - @deepTracked multiSelectNoModelState__demo1 = { - row1: false, - row2: true, - row3: false, - row4: false, - }; - @deepTracked multiSelectModelData__demo2 = [ - ...this.model.selectableDataDemo2, - ]; - @tracked multiSelectToggleScope__demo2 = false; - @tracked multiSelectToggleDebug__demo2 = false; - @tracked multiSelectPaginatedCurrentPage_demo2 = 1; - @tracked multiSelectPaginatedCurrentPageSize_demo2 = 2; - @tracked multiSelectToggleScope__demo3 = false; - @tracked multiSelectToggleDebug__demo3 = false; - @deepTracked multiSelectModelData__demo3 = [...this.model.userDataDemo3]; - @tracked multiSelectUsersCurrentPage_demo3 = 1; - @tracked multiSelectUsersCurrentPageSize_demo3 = 4; - @deepTracked multiSelectUserData__demo4 = [...this.model.userDataDemo4]; - @deepTracked multiSelectSelectableData__demo5 = [ - ...this.model.selectableDataDemo5, - ]; - @tracked customSortBy_demo6 = undefined; - @tracked customSortOrder_demo6 = 'asc'; - @deepTracked multiSelectSelectableData__demo6 = [ - ...this.model.selectableDataDemo6, - ]; - - // CUSTOM SORTING DEMO #1 - // Sortable table with custom sorting done via extra key added to the data model - - get clustersWithExtraData_demo1() { - return this.model.clusters.map((record) => { - return { - ...record, - 'status-sort-order': customSortingCriteriaArray.indexOf( - record['status'], - ), - }; - }); - } - - // CUSTOM SORTING DEMO #2 - // Sortable table with custom `sortingFunction` declared in the column hash - - get customSortingFunction_demo2() { - return (s1, s2) => { - const index1 = customSortingCriteriaArray.indexOf(s1['status']); - const index2 = customSortingCriteriaArray.indexOf(s2['status']); - if (index1 < index2) { - return this.customSortOrder_demo2 === 'asc' ? -1 : 1; - } else if (index1 > index2) { - return this.customSortOrder_demo2 === 'asc' ? 1 : -1; - } else { - return 0; - } - }; - } - - @action - customOnSort_demo2(_sortBy, sortOrder) { - this.customSortOrder_demo2 = sortOrder; - } - - // CUSTOM SORTING DEMO #3 - // Sortable table with custom sorting using yielded `` + `sortBy/sortOrder/setSortBy` properties - - @action - onClickThSort__demo3(column, setSortBy) { - // NOTICE: this code is a direct clone of the internal code of `Hds::Table` backing class - // we need to keep an internal state of the sorting - if (this.customSortBy_demo3 === column) { - // check to see if the column is already sorted and invert the sort order if so - this.customSortOrder_demo3 = - this.customSortOrder_demo3 === 'asc' ? 'desc' : 'asc'; - } else { - // otherwise, set the sort order to ascending - this.customSortBy_demo3 = column; - this.customSortOrder_demo3 = 'asc'; - } - // update the sorting icons for the table - if (setSortBy) { - setSortBy(column); - } - } - - get sortedModelClusters__demo3() { - const clonedModelClusters = Array.from(this.model.clusters); - if (this.customSortBy_demo3 === 'peer-name') { - clonedModelClusters.sort((s1, s2) => { - const name1 = s1['peer-name'].toLowerCase(); - const name2 = s2['peer-name'].toLowerCase(); - if (name1 < name2) { - return this.customSortOrder_demo3 === 'asc' ? -1 : 1; - } - if (name1 > name2) { - return this.customSortOrder_demo3 === 'asc' ? 1 : -1; - } - return 0; - }); - } else if (this.customSortBy_demo3 === 'status') { - clonedModelClusters.sort((s1, s2) => { - const index1 = customSortingCriteriaArray.indexOf(s1['status']); - const index2 = customSortingCriteriaArray.indexOf(s2['status']); - if (index1 < index2) { - return this.customSortOrder_demo3 === 'asc' ? -1 : 1; - } else if (index1 > index2) { - return this.customSortOrder_demo3 === 'asc' ? 1 : -1; - } else { - return 0; - } - }); - } - return clonedModelClusters; - } - - @action - extraOnSortCallback_demo3() { - console.log( - `extraOnSortCallback called with customSortBy='${this.customSortBy_demo3}' and customSortOrder='${this.customSortOrder_demo3}'`, - ); - } - - // CUSTOM SORTING DEMO #4 - // Sortable table with custom sorting using yielded `` + `sortBy/sortOrder/setSortBy` properties - - sortModelClusters__demo4 = (sortBy, sortOrder) => { - // here goes the logic for the custom sorting of the `model` array based on `sortBy/sortOrder` - const clonedModelClusters = Array.from(this.model.clusters); - if (sortBy === 'peer-name') { - clonedModelClusters.sort((s1, s2) => { - const name1 = s1['peer-name'].toLowerCase(); - const name2 = s2['peer-name'].toLowerCase(); - if (name1 < name2) { - return sortOrder === 'asc' ? -1 : 1; - } - if (name1 > name2) { - return sortOrder === 'asc' ? 1 : -1; - } - return 0; - }); - } else if (sortBy === 'status') { - clonedModelClusters.sort((s1, s2) => { - const index1 = customSortingCriteriaArray.indexOf(s1['status']); - const index2 = customSortingCriteriaArray.indexOf(s2['status']); - if (index1 < index2) { - return sortOrder === 'asc' ? -1 : 1; - } else if (index1 > index2) { - return sortOrder === 'asc' ? 1 : -1; - } else { - return 0; - } - }); - } - return clonedModelClusters; - }; - - // CUSTOM SORTING DEMO #5 - // Sortable table with model and sorting by selected row - - @action - onMultiSelectSelectionChange__demo5({ - selectionKey, - selectionCheckboxElement, - }) { - if (selectionKey === 'all') { - this.multiSelectSelectableData__demo5.forEach((modelRow) => { - modelRow.isSelected = selectionCheckboxElement.checked; - }); - } else { - const recordToUpdate = this.multiSelectSelectableData__demo5.find( - (modelRow) => modelRow.id === selectionKey, - ); - - if (recordToUpdate) { - recordToUpdate.isSelected = !recordToUpdate.isSelected; - } - } - } - - // CUSTOM SORTING DEMO #6 - // Sortable table with sorting by selected using yielded `/` - - get sortedMultiSelect__demo6() { - const clonedMultiSelect = Array.from(this.multiSelectSelectableData__demo6); - clonedMultiSelect.sort((s1, s2) => { - const v1 = s1[this.customSortBy_demo6]; - const v2 = s2[this.customSortBy_demo6]; - if (v1 < v2) { - return this.customSortOrder_demo6 === 'asc' ? -1 : 1; - } - if (v1 > v2) { - return this.customSortOrder_demo6 === 'asc' ? 1 : -1; - } - return 0; - }); - return clonedMultiSelect; - } - - @action - customOnSort_demo6(sortBy, sortOrder) { - this.customSortBy_demo6 = sortBy; - this.customSortOrder_demo6 = sortOrder; - } - - @action - onMultiSelectSelectionChange__demo6({ - selectionKey, - selectionCheckboxElement, - }) { - if (selectionKey === 'all') { - this.multiSelectSelectableData__demo6.forEach((modelRow) => { - modelRow.isSelected = selectionCheckboxElement.checked; - }); - } else { - const recordToUpdate = this.multiSelectSelectableData__demo6.find( - (modelRow) => modelRow.id === selectionKey, - ); - - if (recordToUpdate) { - recordToUpdate.isSelected = !recordToUpdate.isSelected; - } - } - } - - // GENERIC MULTI-SELECT FUNCTIONALITIES - - @action - onSelectionChangeLogArguments() { - console.log(...arguments); - } - - @action - mockIndeterminateState(checkbox) { - checkbox.indeterminate = true; - } - - // MULTI-SELECT DEMO #1 - // Multi-select table with external filter for odd/even rows - - get multiSelectFilteredData__demo1() { - if (this.multiSelectFilterRows__demo1 === 'all') { - return this.multiSelectModelData__demo1; - } else { - const remainder = this.multiSelectFilterRows__demo1 === 'even' ? 0 : 1; - return this.multiSelectModelData__demo1.filter( - (item) => item.id % 2 === remainder, - ); - } - } - - @action - toggleMultiSelectToggleScope__demo1(event) { - this.multiSelectToggleScope__demo1 = event.target.checked; - } - - @action - toggleMultiSelectToggleDebug__demo1(event) { - this.multiSelectToggleDebug__demo1 = event.target.checked; - } - - @action - onChangeMultiSelectFilter__demo1(event) { - this.multiSelectFilterRows__demo1 = event.target.value; - } - - @action - onSelectionChangeWithModel__demo1({ - selectionKey, - selectionCheckboxElement, - selectableRowsStates, - }) { - console.log(...arguments); - if (selectionKey === 'all' && this.multiSelectToggleScope__demo1) { - updateModelWithSelectAllState( - this.multiSelectModelData__demo1, - selectionCheckboxElement.checked, - ); - } else { - updateModelWithSelectableRowsStates( - this.multiSelectModelData__demo1, - selectableRowsStates, - ); - } - } - - @action - onSelectionChangeWithoutModel__demo1({ - selectionKey, - selectionCheckboxElement, - selectableRowsStates, - }) { - console.log(...arguments); - // notice: the shape of the "model" is slightly different, it's not an array of objects but an object with keys so - // we can't use the `updateModelWithSelectAllsState` and `updateModelWithSelectableRowsStates` functions - if (selectionKey === 'all' && this.multiSelectToggleScope__demo1) { - const selectAllState = selectionCheckboxElement.checked; - Object.keys(this.multiSelectNoModelState__demo1).forEach((rowKey) => { - this.multiSelectNoModelState__demo1[rowKey] = selectAllState; - }); - } else { - selectableRowsStates.forEach((row) => { - this.multiSelectNoModelState__demo1[row.selectionKey] = row.isSelected; - }); - } - } - - // MULTI-SELECT DEMO #2 - // Multi-select table with pagination - - @action - toggleMultiSelectPaginatedToggleScope__demo2(event) { - this.multiSelectToggleScope__demo2 = event.target.checked; - } - - @action - toggleMultiSelectPaginatedToggleDebug__demo2(event) { - this.multiSelectToggleDebug__demo2 = event.target.checked; - } - - get multiSelectPaginatedTotalItems_demo2() { - return this.multiSelectModelData__demo2.length; - } - - get multiSelectPaginatedData_demo2() { - const start = - (this.multiSelectPaginatedCurrentPage_demo2 - 1) * - this.multiSelectPaginatedCurrentPageSize_demo2; - const end = - this.multiSelectPaginatedCurrentPage_demo2 * - this.multiSelectPaginatedCurrentPageSize_demo2; - return this.multiSelectModelData__demo2.slice(start, end); - } - - @action - onMultiSelectPaginatedPageChange_demo2(page) { - this.multiSelectPaginatedCurrentPage_demo2 = page; - } - - @action - onMultiSelectPaginatedPageSizeChange_demo2(pageSize) { - // we agreed to reset the pagination to the first element (any alternative would result in an unpredictable UX) - this.multiSelectPaginatedCurrentPage_demo2 = 1; - this.multiSelectPaginatedCurrentPageSize_demo2 = pageSize; - } - - @action - onMultiSelectPaginatedSelectionChange__demo2({ - selectionKey, - selectionCheckboxElement, - selectableRowsStates, - }) { - console.log(...arguments); - if (selectionKey === 'all' && this.multiSelectToggleScope__demo2) { - updateModelWithSelectAllState( - this.multiSelectModelData__demo2, - selectionCheckboxElement.checked, - ); - } else { - updateModelWithSelectableRowsStates( - this.multiSelectModelData__demo2, - selectableRowsStates, - ); - } - } - - // MULTI-SELECT DEMO #3 - // Delete selected rows - - @action - toggleMultiSelectPaginatedToggleScope__demo3(event) { - this.multiSelectToggleScope__demo3 = event.target.checked; - } - - @action - toggleMultiSelectPaginatedToggleDebug__demo3(event) { - this.multiSelectToggleDebug__demo3 = event.target.checked; - } - - get multiSelectUsersTotalItems_demo3() { - return this.multiSelectModelData__demo3.length; - } - - get multiSelectUsersData_demo3() { - const start = - (this.multiSelectUsersCurrentPage_demo3 - 1) * - this.multiSelectUsersCurrentPageSize_demo3; - const end = - this.multiSelectUsersCurrentPage_demo3 * - this.multiSelectUsersCurrentPageSize_demo3; - return this.multiSelectModelData__demo3.slice(start, end); - } - - @action - onMultiSelectUsersPageChange_demo3(page) { - this.multiSelectUsersCurrentPage_demo3 = page; - } - - @action - onMultiSelectUsersPageSizeChange_demo3(pageSize) { - // we agreed to reset the pagination to the first element (any alternative would result in an unpredictable UX) - this.multiSelectUsersCurrentPage_demo3 = 1; - this.multiSelectUsersCurrentPageSize_demo3 = pageSize; - } - - @action - onMultiSelectUsersSelectionChange__demo3({ - selectionKey, - selectionCheckboxElement, - selectableRowsStates, - }) { - console.log(...arguments); - if (selectionKey === 'all' && this.multiSelectToggleScope__demo3) { - updateModelWithSelectAllState( - this.multiSelectModelData__demo3, - selectionCheckboxElement.checked, - ); - } else { - selectableRowsStates.forEach((row) => { - const recordToUpdate = this.multiSelectModelData__demo3.find( - (modelRow) => modelRow.id === row.selectionKey, - ); - if (recordToUpdate) { - recordToUpdate.isSelected = row.isSelected; - } - }); - } - } - - @action - multiSelectDeleteSelectedUsers_demo3() { - const newData = this.multiSelectModelData__demo3.filter( - (user) => !user.isSelected, - ); - this.multiSelectModelData__demo3 = [...newData]; - } - - // MULTI-SELECT DEMO #4 - // Execute action on selected rows - - @action - onMultiSelectSelectionChange__demo4({ selectedRowsKeys }) { - console.log(...arguments); - this.multiSelectUserData__demo4.forEach((user) => { - user.isSelected = selectedRowsKeys.includes(user.id); - }); - } - - @action - multiSelectAnimateSelectedUsers_demo4() { - this.multiSelectUserData__demo4.forEach((user) => { - user.isAnimated = user.isSelected; - }); - // eslint-disable-next-line ember/no-runloop - later(() => { - this.multiSelectResetUserAnimation_demo4(); - }, 5000); - } - - @action - multiSelectResetUserAnimation_demo4() { - this.multiSelectUserData__demo4.forEach((user) => { - user.isAnimated = false; - }); - } - - @action - noop() { - // no-op - } -} diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts new file mode 100644 index 00000000000..3aa7dd190cf --- /dev/null +++ b/showcase/app/controllers/page-components/table.ts @@ -0,0 +1,649 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { deepTracked } from 'ember-deep-tracked'; +import { later } from '@ember/runloop'; + +import type { PageComponentsTableModel } from '../../routes/page-components/table'; +import type { HdsTableOnSelectionChangeSignature } from '@hashicorp/design-system-components/components/hds/table/types'; + +import type { + MockDataCluster, + MockDataSelectable, + MockDataUser, +} from '../../routes/page-components/table'; + +type MultiSelectNoModel = { + row1: boolean; + row2: boolean; + row3: boolean; + row4: boolean; +}; + +// we use an array to declare the custom sorting order for the clusters' status +const customSortingCriteriaArray = [ + 'failing', + 'active', + 'establishing', + 'pending', +]; + +const updateModelWithSelectAllState = ( + modelData: MockDataSelectable[] | MockDataUser[], + selectAllState: boolean, +) => { + modelData.forEach((modelRow) => { + modelRow.isSelected = selectAllState; + }); +}; + +const updateModelWithSelectableRowsStates = ( + modelData: MockDataSelectable[] | MockDataUser[], + selectableRowsStates: HdsTableOnSelectionChangeSignature['selectableRowsStates'], +) => { + const modelDataMap = new Map( + modelData.map((modelRow) => [modelRow.id, modelRow]), + ); + selectableRowsStates.forEach((row) => { + // safe to assume that there is always a record for the "selectionKey" since it's coming from the model (the selectable "rows" are a subset of the model dataset) + modelDataMap.get(Number(row.selectionKey))!.isSelected = row.isSelected + ? true + : false; + }); +}; + +export default class PageComponentsTableController extends Controller { + declare model: PageComponentsTableModel; + + // custom sorting + @tracked customSortOrder_demo2 = 'asc'; + @tracked customSortBy_demo3: string | undefined = undefined; + @tracked customSortOrder_demo3 = 'asc'; + + // multi-select + @tracked multiSelectFilterRows__demo1 = 'all'; + @tracked multiSelectToggleScope__demo1 = false; + @tracked multiSelectToggleDebug__demo1 = false; + @deepTracked multiSelectSelectableData__demo1 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.selectableDataDemo1, + ]; + @deepTracked multiSelectNoModelState__demo1: MultiSelectNoModel = { + row1: false, + row2: true, + row3: false, + row4: false, + }; + @deepTracked multiSelectSelectableData__demo2 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.selectableDataDemo2, + ]; + @tracked multiSelectToggleScope__demo2 = false; + @tracked multiSelectToggleDebug__demo2 = false; + @tracked multiSelectPaginatedCurrentPage_demo2 = 1; + @tracked multiSelectPaginatedCurrentPageSize_demo2 = 2; + @tracked multiSelectToggleScope__demo3 = false; + @tracked multiSelectToggleDebug__demo3 = false; + @deepTracked multiSelectUserData__demo3 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.userDataDemo3, + ]; + @tracked multiSelectUsersCurrentPage_demo3 = 1; + @tracked multiSelectUsersCurrentPageSize_demo3 = 4; + @deepTracked multiSelectUserData__demo4 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.userDataDemo4, + ]; + @deepTracked multiSelectSelectableData__demo5 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.selectableDataDemo5, + ]; + @tracked customSortBy_demo6: keyof MockDataSelectable | undefined = undefined; + @tracked customSortOrder_demo6 = 'asc'; + @deepTracked multiSelectSelectableData__demo6 = [ + // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components + ...this.model.selectableDataDemo6, + ]; + + // CUSTOM SORTING DEMO #1 + // Sortable table with custom sorting done via extra key added to the data model + + get clustersWithExtraData_demo1(): Record[] { + return this.model.clusters.map((record: MockDataCluster) => { + return { + ...record, + 'status-sort-order': customSortingCriteriaArray.indexOf( + record['status'], + ), + }; + }); + } + + // CUSTOM SORTING DEMO #2 + // Sortable table with custom `sortingFunction` declared in the column hash + + get customSortingFunction_demo2(): (s1: T, s2: T) => number { + return (s1, s2) => { + // check that s1 and s2 are objects and have the 'status' property + if ( + s1 instanceof Object && + s2 instanceof Object && + 'status' in s1 && + 'status' in s2 && + typeof s1['status'] === 'string' && + typeof s2['status'] === 'string' + ) { + const index1 = customSortingCriteriaArray.indexOf(s1['status']); + const index2 = customSortingCriteriaArray.indexOf(s2['status']); + if (index1 < index2) { + return this.customSortOrder_demo2 === 'asc' ? -1 : 1; + } else if (index1 > index2) { + return this.customSortOrder_demo2 === 'asc' ? 1 : -1; + } else { + return 0; + } + } + return 0; + }; + } + + @action + customOnSort_demo2(_sortBy: string, sortOrder: string) { + this.customSortOrder_demo2 = sortOrder; + } + + // CUSTOM SORTING DEMO #3 + // Sortable table with custom sorting using yielded `` + `sortBy/sortOrder/setSortBy` properties + + @action + onClickThSort__demo3(column: string, setSortBy?: (column: string) => void) { + // NOTICE: this code is a direct clone of the internal code of `Hds::Table` backing class + // we need to keep an internal state of the sorting + if (this.customSortBy_demo3 === column) { + // check to see if the column is already sorted and invert the sort order if so + this.customSortOrder_demo3 = + this.customSortOrder_demo3 === 'asc' ? 'desc' : 'asc'; + } else { + // otherwise, set the sort order to ascending + this.customSortBy_demo3 = column; + this.customSortOrder_demo3 = 'asc'; + } + // update the sorting icons for the table + if (setSortBy && typeof setSortBy === 'function') { + setSortBy(column); + } + } + + get sortedModelClusters__demo3() { + const clonedModelClusters = Array.from(this.model.clusters); + if (this.customSortBy_demo3 === 'peer-name') { + clonedModelClusters.sort((s1, s2) => { + const name1 = s1['peer-name'].toLowerCase(); + const name2 = s2['peer-name'].toLowerCase(); + if (name1 < name2) { + return this.customSortOrder_demo3 === 'asc' ? -1 : 1; + } + if (name1 > name2) { + return this.customSortOrder_demo3 === 'asc' ? 1 : -1; + } + return 0; + }); + } else if (this.customSortBy_demo3 === 'status') { + clonedModelClusters.sort((s1, s2) => { + const index1 = customSortingCriteriaArray.indexOf(s1['status']); + const index2 = customSortingCriteriaArray.indexOf(s2['status']); + if (index1 < index2) { + return this.customSortOrder_demo3 === 'asc' ? -1 : 1; + } else if (index1 > index2) { + return this.customSortOrder_demo3 === 'asc' ? 1 : -1; + } else { + return 0; + } + }); + } + return clonedModelClusters; + } + + @action + extraOnSortCallback_demo3() { + console.log( + `extraOnSortCallback called with customSortBy='${this.customSortBy_demo3}' and customSortOrder='${this.customSortOrder_demo3}'`, + ); + } + + // CUSTOM SORTING DEMO #4 + // Sortable table with custom sorting using yielded `` + `sortBy/sortOrder/setSortBy` properties + + sortModelClusters__demo4 = (sortBy?: string, sortOrder?: string) => { + // here goes the logic for the custom sorting of the `model` array based on `sortBy/sortOrder` + const clonedModelClusters = Array.from(this.model.clusters); + if (sortBy === 'peer-name') { + clonedModelClusters.sort((s1, s2) => { + const name1 = s1['peer-name'].toLowerCase(); + const name2 = s2['peer-name'].toLowerCase(); + if (name1 < name2) { + return sortOrder === 'asc' ? -1 : 1; + } + if (name1 > name2) { + return sortOrder === 'asc' ? 1 : -1; + } + return 0; + }); + } else if (sortBy === 'status') { + clonedModelClusters.sort((s1, s2) => { + const index1 = customSortingCriteriaArray.indexOf(s1['status']); + const index2 = customSortingCriteriaArray.indexOf(s2['status']); + if (index1 < index2) { + return sortOrder === 'asc' ? -1 : 1; + } else if (index1 > index2) { + return sortOrder === 'asc' ? 1 : -1; + } else { + return 0; + } + }); + } + return clonedModelClusters; + }; + + // CUSTOM SORTING DEMO #5 + // Sortable table with model and sorting by selected row + + @action + onMultiSelectSelectionChange__demo5({ + selectionKey, + selectionCheckboxElement, + }: HdsTableOnSelectionChangeSignature) { + if (selectionKey) { + if (selectionKey === 'all' && selectionCheckboxElement) { + this.multiSelectSelectableData__demo5.forEach( + (modelRow: MockDataSelectable) => { + modelRow.isSelected = selectionCheckboxElement.checked; + }, + ); + } else { + const recordToUpdate = this.multiSelectSelectableData__demo5.find( + (modelRow: MockDataSelectable) => + modelRow.id === Number(selectionKey), + ); + + if (recordToUpdate) { + recordToUpdate.isSelected = !recordToUpdate.isSelected; + } + } + } + } + + // CUSTOM SORTING DEMO #6 + // Sortable table with sorting by selected using yielded `/` + + get sortedMultiSelect__demo6() { + const clonedMultiSelect = Array.from(this.multiSelectSelectableData__demo6); + clonedMultiSelect.sort((s1: MockDataSelectable, s2: MockDataSelectable) => { + if (this.customSortBy_demo6) { + const v1 = s1[this.customSortBy_demo6]; + const v2 = s2[this.customSortBy_demo6]; + if (v1 < v2) { + return this.customSortOrder_demo6 === 'asc' ? -1 : 1; + } + if (v1 > v2) { + return this.customSortOrder_demo6 === 'asc' ? 1 : -1; + } + } + return 0; + }); + return clonedMultiSelect; + } + + @action + customOnSort_demo6(sortBy: string, sortOrder: string) { + this.customSortBy_demo6 = sortBy as keyof MockDataSelectable; + this.customSortOrder_demo6 = sortOrder; + } + + @action + onMultiSelectSelectionChange__demo6({ + selectionKey, + selectionCheckboxElement, + }: HdsTableOnSelectionChangeSignature) { + if (selectionKey) { + if (selectionKey === 'all' && selectionCheckboxElement) { + this.multiSelectSelectableData__demo5.forEach( + (modelRow: MockDataSelectable) => { + modelRow.isSelected = selectionCheckboxElement.checked; + }, + ); + } else { + const recordToUpdate = this.multiSelectSelectableData__demo5.find( + (modelRow: MockDataSelectable) => + modelRow.id === Number(selectionKey), + ); + + if (recordToUpdate) { + recordToUpdate.isSelected = !recordToUpdate.isSelected; + } + } + } + } + + // // GENERIC MULTI-SELECT FUNCTIONALITIES + + @action + onSelectionChangeLogArguments({ + selectionKey, + selectionCheckboxElement, + selectableRowsStates, + selectedRowsKeys, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selection Key:', selectionKey); + console.log('Checkbox Element:', selectionCheckboxElement); + console.log('Selectable Rows States:', selectableRowsStates); + console.log('Selected Rows Keys:', selectedRowsKeys); + console.groupEnd(); + } + + @action + mockIndeterminateState(checkbox: HTMLInputElement) { + checkbox.indeterminate = true; + } + + // MULTI-SELECT DEMO #1 + // Multi-select table with external filter for odd/even rows + + get multiSelectFilteredData__demo1() { + if (this.multiSelectFilterRows__demo1 === 'all') { + return this.multiSelectSelectableData__demo1; + } else { + const remainder = this.multiSelectFilterRows__demo1 === 'even' ? 0 : 1; + return this.multiSelectSelectableData__demo1.filter( + (item) => item.id % 2 === remainder, + ); + } + } + + @action + toggleMultiSelectToggleScope__demo1(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleScope__demo1 = checkbox.checked; + } + + @action + toggleMultiSelectToggleDebug__demo1(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleDebug__demo1 = checkbox.checked; + } + + @action + onChangeMultiSelectFilter__demo1(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectFilterRows__demo1 = checkbox.value; + } + + @action + onSelectionChangeWithModel__demo1({ + selectionKey, + selectionCheckboxElement, + selectableRowsStates, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selection Key:', selectionKey); + console.log('Checkbox Element:', selectionCheckboxElement); + console.log('Selectable Rows States:', selectableRowsStates); + console.groupEnd(); + if (selectionKey) { + if ( + selectionKey === 'all' && + selectionCheckboxElement && + this.multiSelectToggleScope__demo1 + ) { + updateModelWithSelectAllState( + this.multiSelectSelectableData__demo1, + selectionCheckboxElement.checked, + ); + } else { + updateModelWithSelectableRowsStates( + this.multiSelectSelectableData__demo1, + selectableRowsStates, + ); + } + } + } + + @action + onSelectionChangeWithoutModel__demo1({ + selectionKey, + selectionCheckboxElement, + selectableRowsStates, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selection Key:', selectionKey); + console.log('Checkbox Element:', selectionCheckboxElement); + console.log('Selectable Rows States:', selectableRowsStates); + console.groupEnd(); + // notice: the shape of the "model" is slightly different, it's not an array of objects but an object with keys so + // we can't use the `updateModelWithSelectAllsState` and `updateModelWithSelectableRowsStates` functions + if (selectionKey) { + if ( + selectionKey === 'all' && + selectionCheckboxElement && + this.multiSelectToggleScope__demo1 + ) { + const selectAllState = selectionCheckboxElement.checked; + Object.keys(this.multiSelectNoModelState__demo1).forEach((rowKey) => { + this.multiSelectNoModelState__demo1[ + rowKey as keyof MultiSelectNoModel + ] = selectAllState; + }); + } else { + const mapSelectionKeyToRowKey = ( + key: string | number, + ): keyof MultiSelectNoModel => { + return key as keyof MultiSelectNoModel; + }; + selectableRowsStates.forEach((row) => { + const rowKey = mapSelectionKeyToRowKey(row.selectionKey); + this.multiSelectNoModelState__demo1[rowKey] = row.isSelected + ? true + : false; + }); + } + } + } + + // // MULTI-SELECT DEMO #2 + // // Multi-select table with pagination + + @action + toggleMultiSelectPaginatedToggleScope__demo2(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleScope__demo2 = checkbox.checked; + } + + @action + toggleMultiSelectPaginatedToggleDebug__demo2(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleDebug__demo2 = checkbox.checked; + } + + get multiSelectPaginatedTotalItems_demo2() { + return this.multiSelectSelectableData__demo2.length; + } + + get multiSelectPaginatedData_demo2() { + const start = + (this.multiSelectPaginatedCurrentPage_demo2 - 1) * + this.multiSelectPaginatedCurrentPageSize_demo2; + const end = + this.multiSelectPaginatedCurrentPage_demo2 * + this.multiSelectPaginatedCurrentPageSize_demo2; + return this.multiSelectSelectableData__demo2.slice(start, end); + } + + @action + onMultiSelectPaginatedPageChange_demo2(page: number) { + this.multiSelectPaginatedCurrentPage_demo2 = page; + } + + @action + onMultiSelectPaginatedPageSizeChange_demo2(pageSize: number) { + // we agreed to reset the pagination to the first element (any alternative would result in an unpredictable UX) + this.multiSelectPaginatedCurrentPage_demo2 = 1; + this.multiSelectPaginatedCurrentPageSize_demo2 = pageSize; + } + + @action + onMultiSelectPaginatedSelectionChange__demo2({ + selectionKey, + selectionCheckboxElement, + selectableRowsStates, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selection Key:', selectionKey); + console.log('Checkbox Element:', selectionCheckboxElement); + console.log('Selectable Rows States:', selectableRowsStates); + console.groupEnd(); + if (selectionKey) { + if ( + selectionKey === 'all' && + selectionCheckboxElement && + this.multiSelectToggleScope__demo2 + ) { + updateModelWithSelectAllState( + this.multiSelectSelectableData__demo2, + selectionCheckboxElement.checked, + ); + } else { + updateModelWithSelectableRowsStates( + this.multiSelectSelectableData__demo2, + selectableRowsStates, + ); + } + } + } + + // // MULTI-SELECT DEMO #3 + // // Delete selected rows + + @action + toggleMultiSelectPaginatedToggleScope__demo3(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleScope__demo3 = checkbox.checked; + } + + @action + toggleMultiSelectPaginatedToggleDebug__demo3(event: Event) { + const checkbox = event.target as HTMLInputElement; + this.multiSelectToggleDebug__demo3 = checkbox.checked; + } + + get multiSelectUsersTotalItems_demo3() { + return this.multiSelectUserData__demo3.length; + } + + get multiSelectUsersData_demo3() { + const start = + (this.multiSelectUsersCurrentPage_demo3 - 1) * + this.multiSelectUsersCurrentPageSize_demo3; + const end = + this.multiSelectUsersCurrentPage_demo3 * + this.multiSelectUsersCurrentPageSize_demo3; + return this.multiSelectUserData__demo3.slice(start, end); + } + + @action + onMultiSelectUsersPageChange_demo3(page: number) { + this.multiSelectUsersCurrentPage_demo3 = page; + } + + @action + onMultiSelectUsersPageSizeChange_demo3(pageSize: number) { + // we agreed to reset the pagination to the first element (any alternative would result in an unpredictable UX) + this.multiSelectUsersCurrentPage_demo3 = 1; + this.multiSelectUsersCurrentPageSize_demo3 = pageSize; + } + + @action + onMultiSelectUsersSelectionChange__demo3({ + selectionKey, + selectionCheckboxElement, + selectableRowsStates, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selection Key:', selectionKey); + console.log('Checkbox Element:', selectionCheckboxElement); + console.log('Selectable Rows States:', selectableRowsStates); + console.groupEnd(); + if (selectionKey) { + if ( + selectionKey === 'all' && + selectionCheckboxElement && + this.multiSelectToggleScope__demo3 + ) { + updateModelWithSelectAllState( + this.multiSelectUserData__demo3, + selectionCheckboxElement.checked, + ); + } else { + selectableRowsStates.forEach((row) => { + const recordToUpdate = this.multiSelectUserData__demo3.find( + (modelRow) => modelRow.id === Number(row.selectionKey), + ); + if (recordToUpdate) { + recordToUpdate.isSelected = row.isSelected; + } + }); + } + } + } + + @action + multiSelectDeleteSelectedUsers_demo3() { + const newData = this.multiSelectUserData__demo3.filter( + (user) => !user.isSelected, + ); + this.multiSelectUserData__demo3 = [...newData]; + } + + // // MULTI-SELECT DEMO #4 + // // Execute action on selected rows + + @action + onMultiSelectSelectionChange__demo4({ + selectedRowsKeys, + }: HdsTableOnSelectionChangeSignature) { + console.group('Selection Change with Model Arguments'); + console.log('Selected Row Keys:', selectedRowsKeys); + console.groupEnd(); + this.multiSelectUserData__demo4.forEach((user) => { + user.isSelected = selectedRowsKeys.includes(user.id); + }); + } + + @action + multiSelectAnimateSelectedUsers_demo4() { + this.multiSelectUserData__demo4.forEach((user) => { + user.isAnimated = user.isSelected; + }); + // eslint-disable-next-line ember/no-runloop + later(() => { + this.multiSelectResetUserAnimation_demo4(); + }, 5000); + } + + @action + multiSelectResetUserAnimation_demo4() { + this.multiSelectUserData__demo4.forEach((user) => { + user.isAnimated = false; + }); + } + + @action + noop() { + // no-op + } +} diff --git a/showcase/app/routes/page-components/table.js b/showcase/app/routes/page-components/table.js deleted file mode 100644 index 139ea5a41f3..00000000000 --- a/showcase/app/routes/page-components/table.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import Route from '@ember/routing/route'; -import { DENSITIES } from '@hashicorp/design-system-components/components/hds/table/index'; - -import clusters from 'showcase/mocks/cluster-data'; -import folkMusic from 'showcase/mocks/folk-music-data'; -import selectableItems from 'showcase/mocks/selectable-item-data'; -import users from 'showcase/mocks/user-data'; -import userWithMoreColumns from 'showcase/mocks/user-with-more-columns-data'; - -// basic function that clones an array of objects (not deep) -const clone = (arr) => { - return arr.map((item) => ({ ...item })); -}; - -const STATES = ['default', 'hover', 'active', 'focus']; - -export default class PageComponentsTableRoute extends Route { - model() { - return { - music: folkMusic, - selectableData: selectableItems, - selectableDataDemo1: clone(selectableItems), - selectableDataDemo2: clone(selectableItems), - selectableDataDemo5: clone(selectableItems), - selectableDataDemo6: clone(selectableItems), - userDataDemo3: clone(users.slice(0, 16)), - userDataDemo4: clone(users.slice(0, 4)), - clusters, - manycolumns: userWithMoreColumns, - DENSITIES, - STATES, - }; - } -} diff --git a/showcase/app/routes/page-components/table.ts b/showcase/app/routes/page-components/table.ts new file mode 100644 index 00000000000..1cc7fd00f8d --- /dev/null +++ b/showcase/app/routes/page-components/table.ts @@ -0,0 +1,137 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Route from '@ember/routing/route'; +import { DENSITIES } from '@hashicorp/design-system-components/components/hds/table/index'; + +import type { ModelFrom } from 'showcase/utils/ModelFromRoute'; +import type { + HdsBadgeTypes, + HdsBadgeColors, +} from '@hashicorp/design-system-components/components/hds/badge/types'; +import type { HdsIconSignature } from '@hashicorp/design-system-components/components/hds/icon/index'; + +export interface MockDataMusic { + id: number; + type: string; + attributes: { + artist: string; + album: string; + year: string; + quote: string; + 'vinyl-cost': string; + icon: HdsIconSignature['Args']['name']; + 'badge-type': HdsBadgeTypes; + 'badge-color': { + name: HdsBadgeColors; + key: number; + }; + color: HdsBadgeColors; + }; +} +export interface MockDataMusicItem { + id: number; + type: string; + attributes: { + artist: string; + album: string; + year: string; + quote: string; + 'vinyl-cost': string; + icon: string; + 'badge-type': HdsBadgeTypes; + 'badge-color': { + name: HdsBadgeColors; + key: number; + }; + color: HdsBadgeColors; + }; +} + +export interface MockDataCluster { + id: number; + 'peer-name': string; + 'cluster-partition': string; + status: string; + services: { + imported: number; + exported: number; + }; +} + +export interface MockDataManyColumns { + first_name: string; + last_name: string; + age: string; + email: string; + phone: string; + education: string; + occupation: string; + bio: string; +} + +export interface MockDataSelectable { + id: number; + lorem: string; + ipsum: string; + dolor: string; + isSelected: boolean; +} + +export interface MockDataUser { + id: number; + name: string; + email: string; + role: 'Owner' | 'Admin' | 'Contributor'; + isSelected?: boolean; + isAnimated?: boolean; +} + +export type PageComponentsTableModel = ModelFrom; + +// basic function that clones an array of objects (not deep) +export const clone = (arr: T[]): T[] => { + return arr.map((item) => ({ ...item })); +}; + +export default class PageComponentsTableRoute extends Route { + async model() { + const STATES = ['default', 'hover', 'active', 'focus']; + + const responseMusic = await fetch('/api/folk.json'); + const responseClusters = await fetch('/api/mock-clusters-with-status.json'); + const responseManyColumns = await fetch('/api/mock-many-columns.json'); + const responseSelectableData = await fetch( + '/api/mock-selectable-data.json', + ); + const responseUserData = await fetch('/api/mock-users.json'); + + const { data: music } = (await responseMusic.json()) as Record< + 'data', + MockDataMusic[] + >; + const clusters = (await responseClusters.json()) as MockDataCluster[]; + const manycolumns = + (await responseManyColumns.json()) as MockDataManyColumns[]; + const selectableData = + (await responseSelectableData.json()) as MockDataSelectable[]; + const userData = (await responseUserData.json()) as MockDataUser[]; + + return { + music: music.map((record) => ({ id: record.id, ...record.attributes })), + selectableData, + selectableDataDemo1: clone(selectableData), + selectableDataDemo2: clone(selectableData), + userDataDemo3: clone(userData.slice(0, 16)), + userDataDemo4: clone(userData.slice(0, 4)), + selectableDataDemo5: clone(selectableData), + selectableDataDemo6: clone(selectableData), + clusters, + manycolumns, + DENSITIES, + STATES, + }; + } +} diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 99a291bc516..92e5702fdb3 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -3,6 +3,7 @@ SPDX-License-Identifier: MPL-2.0 }} +{{! @glint-expect-error - file will be broken up in https://hashicorp.atlassian.net/browse/HDS-5077}} {{page-title "Table Component"}} Table @@ -22,8 +23,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -76,8 +80,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -95,8 +102,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -114,8 +124,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -134,8 +147,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -166,20 +182,24 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.peer-name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.cluster-partition}} - {{#if (eq B.data.status "failing")}} + {{#if (eq (get B.data "status") "failing")}} - {{else if (eq B.data.status "active")}} + {{else if (eq (get B.data "status") "active")}} - {{else if (eq B.data.status "pending")}} + {{else if (eq (get B.data "status") "pending")}} - {{else if (eq B.data.status "establishing")}} + {{else if (eq (get B.data "status") "establishing")}} {{/if}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.services.imported}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.services.exported}} @@ -202,6 +222,7 @@ Sortable table with custom sortingFunction declared in the column hash <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.peer-name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.cluster-partition}} - {{#if (eq B.data.status "failing")}} + {{#if (eq (get B.data "status") "failing")}} - {{else if (eq B.data.status "active")}} + {{else if (eq (get B.data "status") "active")}} - {{else if (eq B.data.status "pending")}} + {{else if (eq (get B.data "status") "pending")}} - {{else if (eq B.data.status "establishing")}} + {{else if (eq (get B.data "status") "establishing")}} {{/if}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.services.imported}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.services.exported}} @@ -260,15 +285,19 @@ <:head as |H|> - Peer name + {{#if H.setSortBy}} + Peer name + {{/if}} Cluster partition - Status + {{#if H.setSortBy}} + Status + {{/if}} Imported services Exported services Actions @@ -280,13 +309,13 @@ {{item.peer-name}} {{item.cluster-partition}} - {{#if (eq item.status "failing")}} + {{#if (eq (get item "status") "failing")}} - {{else if (eq item.status "active")}} + {{else if (eq (get item "status") "active")}} - {{else if (eq item.status "pending")}} + {{else if (eq (get item "status") "pending")}} - {{else if (eq item.status "establishing")}} + {{else if (eq (get item "status") "establishing")}} {{/if}} @@ -322,15 +351,19 @@ <:head as |H|> - Peer name + {{#if H.setSortBy}} + Peer name + {{/if}} Cluster partition - Status + {{#if H.setSortBy}} + Status + {{/if}} Imported services Exported services Actions @@ -342,13 +375,13 @@ {{item.peer-name}} {{item.cluster-partition}} - {{#if (eq item.status "failing")}} + {{#if (eq (get item "status") "failing")}} - {{else if (eq item.status "active")}} + {{else if (eq (get item "status") "active")}} - {{else if (eq item.status "pending")}} + {{else if (eq (get item "status") "pending")}} - {{else if (eq item.status "establishing")}} + {{else if (eq (get item "status") "establishing")}} {{/if}} @@ -386,9 +419,13 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.vinyl-cost}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -437,6 +474,7 @@ <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.lorem}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.ipsum}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.dolor}} @@ -501,6 +546,7 @@ @isSelectable={{true}} @selectableColumnKey="isSelected" @onSelectionChange={{this.onMultiSelectSelectionChange__demo5}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} @model={{this.multiSelectSelectableData__demo5}} @columns={{array (hash key="lorem" label="Row #" isSortable=true) @@ -509,13 +555,20 @@ }} > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.lorem}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.ipsum}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.dolor}} @@ -539,13 +592,16 @@ > <:head as |H|> - Row # + {{#if H.setSortBy}} + Row # + {{/if}} Ipsum Dolor <:body as |B|> {{#each this.sortedMultiSelect__demo6 as |record|}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} Functional examples + {{! ================================================ }} + {{! ===== DEMO #1 - MULTI-SELECT INLINE FILTER ===== }} + {{! ================================================ }} + With inline filter
@@ -605,6 +665,7 @@ <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.lorem}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.ipsum}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.dolor}} {{#if this.multiSelectToggleDebug__demo1}} - {{#each this.multiSelectModelData__demo1 as |row|}} + {{#each this.multiSelectSelectableData__demo1 as |row|}}
row{{row.id}} = {{if row.isSelected "✅"}}
{{/each}} {{/if}} @@ -652,7 +720,9 @@ 2 Cell Content - Cell Content + Cell Content + {{get this.multiSelectNoModelState__demo1 "row2"}} + {{/if}} {{#if (not (eq this.multiSelectFilterRows__demo1 "even"))}} @@ -680,6 +750,10 @@ + {{! ============================================= }} + {{! ===== DEMO #2 - MULTI-SELECT PAGINATION ===== }} + {{! ============================================= }} + With pagination
@@ -707,6 +781,7 @@ <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.lorem}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.ipsum}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.dolor}} @@ -736,12 +818,16 @@ @ariaLabel="Pagination for basic multi-select table" /> {{#if this.multiSelectToggleDebug__demo2}} - {{#each this.multiSelectModelData__demo2 as |row|}} + {{#each this.multiSelectSelectableData__demo2 as |row|}}
row{{row.id}} = {{if row.isSelected "✅"}}
{{/each}} {{/if}}
+ {{! ============================== }} + {{! ===== DEMO #3 - DELETION ===== }} + {{! ============================== }} + Delete selected rows This demo emulates, for example, when a user needs to delete the selected users. @@ -774,6 +860,7 @@ <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.id}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.role}} @@ -805,12 +900,16 @@ @ariaLabel="Pagination for multi-select table with delete action" /> {{#if this.multiSelectToggleDebug__demo3}} - {{#each this.multiSelectModelData__demo3 as |row|}} + {{#each this.multiSelectUserData__demo3 as |row|}}
row{{row.id}} = {{if row.isSelected "✅"}}
{{/each}} {{/if}}
+ {{! ===================================== }} + {{! ===== DEMO #4 - EXTERNAL ACTION ===== }} + {{! ===================================== }} + Execute action on selected rows This demo emulates, for example, when a user needs to download the selected files. @@ -824,6 +923,7 @@ <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.id}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.role}} @@ -855,51 +963,49 @@ Density - {{#let (array "default" "short" "tall") as |densities|}} - {{#each densities as |density|}} - - - {{#let (array false true) as |booleans|}} - {{#each booleans as |bool|}} - - - <:head as |H|> - - Lorem - Ipsum - Dolor - - - <:body as |B|> - - Scope Row - Cell Content - Cell Content - - - Scope Row - Cell Content - Cell Content - - - Scope Row - Cell Content - Cell Content - - - Scope Row - Cell Content - Cell Content - - - - - {{/each}} - {{/let}} - - - {{/each}} - {{/let}} + {{#each this.model.DENSITIES as |density|}} + + + {{#let (array false true) as |booleans|}} + {{#each booleans as |bool|}} + + + <:head as |H|> + + Lorem + Ipsum + Dolor + + + <:body as |B|> + + Scope Row + Cell Content + Cell Content + + + Scope Row + Cell Content + Cell Content + + + Scope Row + Cell Content + Cell Content + + + Scope Row + Cell Content + Cell Content + + + + + {{/each}} + {{/let}} + + + {{/each}} @@ -962,8 +1068,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} @@ -992,9 +1101,13 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.year}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.vinyl-cost}} @@ -1013,10 +1126,13 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}}
+ {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}}
@@ -1047,8 +1163,11 @@ > <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.artist}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.album}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} “{{B.data.quote}}” @@ -1063,6 +1182,7 @@ Width in px + Table-layout = 'auto' <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} @@ -1090,6 +1215,7 @@ applied to the table, with only some columns having a width declared. <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} @@ -1114,6 +1245,7 @@ Width in % + Table-layout = 'auto' <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} @@ -1137,6 +1274,7 @@ Width in % + Table-layout = 'fixed' <:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} @@ -1167,6 +1310,7 @@
<:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.phone}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.education}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.occupation}} @@ -1201,6 +1353,7 @@
<:body as |B|> + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.first_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.last_name}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.age}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.email}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.phone}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.bio}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.education}} + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} {{B.data.occupation}} From 28ee54caac626dc0131f23a72e426f20c34d4a0d Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Tue, 29 Jul 2025 14:54:36 -0400 Subject: [PATCH 02/12] Feat: Add `ember-composable-helpers` to showcase devDependencies --- pnpm-lock.yaml | 3 +++ showcase/package.json | 1 + 2 files changed, 4 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0d9b3c64f3..22681a858fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -529,6 +529,9 @@ importers: '@hashicorp/design-system-tokens': specifier: workspace:* version: link:../packages/tokens + '@nullvoxpopuli/ember-composable-helpers': + specifier: ^5.2.11 + version: 5.2.11(@babel/core@7.27.1)(ember-source@6.4.0(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)) '@percy/cli': specifier: ^1.30.5 version: 1.31.1(typescript@5.9.2) diff --git a/showcase/package.json b/showcase/package.json index 454bee679d3..3ebc39f9345 100644 --- a/showcase/package.json +++ b/showcase/package.json @@ -57,6 +57,7 @@ "@glint/template": "^1.5.2", "@hashicorp/design-system-components": "workspace:*", "@hashicorp/design-system-tokens": "workspace:*", + "@nullvoxpopuli/ember-composable-helpers": "^5.2.11", "@percy/cli": "^1.30.5", "@percy/ember": "^4.2.0", "@tsconfig/ember": "^3.0.10", From a506d4ac119872a9e8836a913e66f5bafadbaa1d Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Tue, 29 Jul 2025 16:44:06 -0400 Subject: [PATCH 03/12] Fix: Remove testing example from table template --- showcase/app/templates/page-components/table.hbs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 92e5702fdb3..9bf4b2b4c1b 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -720,9 +720,7 @@ 2 Cell Content - Cell Content - {{get this.multiSelectNoModelState__demo1 "row2"}} - + Cell Content {{/if}} {{#if (not (eq this.multiSelectFilterRows__demo1 "even"))}} From 0aba88dffe539d3e7769a859d8d818f30aa58a8b Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Wed, 30 Jul 2025 13:53:59 -0400 Subject: [PATCH 04/12] Fix: Set `@selectionKey` in showcase to string values --- showcase/app/controllers/page-components/table.ts | 2 +- showcase/app/templates/page-components/table.hbs | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index 3aa7dd190cf..d1d6fedf523 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -620,7 +620,7 @@ export default class PageComponentsTableController extends Controller { console.log('Selected Row Keys:', selectedRowsKeys); console.groupEnd(); this.multiSelectUserData__demo4.forEach((user) => { - user.isSelected = selectedRowsKeys.includes(user.id); + user.isSelected = selectedRowsKeys.includes(String(user.id)); }); } diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 9bf4b2b4c1b..93479f56072 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -486,7 +486,7 @@ {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} <:body as |B|> {{#each this.sortedMultiSelect__demo6 as |record|}} - {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} @@ -677,7 +676,7 @@ {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} Date: Mon, 4 Aug 2025 11:00:12 -0400 Subject: [PATCH 05/12] Fix: Update lockfile --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22681a858fa..141c7b2cafe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -531,7 +531,7 @@ importers: version: link:../packages/tokens '@nullvoxpopuli/ember-composable-helpers': specifier: ^5.2.11 - version: 5.2.11(@babel/core@7.27.1)(ember-source@6.4.0(@glimmer/component@1.1.2(@babel/core@7.27.1))(rsvp@4.8.5)) + version: 5.2.11(@babel/core@7.28.0)(ember-source@6.5.0(@glimmer/component@1.1.2(@babel/core@7.28.0))(rsvp@4.8.5)) '@percy/cli': specifier: ^1.30.5 version: 1.31.1(typescript@5.9.2) From f615b9d3b97571f9f691e3dd1f4a8159a70bcf3b Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Mon, 4 Aug 2025 11:02:15 -0400 Subject: [PATCH 06/12] Fix: Use `console.group` in controller --- showcase/app/controllers/page-components/table.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index d1d6fedf523..341f586fcfd 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -211,9 +211,10 @@ export default class PageComponentsTableController extends Controller { @action extraOnSortCallback_demo3() { - console.log( - `extraOnSortCallback called with customSortBy='${this.customSortBy_demo3}' and customSortOrder='${this.customSortOrder_demo3}'`, - ); + console.group('extraOnSortCallback called with:'); + console.log('customSortBy:', this.customSortBy_demo3); + console.log('customSortOrder:', this.customSortOrder_demo3); + console.groupEnd(); } // CUSTOM SORTING DEMO #4 From dc36108de32b42655991c63229132d8d65734d3a Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Mon, 4 Aug 2025 11:11:49 -0400 Subject: [PATCH 07/12] Fix: Remove unneeded check for `H.setSortBy` --- .../app/templates/page-components/table.hbs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 93479f56072..0c8f347f4c8 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -285,19 +285,15 @@ <:head as |H|> - {{#if H.setSortBy}} - Peer name - {{/if}} + Peer name Cluster partition - {{#if H.setSortBy}} - Status - {{/if}} + Status Imported services Exported services Actions From 02352727777587c5a48af44ad82a7f0e06043b2b Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Thu, 7 Aug 2025 09:35:52 -0400 Subject: [PATCH 08/12] Feat: Align table controller to adv table controller --- .../app/controllers/page-components/table.ts | 186 +++++++++--------- .../app/templates/page-components/table.hbs | 2 +- 2 files changed, 90 insertions(+), 98 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index 341f586fcfd..cd5bea4579c 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -10,13 +10,12 @@ import { deepTracked } from 'ember-deep-tracked'; import { later } from '@ember/runloop'; import type { PageComponentsTableModel } from '../../routes/page-components/table'; -import type { HdsTableOnSelectionChangeSignature } from '@hashicorp/design-system-components/components/hds/table/types'; - import type { - MockDataCluster, - MockDataSelectable, - MockDataUser, -} from '../../routes/page-components/table'; + HdsTableOnSelectionChangeSignature, + HdsTableThSortOrder, +} from '@hashicorp/design-system-components/components/hds/table/types'; + +import type { MockDataSelectable } from '../../routes/page-components/table'; type MultiSelectNoModel = { row1: boolean; @@ -33,29 +32,35 @@ const customSortingCriteriaArray = [ 'pending', ]; -const updateModelWithSelectAllState = ( - modelData: MockDataSelectable[] | MockDataUser[], +const updateModelWithSelectAllState = ( + modelData: T[], selectAllState: boolean, ) => { modelData.forEach((modelRow) => { - modelRow.isSelected = selectAllState; + if (modelRow instanceof Object && 'isSelected' in modelRow) { + modelRow.isSelected = selectAllState; + } }); }; -const updateModelWithSelectableRowsStates = ( - modelData: MockDataSelectable[] | MockDataUser[], +function updateModelWithSelectableRowsStates< + T extends { id: number; isSelected?: boolean }, +>( + modelData: T[], selectableRowsStates: HdsTableOnSelectionChangeSignature['selectableRowsStates'], -) => { - const modelDataMap = new Map( - modelData.map((modelRow) => [modelRow.id, modelRow]), +): void { + // Create a map from id to row for quick lookup + const modelDataMap: Map = new Map( + modelData.map((modelRow) => [String(modelRow.id), modelRow]), ); + selectableRowsStates.forEach((row) => { - // safe to assume that there is always a record for the "selectionKey" since it's coming from the model (the selectable "rows" are a subset of the model dataset) - modelDataMap.get(Number(row.selectionKey))!.isSelected = row.isSelected - ? true - : false; + const record = modelDataMap.get(row.selectionKey); + if (record) { + record.isSelected = row.isSelected; + } }); -}; +} export default class PageComponentsTableController extends Controller { declare model: PageComponentsTableModel; @@ -110,11 +115,8 @@ export default class PageComponentsTableController extends Controller { ...this.model.selectableDataDemo6, ]; - // CUSTOM SORTING DEMO #1 - // Sortable table with custom sorting done via extra key added to the data model - - get clustersWithExtraData_demo1(): Record[] { - return this.model.clusters.map((record: MockDataCluster) => { + get clustersWithExtraData() { + return this.model.clusters.map((record) => { return { ...record, 'status-sort-order': customSortingCriteriaArray.indexOf( @@ -153,7 +155,7 @@ export default class PageComponentsTableController extends Controller { } @action - customOnSort_demo2(_sortBy: string, sortOrder: string) { + customOnSort_demo2(_sortBy: string, sortOrder: HdsTableThSortOrder) { this.customSortOrder_demo2 = sortOrder; } @@ -331,20 +333,15 @@ export default class PageComponentsTableController extends Controller { } } - // // GENERIC MULTI-SELECT FUNCTIONALITIES + // GENERIC MULTI-SELECT FUNCTIONALITIES @action - onSelectionChangeLogArguments({ - selectionKey, - selectionCheckboxElement, - selectableRowsStates, - selectedRowsKeys, - }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); - console.log('Selection Key:', selectionKey); - console.log('Checkbox Element:', selectionCheckboxElement); - console.log('Selectable Rows States:', selectableRowsStates); - console.log('Selected Rows Keys:', selectedRowsKeys); + onSelectionChangeLogArguments(args: HdsTableOnSelectionChangeSignature) { + console.group('onSelectionChangeLogArguments'); + console.log('Selection Key:', args.selectionKey); + console.log('Checkbox Element:', args.selectionCheckboxElement); + console.log('Selectable Rows Keys:', args.selectedRowsKeys); + console.log('Selectable Rows States:', args.selectableRowsStates); console.groupEnd(); } @@ -369,20 +366,23 @@ export default class PageComponentsTableController extends Controller { @action toggleMultiSelectToggleScope__demo1(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleScope__demo1 = checkbox.checked; + this.multiSelectToggleScope__demo1 = ( + event.target as HTMLInputElement + ).checked; } @action toggleMultiSelectToggleDebug__demo1(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleDebug__demo1 = checkbox.checked; + this.multiSelectToggleDebug__demo1 = ( + event.target as HTMLInputElement + ).checked; } @action onChangeMultiSelectFilter__demo1(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectFilterRows__demo1 = checkbox.value; + this.multiSelectFilterRows__demo1 = ( + event.target as HTMLInputElement + ).value; } @action @@ -391,7 +391,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); + console.group('onSelectionChangeWithModel__demo1'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -421,7 +421,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); + console.group('onSelectionChangeWithoutModel__demo1'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -456,19 +456,21 @@ export default class PageComponentsTableController extends Controller { } } - // // MULTI-SELECT DEMO #2 - // // Multi-select table with pagination + // MULTI-SELECT DEMO #2 + // Multi-select table with pagination @action toggleMultiSelectPaginatedToggleScope__demo2(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleScope__demo2 = checkbox.checked; + this.multiSelectToggleScope__demo2 = ( + event.target as HTMLInputElement + ).checked; } @action toggleMultiSelectPaginatedToggleDebug__demo2(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleDebug__demo2 = checkbox.checked; + this.multiSelectToggleDebug__demo2 = ( + event.target as HTMLInputElement + ).checked; } get multiSelectPaginatedTotalItems_demo2() { @@ -503,43 +505,39 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); + console.group('onMultiSelectPaginatedSelectionChange__demo2'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); console.groupEnd(); - if (selectionKey) { - if ( - selectionKey === 'all' && - selectionCheckboxElement && - this.multiSelectToggleScope__demo2 - ) { - updateModelWithSelectAllState( - this.multiSelectSelectableData__demo2, - selectionCheckboxElement.checked, - ); - } else { - updateModelWithSelectableRowsStates( - this.multiSelectSelectableData__demo2, - selectableRowsStates, - ); - } + if (selectionKey === 'all' && this.multiSelectToggleScope__demo2) { + updateModelWithSelectAllState( + this.multiSelectSelectableData__demo2, + selectionCheckboxElement ? selectionCheckboxElement.checked : false, + ); + } else { + updateModelWithSelectableRowsStates( + this.multiSelectSelectableData__demo2, + selectableRowsStates, + ); } } - // // MULTI-SELECT DEMO #3 - // // Delete selected rows + // MULTI-SELECT DEMO #3 + // Delete selected rows @action toggleMultiSelectPaginatedToggleScope__demo3(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleScope__demo3 = checkbox.checked; + this.multiSelectToggleScope__demo3 = ( + event.target as HTMLInputElement + ).checked; } @action toggleMultiSelectPaginatedToggleDebug__demo3(event: Event) { - const checkbox = event.target as HTMLInputElement; - this.multiSelectToggleDebug__demo3 = checkbox.checked; + this.multiSelectToggleDebug__demo3 = ( + event.target as HTMLInputElement + ).checked; } get multiSelectUsersTotalItems_demo3() { @@ -574,31 +572,25 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); + console.group('onMultiSelectUsersSelectionChange__demo3'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); console.groupEnd(); - if (selectionKey) { - if ( - selectionKey === 'all' && - selectionCheckboxElement && - this.multiSelectToggleScope__demo3 - ) { - updateModelWithSelectAllState( - this.multiSelectUserData__demo3, - selectionCheckboxElement.checked, + if (selectionKey === 'all' && this.multiSelectToggleScope__demo3) { + updateModelWithSelectAllState( + this.multiSelectUserData__demo3, + selectionCheckboxElement ? selectionCheckboxElement.checked : false, + ); + } else { + selectableRowsStates.forEach((row) => { + const recordToUpdate = this.multiSelectUserData__demo3.find( + (modelRow) => String(modelRow.id) === row.selectionKey, ); - } else { - selectableRowsStates.forEach((row) => { - const recordToUpdate = this.multiSelectUserData__demo3.find( - (modelRow) => modelRow.id === Number(row.selectionKey), - ); - if (recordToUpdate) { - recordToUpdate.isSelected = row.isSelected; - } - }); - } + if (recordToUpdate) { + recordToUpdate.isSelected = row.isSelected; + } + }); } } @@ -610,14 +602,14 @@ export default class PageComponentsTableController extends Controller { this.multiSelectUserData__demo3 = [...newData]; } - // // MULTI-SELECT DEMO #4 - // // Execute action on selected rows + // MULTI-SELECT DEMO #4 + // Execute action on selected rows @action onMultiSelectSelectionChange__demo4({ selectedRowsKeys, }: HdsTableOnSelectionChangeSignature) { - console.group('Selection Change with Model Arguments'); + console.group('onMultiSelectSelectionChange__demo4'); console.log('Selected Row Keys:', selectedRowsKeys); console.groupEnd(); this.multiSelectUserData__demo4.forEach((user) => { diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 0c8f347f4c8..0f0139e3d30 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -168,7 +168,7 @@ Sortable table with custom sorting done via extra key added to the data model Date: Thu, 7 Aug 2025 09:38:37 -0400 Subject: [PATCH 09/12] Fix: Update `console.group` titles --- showcase/app/controllers/page-components/table.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index cd5bea4579c..b6149f2a9c4 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -213,7 +213,7 @@ export default class PageComponentsTableController extends Controller { @action extraOnSortCallback_demo3() { - console.group('extraOnSortCallback called with:'); + console.group('extraOnSortCallback_demo3 invoked:'); console.log('customSortBy:', this.customSortBy_demo3); console.log('customSortOrder:', this.customSortOrder_demo3); console.groupEnd(); @@ -337,7 +337,7 @@ export default class PageComponentsTableController extends Controller { @action onSelectionChangeLogArguments(args: HdsTableOnSelectionChangeSignature) { - console.group('onSelectionChangeLogArguments'); + console.group('onSelectionChangeLogArguments invoked with arguments:'); console.log('Selection Key:', args.selectionKey); console.log('Checkbox Element:', args.selectionCheckboxElement); console.log('Selectable Rows Keys:', args.selectedRowsKeys); @@ -391,7 +391,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onSelectionChangeWithModel__demo1'); + console.group('onSelectionChangeWithModel__demo1 invoked with arguments:'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -421,7 +421,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onSelectionChangeWithoutModel__demo1'); + console.group('onSelectionChangeWithoutModel__demo1 invoked with arguments:'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -505,7 +505,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectPaginatedSelectionChange__demo2'); + console.group('onMultiSelectPaginatedSelectionChange__demo2 invoked with arguments:'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -572,7 +572,7 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectUsersSelectionChange__demo3'); + console.group('onMultiSelectUsersSelectionChange__demo3 invoked with arguments:'); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -609,7 +609,7 @@ export default class PageComponentsTableController extends Controller { onMultiSelectSelectionChange__demo4({ selectedRowsKeys, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectSelectionChange__demo4'); + console.group('onMultiSelectSelectionChange__demo4 invoked with arguments:'); console.log('Selected Row Keys:', selectedRowsKeys); console.groupEnd(); this.multiSelectUserData__demo4.forEach((user) => { From b33a4da12dca3fbc8a1074a903099294cb026988 Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Thu, 7 Aug 2025 09:57:17 -0400 Subject: [PATCH 10/12] Fix: Linting --- .../app/controllers/page-components/table.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index b6149f2a9c4..a5a35560eb5 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -421,7 +421,9 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onSelectionChangeWithoutModel__demo1 invoked with arguments:'); + console.group( + 'onSelectionChangeWithoutModel__demo1 invoked with arguments:', + ); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -505,7 +507,9 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectPaginatedSelectionChange__demo2 invoked with arguments:'); + console.group( + 'onMultiSelectPaginatedSelectionChange__demo2 invoked with arguments:', + ); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -572,7 +576,9 @@ export default class PageComponentsTableController extends Controller { selectionCheckboxElement, selectableRowsStates, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectUsersSelectionChange__demo3 invoked with arguments:'); + console.group( + 'onMultiSelectUsersSelectionChange__demo3 invoked with arguments:', + ); console.log('Selection Key:', selectionKey); console.log('Checkbox Element:', selectionCheckboxElement); console.log('Selectable Rows States:', selectableRowsStates); @@ -609,7 +615,9 @@ export default class PageComponentsTableController extends Controller { onMultiSelectSelectionChange__demo4({ selectedRowsKeys, }: HdsTableOnSelectionChangeSignature) { - console.group('onMultiSelectSelectionChange__demo4 invoked with arguments:'); + console.group( + 'onMultiSelectSelectionChange__demo4 invoked with arguments:', + ); console.log('Selected Row Keys:', selectedRowsKeys); console.groupEnd(); this.multiSelectUserData__demo4.forEach((user) => { From 3cea4bcc27d45cd186248a862939ed200babb8f7 Mon Sep 17 00:00:00 2001 From: Dylan Hyun Date: Thu, 7 Aug 2025 14:30:44 -0400 Subject: [PATCH 11/12] Feat: Update table to use new data imports --- .../app/controllers/page-components/table.ts | 27 ++-- showcase/app/routes/page-components/table.ts | 130 +++--------------- .../app/templates/page-components/table.hbs | 24 +++- 3 files changed, 49 insertions(+), 132 deletions(-) diff --git a/showcase/app/controllers/page-components/table.ts b/showcase/app/controllers/page-components/table.ts index a5a35560eb5..6e37afebe12 100644 --- a/showcase/app/controllers/page-components/table.ts +++ b/showcase/app/controllers/page-components/table.ts @@ -15,7 +15,8 @@ import type { HdsTableThSortOrder, } from '@hashicorp/design-system-components/components/hds/table/types'; -import type { MockDataSelectable } from '../../routes/page-components/table'; +import type { SelectableItem } from 'showcase/mocks/selectable-item-data'; +import type { User } from 'showcase/mocks/user-data'; type MultiSelectNoModel = { row1: boolean; @@ -32,12 +33,12 @@ const customSortingCriteriaArray = [ 'pending', ]; -const updateModelWithSelectAllState = ( - modelData: T[], +const updateModelWithSelectAllState = ( + modelData: SelectableItem[] | User[], selectAllState: boolean, ) => { modelData.forEach((modelRow) => { - if (modelRow instanceof Object && 'isSelected' in modelRow) { + if (modelRow instanceof Object) { modelRow.isSelected = selectAllState; } }); @@ -108,7 +109,7 @@ export default class PageComponentsTableController extends Controller { // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components ...this.model.selectableDataDemo5, ]; - @tracked customSortBy_demo6: keyof MockDataSelectable | undefined = undefined; + @tracked customSortBy_demo6: keyof SelectableItem | undefined = undefined; @tracked customSortOrder_demo6 = 'asc'; @deepTracked multiSelectSelectableData__demo6 = [ // @ts-expect-error - we need to reevaluate how we get the data for the table demos when we break up the template files into sub components @@ -264,14 +265,13 @@ export default class PageComponentsTableController extends Controller { if (selectionKey) { if (selectionKey === 'all' && selectionCheckboxElement) { this.multiSelectSelectableData__demo5.forEach( - (modelRow: MockDataSelectable) => { + (modelRow: SelectableItem) => { modelRow.isSelected = selectionCheckboxElement.checked; }, ); } else { const recordToUpdate = this.multiSelectSelectableData__demo5.find( - (modelRow: MockDataSelectable) => - modelRow.id === Number(selectionKey), + (modelRow: SelectableItem) => modelRow.id === Number(selectionKey), ); if (recordToUpdate) { @@ -286,7 +286,7 @@ export default class PageComponentsTableController extends Controller { get sortedMultiSelect__demo6() { const clonedMultiSelect = Array.from(this.multiSelectSelectableData__demo6); - clonedMultiSelect.sort((s1: MockDataSelectable, s2: MockDataSelectable) => { + clonedMultiSelect.sort((s1: SelectableItem, s2: SelectableItem) => { if (this.customSortBy_demo6) { const v1 = s1[this.customSortBy_demo6]; const v2 = s2[this.customSortBy_demo6]; @@ -304,7 +304,7 @@ export default class PageComponentsTableController extends Controller { @action customOnSort_demo6(sortBy: string, sortOrder: string) { - this.customSortBy_demo6 = sortBy as keyof MockDataSelectable; + this.customSortBy_demo6 = sortBy as keyof SelectableItem; this.customSortOrder_demo6 = sortOrder; } @@ -316,14 +316,13 @@ export default class PageComponentsTableController extends Controller { if (selectionKey) { if (selectionKey === 'all' && selectionCheckboxElement) { this.multiSelectSelectableData__demo5.forEach( - (modelRow: MockDataSelectable) => { + (modelRow: SelectableItem) => { modelRow.isSelected = selectionCheckboxElement.checked; }, ); } else { const recordToUpdate = this.multiSelectSelectableData__demo5.find( - (modelRow: MockDataSelectable) => - modelRow.id === Number(selectionKey), + (modelRow: SelectableItem) => modelRow.id === Number(selectionKey), ); if (recordToUpdate) { @@ -628,7 +627,7 @@ export default class PageComponentsTableController extends Controller { @action multiSelectAnimateSelectedUsers_demo4() { this.multiSelectUserData__demo4.forEach((user) => { - user.isAnimated = user.isSelected; + user.isAnimated = user.isSelected ? user.isSelected : false; }); // eslint-disable-next-line ember/no-runloop later(() => { diff --git a/showcase/app/routes/page-components/table.ts b/showcase/app/routes/page-components/table.ts index 1cc7fd00f8d..8e8421e9a6a 100644 --- a/showcase/app/routes/page-components/table.ts +++ b/showcase/app/routes/page-components/table.ts @@ -7,87 +7,12 @@ import Route from '@ember/routing/route'; import { DENSITIES } from '@hashicorp/design-system-components/components/hds/table/index'; import type { ModelFrom } from 'showcase/utils/ModelFromRoute'; -import type { - HdsBadgeTypes, - HdsBadgeColors, -} from '@hashicorp/design-system-components/components/hds/badge/types'; -import type { HdsIconSignature } from '@hashicorp/design-system-components/components/hds/icon/index'; -export interface MockDataMusic { - id: number; - type: string; - attributes: { - artist: string; - album: string; - year: string; - quote: string; - 'vinyl-cost': string; - icon: HdsIconSignature['Args']['name']; - 'badge-type': HdsBadgeTypes; - 'badge-color': { - name: HdsBadgeColors; - key: number; - }; - color: HdsBadgeColors; - }; -} -export interface MockDataMusicItem { - id: number; - type: string; - attributes: { - artist: string; - album: string; - year: string; - quote: string; - 'vinyl-cost': string; - icon: string; - 'badge-type': HdsBadgeTypes; - 'badge-color': { - name: HdsBadgeColors; - key: number; - }; - color: HdsBadgeColors; - }; -} - -export interface MockDataCluster { - id: number; - 'peer-name': string; - 'cluster-partition': string; - status: string; - services: { - imported: number; - exported: number; - }; -} - -export interface MockDataManyColumns { - first_name: string; - last_name: string; - age: string; - email: string; - phone: string; - education: string; - occupation: string; - bio: string; -} - -export interface MockDataSelectable { - id: number; - lorem: string; - ipsum: string; - dolor: string; - isSelected: boolean; -} - -export interface MockDataUser { - id: number; - name: string; - email: string; - role: 'Owner' | 'Admin' | 'Contributor'; - isSelected?: boolean; - isAnimated?: boolean; -} +import clusters from 'showcase/mocks/cluster-data'; +import folkMusic from 'showcase/mocks/folk-music-data'; +import selectableItems from 'showcase/mocks/selectable-item-data'; +import users from 'showcase/mocks/user-data'; +import userWithMoreColumns from 'showcase/mocks/user-with-more-columns-data'; export type PageComponentsTableModel = ModelFrom; @@ -96,40 +21,23 @@ export const clone = (arr: T[]): T[] => { return arr.map((item) => ({ ...item })); }; -export default class PageComponentsTableRoute extends Route { - async model() { - const STATES = ['default', 'hover', 'active', 'focus']; - - const responseMusic = await fetch('/api/folk.json'); - const responseClusters = await fetch('/api/mock-clusters-with-status.json'); - const responseManyColumns = await fetch('/api/mock-many-columns.json'); - const responseSelectableData = await fetch( - '/api/mock-selectable-data.json', - ); - const responseUserData = await fetch('/api/mock-users.json'); - - const { data: music } = (await responseMusic.json()) as Record< - 'data', - MockDataMusic[] - >; - const clusters = (await responseClusters.json()) as MockDataCluster[]; - const manycolumns = - (await responseManyColumns.json()) as MockDataManyColumns[]; - const selectableData = - (await responseSelectableData.json()) as MockDataSelectable[]; - const userData = (await responseUserData.json()) as MockDataUser[]; +const STATES = ['default', 'hover', 'active', 'focus']; +export default class PageComponentsTableRoute extends Route { + model() { return { - music: music.map((record) => ({ id: record.id, ...record.attributes })), - selectableData, - selectableDataDemo1: clone(selectableData), - selectableDataDemo2: clone(selectableData), - userDataDemo3: clone(userData.slice(0, 16)), - userDataDemo4: clone(userData.slice(0, 4)), - selectableDataDemo5: clone(selectableData), - selectableDataDemo6: clone(selectableData), + music: folkMusic, + selectableData: selectableItems, + selectableDataDemo1: clone(selectableItems), + selectableDataDemo2: clone(selectableItems), + userDataDemo3: clone(users.slice(0, 16)), + userDataDemo4: clone( + users.slice(0, 4).map((user) => ({ ...user, isAnimated: false })), + ), + selectableDataDemo5: clone(selectableItems), + selectableDataDemo6: clone(selectableItems), clusters, - manycolumns, + userMoreColumnsData: userWithMoreColumns, DENSITIES, STATES, }; diff --git a/showcase/app/templates/page-components/table.hbs b/showcase/app/templates/page-components/table.hbs index 0f0139e3d30..c390408cea4 100644 --- a/showcase/app/templates/page-components/table.hbs +++ b/showcase/app/templates/page-components/table.hbs @@ -14,6 +14,7 @@ Table with model Sortable table (all columns sortable) Sortable table (only some columns sortable) Sortable table, one column right-aligned Sortable table, some columns sortable, artist column pre-sorted. Table with model (sortable and non-sortable columns) Sortable table, last column not sortable, visually hidden and with custom width. Table where last column has right-aligned text Table with various cell content + {{! @glint-expect-error - will be fixed by https://hashicorp.atlassian.net/browse/HDS-5090}} @@ -1146,6 +1155,7 @@ Table with multi-line content Date: Thu, 7 Aug 2025 14:31:17 -0400 Subject: [PATCH 12/12] Fix: Adv table - Broken delete rows example, unused data --- .../app/controllers/page-components/advanced-table.ts | 8 +++++--- showcase/app/routes/page-components/advanced-table.ts | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/showcase/app/controllers/page-components/advanced-table.ts b/showcase/app/controllers/page-components/advanced-table.ts index e7afd0e1b53..c05e90d76de 100644 --- a/showcase/app/controllers/page-components/advanced-table.ts +++ b/showcase/app/controllers/page-components/advanced-table.ts @@ -10,6 +10,8 @@ import { deepTracked } from 'ember-deep-tracked'; import { later } from '@ember/runloop'; import type { PageComponentsAdvancedTableModel } from 'showcase/routes/page-components/advanced-table'; +import type { SelectableItem } from 'showcase/mocks/selectable-item-data'; +import type { User } from 'showcase/mocks/user-data'; import type { HdsAdvancedTableOnSelectionChangeSignature, @@ -25,12 +27,12 @@ const customSortingCriteriaArray = [ 'pending', ]; -const updateModelWithSelectAllState = ( - modelData: T[], +const updateModelWithSelectAllState = ( + modelData: SelectableItem[] | User[], selectAllState: boolean, ) => { modelData.forEach((modelRow) => { - if (modelRow instanceof Object && 'isSelected' in modelRow) { + if (modelRow instanceof Object) { modelRow.isSelected = selectAllState; } }); diff --git a/showcase/app/routes/page-components/advanced-table.ts b/showcase/app/routes/page-components/advanced-table.ts index 7fcc329d674..3534c855694 100644 --- a/showcase/app/routes/page-components/advanced-table.ts +++ b/showcase/app/routes/page-components/advanced-table.ts @@ -15,7 +15,6 @@ import policies from 'showcase/mocks/policy-data'; import selectableItems from 'showcase/mocks/selectable-item-data'; import spanningCells from 'showcase/mocks/spanning-cell-data'; import users from 'showcase/mocks/user-data'; -import userWithMoreColumns from 'showcase/mocks/user-with-more-columns-data'; export type PageComponentsAdvancedTableModel = ModelFrom; @@ -42,7 +41,6 @@ export default class PageComponentsAdvancedTableRoute extends Route { userDataDemo4: clone( users.slice(0, 4).map((user) => ({ ...user, isAnimated: false })), ), - manyColumns: userWithMoreColumns, nestedData: policies, nestedDataCustom: policies.map((policy) => { const { children, ...rest } = policy;