diff --git a/packages/angular/src/components.ts b/packages/angular/src/components.ts index 1cadcb1b060..9f5f882bed0 100644 --- a/packages/angular/src/components.ts +++ b/packages/angular/src/components.ts @@ -89,14 +89,14 @@ export declare interface IxApplicationHeader extends Components.IxApplicationHea @ProxyCmp({ - inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username'] + inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username', 'wrapUsername'] }) @Component({ selector: 'ix-avatar', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username'], + inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username', 'wrapUsername'], standalone: false }) export class IxAvatar { diff --git a/packages/angular/standalone/src/components.ts b/packages/angular/standalone/src/components.ts index 9d1ca8c30d4..6dc5d6c42dd 100644 --- a/packages/angular/standalone/src/components.ts +++ b/packages/angular/standalone/src/components.ts @@ -199,14 +199,14 @@ export declare interface IxApplicationHeader extends Components.IxApplicationHea @ProxyCmp({ defineCustomElementFn: defineIxAvatar, - inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username'] + inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username', 'wrapUsername'] }) @Component({ selector: 'ix-avatar', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username'], + inputs: ['ariaLabelTooltip', 'extra', 'image', 'initials', 'tooltipText', 'username', 'wrapUsername'], }) export class IxAvatar { protected el: HTMLIxAvatarElement; diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 2a62dbd0e69..ec413968089 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -277,6 +277,12 @@ export namespace Components { * If set an info card displaying the username will be placed inside the dropdown. Note: Only working if avatar is part of the ix-application-header */ "username"?: string; + /** + * If `true`, the username and extra text will wrap to multiple lines instead of being truncated with an ellipsis. Note: Only working if avatar is part of the ix-application-header + * @since 5.2.0 + * @default false + */ + "wrapUsername": boolean; } interface IxBlind { /** @@ -7043,6 +7049,12 @@ declare namespace LocalJSX { * If set an info card displaying the username will be placed inside the dropdown. Note: Only working if avatar is part of the ix-application-header */ "username"?: string; + /** + * If `true`, the username and extra text will wrap to multiple lines instead of being truncated with an ellipsis. Note: Only working if avatar is part of the ix-application-header + * @since 5.2.0 + * @default false + */ + "wrapUsername"?: boolean; } interface IxBlind { /** @@ -11999,6 +12011,7 @@ declare namespace LocalJSX { "extra": string; "tooltipText": string; "ariaLabelTooltip": string; + "wrapUsername": boolean; } interface IxBlindAttributes { "collapsed": boolean; diff --git a/packages/core/src/components/avatar/avatar.scss b/packages/core/src/components/avatar/avatar.scss index c56e9708d28..e0dc94c1613 100644 --- a/packages/core/src/components/avatar/avatar.scss +++ b/packages/core/src/components/avatar/avatar.scss @@ -86,6 +86,7 @@ flex-direction: column; justify-content: center; max-width: 10rem; + min-width: 0; width: 100%; overflow: hidden; } @@ -101,6 +102,32 @@ overflow: hidden; text-overflow: ellipsis; } + + &.user-info--no-truncate { + height: auto; + width: 12.75rem; + min-width: 12.75rem; + max-width: 12.75rem; + + .user { + max-width: unset; + overflow: visible; + } + + .username { + white-space: normal; + overflow: visible; + text-overflow: unset; + overflow-wrap: break-word; + } + + .extra { + white-space: normal; + overflow: visible; + text-overflow: unset; + overflow-wrap: break-word; + } + } } } diff --git a/packages/core/src/components/avatar/avatar.tsx b/packages/core/src/components/avatar/avatar.tsx index ffcc7cb51dc..e59560b04e0 100644 --- a/packages/core/src/components/avatar/avatar.tsx +++ b/packages/core/src/components/avatar/avatar.tsx @@ -107,11 +107,19 @@ function UserInfo( userName: string; extra?: string; ariaLabel?: string; + wrapUsername?: boolean; }> ) { + const noTruncate = props.wrapUsername === true; return ( -
event.preventDefault()}> +
event.preventDefault()} + > {this.hasSlottedElements && ( e.preventDefault()}> diff --git a/packages/core/src/components/avatar/test/avatar.ct.ts b/packages/core/src/components/avatar/test/avatar.ct.ts index 791ee2493c3..5467d9989ad 100644 --- a/packages/core/src/components/avatar/test/avatar.ct.ts +++ b/packages/core/src/components/avatar/test/avatar.ct.ts @@ -120,6 +120,96 @@ regressionTest.describe('embedded into header', () => { } ); + regressionTest( + 'should apply no-truncate class when wrapUsername is true', + async ({ page, mount }) => { + await page.setViewportSize(viewPorts.lg); + await mount( + ` + + + + + ` + ); + + const avatar = page.locator('ix-avatar'); + await avatar.click(); + + await expect(avatar.locator('.user-info')).toHaveClass( + /\buser-info--no-truncate\b/ + ); + } + ); + + regressionTest( + 'should not apply no-truncate class when wrapUsername is false', + async ({ page, mount }) => { + await page.setViewportSize(viewPorts.lg); + await mount( + ` + + + + + ` + ); + + const avatar = page.locator('ix-avatar'); + await avatar.click(); + + await expect(avatar.locator('.user-info')).not.toHaveClass( + /\buser-info--no-truncate\b/ + ); + } + ); + + regressionTest( + 'should keep the popup width fixed and wrap long usernames when wrapUsername is true', + async ({ page, mount }) => { + await page.setViewportSize(viewPorts.lg); + await mount( + ` + + + + + ` + ); + + const avatar = page.locator('ix-avatar'); + await avatar.click(); + + const userInfo = avatar.locator('.user-info'); + + const initialMetrics = await userInfo.evaluate((element) => { + const rect = element.getBoundingClientRect(); + return { + width: Math.round(rect.width), + height: Math.round(rect.height), + }; + }); + + const longUsername = 'verylongstringthatisnotfullydisplayed'; + await avatar.evaluate((element, value) => { + element.setAttribute('username', value); + }, longUsername); + + await expect(userInfo).toHaveText(new RegExp(longUsername)); + + const updatedMetrics = await userInfo.evaluate((element) => { + const rect = element.getBoundingClientRect(); + return { + width: Math.round(rect.width), + height: Math.round(rect.height), + }; + }); + + expect(updatedMetrics.width).toBe(initialMetrics.width); + expect(updatedMetrics.height).toBeGreaterThan(initialMetrics.height); + } + ); + regressionTest('should show no tooltip', async ({ page, mount }) => { await mount(``); diff --git a/packages/react/src/components/components.server.ts b/packages/react/src/components/components.server.ts index c7fb903f835..64fced4eeb4 100644 --- a/packages/react/src/components/components.server.ts +++ b/packages/react/src/components/components.server.ts @@ -195,7 +195,8 @@ export const IxAvatar: StencilReactComponent) : undefined, clientModule: clientComponents.IxAvatar as StencilReactComponent, diff --git a/packages/vue/src/components/ix-avatar.ts b/packages/vue/src/components/ix-avatar.ts index 8cbdd99664f..55862c8f420 100644 --- a/packages/vue/src/components/ix-avatar.ts +++ b/packages/vue/src/components/ix-avatar.ts @@ -12,5 +12,6 @@ export const IxAvatar: StencilVueComponent = /*@__PURE__*/ defineC 'username', 'extra', 'tooltipText', - 'ariaLabelTooltip' + 'ariaLabelTooltip', + 'wrapUsername' ]);