Skip to content

Commit e020438

Browse files
committed
Feat: Date, datetime, time filters
1 parent a85b97c commit e020438

File tree

18 files changed

+631
-75
lines changed

18 files changed

+631
-75
lines changed

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@
236236
"./components/hds/dropdown/toggle/chevron.js": "./dist/_app_/components/hds/dropdown/toggle/chevron.js",
237237
"./components/hds/dropdown/toggle/icon.js": "./dist/_app_/components/hds/dropdown/toggle/icon.js",
238238
"./components/hds/filter-bar/checkbox.js": "./dist/_app_/components/hds/filter-bar/checkbox.js",
239+
"./components/hds/filter-bar/date.js": "./dist/_app_/components/hds/filter-bar/date.js",
239240
"./components/hds/filter-bar/filter-group.js": "./dist/_app_/components/hds/filter-bar/filter-group.js",
240241
"./components/hds/filter-bar/filters-dropdown.js": "./dist/_app_/components/hds/filter-bar/filters-dropdown.js",
241242
"./components/hds/filter-bar.js": "./dist/_app_/components/hds/filter-bar.js",

packages/components/src/components.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export * from './components/hds/dropdown/types.ts';
133133
// FilterBar
134134
export { default as HdsFilterBar } from './components/hds/filter-bar/index.ts';
135135
export { default as HdsFilterBarCheckbox } from './components/hds/filter-bar/checkbox.ts';
136+
export { default as HdsFilterBarDate } from './components/hds/filter-bar/date.ts';
136137
export { default as HdsFilterBarFiltersDropdown } from './components/hds/filter-bar/filters-dropdown.ts';
137138
export { default as HdsFilterBarFilterGroup } from './components/hds/filter-bar/filter-group.ts';
138139
export { default as HdsFilterBarRadio } from './components/hds/filter-bar/radio.ts';

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import Component from '@glimmer/component';
77
import { action } from '@ember/object';
88

9-
import type { HdsFilterBarData } from './types.ts';
9+
import type { HdsFilterBarFilter } from './types.ts';
1010

