From d6a5b2116acc1193e164b649eecc55c2603dfa68 Mon Sep 17 00:00:00 2001 From: Owen Coogan Date: Thu, 12 Feb 2026 17:38:18 +0100 Subject: [PATCH 1/5] feat: added @suffixIcon arg to oss::button --- addon/components/o-s-s/button.hbs | 11 +++-- addon/components/o-s-s/button.stories.js | 41 ++++++++++++++++++- addon/components/o-s-s/button.ts | 21 ++++++++++ .../components/o-s-s/button-test.ts | 23 +++++++++-- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/addon/components/o-s-s/button.hbs b/addon/components/o-s-s/button.hbs index 43a9b23e6..81172f7be 100644 --- a/addon/components/o-s-s/button.hbs +++ b/addon/components/o-s-s/button.hbs @@ -20,9 +20,14 @@ {{else if @iconUrl}} icon {{/if}} +
+ {{#if @label}} + {{@label}} + {{/if}} - {{#if @label}} - {{@label}} - {{/if}} + {{#if @suffixIcon}} + + {{/if}} +
{{/if}} \ No newline at end of file diff --git a/addon/components/o-s-s/button.stories.js b/addon/components/o-s-s/button.stories.js index 18e3374ee..4cbd47e85 100644 --- a/addon/components/o-s-s/button.stories.js +++ b/addon/components/o-s-s/button.stories.js @@ -20,6 +20,7 @@ const SkinTypes = [ ]; const SizeTypes = ['xs', 'sm', 'md', 'lg']; const ThemeTypes = ['light', 'dark']; +const GapTypes = ['fx-gap-px-3', 'fx-gap-px-6', 'fx-gap-px-9', 'fx-gap-px-12', 'fx-gap-px-18', 'fx-gap-px-24']; export default { title: 'Components/OSS::Button', @@ -97,6 +98,25 @@ export default { type: 'text' } }, + suffixIcon: { + description: 'Font Awesome class for an icon displayed after the label (e.g. fas fa-chevron-down)', + table: { + type: { summary: 'string' }, + defaultValue: { summary: 'undefined' } + }, + control: { + type: 'text' + } + }, + gap: { + description: 'Gap between icon, label, and suffix icon. Must be a valid fx-gap-px-* class. Defaults to fx-gap-px-6.', + table: { + type: { summary: GapTypes.join('|') }, + defaultValue: { summary: 'fx-gap-px-6' } + }, + options: GapTypes, + control: { type: 'select' } + }, square: { description: 'Displays the button as a square. Useful for icon buttons.', table: { @@ -164,6 +184,8 @@ const defaultArgs = { countDown: undefined, loadingOptions: undefined, iconUrl: undefined, + suffixIcon: undefined, + gap: undefined, disabled: undefined }; @@ -172,7 +194,8 @@ const Template = (args) => ({ + @iconUrl={{this.iconUrl}} @suffixIcon={{this.suffixIcon}} @gap={{this.gap}} + @loadingOptions={{this.loadingOptions}} disabled={{this.disabled}} /> `, context: args }); @@ -201,3 +224,19 @@ WithIconUrl.args = { iconUrl: '/@upfluence/oss-components/assets/heart.svg' } }; + +export const WithSuffixIcon = Template.bind({}); +WithSuffixIcon.args = { + ...defaultArgs, + ...{ + suffixIcon: 'fas fa-chevron-down' + } +}; + +export const WithCustomGap = Template.bind({}); +WithCustomGap.args = { + ...defaultArgs, + ...{ + gap: 'fx-gap-px-12' + } +}; diff --git a/addon/components/o-s-s/button.ts b/addon/components/o-s-s/button.ts index 3a0329d88..ec64711be 100644 --- a/addon/components/o-s-s/button.ts +++ b/addon/components/o-s-s/button.ts @@ -36,6 +36,17 @@ type ThemeDefType = { }; type LoadingOptions = { showLabel?: boolean }; +const VALID_GAP_CLASSES = [ + 'fx-gap-px-3', + 'fx-gap-px-6', + 'fx-gap-px-9', + 'fx-gap-px-12', + 'fx-gap-px-18', + 'fx-gap-px-24' +] as const; + +export type GapClassType = (typeof VALID_GAP_CLASSES)[number]; + const SkinDefinition: SkinDefType = { default: 'default', primary: 'primary', @@ -77,6 +88,8 @@ export interface OSSButtonArgs { loadingOptions?: LoadingOptions; icon?: string; iconUrl?: string; + suffixIcon?: string; + gap?: GapClassType; label?: string; theme?: string; square?: boolean; @@ -106,6 +119,10 @@ export default class OSSButton extends Component { "[component][OSS::Button] You must pass either a hash with 'callback' value to @countDown argument.", args.countDown ? args.countDown.callback : true ); + assert( + '[component][OSS::Button] When @gap is provided, it must be a valid fx-gap-px-* class.', + !this.args.gap || VALID_GAP_CLASSES.includes(this.args.gap) + ); } get skin(): string { @@ -130,6 +147,10 @@ export default class OSSButton extends Component { return this.args.theme; } + get suffixGapClass(): string { + return this.args.gap ?? 'fx-gap-px-6'; + } + get computedClass() { let classes = [this.args.square ? SQUARE_CLASS : BASE_CLASS, `upf-btn--${this.skin}`]; diff --git a/tests/integration/components/o-s-s/button-test.ts b/tests/integration/components/o-s-s/button-test.ts index 30189c303..eeb0d96cb 100644 --- a/tests/integration/components/o-s-s/button-test.ts +++ b/tests/integration/components/o-s-s/button-test.ts @@ -50,6 +50,23 @@ module('Integration | Component | o-s-s/button', function (hooks) { assert.dom('.upf-btn').hasText('Test'); }); + test('it applies custom gap when @gap is provided', async function (assert) { + await render(hbs``); + + assert.dom('.upf-btn .fx-row').hasClass('fx-gap-px-12'); + }); + + test('it fails when an invalid @gap class is provided', async function (assert) { + setupOnerror((err: { message: string }) => { + assert.equal( + err.message, + 'Assertion Failed: [component][OSS::Button] When @gap is provided, it must be a valid fx-gap-px-* class.' + ); + }); + + await render(hbs``); + }); + module('it renders with the correct skin', function () { test('when using an unknown skin, it is set to default', async function (assert) { await render(hbs``); @@ -138,7 +155,7 @@ module('Integration | Component | o-s-s/button', function (hooks) { assert.dom('.upf-btn i.fas').exists(); assert.dom('.upf-btn i.fas').hasClass('fa-circle-notch'); assert.dom('.upf-btn i.fas').hasClass('fa-spin'); - assert.dom('.upf-btn span.margin-left-px-6').doesNotExist(); + assert.dom('.upf-btn span').doesNotExist(); }); test('when loading and the showLabel loading option is truthy, the label is displayed', async function (assert) { @@ -148,8 +165,8 @@ module('Integration | Component | o-s-s/button', function (hooks) { assert.dom('.upf-btn i.fas').exists(); assert.dom('.upf-btn i.fas').hasClass('fa-circle-notch'); assert.dom('.upf-btn i.fas').hasClass('fa-spin'); - assert.dom('.upf-btn span.margin-left-px-6').exists(); - assert.dom('.upf-btn span.margin-left-px-6').hasText('Test'); + assert.dom('.upf-btn span').exists(); + assert.dom('.upf-btn span').hasText('Test'); }); }); From 8dd1a1aeac670b7743172ccd122b659573eba116 Mon Sep 17 00:00:00 2001 From: Owen Coogan Date: Fri, 13 Feb 2026 13:05:37 +0100 Subject: [PATCH 2/5] fix: fixed linter issue --- addon/components/o-s-s/button.stories.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addon/components/o-s-s/button.stories.js b/addon/components/o-s-s/button.stories.js index 4cbd47e85..d907f2ef0 100644 --- a/addon/components/o-s-s/button.stories.js +++ b/addon/components/o-s-s/button.stories.js @@ -109,7 +109,8 @@ export default { } }, gap: { - description: 'Gap between icon, label, and suffix icon. Must be a valid fx-gap-px-* class. Defaults to fx-gap-px-6.', + description: + 'Gap between icon, label, and suffix icon. Must be a valid fx-gap-px-* class. Defaults to fx-gap-px-6.', table: { type: { summary: GapTypes.join('|') }, defaultValue: { summary: 'fx-gap-px-6' } From ff828704bf2c45c84b8689ca873a595ee3b1e089 Mon Sep 17 00:00:00 2001 From: Owen Coogan Date: Fri, 13 Feb 2026 14:35:50 +0100 Subject: [PATCH 3/5] fix: removed dynamic gap for suffix icon --- addon/components/o-s-s/button.hbs | 19 ++++++++-------- addon/components/o-s-s/button.stories.js | 22 +------------------ addon/components/o-s-s/button.ts | 20 ----------------- .../components/o-s-s/button-test.ts | 17 +++++--------- 4 files changed, 17 insertions(+), 61 deletions(-) diff --git a/addon/components/o-s-s/button.hbs b/addon/components/o-s-s/button.hbs index 81172f7be..794170fdd 100644 --- a/addon/components/o-s-s/button.hbs +++ b/addon/components/o-s-s/button.hbs @@ -20,14 +20,15 @@ {{else if @iconUrl}} icon {{/if}} -
- {{#if @label}} - {{@label}} - {{/if}} - - {{#if @suffixIcon}} - - {{/if}} -
+ {{#if @label}} + {{@label}} + {{/if}} + {{#if @suffixIcon}} + + {{/if}} {{/if}} \ No newline at end of file diff --git a/addon/components/o-s-s/button.stories.js b/addon/components/o-s-s/button.stories.js index d907f2ef0..3e05f6f23 100644 --- a/addon/components/o-s-s/button.stories.js +++ b/addon/components/o-s-s/button.stories.js @@ -20,7 +20,6 @@ const SkinTypes = [ ]; const SizeTypes = ['xs', 'sm', 'md', 'lg']; const ThemeTypes = ['light', 'dark']; -const GapTypes = ['fx-gap-px-3', 'fx-gap-px-6', 'fx-gap-px-9', 'fx-gap-px-12', 'fx-gap-px-18', 'fx-gap-px-24']; export default { title: 'Components/OSS::Button', @@ -108,16 +107,6 @@ export default { type: 'text' } }, - gap: { - description: - 'Gap between icon, label, and suffix icon. Must be a valid fx-gap-px-* class. Defaults to fx-gap-px-6.', - table: { - type: { summary: GapTypes.join('|') }, - defaultValue: { summary: 'fx-gap-px-6' } - }, - options: GapTypes, - control: { type: 'select' } - }, square: { description: 'Displays the button as a square. Useful for icon buttons.', table: { @@ -186,7 +175,6 @@ const defaultArgs = { loadingOptions: undefined, iconUrl: undefined, suffixIcon: undefined, - gap: undefined, disabled: undefined }; @@ -195,7 +183,7 @@ const Template = (args) => ({ `, context: args @@ -233,11 +221,3 @@ WithSuffixIcon.args = { suffixIcon: 'fas fa-chevron-down' } }; - -export const WithCustomGap = Template.bind({}); -WithCustomGap.args = { - ...defaultArgs, - ...{ - gap: 'fx-gap-px-12' - } -}; diff --git a/addon/components/o-s-s/button.ts b/addon/components/o-s-s/button.ts index ec64711be..6cd3fc2d9 100644 --- a/addon/components/o-s-s/button.ts +++ b/addon/components/o-s-s/button.ts @@ -36,17 +36,6 @@ type ThemeDefType = { }; type LoadingOptions = { showLabel?: boolean }; -const VALID_GAP_CLASSES = [ - 'fx-gap-px-3', - 'fx-gap-px-6', - 'fx-gap-px-9', - 'fx-gap-px-12', - 'fx-gap-px-18', - 'fx-gap-px-24' -] as const; - -export type GapClassType = (typeof VALID_GAP_CLASSES)[number]; - const SkinDefinition: SkinDefType = { default: 'default', primary: 'primary', @@ -89,7 +78,6 @@ export interface OSSButtonArgs { icon?: string; iconUrl?: string; suffixIcon?: string; - gap?: GapClassType; label?: string; theme?: string; square?: boolean; @@ -119,10 +107,6 @@ export default class OSSButton extends Component { "[component][OSS::Button] You must pass either a hash with 'callback' value to @countDown argument.", args.countDown ? args.countDown.callback : true ); - assert( - '[component][OSS::Button] When @gap is provided, it must be a valid fx-gap-px-* class.', - !this.args.gap || VALID_GAP_CLASSES.includes(this.args.gap) - ); } get skin(): string { @@ -147,10 +131,6 @@ export default class OSSButton extends Component { return this.args.theme; } - get suffixGapClass(): string { - return this.args.gap ?? 'fx-gap-px-6'; - } - get computedClass() { let classes = [this.args.square ? SQUARE_CLASS : BASE_CLASS, `upf-btn--${this.skin}`]; diff --git a/tests/integration/components/o-s-s/button-test.ts b/tests/integration/components/o-s-s/button-test.ts index eeb0d96cb..74bc1d686 100644 --- a/tests/integration/components/o-s-s/button-test.ts +++ b/tests/integration/components/o-s-s/button-test.ts @@ -50,21 +50,16 @@ module('Integration | Component | o-s-s/button', function (hooks) { assert.dom('.upf-btn').hasText('Test'); }); - test('it applies custom gap when @gap is provided', async function (assert) { - await render(hbs``); + test('it applies margin-left-px-6 to suffix icon when label is present', async function (assert) { + await render(hbs``); - assert.dom('.upf-btn .fx-row').hasClass('fx-gap-px-12'); + assert.dom('.upf-btn i.fa-chevron-down').hasClass('margin-left-px-6'); }); - test('it fails when an invalid @gap class is provided', async function (assert) { - setupOnerror((err: { message: string }) => { - assert.equal( - err.message, - 'Assertion Failed: [component][OSS::Button] When @gap is provided, it must be a valid fx-gap-px-* class.' - ); - }); + test('it does not apply margin to suffix icon when label is absent', async function (assert) { + await render(hbs``); - await render(hbs``); + assert.dom('.upf-btn i.fa-chevron-down').doesNotHaveClass('margin-left-px-6'); }); module('it renders with the correct skin', function () { From 00e18536b7345dfaea89dd0ceb40c7bf5be51f3e Mon Sep 17 00:00:00 2001 From: Owen Coogan Date: Fri, 13 Feb 2026 14:41:00 +0100 Subject: [PATCH 4/5] fix: added fix for margin size --- addon/components/o-s-s/button.hbs | 2 +- tests/integration/components/o-s-s/button-test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addon/components/o-s-s/button.hbs b/addon/components/o-s-s/button.hbs index 794170fdd..7bd3d5fe6 100644 --- a/addon/components/o-s-s/button.hbs +++ b/addon/components/o-s-s/button.hbs @@ -27,7 +27,7 @@ {{/if}} {{/if}} diff --git a/tests/integration/components/o-s-s/button-test.ts b/tests/integration/components/o-s-s/button-test.ts index 74bc1d686..49bcc4b7d 100644 --- a/tests/integration/components/o-s-s/button-test.ts +++ b/tests/integration/components/o-s-s/button-test.ts @@ -53,13 +53,13 @@ module('Integration | Component | o-s-s/button', function (hooks) { test('it applies margin-left-px-6 to suffix icon when label is present', async function (assert) { await render(hbs``); - assert.dom('.upf-btn i.fa-chevron-down').hasClass('margin-left-px-6'); + assert.dom('.upf-btn i.fa-chevron-down').hasClass('margin-left-px-12'); }); test('it does not apply margin to suffix icon when label is absent', async function (assert) { await render(hbs``); - assert.dom('.upf-btn i.fa-chevron-down').doesNotHaveClass('margin-left-px-6'); + assert.dom('.upf-btn i.fa-chevron-down').doesNotHaveClass('margin-left-px-12'); }); module('it renders with the correct skin', function () { From 9ef4f256811d3a4ad79cf2151e24dbe98fb90a04 Mon Sep 17 00:00:00 2001 From: Owen Coogan Date: Fri, 13 Feb 2026 15:43:02 +0100 Subject: [PATCH 5/5] fix: fixed gap based on design input --- addon/components/o-s-s/button.hbs | 2 +- tests/integration/components/o-s-s/button-test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addon/components/o-s-s/button.hbs b/addon/components/o-s-s/button.hbs index 7bd3d5fe6..1cb5eb7a3 100644 --- a/addon/components/o-s-s/button.hbs +++ b/addon/components/o-s-s/button.hbs @@ -27,7 +27,7 @@ {{/if}} {{/if}} diff --git a/tests/integration/components/o-s-s/button-test.ts b/tests/integration/components/o-s-s/button-test.ts index 49bcc4b7d..025038bd8 100644 --- a/tests/integration/components/o-s-s/button-test.ts +++ b/tests/integration/components/o-s-s/button-test.ts @@ -50,16 +50,16 @@ module('Integration | Component | o-s-s/button', function (hooks) { assert.dom('.upf-btn').hasText('Test'); }); - test('it applies margin-left-px-6 to suffix icon when label is present', async function (assert) { + test('it applies margin-left-px-18 to suffix icon when label is present', async function (assert) { await render(hbs``); - assert.dom('.upf-btn i.fa-chevron-down').hasClass('margin-left-px-12'); + assert.dom('.upf-btn i.fa-chevron-down').hasClass('margin-left-px-18'); }); test('it does not apply margin to suffix icon when label is absent', async function (assert) { await render(hbs``); - assert.dom('.upf-btn i.fa-chevron-down').doesNotHaveClass('margin-left-px-12'); + assert.dom('.upf-btn i.fa-chevron-down').doesNotHaveClass('margin-left-px-18'); }); module('it renders with the correct skin', function () {