Skip to content

Commit a2e6252

Browse files
authored
fix(input): floating label works correctly with long text and notch (#26273)
1 parent 0674b4a commit a2e6252

File tree

92 files changed

+274
-32
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+274
-32
lines changed

core/src/components/input/input.md.outline.scss

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "./input.vars";
2+
13
// Input Fill: Outline
24
// ----------------------------------------------------------------
35

@@ -9,7 +11,7 @@
911
}
1012

1113
:host(.input-fill-outline.input-shape-round) {
12-
--border-radius: 9999px;
14+
--border-radius: 28px;
1315
--padding-start: 32px;
1416
--padding-end: 32px;
1517
}
@@ -62,7 +64,15 @@
6264

6365
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper,
6466
:host(.input-fill-outline.input-label-placement-floating) .label-text-wrapper {
65-
@include transform-origin(center, top);
67+
@include transform-origin(start, top);
68+
69+
position: absolute;
70+
71+
/**
72+
* Label text should not extend
73+
* beyond the bounds of the input.
74+
*/
75+
max-width: calc(100% - var(--padding-start) - var(--padding-end));
6676
}
6777

6878
/**
@@ -81,10 +91,15 @@
8191
*/
8292
:host(.has-focus.input-fill-outline.input-label-placement-floating) .label-text-wrapper,
8393
:host(.has-value.input-fill-outline.input-label-placement-floating) .label-text-wrapper,
84-
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper,
8594
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper {
86-
@include transform(translateY(-32%), scale(.75));
95+
@include transform(translateY(-32%), scale(#{$input-floating-label-scale}));
8796
@include margin(0);
97+
98+
/**
99+
* Label text should not extend
100+
* beyond the bounds of the input.
101+
*/
102+
max-width: calc((100% - var(--padding-start) - var(--padding-end) - #{$input-md-floating-label-padding * 2}) / #{$input-floating-label-scale});
88103
}
89104

90105
/**
@@ -128,20 +143,50 @@
128143
border-bottom: var(--border-width) var(--border-style) var(--border-color);
129144
}
130145

131-
:host(.input-fill-outline) .input-outline-start {
132-
@include border-radius(var(--border-radius), 0px, 0px, var(--border-radius));
133-
@include border(null, null, null, var(--border-width) var(--border-style) var(--border-color));
134-
135-
width: 12px;
146+
/**
147+
* Ensures long labels do not cause the notch to flow
148+
* out of bounds.
149+
*/
150+
:host(.input-fill-outline) .input-outline-notch {
151+
max-width: calc(100% - var(--padding-start) - var(--padding-end));
136152
}
137153

138154
/**
139-
* When shape="round", the starting outline fragment
140-
* should appear with a pill shape.
155+
* This element ensures that the notch used
156+
* the size of the scaled text so that the
157+
* border cut out is the correct width.
158+
* The text in this element should not
159+
* be interactive.
141160
*/
142-
:host(.input-fill-outline.input-shape-round) .input-outline-start {
143-
@include border-radius(28px, 0px, 0px, 28px);
144-
width: 28px;
161+
:host(.input-fill-outline) .notch-spacer {
162+
/**
163+
* We need $input-md-floating-label-padding of padding on the right.
164+
* However, we also subtracted $input-md-floating-label-padding from
165+
* the width of .input-outline-start
166+
* to create space, so we need to take
167+
* that into consideration here.
168+
*/
169+
@include padding(null, #{$input-md-floating-label-padding * 2}, null, null);
170+
171+
font-size: calc(1em * #{$input-floating-label-scale});
172+
173+
opacity: 0;
174+
pointer-events: none;
175+
}
176+
177+
:host(.input-fill-outline) .input-outline-start {
178+
@include border-radius(var(--border-radius), 0px, 0px, var(--border-radius));
179+
@include border(null, null, null, var(--border-width) var(--border-style) var(--border-color));
180+
181+
/**
182+
* There should be spacing between the translated text
183+
* and .input-outline-start. However, we can't add this
184+
* spacing onto the notch because it would cause the
185+
* label to look like it is not aligned with the
186+
* text input. Instead, we subtract a few pixels from
187+
* this element.
188+
*/
189+
width: calc(var(--padding-start) - #{$input-md-floating-label-padding});
145190
}
146191

147192
:host(.input-fill-outline) .input-outline-end {
@@ -163,7 +208,6 @@
163208
*/
164209
:host(.has-focus.input-fill-outline.input-label-placement-floating) .input-outline-notch,
165210
:host(.has-value.input-fill-outline.input-label-placement-floating) .input-outline-notch,
166-
:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch,
167211
:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch {
168212
border-top: none;
169213
}

core/src/components/input/input.md.solid.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "./input.vars";
2+
13
// Input Fill: Solid
24
// ----------------------------------------------------------------
35

@@ -59,3 +61,16 @@
5961
*/
6062
@include border-radius(var(--border-radius), var(--border-radius), 0px, 0px);
6163
}
64+
65+
// Input Label
66+
// ----------------------------------------------------------------
67+
68+
:host(.input-fill-solid.input-label-placement-stacked) .label-text-wrapper,
69+
:host(.has-focus.input-fill-solid.input-label-placement-floating) .label-text-wrapper,
70+
:host(.has-value.input-fill-solid.input-label-placement-floating) .label-text-wrapper {
71+
/**
72+
* Label text should not extend
73+
* beyond the bounds of the input.
74+
*/
75+
max-width: calc(100% / #{$input-floating-label-scale});
76+
}

core/src/components/input/input.md.vars.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,6 @@ $input-md-inset-margin-bottom: ($item-md-padding-bottom * 0.5) !defau
7575

7676
/// @prop - Margin start of the inset input
7777
$input-md-inset-margin-start: $item-md-padding-start !default;
78+
79+
/// @prop - The amount of whitespace to display on either side of the floating label
80+
$input-md-floating-label-padding: 4px !default;

core/src/components/input/input.scss

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,13 +393,6 @@
393393
// ----------------------------------------------------------------
394394

395395
.label-text-wrapper {
396-
/**
397-
* The margin between the label and
398-
* the input should be on the end
399-
* when the label sits at the start.
400-
*/
401-
@include margin(0, 8px, 0, 0);
402-
403396
/**
404397
* This causes the label to take up
405398
* the entire height of its container
@@ -409,6 +402,16 @@
409402

410403
align-items: center;
411404

405+
/**
406+
* Label text should not extend
407+
* beyond the bounds of the input.
408+
* However, we do not set the max
409+
* width to 100% because then
410+
* only the label would show and users
411+
* would not be able to see what they are typing.
412+
*/
413+
max-width: 200px;
414+
412415
transition: color 150ms cubic-bezier(.4, 0, .2, 1), transform 150ms cubic-bezier(.4, 0, .2, 1);
413416

414417
/**
@@ -460,6 +463,15 @@
460463
flex-direction: row;
461464
}
462465

466+
:host(.input-label-placement-start) .label-text-wrapper {
467+
/**
468+
* The margin between the label and
469+
* the input should be on the end
470+
* when the label sits at the start.
471+
*/
472+
@include margin(0, 8px, 0, 0);
473+
}
474+
463475
// Input Label Placement - End
464476
// ----------------------------------------------------------------
465477

@@ -483,6 +495,15 @@
483495
// Input Label Placement - Fixed
484496
// ----------------------------------------------------------------
485497

498+
:host(.input-label-placement-fixed) .label-text-wrapper {
499+
/**
500+
* The margin between the label and
501+
* the input should be on the end
502+
* when the label sits at the start.
503+
*/
504+
@include margin(0, 8px, 0, 0);
505+
}
506+
486507
/**
487508
* Label is on the left of the input in LTR and
488509
* on the right in RTL. Label also has a fixed width.
@@ -519,6 +540,8 @@
519540
:host(.input-label-placement-stacked) .label-text-wrapper,
520541
:host(.input-label-placement-floating) .label-text-wrapper {
521542
@include transform-origin(start, top);
543+
544+
max-width: 100%;
522545
}
523546

524547
/**
@@ -559,4 +582,10 @@
559582
:host(.has-focus.input-label-placement-floating) .label-text-wrapper,
560583
:host(.has-value.input-label-placement-floating) .label-text-wrapper {
561584
@include transform(translateY(50%), scale(.75));
585+
586+
/**
587+
* Label text should not extend
588+
* beyond the bounds of the input.
589+
*/
590+
max-width: calc(100% / #{$input-floating-label-scale});
562591
}

core/src/components/input/input.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -572,28 +572,28 @@ export class Input implements ComponentInterface {
572572
* when fill="outline".
573573
*/
574574
private renderLabelContainer() {
575-
const { labelPlacement } = this;
576-
const hasOutlineFill = this.fill === 'outline';
577-
const needsNotch = labelPlacement === 'floating' || labelPlacement === 'stacked';
575+
const mode = getIonMode(this);
576+
const hasOutlineFill = mode === 'md' && this.fill === 'outline';
578577

579578
if (hasOutlineFill) {
580579
/**
581580
* The outline fill has a special outline
582581
* that appears around the input and the label.
583-
* Certain label placements cause the
582+
* Certain stacked and floating label placements cause the
584583
* label to translate up and create a "cut out"
585-
* inside of that border. When this happens, we need
586-
* to render the label inside of the input-outline-notch
587-
* element. Otherwise, we can render it as a sibling
588-
* of the outline container.
584+
* inside of that border by using the notch-spacer element.
589585
*/
590586
return [
591587
<div class="input-outline-container">
592588
<div class="input-outline-start"></div>
593-
<div class="input-outline-notch">{needsNotch && this.renderLabel()}</div>
589+
<div class="input-outline-notch">
590+
<div class="notch-spacer" aria-hidden="true">
591+
{this.label}
592+
</div>
593+
</div>
594594
<div class="input-outline-end"></div>
595595
</div>,
596-
!needsNotch && this.renderLabel(),
596+
this.renderLabel(),
597597
];
598598
}
599599

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
@import "../../themes/ionic.globals";
2+
3+
/// @prop - How much to scale the floating label by
4+
$input-floating-label-scale: 0.75 !default;

core/src/components/input/test/fill/input.e2e.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ test.describe('input: fill', () => {
2222
const input = page.locator('ion-input');
2323
expect(await input.screenshot()).toMatchSnapshot(`input-fill-solid-${page.getSnapshotSettings()}.png`);
2424
});
25+
test('should render correctly with floating label', async ({ page }) => {
26+
await page.setContent(`
27+
<ion-input
28+
fill="solid"
29+
label="Email"
30+
label-placement="floating"
31+
32+
helper-text="Enter your email"
33+
maxlength="20"
34+
counter="true"
35+
></ion-input>
36+
`);
37+
38+
const input = page.locator('ion-input');
39+
expect(await input.screenshot()).toMatchSnapshot(
40+
`input-fill-solid-label-floating-${page.getSnapshotSettings()}.png`
41+
);
42+
});
2543
test('should not have visual regressions with shaped solid', async ({ page }) => {
2644
await page.setContent(`
2745
<ion-input
@@ -38,6 +56,33 @@ test.describe('input: fill', () => {
3856
const input = page.locator('ion-input');
3957
expect(await input.screenshot()).toMatchSnapshot(`input-fill-shaped-solid-${page.getSnapshotSettings()}.png`);
4058
});
59+
test('padding and border radius should be customizable', async ({ page }) => {
60+
await page.setContent(`
61+
<style>
62+
ion-input {
63+
--border-radius: 10px !important;
64+
--padding-start: 50px !important;
65+
--padding-end: 50px !important;
66+
}
67+
</style>
68+
69+
<ion-input
70+
shape="round"
71+
fill="solid"
72+
label="Email"
73+
label-placement="floating"
74+
75+
helper-text="Enter your email"
76+
maxlength="20"
77+
counter="true"
78+
></ion-input>
79+
`);
80+
81+
const input = page.locator('ion-input');
82+
expect(await input.screenshot()).toMatchSnapshot(
83+
`input-fill-shaped-solid-custom-${page.getSnapshotSettings()}.png`
84+
);
85+
});
4186
});
4287
test.describe('input: fill outline', () => {
4388
test('should not have visual regressions', async ({ page }) => {
@@ -55,6 +100,24 @@ test.describe('input: fill', () => {
55100
const input = page.locator('ion-input');
56101
expect(await input.screenshot()).toMatchSnapshot(`input-fill-outline-${page.getSnapshotSettings()}.png`);
57102
});
103+
test('should render correctly with floating label', async ({ page }) => {
104+
await page.setContent(`
105+
<ion-input
106+
fill="outline"
107+
label="Email"
108+
label-placement="floating"
109+
110+
helper-text="Enter your email"
111+
maxlength="20"
112+
counter="true"
113+
></ion-input>
114+
`);
115+
116+
const input = page.locator('ion-input');
117+
expect(await input.screenshot()).toMatchSnapshot(
118+
`input-fill-outline-label-floating-${page.getSnapshotSettings()}.png`
119+
);
120+
});
58121
test('should not have visual regressions with shaped outline', async ({ page }) => {
59122
await page.setContent(`
60123
<ion-input
@@ -71,5 +134,32 @@ test.describe('input: fill', () => {
71134
const input = page.locator('ion-input');
72135
expect(await input.screenshot()).toMatchSnapshot(`input-fill-shaped-outline-${page.getSnapshotSettings()}.png`);
73136
});
137+
test('padding and border radius should be customizable', async ({ page }) => {
138+
await page.setContent(`
139+
<style>
140+
ion-input {
141+
--border-radius: 10px !important;
142+
--padding-start: 50px !important;
143+
--padding-end: 50px !important;
144+
}
145+
</style>
146+
147+
<ion-input
148+
shape="round"
149+
fill="outline"
150+
label="Email"
151+
label-placement="floating"
152+
153+
helper-text="Enter your email"
154+
maxlength="20"
155+
counter="true"
156+
></ion-input>
157+
`);
158+
159+
const input = page.locator('ion-input');
160+
expect(await input.screenshot()).toMatchSnapshot(
161+
`input-fill-shaped-outline-custom-${page.getSnapshotSettings()}.png`
162+
);
163+
});
74164
});
75165
});
13.5 KB
Loading
4.54 KB
Loading
12.8 KB
Loading

0 commit comments

Comments
 (0)