1111
export interface HdsFilterBarCheckboxSignature {
1212
Args: {
1313
value?: string;
14-
keyFilter: HdsFilterBarData | undefined;
14+
keyFilter: HdsFilterBarFilter | undefined;
1515
onChange?: (event: Event) => void;
1616
};
1717
Blocks: {
@@ -31,8 +31,8 @@ export default class HdsFilterBarCheckbox extends Component<HdsFilterBarCheckbox
3131

3232
get isChecked(): boolean {
3333
const { keyFilter, value } = this.args;
34-
if (Array.isArray(keyFilter)) {
35-
return keyFilter.some((filter) => filter.value === value);
34+
if (keyFilter && Array.isArray(keyFilter.data)) {
35+
return keyFilter.data.some((filter) => filter.value === value);
3636
}
3737
return false;
3838
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<div class="hds-filter-bar__filters-dropdown__filter-date">
6+
<Hds::Layout::Flex class="hds-filter-bar__filters-dropdown__fields" @direction="column" @gap="16">
7+
<Hds::Form::Select::Field
8+
@id={{this._selectorInputId}}
9+
{{on "change" this.onSelectorChange}}
10+
class="hds-filter-bar__filters-dropdown__field"
11+
as |F|
12+
>
13+
<F.Label>{{this.selectorLabelText}}</F.Label>
14+
<F.Options>
15+
<option value="">{{hds-t
16+
"hds.components.filter-bar.date.selector-input.default-value"
17+
default="Pick a selector"
18+
}}</option>
19+
{{#each this._selectorValues as |selectorValue|}}
20+
<option value={{selectorValue}} selected={{eq selectorValue this._selector}}>{{this._selectorText
21+
selectorValue
22+
}}</option>
23+
{{/each}}
24+
</F.Options>
25+
</Hds::Form::Select::Field>
26+
{{#if (eq this._selector "between")}}
27+
<Hds::Layout::Flex @gap="8" @direction={{if (eq @type "datetime") "column" "row"}}>
28+
<Hds::Form::TextInput::Base
29+
@id={{this._betweenValueStartInputId}}
30+
@type={{this.inputType}}
31+
@value={{this._betweenValueStart}}
32+
placeholder={{hds-t "hds.components.filter-bar.between-value-inputs.date.start-placeholder" default="Start"}}
33+
class="hds-filter-bar__filters-dropdown__field"
34+
{{on "change" this.onBetweenValueStartChange}}
35+
/>
36+
<Hds::Form::TextInput::Base
37+
@id={{this._betweenValueEndInputId}}
38+
@type={{this.inputType}}
39+
@value={{this._betweenValueEnd}}
40+
placeholder={{hds-t "hds.components.filter-bar.between-value-inputs.date.end-placeholder" default="End"}}
41+
class="hds-filter-bar__filters-dropdown__field"
42+
{{on "change" this.onBetweenValueEndChange}}
43+
/>
44+
</Hds::Layout::Flex>
45+
{{else}}
46+
<Hds::Form::TextInput::Base
47+
@id={{this._valueInputId}}
48+
@type={{this.inputType}}
49+
@value={{this._value}}
50+
placeholder={{hds-t "hds.components.filter-bar.date.value-input.placeholder" default="Enter a value"}}
51+
class="hds-filter-bar__filters-dropdown__field"
52+
{{on "change" this.onValueChange}}
53+
/>
54+
{{/if}}
55+
</Hds::Layout::Flex>
56+
<div class="hds-filter-bar__filters-dropdown__clear">
57+
<Hds::Button
58+
@text={{hds-t "hds.components.filter-bar.date.clear" default="Clear filter"}}
59+
@color="tertiary"
60+
@icon="rotate-ccw"
61+
@isInline={{true}}
62+
{{on "click" this.onClear}}
63+
/>
64+
</div>
65+
</div>
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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 type Owner from '@ember/owner';
10+
import { guidFor } from '@ember/object/internals';
11+
import { service } from '@ember/service';
12+
13+
import type HdsIntlService from '../../../services/hds-intl';
14+
import type { HdsFormTextInputTypes } from '../form/text-input/types.ts';
15+
16+
import type {
17+
HdsFilterBarFilter,
18+
HdsFilterBarDateFilterSelector,
19+
HdsFilterBarDateFilterValue,
20+
} from './types.ts';
21+
import { HdsFilterBarDateFilterSelectorValues } from './types.ts';
22+
23+
export const DATE_SELECTORS: HdsFilterBarDateFilterSelector[] = Object.values(
24+
HdsFilterBarDateFilterSelectorValues
25+
);
26+
27+
export const DATE_SELECTORS_TEXT: Record<
28+
HdsFilterBarDateFilterSelector,
29+
string
30+
> = {
31+
[HdsFilterBarDateFilterSelectorValues.before]: 'before',
32+
[HdsFilterBarDateFilterSelectorValues.exactly]: 'exactly',
33+
[HdsFilterBarDateFilterSelectorValues.after]: 'after',
34+
[HdsFilterBarDateFilterSelectorValues.between]: 'between',
35+
};
36+
37+
export const DATE_SELECTORS_INPUT_TEXT: Record<
38+
HdsFilterBarDateFilterSelector,
39+
string
40+
> = {
41+
[HdsFilterBarDateFilterSelectorValues.before]: 'Before',
42+
[HdsFilterBarDateFilterSelectorValues.exactly]: 'Exactly',
43+
[HdsFilterBarDateFilterSelectorValues.after]: 'After',
44+
[HdsFilterBarDateFilterSelectorValues.between]: 'Between',
45+
};
46+
47+
export interface HdsFilterBarDateSignature {
48+
Args: {
49+
keyFilter: HdsFilterBarFilter | undefined;
50+
type?: 'date' | 'time' | 'datetime';
51+
onChange?: (
52+
selector?: HdsFilterBarDateFilterSelector,
53+
value?: HdsFilterBarDateFilterValue
54+
) => void;
55+
};
56+
Blocks: {
57+
default: [];
58+
};
59+
Element: HTMLDivElement;
60+
}
61+
62+
export default class HdsFilterBarDate extends Component<HdsFilterBarDateSignature> {
63+
@service hdsIntl!: HdsIntlService;
64+
65+
@tracked private _selector: HdsFilterBarDateFilterSelector | undefined;
66+
@tracked private _value: string | undefined;
67+
@tracked private _betweenValueStart: string | undefined;
68+
@tracked private _betweenValueEnd: string | undefined;
69+
70+
private _selectorValues = DATE_SELECTORS;
71+
private _selectorInputId = 'selector-input-' + guidFor(this);
72+
private _valueInputId = 'value-input-' + guidFor(this);
73+
private _betweenValueStartInputId =
74+
'between-value-start-input-' + guidFor(this);
75+
private _betweenValueEndInputId = 'between-value-end-input-' + guidFor(this);
76+
77+
constructor(owner: Owner, args: HdsFilterBarDateSignature['Args']) {
78+
super(owner, args);
79+
80+
const { keyFilter } = this.args;
81+
if (
82+
keyFilter &&
83+
(keyFilter.type === 'date' ||
84+
keyFilter.type === 'time' ||
85+
keyFilter.type === 'datetime')
86+
) {
87+
const data = keyFilter.data;
88+
this._selector = data.selector;
89+
if (data.selector === 'between') {
90+
if (
91+
data.value &&
92+
typeof data.value === 'object' &&
93+
'start' in data.value &&
94+
'end' in data.value
95+
) {
96+
this._betweenValueStart = data.value.start;
97+
this._betweenValueEnd = data.value.end;
98+
}
99+
} else {
100+
this._value = data.value as string;
101+
}
102+
}
103+
}
104+
105+
get type(): 'date' | 'time' | 'datetime' {
106+
return this.args.type || 'date';
107+
}
108+
109+
get inputType(): HdsFormTextInputTypes {
110+
if (this.type === 'datetime') {
111+
return 'datetime-local';
112+
}
113+
return this.type;
114+
}
115+
116+
get selectorLabelText(): string {
117+
return this.hdsIntl.t(`hds.components.filter-bar.date.${this.type}.label`, {
118+
default: 'Date is',
119+
});
120+
}
121+
122+
@action
123+
onSelectorChange(event: Event): void {
124+
const select = event.target as HTMLSelectElement;
125+
this._selector = select.value as HdsFilterBarDateFilterSelector;
126+
if (this._selector === 'between') {
127+
this._value = undefined;
128+
} else {
129+
this._betweenValueStart = undefined;
130+
this._betweenValueEnd = undefined;
131+
}
132+
this._onChange();
133+
}
134+
135+
@action
136+
onValueChange(event: Event): void {
137+
const input = event.target as HTMLInputElement;
138+
this._value = input.value;
139+
this._onChange();
140+
}
141+
142+
@action
143+
onBetweenValueStartChange(event: Event): void {
144+
const input = event.target as HTMLInputElement;
145+
this._betweenValueStart = input.value;
146+
this._onChange();
147+
}
148+
149+
@action
150+
onBetweenValueEndChange(event: Event): void {
151+
const input = event.target as HTMLInputElement;
152+
this._betweenValueEnd = input.value;
153+
this._onChange();
154+
}
155+
156+
@action
157+
onClear(): void {
158+
this._resetInputValues();
159+
this._onChange();
160+
}
161+
162+
private _onChange(): void {
163+
const { onChange } = this.args;
164+
if (onChange && typeof onChange === 'function') {
165+
if (
166+
this._selector === 'between' &&
167+
this._betweenValueStart !== undefined &&
168+
this._betweenValueEnd !== undefined
169+
) {
170+
onChange(this._selector, {
171+
start: this._betweenValueStart,
172+
end: this._betweenValueEnd,
173+
});
174+
} else {
175+
onChange(this._selector, this._value);
176+
}
177+
}
178+
}
179+
180+
private _selectorText = (
181+
selector: HdsFilterBarDateFilterSelector
182+
): string => {
183+
return this.hdsIntl.t(
184+
`hds.components.filter-bar.date.selector-input.${selector}`,
185+
{
186+
default: DATE_SELECTORS_INPUT_TEXT[selector],
187+
}
188+
);
189+
};
190+
191+
private _resetInputValues = (): void => {
192+
this._selector = undefined;
193+
this._value = undefined;
194+
this._betweenValueStart = undefined;
195+
this._betweenValueEnd = undefined;
196+
};
197+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
{{/if}}
2929
{{#if (eq @type "range")}}
3030
<Hds::FilterBar::Range @keyFilter={{this.keyFilter}} @onChange={{this.onRangeChange}} />
31+
{{else if (eq @type "date")}}
32+
<Hds::FilterBar::Date @keyFilter={{this.keyFilter}} @onChange={{this.onDateChange}} @type="date" />
33+
{{else if (eq @type "datetime")}}
34+
<Hds::FilterBar::Date @keyFilter={{this.keyFilter}} @onChange={{this.onDateChange}} @type="datetime" />
35+
{{else if (eq @type "time")}}
36+
<Hds::FilterBar::Date @keyFilter={{this.keyFilter}} @onChange={{this.onDateChange}} @type="time" />
3137
{{else}}
3238
<div class="hds-filter-bar__filters-dropdown__filter-group__values-list">
3339
<div class="hds-filter-bar__filters-dropdown__clear">

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import type {
2525
HdsFilterBarRangeFilterData,
2626
HdsFilterBarRangeFilterSelector,
2727
HdsFilterBarRangeFilterValue,
28+
HdsFilterBarDateFilterData,
29+
HdsFilterBarDateFilterSelector,
30+
HdsFilterBarDateFilterValue,
2831
} from './types.ts';
2932

3033
export interface HdsFilterBarFilterGroupSignature {
@@ -66,7 +69,7 @@ export default class HdsFilterBarFilterGroup extends Component<HdsFilterBarFilte
6669

6770
if (this.keyFilter) {
6871
this.internalFilters = JSON.parse(
69-
JSON.stringify(this.keyFilter)
72+
JSON.stringify(this.keyFilter.data)
7073
) as HdsFilterBarData;
7174
}
7275
}
@@ -81,13 +84,13 @@ export default class HdsFilterBarFilterGroup extends Component<HdsFilterBarFilte
8184
return type;
8285
}
8386

84-
get keyFilter(): HdsFilterBarData | undefined {
87+
get keyFilter(): HdsFilterBarFilter | undefined {
8588
const { filters, key } = this.args;
8689

8790
if (!filters) {
8891
return undefined;
8992
}
90-
return filters[key]?.data;
93+
return filters[key];
9194
}
9295

9396
get numFilters(): number {
@@ -177,6 +180,31 @@ export default class HdsFilterBarFilterGroup extends Component<HdsFilterBarFilte
177180
}
178181
}
179182

183+
@action
184+
onDateChange(
185+
selector?: HdsFilterBarDateFilterSelector,
186+
value?: HdsFilterBarDateFilterValue
187+
): void {
188+
const addFilter = (): HdsFilterBarData => {
189+
const newFilter = {
190+
selector: selector,
191+
value: value,
192+
} as HdsFilterBarDateFilterData;
193+
return newFilter;
194+
};
195+
196+
if (selector && value) {
197+
this.internalFilters = addFilter();
198+
} else {
199+
this.internalFilters = undefined;
200+
}
201+
202+
const { onChange } = this.args;
203+
if (onChange && typeof onChange === 'function') {
204+
onChange(this.args.key, this.formattedFilters);
205+
}
206+
}
207+
180208
@action
181209
onClear(): void {
182210
this.internalFilters = undefined;

0 commit comments

Comments
 (0)