feat(select): add props to customize toggle icons (#27648)

Issue number: resolves #17248

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

While the `icon` shadow part allows customization of the existing toggle
icon, developers do not have a way to specify a different icon to use
entirely.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

New props `toggleIcon` and `expandedIcon` added. (Design docs are
[here](https://github.com/ionic-team/ionic-framework-design-documents/blob/main/projects/ionic-framework/components/select/0002-custom-icons.md)
and
[here](https://github.com/ionic-team/ionic-framework-design-documents/blob/main/projects/ionic-framework/components/select/0003-custom-icon-on-open.md)
respectively.)

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Docs PR: https://github.com/ionic-team/ionic-docs/pull/2996
Dev build: `7.0.15-dev.11687278023.161b97d8`

---------

Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
Amanda Johnston
2023-06-20 12:18:36 -05:00
committed by GitHub
parent 8179366845
commit 95e28b6629
14 changed files with 151 additions and 9 deletions

View File

@ -94,7 +94,7 @@
* when the select is activated.
* This should only happen on MD.
*/
:host(.select-expanded:not(.legacy-select)) .select-icon {
:host(.select-expanded:not(.legacy-select):not(.has-expanded-icon)) .select-icon {
@include transform(rotate(180deg));
}
@ -123,7 +123,7 @@
@include transform(translate3d(0, -9px, 0));
}
:host-context(.item-has-focus) .select-icon {
:host-context(.item-has-focus):host(:not(.has-expanded-icon)) .select-icon {
@include transform(rotate(180deg));
}
@ -131,8 +131,8 @@
* Ensure that the translation we did
* above is preserved when we rotate the select icon.
*/
:host-context(.item-has-focus.item-label-stacked) .select-icon,
:host-context(.item-has-focus.item-label-floating:not(.item-fill-outline)) .select-icon {
:host-context(.item-has-focus.item-label-stacked):host(:not(.has-expanded-icon)) .select-icon,
:host-context(.item-has-focus.item-label-floating:not(.item-fill-outline)):host(:not(.has-expanded-icon)) .select-icon {
@include transform(rotate(180deg), translate3d(0, -9px, 0));
}

View File

@ -183,6 +183,19 @@ export class Select implements ComponentInterface {
*/
@Prop() selectedText?: string | null;
/**
* The toggle icon to use. Defaults to `chevronExpand` for `ios` mode,
* or `caretDownSharp` for `md` mode.
*/
@Prop() toggleIcon?: string;
/**
* The toggle icon to show when the select is open. If defined, the icon
* rotation behavior in `md` mode will be disabled. If undefined, `toggleIcon`
* will be used for when the select is both open and closed.
*/
@Prop() expandedIcon?: string;
/**
* The shape of the select. If "round" it will have an increased border radius.
*/
@ -820,7 +833,8 @@ export class Select implements ComponentInterface {
}
private renderSelect() {
const { disabled, el, isExpanded, labelPlacement, justify, placeholder, fill, shape, name, value } = this;
const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } =
this;
const mode = getIonMode(this);
const hasFloatingOrStackedLabel = labelPlacement === 'floating' || labelPlacement === 'stacked';
const justifyEnabled = !hasFloatingOrStackedLabel;
@ -839,6 +853,7 @@ export class Select implements ComponentInterface {
'in-item-color': hostContext('ion-item.ion-color', el),
'select-disabled': disabled,
'select-expanded': isExpanded,
'has-expanded-icon': expandedIcon !== undefined,
'has-value': this.hasValue(),
'has-placeholder': placeholder !== undefined,
'ion-focusable': true,
@ -893,7 +908,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
this.hasLoggedDeprecationWarning = true;
}
const { disabled, el, inputId, isExpanded, name, placeholder, value } = this;
const { disabled, el, inputId, isExpanded, expandedIcon, name, placeholder, value } = this;
const mode = getIonMode(this);
const { labelText, labelId } = getAriaLabel(el, inputId);
@ -926,6 +941,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
'in-item-color': hostContext('ion-item.ion-color', el),
'select-disabled': disabled,
'select-expanded': isExpanded,
'has-expanded-icon': expandedIcon !== undefined,
'legacy-select': true,
}}
>
@ -974,7 +990,16 @@ Developers can use the "legacy" property to continue using the legacy form marku
*/
private renderSelectIcon() {
const mode = getIonMode(this);
const icon = mode === 'ios' ? chevronExpand : caretDownSharp;
const { isExpanded, toggleIcon, expandedIcon } = this;
let icon: string;
if (isExpanded && expandedIcon !== undefined) {
icon = expandedIcon;
} else {
const defaultIcon = mode === 'ios' ? chevronExpand : caretDownSharp;
icon = toggleIcon ?? defaultIcon;
}
return <ion-icon class="select-icon" part="icon" aria-hidden="true" icon={icon}></ion-icon>;
}

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Select - toggleIcon</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Select - toggleIcon</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="test-content">
<ion-list>
<ion-item>
<ion-select label="toggleIcon" toggle-icon="arrow-down" placeholder="Select one" interface="popover">
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="oranges">Oranges</ion-select-option>
<ion-select-option value="pears">Pears</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select label="expandedIcon" expanded-icon="arrow-up" placeholder="Select one" interface="popover">
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="oranges">Oranges</ion-select-option>
<ion-select-option value="pears">Pears</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select
label="Both"
toggle-icon="arrow-down"
expanded-icon="pizza"
placeholder="Select one"
interface="popover"
>
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="oranges">Oranges</ion-select-option>
<ion-select-option value="pears">Pears</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,39 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('select: toggleIcon'), () => {
test('should render a custom toggleIcon', async ({ page }) => {
await page.setContent(
`
<ion-select toggle-icon="pizza" label="Select" value="a">
<ion-select-option value="a">Apple</ion-select-option>
</ion-select>
`,
config
);
const select = page.locator('ion-select');
await expect(select).toHaveScreenshot(screenshot(`select-toggle-icon`));
});
test('should render a custom expandedIcon', async ({ page }) => {
await page.setContent(
`
<ion-select expanded-icon="pizza" interface="popover" label="Select" value="a">
<ion-select-option value="a">Apple</ion-select-option>
</ion-select>
`,
config
);
const select = page.locator('ion-select');
const popoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
await select.click();
await popoverDidPresent.next();
await expect(select).toHaveScreenshot(screenshot(`select-expanded-icon`));
});
});
});