feat(input): add outline appearance for stacked label to ionic theme (#29268)
Issue number: 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 new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> All changes are specific to the `ionic` theme. - Styles added for `fill="outline"` plus `labelPlacement="stacked"`. - Markup rearranged slightly to ensure label sits above outline while still being clickable to focus the input. See code comments for details. - The default `labelPlacement` is now `"stacked"`. - Values for `labelPlacement` besides `"stacked"` and `"floating"` cannot be used. Note that per the ticket, I did not account for any other scope, including styles for helper text, `labelPlacement="floating"`, `shape="round"`, etc. This means that some states will look broken for now, and will be addressed in future tickets. ## 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/.github/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: ionitron <hi@ionicframework.com> Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
@ -610,7 +610,7 @@ ion-input,prop,fill,"outline" | "solid" | undefined,undefined,false,false
|
|||||||
ion-input,prop,helperText,string | undefined,undefined,false,false
|
ion-input,prop,helperText,string | undefined,undefined,false,false
|
||||||
ion-input,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
|
ion-input,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
|
||||||
ion-input,prop,label,string | undefined,undefined,false,false
|
ion-input,prop,label,string | undefined,undefined,false,false
|
||||||
ion-input,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start",'start',false,false
|
ion-input,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start" | undefined,undefined,false,false
|
||||||
ion-input,prop,max,number | string | undefined,undefined,false,false
|
ion-input,prop,max,number | string | undefined,undefined,false,false
|
||||||
ion-input,prop,maxlength,number | undefined,undefined,false,false
|
ion-input,prop,maxlength,number | undefined,undefined,false,false
|
||||||
ion-input,prop,min,number | string | undefined,undefined,false,false
|
ion-input,prop,min,number | string | undefined,undefined,false,false
|
||||||
|
|||||||
6
core/src/components.d.ts
vendored
@ -1395,9 +1395,9 @@ export namespace Components {
|
|||||||
*/
|
*/
|
||||||
"label"?: string;
|
"label"?: string;
|
||||||
/**
|
/**
|
||||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). Defaults to "stacked" for the ionic theme, or "start" for all other themes. In the ionic theme, only the values "stacked" and "floating" are supported.
|
||||||
*/
|
*/
|
||||||
"labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
|
"labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
|
||||||
/**
|
/**
|
||||||
* The maximum value, which must not be less than its minimum (min attribute) value.
|
* The maximum value, which must not be less than its minimum (min attribute) value.
|
||||||
*/
|
*/
|
||||||
@ -6631,7 +6631,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"label"?: string;
|
"label"?: string;
|
||||||
/**
|
/**
|
||||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the input in LTR and to the right in RTL. `"end"`: The label will appear to the right of the input in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input. `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). Defaults to "stacked" for the ionic theme, or "start" for all other themes. In the ionic theme, only the values "stacked" and "floating" are supported.
|
||||||
*/
|
*/
|
||||||
"labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
|
"labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
|
||||||
/**
|
/**
|
||||||
|
|||||||
111
core/src/components/input/input.ionic.outline.scss
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
@import "./input.vars";
|
||||||
|
@import "../../foundations/ionic.vars";
|
||||||
|
|
||||||
|
// Input Fill: Outline (Ionic Theme)
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
:host(.input-fill-outline) {
|
||||||
|
--border-radius: #{$ionic-border-radius-rounded-small};
|
||||||
|
--padding-start: 12px;
|
||||||
|
--padding-end: 12px;
|
||||||
|
--placeholder-color: #{$ionic-color-neutral-600};
|
||||||
|
--placeholder-opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bottom content should never have
|
||||||
|
* a border with the outline style.
|
||||||
|
*/
|
||||||
|
:host(.input-fill-outline) .input-bottom {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.input-fill-outline) .input-wrapper {
|
||||||
|
/**
|
||||||
|
* For the ionic theme, the padding needs to sit on the
|
||||||
|
* native wrapper instead, so that it sits within the
|
||||||
|
* outline container but does not affect the label text.
|
||||||
|
|
||||||
|
* For the ionic theme, the horizontal padding needs to
|
||||||
|
* sit on the native wrapper instead, so that
|
||||||
|
*/
|
||||||
|
@include padding(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outline inputs do not have a bottom border.
|
||||||
|
* Instead, they have a border that wraps the
|
||||||
|
* input + label.
|
||||||
|
*/
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper {
|
||||||
|
@include transform-origin(start, top);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label text should not extend
|
||||||
|
* beyond the bounds of the input.
|
||||||
|
*/
|
||||||
|
max-width: calc(100% - var(--padding-start) - var(--padding-end));
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.input-fill-outline) .label-text-wrapper {
|
||||||
|
/**
|
||||||
|
* The label should appear on top of an outline
|
||||||
|
* container that overlaps it so it is always clickable.
|
||||||
|
*/
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
color: #{$ionic-color-neutral-700};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.input-fill-outline) .native-wrapper {
|
||||||
|
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
|
||||||
|
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Input Fill: Outline, Outline Container
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
:host(.input-fill-outline) .input-outline {
|
||||||
|
@include position(0, 0, 0, 0);
|
||||||
|
@include border-radius(var(--border-radius));
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
border: var(--border-width) var(--border-style) var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Input Fill: Outline, Label Placement: Stacked
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
// This makes the label sit above the input.
|
||||||
|
:host(.label-floating.input-fill-outline.input-label-placement-stacked:not(.input-shape-round)) .label-text-wrapper {
|
||||||
|
@include transform(translateY(0), scale(#{$form-control-label-stacked-scale}));
|
||||||
|
@include margin(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label text should not extend
|
||||||
|
* beyond the bounds of the input.
|
||||||
|
*/
|
||||||
|
max-width: calc((100% - var(--padding-start) - var(--padding-end)) / #{$form-control-label-stacked-scale});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start/End Slots
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
:host(.input-fill-outline) ::slotted([slot="start"]) {
|
||||||
|
margin-inline-end: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.input-fill-outline) ::slotted([slot="end"]) {
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
}
|
||||||
@ -1,9 +1,14 @@
|
|||||||
@import "./input";
|
@import "./input";
|
||||||
@import "./input.ionic.vars";
|
@import "./input.ionic.vars";
|
||||||
|
@import "./input.ionic.outline.scss";
|
||||||
|
|
||||||
// Ionic Input
|
// Ionic Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
|
--border-width: #{$ionic-border-size-small};
|
||||||
|
--border-color: #{$ionic-color-neutral-300};
|
||||||
|
|
||||||
// TODO(FW-6113): Verify the ionic design token is correct once it's available and remove the hardcoded value.
|
// TODO(FW-6113): Verify the ionic design token is correct once it's available and remove the hardcoded value.
|
||||||
--highlight-color-invalid: var(--ionic-color-error-600, #970606);
|
--highlight-color-invalid: var(--ionic-color-error-600, #970606);
|
||||||
}
|
}
|
||||||
@ -11,7 +16,7 @@
|
|||||||
// Ionic Input Sizes
|
// Ionic Input Sizes
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
:host(.input-size-large) {
|
:host(.input-size-large) .native-wrapper {
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
// Ionic Input
|
// Ionic Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -181,8 +181,12 @@ export class Input implements ComponentInterface {
|
|||||||
* `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input.
|
* `"floating"`: The label will appear smaller and above the input when the input is focused or it has a value. Otherwise it will appear on top of the input.
|
||||||
* `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value.
|
* `"stacked"`: The label will appear smaller and above the input regardless even when the input is blurred or has no value.
|
||||||
* `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
* `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||||
|
*
|
||||||
|
* Defaults to "stacked" for the ionic theme, or "start" for all other themes.
|
||||||
|
*
|
||||||
|
* In the ionic theme, only the values "stacked" and "floating" are supported.
|
||||||
*/
|
*/
|
||||||
@Prop() labelPlacement: 'start' | 'end' | 'floating' | 'stacked' | 'fixed' = 'start';
|
@Prop({ mutable: true }) labelPlacement?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum value, which must not be less than its minimum (min attribute) value.
|
* The maximum value, which must not be less than its minimum (min attribute) value.
|
||||||
@ -343,6 +347,10 @@ export class Input implements ComponentInterface {
|
|||||||
...inheritAriaAttributes(this.el),
|
...inheritAriaAttributes(this.el),
|
||||||
...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type']),
|
...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type']),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.labelPlacement === undefined) {
|
||||||
|
this.labelPlacement = getIonTheme(this) === 'ionic' ? ionicThemeDefaultLabelPlacement : 'start';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@ -471,6 +479,21 @@ export class Input implements ComponentInterface {
|
|||||||
return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
|
return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getLabelPlacement() {
|
||||||
|
const theme = getIonTheme(this);
|
||||||
|
const { el, labelPlacement } = this;
|
||||||
|
|
||||||
|
if (theme === 'ionic' && labelPlacement !== 'stacked' && labelPlacement !== 'floating') {
|
||||||
|
printIonWarning(
|
||||||
|
`The "${labelPlacement}" label placement is not supported in the ${theme} theme. The default value of "${ionicThemeDefaultLabelPlacement}" will be used instead.`,
|
||||||
|
el
|
||||||
|
);
|
||||||
|
return ionicThemeDefaultLabelPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelPlacement;
|
||||||
|
}
|
||||||
|
|
||||||
private getSize() {
|
private getSize() {
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
const { size } = this;
|
const { size } = this;
|
||||||
@ -663,9 +686,9 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
private renderLabelContainer() {
|
private renderLabelContainer() {
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
const hasOutlineFill = theme === 'md' && this.fill === 'outline';
|
const hasOutlineFill = this.fill === 'outline';
|
||||||
|
|
||||||
if (hasOutlineFill) {
|
if (hasOutlineFill && theme === 'md') {
|
||||||
/**
|
/**
|
||||||
* The outline fill has a special outline
|
* The outline fill has a special outline
|
||||||
* that appears around the input and the label.
|
* that appears around the input and the label.
|
||||||
@ -693,19 +716,21 @@ export class Input implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not using the outline style,
|
* If not using the outline style, OR if using the
|
||||||
* we can render just the label.
|
* ionic theme, just render the label. For the ionic
|
||||||
|
* theme, the outline will be rendered elsewhere.
|
||||||
*/
|
*/
|
||||||
return this.renderLabel();
|
return this.renderLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this;
|
const { disabled, fill, readonly, shape, inputId, el, hasFocus } = this;
|
||||||
const theme = getIonTheme(this);
|
const theme = getIonTheme(this);
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
const size = this.getSize();
|
const size = this.getSize();
|
||||||
const inItem = hostContext('ion-item', this.el);
|
const inItem = hostContext('ion-item', this.el);
|
||||||
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
|
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
|
||||||
|
const labelPlacement = this.getLabelPlacement();
|
||||||
|
|
||||||
const hasValue = this.hasValue();
|
const hasValue = this.hasValue();
|
||||||
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
|
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
|
||||||
@ -755,6 +780,18 @@ export class Input implements ComponentInterface {
|
|||||||
<label class="input-wrapper" htmlFor={inputId}>
|
<label class="input-wrapper" htmlFor={inputId}>
|
||||||
{this.renderLabelContainer()}
|
{this.renderLabelContainer()}
|
||||||
<div class="native-wrapper">
|
<div class="native-wrapper">
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* For the ionic theme, we render the outline container here
|
||||||
|
* instead of higher up, so it can be positioned relative to
|
||||||
|
* the native wrapper instead of the <label> element or the
|
||||||
|
* entire component. This allows the label text to be positioned
|
||||||
|
* above the outline, while staying within the bounds of the
|
||||||
|
* <label> element, ensuring that clicking the label text
|
||||||
|
* focuses the input.
|
||||||
|
*/
|
||||||
|
theme === 'ionic' && fill === 'outline' && <div class="input-outline"></div>
|
||||||
|
}
|
||||||
<slot name="start"></slot>
|
<slot name="start"></slot>
|
||||||
<input
|
<input
|
||||||
class="native-input"
|
class="native-input"
|
||||||
@ -819,3 +856,4 @@ export class Input implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let inputIds = 0;
|
let inputIds = 0;
|
||||||
|
const ionicThemeDefaultLabelPlacement = 'stacked';
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 923 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 875 B After Width: | Height: | Size: 887 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.3 KiB |
@ -248,3 +248,24 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
configs({ modes: ['ionic-md'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('input: ionic theme fill'), () => {
|
||||||
|
test('should not have visual regressions with outline fill and stacked label placement', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input
|
||||||
|
fill="outline"
|
||||||
|
label="Email"
|
||||||
|
label-placement="stacked"
|
||||||
|
placeholder="example@ionic.io"
|
||||||
|
></ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-fill-outline-label-stacked`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.1 KiB |
@ -155,6 +155,36 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-input>
|
</ion-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>No Fill / Stacked Label</h2>
|
||||||
|
<ion-input label-placement="stacked" value="hi@ionic.io" label="Email">
|
||||||
|
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
|
||||||
|
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Solid / Stacked Label</h2>
|
||||||
|
<ion-input label-placement="stacked" fill="solid" value="hi@ionic.io" label="Email">
|
||||||
|
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
|
||||||
|
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-item">
|
||||||
|
<h2>Outline / Stacked Label</h2>
|
||||||
|
<ion-input label-placement="stacked" fill="outline" value="hi@ionic.io" label="Email">
|
||||||
|
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
|
||||||
|
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>Async Slot Content</h1>
|
<h1>Async Slot Content</h1>
|
||||||
|
|||||||
@ -47,6 +47,25 @@ configs().forEach(({ title, screenshot, config }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
configs({ modes: ['ionic-md'] }).forEach(({ title, config, screenshot }) => {
|
||||||
|
test.describe(title('input: start and end slots (visual checks for ionic theme)'), () => {
|
||||||
|
test('should not have visual regressions with a stacked label and outline fill', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-input label-placement="stacked" fill="outline" value="hi@ionic.io" label="Email">
|
||||||
|
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-icon slot="end" name="lock-closed" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-input>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = page.locator('ion-input');
|
||||||
|
await expect(input).toHaveScreenshot(screenshot(`input-slots-label-stacked-fill-outline`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe(title('input: start and end slots (functionality checks)'), () => {
|
test.describe(title('input: start and end slots (functionality checks)'), () => {
|
||||||
test('should raise floating label when there is content in the start slot', async ({ page }) => {
|
test('should raise floating label when there is content in the start slot', async ({ page }) => {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |