diff --git a/ui/app/components/list-accordion.js b/ui/app/components/list-accordion.js new file mode 100644 index 000000000..9e004645d --- /dev/null +++ b/ui/app/components/list-accordion.js @@ -0,0 +1,30 @@ +import Component from '@ember/component'; +import { computed, get } from '@ember/object'; + +export default Component.extend({ + classNames: ['accordion'], + + key: 'id', + source: computed(() => []), + + decoratedSource: computed('source.[]', function() { + const stateCache = this.get('stateCache'); + const key = this.get('key'); + const deepKey = `item.${key}`; + + const decoratedSource = this.get('source').map(item => { + const cacheItem = stateCache.findBy(deepKey, get(item, key)); + return { + item, + isOpen: cacheItem ? !!cacheItem.isOpen : false, + }; + }); + + this.set('stateCache', decoratedSource); + return decoratedSource; + }), + + // When source updates come in, the state cache is used to preserve + // open/close state. + stateCache: computed(() => []), +}); diff --git a/ui/app/components/list-accordion/accordion-body.js b/ui/app/components/list-accordion/accordion-body.js new file mode 100644 index 000000000..32397fce6 --- /dev/null +++ b/ui/app/components/list-accordion/accordion-body.js @@ -0,0 +1,6 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', + isOpen: false, +}); diff --git a/ui/app/components/list-accordion/accordion-head.js b/ui/app/components/list-accordion/accordion-head.js new file mode 100644 index 000000000..4de6d4edc --- /dev/null +++ b/ui/app/components/list-accordion/accordion-head.js @@ -0,0 +1,14 @@ +import Component from '@ember/component'; + +export default Component.extend({ + classNames: ['accordion-head'], + classNameBindings: ['isOpen::is-light', 'isExpandable::is-inactive'], + + buttonLabel: 'toggle', + isOpen: false, + isExpandable: true, + item: null, + + onClose() {}, + onOpen() {}, +}); diff --git a/ui/app/styles/components.scss b/ui/app/styles/components.scss index 49b513806..83efbb676 100644 --- a/ui/app/styles/components.scss +++ b/ui/app/styles/components.scss @@ -1,3 +1,4 @@ +@import './components/accordion'; @import './components/badge'; @import './components/boxed-section'; @import './components/cli-window'; diff --git a/ui/app/styles/components/accordion.scss b/ui/app/styles/components/accordion.scss new file mode 100644 index 000000000..17ca8bcc5 --- /dev/null +++ b/ui/app/styles/components/accordion.scss @@ -0,0 +1,42 @@ +.accordion { + .accordion-head, + .accordion-body { + border: 1px solid $grey-blue; + border-bottom: none; + padding: 0.75em 1.5em; + + &:first-child { + border-top-left-radius: $radius; + border-top-right-radius: $radius; + } + + &:last-child { + border-bottom: 1px solid $grey-blue; + border-bottom-left-radius: $radius; + border-bottom-right-radius: $radius; + } + } + + .accordion-head { + display: flex; + background: $white-ter; + flex: 1; + + &.is-light { + background: $white; + } + + &.is-inactive { + color: $grey-light; + } + + .accordion-head-content { + width: 100%; + } + + .accordion-toggle { + flex-basis: 0; + white-space: nowrap; + } + } +} diff --git a/ui/app/templates/components/list-accordion.hbs b/ui/app/templates/components/list-accordion.hbs new file mode 100644 index 000000000..67678cef2 --- /dev/null +++ b/ui/app/templates/components/list-accordion.hbs @@ -0,0 +1,10 @@ +{{#each decoratedSource as |item|}} + {{yield (hash + head=(component "list-accordion/accordion-head" + isOpen=item.isOpen + onOpen=(action (mut item.isOpen) true) + onClose=(action (mut item.isOpen) false)) + body=(component "list-accordion/accordion-body" isOpen=item.isOpen) + item=item.item + )}} +{{/each}} diff --git a/ui/app/templates/components/list-accordion/accordion-body.hbs b/ui/app/templates/components/list-accordion/accordion-body.hbs new file mode 100644 index 000000000..894e66818 --- /dev/null +++ b/ui/app/templates/components/list-accordion/accordion-body.hbs @@ -0,0 +1,5 @@ +{{#if isOpen}} +