Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/angular/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<ng-content></ng-content>',
// 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 {
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/standalone/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<ng-content></ng-content>',
// 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;
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +280 to +285

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ“ Maintainability & Code Quality | ๐ŸŸ  Major | โšก Quick win

Add a changeset for this public API change.

wrapUsername adds a public ix-avatar property and changes username and extra-text rendering. Add a changeset for the affected package with the consumer-facing behavior. If no changeset is intended, state why the change is internal-only.

As per path instructions, โ€œChangesets are required for public API updates and behavior changes.โ€

Also applies to: 7052-7057, 12014-12014

๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components.d.ts` around lines 280 - 285, Add a changeset
for the affected package describing the new public ix-avatar wrapUsername
property and its behavior of wrapping username and extra text instead of
truncating them. Ensure the changeset uses the appropriate release level; only
omit it if you can establish that the change is internal-only and document that
rationale.

Source: Path instructions

}
interface IxBlind {
/**
Expand Down Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -11999,6 +12011,7 @@ declare namespace LocalJSX {
"extra": string;
"tooltipText": string;
"ariaLabelTooltip": string;
"wrapUsername": boolean;
}
interface IxBlindAttributes {
"collapsed": boolean;
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/components/avatar/avatar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
flex-direction: column;
justify-content: center;
max-width: 10rem;
min-width: 0;
width: 100%;
overflow: hidden;
}
Expand All @@ -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;
}
}
}
}

Expand Down
19 changes: 18 additions & 1 deletion packages/core/src/components/avatar/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,19 @@ function UserInfo(
userName: string;
extra?: string;
ariaLabel?: string;
wrapUsername?: boolean;
}>
) {
const noTruncate = props.wrapUsername === true;
return (
<Fragment>
<div class="user-info" onClick={(event) => event.preventDefault()}>
<div
class={{
'user-info': true,
'user-info--no-truncate': noTruncate,
}}
onClick={(event) => event.preventDefault()}
>
<AvatarImage
image={props.image}
initials={props.initials}
Expand Down Expand Up @@ -182,6 +190,14 @@ export class Avatar
*/
@Prop() ariaLabelTooltip?: 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
*/
@Prop() wrapUsername: boolean = false;

@State() isClosestApplicationHeader = false;
@State() dropdownShow = false;
@State() hasSlottedElements = false;
Expand Down Expand Up @@ -359,6 +375,7 @@ export class Avatar
initials={this.initials}
userName={this.username}
ariaLabel={ariaLabel}
wrapUsername={this.wrapUsername}
/>
{this.hasSlottedElements && (
<ix-divider onClick={(e) => e.preventDefault()}></ix-divider>
Expand Down
90 changes: 90 additions & 0 deletions packages/core/src/components/avatar/test/avatar.ct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
`
<ix-application-header name="Test">
<ix-avatar username="foo" wrap-username>
</ix-avatar>
</ix-application-header>
`
);

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(
`
<ix-application-header name="Test">
<ix-avatar username="foo">
</ix-avatar>
</ix-application-header>
`
);

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(
`
<ix-application-header name="Test">
<ix-avatar username="foo" wrap-username>
</ix-avatar>
</ix-application-header>
`
);

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);
}
Comment on lines +123 to +210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐ŸŽฏ Functional Correctness | ๐ŸŸก Minor | โšก Quick win

Add the required accessibility and hydration coverage.

Add a makeAxeBuilder() assertion for the wrapped dropdown state. Add a basic assertion that the mounted Avatar is hydrated before interaction.

As per coding guidelines, โ€œInclude accessibility coverage using makeAxeBuilder and a basic hydration/render test in component test files.โ€ As per path instructions, โ€œEnsure tests include both: accessibility test using makeAxeBuilder() and renders test verifying hydration.โ€

๐Ÿงฐ Tools
๐Ÿช› ast-grep (0.45.0)

[warning] 197-197: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(longUsername)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[warning] 197-197: Do not use variable for regular expressions
Context: new RegExp(longUsername)
Note: [CWE-1333] Inefficient Regular Expression Complexity. Security best practice.

(regexp-non-literal-typescript)

๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/avatar/test/avatar.ct.ts` around lines 123 -
210, Add a hydration assertion immediately after mounting the Avatar fixture and
before interacting with it, verifying the mounted component is hydrated. In the
wrapped dropdown test using wrap-username, run makeAxeBuilder() against the
expanded state and assert there are no accessibility violations, while
preserving the existing class and layout assertions.

Sources: Coding guidelines, Path instructions

Comment on lines +167 to +210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐ŸŽฏ Functional Correctness | ๐ŸŸก Minor | โšก Quick win

Test the visible dropdown result.

The test measures only .user-info. It can pass if ix-dropdown clips the expanded content. Assert that the dropdown panel expands or that the complete long content remains visible. Include a long extra value because wrapUsername changes both user-information fields.

As per coding guidelines, โ€œUpdate tests ... when user-facing behavior ... changes.โ€

๐Ÿงฐ Tools
๐Ÿช› ast-grep (0.45.0)

[warning] 197-197: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(longUsername)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[warning] 197-197: Do not use variable for regular expressions
Context: new RegExp(longUsername)
Note: [CWE-1333] Inefficient Regular Expression Complexity. Security best practice.

(regexp-non-literal-typescript)

๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/avatar/test/avatar.ct.ts` around lines 167 -
210, Update the regression test around the avatar popup and .user-info to
validate the visible ix-dropdown panel rather than only the inner element
metrics. Supply a long extra value alongside the long username, then assert the
dropdown expands or that the complete username and extra content remain visible
after the update.

Source: Coding guidelines

);

regressionTest('should show no tooltip', async ({ page, mount }) => {
await mount(`<ix-avatar aria-label-tooltip="myTooltip"></ix-avatar>`);

Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/components/components.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ export const IxAvatar: StencilReactComponent<IxAvatarElement, IxAvatarEvents, Co
username: 'username',
extra: 'extra',
tooltipText: 'tooltip-text',
ariaLabelTooltip: 'aria-label-tooltip'
ariaLabelTooltip: 'aria-label-tooltip',
wrapUsername: 'wrap-username'
},
hydrateModule: typeof window === 'undefined' ? (import('@siemens/ix/hydrate') as Promise<HydrateModule>) : undefined,
clientModule: clientComponents.IxAvatar as StencilReactComponent<IxAvatarElement, IxAvatarEvents, Components.IxAvatar>,
Expand Down
3 changes: 2 additions & 1 deletion packages/vue/src/components/ix-avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export const IxAvatar: StencilVueComponent<JSX.IxAvatar> = /*@__PURE__*/ defineC
'username',
'extra',
'tooltipText',
'ariaLabelTooltip'
'ariaLabelTooltip',
'wrapUsername'
]);
Loading