Skip to content

feat(select): add close button text property #30282

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,7 @@ ion-select,part,supporting-text
ion-select,part,text

ion-select-modal,scoped
ion-select-modal,prop,closeText,string | undefined,undefined,false,false
ion-select-modal,prop,header,string | undefined,undefined,false,false
ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false
ion-select-modal,prop,options,SelectModalOption[],[],false,false
Expand Down
2 changes: 2 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2862,6 +2862,7 @@ export namespace Components {
"value"?: any | null;
}
interface IonSelectModal {
"cancelText"?: string;
"header"?: string;
"multiple"?: boolean;
"options": SelectModalOption[];
Expand Down Expand Up @@ -7742,6 +7743,7 @@ declare namespace LocalJSX {
"value"?: any | null;
}
interface IonSelectModal {
"cancelText"?: string;
"header"?: string;
"multiple"?: boolean;
"options"?: SelectModalOption[];
Expand Down
4 changes: 3 additions & 1 deletion core/src/components/select-modal/select-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class SelectModal implements ComponentInterface {

@Prop() header?: string;

@Prop() cancelText?: string;

@Prop() multiple?: boolean;

@Prop() options: SelectModalOption[] = [];
Expand Down Expand Up @@ -149,7 +151,7 @@ export class SelectModal implements ComponentInterface {
{this.header !== undefined && <ion-title>{this.header}</ion-title>}

<ion-buttons slot="end">
<ion-button onClick={() => this.closeModal()}>Close</ion-button>
<ion-button onClick={() => this.closeModal()}>{this.cancelText || 'Close'}</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Select - Modal</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 Modal - Custom Close Text</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-modal is-open="true">
<ion-select-modal multiple="false" cancel-text="Close me"></ion-select-modal>
</ion-modal>
</ion-content>
</ion-app>

<script>
const selectModal = document.querySelector('ion-select-modal');
selectModal.options = [
{ value: 'apple', text: 'Apple', disabled: false, checked: true },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';

import type { SelectModalOption } from '../../select-modal-interface';
import { SelectModalPage } from '../fixtures';

const options: SelectModalOption[] = [
{ value: 'apple', text: 'Apple', disabled: false, checked: false },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];

const checkedOptions: SelectModalOption[] = [
{ value: 'apple', text: 'Apple', disabled: false, checked: true },
{ value: 'banana', text: 'Banana', disabled: false, checked: false },
];

/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('select-modal: custom-close-text'), () => {
test.beforeEach(({ browserName }) => {
test.skip(browserName === 'webkit', 'ROU-5437');
});

test.describe('single selection', () => {
let selectModalPage: SelectModalPage;

test.beforeEach(async ({ page }) => {
selectModalPage = new SelectModalPage(page);
});

test('clicking an unselected option should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);

await selectModalPage.clickOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});

test('clicking a selected option should dismiss the modal', async () => {
await selectModalPage.setup(config, checkedOptions, false);

await selectModalPage.clickOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});

test('pressing Space on an unselected option should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);

await selectModalPage.pressSpaceOnOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});

test('pressing Space on a selected option should dismiss the modal', async ({ browserName }) => {
test.skip(browserName === 'firefox', 'Same behavior as ROU-5437');

await selectModalPage.setup(config, checkedOptions, false);

await selectModalPage.pressSpaceOnOption('apple');
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});

test('clicking the close button should dismiss the modal', async () => {
await selectModalPage.setup(config, options, false);

const closeButton = selectModalPage.modal.locator('ion-header ion-toolbar ion-button');
await closeButton.click();
await selectModalPage.ionModalDidDismiss.next();
await expect(selectModalPage.modal).not.toBeVisible();
});
});
});
});

