diff --git a/packages/uui-avatar/lib/uui-avatar.element.ts b/packages/uui-avatar/lib/uui-avatar.element.ts index 4ebad1b9a..12c6052b4 100644 --- a/packages/uui-avatar/lib/uui-avatar.element.ts +++ b/packages/uui-avatar/lib/uui-avatar.element.ts @@ -1,6 +1,6 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { css, html, LitElement } from 'lit'; -import { property, state } from 'lit/decorators.js'; +import { property } from 'lit/decorators.js'; /** * Avatar for displaying users @@ -43,19 +43,20 @@ export class UUIAvatarElement extends LitElement { * @default '' */ @property({ type: String, reflect: true }) - get name() { - return this._name; - } - set name(newVal) { - const oldValue = this._name; - this._name = newVal; - this.initials = this.createInitials(this._name); - this.requestUpdate('title', oldValue); - } - private _name = ''; + name = ''; - @state() - private initials = ''; + /** + * Use this to override the initials generated from the name. + * @type {string} + * @attr + * @default undefined + */ + @property({ type: String }) + initials?: string; + + private get _initials() { + return this.initials?.substring(0, 3) || this.createInitials(this.name); + } connectedCallback() { super.connectedCallback(); @@ -71,16 +72,16 @@ export class UUIAvatarElement extends LitElement { return initials; } - const words = name.match(/(\w+)/g); - + const matches = [...name.matchAll(/(?:^|\s)(.)/g)]; + const words = matches.map(m => m[1]).join(''); if (!words?.length) { return initials; } - initials = words[0].substring(0, 1); + initials = words[0].charAt(0); if (words.length > 1) { - initials += words[words.length - 1].substring(0, 1); + initials += words[words.length - 1].charAt(0); } return initials.toUpperCase(); @@ -90,14 +91,14 @@ export class UUIAvatarElement extends LitElement { return html` ${this.initials}`; } render() { return html` ${this.imgSrc ? this.renderImage() : ''} - ${!this.imgSrc ? this.initials : ''} + ${!this.imgSrc ? this._initials : ''} `; } diff --git a/packages/uui-avatar/lib/uui-avatar.story.ts b/packages/uui-avatar/lib/uui-avatar.story.ts index 95eb3f866..e70b77e2d 100644 --- a/packages/uui-avatar/lib/uui-avatar.story.ts +++ b/packages/uui-avatar/lib/uui-avatar.story.ts @@ -59,6 +59,13 @@ export const Colors: Story = { }, }; +export const Initials: Story = { + args: { + name: 'Umbraco HQ', + initials: 'AB', + }, +}; + /** * Slotted content might overflow, use the `overflow` attribute to hide overflow. */ diff --git a/packages/uui-avatar/lib/uui-avatar.test.ts b/packages/uui-avatar/lib/uui-avatar.test.ts index debe99c5b..be47610d1 100644 --- a/packages/uui-avatar/lib/uui-avatar.test.ts +++ b/packages/uui-avatar/lib/uui-avatar.test.ts @@ -41,34 +41,77 @@ describe('UuiAvatar', () => { }); }); - it('renders an image when imgSrc is set', async () => { - const avatar = await fixture( - html``, - ); - expect(avatar).shadowDom.to.equal( - `MA`, - ); - }); + describe('initials', () => { + it('renders an image when imgSrc is set', async () => { + const avatar = await fixture( + html``, + ); + expect(avatar).shadowDom.to.equal( + `MA`, + ); + }); - it('renders an image with alt text when imgSrc and text is set', async () => { - const avatar = await fixture( - html``, - ); - expect(avatar).shadowDom.to.equal( - `AT`, - ); - }); + it('renders an image with alt text when imgSrc and text is set', async () => { + const avatar = await fixture( + html``, + ); + expect(avatar).shadowDom.to.equal( + `AT`, + ); + }); - it('shows the first initial when text is used and there is no image', async () => { - const avatar = await fixture(html``); - expect(avatar).shadowDom.to.equal('F'); - }); + it('shows the first initial when text is used and there is no image', async () => { + const avatar = await fixture( + html``, + ); + expect(avatar).shadowDom.to.equal('F'); + }); + + it('shows the first and last initial when text is used and there is no image', async () => { + element.name = 'First Second Last'; + await element.updateComplete; + expect(element).shadowDom.to.equal('FL'); + }); - it('shows the first and last initial when text is used and there is no image', async () => { - const avatar = await fixture( - html``, - ); - expect(avatar).shadowDom.to.equal('FL'); + it('supports unicode characters', async () => { + element.name = '👩‍💻'; + await element.updateComplete; + expect(element).shadowDom.to.equal('\ud83d'); + + element.name = '👩‍💻 👨‍💻'; + await element.updateComplete; + expect(element).shadowDom.to.equal('\ud83d\ud83d'); + }); + + it('supports non-latin characters', async () => { + element.name = 'Привет Ša'; + await element.updateComplete; + expect(element).shadowDom.to.equal('ПŠ'); + + element.name = 'Привет'; + await element.updateComplete; + expect(element).shadowDom.to.equal('П'); + + element.name = 'UlŠa Mya'; + await element.updateComplete; + expect(element).shadowDom.to.equal('UM'); + + element.name = 'åse hylle'; + await element.updateComplete; + expect(element).shadowDom.to.equal('ÅH'); + }); + + it('supports overriding initials', async () => { + element.initials = 'AB'; + await element.updateComplete; + expect(element).shadowDom.to.equal('AB'); + }); + + it('shows a maximum of 3 characters', async () => { + element.initials = '1234'; + await element.updateComplete; + expect(element).shadowDom.to.equal('123'); + }); }); it('passes the a11y audit', async () => {