From 6adb7a1501c2e6354e33945d5ff8be2050f1f993 Mon Sep 17 00:00:00 2001 From: Philippe Ndiaye Date: Wed, 11 Feb 2026 10:58:56 +0100 Subject: [PATCH] o-s-s/access-panel: supported delegated filtering to parent + custom skeletons for loading states --- addon/components/o-s-s/access-panel.hbs | 40 ++++++++---- .../components/o-s-s/access-panel.stories.js | 41 ++++++++++++ addon/components/o-s-s/access-panel.ts | 10 +-- .../components/o-s-s/access-panel-test.ts | 63 ++++++++++++++++++- 4 files changed, 135 insertions(+), 19 deletions(-) diff --git a/addon/components/o-s-s/access-panel.hbs b/addon/components/o-s-s/access-panel.hbs index cf6abd70f..b56030020 100644 --- a/addon/components/o-s-s/access-panel.hbs +++ b/addon/components/o-s-s/access-panel.hbs @@ -6,7 +6,7 @@ {{/if}} - {{#if (and this.displayEmptyState this.hasNoKeyword)}} + {{#if (and this.displayEmptyState (not this.filtered))}}
{{yield to="empty-state"}}
@@ -27,20 +27,28 @@
{{yield to="columns"}}
- -
{{/if}} - {{#if (and this.displayEmptyState this.searchKeyword)}} + {{#if (and this.displayEmptyState this.filtered)}} {{yield to="no-results"}} {{else}} -
+
{{#if (and @loading @initialLoad)}} {{#each this.loadingRows}} -
- - -
+ {{#if (has-block "row-skeleton")}} +
+ {{yield to="row-skeleton"}} +
+ {{else}} +
+ + +
+ {{/if}} {{/each}} {{else}} {{#each @records as |record|}} @@ -56,10 +64,16 @@ {{#if @loading}} {{#each this.loadingMoreRows}} -
- - -
+ {{#if (has-block "row-skeleton")}} +
+ {{yield to="row-skeleton"}} +
+ {{else}} +
+ + +
+ {{/if}} {{/each}} {{/if}} {{/if}} diff --git a/addon/components/o-s-s/access-panel.stories.js b/addon/components/o-s-s/access-panel.stories.js index 29f846ddd..3fd08af88 100644 --- a/addon/components/o-s-s/access-panel.stories.js +++ b/addon/components/o-s-s/access-panel.stories.js @@ -36,6 +36,16 @@ export default { }, control: { type: 'boolean' } }, + filtered: { + description: 'Whether the records displayed are filtered by the parent or not', + table: { + type: { + summary: 'boolean' + }, + defaultValue: { summary: undefined } + }, + control: { type: 'boolean' } + }, onBottomReached: { description: 'Function triggered when the user scrolls to the bottom of the panel.', table: { @@ -80,6 +90,7 @@ const defaultArgs = { records: [{ label: 'foo' }, { label: 'bar' }], initialLoad: false, loading: false, + filtered: undefined, onBottomReached: action('onBottomReached'), onClose: action('onClose'), onSearch: action('onSearch') @@ -123,6 +134,29 @@ const CustomContentTemplate = (args) => ({ context: args }); +const CustomLoadingStatesTemplate = (args) => ({ + template: hbs` +
+ + <:header>Header + <:empty-state>Empty state + <:columns>Columns + <:no-results>No results + <:row as |record|>{{record.label}} + <:row-skeleton>Loading... + +
+ `, + context: args +}); + export const BasicUsage = DefaultUsageTemplate.bind({}); BasicUsage.args = defaultArgs; @@ -143,3 +177,10 @@ LoadingState.args = { loading: true, initialLoad: false }; + +export const CustomLoadingState = CustomLoadingStatesTemplate.bind({}); +CustomLoadingState.args = { + ...defaultArgs, + loading: true, + initialLoad: false +}; diff --git a/addon/components/o-s-s/access-panel.ts b/addon/components/o-s-s/access-panel.ts index 18efb1ca6..221732f08 100644 --- a/addon/components/o-s-s/access-panel.ts +++ b/addon/components/o-s-s/access-panel.ts @@ -2,11 +2,13 @@ import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { later } from '@ember/runloop'; +import { isBlank } from '@ember/utils'; interface OSSAccessPanelArgs { records: unknown[]; initialLoad: boolean; loading?: boolean; + filtered?: boolean; onBottomReached(): void; onClose(): void; onSearch?(keyword: string): void; @@ -18,12 +20,12 @@ export default class OSSAccessPanel extends Component { @tracked searchKeyword: string = ''; - get displayEmptyState(): boolean { - return (this.args.records || []).length === 0 && !this.args.loading; + get filtered(): boolean { + return this.args.filtered || !isBlank(this.searchKeyword); } - get hasNoKeyword(): boolean { - return !this.searchKeyword; + get displayEmptyState(): boolean { + return (this.args.records || []).length === 0 && !this.args.loading; } @action diff --git a/tests/integration/components/o-s-s/access-panel-test.ts b/tests/integration/components/o-s-s/access-panel-test.ts index 7a3028490..b8f8f26bc 100644 --- a/tests/integration/components/o-s-s/access-panel-test.ts +++ b/tests/integration/components/o-s-s/access-panel-test.ts @@ -23,7 +23,8 @@ module('Integration | Component | o-s-s/access-panel', function (hooks) { await render( hbs` + @onBottomReached={{this.loadMore}} @onSearch={{this.onSearch}} @onClose={{this.onClose}} + @filtered={{this.filtered}}> <:header>Header <:columns>Columns <:row as |record|>row display: {{record.label}} @@ -50,15 +51,62 @@ module('Integration | Component | o-s-s/access-panel', function (hooks) { assert.dom('.oss-access-panel-container__row .upf-skeleton-effect').exists({ count: 24 }); }); - test('the initial loading state is correctly displayed', async function (assert) { + test('the initial loading state uses the row-skeleton named block if passed', async function (assert) { + this.loading = true; + this.initialLoad = true; + + await render( + hbs` + <:header>Header + <:columns>Columns + <:row as |record|>row display: {{record.label}} + <:empty-state>
empty state
+ <:no-results>
no search results
+ <:row-skeleton>
custom loading state
+
+ ` + ); + + assert.dom('.oss-access-panel-container__row').exists({ count: 12 }); + assert.dom('.oss-access-panel-container__row .super-skeleton').exists({ count: 12 }); + }); + + test('the loading state when loading more records is correctly displayed', async function (assert) { this.loading = true; this.initialLoad = false; + await renderComponent(); assert.dom('.oss-access-panel-container__row').exists({ count: 5 }); assert.dom('.oss-access-panel-container__row .upf-skeleton-effect').exists({ count: 6 }); }); + test('the loading state when loading more records uses the row-skeleton named block if passed', async function (assert) { + this.loading = true; + this.initialLoad = false; + + await render( + hbs` + <:header>Header + <:columns>Columns + <:row as |record|>row display: {{record.label}} + <:empty-state>
empty state
+ <:no-results>
no search results
+ <:row-skeleton>
custom loading state
+
+ ` + ); + + assert.dom('.oss-access-panel-container__row').exists({ count: 5 }); + assert.dom('.oss-access-panel-container__row .super-skeleton').exists({ count: 3 }); + }); + test('The header named block is correctly filled', async function (assert) { await renderComponent(); assert.dom('.oss-access-panel-container__header').exists(); @@ -104,6 +152,17 @@ module('Integration | Component | o-s-s/access-panel', function (hooks) { assert.dom('.no-results').hasText('no search results'); }); + test('it renders the right empty state when no records are found and there is an ongoing search handled by the parent', async function (assert) { + this.onSearch = undefined; + this.records = []; + this.filtered = true; + + await renderComponent(); + + assert.dom('.no-results').exists(); + assert.dom('.no-results').hasText('no search results'); + }); + test('if the onSearch arg is not passed, the search input is not displayed', async function (assert) { this.onSearch = undefined; await renderComponent();