Skip to content

Commit a6b9000

Browse files
committed
Feat: Search filter
1 parent 6641e0a commit a6b9000

File tree

5 files changed

+91
-39
lines changed

5 files changed

+91
-39
lines changed

packages/components/src/components/hds/filter-bar/filter-group.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type {
2121
HdsFilterBarFilters,
2222
HdsFilterBarFilterType,
2323
HdsFilterBarData,
24-
HdsFilterBarSelectionFilterData,
24+
HdsFilterBarGenericFilterData,
2525
HdsFilterBarRangeFilterData,
2626
HdsFilterBarRangeFilterSelector,
2727
} from './types.ts';
@@ -106,9 +106,8 @@ export default class HdsFilterBarFilterGroup extends Component<HdsFilterBarFilte
106106
onSelectionChange(event: Event): void {
107107
const addFilter = (value: unknown): void => {
108108
const newFilter = {
109-
text: value as string,
110109
value: value,
111-
} as HdsFilterBarSelectionFilterData;
110+
} as HdsFilterBarGenericFilterData;
112111
if (this.type === 'single-select') {
113112
this.internalFilters = newFilter;
114113
} else {
@@ -125,7 +124,7 @@ export default class HdsFilterBarFilterGroup extends Component<HdsFilterBarFilte
125124
this.internalFilters = undefined;
126125
} else {
127126
if (Array.isArray(this.internalFilters)) {
128-
const newFilter = [] as HdsFilterBarSelectionFilterData[];
127+
const newFilter = [] as HdsFilterBarGenericFilterData[];
129128
this.internalFilters.forEach((filter) => {
130129
if (filter.value != value) {
131130
newFilter.push(filter);

packages/components/src/components/hds/filter-bar/index.hbs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
{{#if @hasSearch}}
1111
<Hds::Form::TextInput::Base
1212
@type="search"
13-
@width="185px"
13+
@width="320px"
14+
@value={{this.searchValue}}
1415
class="hds-filter-bar__search"
1516
placeholder={{hds-t "hds.components.filter-bar.search.placeholder" default="Search"}}
1617
aria-label={{hds-t "hds.components.filter-bar.search.aria-label" default="Search filters"}}
@@ -50,10 +51,15 @@
5051
@text="{{this._filterKeyText key filter}} {{this._rangeFilterText filter.data}}"
5152
@onDismiss={{fn this.onFilterDismiss key}}
5253
/>
54+
{{else if (eq filter.type "search")}}
55+
<Hds::Tag
56+
@text="{{this._filterKeyText key filter}}: {{this._filterText filter.data}}"
57+
@onDismiss={{fn this.onFilterDismiss key}}
58+
/>
5359
{{else if (eq filter.type "multi-select")}}
5460
{{#each (this._filterArrayData filter.data) as |item|}}
5561
<Hds::Tag
56-
@text="{{this._filterKeyText key filter}}: {{item.text}}"
62+
@text="{{this._filterKeyText key filter}}: {{item.value}}"
5763
@onDismiss={{fn this.onFilterDismiss key item.value}}
5864
/>
5965
{{/each}}

packages/components/src/components/hds/filter-bar/index.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
HdsFilterBarFilters,
1414
HdsFilterBarFilter,
1515
HdsFilterBarData,
16-
HdsFilterBarSelectionFilterData,
16+
HdsFilterBarGenericFilterData,
1717
} from './types.ts';
1818
import HdsDropdown from '../dropdown/index.ts';
1919
import HdsFilterBarFiltersDropdown from './filters-dropdown.ts';
@@ -26,7 +26,6 @@ export interface HdsFilterBarSignature {
2626
filters: HdsFilterBarFilters;
2727
hasSearch?: boolean;
2828
onFilter?: (filters: HdsFilterBarFilters) => void;
29-
onSearch?: (event: Event) => void;
3029
};
3130
Blocks: {
3231
default?: [
@@ -45,6 +44,14 @@ export interface HdsFilterBarSignature {
4544
export default class HdsFilterBar extends Component<HdsFilterBarSignature> {
4645
@tracked _isExpanded: boolean = true;
4746

47+
get searchValue(): string {
48+
const { filters } = this.args;
49+
if (filters['search']) {
50+
return this._filterText(filters['search'].data);
51+
}
52+
return '';
53+
}
54+
4855
@action
4956
onFilter(filters: HdsFilterBarFilters): void {
5057
const { onFilter } = this.args;
@@ -63,10 +70,29 @@ export default class HdsFilterBar extends Component<HdsFilterBarSignature> {
6370

6471
@action
6572
onSearch(event: Event): void {
66-
const { onSearch } = this.args;
67-
if (onSearch && typeof onSearch === 'function') {
68-
onSearch(event);
73+
const { filters } = this.args;
74+
const input = event.target as HTMLInputElement;
75+
const value = input?.value;
76+
77+
const newFilters = {} as HdsFilterBarFilters;
78+
79+
Object.keys(filters).forEach((k) => {
80+
newFilters[k] = JSON.parse(
81+
JSON.stringify(filters[k])
82+
) as HdsFilterBarFilter;
83+
});
84+
85+
if (value.length > 0) {
86+
newFilters['search'] = {
87+
type: 'search',
88+
text: 'Search',
89+
data: { value },
90+
};
91+
} else {
92+
delete newFilters['search'];
6993
}
94+
95+
this.onFilter({ ...newFilters });
7096
}
7197

7298
@action
@@ -90,9 +116,7 @@ export default class HdsFilterBar extends Component<HdsFilterBarSignature> {
90116
) as HdsFilterBarFilter;
91117
});
92118

93-
if (keyFilter.type === 'single-select' || keyFilter.type === 'range') {
94-
delete newFilters[key];
95-
} else if (isArray(keyFilter.data)) {
119+
if (keyFilter.type === 'multi-select' && isArray(keyFilter.data)) {
96120
const newKeyfilter = keyFilter.data?.filter(
97121
(item) => item.value !== filterValue
98122
);
@@ -105,6 +129,8 @@ export default class HdsFilterBar extends Component<HdsFilterBarSignature> {
105129
data: newKeyfilter,
106130
};
107131
}
132+
} else {
133+
delete newFilters[key];
108134
}
109135

110136
this.onFilter({ ...newFilters });
@@ -113,21 +139,20 @@ export default class HdsFilterBar extends Component<HdsFilterBarSignature> {
113139

114140
private _filterData = (
115141
data: HdsFilterBarData
116-
): HdsFilterBarSelectionFilterData => {
117-
if ('text' in data && 'value' in data) {
118-
return { text: data.text, value: data.value };
142+
): HdsFilterBarGenericFilterData => {
143+
if ('value' in data) {
144+
return { value: data.value };
119145
}
120-
return { text: '', value: '' };
146+
return { value: '' };
121147
};
122148

123149
private _filterText = (data: HdsFilterBarData): string => {
124150
const result = this._filterData(data);
125-
return result?.text ?? '';
151+
const resultText = result?.value as string;
152+
return resultText ?? '';
126153
};
127154

128-
private _filterArrayData = (
129-
data: HdsFilterBarData
130-
): { text: string; value: unknown }[] => {
155+
private _filterArrayData = (data: HdsFilterBarData): { value: unknown }[] => {
131156
if (isArray(data)) {
132157
return data.map((item) => this._filterData(item));
133158
}

packages/components/src/components/hds/filter-bar/types.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export enum HdsFilterBarFilterTypeValues {
88
singleSelect = 'single-select',
99
range = 'range',
1010
generic = 'generic',
11+
search = 'search',
1112
}
1213

1314
export type HdsFilterBarFilterType = `${HdsFilterBarFilterTypeValues}`;
1415

15-
export interface HdsFilterBarSelectionFilterData {
16-
text: string;
16+
export interface HdsFilterBarGenericFilterData {
1717
value: unknown;
1818
}
1919

@@ -23,20 +23,20 @@ export interface HdsFilterBarRangeFilterData {
2323
}
2424

2525
export type HdsFilterBarData =
26-
| HdsFilterBarSelectionFilterData[]
27-
| HdsFilterBarSelectionFilterData
26+
| HdsFilterBarGenericFilterData[]
27+
| HdsFilterBarGenericFilterData
2828
| HdsFilterBarRangeFilterData;
2929

3030
export interface HdsFilterBarSingleSelectFilter {
3131
type: 'single-select';
3232
text?: string;
33-
data: HdsFilterBarSelectionFilterData;
33+
data: HdsFilterBarGenericFilterData;
3434
}
3535

3636
export interface HdsFilterBarMultiSelectFilter {
3737
type: 'multi-select';
3838
text?: string;
39-
data: HdsFilterBarSelectionFilterData[];
39+
data: HdsFilterBarGenericFilterData[];
4040
}
4141

4242
export interface HdsFilterBarRangeFilter {
@@ -45,10 +45,17 @@ export interface HdsFilterBarRangeFilter {
4545
data: HdsFilterBarRangeFilterData;
4646
}
4747

48+
export interface HdsFilterBarSearchFilter {
49+
type: 'search';
50+
text?: string;
51+
data: HdsFilterBarGenericFilterData;
52+
}
53+
4854
export type HdsFilterBarFilter =
4955
| HdsFilterBarSingleSelectFilter
5056
| HdsFilterBarMultiSelectFilter
51-
| HdsFilterBarRangeFilter;
57+
| HdsFilterBarRangeFilter
58+
| HdsFilterBarSearchFilter;
5259

5360
export interface HdsFilterBarFilters {
5461
[name: string]: HdsFilterBarFilter;

showcase/app/components/mock/app/main/generic-advanced-table.gts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
type HdsFilterBarRangeFilter,
2727
type HdsFilterBarSingleSelectFilter,
2828
type HdsFilterBarMultiSelectFilter,
29+
type HdsFilterBarSearchFilter,
2930
type HdsFilterBarFilter,
3031
} from '@hashicorp/design-system-components/components';
3132

@@ -563,17 +564,10 @@ export default class MockAppMainGenericAdvancedTable extends Component<MockAppMa
563564
}
564565

565566
onFilter = (filters: HdsFilterBarSignature['Args']['filters']) => {
567+
console.log('onFilter called with filters: ', filters);
566568
this.filters = filters;
567569
};
568570

569-
onSearch = (event: Event) => {
570-
const target = event.target as HTMLInputElement;
571-
const value = target.value;
572-
if (value.length > 0) {
573-
window.alert(`✅ Search executed with value: ${value}`);
574-
}
575-
};
576-
577571
get demoModelFilteredData() {
578572
const filterItem = (item: Record<string, unknown>): boolean => {
579573
if (Object.keys(this.filters).length === 0) return true;
@@ -589,6 +583,10 @@ export default class MockAppMainGenericAdvancedTable extends Component<MockAppMa
589583
if (!this.isSingleSelectFilterMatch(item[key], filter)) {
590584
match = false;
591585
}
586+
} else if (filter.type === 'search') {
587+
if (!this.isSearchFilterMatch(item, filter)) {
588+
match = false;
589+
}
592590
} else {
593591
if (!this.isMultiSelectFilterMatch(item[key], filter)) {
594592
match = false;
@@ -650,6 +648,25 @@ export default class MockAppMainGenericAdvancedTable extends Component<MockAppMa
650648
return filterValues.includes(itemValue);
651649
}
652650

651+
isSearchFilterMatch(
652+
item: Record<string, unknown>,
653+
filter: HdsFilterBarSearchFilter,
654+
): boolean {
655+
let match = false;
656+
Object.keys(item).forEach((key) => {
657+
const itemValue = item[key];
658+
const filterValue = filter.data.value;
659+
if (
660+
typeof itemValue === 'string' &&
661+
typeof filterValue === 'string' &&
662+
itemValue.toLowerCase().includes(filterValue.toLowerCase())
663+
) {
664+
match = true;
665+
}
666+
});
667+
return match;
668+
}
669+
653670
clearFilters = () => {
654671
this.filters = {};
655672
};
@@ -676,7 +693,6 @@ export default class MockAppMainGenericAdvancedTable extends Component<MockAppMa
676693
@hasSearch={{true}}
677694
@filters={{this.filters}}
678695
@onFilter={{this.onFilter}}
679-
@onSearch={{this.onSearch}}
680696
{{style marginBottom="24px"}}
681697
as |F|
682698
>
@@ -755,7 +771,6 @@ export default class MockAppMainGenericAdvancedTable extends Component<MockAppMa
755771
@hasSearch={{true}}
756772
@filters={{this.filters}}
757773
@onFilter={{this.onFilter}}
758-
@onSearch={{this.onSearch}}
759774
as |F|
760775
>
761776
<F.ActionsDropdown as |D|>

0 commit comments

Comments
 (0)