diff --git a/addon/components/o-s-s/empty-state.hbs b/addon/components/o-s-s/empty-state.hbs new file mode 100644 index 00000000..cdb90ed7 --- /dev/null +++ b/addon/components/o-s-s/empty-state.hbs @@ -0,0 +1,18 @@ +
+ {{#if (has-block "image")}} + {{yield to="image"}} + {{else if @badgeIcon}} + + {{/if}} + +
+ {{@title}} +
+
+ {{@subtitle}} +
+
+ {{#if (has-block "actions")}} + {{yield to="actions"}} + {{/if}} +
\ No newline at end of file diff --git a/addon/components/o-s-s/empty-state.stories.js b/addon/components/o-s-s/empty-state.stories.js new file mode 100644 index 00000000..5277e950 --- /dev/null +++ b/addon/components/o-s-s/empty-state.stories.js @@ -0,0 +1,95 @@ +import { hbs } from 'ember-cli-htmlbars'; + +export default { + title: 'Components/OSS::EmptyState', + component: 'empty state', + argTypes: { + badgeIcon: { + description: 'A font-awesome icon to be displayed in a badge', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'text' } + }, + title: { + description: 'A title displayed below the icon or badge in the component', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'text' } + }, + subtitle: { + description: 'A subtitle displayed under the title in the component', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'text' } + }, + size: { + description: 'The size of the state', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'select' }, + options: ['sm', 'md'] + } + }, + parameters: { + docs: { + description: { + component: 'A component used when there is nothing to display on a page' + } + } + } +}; + +const defaultArgs = { + badgeIcon: 'fa-thumbs-up', + title: 'Empty State Title', + subtitle: 'Additional information here', + size: 'md' +}; + +const Template = (args) => ({ + template: hbs``, + context: args +}); + +const ImageTemplate = (args) => ({ + template: hbs` + <:image> + + + `, + context: args +}); + +const ActionTemplate = (args) => ({ + template: hbs` + <:actions> + + + `, + context: args +}); + +export const Default = Template.bind({}); +Default.args = defaultArgs; + +export const UsageWithImage = ImageTemplate.bind({}); +UsageWithImage.args = defaultArgs; + +export const UsageWithActions = ActionTemplate.bind({}); +UsageWithActions.args = defaultArgs; diff --git a/addon/components/o-s-s/empty-state.ts b/addon/components/o-s-s/empty-state.ts new file mode 100644 index 00000000..59016f01 --- /dev/null +++ b/addon/components/o-s-s/empty-state.ts @@ -0,0 +1,29 @@ +import type { htmlSafe } from '@ember/template'; +import { assert } from '@ember/debug'; +import Component from '@glimmer/component'; + +interface OSSEmptyStateComponentSignature { + badgeIcon?: string; + title: ReturnType; + subtitle: ReturnType; + size?: 'sm' | 'md'; +} + +const ALLOWED_SIZES: string[] = ['sm', 'md']; + +export default class OSSEmptyStateComponent extends Component { + constructor(owner: unknown, args: OSSEmptyStateComponentSignature) { + super(owner, args); + + assert('[component][OSS::EmptyState] The title parameter is mandatory', typeof args.title === 'string'); + assert('[component][OSS::EmptyState] The subtitle parameter is mandatory', typeof args.subtitle === 'string'); + } + + get titleSize(): string { + return this.size === 'sm' ? 'md' : 'lg'; + } + + get size(): string { + return this.args.size && ALLOWED_SIZES.includes(this.args.size) ? this.args.size : 'md'; + } +} diff --git a/app/components/o-s-s/empty-state.js b/app/components/o-s-s/empty-state.js new file mode 100644 index 00000000..7f68cc78 --- /dev/null +++ b/app/components/o-s-s/empty-state.js @@ -0,0 +1 @@ +export { default } from '@upfluence/oss-components/components/o-s-s/empty-state'; diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs index c699a333..09c48316 100644 --- a/tests/dummy/app/templates/index.hbs +++ b/tests/dummy/app/templates/index.hbs @@ -276,4 +276,44 @@ +
+
+ Empty state +
+
+ + <:image> + + + <:actions> + + + + + + <:actions> + + + + + + <:image> + + + <:actions> + + + + + + <:actions> + + + + +
+
+ \ No newline at end of file diff --git a/tests/integration/components/o-s-s/empty-state-test.ts b/tests/integration/components/o-s-s/empty-state-test.ts new file mode 100644 index 00000000..d0a5e201 --- /dev/null +++ b/tests/integration/components/o-s-s/empty-state-test.ts @@ -0,0 +1,96 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, setupOnerror } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | o-s-s/empty-state', function (hooks) { + setupRenderingTest(hooks); + + test('it renders with default properties', async function (assert) { + await render(hbs``); + + assert.dom('div.font-color-gray-900').hasText('No Data'); + assert.dom('div.font-color-gray-500').hasText('Try again later'); + }); + + test('it renders with a badge icon', async function (assert) { + this.set('image', 'fa-thumbs-up'); + await render(hbs``); + + assert.dom('div.font-color-gray-900').hasText('No Data'); + assert.dom('div.font-color-gray-500').hasText('Try again later'); + assert.dom('.upf-badge').exists(); + }); + + test('it supports named-block usage for image', async function (assert) { + await render(hbs` + + <:image> + Test Image + + + `); + + assert.dom('div.font-color-gray-900').hasText('No Data'); + assert.dom('div.font-color-gray-500').hasText('Try again later'); + assert.dom('img').exists(); + }); + + test('it supports named-block usage for actions', async function (assert) { + await render(hbs` + + <:actions> + + + + `); + + assert.dom('div.font-color-gray-900').hasText('No Data'); + assert.dom('div.font-color-gray-500').hasText('Try again later'); + assert.dom('button').hasText('Retry'); + }); + + module('component size', function (hooks) { + test('it applies md sizes by default', async function (assert) { + await render(hbs``); + assert.dom('div.font-color-gray-900').hasClass('font-size-lg'); + assert.dom('div.font-color-gray-500').hasClass('font-size-md'); + }); + + test('it applies md sizes when given a wrong size', async function (assert) { + await render(hbs``); + assert.dom('div.font-color-gray-900').hasClass('font-size-lg'); + assert.dom('div.font-color-gray-500').hasClass('font-size-md'); + }); + + test('it applies md sizes when specified', async function (assert) { + await render(hbs``); + assert.dom('div.font-color-gray-900').hasClass('font-size-lg'); + assert.dom('div.font-color-gray-500').hasClass('font-size-md'); + }); + + test('it applies sm sizes when specified', async function (assert) { + await render(hbs``); + assert.dom('div.font-color-gray-900').hasClass('font-size-md'); + assert.dom('div.font-color-gray-500').hasClass('font-size-sm'); + }); + }); + + module('error management', function (hooks) { + test('it throws an error if the @title parameter is not passed', async function (assert) { + setupOnerror((err: any) => { + assert.equal(err.message, 'Assertion Failed: [component][OSS::EmptyState] The title parameter is mandatory'); + }); + + await render(hbs``); + }); + + test('it throws an error if the @subtitle parameter is not passed', async function (assert) { + setupOnerror((err: any) => { + assert.equal(err.message, 'Assertion Failed: [component][OSS::EmptyState] The subtitle parameter is mandatory'); + }); + + await render(hbs``); + }); + }); +});