From e1dc841738f59190d7f44773fe5ecb35f63b0b1c Mon Sep 17 00:00:00 2001 From: Antoine Prentout Date: Fri, 24 Jul 2026 15:15:07 +0200 Subject: [PATCH 1/3] [OSS::Avatar] Add new skin argument --- addon/components/o-s-s/avatar.stories.js | 17 ++++++++- addon/components/o-s-s/avatar.ts | 22 +++++++++++ app/styles/atoms/avatar.less | 37 +++++++++++++++++++ tests/dummy/app/templates/visual.hbs | 9 +++++ .../components/o-s-s/avatar-test.ts | 32 +++++++++++++++- 5 files changed, 114 insertions(+), 3 deletions(-) diff --git a/addon/components/o-s-s/avatar.stories.js b/addon/components/o-s-s/avatar.stories.js index 950b243d1..414b50a6a 100644 --- a/addon/components/o-s-s/avatar.stories.js +++ b/addon/components/o-s-s/avatar.stories.js @@ -1,6 +1,7 @@ import { hbs } from 'ember-cli-htmlbars'; const SizeTypes = ['xs', 'sm', 'md', 'lg']; +const SkinType = ['danger', 'xtd-orange', 'xtd-violet-light', 'xtd-violet', 'xtd-lime', 'primary', 'primary-light', 'xtd-cyan']; export default { title: 'Components/OSS::Avatar', @@ -41,6 +42,17 @@ export default { type: 'text' } }, + skin: { + description: 'Adjust the skin of the avatar component', + table: { + type: { + summary: SkinType.join('|') + }, + defaultValue: { summary: undefined } + }, + options: [undefined, ...SkinType], + control: { type: 'select' } + }, loading: { description: 'Display loading state', table: { @@ -70,12 +82,13 @@ const defaultArgs = { size: 'md', image: undefined, initials: undefined, - loading: false + loading: false, + skin: undefined }; const Template = (args) => ({ template: hbs` - + `, context: args }); diff --git a/addon/components/o-s-s/avatar.ts b/addon/components/o-s-s/avatar.ts index 1ff489880..3a33535a6 100644 --- a/addon/components/o-s-s/avatar.ts +++ b/addon/components/o-s-s/avatar.ts @@ -12,11 +12,25 @@ export const SizeDefinition: SizeDefType = { lg: 'upf-avatar--lg' }; +export const AvatarSkins = [ + 'danger', + 'xtd-orange', + 'xtd-violet-light', + 'xtd-violet', + 'xtd-lime', + 'primary', + 'primary-light', + 'xtd-cyan' +] as const; + +export type SkinType = (typeof AvatarSkins)[number]; + export interface OSSAvatarArgs { image?: string; initials?: string; size?: SizeType; loading?: boolean; + skin?: SkinType; } export const DEFAULT_IMAGE_URL: string = '/@upfluence/oss-components/assets/images/avatar-placeholder.svg'; @@ -44,6 +58,14 @@ export default class OSSAvatar extends Component { classes = classes.concat('upf-avatar--loading '); } + if (this.args.skin) { + assert( + `[component][OSS::Avatar] Unknown skin. Available skins are: ${AvatarSkins.join(', ')}`, + AvatarSkins.includes(this.args.skin as SkinType) + ); + classes = classes.concat(`upf-avatar--skin upf-avatar--skin-${this.args.skin} `); + } + assert( `[component][OSS::Avatar] Unknown size. Available sizes are: ${Object.keys(SizeDefinition).join(', ')}`, Object.keys(SizeDefinition).includes(size as SizeType) diff --git a/app/styles/atoms/avatar.less b/app/styles/atoms/avatar.less index 705b81e45..81c49e15f 100644 --- a/app/styles/atoms/avatar.less +++ b/app/styles/atoms/avatar.less @@ -70,4 +70,41 @@ .upf-skeleton-effect; .border-radius-round; } + + &--skin { + color: var(--color-white); + border: 1px solid var(--color-white); + + &-danger { + background-color: var(--color-error-400); + } + + &-xtd-orange { + background-color: var(--color-orange-400); + } + + &-xtd-violet-light { + background-color: var(--color-violet-400); + } + + &-xtd-violet { + background-color: var(--color-violet-500); + } + + &-xtd-lime { + background-color: var(--color-lime-500); + } + + &-primary { + background-color: var(--color-primary-400); + } + + &-primary-light { + background-color: var(--color-primary-300); + } + + &-xtd-cyan { + background-color: var(--color-cyan-500); + } + } } diff --git a/tests/dummy/app/templates/visual.hbs b/tests/dummy/app/templates/visual.hbs index e6950b2e3..65f43cd82 100644 --- a/tests/dummy/app/templates/visual.hbs +++ b/tests/dummy/app/templates/visual.hbs @@ -385,6 +385,15 @@ + + + + + + + + + diff --git a/tests/integration/components/o-s-s/avatar-test.ts b/tests/integration/components/o-s-s/avatar-test.ts index 1339d2478..538db4f31 100644 --- a/tests/integration/components/o-s-s/avatar-test.ts +++ b/tests/integration/components/o-s-s/avatar-test.ts @@ -3,7 +3,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { find, render, setupOnerror, waitFor, waitUntil } from '@ember/test-helpers'; -import { SizeDefinition, DEFAULT_IMAGE_URL } from '@upfluence/oss-components/components/o-s-s/avatar'; +import { SizeDefinition, AvatarSkins, DEFAULT_IMAGE_URL } from '@upfluence/oss-components/components/o-s-s/avatar'; module('Integration | Component | o-s-s/avatar', function (hooks) { setupRenderingTest(hooks); @@ -63,6 +63,25 @@ module('Integration | Component | o-s-s/avatar', function (hooks) { }); }); + module('Skins', function () { + test('it sets no skin class when skin is not provided', async function (assert) { + await render(hbs``); + + assert.dom('.upf-avatar').exists(); + assert.dom('.upf-avatar').hasNoClass(`upf-avatar--skin`); + }); + + AvatarSkins.forEach((skin) => { + test(`it sets the right class when using a supported skin: ${skin}`, async function (assert) { + this.skin = skin; + await render(hbs``); + + assert.dom('.upf-avatar').hasClass(`upf-avatar--skin`).hasClass(`upf-avatar--skin-${skin}`); + }) + }) + + }) + module('Sizes', function () { test('it sets the right default class when size is not provided', async function (assert) { await render(hbs``); @@ -101,6 +120,17 @@ module('Integration | Component | o-s-s/avatar', function (hooks) { await render(hbs``); }); + test('it throws an error if the wrong skin argument is passed', async function (assert: Assert) { + setupOnerror((err: Error) => { + assert.equal( + err.message, + `Assertion Failed: [component][OSS::Avatar] Unknown skin. Available skins are: ${AvatarSkins.join(', ')}` + ); + }); + + await render(hbs``); + }); + test('it displays the initials when both initials and image are provided and the image fails to load', async function (assert) { await render(hbs``); await waitFor('.upf-avatar span'); From e96c419adc0f8072f95e9f7dcdc4679cd875c144 Mon Sep 17 00:00:00 2001 From: Antoine Prentout Date: Fri, 24 Jul 2026 15:21:07 +0200 Subject: [PATCH 2/3] Fix linter --- addon/components/o-s-s/avatar.stories.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/addon/components/o-s-s/avatar.stories.js b/addon/components/o-s-s/avatar.stories.js index 414b50a6a..b873582d4 100644 --- a/addon/components/o-s-s/avatar.stories.js +++ b/addon/components/o-s-s/avatar.stories.js @@ -1,7 +1,16 @@ import { hbs } from 'ember-cli-htmlbars'; const SizeTypes = ['xs', 'sm', 'md', 'lg']; -const SkinType = ['danger', 'xtd-orange', 'xtd-violet-light', 'xtd-violet', 'xtd-lime', 'primary', 'primary-light', 'xtd-cyan']; +const SkinType = [ + 'danger', + 'xtd-orange', + 'xtd-violet-light', + 'xtd-violet', + 'xtd-lime', + 'primary', + 'primary-light', + 'xtd-cyan' +]; export default { title: 'Components/OSS::Avatar', From 6f06924363c37de6820ae8e75f8d9862dc964242 Mon Sep 17 00:00:00 2001 From: Antoine Prentout Date: Fri, 24 Jul 2026 16:48:14 +0200 Subject: [PATCH 3/3] Fix pr feedbacks --- addon/components/o-s-s/avatar.stories.js | 2 +- addon/components/o-s-s/avatar.ts | 9 +++++---- tests/integration/components/o-s-s/avatar-test.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/addon/components/o-s-s/avatar.stories.js b/addon/components/o-s-s/avatar.stories.js index b873582d4..aa1de57da 100644 --- a/addon/components/o-s-s/avatar.stories.js +++ b/addon/components/o-s-s/avatar.stories.js @@ -55,7 +55,7 @@ export default { description: 'Adjust the skin of the avatar component', table: { type: { - summary: SkinType.join('|') + summary: SkinType.join(' | ') }, defaultValue: { summary: undefined } }, diff --git a/addon/components/o-s-s/avatar.ts b/addon/components/o-s-s/avatar.ts index 3a33535a6..b02c93766 100644 --- a/addon/components/o-s-s/avatar.ts +++ b/addon/components/o-s-s/avatar.ts @@ -51,11 +51,11 @@ export default class OSSAvatar extends Component { } get computedClass(): string { - let classes = 'upf-avatar '; + const classes = ['upf-avatar']; const size: SizeType = this.args.size || 'md'; if (this.args.loading) { - classes = classes.concat('upf-avatar--loading '); + classes.push('upf-avatar--loading'); } if (this.args.skin) { @@ -63,7 +63,7 @@ export default class OSSAvatar extends Component { `[component][OSS::Avatar] Unknown skin. Available skins are: ${AvatarSkins.join(', ')}`, AvatarSkins.includes(this.args.skin as SkinType) ); - classes = classes.concat(`upf-avatar--skin upf-avatar--skin-${this.args.skin} `); + classes.push('upf-avatar--skin', `upf-avatar--skin-${this.args.skin}`); } assert( @@ -71,7 +71,8 @@ export default class OSSAvatar extends Component { Object.keys(SizeDefinition).includes(size as SizeType) ); - return classes.concat(SizeDefinition[size as SizeType]); + classes.push(SizeDefinition[size as SizeType]) + return classes.join(' ') } @action diff --git a/tests/integration/components/o-s-s/avatar-test.ts b/tests/integration/components/o-s-s/avatar-test.ts index 538db4f31..1590a3932 100644 --- a/tests/integration/components/o-s-s/avatar-test.ts +++ b/tests/integration/components/o-s-s/avatar-test.ts @@ -3,7 +3,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { find, render, setupOnerror, waitFor, waitUntil } from '@ember/test-helpers'; -import { SizeDefinition, AvatarSkins, DEFAULT_IMAGE_URL } from '@upfluence/oss-components/components/o-s-s/avatar'; +import { SizeDefinition, AvatarSkins, DEFAULT_IMAGE_URL } from '@upfluence/oss-components/components/o-s-s/avatar'; module('Integration | Component | o-s-s/avatar', function (hooks) { setupRenderingTest(hooks);