Skip to content

OSS::Stats::Banner - #700

Merged
OwenCoogan merged 16 commits into
masterfrom
oc/DRA-5594
Aug 24, 2026
Merged

OSS::Stats::Banner#700
OwenCoogan merged 16 commits into
masterfrom
oc/DRA-5594

Conversation

@OwenCoogan

@OwenCoogan OwenCoogan commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Screenshot 2026-08-19 at 17 17 31

Introduces a new OSS::Stats::Banner component for flexible KPI/header composition.

  • Adds structured config args for a few elements ( badge & icon )
  • Uses dropdown, extra-badges, and cta as named blocks to preserve flexibility in those sections.
  • Implements title ellipsis with a top-position tooltip shown only on overflow.
  • Renders the info icon when titleInfoCircle is provided.
  • Hides extra-badges and cta block content in loading mode for consistent loading UI.
  • Updates to Storybook and tests

Related to: #

What are the observable changes?

Good PR checklist

  • Title makes sense
  • Is against the correct branch
  • Only addresses one issue
  • Properly assigned
  • Added/updated tests
  • Added/updated documentation with Figma design link. Don't forget to replace "design" by "file" in the URL. For example https://www.figma.com/file/example
  • Properly labeled

@OwenCoogan OwenCoogan self-assigned this Aug 19, 2026
@linear-code

linear-code Bot commented Aug 19, 2026

Copy link
Copy Markdown

DRA-5594

@Miexil Miexil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't have mutch to say here except in the tests, good work

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +29 to +33
{{#if @titleInfoCircle}}
<span {{enable-tooltip title=@titleInfoCircle placement="top"}}>
<OSS::Icon @icon="fa-info-circle" class="font-color-gray-400" />
</span>
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this one shouldn't be a named-block instead.

IMO it really depends on how often it is used. If we have it in at least 50% of the use-cases, might as well leave it this way; it'll be lighter on the parent caller

@phndiaye wdyt ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was actually debating with myself yesterday if the info-circle & dropdown should both be wrapped in a title extra actions block.
For now in the redesign, we have 13 banners to update & only 2 contain info-circles 👀

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, i would rather make a slightly more complex/future-proof title arg:

type StatTitle = {
  label: ReturnType<typeof IntlService['t']> | string;
  tooltip?: ReturnType<typeof IntlService['t']> | string;
}

This way we don't necessarily need a named-block but we still group this as belonging to the title

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +24 to +27
<span
class="oss-stats-banner__title text-ellipsis font-color-gray-600 font-weight-semibold"
{{enable-tooltip title=@title placement="top" displayOnlyOnOverflow=true}}
>{{@title}}</span>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiniest nit, can we have the {{@title}} on its own line ?

<span
  class="..."
  {{enable-tooltip ...}}
>
  {{@title}}
</span>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}
}

@container (width <= 900px) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... But you're missing container-type: inline-size in the parent in order to enable this ;)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.dom('.oss-stats-banner .fa-star').hasAttribute('style', 'color:red');
});

test('when @titleInfoCircle is defined, it renders an info-circle icon next to the title', async function (assert) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a counter test, ie, when not defined, not showing up

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test

assert.dom('.oss-stats-banner .fa-info-circle').exists();
});

test('when the dropdown block is defined, it renders its content in the title row', async function (assert) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, missing counter test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added tests

assert.dom('.oss-stats-banner .test-dropdown').exists();
});

