diff --git a/packages/web/src/scss/components/TextField/README.md b/packages/web/src/scss/components/TextField/README.md index 1a289d6dfa..1b9a5a3e15 100644 --- a/packages/web/src/scss/components/TextField/README.md +++ b/packages/web/src/scss/components/TextField/README.md @@ -93,7 +93,7 @@ The `size` attribute is supported on inputs of the following types: `email`, This option is generally recommended for inputs with a limited value length (e.g. numeric representation of day, month, year). Supported values are `2`, `3` and `4` (characters). If you need any other value or prefer using `em` unit -instead of default `ch`, define a `--width` CSS custom property on the `` +instead of default `ch`, define a `--textfield-input-width` CSS custom property on the `` element: ```html @@ -110,7 +110,7 @@ element: class="TextField__input" name="sizeEm" placeholder="Placeholder" - style="--width: 4em;" + style="--textfield-input-width: 4em;" /> ``` diff --git a/packages/web/src/scss/components/TextField/_TextField.scss b/packages/web/src/scss/components/TextField/_TextField.scss index f598c6cf11..af116117d4 100644 --- a/packages/web/src/scss/components/TextField/_TextField.scss +++ b/packages/web/src/scss/components/TextField/_TextField.scss @@ -1,3 +1,20 @@ +// 1. We pass the input arrows width if the input type is number. +// 2. If the input has a size attribute, we calculate the width based on the input width, padding, border width and arrows width. +// 3. We generate the input width based on the number of characters. This is useful if you want to set the width based on the number of characters. +// 4. We position the password toggle button next to the input and use pseudo-element for border styling. +// We do this because password managers place their icon buttons inside the password input. +// 5. Because the button is next to the input, we need to use pseudo element to style the border around both of them. +// 6. We need to set the same background color for the button when the input is hovered, active or focused. +// 7. We toggle icons of the password toggle and we disable the no-descending-specificity to make the code more readable. +// 8. To make the 5. work, we need to style the input and the button when the input is focused. +// 9. The password toggle button should have its own focus-visible style. +// 10. Disabled input and its optional password toggle should have the same disabled style. +// The code is more comprehensible when styling elements in this order: +// 1. input (with a pseudo-element), +// 2. password toggle button, +// 3. validation text. +// Also, high-specificity selectors are necessary to achieve the desired result. + @use '../../settings/cursors'; @use '../../theme/form-fields' as form-fields-theme; @use '../../tools/form-fields' as form-fields-tools; @@ -35,20 +52,23 @@ $_field-name: 'TextField'; appearance: none; } + // 1. &[type='number'] { - --arrows-width: #{theme.$input-number-arrows-width}; + --textfield-arrows-width: #{theme.$input-number-arrows-width}; } + // 2. &[size] { width: calc( - var(--width) + 2 * #{form-fields-theme.$box-field-input-padding-x} + 2 * #{form-fields-theme.$box-field-input-border-width} + - var(--arrows-width, 0px) + var(--textfield-input-width) + 2 * #{form-fields-theme.$box-field-input-padding-x} + 2 * #{form-fields-theme.$box-field-input-border-width} + + var(--textfield-arrows-width, 0px) ); } + // 3. @each $size in theme.$input-size-characters { &[size='#{$size}'] { - --width: #{$size}ch; + --textfield-input-width: #{$size}ch; } } } @@ -70,8 +90,7 @@ $_field-name: 'TextField'; border-bottom-right-radius: 0; } -// We position the button next to the input and use pseudo-element for border styling. -// We do this because password managers place their icon buttons inside the password input. +// 4. .TextField__passwordToggle__button { @include reset.button(); @@ -81,19 +100,30 @@ $_field-name: 'TextField'; padding: theme.$input-password-toggle-padding; color: form-fields-theme.$box-field-input-color-default; border-radius: 0 form-fields-theme.$box-field-input-border-radius form-fields-theme.$box-field-input-border-radius 0; - background-color: form-fields-theme.$box-field-input-background; + background-color: form-fields-theme.$input-background-color; + // 5. &::before { content: ''; position: absolute; inset: 0; border: form-fields-theme.$box-field-input-border-width form-fields-theme.$box-field-input-border-style - form-fields-theme.$box-field-input-border-color-default; + form-fields-theme.$input-border-color; border-radius: form-fields-theme.$box-field-input-border-radius; pointer-events: none; } } +// 6. +.TextField__input:hover + .TextField__passwordToggle__button { + background-color: form-fields-theme.$input-background-color-hover; +} + +.TextField__input:active + .TextField__passwordToggle__button, +.TextField__input:focus-within + .TextField__passwordToggle__button { + background-color: form-fields-theme.$input-background-color-active; +} + .TextField__passwordToggle__icon { pointer-events: none; } @@ -103,13 +133,13 @@ $_field-name: 'TextField'; display: block; } -// stylelint-disable-next-line no-descending-specificity -- In this case ↕️, the code is more readable this way. +// stylelint-disable-next-line no-descending-specificity -- 7. .TextField__passwordToggle__icon--shown, .TextField__passwordToggle__button[aria-pressed='true'] > .TextField__passwordToggle__icon--hidden { display: none; } -// stylelint-disable selector-max-specificity, selector-max-class, selector-max-compound-selectors -- We need high-specificity selectors here. +// stylelint-disable selector-max-specificity, selector-max-class, selector-max-compound-selectors -- 8. .TextField > .TextField__input:focus-visible, .TextField > .TextField__passwordToggle > .TextField__input:focus-visible ~ .TextField__passwordToggle__button::before { @include form-fields-tools.box-field-focus-visible(); @@ -121,6 +151,7 @@ $_field-name: 'TextField'; outline: 0; } +// 9. .TextField__passwordToggle__button:focus-visible::after { content: ''; position: absolute; @@ -153,36 +184,31 @@ $_field-name: 'TextField'; @include form-fields-tools.box-field-disabled-input(); } -// stylelint-disable no-descending-specificity, selector-max-specificity, selector-max-class, selector-max-compound-selectors -// -- The code is more comprehensible when styling elements in this order: -// 1. input (with a pseudo-element), -// 2. password toggle button, -// 3. validation text. -// Also, high-specificity selectors are necessary to achieve the desired result. +// stylelint-disable no-descending-specificity, selector-max-specificity, selector-max-class, selector-max-compound-selectors -- 10. .TextField > .TextField__input:disabled, :is(.TextField--disabled, .TextField.is-disabled) > .TextField__input, .TextField .TextField__passwordToggle .TextField__input:disabled ~ .TextField__passwordToggle__button::before, :is(.TextField--disabled, .TextField.is-disabled) .TextField__passwordToggle .TextField__passwordToggle__button::before { - border-color: theme.$input-border-color-disabled; + border-color: form-fields-theme.$input-border-color-disabled; } .TextField .TextField__input:disabled ~ .TextField__passwordToggle__button, :is(.TextField--disabled, .TextField.is-disabled) .TextField__passwordToggle__button { @include form-fields-tools.input-disabled(); - background-color: form-fields-theme.$box-field-input-background-disabled; + background-color: form-fields-theme.$input-background-color-disabled; pointer-events: none; cursor: cursors.$disabled; } // stylelint-enable +:is(.TextField--disabled, .TextField.is-disabled) > :is(.TextField__helperText) { + @include form-fields-tools.helper-text-disabled(); +} + :is(.TextField--disabled, .TextField.is-disabled) > :is(.TextField__validationText, [data-spirit-element='validation_text']) { @include form-fields-tools.validation-text-disabled(); } - -:is(.TextField--disabled, .TextField.is-disabled) > :is(.TextField__helperText) { - @include form-fields-tools.helper-text-disabled(); -} diff --git a/packages/web/src/scss/components/TextField/_theme.scss b/packages/web/src/scss/components/TextField/_theme.scss index cf733cfc77..1686280c7e 100644 --- a/packages/web/src/scss/components/TextField/_theme.scss +++ b/packages/web/src/scss/components/TextField/_theme.scss @@ -1,7 +1,7 @@ -@use '@tokens' as tokens; +@use '@global' as global-tokens; +@use '../../settings/globals'; -$input-border-color-disabled: tokens.$border-primary-disabled; $input-number-arrows-width: 1.25rem; -$input-password-toggle-padding: tokens.$space-400; -$input-password-toggle-icon-size: tokens.$space-700; +$input-password-toggle-padding: global-tokens.$space-500; +$input-password-toggle-icon-size: global-tokens.$space-900; $input-size-characters: (2, 3, 4); diff --git a/packages/web/src/scss/components/index.scss b/packages/web/src/scss/components/index.scss index 4dcbe6460b..91c3233cfb 100644 --- a/packages/web/src/scss/components/index.scss +++ b/packages/web/src/scss/components/index.scss @@ -32,7 +32,8 @@ @forward 'Tag'; // @forward 'TextArea'; -// @forward 'TextField'; +@forward 'TextField'; + // @forward 'Toast'; // @forward 'Tooltip'; @forward 'UNSTABLE_ActionLayout'; diff --git a/packages/web/src/scss/theme/_form-fields.scss b/packages/web/src/scss/theme/_form-fields.scss index 28913a2a15..61033fd87f 100644 --- a/packages/web/src/scss/theme/_form-fields.scss +++ b/packages/web/src/scss/theme/_form-fields.scss @@ -39,24 +39,20 @@ $inline-field-input-border-width: global-tokens.$border-width-200; $inline-field-input-background-color-checked: var(--#{globals.$prefix}color-selected-default); // Box field form components – TextField, TextArea, etc. -// Commented out to allow nice diff -// $box-field-input-color-default: tokens.$text-primary-default; -// $box-field-input-border-width: tokens.$border-width-100; -// $box-field-input-border-style: tokens.$border-style-100; -// $box-field-input-border-color-default: tokens.$border-primary-default; -// $box-field-input-border-color-disabled: tokens.$border-primary-disabled; -// $box-field-input-border-color-focus: tokens.$focus-default; -// -// $box-field-input-border-radius: tokens.$radius-100; -// $box-field-input-background: tokens.$background-basic; -// $box-field-input-background-disabled: tokens.$background-cover; -// $box-field-input-placeholder-color-default: tokens.$text-secondary-default; -// $box-field-input-placeholder-color-disabled: tokens.$text-secondary-disabled; -// $box-field-input-padding-x: tokens.$space-400; -// $box-field-input-padding-y: calc(#{tokens.$space-400} - #{tokens.$border-width-100}); -// $box-field-input-width: 18rem; -// $box-field-label-typography: tokens.$body-small-text-regular; -// $box-field-label-margin-bottom: tokens.$space-300; +$box-field-input-color-default: var(--#{globals.$prefix}color-form-field-content); +$box-field-input-border-width: global-tokens.$border-width-100; +$box-field-input-border-style: solid; +$box-field-input-border-color-focus: var(--#{globals.$prefix}color-border-focus); +$box-field-input-focus-shadow: global-tokens.$focus; + +$box-field-input-border-radius: global-tokens.$radius-300; +$box-field-input-placeholder-color-default: var(--#{globals.$prefix}color-form-field-placeholder); +$box-field-input-placeholder-color-disabled: var(--#{globals.$prefix}color-disabled-content); +$box-field-input-padding-x: global-tokens.$space-600; +$box-field-input-padding-y: calc(#{global-tokens.$space-500} - #{global-tokens.$border-width-100}); +$box-field-input-width: 18rem; +$box-field-label-typography: global-tokens.$body-small-semibold; +$box-field-label-margin-bottom: global-tokens.$space-400; $validation-states: ( success: ( diff --git a/packages/web/src/scss/tools/_form-fields.scss b/packages/web/src/scss/tools/_form-fields.scss index 472d88ff14..ecaa4684b0 100644 --- a/packages/web/src/scss/tools/_form-fields.scss +++ b/packages/web/src/scss/tools/_form-fields.scss @@ -101,14 +101,25 @@ padding: form-fields-theme.$box-field-input-padding-y form-fields-theme.$box-field-input-padding-x; color: form-fields-theme.$box-field-input-color-default; border: form-fields-theme.$box-field-input-border-width form-fields-theme.$box-field-input-border-style - form-fields-theme.$box-field-input-border-color-default; + form-fields-theme.$input-border-color; border-radius: form-fields-theme.$box-field-input-border-radius; - background: form-fields-theme.$box-field-input-background; + background: form-fields-theme.$input-background-color; &::placeholder { color: form-fields-theme.$box-field-input-placeholder-color-default; opacity: 1; } + + @media (hover: hover) { + &:hover { + background-color: form-fields-theme.$input-background-color-hover; + } + } + + &:active, + &:focus-within { + background-color: form-fields-theme.$input-background-color-active; + } } @mixin input-field-validation-states($field-name, $shadow-x: null, $shadow-y: null) { @@ -183,8 +194,8 @@ @mixin box-field-disabled-input() { @include input-disabled(); - border-color: form-fields-theme.$box-field-input-border-color-disabled; - background-color: form-fields-theme.$box-field-input-background-disabled; + border-color: form-fields-theme.$input-border-color-disabled; + background-color: form-fields-theme.$input-background-color-disabled; &::placeholder { color: form-fields-theme.$box-field-input-placeholder-color-disabled; diff --git a/tests/e2e/demo-components-compare.spec.ts b/tests/e2e/demo-components-compare.spec.ts index 1a4ad3410a..cdc4c1faa7 100644 --- a/tests/e2e/demo-components-compare.spec.ts +++ b/tests/e2e/demo-components-compare.spec.ts @@ -22,7 +22,6 @@ const IGNORED_TESTS: string[] = [ 'Stack', 'Text', 'TextArea', - 'TextField', 'Toast', 'Tooltip', 'UNSTABLE_Slider', diff --git a/tests/e2e/demo-components-compare.spec.ts-snapshots/textfield-chromium-linux.png b/tests/e2e/demo-components-compare.spec.ts-snapshots/textfield-chromium-linux.png index 582822c32a..0941b6f3c3 100644 Binary files a/tests/e2e/demo-components-compare.spec.ts-snapshots/textfield-chromium-linux.png and b/tests/e2e/demo-components-compare.spec.ts-snapshots/textfield-chromium-linux.png differ