diff --git a/README.md b/README.md index 703fb1c..3cd7dc2 100644 --- a/README.md +++ b/README.md @@ -18,27 +18,26 @@ Usage Contributing ------------------------------------------------------------------------------ +This project uses docker-compose to ensure a consistant testing environment. See alternative instructions below for local testing. + ### Installation * `git clone https://github.com/limit-zero/ember-common-uikit` * `cd ember-common-uikit` -* `yarn install` +* `yarn build` ### Linting -* `yarn lint:js` -* `yarn lint:js --fix` +* `yarn lint` ### Running tests -* `ember test` – Runs the test suite on the current Ember version -* `ember test --server` – Runs the test suite in "watch mode" -* `ember try:each` – Runs the test suite against multiple Ember versions +* `yarn test` – Runs the test suite on the current Ember version ### Running the dummy application -* `ember serve` -* Visit the dummy application at [http://localhost:4200](http://localhost:4200). +* `yarn start` +* Visit the dummy application at [http://localhost:9905](http://localhost:9905). For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/addon/.gitkeep b/addon/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/addon/components/bs-modal.js b/addon/components/bs-modal.js new file mode 100644 index 0000000..55035ff --- /dev/null +++ b/addon/components/bs-modal.js @@ -0,0 +1,15 @@ +import Component from '@ember/component'; +import layout from '../templates/components/bs-modal'; + +export default Component.extend({ + layout, + to: 'bootstrap-modals', + backdrop: true, + fade: true, + show: false, + keyboard: true, + focus: true, + size: null, + contentClass: null, + dislogClass: null, +}); diff --git a/addon/components/bs-modal/body.js b/addon/components/bs-modal/body.js new file mode 100644 index 0000000..8436d64 --- /dev/null +++ b/addon/components/bs-modal/body.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/bs-modal/body'; + +export default Component.extend({ + layout, + classNames: ['modal-body'], +}); diff --git a/addon/components/bs-modal/dialog.js b/addon/components/bs-modal/dialog.js new file mode 100644 index 0000000..af37528 --- /dev/null +++ b/addon/components/bs-modal/dialog.js @@ -0,0 +1,24 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/bs-modal/dialog'; + +export default Component.extend({ + layout, + classNames: ['modal-dialog'], + classNameBindings: ['_sizeClass'], + attributeBindings: ['role'], + + role: 'document', + + size: null, + + _sizeClass: computed('size', function() { + switch (this.get('size')) { + case 'small': + return 'modal-sm'; + case 'large': + return 'modal-lg'; + default: + return null; + } + }), +}); diff --git a/addon/components/bs-modal/footer.js b/addon/components/bs-modal/footer.js new file mode 100644 index 0000000..67efbd8 --- /dev/null +++ b/addon/components/bs-modal/footer.js @@ -0,0 +1,7 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/bs-modal/footer'; + +export default Component.extend({ + layout, + classNames: ['modal-footer'], +}); diff --git a/addon/components/bs-modal/header.js b/addon/components/bs-modal/header.js new file mode 100644 index 0000000..2abad8a --- /dev/null +++ b/addon/components/bs-modal/header.js @@ -0,0 +1,12 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/bs-modal/header'; + +export default Component.extend({ + layout, + classNames: ['modal-header'], + title: null, + showClose: true, + + titleComponent: 'bs-modal/title', + closeComponent: 'bs-modal/close-icon' +}); diff --git a/addon/components/bs-modal/wrapper.js b/addon/components/bs-modal/wrapper.js new file mode 100644 index 0000000..5ceb4c2 --- /dev/null +++ b/addon/components/bs-modal/wrapper.js @@ -0,0 +1,137 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/bs-modal/wrapper'; + +export default Component.extend({ + layout, + classNames: ['modal'], + classNameBindings: ['fade'], + attributeBindings: ['role'], + + fade: true, + show: false, + role: 'dialog', + size: null, + + isShowing: false, + isShown: false, + isHiding: false, + isHidden: true, + isTransitioning: false, + + didInsertElement() { + const $obj = this.$(); + // Set the modal options. + this.setModalOptions($obj); + // Replace Bootstraps native dismiss with the internal action. + this.replaceDismiss($obj); + // Set modal event hooks + this.setModalHooks($obj); + // Show the modal, if directed to. + if (this.get('show')) this.send('show'); + }, + + willDestroyElement() { + const $obj = this.$(); + if (this.get('isShown')) { + // The modal was closed by sending `show=false` and it's still open. + // Remove internal events and then natively hide the modal and disposed once hidden. + $obj.off('hidden.bs.modal') + $obj.on('hidden.bs.modal', () => { + this.sendEvent('onHidden'); + $obj.modal('dispose'); + }); + $obj.modal('hide'); + } else { + $obj.modal('dispose'); + } + }, + + actions: { + show() { + if (this.get('isTransitioning')) return; + this.$().modal('show'); + }, + + hide() { + if (this.get('isTransitioning')) return; + this.$().modal('hide'); + }, + }, + + setModalOptions($obj) { + const keys = ['backdrop', 'keyboard', 'focus']; + const options = keys.reduce((opts, key) => { + const value = this.get(key); + if (isPresent(value)) opts[key] = value; + return opts; + }, { show: false }); + $obj.modal(options); + }, + + replaceDismiss($obj) { + // Turn off Bootstrap's native dismissing of the modal (via a click from a`[data-dismiss="modal"]` element or by clicking the backdrop) + $obj.off('click.dismiss.bs.modal'); + // Replace with the Ember hide() action. + $obj.on('click.dismiss.bs.modal', (event) => { + if ($(event.currentTarget).is(event.target) && true === this.get('backdrop')) { + this.send('hide'); + } + }); + }, + + resetShowing() { + this.set('isShowing', false); + this.set('isShown', false); + }, + + resetHiding() { + this.set('isHiding', false); + this.set('isHidden', false); + }, + + setModalHooks($obj) { + // This event fires immediately when the show instance method is called. + // If caused by a click, the clicked element is available as the relatedTarget property of the event. + $obj.on('show.bs.modal', () => { + this.resetHiding(); + this.set('isTransitioning', true); + this.set('isShowing', true); + this.set('isShown', false); + this.sendEvent('onShow'); + + }); + + // This event is fired when the modal has been made visible to the user (will wait for CSS transitions to complete). + // If caused by a click, the clicked element is available as the relatedTarget property of the event. + $obj.on('shown.bs.modal', () => { + this.set('isShown', true); + this.set('isShowing', false); + this.set('isTransitioning', false); + this.sendEvent('onShown'); + }); + + // This event is fired immediately when the hide instance method has been called. + $obj.on('hide.bs.modal', () => { + this.resetShowing(); + this.set('isTransitioning', true); + this.set('isHiding', true); + this.set('isHidden', false); + this.sendEvent('onHide'); + }); + + // This event is fired when the modal has finished being hidden from the user (will wait for CSS transitions to complete). + $obj.on('hidden.bs.modal', () => { + this.set('isHidden', true); + this.set('isHiding', false); + this.set('isTransitioning', false); + this.sendEvent('onHidden'); + if (!this.get('isDestroyed')) this.set('show', false); + }); + }, + + sendEvent(name) { + const fn = this.get(name); + if (fn && typeof fn === 'function') return fn(); + }, + +}); diff --git a/addon/components/confirm-button.js b/addon/components/confirm-button.js new file mode 100644 index 0000000..5460dd8 --- /dev/null +++ b/addon/components/confirm-button.js @@ -0,0 +1,31 @@ +import Component from '@ember/component'; +import layout from '../templates/components/confirm-button'; + +export default Component.extend({ + layout, + tagName: 'button', + classNames: ['btn', 'clickable'], + attributeBindings: ['disabled', 'type'], + + disabled: false, + type: 'button', + + icon: '', + label: 'Action', + confirmLabel: 'You Sure?', + onConfirm: null, + + hasConfirmed: false, + + click() { + if (this.get('hasConfirmed')) { + this.get('onConfirm')(); + } else { + this.set('hasConfirmed', true); + } + }, + + focusOut() { + this.set('hasConfirmed', false); + }, +}); diff --git a/addon/components/fetch-more.js b/addon/components/fetch-more.js new file mode 100644 index 0000000..f090637 --- /dev/null +++ b/addon/components/fetch-more.js @@ -0,0 +1,82 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { assign } from '@ember/polyfills'; +import { isArray } from '@ember/array'; +import layout from '../templates/components/fetch-more'; + +export default Component.extend({ + layout, + + /** + * The Apollo client query observable. + * @type {Observable} + */ + query: null, + + hasNextPage: false, + endCursor: null, + resultKey: null, + + isFetching: false, + + nodes: computed('edges.@each.node', function() { + const edges = this.get('edges'); + if (!isArray(edges)) return []; + return edges.map(edge => edge.node); + }), + + hasEvent(name) { + const fn = this.get(name); + return fn && typeof fn === 'function'; + }, + + sendEvent(name, ...args) { + if (this.hasEvent(name)) this.get(name)(...args, this); + }, + + actions: { + /** + * Fetches more results using the observable from the original query. + * @see https://www.apollographql.com/docs/react/features/pagination.html + */ + async fetchMore() { + this.set('isFetching', true); + this.sendEvent('on-fetch-start'); + const observable = this.get('query'); + const endCursor = this.get('endCursor'); + const resultKey = this.get('resultKey'); + + const updateQuery = (previous, { fetchMoreResult }) => { + const { edges, pageInfo, totalCount } = fetchMoreResult[resultKey]; + if (edges.length) { + return { + [resultKey]: { + __typename: previous[resultKey].__typename, + totalCount, + edges: [...previous[resultKey].edges, ...edges], + pageInfo, + }, + }; + } + return previous; + }; + const pagination = assign({}, observable.variables.pagination, { after: endCursor }); + const variables = { pagination }; + try { + const result = await observable.fetchMore({ updateQuery, variables }); + this.sendEvent('on-fetch-success', result); + return result; + } catch (e) { + const evt = 'on-fetch-error'; + if (this.hasEvent(evt)) { + this.sendEvent(evt, e); + } else { + throw e; + } + } finally { + this.set('isFetching', false); + this.sendEvent('on-fetch-end'); + } + }, + }, +}); diff --git a/addon/components/list-controls/button.js b/addon/components/list-controls/button.js new file mode 100644 index 0000000..9c4bba0 --- /dev/null +++ b/addon/components/list-controls/button.js @@ -0,0 +1,13 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/list-controls/button'; + +export default Component.extend({ + layout, + + tagName: 'button', + classNames: ['btn'], + attributeBindings: ['type', 'disabled', 'data-toggle', 'aria-haspopup', 'aria-expanded'], + + type: 'button', + disabled: false, +}); diff --git a/addon/components/list-controls/limit.js b/addon/components/list-controls/limit.js new file mode 100644 index 0000000..3e7ce8d --- /dev/null +++ b/addon/components/list-controls/limit.js @@ -0,0 +1,52 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/list-controls/limit'; + +export default Component.extend({ + layout, + classNames: ['btn-group'], + attributeBindings: ['role', 'aria-label'], + + role: 'group', + 'aria-label': 'Display Limit Filter', + + /** + * The limit number, e.g. `25`. + * @public + * @type {number} + */ + limit: null, + + /** + * The label to display before the limit number. + * @public + * @type {string} + */ + label: 'Show:', + + /** + * Whether the limit dropdown control is completely disabled. + * @public + * @type {boolean} + */ + disabled: false, + + /** + * The class to apply to buttons within this group + * @public + * @type {string} + */ + buttonClass: 'btn-primary', + + /** + * Displays filtered limit options by removing the currently selected `limit` value. + */ + filteredOptions: computed('options', 'limit', function() { + return this.get('options').reject(item => item === this.get('limit')); + }), + + actions: { + setLimit(value) { + this.set('limit', value); + }, + }, +}); diff --git a/addon/components/list-controls/menu-mixin.js b/addon/components/list-controls/menu-mixin.js new file mode 100644 index 0000000..54d2e53 --- /dev/null +++ b/addon/components/list-controls/menu-mixin.js @@ -0,0 +1,47 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/list-controls/menu-mixin'; + +export default Component.extend({ + layout, + /** + * The dropdown menu direction. + * An empty value will use the default `down` direction. + * Can be one of `up`, `right` or `left`. + * @public + * @type {string} + */ + direction: null, + + /** + * Aligns the menu. + * An empty value will use the default `left` alignment. + * A value of `right` will right-align the menu. + * @public + * @type {string} + */ + alignment: null, + + /** + * Determines the menu direction class based off the `direction` property. + */ + directionClass: computed('direction', function() { + switch (this.get('direction')) { + case 'up': + return 'dropup'; + case 'left': + return 'dropleft'; + case 'right': + return 'dropright'; + default: + return ''; + } + }), + + /** + * Determines the menu alignment class based off the `alignment` property. + */ + alignmentClass: computed('alignment', function() { + if (this.get('alignment') === 'right') return 'dropdown-menu-right'; + return ''; + }), +}); diff --git a/addon/components/list-controls/search.js b/addon/components/list-controls/search.js new file mode 100644 index 0000000..285787b --- /dev/null +++ b/addon/components/list-controls/search.js @@ -0,0 +1,86 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/list-controls/search'; + +export default Component.extend({ + layout, + tagName: 'form', + + notify: inject(), + + submit(event) { + event.preventDefault(); + event.stopPropagation(); + // @todo Eventually enable this. + this.get('notify').warning('Search is not enabled... yet. Stay tuned.'); + + // const onSubmit = this.get('onSubmit'); + // if (onSubmit) onSubmit(this.get('value')); + }, + + /** + * The initial search phrase input value. + * This component will *not* directly manipulate this value. + * Instead, the new value can be retrieve when the search control is submitted. + * @public + * @type {string} + */ + phrase: null, + + /** + * The icon to display in the search button. + * @public + * @type {string} + */ + icon: 'magnifying-glass', + + /** + * The label to display in the search button. + * @public + * @type {string} + */ + label: null, + + /** + * The search field placeholder value. + * @public + * @type {string} + */ + placeholder: 'Search...', + + /** + * Whether to select all of the text in the search box when focusd. + * @public + * @type {boolean} + */ + selectAllOnFocus: true, + + /** + * Whether the search control is completely disabled. + * @public + * @type {boolean} + */ + disabled: false, + + /** + * The class to apply to buttons within this control. + * @public + * @type {string} + */ + buttonClass: 'btn-primary', + + init() { + this._super(...arguments); + this.set('value', this.get('phrase')); + }, + + actions: { + selectAll(event) { + if (this.get('selectAllOnFocus')) event.target.select(); + }, + clear() { + this.set('value', ''); + const onSubmit = this.get('onSubmit'); + if (onSubmit) onSubmit(''); + }, + }, +}); diff --git a/addon/components/list-controls/sort.js b/addon/components/list-controls/sort.js new file mode 100644 index 0000000..5ccc784 --- /dev/null +++ b/addon/components/list-controls/sort.js @@ -0,0 +1,77 @@ +import Component from '@ember/component'; +import layout from '../../templates/components/list-controls/sort'; +import MenuMixin from './menu-mixin'; + +export default Component.extend({ + layout, + classNames: ['btn-group'], + attributeBindings: ['role', 'aria-label'], + + role: 'group', + 'aria-label': 'Sort filter', + + /** + * The sortBy field value, e.g. `createdAt` or `name`. + * @public + * @type {string} + */ + sortBy: null, + + /** + * Whether the sort is ascending. A false value signifies descending. + * @public + * @type {boolean} + */ + ascending: true, + + /** + * Whether the sort dropdown control is completely disabled. + * @public + * @type {boolean} + */ + disabled: false, + + /** + * The class to apply to buttons within this group + * @public + * @type {string} + */ + buttonClass: 'btn-primary', + + /** + * Based on the `sortBy` value, computes the selected sort object. + * For example, if the `sortBy` value equals `createdAt`, this would return + * something like `{ key: 'createdAt', label: 'Created' }`. + */ + selected: computed('options.[]', 'sortBy', function() { + return this.get('options').findBy('key', this.get('sortBy')); + }), + + /** + * Displays filtered sort options by removing the currently selected `sortBy` value. + * Returns an array of sort option objects. + */ + filteredOptions: computed('options.[]', 'sortBy', function() { + return this.get('options').rejectBy('key', this.get('sortBy')); + }), + + /** + * Initializes the component. + * If the `options` property is not an array, it will set it as an empty array. + */ + init() { + this._super(...arguments); + if (!isArray(this.get('options'))) { + this.set('options', []); + } + }, + + actions: { + toggleAscending() { + this.toggleProperty('ascending'); + }, + sortBy(key) { + this.set('sortBy', key); + }, + }, +}); diff --git a/addon/templates/components/bs-modal.hbs b/addon/templates/components/bs-modal.hbs new file mode 100644 index 0000000..bf5e80e --- /dev/null +++ b/addon/templates/components/bs-modal.hbs @@ -0,0 +1,19 @@ +{{#if show}} + {{#ember-wormhole to=to}} + {{#bs-modal/wrapper + fade=fade + backdrop=backdrop + keyboard=keyboard + focus=focus + show=show + size=size + contentClass=contentClass + dialogClass=dialogClass + onHidden=onHidden + as |wrapper|}} + {{yield wrapper}} + {{/bs-modal/wrapper}} + {{/ember-wormhole}} +{{/if}} +{{!-- onHide=onHide +onHidden=onHidden --}} diff --git a/addon/templates/components/bs-modal/body.hbs b/addon/templates/components/bs-modal/body.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/bs-modal/body.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/bs-modal/dialog.hbs b/addon/templates/components/bs-modal/dialog.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/bs-modal/dialog.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/bs-modal/footer.hbs b/addon/templates/components/bs-modal/footer.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/bs-modal/footer.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/bs-modal/header.hbs b/addon/templates/components/bs-modal/header.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/bs-modal/header.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/bs-modal/wrapper.hbs b/addon/templates/components/bs-modal/wrapper.hbs new file mode 100644 index 0000000..7b94edf --- /dev/null +++ b/addon/templates/components/bs-modal/wrapper.hbs @@ -0,0 +1,13 @@ +{{#bs-modal/dialog size=size class=dialogClass}} + +{{/bs-modal/dialog}} diff --git a/addon/templates/components/confirm-button.hbs b/addon/templates/components/confirm-button.hbs new file mode 100644 index 0000000..fa331d5 --- /dev/null +++ b/addon/templates/components/confirm-button.hbs @@ -0,0 +1 @@ +{{#if icon}}{{entypo-icon icon}} {{/if}}{{#unless hasConfirmed}}{{ label }}{{else}}{{ confirmLabel }}{{/unless}} diff --git a/addon/templates/components/fetch-more.hbs b/addon/templates/components/fetch-more.hbs new file mode 100644 index 0000000..40b0807 --- /dev/null +++ b/addon/templates/components/fetch-more.hbs @@ -0,0 +1,8 @@ +{{yield (hash + nodes=nodes + hasNextPage=hasNextPage + isFetching=isFetching + actions=(hash + loadMore=(action "fetchMore") + ) +)}} diff --git a/addon/templates/components/list-controls/button.hbs b/addon/templates/components/list-controls/button.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/list-controls/button.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/list-controls/limit.hbs b/addon/templates/components/list-controls/limit.hbs new file mode 100644 index 0000000..8c83a0b --- /dev/null +++ b/addon/templates/components/list-controls/limit.hbs @@ -0,0 +1,18 @@ +{{#if options.length}} + {{#list-controls/button + id="dropdown-display-limit" + disabled=disabled + class=(concat "dropdown-toggle" " " buttonClass) + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + }} + {{label}} {{limit}} + {{/list-controls/button}} + + +{{/if}} diff --git a/addon/templates/components/list-controls/menu-mixin.hbs b/addon/templates/components/list-controls/menu-mixin.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/templates/components/list-controls/menu-mixin.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/templates/components/list-controls/search.hbs b/addon/templates/components/list-controls/search.hbs new file mode 100644 index 0000000..fd322b3 --- /dev/null +++ b/addon/templates/components/list-controls/search.hbs @@ -0,0 +1,18 @@ +
+ {{input + type="search" + class="form-control" + placeholder=placeholder + value=value + disabled=disabled + focusIn=(action "selectAll") + }} +
+ {{#list-controls/button class=buttonClass type="submit" disabled=disabled}} + {{entypo-icon icon}}{{#if label}} {{label}}{{/if}} + {{/list-controls/button}} + {{#list-controls/button class=buttonClass disabled=disabled click=(action "clear")}} + {{entypo-icon "cross"}} + {{/list-controls/button}} +
+
diff --git a/addon/templates/components/list-controls/sort.hbs b/addon/templates/components/list-controls/sort.hbs new file mode 100644 index 0000000..05fc713 --- /dev/null +++ b/addon/templates/components/list-controls/sort.hbs @@ -0,0 +1,23 @@ +{{#if options.length}} +
+ {{#list-controls/button + id="dropdown-sort-type" + disabled=disabled + class=(concat "dropdown-toggle" " " buttonClass) + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + }} + {{selected.label}} + {{/list-controls/button}} + + +
+ {{#list-controls/button class=buttonClass disabled=disabled click=(action "toggleAscending")}} + {{entypo-icon (if ascending "arrow-up" "arrow-down")}} + {{/list-controls/button}} +{{/if}} diff --git a/app/.gitkeep b/app/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/components/bs-modal.js b/app/components/bs-modal.js new file mode 100644 index 0000000..c5af90f --- /dev/null +++ b/app/components/bs-modal.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal'; \ No newline at end of file diff --git a/app/components/bs-modal/body.js b/app/components/bs-modal/body.js new file mode 100644 index 0000000..5af89a6 --- /dev/null +++ b/app/components/bs-modal/body.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal/body'; \ No newline at end of file diff --git a/app/components/bs-modal/dialog.js b/app/components/bs-modal/dialog.js new file mode 100644 index 0000000..c9b33a4 --- /dev/null +++ b/app/components/bs-modal/dialog.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal/dialog'; \ No newline at end of file diff --git a/app/components/bs-modal/footer.js b/app/components/bs-modal/footer.js new file mode 100644 index 0000000..149a2b5 --- /dev/null +++ b/app/components/bs-modal/footer.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal/footer'; \ No newline at end of file diff --git a/app/components/bs-modal/header.js b/app/components/bs-modal/header.js new file mode 100644 index 0000000..62080a2 --- /dev/null +++ b/app/components/bs-modal/header.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal/header'; \ No newline at end of file diff --git a/app/components/bs-modal/wrapper.js b/app/components/bs-modal/wrapper.js new file mode 100644 index 0000000..d56f4f1 --- /dev/null +++ b/app/components/bs-modal/wrapper.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/bs-modal/wrapper'; \ No newline at end of file diff --git a/app/components/confirm-button.js b/app/components/confirm-button.js new file mode 100644 index 0000000..b47e377 --- /dev/null +++ b/app/components/confirm-button.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/confirm-button'; \ No newline at end of file diff --git a/app/components/fetch-more.js b/app/components/fetch-more.js new file mode 100644 index 0000000..d24b800 --- /dev/null +++ b/app/components/fetch-more.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/fetch-more'; \ No newline at end of file diff --git a/app/components/list-controls/button.js b/app/components/list-controls/button.js new file mode 100644 index 0000000..91bde57 --- /dev/null +++ b/app/components/list-controls/button.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/list-controls/button'; \ No newline at end of file diff --git a/app/components/list-controls/limit.js b/app/components/list-controls/limit.js new file mode 100644 index 0000000..a4609db --- /dev/null +++ b/app/components/list-controls/limit.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/list-controls/limit'; \ No newline at end of file diff --git a/app/components/list-controls/menu-mixin.js b/app/components/list-controls/menu-mixin.js new file mode 100644 index 0000000..7e85fc6 --- /dev/null +++ b/app/components/list-controls/menu-mixin.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/list-controls/menu-mixin'; \ No newline at end of file diff --git a/app/components/list-controls/search.js b/app/components/list-controls/search.js new file mode 100644 index 0000000..34dac57 --- /dev/null +++ b/app/components/list-controls/search.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/list-controls/search'; \ No newline at end of file diff --git a/app/components/list-controls/sort.js b/app/components/list-controls/sort.js new file mode 100644 index 0000000..3b40fac --- /dev/null +++ b/app/components/list-controls/sort.js @@ -0,0 +1 @@ +export { default } from '@limit0/ember-common-uikit/components/list-controls/sort'; \ No newline at end of file diff --git a/index.js b/index.js index 13bf1d0..c0da2dc 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ 'use strict'; module.exports = { - name: 'ember-common-uikit' + name: '@limit0/ember-common-uikit' }; diff --git a/package.json b/package.json index c04d4c3..916cf7f 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,43 @@ }, "repository": "https://github.com/limit-zero/ember-common-uikit", "scripts": { - "build": "ember build", - "lint:js": "eslint ./*.js addon addon-test-support app blueprints config lib server test-support tests", - "start": "ember serve", - "test": "ember test", - "test:all": "ember try:each" + "pretest": "yarn run posttest && $npm_package_docker_test_install", + "posttest": "$npm_package_docker_test_down", + "test": "$npm_package_docker_test_test", + "terminal": "docker exec -it embercommonuikit_app_1 /bin/bash", + "prestart": "$npm_package_docker_dev_install", + "prebuild": "$npm_package_docker_dev_install", + "build": "$npm_package_docker_dev_build", + "postbuild": "$npm_package_docker_dev_down", + "start": "$npm_package_docker_dev_up", + "stop": "$npm_package_docker_dev_down", + "prelint": "$npm_package_docker_test_install", + "lint": "$npm_package_docker_test_lint", + "postlint": "$npm_package_docker_test_down", + "precoverage": "yarn run postcoverage && $npm_package_docker_test_install", + "coverage": "$npm_package_docker_test_coverage", + "postcoverage": "$npm_package_docker_test_down", + "test:ci": "yarn run lint:run && ./node_modules/.bin/ember test --reporter xunit --silent", + "test:run": "yarn run lint:run && ./node_modules/.bin/ember test", + "build:run": "./node_modules/.bin/ember build", + "start:run": "./node_modules/.bin/ember serve", + "lint:run": "./node_modules/.bin/eslint ./*.js addon addon-test-support app blueprints config lib server test-support tests", + "coverage:run": "./node_modules/.bin/nyc yarn run test:ci" + }, + "docker": { + "dev": { + "install": "docker-compose -p embercommonuikit run --no-deps --entrypoint yarn app install", + "up": "docker-compose -p embercommonuikit up", + "down": "docker-compose -p embercommonuikit down", + "build": "docker-compose -p embercommonuikit run --entrypoint yarn app build:run" + }, + "test": { + "install": "docker-compose -p embercommonuikittest -f tests/docker-compose.yml run --no-deps --entrypoint yarn test", + "test": "docker-compose -p embercommonuikittest -f tests/docker-compose.yml run test", + "coverage": "docker-compose -p embercommonuikittest -f tests/docker-compose.yml run --entrypoint yarn test run coverage:run", + "lint": "docker-compose -p embercommonuikittest -f tests/docker-compose.yml run --entrypoint yarn test run lint:run", + "down": "docker-compose -p embercommonuikittest -f tests/docker-compose.yml down" + } }, "dependencies": { "ember-cli-babel": "^6.6.0" diff --git a/tests/integration/components/bs-modal-test.js b/tests/integration/components/bs-modal-test.js new file mode 100644 index 0000000..6bcdb65 --- /dev/null +++ b/tests/integration/components/bs-modal-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal}} + template block text + {{/bs-modal}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/bs-modal/body-test.js b/tests/integration/components/bs-modal/body-test.js new file mode 100644 index 0000000..5c99c36 --- /dev/null +++ b/tests/integration/components/bs-modal/body-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal/body', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal/body}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal/body}} + template block text + {{/bs-modal/body}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/bs-modal/dialog-test.js b/tests/integration/components/bs-modal/dialog-test.js new file mode 100644 index 0000000..091e133 --- /dev/null +++ b/tests/integration/components/bs-modal/dialog-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal/dialog', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal/dialog}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal/dialog}} + template block text + {{/bs-modal/dialog}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/bs-modal/footer-test.js b/tests/integration/components/bs-modal/footer-test.js new file mode 100644 index 0000000..0e7e1f2 --- /dev/null +++ b/tests/integration/components/bs-modal/footer-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal/footer', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal/footer}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal/footer}} + template block text + {{/bs-modal/footer}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/bs-modal/header-test.js b/tests/integration/components/bs-modal/header-test.js new file mode 100644 index 0000000..abd28fa --- /dev/null +++ b/tests/integration/components/bs-modal/header-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal/header', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal/header}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal/header}} + template block text + {{/bs-modal/header}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/bs-modal/wrapper-test.js b/tests/integration/components/bs-modal/wrapper-test.js new file mode 100644 index 0000000..7f5ca9d --- /dev/null +++ b/tests/integration/components/bs-modal/wrapper-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | bs-modal/wrapper', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{bs-modal/wrapper}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#bs-modal/wrapper}} + template block text + {{/bs-modal/wrapper}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/confirm-button-test.js b/tests/integration/components/confirm-button-test.js new file mode 100644 index 0000000..0ceaf91 --- /dev/null +++ b/tests/integration/components/confirm-button-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | confirm-button', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{confirm-button}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#confirm-button}} + template block text + {{/confirm-button}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/fetch-more-test.js b/tests/integration/components/fetch-more-test.js new file mode 100644 index 0000000..a479e03 --- /dev/null +++ b/tests/integration/components/fetch-more-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | fetch-more', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{fetch-more}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#fetch-more}} + template block text + {{/fetch-more}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/list-controls/button-test.js b/tests/integration/components/list-controls/button-test.js new file mode 100644 index 0000000..0784958 --- /dev/null +++ b/tests/integration/components/list-controls/button-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | list-controls/button', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{list-controls/button}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#list-controls/button}} + template block text + {{/list-controls/button}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/list-controls/limit-test.js b/tests/integration/components/list-controls/limit-test.js new file mode 100644 index 0000000..898db19 --- /dev/null +++ b/tests/integration/components/list-controls/limit-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | list-controls/limit', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{list-controls/limit}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#list-controls/limit}} + template block text + {{/list-controls/limit}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/list-controls/menu-mixin-test.js b/tests/integration/components/list-controls/menu-mixin-test.js new file mode 100644 index 0000000..6047f7c --- /dev/null +++ b/tests/integration/components/list-controls/menu-mixin-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | list-controls/menu-mixin', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{list-controls/menu-mixin}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#list-controls/menu-mixin}} + template block text + {{/list-controls/menu-mixin}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/list-controls/search-test.js b/tests/integration/components/list-controls/search-test.js new file mode 100644 index 0000000..dde7f3e --- /dev/null +++ b/tests/integration/components/list-controls/search-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | list-controls/search', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{list-controls/search}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#list-controls/search}} + template block text + {{/list-controls/search}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +}); diff --git a/tests/integration/components/list-controls/sort-test.js b/tests/integration/components/list-controls/sort-test.js new file mode 100644 index 0000000..37f42f2 --- /dev/null +++ b/tests/integration/components/list-controls/sort-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | list-controls/sort', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{list-controls/sort}}`); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + {{#list-controls/sort}} + template block text + {{/list-controls/sort}} + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); +});