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

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

Merged
merged 13 commits into from
Jun 20, 2023
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
4 changes: 2 additions & 2 deletions angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1954,15 +1954,15 @@ export declare interface IonSegmentButton extends Components.IonSegmentButton {}


@ProxyCmp({
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'value'],
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
methods: ['open']
})
@Component({
selector: 'ion-select',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'value'],
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'legacy', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
})
export class IonSelect {
protected el: HTMLElement;
Expand Down
2 changes: 2 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ ion-select,prop,cancelText,string,'Cancel',false,false
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
ion-select,prop,disabled,boolean,false,false,false
ion-select,prop,expandedIcon,string | undefined,undefined,false,false
ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false
ion-select,prop,interface,"action-sheet" | "alert" | "popover",'alert',false,false
ion-select,prop,interfaceOptions,any,{},false,false
Expand All @@ -1252,6 +1253,7 @@ ion-select,prop,okText,string,'OK',false,false
ion-select,prop,placeholder,string | undefined,undefined,false,false
ion-select,prop,selectedText,null | string | undefined,undefined,false,false
ion-select,prop,shape,"round" | undefined,undefined,false,false
ion-select,prop,toggleIcon,string | undefined,undefined,false,false
ion-select,prop,value,any,undefined,false,false
ion-select,method,open,open(event?: UIEvent) => Promise<any>
ion-select,event,ionBlur,void,true
Expand Down
16 changes: 16 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2681,6 +2681,10 @@ export namespace Components {
* If `true`, the user cannot interact with the select.
*/
"disabled": boolean;
/**
* 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.
*/
"expandedIcon"?: string;
/**
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
*/
Expand Down Expand Up @@ -2742,6 +2746,10 @@ export namespace Components {
* The shape of the select. If "round" it will have an increased border radius.
*/
"shape"?: 'round';
/**
* The toggle icon to use. Defaults to `chevronExpand` for `ios` mode, or `caretDownSharp` for `md` mode.
*/
"toggleIcon"?: string;
/**
* The value of the select.
*/
Expand Down Expand Up @@ -6755,6 +6763,10 @@ declare namespace LocalJSX {
* If `true`, the user cannot interact with the select.
*/
"disabled"?: boolean;
/**
* 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.
*/
"expandedIcon"?: string;
/**
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
*/
Expand Down Expand Up @@ -6835,6 +6847,10 @@ declare namespace LocalJSX {
* The shape of the select. If "round" it will have an increased border radius.
*/
"shape"?: 'round';
/**
* The toggle icon to use. Defaults to `chevronExpand` for `ios` mode, or `caretDownSharp` for `md` mode.
*/
"toggleIcon"?: string;
/**
* The value of the select.
*/
Expand Down
8 changes: 4 additions & 4 deletions core/src/components/select/select.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -123,16 +123,16 @@
@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));
}

/**
* 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));
}

Expand Down
31 changes: 28 additions & 3 deletions core/src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
}}
>
Expand Down Expand Up @@ -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>;
}

Expand Down
58 changes: 58 additions & 0 deletions core/src/components/select/test/toggle-icon/index.html
Original file line number Diff line number Diff line change
@@ -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>
39 changes: 39 additions & 0 deletions core/src/components/select/test/toggle-icon/select.e2e.ts
Original file line number Diff line number Diff line change
@@ -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`));
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,8 @@ export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSel
'okText',
'placeholder',
'selectedText',
'toggleIcon',
'expandedIcon',
'shape',
'value',
'ionChange',
Expand Down