diff --git a/packages/components/package.json b/packages/components/package.json
index 4c72d0b25cb..e63acb4661e 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -236,6 +236,13 @@
"./components/hds/dropdown/toggle/button.js": "./dist/_app_/components/hds/dropdown/toggle/button.js",
"./components/hds/dropdown/toggle/chevron.js": "./dist/_app_/components/hds/dropdown/toggle/chevron.js",
"./components/hds/dropdown/toggle/icon.js": "./dist/_app_/components/hds/dropdown/toggle/icon.js",
+ "./components/hds/filter-bar/checkbox.js": "./dist/_app_/components/hds/filter-bar/checkbox.js",
+ "./components/hds/filter-bar/dropdown.js": "./dist/_app_/components/hds/filter-bar/dropdown.js",
+ "./components/hds/filter-bar/filters-checkbox.js": "./dist/_app_/components/hds/filter-bar/filters-checkbox.js",
+ "./components/hds/filter-bar/filters-dropdown.js": "./dist/_app_/components/hds/filter-bar/filters-dropdown.js",
+ "./components/hds/filter-bar.js": "./dist/_app_/components/hds/filter-bar.js",
+ "./components/hds/filter-bar/radio.js": "./dist/_app_/components/hds/filter-bar/radio.js",
+ "./components/hds/filter-bar/range.js": "./dist/_app_/components/hds/filter-bar/range.js",
"./components/hds/flyout/body.js": "./dist/_app_/components/hds/flyout/body.js",
"./components/hds/flyout/description.js": "./dist/_app_/components/hds/flyout/description.js",
"./components/hds/flyout/footer.js": "./dist/_app_/components/hds/flyout/footer.js",
diff --git a/packages/components/src/components.ts b/packages/components/src/components.ts
index 400dc061324..869ddeb795e 100644
--- a/packages/components/src/components.ts
+++ b/packages/components/src/components.ts
@@ -130,6 +130,16 @@ export * from './components/hds/dropdown/list-item/types.ts';
export * from './components/hds/dropdown/toggle/types.ts';
export * from './components/hds/dropdown/types.ts';
+// FilterBar
+export { default as HdsFilterBar } from './components/hds/filter-bar/index.ts';
+export { default as HdsFilterBarCheckbox } from './components/hds/filter-bar/checkbox.ts';
+export { default as HdsFilterBarDropdown } from './components/hds/filter-bar/dropdown.ts';
+export { default as HdsFilterBarFiltersCheckbox } from './components/hds/filter-bar/filters-checkbox.ts';
+export { default as HdsFilterBarFiltersDropdown } from './components/hds/filter-bar/filters-dropdown.ts';
+export { default as HdsFilterBarRadio } from './components/hds/filter-bar/radio.ts';
+export { default as HdsFilterBarRange } from './components/hds/filter-bar/range.ts';
+export * from './components/hds/filter-bar/types.ts';
+
// Flyout
export { default as HdsFlyout } from './components/hds/flyout/index.ts';
export * from './components/hds/flyout/types.ts';
diff --git a/packages/components/src/components/hds/advanced-table/index.hbs b/packages/components/src/components/hds/advanced-table/index.hbs
index 8f8bae33e58..3a8934c884f 100644
--- a/packages/components/src/components/hds/advanced-table/index.hbs
+++ b/packages/components/src/components/hds/advanced-table/index.hbs
@@ -3,6 +3,11 @@
SPDX-License-Identifier: MPL-2.0
}}
+{{#if (has-block "actions")}}
+
+ {{yield (hash FilterBar=(component "hds/filter-bar")) to="actions"}}
+
+{{/if}}
{{#if this.showScrollIndicatorLeft}}
@@ -196,10 +203,29 @@
/>
{{/if}}
- {{#if this.showScrollIndicatorBottom}}
-
+ {{#unless this.isEmpty}}
+ {{#if this.showScrollIndicatorBottom}}
+
+ {{/if}}
+ {{/unless}}
+
+ {{#if this.isEmpty}}
+
+
+ {{#if (has-block "emptyState")}}
+ {{yield to="emptyState"}}
+ {{else}}
+
+ No data to display
+
+ No data is available in the table to display.
+
+
+ {{/if}}
+
+
{{/if}}
\ No newline at end of file
diff --git a/packages/components/src/components/hds/advanced-table/index.ts b/packages/components/src/components/hds/advanced-table/index.ts
index a5bfae87784..61d49bf72a2 100644
--- a/packages/components/src/components/hds/advanced-table/index.ts
+++ b/packages/components/src/components/hds/advanced-table/index.ts
@@ -14,6 +14,7 @@ import HdsAdvancedTableTableModel from './models/table.ts';
import type Owner from '@ember/owner';
import type { WithBoundArgs } from '@glint/template';
+import type { ComponentLike } from '@glint/template';
import {
HdsAdvancedTableDensityValues,
HdsAdvancedTableVerticalAlignmentValues,
@@ -30,6 +31,7 @@ import type {
HdsAdvancedTableExpandState,
HdsAdvancedTableColumnReorderCallback,
} from './types.ts';
+import type { HdsFilterBarSignature } from '../filter-bar/index.ts';
import type HdsAdvancedTableColumnType from './models/column.ts';
import type { HdsFormCheckboxBaseSignature } from '../form/checkbox/base.ts';
import type HdsAdvancedTableTd from './td.ts';
@@ -149,6 +151,7 @@ export interface HdsAdvancedTableSignature {
hasStickyFirstColumn?: boolean;
childrenKey?: string;
maxHeight?: string;
+ isEmpty?: boolean;
onColumnReorder?: HdsAdvancedTableColumnReorderCallback;
onColumnResize?: (columnKey: string, newWidth?: string) => void;
onSelectionChange?: (
@@ -157,6 +160,11 @@ export interface HdsAdvancedTableSignature {
onSort?: (sortBy: string, sortOrder: HdsAdvancedTableThSortOrder) => void;
};
Blocks: {
+ actions?: [
+ {
+ FilterBar?: ComponentLike;
+ },
+ ];
body?: [
{
Td?: WithBoundArgs;
@@ -192,6 +200,7 @@ export interface HdsAdvancedTableSignature {
isOpen?: HdsAdvancedTableExpandState;
},
];
+ emptyState?: [];
};
Element: HTMLDivElement;
}
@@ -259,6 +268,14 @@ export default class HdsAdvancedTable extends Component
-
+
+ {{#if this.onDismiss}}
+
+
+
+ {{/if}}
{{yield
(hash
ToggleButton=(component
@@ -21,6 +26,15 @@
>
{{#if (or PP.isOpen @preserveContentInDom)}}
{{yield (hash Header=(component "hds/dropdown/header") close=PP.hidePopover)}}
+ {{#if @searchEnabled}}
+
+
+
+ {{/if}}
{{yield
(hash
diff --git a/packages/components/src/components/hds/dropdown/index.ts b/packages/components/src/components/hds/dropdown/index.ts
index 12bccfcd4ce..a73c5e6b3ee 100644
--- a/packages/components/src/components/hds/dropdown/index.ts
+++ b/packages/components/src/components/hds/dropdown/index.ts
@@ -6,6 +6,7 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { assert } from '@ember/debug';
+import { modifier } from 'ember-modifier';
import {
// map Dropdown's `listPosition` values to PopoverPrimitive's `placement` values
@@ -48,6 +49,10 @@ export interface HdsDropdownSignature {
enableCollisionDetection?: HdsAnchoredPositionOptions['enableCollisionDetection'];
preserveContentInDom?: boolean;
matchToggleWidth?: boolean;
+ searchEnabled?: boolean;
+ searchPlaceholder?: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ onDismiss?: (event: MouseEvent, ...args: any[]) => void;
};
Blocks: {
default: [
@@ -73,6 +78,14 @@ export interface HdsDropdownSignature {
}
export default class HdsDropdown extends Component {
+ private _element!: HTMLDivElement;
+
+ private _setUpDropdown = modifier((element: HTMLDivElement) => {
+ this._element = element;
+
+ return () => {};
+ });
+
/**
* @param listPosition
* @type {string}
@@ -116,6 +129,17 @@ export default class HdsDropdown extends Component {
};
}
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ get onDismiss(): ((event: MouseEvent, ...args: any[]) => void) | false {
+ const { onDismiss } = this.args;
+
+ if (typeof onDismiss === 'function') {
+ return onDismiss;
+ } else {
+ return false;
+ }
+ }
+
/**
* Get the class names to apply to the element
* @method classNames
@@ -129,6 +153,10 @@ export default class HdsDropdown extends Component {
classes.push('hds-dropdown--is-inline');
}
+ if (this.args.onDismiss) {
+ classes.push('hds-dropdown--has-dismiss');
+ }
+
return classes.join(' ');
}
@@ -169,4 +197,20 @@ export default class HdsDropdown extends Component {
}
}
}
+
+ onSearch = (event: Event) => {
+ const listItems = this._element.querySelectorAll('.hds-dropdown-list-item') as NodeListOf;
+ const input = event.target as HTMLInputElement;
+ listItems.forEach((item) => {
+ if (item.textContent) {
+ const text = item.textContent.toLowerCase();
+ const searchText = input.value.toLowerCase();
+ if (text.includes(searchText)) {
+ item.style.display = '';
+ } else {
+ item.style.display = 'none';
+ }
+ }
+ });
+ };
}
diff --git a/packages/components/src/components/hds/filter-bar/checkbox.hbs b/packages/components/src/components/hds/filter-bar/checkbox.hbs
new file mode 100644
index 00000000000..6d2e3ecd56f
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/checkbox.hbs
@@ -0,0 +1,5 @@
+{{#let @checkbox as |Checkbox|}}
+
+ {{yield}}
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/checkbox.ts b/packages/components/src/components/hds/filter-bar/checkbox.ts
new file mode 100644
index 00000000000..8bee0ba9386
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/checkbox.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import type { WithBoundArgs } from '@glint/template';
+
+import type { HdsFilterBarSelectionFilter } from './types.ts';
+
+import HdsDropdownListItemCheckbox from '../dropdown/list-item/checkbox.ts';
+
+import type { HdsDropdownSignature } from '../dropdown/index.ts';
+
+export interface HdsFilterBarCheckboxSignature {
+ Args: HdsDropdownSignature['Args'] & {
+ checkbox?: WithBoundArgs;
+ value?: string;
+ keyFilter: HdsFilterBarSelectionFilter[] | undefined;
+ onChange?: (event: Event) => void;
+ };
+ Blocks: {
+ default: [];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBarCheckbox extends Component {
+ @action
+ onChange(event: Event): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(event);
+ }
+ }
+
+ get isChecked(): boolean {
+ const { keyFilter, value } = this.args;
+ if (Array.isArray(keyFilter)) {
+ return keyFilter.some((filter) => filter.value === value);
+ }
+ return false;
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/dropdown.hbs b/packages/components/src/components/hds/filter-bar/dropdown.hbs
new file mode 100644
index 00000000000..adc686499b4
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/dropdown.hbs
@@ -0,0 +1,52 @@
+{{! @glint-nocheck }}
+
+
+
+
+ {{#if (eq @type "range")}}
+
+ {{else}}
+ {{yield
+ (hash
+ Checkbox=(component
+ "hds/filter-bar/checkbox" checkbox=D.Checkbox keyFilter=this.internalFilters onChange=this.onSelectionChange
+ )
+ Radio=(component
+ "hds/filter-bar/radio" radio=D.Radio keyFilter=this.internalFilters onChange=this.onSelectionChange
+ )
+ )
+ }}
+ {{/if}}
+ {{#unless @isLiveFilter}}
+
+
+
+
+
+
+ {{/unless}}
+
+
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/dropdown.ts b/packages/components/src/components/hds/filter-bar/dropdown.ts
new file mode 100644
index 00000000000..3234a4cacfd
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/dropdown.ts
@@ -0,0 +1,293 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import { modifier } from 'ember-modifier';
+import type { WithBoundArgs } from '@glint/template';
+
+import HdsDropdown from '../dropdown/index.ts';
+
+import HdsFilterBarCheckbox from './checkbox.ts';
+import HdsFilterBarRadio from './radio.ts';
+
+import type {
+ HdsFilterBarFilter,
+ HdsFilterBarFilters,
+ HdsFilterBarFilterType,
+ HdsFilterBarData,
+ HdsFilterBarSelectionFilter,
+ HdsFilterBarRangeFilter,
+ HdsFilterBarRangeFilterSelector,
+} from './types.ts';
+import { SELECTORS_DISPLAY_SYMBOL } from './range.ts';
+
+export interface HdsFilterBarDropdownSignature {
+ Args: {
+ dropdown?: WithBoundArgs;
+ key: string;
+ text?: string;
+ type?: HdsFilterBarFilterType;
+ filters: HdsFilterBarFilters;
+ isMultiSelect?: boolean;
+ isLiveFilter?: boolean;
+ activeFilterableColumns?: string[];
+ searchEnabled?: boolean;
+ onChange: (key: string, keyFilter?: HdsFilterBarFilter) => void;
+ };
+ Blocks: {
+ default: [
+ {
+ Checkbox?: WithBoundArgs<
+ typeof HdsFilterBarCheckbox,
+ 'checkbox' | 'keyFilter' | 'onChange'
+ >;
+ Radio?: WithBoundArgs<
+ typeof HdsFilterBarRadio,
+ 'radio' | 'keyFilter' | 'onChange'
+ >;
+ },
+ ];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBarDropdown extends Component {
+ @tracked internalFilters: HdsFilterBarData | undefined = [];
+
+ private _setUpDropdown = modifier(() => {
+ if (this.keyFilter) {
+ this.internalFilters = JSON.parse(
+ JSON.stringify(this.keyFilter)
+ ) as HdsFilterBarData;
+ }
+ });
+
+ get type(): HdsFilterBarFilterType {
+ const { type } = this.args;
+
+ if (!type) {
+ return 'multi-select';
+ }
+ return type;
+ }
+
+ get keyFilter(): HdsFilterBarData | undefined {
+ const { filters, key } = this.args;
+
+ if (!filters) {
+ return undefined;
+ }
+ return filters[key]?.data;
+ }
+
+ @action
+ onSelectionChange(event: Event): void {
+ const addFilter = (value: unknown): void => {
+ const newFilter = {
+ text: value as string,
+ value: value,
+ } as HdsFilterBarSelectionFilter;
+ if (this.type === 'single-select') {
+ this.internalFilters = newFilter;
+ } else {
+ if (Array.isArray(this.internalFilters)) {
+ this.internalFilters.push(newFilter);
+ } else {
+ this.internalFilters = [newFilter];
+ }
+ }
+ };
+
+ const removeFilter = (value: string): void => {
+ if (this.type === 'single-select') {
+ this.internalFilters = undefined;
+ } else {
+ if (Array.isArray(this.internalFilters)) {
+ const newFilter = [] as HdsFilterBarSelectionFilter[];
+ this.internalFilters.forEach((filter) => {
+ if (filter.value != value) {
+ newFilter.push(filter);
+ }
+ });
+ this.internalFilters = newFilter;
+ } else {
+ this.internalFilters = [];
+ }
+ }
+ };
+
+ const input = event.target as HTMLInputElement;
+
+ if (input.checked) {
+ addFilter(input.value);
+ } else {
+ removeFilter(input.value);
+ }
+
+ if (this.args.isLiveFilter) {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.args.key, this.formattedFilters);
+ }
+ }
+ }
+
+ @action
+ onRangeChange(
+ selector?: HdsFilterBarRangeFilterSelector,
+ value?: number
+ ): void {
+ const addFilter = (): HdsFilterBarData => {
+ const newFilter = {
+ selector: selector,
+ value: value,
+ } as HdsFilterBarRangeFilter;
+ return newFilter;
+ };
+
+ if (selector && value) {
+ this.internalFilters = addFilter();
+ } else {
+ this.internalFilters = undefined;
+ }
+
+ if (this.args.isLiveFilter) {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.args.key, this.formattedFilters);
+ }
+ }
+ }
+
+ @action
+ onApply(closeDropdown?: () => void): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.args.key, this.formattedFilters);
+ }
+
+ if (closeDropdown && typeof closeDropdown === 'function') {
+ closeDropdown();
+ }
+ }
+
+ @action
+ onClear(closeDropdown?: () => void): void {
+ this._clearFilters();
+
+ if (closeDropdown && typeof closeDropdown === 'function') {
+ closeDropdown();
+ }
+ }
+
+ @action
+ onDismiss(): void {
+ this._clearFilters();
+ }
+
+ get formattedFilters(): HdsFilterBarFilter | undefined {
+ if (
+ this.internalFilters === undefined ||
+ (Array.isArray(this.internalFilters) && this.internalFilters.length === 0)
+ ) {
+ return undefined;
+ }
+ return {
+ type: this.type,
+ data: this.internalFilters,
+ } as HdsFilterBarFilter;
+ }
+
+ get toggleButtonText(): string {
+ const { key, filters, text } = this.args;
+
+ let displayText = key;
+ if (text && text.length > 0) {
+ displayText = text;
+ }
+
+ const keyFilter = filters[key];
+
+ if (!filters || !keyFilter || !keyFilter.data) {
+ return displayText;
+ } else if (this.args.type === 'range') {
+ return `${displayText} ${this._rangeFilterText(keyFilter.data)}`;
+ } else if (this.args.type === 'single-select') {
+ return `${displayText}: ${this._singleSelectFilterText(keyFilter.data)}`;
+ } else {
+ return `${displayText}: ${this._multiSelectFilterText(keyFilter.data)}`;
+ }
+ }
+
+ private _rangeFilterText(filterData: HdsFilterBarData): string {
+ if ('selector' in filterData && 'value' in filterData) {
+ return `${SELECTORS_DISPLAY_SYMBOL[filterData.selector]} ${filterData.value}`;
+ } else {
+ return '';
+ }
+ }
+
+ private _singleSelectFilterText(filterData: HdsFilterBarData): string {
+ if ('value' in filterData) {
+ return filterData.value as string;
+ } else {
+ return '';
+ }
+ }
+
+ private _multiSelectFilterText(filterData: HdsFilterBarData): string {
+ if (Array.isArray(filterData) && filterData.length > 0) {
+ const charMax = 10;
+ let filtersString = '';
+
+ filtersString = filterData
+ .map((filter) => {
+ if ('text' in filter && typeof filter.text === 'string') {
+ if (filter.text.length > charMax) {
+ return filter.text.slice(0, charMax) + '...';
+ }
+ return filter.text;
+ }
+ return '';
+ })
+ .join(', ');
+
+ return filtersString;
+ } else {
+ return '';
+ }
+ }
+
+ get classNames(): string {
+ const classes = ['hds-filter-bar__dropdown'];
+
+ // add a class based on the @align argument
+ if (!this._isActiveFilterableColumn()) {
+ classes.push('hds-filter-bar__dropdown--hidden');
+ }
+
+ classes.push(`hds-filter-bar__dropdown--type-${this.type}`);
+
+ return classes.join(' ');
+ }
+
+ private _isActiveFilterableColumn(): boolean {
+ if (this.args.activeFilterableColumns) {
+ return this.args.activeFilterableColumns.includes(this.args.key);
+ }
+ return false;
+ }
+
+ private _clearFilters(): void {
+ this.internalFilters = undefined;
+
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.args.key, undefined);
+ }
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/filters-checkbox.hbs b/packages/components/src/components/hds/filter-bar/filters-checkbox.hbs
new file mode 100644
index 00000000000..6d2e3ecd56f
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/filters-checkbox.hbs
@@ -0,0 +1,5 @@
+{{#let @checkbox as |Checkbox|}}
+
+ {{yield}}
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/filters-checkbox.ts b/packages/components/src/components/hds/filter-bar/filters-checkbox.ts
new file mode 100644
index 00000000000..1c8692c9687
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/filters-checkbox.ts
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import type { WithBoundArgs } from '@glint/template';
+
+import HdsDropdownListItemCheckbox from '../dropdown/list-item/checkbox.ts';
+
+import type { HdsDropdownSignature } from '../dropdown/index.ts';
+
+export interface HdsAdvancedTableFilterBarFiltersCheckboxSignature {
+ Args: HdsDropdownSignature['Args'] & {
+ checkbox?: WithBoundArgs;
+ value?: string;
+ activeFilterableColumns?: string[];
+ onChange?: (event: Event) => void;
+ };
+ Blocks: {
+ default: [];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsAdvancedTableFilterBarFiltersCheckbox extends Component {
+ @action
+ onChange(event: Event): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(event);
+ }
+ }
+
+ get isChecked(): boolean {
+ const { value, activeFilterableColumns } = this.args;
+ return activeFilterableColumns?.includes(value || '') || false;
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/filters-dropdown.hbs b/packages/components/src/components/hds/filter-bar/filters-dropdown.hbs
new file mode 100644
index 00000000000..21f94aab149
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/filters-dropdown.hbs
@@ -0,0 +1,26 @@
+{{! @glint-nocheck }}
+
+
+ {{yield
+ (hash
+ Checkbox=(component
+ "hds/filter-bar/filters-checkbox"
+ checkbox=D.Checkbox
+ activeFilterableColumns=this.internalFilterableColumns
+ onChange=this.onChange
+ )
+ )
+ }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/filters-dropdown.ts b/packages/components/src/components/hds/filter-bar/filters-dropdown.ts
new file mode 100644
index 00000000000..a9d5fe5e2f5
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/filters-dropdown.ts
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import { modifier } from 'ember-modifier';
+import type { WithBoundArgs } from '@glint/template';
+
+import HdsDropdown from './../dropdown/index.ts';
+import HdsFilterBarFiltersCheckbox from './filters-checkbox.ts';
+
+import type { HdsDropdownSignature } from '../dropdown/index.ts';
+
+export interface HdsFilterBarFiltersDropdownSignature {
+ Args: HdsDropdownSignature['Args'] & {
+ dropdown?: WithBoundArgs;
+ activeFilterableColumns?: string[];
+ onChange: (filterableColumns: string[]) => void;
+ };
+ Blocks: {
+ default: [
+ {
+ Checkbox?: WithBoundArgs<
+ typeof HdsFilterBarFiltersCheckbox,
+ 'checkbox' | 'onChange' | 'activeFilterableColumns'
+ >;
+ },
+ ];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBarFiltersDropdown extends Component<
+ HdsDropdownSignature & HdsFilterBarFiltersDropdownSignature
+> {
+ @tracked internalFilterableColumns: string[] = [];
+
+ private _updateInternalFilterableColumns = modifier(() => {
+ const { activeFilterableColumns } = this.args;
+
+ if (activeFilterableColumns) {
+ this.internalFilterableColumns = activeFilterableColumns;
+ } else {
+ this.internalFilterableColumns = [];
+ }
+ });
+
+ @action
+ onChange(event: Event): void {
+ const input = event.target as HTMLInputElement;
+
+ if (input.checked) {
+ this.internalFilterableColumns = [
+ ...this.internalFilterableColumns,
+ input.value,
+ ];
+ } else {
+ this.internalFilterableColumns = this.internalFilterableColumns?.filter(
+ (col) => col !== input.value
+ );
+ }
+ }
+
+ @action
+ onApply(closeDropdown?: () => void): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.internalFilterableColumns);
+ }
+
+ if (closeDropdown && typeof closeDropdown === 'function') {
+ closeDropdown();
+ }
+ }
+
+ @action
+ onClear(closeDropdown?: () => void): void {
+ this.internalFilterableColumns = [];
+
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this.internalFilterableColumns);
+ }
+
+ if (closeDropdown && typeof closeDropdown === 'function') {
+ closeDropdown();
+ }
+ }
+
+ get classNames(): string {
+ const classes = ['hds-filter-bar__filters-dropdown'];
+
+ return classes.join(' ');
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/index.hbs b/packages/components/src/components/hds/filter-bar/index.hbs
new file mode 100644
index 00000000000..7a3f9303d03
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/index.hbs
@@ -0,0 +1,59 @@
+
+
+ {{#if @hasSearch}}
+
+ {{/if}}
+
+ {{yield (hash ActionsDropdown=(component "hds/dropdown"))}}
+
+
+
+ {{#if this.showFilters}}
+
+ {{yield
+ (hash
+ Dropdown=(component
+ "hds/filter-bar/dropdown"
+ onChange=this.onFilter
+ filters=@filters
+ isLiveFilter=@isLiveFilter
+ activeFilterableColumns=this.activeFilterableColumns
+ )
+ )
+ }}
+ {{yield
+ (hash
+ FiltersDropdown=(component
+ "hds/filter-bar/filters-dropdown"
+ activeFilterableColumns=this.activeFilterableColumns
+ onChange=this.onFiltersChange
+ )
+ )
+ }}
+ {{#if this.hasActiveFilters}}
+
+ {{/if}}
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/index.ts b/packages/components/src/components/hds/filter-bar/index.ts
new file mode 100644
index 00000000000..04679f53232
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/index.ts
@@ -0,0 +1,214 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import { schedule } from '@ember/runloop';
+import { modifier } from 'ember-modifier';
+
+import type { ComponentLike, WithBoundArgs } from '@glint/template';
+import type Owner from '@ember/owner';
+
+import type { HdsFilterBarFilter, HdsFilterBarFilters } from './types.ts';
+import HdsDropdown from '../dropdown/index.ts';
+import HdsFilterBarDropdown from './dropdown.ts';
+import HdsFilterBarFiltersDropdown from './filters-dropdown.ts';
+
+export interface HdsFilterBarSignature {
+ Args: {
+ filters: HdsFilterBarFilters;
+ visibleFilterableColumns?: string[];
+ isLiveFilter?: boolean;
+ hasSearch?: boolean;
+ showFilters?: boolean;
+ onFilter?: (filters: HdsFilterBarFilters) => void;
+ onSearch?: (event: Event) => void;
+ };
+ Blocks: {
+ default?: [
+ {
+ ActionsDropdown?: ComponentLike;
+ FiltersDropdown?: WithBoundArgs<
+ typeof HdsFilterBarFiltersDropdown,
+ 'onChange'
+ >;
+ Dropdown?: WithBoundArgs<
+ typeof HdsFilterBarDropdown,
+ 'onChange' | 'filters' | 'isLiveFilter'
+ >;
+ },
+ ];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBar extends Component {
+ @tracked visibleFilterableColumns: string[] = [];
+ @tracked showFilters: boolean = true;
+
+ private _element!: HTMLDivElement;
+ private _filtersDropdownToggleElement!: HTMLDivElement;
+
+ private _setUpFilterBar = modifier((element: HTMLDivElement) => {
+ this._element = element;
+
+ this._filtersDropdownToggleElement = element.querySelector(
+ '.hds-filter-bar__filters-dropdown .hds-dropdown-toggle-button'
+ ) as HTMLDivElement;
+
+ return () => {};
+ });
+
+ constructor(owner: Owner, args: HdsFilterBarSignature['Args']) {
+ super(owner, args);
+
+ const { filters, visibleFilterableColumns, showFilters } = args;
+
+ console.log('Initializing FilterBar with filters:', filters);
+
+ if (showFilters != null) {
+ this.showFilters = showFilters;
+ }
+
+ if (visibleFilterableColumns) {
+ this.visibleFilterableColumns = [...visibleFilterableColumns];
+ }
+
+ Object.keys(filters).forEach((k) => {
+ if (!this.activeFilterableColumns.includes(k)) {
+ this.activeFilterableColumns.push(k);
+ }
+ });
+ }
+
+ get hasActiveFilters(): boolean {
+ return Object.keys(this.args.filters).length > 0;
+ }
+
+ get activeFilterableColumns(): string[] {
+ const { filters } = this.args;
+ const columns: string[] = [];
+
+ Object.keys(filters).forEach((k) => {
+ columns.push(k);
+ });
+
+ return columns.concat(this.visibleFilterableColumns);
+ }
+
+ @action
+ onFilter(key: string, keyFilter?: HdsFilterBarFilter): void {
+ this._triggerFilter(key, keyFilter);
+ }
+
+ @action
+ onFiltersChange(visibleFilterableColumns: string[]): void {
+ const { filters } = this.args;
+
+ this.visibleFilterableColumns = visibleFilterableColumns;
+
+ Object.keys(filters).forEach((k) => {
+ if (!this.activeFilterableColumns.includes(k)) {
+ this._triggerFilter(k);
+ }
+ });
+
+ let filterKeyToOpen: string | undefined = undefined;
+ this.activeFilterableColumns.forEach((k) => {
+ if (!filters[k]) {
+ filterKeyToOpen = k;
+ }
+ });
+
+ console.log('Filter key to open:', filterKeyToOpen);
+ if (filterKeyToOpen) {
+ // eslint-disable-next-line ember/no-runloop
+ schedule('afterRender', (): void => {
+ this._triggerDropdownOpen(filterKeyToOpen as string);
+ });
+ }
+ }
+
+ @action
+ clearFilters(): void {
+ this.visibleFilterableColumns = [];
+ const { onFilter } = this.args;
+ if (onFilter && typeof onFilter === 'function') {
+ onFilter({});
+ }
+
+ this._filtersDropdownToggleElement.focus();
+ }
+
+ @action
+ onSearch(event: Event): void {
+ const { onSearch } = this.args;
+ if (onSearch && typeof onSearch === 'function') {
+ onSearch(event);
+ }
+ }
+
+ @action
+ toggleFilters(): void {
+ this.showFilters = !this.showFilters;
+ }
+
+ private _triggerFilter(key: string, keyFilter?: HdsFilterBarFilter): void {
+ const newFilters = this._updateFilter(key, keyFilter);
+
+ const { onFilter } = this.args;
+ if (onFilter && typeof onFilter === 'function') {
+ onFilter(newFilters);
+ }
+ }
+
+ private _updateFilter(
+ key: string,
+ keyFilter?: HdsFilterBarFilter
+ ): HdsFilterBarFilters {
+ const { filters } = this.args;
+ const newFilters = {} as HdsFilterBarFilters;
+
+ Object.keys(filters).forEach((k) => {
+ newFilters[k] = JSON.parse(
+ JSON.stringify(filters[k])
+ ) as HdsFilterBarFilter;
+ });
+ if (
+ keyFilter === undefined ||
+ (Array.isArray(keyFilter) && keyFilter.length === 0)
+ ) {
+ delete newFilters[key];
+ this.visibleFilterableColumns = this.visibleFilterableColumns.filter(
+ (colKey) => colKey !== key
+ );
+ // Focus back on the filters dropdown toggle after removing a filter
+ this._filtersDropdownToggleElement.focus();
+ } else {
+ Object.assign(newFilters, { [key]: keyFilter });
+ }
+
+ return { ...newFilters };
+ }
+
+ private _triggerDropdownOpen(key: string): void {
+ const dropdownElement = this._element.querySelector(
+ `.hds-filter-bar__dropdown[data-filter-key="${key}"]`
+ ) as HTMLElement;
+ console.log('Triggering dropdown open for key:', key, dropdownElement);
+
+ if (dropdownElement) {
+ const toggleButton = dropdownElement.querySelector(
+ '.hds-dropdown-toggle-button'
+ ) as HTMLElement;
+
+ if (toggleButton) {
+ toggleButton.focus();
+ toggleButton.click();
+ }
+ }
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/radio.hbs b/packages/components/src/components/hds/filter-bar/radio.hbs
new file mode 100644
index 00000000000..3cadd013836
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/radio.hbs
@@ -0,0 +1,5 @@
+{{#let @radio as |Radio|}}
+
+ {{yield}}
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/radio.ts b/packages/components/src/components/hds/filter-bar/radio.ts
new file mode 100644
index 00000000000..8bfc7e7ef2b
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/radio.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import type { WithBoundArgs } from '@glint/template';
+
+import type { HdsFilterBarSelectionFilter } from './types.ts';
+
+import HdsDropdownListItemRadio from '../dropdown/list-item/radio.ts';
+
+import type { HdsDropdownSignature } from '../dropdown/index.ts';
+
+export interface HdsFilterBarRadioSignature {
+ Args: HdsDropdownSignature['Args'] & {
+ radio?: WithBoundArgs;
+ value?: string;
+ keyFilter: HdsFilterBarSelectionFilter | undefined;
+ onChange?: (event: Event) => void;
+ };
+ Blocks: {
+ default: [];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBarRadio extends Component {
+ @action
+ onChange(event: Event): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(event);
+ }
+ }
+
+ get isChecked(): boolean {
+ const { keyFilter, value } = this.args;
+ if (keyFilter && value) {
+ return keyFilter.value === value;
+ }
+ return false;
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/range.hbs b/packages/components/src/components/hds/filter-bar/range.hbs
new file mode 100644
index 00000000000..968859cb4af
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/range.hbs
@@ -0,0 +1,26 @@
+{{#let @generic as |Generic|}}
+
+
+ Number is
+
+
+
+
+ Pick a selector
+ {{#each this._selectorValues as |selectorValue|}}
+ {{this.selectorText
+ selectorValue
+ }}
+ {{/each}}
+
+
+
+
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/components/src/components/hds/filter-bar/range.ts b/packages/components/src/components/hds/filter-bar/range.ts
new file mode 100644
index 00000000000..76844ed73e6
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/range.ts
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import type Owner from '@ember/owner';
+import type { WithBoundArgs } from '@glint/template';
+import { guidFor } from '@ember/object/internals';
+
+import type {
+ HdsFilterBarRangeFilter,
+ HdsFilterBarRangeFilterSelector,
+} from './types.ts';
+import { HdsFilterBarRangeFilterSelectorValues } from './types.ts';
+
+import HdsDropdownListItemGeneric from '../dropdown/list-item/generic.ts';
+
+import type { HdsDropdownSignature } from '../dropdown/index.ts';
+
+export const SELECTORS: HdsFilterBarRangeFilterSelector[] = Object.values(
+ HdsFilterBarRangeFilterSelectorValues
+);
+
+export const SELECTORS_DISPLAY_TEXT: Record<
+ HdsFilterBarRangeFilterSelectorValues,
+ string
+> = {
+ [HdsFilterBarRangeFilterSelectorValues.lessThan]: 'Less than',
+ [HdsFilterBarRangeFilterSelectorValues.lessThanOrEqualTo]:
+ 'Less than or equal to',
+ [HdsFilterBarRangeFilterSelectorValues.equalTo]: 'Equal to',
+ [HdsFilterBarRangeFilterSelectorValues.greaterThanOrEqualTo]:
+ 'Greater than or equal to',
+ [HdsFilterBarRangeFilterSelectorValues.greaterThan]: 'Greater than',
+};
+
+export const SELECTORS_DISPLAY_SYMBOL: Record<
+ HdsFilterBarRangeFilterSelectorValues,
+ string
+> = {
+ [HdsFilterBarRangeFilterSelectorValues.lessThan]: '<',
+ [HdsFilterBarRangeFilterSelectorValues.lessThanOrEqualTo]: '<=',
+ [HdsFilterBarRangeFilterSelectorValues.equalTo]: '=',
+ [HdsFilterBarRangeFilterSelectorValues.greaterThanOrEqualTo]: '>=',
+ [HdsFilterBarRangeFilterSelectorValues.greaterThan]: '>',
+};
+
+export interface HdsFilterBarRangeSignature {
+ Args: HdsDropdownSignature['Args'] & {
+ generic?: WithBoundArgs;
+ keyFilter: HdsFilterBarRangeFilter | undefined;
+ onChange?: (
+ selector?: HdsFilterBarRangeFilterSelector,
+ value?: number
+ ) => void;
+ };
+ Blocks: {
+ default: [];
+ };
+ Element: HTMLDivElement;
+}
+
+export default class HdsFilterBarRange extends Component {
+ @tracked private _selector: HdsFilterBarRangeFilterSelector | undefined;
+ @tracked private _value: number | undefined;
+
+ private _selectorValues = SELECTORS;
+ private _selectorInputId = 'selector-input-' + guidFor(this);
+ private _valueInputId = 'value-input-' + guidFor(this);
+
+ constructor(owner: Owner, args: HdsFilterBarRangeSignature['Args']) {
+ super(owner, args);
+
+ const { keyFilter } = this.args;
+ if (keyFilter) {
+ this._selector = keyFilter.selector;
+ this._value = keyFilter.value;
+ }
+ }
+
+ get stringValue(): string | undefined {
+ return this._value !== undefined ? this._value.toString() : undefined;
+ }
+
+ selectorText(selector: HdsFilterBarRangeFilterSelector): string {
+ return SELECTORS_DISPLAY_TEXT[selector];
+ }
+
+ @action
+ onSelectorChange(event: Event): void {
+ const select = event.target as HTMLSelectElement;
+ this._selector = select.value as HdsFilterBarRangeFilterSelector;
+ this._onChange();
+ }
+
+ @action
+ onValueChange(event: Event): void {
+ const input = event.target as HTMLInputElement;
+ this._value = parseFloat(input.value);
+ this._onChange();
+ }
+
+ private _onChange(): void {
+ const { onChange } = this.args;
+ if (onChange && typeof onChange === 'function') {
+ onChange(this._selector, this._value);
+ }
+ }
+}
diff --git a/packages/components/src/components/hds/filter-bar/types.ts b/packages/components/src/components/hds/filter-bar/types.ts
new file mode 100644
index 00000000000..7318b479a4e
--- /dev/null
+++ b/packages/components/src/components/hds/filter-bar/types.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+export interface HdsFilterBarSelectionFilter {
+ text: string;
+ value: unknown;
+}
+
+export enum HdsFilterBarRangeFilterSelectorValues {
+ lessThan = 'less-than',
+ lessThanOrEqualTo = 'less-than-or-equal-to',
+ equalTo = 'equal-to',
+ greaterThanOrEqualTo = 'greater-than-or-equal-to',
+ greaterThan = 'greater-than',
+}
+
+export type HdsFilterBarRangeFilterSelector =
+ `${HdsFilterBarRangeFilterSelectorValues}`;
+
+export interface HdsFilterBarRangeFilter {
+ selector: HdsFilterBarRangeFilterSelector;
+ value: number;
+}
+
+export enum HdsFilterBarFilterTypeValues {
+ multiSelect = 'multi-select',
+ singleSelect = 'single-select',
+ range = 'range',
+}
+
+export type HdsFilterBarFilterType = `${HdsFilterBarFilterTypeValues}`;
+
+export type HdsFilterBarData =
+ | HdsFilterBarSelectionFilter[]
+ | HdsFilterBarSelectionFilter
+ | HdsFilterBarRangeFilter;
+
+export interface HdsFilterBarFilter {
+ type?: HdsFilterBarFilterType;
+ data?: HdsFilterBarData;
+}
+
+// export interface HdsFilterBarFilters {
+// [name: string]: HdsFilterBarFilter[] | HdsFilterBarFilter | undefined;
+// }
+
+export interface HdsFilterBarFilters {
+ [name: string]: HdsFilterBarFilter;
+}
diff --git a/packages/components/src/styles/@hashicorp/design-system-components.scss b/packages/components/src/styles/@hashicorp/design-system-components.scss
index 00c6008b820..dbf9b828cc7 100644
--- a/packages/components/src/styles/@hashicorp/design-system-components.scss
+++ b/packages/components/src/styles/@hashicorp/design-system-components.scss
@@ -33,6 +33,7 @@
@use "../components/disclosure-primitive";
@use "../components/dismiss-button";
@use "../components/dropdown";
+@use "../components/filter-bar";
@use "../components/flyout";
@use "../components/form"; // multiple components
@use "../components/icon";
diff --git a/packages/components/src/styles/components/advanced-table.scss b/packages/components/src/styles/components/advanced-table.scss
index 75936fadddd..fdd3679bcc3 100644
--- a/packages/components/src/styles/components/advanced-table.scss
+++ b/packages/components/src/styles/components/advanced-table.scss
@@ -415,7 +415,8 @@ $hds-advanced-table-drag-preview-background-color: rgba(204, 227, 254, 30%);
}
}
-.hds-advanced-table__th-context-menu .hds-dropdown-toggle-icon {
+.hds-advanced-table__th-context-menu .hds-dropdown-toggle-icon,
+.hds-advanced-table__th-filter-menu .hds-dropdown-toggle-icon {
width: $hds-advanced-table-button-size;
height: $hds-advanced-table-button-size;
margin: -2px 0;
@@ -504,6 +505,25 @@ $hds-advanced-table-drag-preview-background-color: rgba(204, 227, 254, 30%);
align-self: flex-start;
}
+.hds-advanced-table__th-filter-menu--active {
+ position: relative;
+
+ &::before {
+ position: absolute;
+ top: -4px;
+ right: -4px;
+ width: 6px;
+ height: 6px;
+ background-color: var(--token-color-foreground-action);
+ border-radius: 50%;
+ content: "";
+ }
+}
+
+.hds-advanced-table__clear-filters-button {
+ margin-bottom: 16px;
+}
+
// ----------------------------------------------------------------
// TABLE BODY
@@ -745,3 +765,49 @@ $hds-advanced-table-drag-preview-background-color: rgba(204, 227, 254, 30%);
border-radius: var(--token-border-radius-medium);
box-shadow: var(--token-elevation-mid-box-shadow);
}
+
+// ----------------------------------------------------------------
+
+// FILTER BAR
+.hds-advanced-table__actions .hds-filter-bar {
+ border-bottom: none;
+ border-radius: $hds-advanced-table-border-radius $hds-advanced-table-border-radius 0 0;
+}
+
+.hds-advanced-table__actions + .hds-advanced-table__container {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+
+ .hds-advanced-table__thead .hds-advanced-table__tr:first-of-type .hds-advanced-table__th {
+ &:first-child {
+ border-top-left-radius: 0;
+ }
+
+ &:last-child {
+ border-top-right-radius: 0;
+ }
+ }
+}
+
+/// ----------------------------------------------------------------
+
+// EMPTY STATE
+.hds-advanced-table__empty-state {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 400px;
+ background-color: var(--token-color-surface-primary);
+ border: 1px solid var(--token-color-border-primary);
+ border-bottom-right-radius: $hds-advanced-table-border-radius;
+ border-bottom-left-radius: $hds-advanced-table-border-radius;
+}
+
+.hds-advanced-table__empty-state__content {
+ max-width: 450px;
+}
+
+.hds-advanced-table:not(:has(+ .hds-advanced-table__empty-state)) {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
diff --git a/packages/components/src/styles/components/dropdown.scss b/packages/components/src/styles/components/dropdown.scss
index 0c35374cd8b..e82f6a0bb2c 100644
--- a/packages/components/src/styles/components/dropdown.scss
+++ b/packages/components/src/styles/components/dropdown.scss
@@ -602,3 +602,58 @@ $hds-dropdown-toggle-border-radius: $hds-button-border-radius;
vertical-align: bottom;
}
}
+
+// SEARCH
+
+.hds-dropdown__search {
+ flex: none;
+ padding: 8px;
+ border-bottom: 1px solid var(--token-color-border-primary);
+}
+
+// DISMISS BUTTON
+.hds-dropdown__dismiss {
+ color: var(--token-color-foreground-primary);
+ background-color: var(--token-color-surface-faint);
+ border: 1px solid var(--token-color-border-strong);
+ border-right: none;
+ border-radius: var(--token-border-radius-small) 0 0 var(--token-border-radius-small);
+
+ &:hover,
+ &.mock-hover {
+ background-color: var(--token-color-surface-interactive);
+ cursor: pointer;
+ }
+
+ &:focus,
+ &.mock-focus {
+ @include hds-button-state-focus();
+ border-color: var(--token-color-focus-action-internal);
+
+ &::before {
+ border-color: var(--token-color-focus-action-external);
+ }
+ }
+
+ &:active,
+ &.mock-active {
+ background-color: var(--token-color-surface-interactive-active);
+ border-color: var(--token-color-border-strong);
+
+ &::before {
+ border-color: transparent;
+ }
+ }
+}
+
+.hds-dropdown--has-dismiss {
+ display: flex;
+
+ .hds-dropdown-toggle-button {
+ border-left: none;
+
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ box-shadow: none;
+ }
+}
diff --git a/packages/components/src/styles/components/filter-bar.scss b/packages/components/src/styles/components/filter-bar.scss
new file mode 100644
index 00000000000..31b2b288b16
--- /dev/null
+++ b/packages/components/src/styles/components/filter-bar.scss
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+//
+// FILTER BAR
+//
+
+.hds-filter-bar {
+ display: grid;
+ gap: 8px;
+ padding: 8px;
+ background-color: var(--token-color-surface-faint);
+ border: 1px solid var(--token-color-border-primary);
+ border-radius: var(--token-border-radius-medium);
+
+ .hds-filter-bar__filters {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 12px;
+ align-items: end;
+ padding-top: 8px;
+ border-top: 1px solid var(--token-color-border-primary);
+
+ .hds-dropdown__list .hds-form-text-input {
+ width: auto;
+ }
+ }
+
+ .hds-filter-bar__actions {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ align-items: end;
+ justify-content: space-between;
+ }
+
+ .hds-filter-bar__active-filters {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ }
+}
+
+.hds-filter-bar__actions__right {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+}
+
+.hds-filter-bar__search {
+ --token-form-control-padding: 4px;
+}
+
+// DROPDOWN
+.hds-filter-bar__dropdown--hidden.hds-segmented-group {
+ display: none;
+}
+
+.hds-filter-bar__dropdown--type-range .hds-dropdown-list-item {
+ padding: 8px 16px;
+}
+
+.hds-filter-bar__dropdown--type-range .hds-form-label {
+ margin-bottom: 4px;
+}
diff --git a/packages/components/src/template-registry.ts b/packages/components/src/template-registry.ts
index 66183aa31c6..06a735ebd98 100644
--- a/packages/components/src/template-registry.ts
+++ b/packages/components/src/template-registry.ts
@@ -97,6 +97,13 @@ import type HdsDropdownListItemTitleComponent from './components/hds/dropdown/li
import type HdsDropdownToggleButtonComponent from './components/hds/dropdown/toggle/button';
import type HdsDropdownToggleChevronComponent from './components/hds/dropdown/toggle/chevron';
import type HdsDropdownToggleIconComponent from './components/hds/dropdown/toggle/icon';
+import type HdsFilterBarComponent from './components/hds/filter-bar';
+import type HdsFilterBarDropdownComponent from './components/hds/filter-bar/dropdown';
+import type HdsFilterBarCheckboxComponent from './components/hds/filter-bar/checkbox';
+import type HdsFilterBarRadioComponent from './components/hds/filter-bar/radio';
+import type HdsFilterBarFiltersDropdownComponent from './components/hds/filter-bar/filters-dropdown';
+import type HdsFilterBarFiltersCheckboxComponent from './components/hds/filter-bar/filters-checkbox';
+import type HdsFilterBarRangeComponent from './components/hds/filter-bar/range';
import type HdsFlyoutBodyComponent from './components/hds/flyout/body';
import type HdsFlyoutDescriptionComponent from './components/hds/flyout/description';
import type HdsFlyoutFooterComponent from './components/hds/flyout/footer';
@@ -566,6 +573,22 @@ export default interface HdsComponentsRegistry {
'Hds::Dropdown::Toggle::Icon': typeof HdsDropdownToggleIconComponent;
'hds/dropdown/toggle/icon': typeof HdsDropdownToggleIconComponent;
+ // Filter Bar
+ 'Hds::FilterBar': typeof HdsFilterBarComponent;
+ 'hds/filter-bar': typeof HdsFilterBarComponent;
+ 'Hds::FilterBar::Dropdown': typeof HdsFilterBarDropdownComponent;
+ 'hds/filter-bar/dropdown': typeof HdsFilterBarDropdownComponent;
+ 'Hds::FilterBar::Checkbox': typeof HdsFilterBarCheckboxComponent;
+ 'hds/filter-bar/checkbox': typeof HdsFilterBarCheckboxComponent;
+ 'Hds::FilterBar::Radio': typeof HdsFilterBarRadioComponent;
+ 'hds/filter-bar/radio': typeof HdsFilterBarRadioComponent;
+ 'Hds::FilterBar::FiltersDropdown': typeof HdsFilterBarFiltersDropdownComponent;
+ 'hds/filter-bar/filters-dropdown': typeof HdsFilterBarFiltersDropdownComponent;
+ 'Hds::FilterBar::FiltersCheckbox': typeof HdsFilterBarFiltersCheckboxComponent;
+ 'hds/filter-bar/filters-checkbox': typeof HdsFilterBarFiltersCheckboxComponent;
+ 'Hds::FilterBar::Range': typeof HdsFilterBarRangeComponent;
+ 'hds/filter-bar/range': typeof HdsFilterBarRangeComponent;
+
// Flyout
'Hds::Flyout': typeof HdsFlyoutComponent;
'hds/flyout': typeof HdsFlyoutComponent;
diff --git a/showcase/app/components/mock/app/main/generic-advanced-table.gts b/showcase/app/components/mock/app/main/generic-advanced-table.gts
index c238ae7606e..c0a6d420ed3 100644
--- a/showcase/app/components/mock/app/main/generic-advanced-table.gts
+++ b/showcase/app/components/mock/app/main/generic-advanced-table.gts
@@ -4,111 +4,35 @@
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
import { deepTracked } from 'ember-deep-tracked';
import { get } from '@ember/helper';
+import { on } from '@ember/modifier';
+import style from 'ember-style-modifier/modifiers/style';
// HDS components
import {
HdsAdvancedTable,
+ HdsButton,
+ HdsFilterBar,
+ HdsLayoutFlex,
HdsLinkInline,
HdsBadge,
HdsBadgeColorValues,
+ HdsFormToggleField,
+ HdsTextBody,
+ HdsTextDisplay,
type HdsAdvancedTableOnSelectionChangeSignature,
+ type HdsFilterBarRangeFilterSelector,
} from '@hashicorp/design-system-components/components';
import type { HdsAdvancedTableSignature } from '@hashicorp/design-system-components/components/hds/advanced-table/index';
+import type { HdsFilterBarSignature } from '@hashicorp/design-system-components/components/hds/filter-bar/index';
export interface MockAppMainGenericAdvancedTableSignature {
Element: HTMLDivElement;
}
-const SAMPLE_COLUMNS = [
- {
- isSortable: true,
- label: 'Name',
- key: 'name',
- width: 'max-content',
- },
- {
- label: 'Project name',
- key: 'project-name',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Current run ID',
- key: 'current-run-id',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Run status',
- key: 'run-status',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Current run applied',
- key: 'current-run-applied',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'VCS repo',
- key: 'vcs-repo',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Module count',
- key: 'module-count',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Modules',
- key: 'modules',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Provider count',
- key: 'provider-count',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Providers',
- key: 'providers',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Terraform version',
- key: 'terraform-version',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'State terraform version',
- key: 'state-terraform-version',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Created',
- key: 'created',
- isSortable: true,
- width: 'max-content',
- },
- {
- label: 'Updated',
- key: 'updated',
- isSortable: true,
- width: 'max-content',
- },
-];
-
const SAMPLE_MODEL = [
{
name: 'zoguve-guw-mannaz',
@@ -151,7 +75,7 @@ const SAMPLE_MODEL = [
'run-status': 'applied',
'run-status-color': HdsBadgeColorValues.Success,
'current-run-applied': 'Mar 06, 2025 09:08:14 am',
- 'vcs-repo': 'example/sClKKTBbyCIzf@d8NxH2',
+ 'vcs-repo': 'example/a))!hzfpKcBl0',
'module-count': 31,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 42,
@@ -168,7 +92,7 @@ const SAMPLE_MODEL = [
'run-status': 'planned',
'run-status-color': HdsBadgeColorValues.Warning,
'current-run-applied': 'Mar 06, 2025 09:07:14 am',
- 'vcs-repo': 'example/y0^(Nm*63',
+ 'vcs-repo': 'example/a))!hzfpKcBl0',
'module-count': 58,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 140,
@@ -185,7 +109,7 @@ const SAMPLE_MODEL = [
'run-status': 'applied',
'run-status-color': HdsBadgeColorValues.Success,
'current-run-applied': 'Mar 06, 2025 09:06:14 am',
- 'vcs-repo': 'example/ljPWe[4',
+ 'vcs-repo': 'example/a))!hzfpKcBl0',
'module-count': 32,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 50,
@@ -202,7 +126,7 @@ const SAMPLE_MODEL = [
'run-status': 'errored',
'run-status-color': HdsBadgeColorValues.Critical,
'current-run-applied': 'Mar 06, 2025 09:05:14 am',
- 'vcs-repo': 'example/E*fcS4mn@BoDgZu0O5',
+ 'vcs-repo': 'example/a))!hzfpKcBl0',
'module-count': 94,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 113,
@@ -236,13 +160,13 @@ const SAMPLE_MODEL = [
'run-status': 'errored',
'run-status-color': HdsBadgeColorValues.Critical,
'current-run-applied': 'Mar 06, 2025 09:03:14 am',
- 'vcs-repo': 'example/(DCFjSEKcBuU44J8AB87',
+ 'vcs-repo': 'example/&j[RmmtjpQX6',
'module-count': 114,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 107,
providers: 'susnup-da-zuw',
'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 27 2025',
updated: 'Feb 27 2025',
},
@@ -253,13 +177,13 @@ const SAMPLE_MODEL = [
'run-status': 'planned',
'run-status-color': HdsBadgeColorValues.Warning,
'current-run-applied': 'Mar 06, 2025 09:02:14 am',
- 'vcs-repo': 'example/9YURY8',
+ 'vcs-repo': 'example/&j[RmmtjpQX6',
'module-count': 106,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 185,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'terraform-version': '0.14.5',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 26 2025',
updated: 'Feb 26 2025',
},
@@ -270,13 +194,13 @@ const SAMPLE_MODEL = [
'run-status': 'errored',
'run-status-color': HdsBadgeColorValues.Critical,
'current-run-applied': 'Mar 06, 2025 09:01:14 am',
- 'vcs-repo': 'example/9YURY8',
+ 'vcs-repo': 'example/&j[RmmtjpQX6',
'module-count': 124,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 175,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'terraform-version': '0.14.5',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 25 2025',
updated: 'Feb 25 2025',
},
@@ -287,13 +211,13 @@ const SAMPLE_MODEL = [
'run-status': 'applied',
'run-status-color': HdsBadgeColorValues.Success,
'current-run-applied': 'Mar 06, 2025 09:00:14 am',
- 'vcs-repo': 'example/d2s3B46I10',
+ 'vcs-repo': 'example/&j[RmmtjpQX6',
'module-count': 70,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 168,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'terraform-version': '0.14.5',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 24 2025',
updated: 'Feb 24 2025',
},
@@ -309,8 +233,8 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 168,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'terraform-version': '0.14.5',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 23 2025',
updated: 'Feb 23 2025',
},
@@ -321,13 +245,13 @@ const SAMPLE_MODEL = [
'run-status': 'errored',
'run-status-color': HdsBadgeColorValues.Critical,
'current-run-applied': 'Mar 06, 2025 08:59:14 am',
- 'vcs-repo': 'example/v@C6&hBTou11',
+ 'vcs-repo': 'example/d2s3B46I10',
'module-count': 106,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 61,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
- 'state-terraform-version': '0.15.0',
+ 'terraform-version': '0.14.5',
+ 'state-terraform-version': '0.16.0',
created: 'Feb 22 2025',
updated: 'Feb 22 2025',
},
@@ -338,12 +262,12 @@ const SAMPLE_MODEL = [
'run-status': 'applied',
'run-status-color': HdsBadgeColorValues.Success,
'current-run-applied': 'Mar 06, 2025 08:58:14 am',
- 'vcs-repo': 'example/@t23^12',
+ 'vcs-repo': 'example/d2s3B46I10',
'module-count': 14,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 143,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 21 2025',
updated: 'Feb 21 2025',
@@ -355,12 +279,12 @@ const SAMPLE_MODEL = [
'run-status': 'planned',
'run-status-color': HdsBadgeColorValues.Warning,
'current-run-applied': 'Mar 06, 2025 08:58:14 am',
- 'vcs-repo': 'example/@t23^12',
+ 'vcs-repo': 'example/d2s3B46I10',
'module-count': 14,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 143,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 20 2025',
updated: 'Feb 20 2025',
@@ -377,7 +301,7 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 98,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 19 2025',
updated: 'Feb 19 2025',
@@ -394,7 +318,7 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 170,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 18 2025',
updated: 'Feb 18 2025',
@@ -406,12 +330,12 @@ const SAMPLE_MODEL = [
'run-status': 'planned',
'run-status-color': HdsBadgeColorValues.Warning,
'current-run-applied': 'Mar 06, 2025 08:57:14 am',
- 'vcs-repo': 'example/t*vN3@*BxJnG116',
+ 'vcs-repo': 'example/d2s3B46I10',
'module-count': 139,
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 170,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 17 2025',
updated: 'Feb 17 2025',
@@ -428,7 +352,7 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 83,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 16 2025',
updated: 'Feb 16 2025',
@@ -445,7 +369,7 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 152,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 15 2025',
updated: 'Feb 15 2025',
@@ -462,13 +386,127 @@ const SAMPLE_MODEL = [
modules: 'wad-bedzeaje-rogmejca',
'provider-count': 11,
providers: 'susnup-da-zuw',
- 'terraform-version': '0.14.0',
+ 'terraform-version': '0.14.5',
'state-terraform-version': '0.15.0',
created: 'Feb 14 2025',
updated: 'Feb 14 2025',
},
];
+const SAMPLE_MODEL_VALUES = {
+ name: Array.from(new Set(SAMPLE_MODEL.map((item) => item['name']))).map(
+ (value) => ({ value, label: value }),
+ ),
+ 'project-name': Array.from(
+ new Set(SAMPLE_MODEL.map((item) => item['project-name'])),
+ ).map((value) => ({ value, label: value })),
+ 'run-status': Array.from(
+ new Set(SAMPLE_MODEL.map((item) => item['run-status'])),
+ ).map((value) => ({ value, label: value })),
+ 'vcs-repo': Array.from(
+ new Set(SAMPLE_MODEL.map((item) => item['vcs-repo'])),
+ ).map((value) => ({ value, label: value })),
+ 'terraform-version': Array.from(
+ new Set(SAMPLE_MODEL.map((item) => item['terraform-version'])),
+ ).map((value) => ({ value, label: value })),
+ 'state-terraform-version': Array.from(
+ new Set(SAMPLE_MODEL.map((item) => item['state-terraform-version'])),
+ ).map((value) => ({ value, label: value })),
+};
+
+const SAMPLE_COLUMNS = [
+ {
+ isSortable: true,
+ label: 'Name',
+ key: 'name',
+ width: 'max-content',
+ filterType: 'checkbox',
+ },
+ {
+ label: 'Project name',
+ key: 'project-name',
+ isSortable: true,
+ width: 'max-content',
+ filterType: 'checkbox',
+ },
+ {
+ label: 'Current run ID',
+ key: 'current-run-id',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Run status',
+ key: 'run-status',
+ isSortable: true,
+ width: 'max-content',
+ filterType: 'checkbox',
+ },
+ {
+ label: 'Current run applied',
+ key: 'current-run-applied',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'VCS repo',
+ key: 'vcs-repo',
+ isSortable: true,
+ width: 'max-content',
+ filterType: 'checkbox',
+ },
+ {
+ label: 'Module count',
+ key: 'module-count',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Modules',
+ key: 'modules',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Provider count',
+ key: 'provider-count',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Providers',
+ key: 'providers',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Terraform version',
+ key: 'terraform-version',
+ isSortable: true,
+ width: 'max-content',
+ filterType: 'radio',
+ },
+ {
+ label: 'State terraform version',
+ key: 'state-terraform-version',
+ isSortable: true,
+ width: 'max-content',
+ filterType: 'radio',
+ },
+ {
+ label: 'Created',
+ key: 'created',
+ isSortable: true,
+ width: 'max-content',
+ },
+ {
+ label: 'Updated',
+ key: 'updated',
+ isSortable: true,
+ width: 'max-content',
+ },
+];
+
const updateModelWithSelectAllState = (
modelData: HdsAdvancedTableSignature['Args']['model'],
selectAllState: boolean,
@@ -499,6 +537,10 @@ export default class MockAppMainGenericAdvancedTable extends Component {
+ this.filters = filters;
+ };
+
+ onSearch = (event: Event) => {
+ const value = event.target.value;
+ if (value.length > 0) {
+ window.alert(`✅ Search executed with value: ${value}`);
+ }
+ };
+
+ get demoModelFilteredData() {
+ const filterItem = (
+ item: HdsFilterBarSignature['Args']['filters'],
+ ): boolean => {
+ if (Object.keys(this.filters).length === 0) return true;
+ let match = true;
+ Object.keys(this.filters).forEach((key) => {
+ const filter = this.filters[key];
+ if (filter && filter.data) {
+ if (filter.type === 'range') {
+ if (!this.isRangeFilterMatch(item[key], filter.data)) {
+ match = false;
+ }
+ } else if (filter.type === 'single-select') {
+ if (!this.isSingleSelectFilterMatch(item[key], filter.data)) {
+ match = false;
+ }
+ } else {
+ if (!this.isMultiSelectFilterMatch(item[key], filter.data)) {
+ match = false;
+ }
+ }
+ }
+ });
+ return match;
+ };
+
+ const filteredData = this.demoModel.filter(filterItem);
+ this.emptyData = !(filteredData.length > 0);
+ return filteredData;
+ }
+
+ isRangeFilterMatch(
+ itemValue: unknown,
+ filterData: HdsFilterBarSignature['Args']['filters']['data'],
+ ): boolean {
+ const selector = filterData.selector as HdsFilterBarRangeFilterSelector;
+ const number = Number(itemValue);
+
+ if (isNaN(number)) {
+ return false;
+ } else {
+ switch (selector) {
+ case 'less-than':
+ return number < filterData.value;
+ case 'less-than-or-equal-to':
+ return number <= filterData.value;
+ case 'equal-to':
+ return number === filterData.value;
+ case 'greater-than-or-equal-to':
+ return number >= filterData.value;
+ case 'greater-than':
+ return number > filterData.value;
+ default:
+ return false;
+ }
+ }
+ }
+
+ isSingleSelectFilterMatch(
+ itemValue: unknown,
+ filterData: HdsFilterBarSignature['Args']['filters']['data'],
+ ): boolean {
+ return itemValue === filterData.value;
+ }
+
+ isMultiSelectFilterMatch(
+ itemValue: unknown,
+ filterData: HdsFilterBarSignature['Args']['filters']['data'],
+ ): boolean {
+ const filterValues = filterData.map(
+ (d: HdsFilterBarSignature['Args']['filters']['data']['value']) => d.value,
+ );
+ return filterValues.includes(itemValue);
+ }
+
+ clearFilters = () => {
+ this.filters = {};
+ };
+
+ onLiveFilterToggle = (event: Event) => {
+ const target = event.target as HTMLInputElement;
+ this.isLiveFilter = target.checked;
+ };
+
+ onSeparatedFilterBar = (event: Event) => {
+ const target = event.target as HTMLInputElement;
+ this.isSeparatedFilterBar = target.checked;
+ };
+
+
+
+ Live filtering
+
+
+
+
+ Separated filter bar component
+
+
+
+ {{#if this.isSeparatedFilterBar}}
+
+
+
+ access
+ homework
+ discovery
+ memories
+
+
+ Name
+ Project name
+ Run status
+ Terraform version
+ Module count
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "name") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "project-name") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "run-status") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "terraform-version") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+
+ {{/if}}
+
+ <:actions as |A|>
+ {{#unless this.isSeparatedFilterBar}}
+
+
+
+ access
+ homework
+ discovery
+ memories
+
+
+ Name
+ Project name
+ Run status
+ Terraform version
+ Module count
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "name") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "project-name") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each (get SAMPLE_MODEL_VALUES "run-status") as |option|}}
+ {{option.label}}
+ {{/each}}
+
+
+ {{#each
+ (get SAMPLE_MODEL_VALUES "terraform-version")
+ as |option|
+ }}
+ {{option.label}}
+ {{/each}}
+
+
+
+ {{/unless}}
+
<:body as |B|>
{{! @glint-expect-error }}
@@ -607,6 +905,23 @@ export default class MockAppMainGenericAdvancedTable extends Component
+ <:emptyState>
+ {{#if this.emptyData}}
+
+ No data to display
+
+ No results were found with the selected filters. Please clear or
+ update the filters.
+
+
+
+
+
+ {{/if}}
+
}