Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06ba5a3
SPIKE Adv table filter bar filtering
dchyun Sep 5, 2025
ea8df8f
Feat: Add search bar, filters toggle, yielded block
dchyun Oct 16, 2025
8de20f7
Feat: Add dropdown search
dchyun Oct 16, 2025
71f32cc
Feat: Dropdown dismiss button
dchyun Oct 22, 2025
2b0408c
Feat: Separate filter bar component from adv table
dchyun Oct 24, 2025
34f1688
Feat: Refactor of filter api, support for range filters
dchyun Oct 27, 2025
356cf66
Feat: Dropdown opens on add
dchyun Oct 27, 2025
7442f7a
Feat: Use SegmentedGroup for dropdown dismiss
dchyun Oct 27, 2025
e4590d8
Feat: Empty state for adv table
dchyun Oct 28, 2025
bd71f42
Feat: Filter bar mega dropdown
dchyun Oct 29, 2025
3e90f62
Feat: dropdown range filter, fix sync of filters in dropdown
dchyun Oct 30, 2025
5dd922d
Fix: Revert search and dismiss features in dropdown
dchyun Oct 30, 2025
175d1f5
Fix: Type and linting errors
dchyun Oct 31, 2025
3ae6bc6
Feat: Refactor filter options and tab into filter group
dchyun Oct 31, 2025
2a4d327
Feat: Filter bar tabs component
dchyun Nov 3, 2025
cc2b8d3
Feat: Expand / collapse button
dchyun Nov 3, 2025
c79212f
Feat: Search filter
dchyun Nov 5, 2025
975044e
Fix: Linting error
dchyun Nov 5, 2025
111dbc7
Chore: add changeset
dchyun Nov 6, 2025
1e58c74
Feat: Update range selector labels
dchyun Nov 17, 2025
570f905
Chore: Update value input placeholder text
dchyun Nov 17, 2025
10dd6c9
Feat: Range input - between selector
dchyun Nov 17, 2025
7bc3fe4
Feat: Clear button
dchyun Nov 18, 2025
b73dbd2
Feat: Date, datetime, time filters
dchyun Nov 19, 2025
be97012
Feat: Date dismiss tag text formatting
dchyun Nov 19, 2025
7db9cc6
Feat: Live filtering
dchyun Nov 20, 2025
2873cad
Feat: Expand/collapse button empty state
dchyun Nov 20, 2025
78f7378
Feat: ActionsGeneric block
dchyun Nov 20, 2025
ec4a7f7
Feat: Design alignments
dchyun Nov 21, 2025
6d4a8a4
Feat: Custom generic filter type
dchyun Nov 24, 2025
93277ce
Fix: Remove numFilters count on dropdown
dchyun Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/dark-signs-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@hashicorp/design-system-components": minor
---

<!-- START components/filter-bar -->
`FilterBar` - Added new Filter Bar component
<!-- END -->

<!-- START components/table/advanced-table -->
`AdvancedTable` - Added support for filtering within the table with new `actions` named block and `FilterBar` contextual component
<!-- END -->

<!-- START components/table/advanced-table -->
`AdvancedTable` - Added argument `isEmpty` and named block `emptyState` for setting an empty state for the table
<!-- END -->

11 changes: 11 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@
"./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/date.js": "./dist/_app_/components/hds/filter-bar/date.js",
"./components/hds/filter-bar/filter-group.js": "./dist/_app_/components/hds/filter-bar/filter-group.js",
"./components/hds/filter-bar/filters-dropdown.js": "./dist/_app_/components/hds/filter-bar/filters-dropdown.js",
"./components/hds/filter-bar/generic.js": "./dist/_app_/components/hds/filter-bar/generic.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/filter-bar/tabs.js": "./dist/_app_/components/hds/filter-bar/tabs.js",
"./components/hds/filter-bar/tabs/panel.js": "./dist/_app_/components/hds/filter-bar/tabs/panel.js",
"./components/hds/filter-bar/tabs/tab.js": "./dist/_app_/components/hds/filter-bar/tabs/tab.js",
"./components/hds/flyout.js": "./dist/_app_/components/hds/flyout.js",
"./components/hds/form/character-count.js": "./dist/_app_/components/hds/form/character-count.js",
"./components/hds/form/checkbox/base.js": "./dist/_app_/components/hds/form/checkbox/base.js",
Expand Down
14 changes: 14 additions & 0 deletions packages/components/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ 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 HdsFilterBarDate } from './components/hds/filter-bar/date.ts';
export { default as HdsFilterBarFiltersDropdown } from './components/hds/filter-bar/filters-dropdown.ts';
export { default as HdsFilterBarFilterGroup } from './components/hds/filter-bar/filter-group.ts';
export { default as HdsFilterBarGeneric } from './components/hds/filter-bar/generic.ts';
export { default as HdsFilterBarRadio } from './components/hds/filter-bar/radio.ts';
export { default as HdsFilterBarRange } from './components/hds/filter-bar/range.ts';
export { default as HdsFilterBarTabs } from './components/hds/filter-bar/tabs/index.ts';
export { default as HdsFilterBarTabsPanel } from './components/hds/filter-bar/tabs/panel.ts';
export { default as HdsFilterBarTabsTab } from './components/hds/filter-bar/tabs/tab.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';
Expand Down
154 changes: 93 additions & 61 deletions packages/components/src/components/hds/advanced-table/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
SPDX-License-Identifier: MPL-2.0
}}

