@@ -17,8 +41,8 @@
{{#let components.Optgroup components.Option as |Optgroup Option|}}
-
-
+
+
{{/let}}
@@ -28,7 +52,7 @@
class="type-sort"
data-test-sort-control
@position="right"
- @onchange={{action onsort}}
+ @onchange={{action @onsort}}
@multiple={{false}}
as |components|>
@@ -41,19 +65,19 @@
))
as |selectable|
}}
- {{get selectable sort}}
+ {{get selectable @sort}}
{{/let}}
{{#let components.Optgroup components.Option as |Optgroup Option|}}
{{/let}}
diff --git a/ui/packages/consul-ui/app/components/consul/upstream/search-bar/index.js b/ui/packages/consul-ui/app/components/consul/upstream/search-bar/index.js
deleted file mode 100644
index 4798652642..0000000000
--- a/ui/packages/consul-ui/app/components/consul/upstream/search-bar/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import Component from '@ember/component';
-
-export default Component.extend({
- tagName: '',
-});
diff --git a/ui/packages/consul-ui/app/components/data-collection/index.hbs b/ui/packages/consul-ui/app/components/data-collection/index.hbs
new file mode 100644
index 0000000000..45e6c381c9
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/data-collection/index.hbs
@@ -0,0 +1,5 @@
+{{yield (hash
+ items=this.items
+ Collection=(if (gt this.items.length 0) (component 'anonymous') '')
+ Empty=(if (eq this.items.length 0) (component 'anonymous') '')
+)}}
diff --git a/ui/packages/consul-ui/app/components/data-collection/index.js b/ui/packages/consul-ui/app/components/data-collection/index.js
new file mode 100644
index 0000000000..fcde9b9f16
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/data-collection/index.js
@@ -0,0 +1,56 @@
+import Component from '@glimmer/component';
+import { inject as service } from '@ember/service';
+import { sort } from '@ember/object/computed';
+import { defineProperty } from '@ember/object';
+
+export default class DataCollectionComponent extends Component {
+ @service('filter') filter;
+ @service('sort') sort;
+ @service('search') search;
+
+ get type() {
+ return this.args.type;
+ }
+
+ get items() {
+ // the ember sort computed accepts either:
+ // 1. The name of a property (as a string) returning an array properties to sort by
+ // 2. A function to use for sorting
+ let comparator = 'comparator';
+ if (typeof this.comparator === 'function') {
+ comparator = this.comparator;
+ }
+ defineProperty(this, 'sorted', sort('searched', comparator));
+ return this.sorted;
+ }
+
+ get searched() {
+ if (typeof this.args.search === 'undefined') {
+ return this.filtered;
+ }
+ const predicate = this.search.predicate(this.type);
+ const options = {};
+ if (typeof this.args.filters.searchproperties !== 'undefined') {
+ options.properties = this.args.filters.searchproperties;
+ }
+ return this.filtered.filter(predicate(this.args.search, options));
+ }
+
+ get filtered() {
+ if (typeof this.args.filters === 'undefined') {
+ return this.args.items;
+ }
+ const predicate = this.filter.predicate(this.type);
+ if (typeof predicate === 'undefined') {
+ return this.args.items;
+ }
+ return this.args.items.filter(predicate(this.args.filters));
+ }
+
+ get comparator() {
+ if (typeof this.args.sort === 'undefined') {
+ return [];
+ }
+ return this.sort.comparator(this.type)(this.args.sort);
+ }
+}
diff --git a/ui/packages/consul-ui/app/components/filter-bar/index.scss b/ui/packages/consul-ui/app/components/filter-bar/index.scss
new file mode 100644
index 0000000000..1e3f2d0f88
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/filter-bar/index.scss
@@ -0,0 +1,5 @@
+@import './skin';
+@import './layout';
+%filter-bar-reversed {
+ color: inherit;
+}
diff --git a/ui/packages/consul-ui/app/components/filter-bar/layout.scss b/ui/packages/consul-ui/app/components/filter-bar/layout.scss
new file mode 100644
index 0000000000..eb9019ea0e
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/filter-bar/layout.scss
@@ -0,0 +1,45 @@
+.filter-bar {
+ &,
+ > div {
+ display: flex;
+ }
+ & {
+ padding: 4px 8px;
+ }
+ .sort {
+ margin-left: auto;
+ }
+ .popover-select {
+ position: relative;
+ z-index: 3;
+ }
+ .popover-menu > [type='checkbox'] + label button {
+ padding-left: 1.5rem !important;
+ padding-right: 1.5rem !important;
+
+ }
+ .popover-menu [role='menuitem'] {
+ justify-content: normal !important;
+ }
+}
+@media #{$--lt-horizontal-filters} {
+ .filter-bar {
+ &,
+ & > div {
+ flex-wrap: wrap;
+ }
+ .search {
+ position: relative;
+ z-index: 4;
+ width: 100%;
+ margin-bottom: .3rem;
+ }
+ }
+}
+@media #{$--lt-horizontal-selects} {
+ .filter-bar {
+ .filters, .sort {
+ display: none;
+ }
+ }
+}
diff --git a/ui/packages/consul-ui/app/components/filter-bar/skin.scss b/ui/packages/consul-ui/app/components/filter-bar/skin.scss
new file mode 100644
index 0000000000..49feb3c5e0
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/filter-bar/skin.scss
@@ -0,0 +1,13 @@
+.filter-bar {
+ & {
+ background-color: $gray-010;
+ border-bottom: $decor-border-100;
+ border-color: $gray-200;
+ }
+ .filters, .sort {
+ .popover-menu > [type='checkbox']:checked + label button {
+ color: $blue-500;
+ background-color: $gray-100;
+ }
+ }
+}
diff --git a/ui/packages/consul-ui/app/components/freetext-filter/README.stories.mdx b/ui/packages/consul-ui/app/components/freetext-filter/README.stories.mdx
new file mode 100644
index 0000000000..009a68ec3e
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/freetext-filter/README.stories.mdx
@@ -0,0 +1,45 @@
+import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
+import { hbs } from 'ember-cli-htmlbars';
+
+
+
+# FreetextFilter
+
+
+
diff --git a/ui/packages/consul-ui/app/components/freetext-filter/index.hbs b/ui/packages/consul-ui/app/components/freetext-filter/index.hbs
index 8ec2ffde24..8c70ff8b89 100644
--- a/ui/packages/consul-ui/app/components/freetext-filter/index.hbs
+++ b/ui/packages/consul-ui/app/components/freetext-filter/index.hbs
@@ -1,6 +1,19 @@
-