Skip to content

Commit d91f5a5

Browse files
authored
Merge pull request #5441 from mozilla/mntor-3823
Add floating labels to InputField
2 parents 780dfd4 + a436d84 commit d91f5a5

File tree

4 files changed

+98
-9
lines changed

4 files changed

+98
-9
lines changed

src/app/components/client/InputField.module.scss

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,36 @@
77
flex-direction: column;
88
position: relative;
99

10+
.inputFieldWrapper {
11+
position: relative;
12+
}
13+
14+
.floatingLabel {
15+
background: $color-white;
16+
border-radius: $border-radius-sm;
17+
color: $color-black;
18+
display: inline-block;
19+
// Add a bit more space: The next spacing step is too much.
20+
left: calc($spacing-sm * 1.5);
21+
line-height: 1em;
22+
padding: 0 calc($spacing-xs * 0.5);
23+
pointer-events: none;
24+
position: absolute;
25+
top: 50%;
26+
transform-origin: top left;
27+
transform: translate(-0.05em, -50%) scale(1);
28+
transition: transform 0.2s ease-in-out;
29+
user-select: none;
30+
}
31+
1032
.inputField {
1133
border: 1px solid $color-grey-30;
1234
border-radius: $border-radius-sm;
1335
color: $color-black;
14-
// Add a bit more vertical space: The next spacing step is too much.
15-
padding: calc($spacing-sm * 1.5) $spacing-md;
36+
// Add a bit more space: The next spacing step is too much.
37+
padding: calc($spacing-sm * 1.5);
38+
width: 100%;
1639

17-
&::placeholder,
1840
&.noValue {
1941
color: $color-grey-40;
2042
}
@@ -39,6 +61,25 @@
3961
}
4062
}
4163

64+
&:has(.floatingLabel) {
65+
::placeholder {
66+
@include visually-hidden;
67+
}
68+
.inputField {
69+
// Move the value string off-center.
70+
padding: calc($spacing-md * 1.25) calc($spacing-sm * 1.5) $spacing-xs;
71+
}
72+
}
73+
74+
&:focus-within,
75+
&:not(:has(:placeholder-shown)) {
76+
.floatingLabel {
77+
color: $color-grey-40;
78+
// Make the floating label visually align with the input value.
79+
transform: translate(-0.05em, -115%) scale(0.75);
80+
}
81+
}
82+
4283
.inputLabel {
4384
font-weight: 600;
4485
margin-bottom: $spacing-sm;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { expect } from "@jest/globals";
6+
import { render } from "@testing-library/react";
7+
import { composeStory } from "@storybook/react";
8+
import { axe } from "jest-axe";
9+
import Meta, {
10+
TextInputFieldEmpty,
11+
TextInputFieldEmptyFloatingLabel,
12+
TextInputFieldFilled,
13+
TextInputFieldFilledFloatingLabel,
14+
} from "./stories/InputField.stories";
15+
16+
describe("InputField", () => {
17+
test.each([
18+
TextInputFieldEmpty,
19+
TextInputFieldFilled,
20+
TextInputFieldEmptyFloatingLabel,
21+
TextInputFieldFilledFloatingLabel,
22+
])("passes the axe accessibility test suite for %s", async (component) => {
23+
const ComposedInput = composeStory(component, Meta);
24+
const { container } = render(<ComposedInput hasFloatingLabel />);
25+
expect(await axe(container)).toHaveNoViolations();
26+
});
27+
});

src/app/components/client/InputField.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useL10n } from "../../hooks/l10n";
1313
export const InputField = (
1414
props: AriaTextFieldProps & {
1515
iconButton?: ReactNode;
16+
hasFloatingLabel?: boolean;
1617
},
1718
) => {
1819
const { isRequired, label, isInvalid, value, description } = props;
@@ -29,13 +30,14 @@ export const InputField = (
2930
return (
3031
<div className={styles.input}>
3132
{label && (
32-
<label {...labelProps} className={styles.inputLabel}>
33-
{label}
34-
{
35-
// TODO: Add unit test when changing this code:
36-
/* c8 ignore next */
37-
isRequired ? <span aria-hidden="true">*</span> : ""
33+
<label
34+
{...labelProps}
35+
className={
36+
props.hasFloatingLabel ? styles.floatingLabel : styles.inputLabel
3837
}
38+
>
39+
{label}
40+
{isRequired ? <span aria-hidden="true">*</span> : ""}
3941
</label>
4042
)}
4143
<input

src/app/components/client/stories/InputField.stories.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,22 @@ export const DateInputFieldInvalidWithMessage: Story = {
8181
errorMessage: "Select a date",
8282
},
8383
};
84+
85+
export const TextInputFieldEmptyFloatingLabel: Story = {
86+
args: {
87+
label: "Text input floating label",
88+
placeholder: "Type here",
89+
type: "text",
90+
hasFloatingLabel: true,
91+
},
92+
};
93+
94+
export const TextInputFieldFilledFloatingLabel: Story = {
95+
args: {
96+
label: "Text input floating label",
97+
placeholder: "Type here",
98+
type: "text",
99+
value: "Input is filled",
100+
hasFloatingLabel: true,
101+
},
102+
};

0 commit comments

Comments
 (0)