test('when @loading is false and title/stat values are defined, it renders text instead of skeletons', async function (assert) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to loading module

import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';

module('Integration | Component | o-s-s/stats/banner', function (hooks) {

@Miexil Miexil Aug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global comment here, let's move related topics into sub-module, for example:

  • Module 'Loading state handling'
    • when @loading is true and no related args or blocks are defined, it renders title/stat skeletons
    • when @loading is true, it renders title/stat skeletons and hides named block content
    • when @loading is false and title/stat values are defined, it renders text instead of skeletons
  • module for extraIconConfig

etc

@phndiaye phndiaye left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global: kinda late on the wording but what about OSS::Stats::Card rather than Banner ? 🤔 (unless it maps to the Figma wordings of course :()

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
{{#if @loading}}
<OSS::Skeleton @height={{12}} @width={{150}} class="oss-stats-banner__skeleton--title" />
{{else}}
<div class="oss-stats-banner__title-row fx-row fx-gap-px-8 fx-xalign-center">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no gap-8 (spotted multiple btw) ^^

also, as there is already a class for it, might as well move the utility classes in there imo (applies to multiple cases below)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Globally, i would add some data-control-name on the key blocks

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +29 to +33
{{#if @titleInfoCircle}}
<span {{enable-tooltip title=@titleInfoCircle placement="top"}}>
<OSS::Icon @icon="fa-info-circle" class="font-color-gray-400" />
</span>
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, i would rather make a slightly more complex/future-proof title arg:

type StatTitle = {
  label: ReturnType<typeof IntlService['t']> | string;
  tooltip?: ReturnType<typeof IntlService['t']> | string;
}

This way we don't necessarily need a named-block but we still group this as belonging to the title

Comment thread addon/components/o-s-s/stats/banner.ts
Comment thread addon/components/o-s-s/stats/banner.hbs
Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +36 to +38
{{#if (has-block "dropdown")}}
{{yield to="dropdown"}}
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is dropdown the best name here? technically you cannot enforce at all what the consumer will do w/ it ^^ maybe title-suffix named-block instead ? (if others have ideas 🙏🏾 )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines +45 to +49
{{#if (and (has-block "extra-badges") (not @loading))}}
<div class="oss-stats-banner__extra-badges fx-row fx-gap-px-3 fx-xalign-center">
{{yield to="extra-badges"}}
</div>
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you link a design for this use case? i'm having trouble visualising what it maps to

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.figma.com/design/6I3IoYJkvAOOs7sPH9L8gt/Stats-card-redesign?node-id=19606-1698&t=muqAvXMEkjKivDa1-11

integrations badges, already tried out in the implem pr from my slack message 👌

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add it to the storybook also ?

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +71 to +75
{{#if (and (has-block "cta") (not @loading))}}
<div class="oss-stats-banner__cta">
{{yield to="cta"}}
</div>
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering... should we keep this a named block or an action: ButtonArgs & { fn: (...args: unknown[]) => unknown (a bit like what we do for OSS::Dialog ? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this one we need to leave it open as a named block, component specs specify that there is a possiblity for secondary actions

Comment thread addon/components/o-s-s/stats/banner.hbs Outdated
Comment on lines +56 to +68
<span class="font-color-gray-900 font-weight-semibold font-size-h3">{{@statValue}}</span>
{{#if @statExtraInfo}}
<span class="font-color-gray-500">{{@statExtraInfo}}</span>
{{/if}}

{{#if @extraInfoTagConfig}}
<OSS::Tag
@label={{@extraInfoTagConfig.label}}
@skin={{@extraInfoTagConfig.skin}}
@icon={{@extraInfoTagConfig.icon}}
@plain={{@extraInfoTagConfig.plain}}
/>
{{/if}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the same logic as above, what about:

type StatValue = {
  label: ReturnType<typeof IntlService['t']>, // or value
  suffix?: ReturnType<typeof IntlService['t']>,
  tags?: TagArgs[] // worst case there is always only one but we support multiple (not mandatory though, could be just `tag`)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would fit better in the visual or data route i think :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved it to data

}
}

@container (width <= 900px) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

}
}

@container (width <= 900px) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... But you're missing container-type: inline-size in the parent in order to enable this ;)

}
}

@container (width <= 900px) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{{yield to="cta"}}
</div>
{{/if}}
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: where is the "normal" / "default" {{ yield }} to pass the leftovers? I might very well be missing something here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need it here, not unless we want to pass other components on the fly but that's a whole other implem IMO ^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants