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 helperText and errorText properties #30143

Open
wants to merge 14 commits into
base: feature-8.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1619,8 +1619,10 @@ 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,errorText,string | undefined,undefined,false,false
ion-select,prop,expandedIcon,string | undefined,undefined,false,false
ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false
ion-select,prop,helperText,string | undefined,undefined,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
Expand Down Expand Up @@ -1674,9 +1676,12 @@ ion-select,css-prop,--placeholder-opacity,md
ion-select,css-prop,--ripple-color,ios
ion-select,css-prop,--ripple-color,md
ion-select,part,container
ion-select,part,error-text
ion-select,part,helper-text
ion-select,part,icon
ion-select,part,label
ion-select,part,placeholder
ion-select,part,supporting-text
ion-select,part,text

ion-select-modal,scoped
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 @@ -2755,6 +2755,10 @@ export namespace Components {
* If `true`, the user cannot interact with the select.
*/
"disabled": boolean;
/**
* Text that is placed under the select and displayed when an error is detected.
*/
"errorText"?: 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.
*/
Expand All @@ -2763,6 +2767,10 @@ export namespace Components {
* 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.
*/
"fill"?: 'outline' | 'solid';
/**
* Text that is placed under the select and displayed when no error is detected.
*/
"helperText"?: string;
/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
Expand Down Expand Up @@ -7568,6 +7576,10 @@ declare namespace LocalJSX {
* If `true`, the user cannot interact with the select.
*/
"disabled"?: boolean;
/**
* Text that is placed under the select and displayed when an error is detected.
*/
"errorText"?: 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.
*/
Expand All @@ -7576,6 +7588,10 @@ declare namespace LocalJSX {
* 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.
*/
"fill"?: 'outline' | 'solid';
/**
* Text that is placed under the select and displayed when no error is detected.
*/
"helperText"?: string;
/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
Expand Down
2 changes: 2 additions & 0 deletions core/src/components/select/select.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// --------------------------------------------------

:host {
--border-width: #{$hairlines-width};
--border-color: #{$item-ios-border-color};
--highlight-height: 0px;
}

Expand Down
4 changes: 4 additions & 0 deletions core/src/components/select/select.md.solid.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
--border-color: var(--highlight-color);
}

/**
* The bottom content should never have
* a border with the solid style.
*/
:host(.select-fill-solid) .select-bottom {
border-top: none;
}
Expand Down
65 changes: 65 additions & 0 deletions core/src/components/select/select.scss
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be beneficial to update @prop --highlight-color-invalid definition to also mention that it will also change error text?

* @prop --highlight-color-invalid: The color of the highlight on the select when invalid

Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,71 @@ button {
--highlight-color: var(--highlight-color-valid);
}

// Select Bottom Content
// ----------------------------------------------------------------

.select-bottom {
/**
* The bottom content should take on the start and end
* padding so it is always aligned with either the label
* or the start of the text select.
*/
@include padding(5px, var(--padding-end), 0, var(--padding-start));

display: flex;

justify-content: space-between;

border-top: var(--border-width) var(--border-style) var(--border-color);

font-size: dynamic-font(12px);

white-space: normal;
}

/**
* If the select has a validity state, the
* border and label should reflect that as a color.
* The invalid state should show if the select is
* invalid and has already been touched.
* The valid state should show if the select
* is valid, has already been touched, and
* is currently focused. Do not show the valid
* highlight when the select is blurred.
*/
:host(.has-focus.ion-valid),
:host(.ion-touched.ion-invalid) {
--border-color: var(--highlight-color);
}

// Select Hint Text
// ----------------------------------------------------------------

/**
* Error text should only be shown when .ion-invalid is
* present on the select. Otherwise the helper text should
* be shown.
*/
.select-bottom .error-text {
display: none;

color: var(--highlight-color-invalid);
}

.select-bottom .helper-text {
display: block;

color: $text-color-step-300;
}

:host(.ion-touched.ion-invalid) .select-bottom .error-text {
display: block;
}

:host(.ion-touched.ion-invalid) .select-bottom .helper-text {
display: none;
}

// Select Label
// ----------------------------------------------------------------

Expand Down
67 changes: 67 additions & 0 deletions core/src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
* @part icon - The select icon container.
* @part container - The container for the selected text or placeholder.
* @part label - The label text describing the select.
* @part supporting-text - Supporting text displayed beneath the select.
* @part helper-text - Supporting text displayed beneath the select when the select is valid.
* @part error-text - Supporting text displayed beneath the select when the select is invalid and touched.
*/
@Component({
tag: 'ion-select',
Expand All @@ -52,6 +55,8 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
})
export class Select implements ComponentInterface {
private inputId = `ion-sel-${selectIds++}`;
private helperTextId = `${this.inputId}-helper-text`;
private errorTextId = `${this.inputId}-error-text`;
private overlay?: OverlaySelect;
private focusEl?: HTMLButtonElement;
private mutationO?: MutationObserver;
Expand Down Expand Up @@ -98,6 +103,16 @@ export class Select implements ComponentInterface {
*/
@Prop() fill?: 'outline' | 'solid';

/**
* Text that is placed under the select and displayed when an error is detected.
*/
@Prop() errorText?: string;

/**
* Text that is placed under the select and displayed when no error is detected.
*/
@Prop() helperText?: string;

/**
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
*/
Expand Down Expand Up @@ -983,13 +998,64 @@ export class Select implements ComponentInterface {
aria-label={this.ariaLabel}
aria-haspopup="dialog"
aria-expanded={`${isExpanded}`}
aria-describedby={this.getHintTextID()}
aria-invalid={this.getHintTextID() === this.errorTextId}
onFocus={this.onFocus}
onBlur={this.onBlur}
ref={(focusEl) => (this.focusEl = focusEl)}
></button>
);
}

private getHintTextID(): string | undefined {
const { el, helperText, errorText, helperTextId, errorTextId } = this;

if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
return errorTextId;
}

if (helperText) {
return helperTextId;
}

return undefined;
}

/**
* Renders the helper text or error text values
*/
private renderHintText() {
const { helperText, errorText, helperTextId, errorTextId } = this;

return [
<div id={helperTextId} class="helper-text" part="supporting-text helper-text">
{helperText}
</div>,
<div id={errorTextId} class="error-text" part="supporting-text error-text">
{errorText}
</div>,
Comment on lines +1031 to +1036
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the supporting-text part so users could change them at the same time using:

ion-select::part(supporting-text) {
  font-size: 20px;
}

but if we think it should only be the one part they could write this without it:

ion-select::part(helper-text),
ion-select::part(error-text) {
  font-size: 20px;
}

I am copying Material Design 2 and Material Design 3 naming here, but let me know if there are other ideas.

];
}

/**
* Responsible for rendering helper text, and error text. This element
* should only be rendered if hint text is set.
*/
private renderBottomContent() {
const { helperText, errorText } = this;

/**
* undefined and empty string values should
* be treated as not having helper/error text.
*/
const hasHintText = !!helperText || !!errorText;
if (!hasHintText) {
return;
}

return <div class="select-bottom">{this.renderHintText()}</div>;
}

render() {
const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } =
this;
Expand Down Expand Up @@ -1069,6 +1135,7 @@ export class Select implements ComponentInterface {
{hasFloatingOrStackedLabel && this.renderSelectIcon()}
{shouldRenderHighlight && <div class="select-highlight"></div>}
</label>
{this.renderBottomContent()}
</Host>
);
}
Expand Down
Loading
Loading