From 938730d20aac15c5fd14d9246bd725224e7d4e95 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 18 Jul 2024 02:35:25 +0200 Subject: [PATCH] fix(NcInputField): Make sure label and placeholder is not jumping Rework layout logic to ensure the label is not jumping on focus. This was caused as our border width changes when focusing. Signed-off-by: Ferdinand Thiessen --- src/components/NcInputField/NcInputField.vue | 115 +++++++++---------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/src/components/NcInputField/NcInputField.vue b/src/components/NcInputField/NcInputField.vue index 6ed116ca16..a69e47f6b7 100644 --- a/src/components/NcInputField/NcInputField.vue +++ b/src/components/NcInputField/NcInputField.vue @@ -20,6 +20,9 @@ For a list of all available props and attributes, please check the [HTMLInputEle
@@ -34,9 +37,6 @@ For a list of all available props and attributes, please check the [HTMLInputEle aria-live="polite" :class="[inputClass, { - 'input-field__input--trailing-icon': showTrailingButton || hasTrailingIcon, - 'input-field__input--leading-icon': hasLeadingIcon, - 'input-field__input--label-outside': labelOutside, 'input-field__input--success': success, 'input-field__input--error': error, }]" @@ -46,10 +46,6 @@ For a list of all available props and attributes, please check the [HTMLInputEle @@ -330,34 +326,45 @@ export default { .input-field { --input-border-radius: var(--border-radius-element, var(--border-radius-large)); - // styles + // The padding before the input can start (leading button or border) + --input-padding-start: var(--border-radius-large); + // The padding where the input has to end (trailing button or border) + --input-padding-end: var(--border-radius-large); + // positional styles position: relative; width: 100%; margin-block-start: 6px; // for the label in active state - &__main-wrapper { - height: var(--default-clickable-area); - position: relative; - } - &--disabled { opacity: 0.4; filter: saturate(0.4); } + // If there is no internal label we reset the margin reserved for it + &--label-outside { + margin-block-start: 0; + } + + &--leading-icon { + --input-padding-start: calc(var(--default-clickable-area) - var(--default-grid-baseline)); + } + + &--trailing-icon { + --input-padding-end: calc(var(--default-clickable-area) - var(--default-grid-baseline)); + } + &--pill { --input-border-radius: var(--border-radius-pill); } - &__input { - margin: 0; - padding-inline: 12px 6px; // align with label 8px margin label + 6px padding label - 2px border input - height: var(--default-clickable-area) !important; - width: 100%; - - font-size: var(--default-font-size); - text-overflow: ellipsis; + &__main-wrapper { + height: var(--default-clickable-area); + position: relative; + } + &__input { + // If border width differes between focused and unfocused we need to compensate to prevent jumping + --input-border-width-offset: calc(var(--border-width-input-focused, 2px) - var(--border-width-input, 2px)); background-color: var(--color-main-background); color: var(--color-main-text); border: var(--border-width-input, 2px) solid var(--color-border-maxcontrast); @@ -368,10 +375,14 @@ export default { -moz-appearance: textfield !important; appearance: textfield !important; - // Center text if external label is used - &--label-outside { - padding-block: 0; - } + font-size: var(--default-font-size); + text-overflow: ellipsis; + + height: calc(var(--default-clickable-area) - 2 * var(--input-border-width-offset)) !important; + width: 100%; + + padding-inline: calc(var(--input-padding-start) + var(--input-border-width-offset)) calc(var(--input-padding-end) + var(--input-border-width-offset)); + padding-block: var(--input-border-width-offset); &:active:not([disabled]), &:hover:not([disabled]), @@ -379,6 +390,8 @@ export default { border-width: var(--border-width-input-focused, 2px); border-color: var(--color-main-text) !important; box-shadow: 0 0 0 2px var(--color-main-background) !important; + // Reset padding offset when focused + --input-border-width-offset: 0px; } &:focus + .input-field__label, @@ -386,11 +399,6 @@ export default { color: var(--color-main-text); } - // Hide placeholder while not focussed -> show label instead (only if internal label is used) - &:not(:focus,&--label-outside)::placeholder { - opacity: 0; - } - &:focus { cursor: text; } @@ -403,14 +411,6 @@ export default { box-shadow: unset !important; // Override server rules } - &--leading-icon { - padding-inline-start: var(--default-clickable-area); - } - - &--trailing-icon { - padding-inline-end: var(--default-clickable-area); - } - &--success { border-color: var(--color-success) !important; //Override hover border color &:focus-visible { @@ -426,12 +426,19 @@ export default { } } + // Hide placeholder while not focussed -> show label instead (only if internal label is used) + &:not(&--label-outside) &__input:not(:focus)::placeholder { + opacity: 0; + } + &__label { + --input-label-font-size: var(--default-font-size); position: absolute; - margin-inline: 14px 0; + margin-inline: var(--input-padding-start) var(--input-padding-end); max-width: fit-content; + font-size: var(--input-label-font-size); inset-block-start: calc((var(--default-clickable-area) - var(--default-line-height)) / 2); // center the label vertically - inset-inline: 0; + inset-inline: var(--border-width-input-focused, 2px); // Fix color so that users do not think the input already has content color: var(--color-text-maxcontrast); // only one line labels are allowed @@ -442,31 +449,21 @@ export default { pointer-events: none; // Position transition transition: height var(--animation-quick), inset-block-start var(--animation-quick), font-size var(--animation-quick), color var(--animation-quick), background-color var(--animation-quick) var(--animation-slow); - - &--leading-icon { - margin-inline-start: var(--default-clickable-area); - } - - &--trailing-icon { - margin-inline-end: var(--default-clickable-area); - } } &__input:focus + &__label, &__input:not(:placeholder-shown) + &__label { - inset-block-start: -10px; + --input-label-font-size: 13px; // minimum allowed font size for accessibility line-height: 1.5; // minimum allowed line height for accessibility - font-size: 13px; // minimum allowed font size for accessibility + // 1.5 * font-size = line-height; line-height / 2 for centering and make it negative as we need to move outside the element + inset-block-start: calc(-1.5 * var(--input-label-font-size) / 2); font-weight: 500; border-radius: var(--default-grid-baseline) var(--default-grid-baseline) 0 0; background-color: var(--color-main-background); - padding-inline: 5px; - margin-inline-start: 9px; + padding-inline: var(--default-grid-baseline); + margin-inline: calc(var(--input-padding-start) - var(--default-grid-baseline)) calc(var(--input-padding-end) - var(--default-grid-baseline)); transition: height var(--animation-quick), inset-block-start var(--animation-quick), font-size var(--animation-quick), color var(--animation-quick); - &--leading-icon { - margin-inline-start: 41px; - } } &__icon { @@ -478,14 +475,14 @@ export default { justify-content: center; opacity: 0.7; + inset-block-end: 0; + &--leading { - inset-block-end: 0; - inset-inline-start: 2px; + inset-inline-start: 0px; } &--trailing { - inset-block-end: 0; - inset-inline-end: 2px; + inset-inline-end: 0px; } }