Skip to content

Commit 98c08c0

Browse files
authored
feat(input): add labelPlacement (#26206)
1 parent 73c6933 commit 98c08c0

File tree

104 files changed

+387
-15
lines changed

Some content is hidden

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

104 files changed

+387
-15
lines changed

core/src/components/input/input.ios.scss

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
@import "./input.ios.vars";
33

44
:host {
5-
--padding-top: #{$input-ios-padding-top};
6-
--padding-end: #{$input-ios-padding-end};
7-
--padding-bottom: #{$input-ios-padding-bottom};
8-
--padding-start: #{$input-ios-padding-start};
95
--border-width: #{$hairlines-width};
106
--border-color: #{$item-ios-border-color};
117

128
font-size: $input-ios-font-size;
139
}
1410

11+
:host(.legacy-input) {
12+
--padding-top: #{$input-ios-padding-top};
13+
--padding-end: #{$input-ios-padding-end};
14+
--padding-bottom: #{$input-ios-padding-bottom};
15+
--padding-start: #{$input-ios-padding-start};
16+
}
17+
1518
:host-context(.item-label-stacked),
1619
:host-context(.item-label-floating) {
1720
--padding-top: 8px;
@@ -27,3 +30,20 @@
2730

2831
background-size: $input-ios-input-clear-icon-size;
2932
}
33+
34+
// Input Wrapper
35+
// ----------------------------------------------------------------
36+
37+
.input-wrapper {
38+
min-height: 44px;
39+
}
40+
41+
/**
42+
* Since the label sits on top of the element,
43+
* the component needs to be taller otherwise the
44+
* label will appear too close to the input text.
45+
*/
46+
:host(.input-label-placement-floating) .input-wrapper,
47+
:host(.input-label-placement-stacked) .input-wrapper {
48+
min-height: 56px;
49+
}

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
// --------------------------------------------------
77

88
:host {
9-
--padding-top: #{$input-md-padding-top};
10-
--padding-end: #{$input-md-padding-end};
11-
--padding-bottom: #{$input-md-padding-bottom};
12-
--padding-start: #{$input-md-padding-start};
139
--border-width: 1px;
1410
--border-color: #{$item-md-border-color};
1511

1612
font-size: $input-md-font-size;
1713
}
1814

15+
:host(.legacy-input) {
16+
--padding-top: #{$input-md-padding-top};
17+
--padding-end: #{$input-md-padding-end};
18+
--padding-bottom: #{$input-md-padding-bottom};
19+
--padding-start: #{$input-md-padding-start};
20+
}
21+
1922
:host-context(.item-label-stacked),
2023
:host-context(.item-label-floating) {
2124
--padding-top: 8px;
@@ -37,3 +40,10 @@
3740
.input-bottom .counter {
3841
letter-spacing: .0333333333em;
3942
}
43+
44+
// Input Wrapper
45+
// ----------------------------------------------------------------
46+
47+
.input-wrapper {
48+
min-height: 56px;
49+
}

core/src/components/input/input.scss

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@
3232
--placeholder-font-style: initial;
3333
--placeholder-font-weight: initial;
3434
--placeholder-opacity: .5;
35-
--padding-top: 0;
36-
--padding-end: 0;
37-
--padding-bottom: 0;
38-
--padding-start: 0;
35+
--padding-top: 0px;
36+
--padding-end: 0px;
37+
--padding-bottom: 0px;
38+
--padding-start: 0px;
3939
--background: transparent;
4040
--color: initial;
4141
--border-style: solid;
4242
--highlight-color-focused: #{ion-color(primary, base)};
4343
--highlight-color-valid: #{ion-color(success, base)};
4444
--highlight-color-invalid: #{ion-color(danger, base)};
4545

46+
display: block;
47+
4648
position: relative;
4749

4850
width: 100%;
@@ -65,6 +67,10 @@
6567
align-items: center;
6668
}
6769

70+
:host(.legacy-input) .native-input {
71+
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
72+
}
73+
6874
:host-context(ion-item:not(.item-label)) {
6975
--padding-start: 0;
7076
}
@@ -78,7 +84,7 @@
7884

7985
.native-input {
8086
@include border-radius(var(--border-radius));
81-
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
87+
@include padding(0, 0, 0, 0);
8288
@include text-inherit();
8389

8490
display: inline-block;
@@ -211,11 +217,17 @@
211217
// Input Wrapper
212218
// ----------------------------------------------------------------
213219
.input-wrapper {
220+
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
221+
214222
display: flex;
215223

216224
flex-grow: 1;
217225

218-
align-items: center;
226+
align-items: stretch;
227+
228+
height: 100%;
229+
230+
background: inherit;
219231
}
220232

221233
// Input Bottom Content
@@ -276,3 +288,140 @@
276288

277289
padding-inline-start: 16px;
278290
}
291+
292+
// Input Label
293+
// ----------------------------------------------------------------
294+
295+
.input-wrapper label {
296+
/**
297+
* The margin between the label and
298+
* the input should be on the end
299+
* when the label sits at the start.
300+
*/
301+
@include margin(0, 8px, 0, 0);
302+
303+
/**
304+
* This causes the label to take up
305+
* the entire height of its container
306+
* while still keeping the text centered.
307+
*/
308+
display: flex;
309+
310+
align-items: center;
311+
312+
transition: color 150ms cubic-bezier(.4, 0, .2, 1), transform 150ms cubic-bezier(.4, 0, .2, 1);
313+
}
314+
315+
.input-wrapper input {
316+
/**
317+
* When the floating label appears on top of the
318+
* input, we need to fade the input out so that the
319+
* label does not overlap with the placeholder.
320+
*/
321+
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
322+
}
323+
324+
// Input Label Placement
325+
// ----------------------------------------------------------------
326+
327+
/**
328+
* Label is on the left of the input in LTR and
329+
* on the right in RTL.
330+
*/
331+
:host(.input-label-placement-start) .input-wrapper {
332+
flex-direction: row;
333+
}
334+
335+
/**
336+
* Label is on the right of the input in LTR and
337+
* on the left in RTL.
338+
*/
339+
:host(.input-label-placement-end) .input-wrapper {
340+
flex-direction: row-reverse;
341+
}
342+
343+
/**
344+
* The margin between the label and
345+
* the input should be on the start
346+
* when the label sits at the end.
347+
*/
348+
:host(.input-label-placement-end) label {
349+
@include margin(0, 0, 0, 8px);
350+
}
351+
352+
/**
353+
* Label is on the left of the input in LTR and
354+
* on the right in RTL. Label also has a fixed width.
355+
*/
356+
:host(.input-label-placement-fixed) label {
357+
@include margin(0, 0, 0, 0);
358+
359+
flex: 0 0 100px;
360+
361+
width: 100px;
362+
min-width: 100px;
363+
max-width: 200px;
364+
}
365+
366+
/**
367+
* Stacked: Label sits above the input and is scaled down.
368+
* Floating: Label sits over the input when the input has no
369+
* value and is blurred. Label sits above the input and is scaled
370+
* down when the input is focused or has a value.
371+
*
372+
*/
373+
:host(.input-label-placement-stacked) .input-wrapper,
374+
:host(.input-label-placement-floating) .input-wrapper {
375+
flex-direction: column;
376+
align-items: start;
377+
}
378+
379+
/**
380+
* Ensures that the label animates
381+
* up and to the left in LTR or
382+
* up and to the right in RTL.
383+
*/
384+
:host(.input-label-placement-stacked) label,
385+
:host(.input-label-placement-floating) label {
386+
@include transform-origin(start, top);
387+
}
388+
389+
/**
390+
* Ensures the input does not
391+
* overlap the label.
392+
*/
393+
:host(.input-label-placement-stacked) input,
394+
:host(.input-label-placement-floating) input {
395+
@include margin(1px, 0, 0, 0);
396+
}
397+
398+
/**
399+
* This makes the label sit over the input
400+
* when the input is blurred and has no value.
401+
*/
402+
:host(.input-label-placement-floating) label {
403+
@include transform(translateY(100%), scale(1));
404+
}
405+
406+
/**
407+
* The input should be hidden when the label
408+
* is on top of the input. This prevents the label
409+
* from overlapping any placeholder value.
410+
*/
411+
:host(.input-label-placement-floating) input {
412+
opacity: 0;
413+
}
414+
415+
:host(.has-focus.input-label-placement-floating) input,
416+
:host(.has-value.input-label-placement-floating) input {
417+
opacity: 1;
418+
}
419+
420+
/**
421+
* This makes the label sit above the input.
422+
*/
423+
:host(.input-label-placement-stacked) label,
424+
:host(.has-focus.input-label-placement-floating) label,
425+
:host(.has-value.input-label-placement-floating) label {
426+
@include transform(translateY(50%), scale(.75));
427+
}

core/src/components/input/input.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ export class Input implements ComponentInterface {
555555
}
556556

557557
private renderInput() {
558-
const { disabled, readonly, inputId } = this;
558+
const { disabled, readonly, inputId, labelPlacement } = this;
559559
const mode = getIonMode(this);
560560
const value = this.getValue();
561561

@@ -566,6 +566,7 @@ export class Input implements ComponentInterface {
566566
[mode]: true,
567567
'has-value': this.hasValue(),
568568
'has-focus': this.hasFocus,
569+
[`input-label-placement-${labelPlacement}`]: true,
569570
})}
570571
>
571572
<div class="input-wrapper">

core/src/components/input/test/a11y/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ <h1>Input - a11y</h1>
1717

1818
<ion-input label="my label"></ion-input>
1919
<ion-input aria-label="my aria label"></ion-input>
20+
<ion-input label="Email" label-placement="stacked" value="[email protected]"></ion-input>
21+
<ion-input label="Email" label-placement="floating"></ion-input>
2022
</main>
2123
</body>
2224
</html>

core/src/components/input/test/bottom-content/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ <h2>Custom Error Color</h2>
7373
error-text="Please enter a valid email"
7474
></ion-input>
7575
</div>
76+
7677
<div class="grid-item">
7778
<h2>Counter</h2>
7879
<ion-input label="Email" counter="true" maxlength="100"></ion-input>

0 commit comments

Comments
 (0)