mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
feat(select): update popover interface to match MD spec on desktop, allow multiple values in popover interface (#23474)
resolves #23657 resolves #15500 resolves #12310
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# ion-select-popover
|
||||
|
||||
SelectPopover is an internal component that is used for create the popover interface, from a Select component.
|
||||
The select popover is an internal component that is used to create the popover interface from a select component.
|
||||
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
@ -5,5 +5,5 @@ export interface SelectPopoverOption {
|
||||
disabled: boolean;
|
||||
checked: boolean;
|
||||
cssClass?: string | string[];
|
||||
handler?: () => void;
|
||||
handler?: (value: any) => boolean | void | {[key: string]: any};
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
@import "./select-popover";
|
||||
@import "./select-popover.ios.vars";
|
||||
|
@ -0,0 +1,5 @@
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
@import "../item/item.ios.vars";
|
||||
|
||||
// iOS Select Popover
|
||||
// --------------------------------------------------
|
25
core/src/components/select-popover/select-popover.md.scss
Normal file
25
core/src/components/select-popover/select-popover.md.scss
Normal file
@ -0,0 +1,25 @@
|
||||
@import "./select-popover";
|
||||
@import "./select-popover.md.vars";
|
||||
|
||||
ion-list ion-radio {
|
||||
opacity: 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)};
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
@import "../../themes/ionic.globals.md";
|
||||
@import "../item/item.md.vars";
|
||||
|
||||
// Material Design Select Popover
|
||||
// --------------------------------------------------
|
@ -1,10 +1,10 @@
|
||||
@import "./select-popover.vars";
|
||||
|
||||
:host ion-list {
|
||||
ion-list {
|
||||
@include margin($select-popover-list-margin-top, $select-popover-list-margin-end, $select-popover-list-margin-bottom, $select-popover-list-margin-start);
|
||||
}
|
||||
|
||||
:host ion-list-header,
|
||||
:host ion-label {
|
||||
ion-list-header,
|
||||
ion-label {
|
||||
@include margin(0);
|
||||
}
|
||||
|
@ -10,60 +10,166 @@ import { getClassMap } from '../../utils/theme';
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-select-popover',
|
||||
styleUrl: 'select-popover.scss',
|
||||
styleUrls: {
|
||||
ios: 'select-popover.ios.scss',
|
||||
md: 'select-popover.md.scss'
|
||||
},
|
||||
scoped: true
|
||||
})
|
||||
export class SelectPopover implements ComponentInterface {
|
||||
|
||||
/** Header text for the popover */
|
||||
/**
|
||||
* The header text of the popover
|
||||
*/
|
||||
@Prop() header?: string;
|
||||
|
||||
/** Subheader text for the popover */
|
||||
/**
|
||||
* The subheader text of the popover
|
||||
*/
|
||||
@Prop() subHeader?: string;
|
||||
|
||||
/** Text for popover body */
|
||||
/**
|
||||
* The text content of the popover body
|
||||
*/
|
||||
@Prop() message?: string;
|
||||
|
||||
/** Array of options for the popover */
|
||||
/**
|
||||
* If true, the select accepts multiple values
|
||||
*/
|
||||
@Prop() multiple?: boolean;
|
||||
|
||||
/**
|
||||
* An array of options for the popover
|
||||
*/
|
||||
@Prop() options: SelectPopoverOption[] = [];
|
||||
|
||||
@Listen('ionChange')
|
||||
onSelect(ev: any) {
|
||||
const option = this.options.find(o => o.value === ev.target.value);
|
||||
if (option) {
|
||||
safeCall(option.handler);
|
||||
this.setChecked(ev);
|
||||
this.callOptionHandler(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an option is selected we need to get the value(s)
|
||||
* of the selected option(s) and return it in the option
|
||||
* handler
|
||||
*/
|
||||
private callOptionHandler(ev: any) {
|
||||
const { options } = this;
|
||||
const option = options.find(o => this.getValue(o.value) === ev.target.value);
|
||||
|
||||
const values = this.getValues(ev);
|
||||
|
||||
if (option && option.handler) {
|
||||
safeCall(option.handler, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is required when selecting a radio that is already
|
||||
* selected because it will not trigger the ionChange event
|
||||
* but we still want to close the popover
|
||||
*/
|
||||
private rbClick(ev: any) {
|
||||
this.callOptionHandler(ev);
|
||||
}
|
||||
|
||||
private setChecked(ev: any): void {
|
||||
const { multiple, options } = this;
|
||||
const option = options.find(o => this.getValue(o.value) === ev.target.value);
|
||||
|
||||
// this is a popover with checkboxes (multiple value select)
|
||||
// we need to set the checked value for this option
|
||||
if (multiple && option) {
|
||||
option.checked = ev.detail.checked;
|
||||
}
|
||||
}
|
||||
|
||||
private getValues(ev: any): any | any[] | null {
|
||||
const { multiple, options } = this;
|
||||
|
||||
if (multiple) {
|
||||
// this is a popover with checkboxes (multiple value select)
|
||||
// return an array of all the checked values
|
||||
return options.filter(o => o.checked).map(o => o.value);
|
||||
}
|
||||
|
||||
// this is a popover with radio buttons (single value select)
|
||||
// return the value that was clicked, otherwise undefined
|
||||
const option = options.find(o => this.getValue(o.value) === ev.target.value);
|
||||
return option ? option.value : undefined;
|
||||
}
|
||||
|
||||
private getValue(value: any): any {
|
||||
return typeof value === 'number' ? value.toString() : value;
|
||||
}
|
||||
|
||||
renderOptions(options: SelectPopoverOption[]) {
|
||||
const { multiple } = this;
|
||||
|
||||
switch (multiple) {
|
||||
case true: return this.renderCheckboxOptions(options);
|
||||
default: return this.renderRadioOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
renderCheckboxOptions(options: SelectPopoverOption[]) {
|
||||
return (
|
||||
options.map(option =>
|
||||
<ion-item class={getClassMap(option.cssClass)}>
|
||||
<ion-checkbox
|
||||
slot="start"
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
checked={option.checked}
|
||||
>
|
||||
</ion-checkbox>
|
||||
<ion-label>
|
||||
{option.text}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
renderRadioOptions(options: SelectPopoverOption[]) {
|
||||
const checked = options.filter(o => o.checked).map(o => o.value)[0];
|
||||
|
||||
return (
|
||||
<ion-radio-group value={checked}>
|
||||
{options.map(option =>
|
||||
<ion-item class={getClassMap(option.cssClass)}>
|
||||
<ion-label>
|
||||
{option.text}
|
||||
</ion-label>
|
||||
<ion-radio
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
onClick={ev => this.rbClick(ev)}
|
||||
>
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
)}
|
||||
</ion-radio-group>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const checkedOption = this.options.find(o => o.checked);
|
||||
const checkedValue = checkedOption ? checkedOption.value : undefined;
|
||||
const { header, message, options, subHeader } = this;
|
||||
const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
|
||||
|
||||
return (
|
||||
<Host class={getIonMode(this)}>
|
||||
<ion-list>
|
||||
{this.header !== undefined && <ion-list-header>{this.header}</ion-list-header>}
|
||||
{ (this.subHeader !== undefined || this.message !== undefined) &&
|
||||
{header !== undefined && <ion-list-header>{header}</ion-list-header>}
|
||||
{ hasSubHeaderOrMessage &&
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
{this.subHeader !== undefined && <h3>{this.subHeader}</h3>}
|
||||
{this.message !== undefined && <p>{this.message}</p>}
|
||||
{subHeader !== undefined && <h3>{subHeader}</h3>}
|
||||
{message !== undefined && <p>{message}</p>}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
}
|
||||
<ion-radio-group value={checkedValue}>
|
||||
{this.options.map(option =>
|
||||
<ion-item class={getClassMap(option.cssClass)}>
|
||||
<ion-label>
|
||||
{option.text}
|
||||
</ion-label>
|
||||
<ion-radio
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
)}
|
||||
</ion-radio-group>
|
||||
{this.renderOptions(options)}
|
||||
</ion-list>
|
||||
</Host>
|
||||
);
|
||||
|
Reference in New Issue
Block a user