Skip to content

Commit

Permalink
feat(select): add modal as interface (#29972)
Browse files Browse the repository at this point in the history
Issue number: resolves internal

---------

<!-- 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. -->

Select only offers `alert`, `action-sheet`, and `popover` as interfaces

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

Adds `modal` as an interface option for `ion-select`

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

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

---------

Co-authored-by: Brandy Carney <[email protected]>
  • Loading branch information
tanner-reits and brandyscarney authored Oct 31, 2024
1 parent 7ab14b8 commit 7b32820
Show file tree
Hide file tree
Showing 35 changed files with 651 additions and 33 deletions.
7 changes: 6 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,7 @@ ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean)
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,interface,"action-sheet" | "alert" | "modal" | "popover",'alert',false,false
ion-select,prop,interfaceOptions,any,{},false,false
ion-select,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
ion-select,prop,label,string | undefined,undefined,false,false
Expand Down Expand Up @@ -1672,6 +1672,11 @@ ion-select,part,label
ion-select,part,placeholder
ion-select,part,text

ion-select-modal,scoped
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

ion-select-option,shadow
ion-select-option,prop,disabled,boolean,false,false,false
ion-select-option,prop,value,any,undefined,false,false
Expand Down
32 changes: 27 additions & 5 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./compone
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
Expand Down Expand Up @@ -70,6 +71,7 @@ export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./compone
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
Expand Down Expand Up @@ -639,6 +641,7 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
"setFocus": () => Promise<void>;
/**
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
*/
Expand Down Expand Up @@ -2279,7 +2282,7 @@ export namespace Components {
*/
"name": string;
"setButtonTabindex": (value: number) => Promise<void>;
"setFocus": (ev: globalThis.Event) => Promise<void>;
"setFocus": (ev?: globalThis.Event) => Promise<void>;
/**
* the value of the radio.
*/
Expand Down Expand Up @@ -2741,11 +2744,11 @@ export namespace Components {
*/
"fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover` or `alert`.
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
"interface": SelectInterface;
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
*/
"interfaceOptions": any;
/**
Expand Down Expand Up @@ -2802,6 +2805,11 @@ export namespace Components {
*/
"value"?: any | null;
}
interface IonSelectModal {
"header"?: string;
"multiple"?: boolean;
"options": SelectModalOption[];
}
interface IonSelectOption {
/**
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
Expand Down Expand Up @@ -4434,6 +4442,12 @@ declare global {
prototype: HTMLIonSelectElement;
new (): HTMLIonSelectElement;
};
interface HTMLIonSelectModalElement extends Components.IonSelectModal, HTMLStencilElement {
}
var HTMLIonSelectModalElement: {
prototype: HTMLIonSelectModalElement;
new (): HTMLIonSelectModalElement;
};
interface HTMLIonSelectOptionElement extends Components.IonSelectOption, HTMLStencilElement {
}
var HTMLIonSelectOptionElement: {
Expand Down Expand Up @@ -4722,6 +4736,7 @@ declare global {
"ion-segment": HTMLIonSegmentElement;
"ion-segment-button": HTMLIonSegmentButtonElement;
"ion-select": HTMLIonSelectElement;
"ion-select-modal": HTMLIonSelectModalElement;
"ion-select-option": HTMLIonSelectOptionElement;
"ion-select-popover": HTMLIonSelectPopoverElement;
"ion-skeleton-text": HTMLIonSkeletonTextElement;
Expand Down Expand Up @@ -7497,11 +7512,11 @@ declare namespace LocalJSX {
*/
"fill"?: 'outline' | 'solid';
/**
* The interface the select should use: `action-sheet`, `popover` or `alert`.
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
"interface"?: SelectInterface;
/**
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet) and the [ion-popover docs](./popover) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
* Any additional options that the `alert`, `action-sheet` or `popover` interface can take. See the [ion-alert docs](./alert), the [ion-action-sheet docs](./action-sheet), the [ion-popover docs](./popover), and the [ion-modal docs](./modal) for the create options for each interface. Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
*/
"interfaceOptions"?: any;
/**
Expand Down Expand Up @@ -7577,6 +7592,11 @@ declare namespace LocalJSX {
*/
"value"?: any | null;
}
interface IonSelectModal {
"header"?: string;
"multiple"?: boolean;
"options"?: SelectModalOption[];
}
interface IonSelectOption {
/**
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
Expand Down Expand Up @@ -8163,6 +8183,7 @@ declare namespace LocalJSX {
"ion-segment": IonSegment;
"ion-segment-button": IonSegmentButton;
"ion-select": IonSelect;
"ion-select-modal": IonSelectModal;
"ion-select-option": IonSelectOption;
"ion-select-popover": IonSelectPopover;
"ion-skeleton-text": IonSkeletonText;
Expand Down Expand Up @@ -8262,6 +8283,7 @@ declare module "@stencil/core" {
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
"ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes<HTMLIonSelectModalElement>;
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
"ion-skeleton-text": LocalJSX.IonSkeletonText & JSXBase.HTMLAttributes<HTMLIonSkeletonTextElement>;
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, h } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers';
import { createColorClasses, hostContext } from '@utils/theme';
Expand Down Expand Up @@ -121,7 +121,9 @@ export class Checkbox implements ComponentInterface {
};
}

private setFocus() {
/** @internal */
@Method()
async setFocus() {
if (this.focusEl) {
this.focusEl.focus();
}
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/radio-group/radio-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ export class RadioGroup implements ComponentInterface {

@Listen('keydown', { target: 'document' })
onKeydown(ev: KeyboardEvent) {
const inSelectPopover = !!this.el.closest('ion-select-popover');
// We don't want the value to automatically change/emit when the radio group is part of a select interface
// as this will cause the interface to close when navigating through the radio group options
const inSelectInterface = !!this.el.closest('ion-select-popover') || !!this.el.closest('ion-select-modal');

if (ev.target && !this.el.contains(ev.target as HTMLElement)) {
return;
Expand Down Expand Up @@ -187,7 +189,7 @@ export class RadioGroup implements ComponentInterface {
if (next && radios.includes(next)) {
next.setFocus(ev);

if (!inSelectPopover) {
if (!inSelectInterface) {
this.value = next.value;
this.emitValueChange(ev);
}
Expand Down
8 changes: 5 additions & 3 deletions core/src/components/radio/radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ export class Radio implements ComponentInterface {

/** @internal */
@Method()
async setFocus(ev: globalThis.Event) {
ev.stopPropagation();
ev.preventDefault();
async setFocus(ev?: globalThis.Event) {
if (ev !== undefined) {
ev.stopPropagation();
ev.preventDefault();
}

this.el.focus();
}
Expand Down
8 changes: 8 additions & 0 deletions core/src/components/select-modal/select-modal-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface SelectModalOption {
text: string;
value: string;
disabled: boolean;
checked: boolean;
cssClass?: string | string[];
handler?: (value: any) => boolean | void | { [key: string]: any };
}
1 change: 1 addition & 0 deletions core/src/components/select-modal/select-modal.ios.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "./select-modal";
30 changes: 30 additions & 0 deletions core/src/components/select-modal/select-modal.md.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import "./select-modal";
@import "../../themes/ionic.mixins.scss";
@import "../item/item.md.vars";

ion-list ion-radio::part(container) {
display: none;
}

ion-list ion-radio::part(label) {
@include margin(0);
}

ion-item {
--inner-border-width: 0;
}

.item-radio-checked {
--background: #{ion-color(primary, base, 0.08)};
--background-focused: #{ion-color(primary, base)};
--background-focused-opacity: 0.2;
--background-hover: #{ion-color(primary, base)};
--background-hover-opacity: 0.12;
}

.item-checkbox-checked {
--background-activated: #{$item-md-color};
--background-focused: #{$item-md-color};
--background-hover: #{$item-md-color};
--color: #{ion-color(primary, base)};
}
3 changes: 3 additions & 0 deletions core/src/components/select-modal/select-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
height: 100%;
}
Loading

0 comments on commit 7b32820

Please sign in to comment.