{{#if (has-block "actions")}}
<div class="hds-advanced-table__actions">
{{yield (hash FilterBar=(component "hds/filter-bar")) to="actions"}}
</div>
{{/if}}
<div
class="hds-advanced-table__container
{{(if this.isStickyHeaderPinned 'hds-advanced-table__container--header-is-pinned')}}"
Expand Down Expand Up @@ -106,79 +111,81 @@
</div>

{{! Body }}
<div class="hds-advanced-table__tbody" role="rowgroup">
{{! ----------------------------------------------------------------------------------------
{{#unless this.isEmpty}}
<div class="hds-advanced-table__tbody" role="rowgroup">
{{! ----------------------------------------------------------------------------------------
IMPORTANT: we loop on the `model` array and for each record
we yield the Tr/Td/Th elements _and_ the record itself as `data`
this means the consumer will *have to* use the `data` key to access it in their template
-------------------------------------------------------------------------------------------- }}
{{#each this._tableModel.sortedRows key=this.identityKey as |record index|}}
{{#if this._tableModel.hasRowsWithChildren}}
<Hds::AdvancedTable::ExpandableTrGroup
@record={{record}}
@rowIndex={{index}}
@onClickToggle={{record.onClickToggle}}
as |T|
>
{{#each this._tableModel.sortedRows key=this.identityKey as |record index|}}
{{#if this._tableModel.hasRowsWithChildren}}
<Hds::AdvancedTable::ExpandableTrGroup
@record={{record}}
@rowIndex={{index}}
@onClickToggle={{record.onClickToggle}}
as |T|
>
{{yield
(hash
Tr=(component
"hds/advanced-table/tr"
isLastRow=(eq this._tableModel.lastVisibleRow.id T.data.id)
isParentRow=T.isExpandable
depth=T.depth
displayRow=T.shouldDisplayChildRows
data=T.data
)
Th=(component
"hds/advanced-table/th"
depth=T.depth
isExpandable=T.isExpandable
isExpanded=T.isExpanded
newLabel=T.id
parentId=T.parentId
scope="row"
onClickToggle=T.onClickToggle
)
Td=(component "hds/advanced-table/td" align=@align)
data=T.data
isOpen=T.isExpanded
rowIndex=T.rowIndex
)
to="body"
}}
</Hds::AdvancedTable::ExpandableTrGroup>
{{else}}
{{yield
(hash
Tr=(component
"hds/advanced-table/tr"
isLastRow=(eq this._tableModel.lastVisibleRow.id T.data.id)
isParentRow=T.isExpandable
depth=T.depth
displayRow=T.shouldDisplayChildRows
data=T.data
selectionScope="row"
isLastRow=(eq this._tableModel.lastVisibleRow.id record.id)
isSelectable=this.isSelectable
onSelectionChange=this.onSelectionRowChange
didInsert=this.didInsertRowCheckbox
willDestroy=this.willDestroyRowCheckbox
selectionAriaLabelSuffix=@selectionAriaLabelSuffix
hasStickyColumn=this.hasStickyFirstColumn
isStickyColumnPinned=this.isStickyColumnPinned
data=record
)
Th=(component
"hds/advanced-table/th"
depth=T.depth
isExpandable=T.isExpandable
isExpanded=T.isExpanded
newLabel=T.id
parentId=T.parentId
scope="row"
onClickToggle=T.onClickToggle
isStickyColumn=this.hasStickyFirstColumn
isStickyColumnPinned=this.isStickyColumnPinned
)
Td=(component "hds/advanced-table/td" align=@align)
data=T.data
isOpen=T.isExpanded
rowIndex=T.rowIndex
data=record
rowIndex=index
)
to="body"
}}
</Hds::AdvancedTable::ExpandableTrGroup>
{{else}}
{{yield
(hash
Tr=(component
"hds/advanced-table/tr"
selectionScope="row"
isLastRow=(eq this._tableModel.lastVisibleRow.id record.id)
isSelectable=this.isSelectable
onSelectionChange=this.onSelectionRowChange
didInsert=this.didInsertRowCheckbox
willDestroy=this.willDestroyRowCheckbox
selectionAriaLabelSuffix=@selectionAriaLabelSuffix
hasStickyColumn=this.hasStickyFirstColumn
isStickyColumnPinned=this.isStickyColumnPinned
data=record
)
Th=(component
"hds/advanced-table/th"
scope="row"
isStickyColumn=this.hasStickyFirstColumn
isStickyColumnPinned=this.isStickyColumnPinned
)
Td=(component "hds/advanced-table/td" align=@align)
data=record
rowIndex=index
)
to="body"
}}
{{/if}}
{{/each}}
</div>
{{/if}}
{{/each}}
</div>
{{/unless}}
</div>

{{#if this.showScrollIndicatorLeft}}
Expand All @@ -195,10 +202,35 @@
/>
{{/if}}

{{#if this.showScrollIndicatorBottom}}
<div
class="hds-advanced-table__scroll-indicator hds-advanced-table__scroll-indicator-bottom"
{{style bottom=this.scrollIndicatorDimensions.bottom width=this.scrollIndicatorDimensions.width}}
/>
{{#unless this.isEmpty}}
{{#if this.showScrollIndicatorBottom}}
<div
class="hds-advanced-table__scroll-indicator hds-advanced-table__scroll-indicator-bottom"
{{style bottom=this.scrollIndicatorDimensions.bottom width=this.scrollIndicatorDimensions.width}}
/>
{{/if}}
{{/unless}}

{{#if this.isEmpty}}
<div class="hds-advanced-table__empty-state">
<div class="hds-advanced-table__empty-state__content">
{{#if (has-block "emptyState")}}
{{yield to="emptyState"}}
{{else}}
<Hds::Layout::Flex @direction="column" @gap="8">
<Hds::Text::Display @tag="h3" @size="300">{{hds-t
"hds.components.advanced-table.empty-state.title"
default="No data available"
}}</Hds::Text::Display>
<Hds::Text::Body>
{{hds-t
"hds.components.advanced-table.empty-state.description"
default="There is currently no data to display in the table."
}}
</Hds::Text::Body>
</Hds::Layout::Flex>
{{/if}}
</div>
</div>
{{/if}}
</div>
17 changes: 17 additions & 0 deletions packages/components/src/components/hds/advanced-table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -149,6 +151,7 @@ export interface HdsAdvancedTableSignature {
hasStickyFirstColumn?: boolean;
childrenKey?: string;
maxHeight?: string;
isEmpty?: boolean;
onColumnReorder?: HdsAdvancedTableColumnReorderCallback;
onColumnResize?: (columnKey: string, newWidth?: string) => void;
onSelectionChange?: (
Expand All @@ -157,6 +160,11 @@ export interface HdsAdvancedTableSignature {
onSort?: (sortBy: string, sortOrder: HdsAdvancedTableThSortOrder) => void;
};
Blocks: {
actions?: [
{
FilterBar?: ComponentLike<HdsFilterBarSignature>;
},
];
body?: [
{
Td?: WithBoundArgs<typeof HdsAdvancedTableTd, 'align'>;
Expand Down Expand Up @@ -192,6 +200,7 @@ export interface HdsAdvancedTableSignature {
isOpen?: HdsAdvancedTableExpandState;
},
];
emptyState?: [];
};
Element: HTMLDivElement;
}
Expand Down Expand Up @@ -259,6 +268,14 @@ export default class HdsAdvancedTable extends Component<HdsAdvancedTableSignatur
}
}

get isEmpty(): boolean {
const { isEmpty } = this.args;
if (isEmpty !== undefined) {
return isEmpty;
}
return false;
}

get identityKey(): string | undefined {
// we have to provide a way for the consumer to pass undefined because Ember tries to interpret undefined as missing an arg and therefore falls back to the default
if (this.args.identityKey === 'none') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<li class="hds-filter-bar__filters-dropdown__filter-option">
<Hds::Form::Checkbox::Field checked={{this.isChecked}} @value={{@value}} {{on "change" this.onChange}} as |F|>
<F.Label>{{yield}}</F.Label>
</Hds::Form::Checkbox::Field>
</li>
39 changes: 39 additions & 0 deletions packages/components/src/components/hds/filter-bar/checkbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Component from '@glimmer/component';
import { action } from '@ember/object';

import type { HdsFilterBarFilter } from './types.ts';

export interface HdsFilterBarCheckboxSignature {
Args: {
value?: string;
keyFilter: HdsFilterBarFilter | undefined;
onChange?: (event: Event) => void;
};
Blocks: {
default: [];
};
Element: HTMLDivElement;
}

export default class HdsFilterBarCheckbox extends Component<HdsFilterBarCheckboxSignature> {
@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 && Array.isArray(keyFilter.data)) {
return keyFilter.data.some((filter) => filter.value === value);
}
return false;
}
}
Loading