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`
`;
}
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(
- `
`,
- );
- });
+ describe('initials', () => {
+ it('renders an image when imgSrc is set', async () => {
+ const avatar = await fixture(
+ html``,
+ );
+ expect(avatar).shadowDom.to.equal(
+ `
`,
+ );
+ });
- it('renders an image with alt text when imgSrc and text is set', async () => {
- const avatar = await fixture(
- html``,
- );
- expect(avatar).shadowDom.to.equal(
- `
`,
- );
- });
+ it('renders an image with alt text when imgSrc and text is set', async () => {
+ const avatar = await fixture(
+ html``,
+ );
+ expect(avatar).shadowDom.to.equal(
+ `
`,
+ );
+ });
- 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 () => {