Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
20 changes: 11 additions & 9 deletions scss/forms/_text-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

--#{$prefix}text-input-error-color: var(--#{$prefix}color-content-status-negative);
--#{$prefix}text-input-invalid-icon: #{$danger-icon};
--#{$prefix}text-input-column-gap-trailing-error: #{$ouds-text-input-space-column-gap-trailing-error-action};
// scss-docs-end text-input-css-vars

.text-input-container {
Expand Down Expand Up @@ -231,7 +232,7 @@
> img,
> svg {
position: absolute;
top: calc(var(--#{$prefix}text-input-min-height) / 2 - var(--#{$prefix}text-input-icon-size) / 2); // stylelint-disable-line function-disallowed-list
top: calc(var(--#{$prefix}text-input-min-height) / 2 - var(--#{$prefix}text-input-icon-size) / 2 - var(--#{$prefix}text-input-border-width-top)); // stylelint-disable-line function-disallowed-list
left: calc(var(--#{$prefix}text-input-padding-x) - var(--#{$prefix}text-input-border-width-left)); // stylelint-disable-line function-disallowed-list
z-index: 2;
width: var(--#{$prefix}text-input-icon-size);
Expand All @@ -251,7 +252,7 @@
// Trailing action
> button {
position: absolute;
top: calc(var(--#{$prefix}text-input-min-height) / 2 - var(--#{$prefix}text-input-trailing-action-height) / 2); // stylelint-disable-line function-disallowed-list
top: calc(var(--#{$prefix}text-input-min-height) / 2 - var(--#{$prefix}text-input-trailing-action-height) / 2 - var(--#{$prefix}text-input-border-width-top)); // stylelint-disable-line function-disallowed-list
right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left)); // stylelint-disable-line function-disallowed-list
z-index: 3;
}
Expand Down Expand Up @@ -283,14 +284,13 @@
.input-container,
label {
// stylelint-disable-next-line function-disallowed-list
padding-right: calc($ouds-button-size-icon-only + var(--#{$prefix}text-input-column-gap) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
padding-right: calc($ouds-button-size-icon-only + var(--#{$prefix}text-input-column-gap-trailing-error) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}
}


&::after {
position: absolute;
top: 50%;
top: calc(var(--#{$prefix}text-input-min-height) / 2 - var(--#{$prefix}text-input-trailing-action-height) / 2 - var(--#{$prefix}text-input-border-width-top)); // stylelint-disable-line function-disallowed-list
right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left)); // stylelint-disable-line function-disallowed-list
width: var(--#{$prefix}text-input-trailing-action-width);
height: var(--#{$prefix}text-input-trailing-action-height);
Expand All @@ -301,12 +301,11 @@
content: "";
background-color: currentcolor;
mask: escape-svg(var(--#{$prefix}text-input-invalid-icon)) no-repeat 50% / px-to-rem($ouds-button-size-icon-only);
transform: translateY(-50%);
}

&:has(> button)::after {
// stylelint-disable-next-line function-disallowed-list
right: calc(calc(var(--#{$prefix}text-input-trailing-action-padding-right) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap)) - var(--#{$prefix}text-input-border-width-left));
right: calc(calc(var(--#{$prefix}text-input-trailing-action-padding-right) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap-trailing-error)) - var(--#{$prefix}text-input-border-width-left));
width: px-to-rem($ouds-button-size-icon-only);
}

Expand All @@ -328,19 +327,20 @@
}
}

// Shared styles for helper and error messages
// Shared styles for helpers and error messages
%text-input-message {
max-width: var(--#{$prefix}text-input-max-width);
padding-right: calc(var(--#{$prefix}text-input-padding-x) - var(--#{$prefix}text-input-border-width-right)); // stylelint-disable-line function-disallowed-list
padding-left: calc(var(--#{$prefix}text-input-padding-x) - var(--#{$prefix}text-input-border-width-left)); // stylelint-disable-line function-disallowed-list
@include get-font-size("label-medium");
}

// Helper text and helper link
.text-input-container ~ .text-input-helper {
@extend %text-input-message;
margin-top: var(--#{$prefix}text-input-helper-margin-top);
margin-bottom: 0;
color: var(--#{$prefix}text-input-helper-color);
@include get-font-size("label-medium");
}

.text-input-container ~ .link {
Expand All @@ -357,7 +357,9 @@
@extend %text-input-message;
display: none;
margin-top: var(--#{$prefix}text-input-helper-margin-top);
margin-bottom: 0;
color: var(--#{$prefix}text-input-error-color);
@include get-font-size("label-medium");
}

.text-input-container:has(.text-input-field.is-invalid) ~ .text-input-error,
Expand Down
131 changes: 73 additions & 58 deletions site/src/content/docs/forms/text-input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Add an icon to help indicate the purpose of the input by placing an `<svg>` or `

Trailing actions can be added to the right side of the input field by placing a minimal icon only button (`.btn`, `.btn-minimal` and `.btn-icon`) inside the `.text-input-container`. It can be used to provide actions related to the field: clear input, toggle password visibility, open a date picker, etc.

<Example code={`<div class="text-input">
<Example code={`<div class="text-input mb-md">
<div class="text-input-container">
<label for="exampleTextInputEmptyWithAction">Label</label>
<input type="email" class="text-input-field" id="exampleTextInputEmptyWithAction" placeholder="">
Expand All @@ -143,6 +143,18 @@ Trailing actions can be added to the right side of the input field by placing a
<span class="visually-hidden">Add to favorites</span>
</button>
</div>
</div>
<div class="text-input">
<div class="text-input-container text-input-container-outlined">
<label for="exampleTextInputEmptyWithActionOutlined">Label</label>
<input type="email" class="text-input-field" id="exampleTextInputEmptyWithActionOutlined" placeholder="">
<button class="btn btn-minimal btn-icon">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
<span class="visually-hidden">Add to favorites</span>
</button>
</div>
</div>`} />

### Helper text
Expand All @@ -158,38 +170,38 @@ Helper text should be explicitly associated with the text input it relates to us
<label for="inputTextPassword">Password</label>
<input type="password" id="inputTextPassword" class="text-input-field" aria-describedby="passwordTextHelpBlock" placeholder="">
</div>
<div id="passwordTextHelpBlock" class="text-input-helper">
<p id="passwordTextHelpBlock" class="text-input-helper">
Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.
</div>
</p>
</div>`} />

### Helper link

If the helper text is not sufficient, it's possible to offer the user an additional help link. The helper link can also be displayed on its own without helper text.
To display a helper link below inputs, use a [standard `.link` class]([[docsref:/components/links#standard-variant]]) as a sibling of a `.text-input-container`.
To display a helper link below inputs, use a standard small link [with `.link` and `.link-sm` classes]([[docsref:/components/links#sizes]]) as a sibling of a `.text-input-container`.

<Callout type="warning">
If a helper link is used in conjunction with a helper text, the `aria-describedby` attribute should refer to the helper text and the helper link should to be referenced by the `aria-labelledby` attribute.
</Callout>

<Example code={`<div class="text-input">
<Example code={`<div class="text-input mb-md">
<div class="text-input-container">
<label for="inputTextWithHelperTextLink">Send your feedback</label>
<input type="text" id="inputTextWithHelperTextLink" aria-describedby="feedbackTextHelpBlock" aria-labelledby="feedbackTextHelpMore" class="text-input-field" placeholder="">
</div>
<div id="feedbackTextHelpBlock" class="text-input-helper">
<p id="feedbackTextHelpBlock" class="text-input-helper">
Please describe your feedback in a few words.
</div>
<a href="#" id="feedbackTextHelpMore" class="link">
</p>
<a href="#" id="feedbackTextHelpMore" class="link link-sm">
More information
</a>
</div>
<div class="text-input">
<div class="text-input-container mt-lg">
<div class="text-input-container">
<label for="inputTextWithHelperLink">Send your feedback</label>
<input type="text" id="inputTextWithHelperLink" aria-describedby="feedbackTextHelpLink" class="text-input-field" placeholder="">
</div>
<a href="#" id="feedbackTextHelpLink" class="link">
<a href="#" id="feedbackTextHelpLink" class="link link-sm">
More information
</a>
</div>`} />
Expand All @@ -202,55 +214,58 @@ A textual prefix or suffix can be added to an input by wrapping the `<input>` in
For accessibility purpose the prefix and suffix should be explicitly mentioned in the helper text related to the input by the `aria-describedby` attribute. This will ensure that assistive technologies—such as screen readers—will announce these additional or already existing elements when the user focuses or enters the input.
</Callout>

<Example code={`<div class="text-input">
<Example code={`<div class="text-input mb-md">
<div class="text-input-container">
<label for="inputTextWithPrefix">Enter you website URL</label>
<div class="input-container" data-bs-prefix="https://">
<input type="text" id="inputTextWithPrefix" aria-describedby="prefixTextHelpBlock" class="text-input-field" placeholder="">
</div>
</div>
<div id="prefixTextHelpBlock" class="text-input-helper">
<p id="prefixTextHelpBlock" class="text-input-helper">
Please fill in the complete URL without the protocol (e.g. www.example.com).
</div>
<div class="text-input-container mt-md">
<label for="inputTextWithSuffix">Price</label>
<div class="input-container" data-bs-suffix="€">
<input type="text" id="inputTextWithSuffix" aria-describedby="suffixTextHelpBlock" class="text-input-field" placeholder="">
</p>
</div>
<div class="text-input mb-md">
<div class="text-input-container">
<label for="inputTextWithSuffix">Email</label>
<div class="input-container" data-bs-suffix="@orange.com">
<input type="email" id="inputTextWithSuffix" aria-describedby="suffixTextHelpBlock" class="text-input-field" placeholder="">
</div>
</div>
<div id="suffixTextHelpBlock" class="text-input-helper">
Enter your price in Euro (€).
</div>
<p id="suffixTextHelpBlock" class="text-input-helper">
Enter your email address without the domain name (e.g. only your username before the @).
</p>
</div>
<div class="text-input">
<div class="text-input-container mt-md">
<div class="text-input mb-md">
<div class="text-input-container">
<label for="inputTextWithPrefixSuffix">Price</label>
<div class="input-container" data-bs-prefix="£" data-bs-suffix=".00">
<input type="text" id="inputTextWithPrefixSuffix" aria-describedby="prefixSuffixTextHelpBlock" class="text-input-field" placeholder="Enter amount">
</div>
</div>
<div id="prefixSuffixTextHelpBlock" class="text-input-helper">
<p id="prefixSuffixTextHelpBlock" class="text-input-helper">
Enter your price in Pounds (£) without decimals.
</div>
</p>
</div>
<div class="text-input">
<div class="text-input-container mt-md">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
<label for="inputTextWithPrefixSuffixLeadingTrailing">Email</label>
<div class="input-container" data-bs-prefix="🖳" data-bs-suffix="@orange.com">
<input type="email" id="inputTextWithPrefixSuffixLeadingTrailing" aria-describedby="globalTextHelpBlock" class="text-input-field" placeholder="">
</div>
<button class="btn btn-minimal btn-icon">
<svg aria-hidden="true">
<div class="text-input-container">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
<span class="visually-hidden">Add to favorites</span>
</button>
</div> <div id="globalTextHelpBlock" class="text-input-helper">
Enter your email address without the domain name (e.g. only your username before the @).
<label for="inputTextWithPrefixSuffixIcon">Price</label>
<div class="input-container" data-bs-prefix="€" data-bs-suffix=".00">
<input type="text" id="inputTextWithPrefixSuffixIcon" aria-describedby="prefixSuffixIconTextHelpBlock" class="text-input-field" placeholder="">
</div>
<button class="btn btn-minimal btn-icon">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
<span class="visually-hidden">Add to favorites</span>
</button>
</div>
<p id="prefixSuffixIconTextHelpBlock" class="text-input-helper">
Enter your price in Euro (€) without decimals.
</p>
</div>`} />

## States
Expand Down Expand Up @@ -328,12 +343,12 @@ For accessibility purpose, the invalid should be associated with a `.text-input-
<label for="exampleTextInputInvalidEmpty">Username</label>
<input type="text" class="text-input-field is-invalid" id="exampleTextInputInvalidEmpty" aria-describedby="usernameTextHelpBlock" aria-labelledby="usernameFeedback" placeholder="">
</div>
<div id="usernameTextHelpBlock" class="text-input-helper">
<p id="usernameTextHelpBlock" class="text-input-helper">
Please choose a username.
</div>
<div id="usernameFeedback" class="text-input-error">
</p>
<p id="usernameFeedback" class="text-input-error">
Username is required.
</div>
</p>
</div>
<div class="text-input mb-md">
<div class="text-input-container">
Expand All @@ -346,9 +361,9 @@ For accessibility purpose, the invalid should be associated with a `.text-input-
<span class="visually-hidden">Add to favorites</span>
</button>
</div>
<div id="emailFeedback" class="text-input-error">
<p id="emailFeedback" class="text-input-error">
Email is invalid.
</div>
</p>
</div>
<div class="text-input mb-md">
<div class="text-input-container text-input-container-outlined">
Expand All @@ -358,21 +373,21 @@ For accessibility purpose, the invalid should be associated with a `.text-input-
<label for="exampleTextInputOutlinedInvalidEmpty">Address</label>
<input type="text" class="text-input-field is-invalid" id="exampleTextInputOutlinedInvalidEmpty" aria-labelledby="addressFeedback" placeholder="">
</div>
<div id="addressFeedback" class="text-input-error">
<p id="addressFeedback" class="text-input-error">
Address is required.
</div>
</p>
</div>
<div class="text-input mb-md">
<div class="text-input-container text-input-container-outlined">
<label for="exampleTextInputOutlinedInvalid" class="form-label">City</label>
<label for="exampleTextInputOutlinedInvalid">City</label>
<input type="text" class="text-input-field is-invalid" id="exampleTextInputOutlinedInvalid" value="Doe" aria-labelledby="cityFeedback" placeholder="">
</div>
<div id="cityFeedback" class="text-input-error">
<p id="cityFeedback" class="text-input-error">
City is invalid.
</div>
</p>
</div>
<div class="text-input mb-md">
<div class="text-input-container text-input-container">
<div class="text-input-container">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
Expand All @@ -381,12 +396,12 @@ For accessibility purpose, the invalid should be associated with a `.text-input-
<input type="text" class="text-input-field is-invalid" id="exampleTextInputInvalidWithPrefixSuffix" value="-2.2XX" aria-labelledby="priceFeedback" placeholder="">
</div>
</div>
<div id="priceFeedback" class="text-input-error">
<p id="priceFeedback" class="text-input-error">
Price is not valid.
</div>
</p>
</div>
<div class="text-input">
<div class="text-input-container text-input-container">
<div class="text-input-container">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#heart-empty')}"/>
</svg>
Expand All @@ -401,13 +416,13 @@ For accessibility purpose, the invalid should be associated with a `.text-input-
<span class="visually-hidden">Delete</span>
</button>
</div>
<div id="priceTextHelpBlock" class="text-input-helper">
<p id="priceTextHelpBlock" class="text-input-helper">
Please enter your price.
</div>
<div id="invalidPrice" class="text-input-error">
</p>
<p id="invalidPrice" class="text-input-error">
Price is not valid.
</div>
<a href="#" id="priceTextHelpLink" class="link">
</p>
<a href="#" id="priceTextHelpLink" class="link link-sm">
More information
</a>
</div>`} />
Expand Down
27 changes: 27 additions & 0 deletions site/src/content/docs/migrations/migration-from-boosted.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,33 @@ See [our new Switch page]([[docsref:/forms/switch]]) for more information.

- <span class="tag tag-sm rounded-none text-bg-status-warning-emphasized">Warning</span> Links in switches’ labels are now forbidden (they won’t be interactive anyway).

#### Text Input

- <span class="tag tag-sm rounded-none text-bg-status-positive-emphasized">New</span> `.text-input` has been added.

- <span class="tag tag-sm rounded-none text-bg-status-negative-emphasized">Breaking</span> Text Input is a new component compared to Boosted’s Input, the DOM is therefore very different.
<div class="ps-xl">
For example, if you used to write:

```html
<div class="mb-3">
<label for="exampleFormControlInput" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleFormControlInput" placeholder="[email protected]">
</div>
```
Now you should write:

```html
<div class="text-input">
<div class="text-input-container">
<label for="exampleTextInput" class="form-label">Email address</label>
<input type="email" class="text-input-field" id="exampleTextInput" placeholder="[email protected]">
</div>
</div>
```
See [our new Text Input page]([[docsref:/forms/text-input]]) for more information.
</div>

## Helpers

### Color background
Expand Down
6 changes: 6 additions & 0 deletions site/src/content/docs/migrations/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ toc: true

- <span class="tag tag-sm rounded-none text-bg-status-negative-emphasized">Breaking</span> The focus-visible polyfill has been removed. So you no longer need it on your side, and you should replace all the calls to `:focus[data-focus-visible-added]` by `:focus-visible`.

### Forms

#### Text Input

- <span class="tag tag-sm rounded-none text-bg-status-positive-emphasized">New</span> Text Input has been implemented.

### Components

#### Button
Expand Down
Loading