Skip to content

Commit d0e5e21

Browse files
XegushuHugues Coulard
and
Hugues Coulard
authored
Empty state component (#476)
* wip * component done * added tests and stories * feedback fixes * feedback fies pt2 * feedback fixes pt3 * feedback fixes pt4 * fixing typo --------- Co-authored-by: Hugues Coulard <hugues.coulard@upfluence.com>
1 parent 34c82e0 commit d0e5e21

File tree

6 files changed

+279
-0
lines changed

6 files changed

+279
-0
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<div class="fx-1 fx-col fx-xalign-center fx-gap-px-18" ...attributes>
2+
{{#if (has-block "image")}}
3+
{{yield to="image"}}
4+
{{else if @badgeIcon}}
5+
<OSS::Badge @icon={{@badgeIcon}} @size="lg" />
6+
{{/if}}
7+
<span class="fx-col fx-xalign-center fx-gap-px-6">
8+
<div class="font-color-gray-900 font-size-{{this.titleSize}} font-weight-semibold">
9+
{{@title}}
10+
</div>
11+
<div class="font-color-gray-500 font-size-{{this.size}}">
12+
{{@subtitle}}
13+
</div>
14+
</span>
15+
{{#if (has-block "actions")}}
16+
{{yield to="actions"}}
17+
{{/if}}
18+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { hbs } from 'ember-cli-htmlbars';
2+
3+
export default {
4+
title: 'Components/OSS::EmptyState',
5+
component: 'empty state',
6+
argTypes: {
7+
badgeIcon: {
8+
description: 'A font-awesome icon to be displayed in a badge',
9+
table: {
10+
type: {
11+
summary: 'string'
12+
},
13+
defaultValue: { summary: 'undefined' }
14+
},
15+
control: { type: 'text' }
16+
},
17+
title: {
18+
description: 'A title displayed below the icon or badge in the component',
19+
table: {
20+
type: {
21+
summary: 'string'
22+
},
23+
defaultValue: { summary: 'undefined' }
24+
},
25+
control: { type: 'text' }
26+
},
27+
subtitle: {
28+
description: 'A subtitle displayed under the title in the component',
29+
table: {
30+
type: {
31+
summary: 'string'
32+
},
33+
defaultValue: { summary: 'undefined' }
34+
},
35+
control: { type: 'text' }
36+
},
37+
size: {
38+
description: 'The size of the state',
39+
table: {
40+
type: {
41+
summary: 'string'
42+
},
43+
defaultValue: { summary: 'undefined' }
44+
},
45+
control: { type: 'select' },
46+
options: ['sm', 'md']
47+
}
48+
},
49+
parameters: {
50+
docs: {
51+
description: {
52+
component: 'A component used when there is nothing to display on a page'
53+
}
54+
}
55+
}
56+
};
57+
58+
const defaultArgs = {
59+
badgeIcon: 'fa-thumbs-up',
60+
title: 'Empty State Title',
61+
subtitle: 'Additional information here',
62+
size: 'md'
63+
};
64+
65+
const Template = (args) => ({
66+
template: hbs`<OSS::EmptyState @badgeIcon={{this.badgeIcon}} @title={{this.title}} @subtitle={{this.subtitle}} @size={{this.size}} />`,
67+
context: args
68+
});
69+
70+
const ImageTemplate = (args) => ({
71+
template: hbs`<OSS::EmptyState @badgeIcon={{this.badgeIcon}} @title={{this.title}} @subtitle={{this.subtitle}} @size={{this.size}}>
72+
<:image>
73+
<OSS::Illustration @src="/@upfluence/oss-components/assets/images/no-records.svg" />
74+
</:image>
75+
</OSS::EmptyState>`,
76+
context: args
77+
});
78+
79+
const ActionTemplate = (args) => ({
80+
template: hbs`<OSS::EmptyState @badgeIcon={{this.badgeIcon}} @title={{this.title}} @subtitle={{this.subtitle}} @size={{this.size}}>
81+
<:actions>
82+
<OSS::Button @label="Click me" />
83+
</:actions>
84+
</OSS::EmptyState>`,
85+
context: args
86+
});
87+
88+
export const Default = Template.bind({});
89+
Default.args = defaultArgs;
90+
91+
export const UsageWithImage = ImageTemplate.bind({});
92+
UsageWithImage.args = defaultArgs;
93+
94+
export const UsageWithActions = ActionTemplate.bind({});
95+
UsageWithActions.args = defaultArgs;

addon/components/o-s-s/empty-state.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { htmlSafe } from '@ember/template';
2+
import { assert } from '@ember/debug';
3+
import Component from '@glimmer/component';
4+
5+
interface OSSEmptyStateComponentSignature {
6+
badgeIcon?: string;
7+
title: ReturnType<typeof htmlSafe>;
8+
subtitle: ReturnType<typeof htmlSafe>;
9+
size?: 'sm' | 'md';
10+
}
11+
12+
const ALLOWED_SIZES: string[] = ['sm', 'md'];
13+
14+
export default class OSSEmptyStateComponent extends Component<OSSEmptyStateComponentSignature> {
15+
constructor(owner: unknown, args: OSSEmptyStateComponentSignature) {
16+
super(owner, args);
17+
18+
assert('[component][OSS::EmptyState] The title parameter is mandatory', typeof args.title === 'string');
19+
assert('[component][OSS::EmptyState] The subtitle parameter is mandatory', typeof args.subtitle === 'string');
20+
}
21+
22+
get titleSize(): string {
23+
return this.size === 'sm' ? 'md' : 'lg';
24+
}
25+
26+
get size(): string {
27+
return this.args.size && ALLOWED_SIZES.includes(this.args.size) ? this.args.size : 'md';
28+
}
29+
}

app/components/o-s-s/empty-state.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '@upfluence/oss-components/components/o-s-s/empty-state';

tests/dummy/app/templates/index.hbs

+40
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,44 @@
276276
</div>
277277
</div>
278278

279+
<div
280+
class="fx-col fx-1 background-color-white border border-color-default border-radius-md padding-px-12 fx-gap-px-12"
281+
>
282+
<div class="font-size-md font-weight-semibold">
283+
Empty state
284+
</div>
285+
<div class="fx-row fx-gap-px-36 fx-xalign-start">
286+
<OSS::EmptyState @title="Title" @subtitle="Subtitle">
287+
<:image>
288+
<OSS::Illustration @src="/@upfluence/oss-components/assets/images/no-records.svg" />
289+
</:image>
290+
<:actions>
291+
<OSS::Button @label="Button" />
292+
<OSS::Button @skin="primary" @label="Button" />
293+
</:actions>
294+
</OSS::EmptyState>
295+
<OSS::EmptyState @title="Title" @subtitle="Subtitle" @badgeIcon="fa-thumbs-up">
296+
<:actions>
297+
<OSS::Button @label="Button" />
298+
<OSS::Button @skin="primary" @label="Button" />
299+
</:actions>
300+
</OSS::EmptyState>
301+
<OSS::EmptyState @title="Title" @subtitle="Subtitle" @size="sm">
302+
<:image>
303+
<OSS::Illustration @src="/@upfluence/oss-components/assets/images/no-records.svg" />
304+
</:image>
305+
<:actions>
306+
<OSS::Button @label="Button" />
307+
<OSS::Button @skin="primary" @label="Button" />
308+
</:actions>
309+
</OSS::EmptyState>
310+
<OSS::EmptyState @title="Title" @subtitle="Subtitle" @size="sm" @badgeIcon="fa-thumbs-up">
311+
<:actions>
312+
<OSS::Button @label="Button" />
313+
<OSS::Button @skin="primary" @label="Button" />
314+
</:actions>
315+
</OSS::EmptyState>
316+
</div>
317+
</div>
318+
279319
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from 'ember-qunit';
3+
import { render, setupOnerror } from '@ember/test-helpers';
4+
import { hbs } from 'ember-cli-htmlbars';
5+
6+
module('Integration | Component | o-s-s/empty-state', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test('it renders with default properties', async function (assert) {
10+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" />`);
11+
12+
assert.dom('div.font-color-gray-900').hasText('No Data');
13+
assert.dom('div.font-color-gray-500').hasText('Try again later');
14+
});
15+
16+
test('it renders with a badge icon', async function (assert) {
17+
this.set('image', 'fa-thumbs-up');
18+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" @badgeIcon={{this.image}} />`);
19+
20+
assert.dom('div.font-color-gray-900').hasText('No Data');
21+
assert.dom('div.font-color-gray-500').hasText('Try again later');
22+
assert.dom('.upf-badge').exists();
23+
});
24+
25+
test('it supports named-block usage for image', async function (assert) {
26+
await render(hbs`
27+
<OSS::EmptyState @title="No Data" @subtitle="Try again later">
28+
<:image>
29+
<img src="/test-image.png" alt="Test Image" />
30+
</:image>
31+
</OSS::EmptyState>
32+
`);
33+
34+
assert.dom('div.font-color-gray-900').hasText('No Data');
35+
assert.dom('div.font-color-gray-500').hasText('Try again later');
36+
assert.dom('img').exists();
37+
});
38+
39+
test('it supports named-block usage for actions', async function (assert) {
40+
await render(hbs`
41+
<OSS::EmptyState @title="No Data" @subtitle="Try again later">
42+
<:actions>
43+
<button type="button">Retry</button>
44+
</:actions>
45+
</OSS::EmptyState>
46+
`);
47+
48+
assert.dom('div.font-color-gray-900').hasText('No Data');
49+
assert.dom('div.font-color-gray-500').hasText('Try again later');
50+
assert.dom('button').hasText('Retry');
51+
});
52+
53+
module('component size', function (hooks) {
54+
test('it applies md sizes by default', async function (assert) {
55+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" />`);
56+
assert.dom('div.font-color-gray-900').hasClass('font-size-lg');
57+
assert.dom('div.font-color-gray-500').hasClass('font-size-md');
58+
});
59+
60+
test('it applies md sizes when given a wrong size', async function (assert) {
61+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" @size="wrong" />`);
62+
assert.dom('div.font-color-gray-900').hasClass('font-size-lg');
63+
assert.dom('div.font-color-gray-500').hasClass('font-size-md');
64+
});
65+
66+
test('it applies md sizes when specified', async function (assert) {
67+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" @size="md" />`);
68+
assert.dom('div.font-color-gray-900').hasClass('font-size-lg');
69+
assert.dom('div.font-color-gray-500').hasClass('font-size-md');
70+
});
71+
72+
test('it applies sm sizes when specified', async function (assert) {
73+
await render(hbs`<OSS::EmptyState @title="No Data" @subtitle="Try again later" @size="sm" />`);
74+
assert.dom('div.font-color-gray-900').hasClass('font-size-md');
75+
assert.dom('div.font-color-gray-500').hasClass('font-size-sm');
76+
});
77+
});
78+
79+
module('error management', function (hooks) {
80+
test('it throws an error if the @title parameter is not passed', async function (assert) {
81+
setupOnerror((err: any) => {
82+
assert.equal(err.message, 'Assertion Failed: [component][OSS::EmptyState] The title parameter is mandatory');
83+
});
84+
85+
await render(hbs`<OSS::EmptyState @subtitle="Try again later" />`);
86+
});
87+
88+
test('it throws an error if the @subtitle parameter is not passed', async function (assert) {
89+
setupOnerror((err: any) => {
90+
assert.equal(err.message, 'Assertion Failed: [component][OSS::EmptyState] The subtitle parameter is mandatory');
91+
});
92+
93+
await render(hbs`<OSS::EmptyState @title="No Data" />`);
94+
});
95+
});
96+
});

0 commit comments

Comments
 (0)