Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core/select): prevent list items to expand beyond screen width and add dropdown width properties #1669

Merged
merged 13 commits into from
Jan 30, 2025
Merged
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
7 changes: 7 additions & 0 deletions .changeset/orange-geckos-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@siemens/ix-angular': minor
'@siemens/ix': minor
'@siemens/ix-vue': minor
---

Prevent `ix-select` list items to expand beyond screen width and add properties: dropdown-width + dropdown-max-width
4 changes: 2 additions & 2 deletions packages/angular/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2186,15 +2186,15 @@ export declare interface IxRow extends Components.IxRow {}


@ProxyCmp({
inputs: ['allowClear', 'disabled', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'selectedIndices', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
inputs: ['allowClear', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'selectedIndices', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
methods: ['getNativeInputElement', 'focusInput']
})
@Component({
selector: 'ix-select',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['allowClear', 'disabled', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'selectedIndices', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
inputs: ['allowClear', 'disabled', 'dropdownMaxWidth', 'dropdownWidth', 'editable', 'helperText', 'hideListHeader', 'i18nNoMatches', 'i18nPlaceholder', 'i18nPlaceholderEditable', 'i18nSelectListHeader', 'infoText', 'invalidText', 'label', 'mode', 'name', 'readonly', 'required', 'selectedIndices', 'showTextAsTooltip', 'validText', 'value', 'warningText'],
})
export class IxSelect {
protected el: HTMLElement;
Expand Down
58 changes: 58 additions & 0 deletions packages/core/component-doc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16946,6 +16946,64 @@
"optional": false,
"required": false
},
{
"name": "dropdownMaxWidth",
"type": "string | undefined",
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"mutable": false,
"attr": "dropdown-max-width",
"reflectToAttr": false,
"docs": "The maximum width of the dropdown element with value and unit (e.g. \"200px\" or \"12.5rem\").\nBy default the maximum width of the dropdown element is set to 100%.",
"docsTags": [
{
"name": "since",
"text": "2.7.0"
}
],
"values": [
{
"type": "string"
},
{
"type": "undefined"
}
],
"optional": true,
"required": false
},
{
"name": "dropdownWidth",
"type": "string | undefined",
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"mutable": false,
"attr": "dropdown-width",
"reflectToAttr": false,
"docs": "The width of the dropdown element with value and unit (e.g. \"200px\" or \"12.5rem\").",
"docsTags": [
{
"name": "since",
"text": "2.7.0"
}
],
"values": [
{
"type": "string"
},
{
"type": "undefined"
}
],
"optional": true,
"required": false
},
{
"name": "editable",
"type": "boolean",
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,16 @@ export namespace Components {
* If true the select will be in disabled state
*/
"disabled": boolean;
/**
* The maximum width of the dropdown element with value and unit (e.g. "200px" or "12.5rem"). By default the maximum width of the dropdown element is set to 100%.
* @since 2.7.0
*/
"dropdownMaxWidth"?: string;
/**
* The width of the dropdown element with value and unit (e.g. "200px" or "12.5rem").
* @since 2.7.0
*/
"dropdownWidth"?: string;
/**
* Select is extendable
*/
Expand Down Expand Up @@ -7685,6 +7695,16 @@ declare namespace LocalJSX {
* If true the select will be in disabled state
*/
"disabled"?: boolean;
/**
* The maximum width of the dropdown element with value and unit (e.g. "200px" or "12.5rem"). By default the maximum width of the dropdown element is set to 100%.
* @since 2.7.0
*/
"dropdownMaxWidth"?: string;
/**
* The width of the dropdown element with value and unit (e.g. "200px" or "12.5rem").
* @since 2.7.0
*/
"dropdownWidth"?: string;
/**
* Select is extendable
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@
}
}

ix-dropdown {
max-width: 100%;
danielleroux marked this conversation as resolved.
Show resolved Hide resolved
}

.dropdown-visible {
--ix-icon-button-color: var(--theme-color-dynamic--hover);
}
Expand Down
34 changes: 31 additions & 3 deletions packages/core/src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,22 @@ export class Select implements IxInputFieldComponent<string | string[]> {
*/
@Prop() hideListHeader = false;

/**
* The width of the dropdown element with value and unit (e.g. "200px" or "12.5rem").
*
* @since 2.7.0
*/
@Prop() dropdownWidth?: string;

/**
* The maximum width of the dropdown element with value and unit (e.g. "200px" or "12.5rem").
* By default the maximum width of the dropdown element is set to 100%.
*
* @since 2.7.0
*
*/
@Prop() dropdownMaxWidth?: string;

/**
* Value changed
* @since 2.0.0
Expand Down Expand Up @@ -953,13 +969,25 @@ export class Select implements IxInputFieldComponent<string | string[]> {
onShowChanged={(e) => this.dropdownVisibilityChanged(e)}
placement="bottom-start"
overwriteDropdownStyle={async () => {
const styleOverwrites: Partial<CSSStyleDeclaration> = {};

const minWidth = this.hostElement.shadowRoot
?.querySelector('.select')
?.getBoundingClientRect().width;

return {
minWidth: `${minWidth}px`,
};
if (minWidth) {
styleOverwrites.minWidth = `${minWidth}px`;
}

if (this.dropdownWidth) {
styleOverwrites.width = `min(${this.dropdownWidth}, 100%)`;
}

if (this.dropdownMaxWidth) {
styleOverwrites.maxWidth = `min(${this.dropdownMaxWidth}, 100%)`;
}

return styleOverwrites;
}}
>
<div
Expand Down
120 changes: 120 additions & 0 deletions packages/core/src/components/select/test/select.ct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,123 @@ test.describe('Enter selection with non-existing and existing items', () => {
await expect(input).toHaveValue('Item 1');
});
});

test.describe('Dropdown width', () => {
test('should be 25rem when dropdown-width is set to 35rem and dropdown-max-width to 25rem', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1" dropdown-width="35rem" dropdown-max-width="25rem">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
expect(box?.width).toBe(16 * 25);
});

test('should be 25rem when dropdown-width is set to 25 and dropdown-max-width to 35rem', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1" dropdown-width="25rem" dropdown-max-width="35rem">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
expect(box?.width).toBe(16 * 25);
});

test('should be 25rem when dropdown-width is set to 25 and dropdown-max-width is not set', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1" dropdown-width="25rem">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
expect(box?.width).toBe(16 * 25);
});

test('should be 35rem when dropdown-width is not set and dropdown-max-width is set to 35rem', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1" dropdown-max-width="35rem">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
expect(box?.width).toBe(16 * 35);
});

test('should be 100% when dropdown-width and dropdown-max-width are not set', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
const pageWidth = page.viewportSize()?.width;

expect(box?.width).toBe(pageWidth);
});
});

test('should be 100% when dropdown-max-width is greater than the viewport width', async ({
mount,
page,
}) => {
await mount(`<ix-select value="1" dropdown-max-width="10000rem">
<ix-select-item label="this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option. this is an example for a very long selection option." value="1"></ix-select-item>
</ix-select>`);

const select = page.locator('ix-select');

await page.locator('[data-select-dropdown]').click();

const dropdown = select.locator('ix-dropdown');
await expect(dropdown).toBeVisible();

const box = await dropdown.boundingBox();
const pageWidth = page.viewportSize()?.width;

expect(box?.width).toBe(pageWidth);
});
2 changes: 1 addition & 1 deletion packages/documentation/docs/controls/_select_styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ A select component allows users to choose from a list of options. It supports si
- **Overflow:**
- The text in an input field is truncated with the length of the container.
- On the multiselect, the selected items break into a second line and then show a scrollbar if it extends beyond two lines.
- The dropdown list is scrollable when the list exceeds the container height. Its width is defined by the longest item.
- The dropdown list is scrollable when the list exceeds the container height. Its width is defined by the longest item. The maximum width of the dropdown list is set to 100% by default. Use the properties `dropdownWidth` and `dropdownMaxWidth` to customize the dimensions.
- **Alignment:** Selects are always aligned to the left, while right alignment is reserved exclusively for [number inputs](input-number.mdx).

### States
Expand Down
25 changes: 25 additions & 0 deletions packages/storybook-docs/src/stories/select.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,28 @@ export const editableSelect: Story = {
disabled: false,
},
};

export const editable_with_dropdown_width: Story = {
render: ({ editable, disabled, dropdownWidth, dropdownMaxWidth }) => {
return html` <ix-select
editable=${editable}
disabled=${disabled}
dropdown-width=${dropdownWidth}
dropdown-max-width=${dropdownMaxWidth}
>
<ix-select-item
label="this is an example for a very long selection option. this is an example for a very long selection option."
value="1"
></ix-select-item>
<ix-select-item label="Item 2" value="2"></ix-select-item>
<ix-select-item label="Item 3" value="3"></ix-select-item>
<ix-select-item label="Item 4" value="4"></ix-select-item>
</ix-select>`;
},
args: {
editable: true,
disabled: false,
dropdownWidth: '35rem',
dropdownMaxWidth: '25rem',
},
};
2 changes: 2 additions & 0 deletions packages/vue/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,8 @@ export const IxSelect = /*@__PURE__*/ defineContainer<JSX.IxSelect, JSX.IxSelect
'i18nSelectListHeader',
'i18nNoMatches',
'hideListHeader',
'dropdownWidth',
'dropdownMaxWidth',
'valueChange',
'itemSelectionChange',
'inputChange',
Expand Down
Loading