Skip to content

Commit 2e2d4d3

Browse files
committed
SPIKE Adv table in-column filtering
1 parent 8c4956b commit 2e2d4d3

File tree

12 files changed

+538
-120
lines changed

12 files changed

+538
-120
lines changed

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"./components/hds/advanced-table/th-button-sort.js": "./dist/_app_/components/hds/advanced-table/th-button-sort.js",
155155
"./components/hds/advanced-table/th-button-tooltip.js": "./dist/_app_/components/hds/advanced-table/th-button-tooltip.js",
156156
"./components/hds/advanced-table/th-context-menu.js": "./dist/_app_/components/hds/advanced-table/th-context-menu.js",
157+
"./components/hds/advanced-table/th-filter-menu.js": "./dist/_app_/components/hds/advanced-table/th-filter-menu.js",
157158
"./components/hds/advanced-table/th-resize-handle.js": "./dist/_app_/components/hds/advanced-table/th-resize-handle.js",
158159
"./components/hds/advanced-table/th-selectable.js": "./dist/_app_/components/hds/advanced-table/th-selectable.js",
159160
"./components/hds/advanced-table/th-sort.js": "./dist/_app_/components/hds/advanced-table/th-sort.js",

packages/components/src/components/hds/advanced-table/index.hbs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
Copyright (c) HashiCorp, Inc.
33
SPDX-License-Identifier: MPL-2.0
44
}}
5+
{{#if this.hasActiveFilters}}
6+
<Hds::Button
7+
@text="Clear all filters"
8+
@color="tertiary"
9+
@icon="x"
10+
@size="small"
11+
class="hds-advanced-table__clear-filters-button"
12+
{{on "click" this.clearFilters}}
13+
/>
14+
{{/if}}
515
<div
616
class="hds-advanced-table__container
717
{{(if this.isStickyHeaderPinned 'hds-advanced-table__container--header-is-pinned')}}"
@@ -53,8 +63,11 @@
5363
@isStickyColumn={{this._isStickyColumn column}}
5464
@isStickyColumnPinned={{this.isStickyColumnPinned}}
5565
@tableHeight={{this._tableHeight}}
66+
@filters={{this.filters}}
67+
@isLiveFilter={{@isLiveFilter}}
5668
@onColumnResize={{@onColumnResize}}
5769
@onPinFirstColumn={{this._onPinFirstColumn}}
70+
@onFilter={{this.onFilter}}
5871
{{this._registerThElement column}}
5972
>
6073
{{column.label}}

packages/components/src/components/hds/advanced-table/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import type {
2828
HdsAdvancedTableVerticalAlignment,
2929
HdsAdvancedTableModel,
3030
HdsAdvancedTableExpandState,
31+
HdsAdvancedTableFilter,
32+
HdsAdvancedTableFilters,
3133
} from './types.ts';
3234
import type HdsAdvancedTableColumnType from './models/column.ts';
3335
import type { HdsFormCheckboxBaseSignature } from '../form/checkbox/base.ts';
@@ -145,11 +147,14 @@ export interface HdsAdvancedTableSignature {
145147
hasStickyFirstColumn?: boolean;
146148
childrenKey?: string;
147149
maxHeight?: string;
150+
filters?: HdsAdvancedTableFilters;
151+
isLiveFilter?: boolean;
148152
onColumnResize?: (columnKey: string, newWidth?: string) => void;
149153
onSelectionChange?: (
150154
selection: HdsAdvancedTableOnSelectionChangeSignature
151155
) => void;
152156
onSort?: (sortBy: string, sortOrder: HdsAdvancedTableThSortOrder) => void;
157+
onFilter?: (filters: HdsAdvancedTableFilters) => void;
153158
};
154159
Blocks: {
155160
body?: [
@@ -214,6 +219,8 @@ export default class HdsAdvancedTable extends Component<HdsAdvancedTableSignatur
214219
@tracked showScrollIndicatorTop = false;
215220
@tracked showScrollIndicatorBottom = false;
216221
@tracked stickyColumnOffset = '0px';
222+
@tracked filters: HdsAdvancedTableFilters = this.args.filters ?? {};
223+
@tracked hasActiveFilters: boolean = Object.keys(this.filters).length > 0;
217224

218225
constructor(owner: Owner, args: HdsAdvancedTableSignature['Args']) {
219226
super(owner, args);
@@ -621,6 +628,30 @@ export default class HdsAdvancedTable extends Component<HdsAdvancedTableSignatur
621628
}
622629
}
623630

631+
@action
632+
onFilter(
633+
key: string,
634+
keyFilter?: HdsAdvancedTableFilter[] | HdsAdvancedTableFilter
635+
): void {
636+
this._updateFilter(key, keyFilter);
637+
638+
const { onFilter } = this.args;
639+
if (onFilter && typeof onFilter === 'function') {
640+
onFilter(this.filters);
641+
}
642+
}
643+
644+
@action
645+
clearFilters(): void {
646+
this.filters = {};
647+
this.hasActiveFilters = false;
648+
649+
const { onFilter } = this.args;
650+
if (onFilter && typeof onFilter === 'function') {
651+
onFilter(this.filters);
652+
}
653+
}
654+
624655
private _updateScrollIndicators(element: HTMLElement): void {
625656
// 6px as a buffer so the shadow doesn't appear over the border radius on the edge of the table
626657
const SCROLL_BUFFER = 6;
@@ -687,4 +718,21 @@ export default class HdsAdvancedTable extends Component<HdsAdvancedTableSignatur
687718
}
688719
return undefined;
689720
};
721+
722+
private _updateFilter(
723+
key: string,
724+
keyFilter?: HdsAdvancedTableFilter[] | HdsAdvancedTableFilter
725+
): void {
726+
const newFilters = { ...this.filters };
727+
if (
728+
!keyFilter ||
729+
(keyFilter && Array.isArray(keyFilter) && keyFilter.length === 0)
730+
) {
731+
delete newFilters[key];
732+
} else {
733+
newFilters[key] = keyFilter;
734+
}
735+
this.filters = newFilters;
736+
this.hasActiveFilters = Object.keys(this.filters).length > 0;
737+
}
690738
}

packages/components/src/components/hds/advanced-table/models/column.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { guidFor } from '@ember/object/internals';
1010
import type HdsAdvancedTableModel from './table.ts';
1111
import type {
1212
HdsAdvancedTableHorizontalAlignment,
13+
HdsAdvancedTableFilterOption,
1314
HdsAdvancedTableColumn as HdsAdvancedTableColumnType,
15+
HdsAdvancedTableFilterType,
1416
} from '../types';
1517

1618
export const DEFAULT_WIDTH = '1fr'; // default to '1fr' to allow flexible width
@@ -38,6 +40,8 @@ export default class HdsAdvancedTableColumn {
3840
@tracked key: string;
3941
@tracked tooltip?: string = undefined;
4042
@tracked thElement?: HTMLDivElement = undefined;
43+
@tracked filterOptions?: HdsAdvancedTableFilterOption[] = undefined;
44+
@tracked filterType?: HdsAdvancedTableFilterType = undefined;
4145

4246
// width properties
4347
@tracked transientWidth?: `${number}px` = undefined; // used for transient width changes
@@ -146,6 +150,8 @@ export default class HdsAdvancedTableColumn {
146150
this.tooltip = column.tooltip;
147151
this._setWidthValues(column);
148152
this.sortingFunction = column.sortingFunction;
153+
this.filterOptions = column.filterOptions;
154+
this.filterType = column.filterType;
149155
}
150156

151157
// main collection function
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<Hds::Dropdown
6+
class="hds-advanced-table__th-filter-menu {{if this.hasActiveFilters 'hds-advanced-table__th-filter-menu--active'}}"
7+
@enableCollisionDetection={{true}}
8+
@listPosition="bottom-right"
9+
@height="210px"
10+
{{this._updateInternalFilters}}
11+
...attributes
12+
as |D|
13+
>
14+
<D.ToggleIcon @icon="filter" @text="Filter for {{@column.label}}" @hasChevron={{false}} @size="small" />
15+
{{#each @column.filterOptions as |option|}}
16+
{{#if (eq @column.filterType "radio")}}
17+
<D.Radio
18+
@value={{option.value}}
19+
checked={{(this._isChecked option.value)}}
20+
data-test-filter-option-key={{option.value}}
21+
{{on "change" this.onFilter}}
22+
>
23+
{{option.label}}
24+
</D.Radio>
25+
{{else}}
26+
<D.Checkbox
27+
@value={{option.value}}
28+
checked={{(this._isChecked option.value)}}
29+
data-test-filter-option-key={{option.value}}
30+
{{on "change" this.onFilter}}
31+
>
32+
{{option.label}}
33+
</D.Checkbox>
34+
{{/if}}
35+
{{/each}}
36+
{{#unless @isLiveFilter}}
37+
<D.Footer @hasDivider={{true}}>
38+
<Hds::ButtonSet>
39+
<Hds::Button @text="Apply filters" @isFullWidth={{true}} @size="small" {{on "click" this.onApply}} />
40+
<Hds::Button @text="Clear" @color="secondary" @size="small" {{on "click" this.onClear}} />
41+
</Hds::ButtonSet>
42+
</D.Footer>
43+
{{/unless}}
44+
</Hds::Dropdown>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import { action } from '@ember/object';
8+
import { tracked } from '@glimmer/tracking';
9+
import { modifier } from 'ember-modifier';
10+
11+
import type HdsAdvancedTableColumn from './models/column.ts';
12+
import type { HdsDropdownSignature } from '../dropdown/index.ts';
13+
import type {
14+
HdsAdvancedTableFilter,
15+
HdsAdvancedTableFilters,
16+
} from './types.ts';
17+
18+
export interface HdsAdvancedTableThFilterMenuSignature {
19+
Args: {
20+
column: HdsAdvancedTableColumn;
21+
filters?: HdsAdvancedTableFilters;
22+
isLiveFilter?: boolean;
23+
onFilter?: (
24+
key: string,
25+
keyFilter?: HdsAdvancedTableFilter | HdsAdvancedTableFilter[]
26+
) => void;
27+
};
28+
Element: HdsDropdownSignature['Element'];
29+
}
30+
31+
export default class HdsAdvancedTableThFilterMenu extends Component<HdsAdvancedTableThFilterMenuSignature> {
32+
@tracked internalFilters:
33+
| HdsAdvancedTableFilter[]
34+
| HdsAdvancedTableFilter
35+
| undefined = [];
36+
@tracked hasActiveFilters: boolean = this.keyFilter !== undefined;
37+
38+
private _updateInternalFilters = modifier(() => {
39+
this.internalFilters = this.keyFilter;
40+
});
41+
42+
get keyFilter():
43+
| HdsAdvancedTableFilter[]
44+
| HdsAdvancedTableFilter
45+
| undefined {
46+
const { filters, column } = this.args;
47+
48+
if (!filters || !column) {
49+
return undefined;
50+
}
51+
return filters[column.key];
52+
}
53+
54+
@action
55+
onFilter(event: Event): void {
56+
const addFilter = (value: unknown): HdsAdvancedTableFilter[] => {
57+
const newFilter = {
58+
text: value as string,
59+
value: value,
60+
};
61+
if (
62+
Array.isArray(this.internalFilters) &&
63+
input.classList.contains('hds-form-checkbox')
64+
) {
65+
this.internalFilters.push(newFilter);
66+
return this.internalFilters;
67+
} else {
68+
return [newFilter];
69+
}
70+
};
71+
72+
const removeFilter = (value: string): HdsAdvancedTableFilter[] => {
73+
const newFilter = [] as HdsAdvancedTableFilter[];
74+
if (Array.isArray(this.internalFilters)) {
75+
this.internalFilters.forEach((filter) => {
76+
if (filter.value != value) {
77+
newFilter.push(filter);
78+
}
79+
});
80+
}
81+
return newFilter;
82+
};
83+
84+
const input = event.target as HTMLInputElement;
85+
86+
let newFilter = [] as HdsAdvancedTableFilter[];
87+
88+
if (input.checked) {
89+
newFilter = addFilter(input.value);
90+
} else {
91+
newFilter = removeFilter(input.value);
92+
}
93+
94+
this.internalFilters = newFilter;
95+
96+
if (this.args.isLiveFilter) {
97+
const { onFilter, column } = this.args;
98+
if (onFilter && typeof onFilter === 'function') {
99+
if (newFilter.length === 0) {
100+
onFilter(column?.key, undefined);
101+
} else {
102+
onFilter(column?.key, newFilter);
103+
}
104+
this.hasActiveFilters = newFilter != undefined && newFilter.length > 0;
105+
}
106+
}
107+
}
108+
109+
@action
110+
onApply(): void {
111+
const { onFilter, column } = this.args;
112+
if (onFilter && typeof onFilter === 'function') {
113+
onFilter(column?.key, this.internalFilters);
114+
}
115+
}
116+
117+
@action
118+
onClear(): void {
119+
this.internalFilters = [];
120+
121+
const { onFilter, column } = this.args;
122+
if (onFilter && typeof onFilter === 'function') {
123+
onFilter(column?.key, this.internalFilters);
124+
}
125+
}
126+
127+
private _isChecked = (value: string): boolean => {
128+
if (Array.isArray(this.internalFilters)) {
129+
return this.internalFilters.some((filter) => filter.value === value);
130+
} else if (this.internalFilters && value) {
131+
return this.internalFilters.value === value;
132+
}
133+
return false;
134+
};
135+
}

packages/components/src/components/hds/advanced-table/th-sort.hbs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
{{#if @tooltip}}
3535
<Hds::AdvancedTable::ThButtonTooltip @tooltip={{@tooltip}} @labelId={{this._labelId}} />
3636
{{/if}}
37+
{{#if (gt this.numFilters 0)}}
38+
<Hds::BadgeCount @text={{this.numFilters}} @type="outlined" @size="small" />
39+
{{/if}}
3740
</div>
3841

3942
<Hds::Layout::Flex class="hds-advanced-table__th-actions" @align="center" @gap="8">
@@ -44,6 +47,14 @@
4447
/>
4548

4649
{{#if @column}}
50+
{{#if @column.filterOptions}}
51+
<Hds::AdvancedTable::ThFilterMenu
52+
@column={{@column}}
53+
@filters={{@filters}}
54+
@isLiveFilter={{@isLiveFilter}}
55+
@onFilter={{@onFilter}}
56+
/>
57+
{{/if}}
4758
{{#if this.showContextMenu}}
4859
<Hds::AdvancedTable::ThContextMenu
4960
@column={{@column}}

0 commit comments

Comments
 (0)