/**
* This behavior does not vary across directions.
* The components used inside of `ion-select-modal`
* do have RTL logic, but those are tested in their
* respective component test files.
*/
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('select-modal: rendering'), () => {
let selectModalPage: SelectModalPage;

test.beforeEach(async ({ page }) => {
selectModalPage = new SelectModalPage(page);
});
test('should not have visual regressions with single selection', async () => {
await selectModalPage.setup(config, checkedOptions, false);
await selectModalPage.screenshot(screenshot, 'select-modal-diff');
});
test('should not have visual regressions with multiple selection', async () => {
await selectModalPage.setup(config, checkedOptions, true);
await selectModalPage.screenshot(screenshot, 'select-modal-multiple-diff');
});
});
});
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.
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.
3 changes: 2 additions & 1 deletion core/src/components/select-modal/test/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import type { E2EPage, E2ELocator, EventSpy, E2EPageOptions, ScreenshotFn } from '@utils/test/playwright';
import type { E2ELocator, E2EPage, E2EPageOptions, EventSpy, ScreenshotFn } from '@utils/test/playwright';

import type { SelectModalOption } from '../select-modal-interface';

Expand Down Expand Up @@ -31,6 +31,7 @@ export class SelectModalPage {
const selectModal = document.querySelector('ion-select-modal');
selectModal.options = ${JSON.stringify(options)};
selectModal.multiple = ${multiple};
selectModal.cancelText = 'Close me';
</script>
`,
config
Expand Down
9 changes: 5 additions & 4 deletions core/src/components/select/select.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h, forceUpdate } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
import type { NotchController } from '@utils/forms';
import { compareOptions, createNotchController, isOptionSelected } from '@utils/forms';
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { focusVisibleElement, inheritAttributes, renderHiddenInput } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays';
import type { OverlaySelect } from '@utils/overlays-interface';
Expand All @@ -18,15 +18,15 @@ import type {
AlertOptions,
Color,
CssClassMap,
ModalOptions,
PopoverOptions,
StyleEventDetail,
ModalOptions,
} from '../../interface';
import type { ActionSheetButton } from '../action-sheet/action-sheet-interface';
import type { AlertInput } from '../alert/alert-interface';
import type { SelectPopoverOption } from '../select-popover/select-popover-interface';

import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from './select-interface';
import type { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from './select-interface';

// TODO(FW-2832): types

Expand Down Expand Up @@ -735,6 +735,7 @@ export class Select implements ComponentInterface {
component: 'ion-select-modal',
componentProps: {
header: interfaceOptions.header,
cancelText: this.cancelText,
multiple,
value,
options: this.createOverlaySelectOptions(this.childOpts, value),
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2109,14 +2109,14 @@ This event will not emit when programmatically setting the `value` property.


@ProxyCmp({
inputs: ['header', 'multiple', 'options']
inputs: ['cancelText', 'header', 'multiple', 'options']
})
@Component({
selector: 'ion-select-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['header', 'multiple', 'options'],
inputs: ['cancelText', 'header', 'multiple', 'options'],
})
export class IonSelectModal {
protected el: HTMLIonSelectModalElement;
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/standalone/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1901,14 +1901,14 @@ export declare interface IonSegmentView extends Components.IonSegmentView {

@ProxyCmp({
defineCustomElementFn: defineIonSelectModal,
inputs: ['header', 'multiple', 'options']
inputs: ['cancelText', 'header', 'multiple', 'options']
})
@Component({
selector: 'ion-select-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['header', 'multiple', 'options'],
inputs: ['cancelText', 'header', 'multiple', 'options'],
standalone: true
})
export class IonSelectModal {
Expand Down
1 change: 1 addition & 0 deletions packages/vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ export const IonSelect: StencilVueComponent<JSX.IonSelect, JSX.IonSelect["value"

export const IonSelectModal: StencilVueComponent<JSX.IonSelectModal> = /*@__PURE__*/ defineContainer<JSX.IonSelectModal>('ion-select-modal', defineIonSelectModal, [
'header',
'cancelText',
'multiple',
'options'
]);
Expand Down