Skip to content

Commit

Permalink
fix: TextField input width with textAfter
Browse files Browse the repository at this point in the history
  • Loading branch information
vadim-kudr committed Dec 21, 2024
1 parent f921dba commit 4c753d5
Show file tree
Hide file tree
Showing 19 changed files with 323 additions and 17 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,93 @@ describe('plasma-b2c: TextField', () => {
cy.matchImageSnapshot();
});

it('textBefore,textAfter', () => {
mount(
<CypressTestDecoratorWithTypo>
<TextField
size="s"
value="Outer"
placeholder="Placeholder"
label="Label"
labelPlacement="outer"
textBefore="_"
textAfter="%"
/>
<SpaceMe />
<TextField
size="s"
value="Outer focused"
placeholder="Placeholder"
label="Label"
labelPlacement="outer"
textBefore="_"
textAfter="%"
/>
<SpaceMe />
<TextField
size="s"
value="Inner"
placeholder="Placeholder"
label="Label"
labelPlacement="inner"
view="innerLabel"
textBefore="_"
textAfter="%"
/>
<SpaceMe />
<TextField
size="s"
value=""
placeholder="Placeholder"
label="Label"
labelPlacement="inner"
view="innerLabel"
textBefore="_"
textAfter="%"
/>
<SpaceMe />
<TextField
id="focused"
size="s"
value=""
placeholder="Placeholder"
label="Label"
labelPlacement="inner"
view="innerLabel"
keepPlaceholder
textBefore="_"
textAfter="%"
/>
</CypressTestDecoratorWithTypo>,
);

cy.get('#focused').focus();

cy.matchImageSnapshot();

mount(
<CypressTestDecoratorWithTypo>
<div id="test">
<TextField
id="empty"
size="s"
value=""
placeholder=""
label="Label"
labelPlacement="inner"
view="innerLabel"
keepPlaceholder
textBefore="_"
textAfter="%"
/>
</div>
</CypressTestDecoratorWithTypo>,
);

cy.get('#empty').focus();
cy.get('#test').matchImageSnapshot('empty');
});

it('_enumerationType:chip, _chipView, _chipValidator', () => {
mount(
<CypressTestDecoratorWithTypo>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const Input = styled.input<{ dynamicWidth: string; isManualInput: boolean
background-color: transparent;
outline: none;
width: ${({ dynamicWidth }) => dynamicWidth};
box-sizing: border-box;
cursor: ${({ isManualInput }) => (isManualInput ? 'text' : 'default')};
pointer-events: ${({ isManualInput }) => (isManualInput ? 'initial' : 'none')};
transition: width 0.1s;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,19 @@ export const Input = styled.input`
}
`;

export const InputContainer = styled.div`
export const InputContainer = styled.div<{ hasDynamicWidth?: boolean }>`
position: relative;
display: flex;
flex: 1;
min-width: 60%;
min-width: ${({ hasDynamicWidth }) => (hasDynamicWidth ? 'auto' : '60%')};
${Input} {
max-width: ${({ hasDynamicWidth }) => (hasDynamicWidth ? '100%' : 'none')};
}
`;

export const InputPlaceholder = styled.div<{ hasPadding?: boolean }>`
display: flex;
position: absolute;
top: 0;
left: 0;
Expand All @@ -90,6 +96,7 @@ export const OuterLabelWrapper = styled.div<{ isInnerLabel: boolean }>`
display: flex;
align-items: center;
white-space: ${({ isInnerLabel }) => (isInnerLabel ? 'nowrap' : 'normal')};
margin-bottom: ${({ isInnerLabel }) =>
isInnerLabel ? `var(${tokens.titleCaptionInnerLabelOffset})` : `var(${tokens.labelOffset})`};
`;
Expand Down Expand Up @@ -134,7 +141,9 @@ export const StyledContentRight = styled.div`

export const LeftHelper = styled.div``;

export const StyledTextBefore = styled.div``;
export const StyledTextBefore = styled.div<{ isHidden?: boolean }>`
visibility: ${({ isHidden }) => (isHidden ? 'hidden' : 'visible')};
`;

export const StyledTextAfter = styled.div``;

Expand Down
56 changes: 51 additions & 5 deletions packages/plasma-new-hope/src/components/TextField/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { classes } from './TextField.tokens';
import { TextFieldChip } from './ui';
import { useKeyNavigation } from './hooks';
import { HintComponent } from './ui/Hint/Hint';
import { getInputWidth } from './getInputWidth';

const optionalText = 'optional';

Expand Down Expand Up @@ -106,12 +107,15 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
onChangeChips,
onSearch,
onKeyDown,
onFocus,
onBlur,

...rest
},
ref,
) => {
const contentRef = useRef<HTMLDivElement>(null);
const inputContainerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const inputForkRef = useForkRef(inputRef, ref);
const chipsRefs = useRef<Array<HTMLButtonElement>>([]);
Expand All @@ -121,6 +125,8 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
const [hasValue, setHasValue] = useState(
Boolean(outerValue) || Boolean(inputRef?.current?.value) || Boolean(rest?.defaultValue),
);
const [hasFocus, setHasFocus] = useState(false);

const [chips, setChips] = useState<Array<ChipValues>>([]);
const [isHintVisible, setIsHintVisible] = useState(false);

Expand All @@ -140,6 +146,16 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
const hasOuterLabel = labelPlacement === 'outer' && hasLabelValue;
const innerKeepPlaceholder = keepPlaceholder && labelPlacement === 'inner';
const hasPlaceholder = Boolean(placeholder) && (innerKeepPlaceholder || !hasInnerLabel);
let hasTextAfter = Boolean(textAfter);
let hasTextBefore = textBefore && !isChipEnumeration;
if (labelPlacement === 'inner') {
if (!hasValue && !hasPlaceholder && !hasFocus) {
hasTextAfter = false;
hasTextBefore = false;
}
}

const hasPlaceholderPadding = hasInnerLabel && keepPlaceholder && size !== 'xs';

const innerLabelValue = hasInnerLabel || hasOuterLabel ? label : undefined;
const innerLabelPlacementValue = labelPlacement === 'inner' && !hasInnerLabel ? undefined : labelPlacement;
Expand Down Expand Up @@ -169,7 +185,31 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
const hintForkRef = useForkRef(hintRef, hintInnerRef);

const handleInput: FormEventHandler<HTMLInputElement> = (event) => {
setHasValue(Boolean((event.target as HTMLInputElement).value));
const { value } = event.target as HTMLInputElement;
if (hasTextAfter) {
const textWidth = getInputWidth(event.currentTarget, inputContainerRef.current);
event.currentTarget.style.width = `${textWidth}px`;
}
setHasValue(Boolean(value));
};

useEffect(() => {
if (hasTextAfter && inputRef.current) {
const textWidth = getInputWidth(inputRef.current, inputContainerRef.current);
inputRef.current.style.width = `${textWidth}px`;
} else {
inputRef.current?.style.removeProperty('width');
}
}, [hasTextAfter]);

const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
setHasFocus(true);
onFocus?.(event);
};

const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setHasFocus(false);
onBlur?.(event);
};

const handleHintShow = () => setIsHintVisible(true);
Expand Down Expand Up @@ -393,7 +433,9 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
onKeyDown={handleContentKeyDown}
className={withHasChips}
>
{textBefore && <StyledTextBefore>{textBefore}</StyledTextBefore>}
{Boolean(textBefore && isChipEnumeration) && (
<StyledTextBefore>{textBefore}</StyledTextBefore>
)}
{isChipEnumeration && Boolean(chips?.length) && (
<StyledChips className={classes.chipsWrapper}>
{chips?.map(({ id: chipId, text }, index) => {
Expand Down Expand Up @@ -427,7 +469,8 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
})}
</StyledChips>
)}
<InputContainer>
<InputContainer ref={inputContainerRef} hasDynamicWidth={hasTextAfter}>
{hasTextBefore && <StyledTextBefore>{textBefore}</StyledTextBefore>}
<Input
ref={inputForkRef}
id={innerId}
Expand All @@ -441,6 +484,8 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
onInput={handleInput}
onChange={handleChange}
onKeyDown={handleOnKeyDown}
onFocus={handleFocus}
onBlur={handleBlur}
{...rest}
/>
{hasInnerLabel && (
Expand All @@ -450,13 +495,14 @@ export const textFieldRoot = (Root: RootProps<HTMLDivElement, TextFieldRootProps
</Label>
)}
{placeholderShown && !hasValue && (
<InputPlaceholder hasPadding={keepPlaceholder && size !== 'xs'}>
<InputPlaceholder hasPadding={hasPlaceholderPadding}>
{hasTextBefore && <StyledTextBefore isHidden>{textBefore}</StyledTextBefore>}
{innerPlaceholderValue}
{hasPlaceholderOptional && optionalTextNode}
</InputPlaceholder>
)}
{hasTextAfter && <StyledTextAfter>{textAfter}</StyledTextAfter>}
</InputContainer>
{textAfter && <StyledTextAfter>{textAfter}</StyledTextAfter>}
</InputLabelWrapper>
{contentRight && <StyledContentRight>{contentRight}</StyledContentRight>}
</InputWrapper>
Expand Down
48 changes: 48 additions & 0 deletions packages/plasma-new-hope/src/components/TextField/getInputWidth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CSSProperties } from 'react';

const INHERITED_PROPERTIES = [
'font',
'letterSpacing',
'textTransform',
'fontKerning',
'fontOpticalSizing',
'fontSizeAdjust',
'fontStretch',
'fontVariant',
'fontWeight',
'fontVariationSettings',
'fontSynthesis',
'textIndent',
] as Extract<keyof CSSStyleDeclaration, string>[];

const measureStyles = {
position: 'absolute',
top: '0px',
left: '0px',
width: '0px',
height: '0px',
visibility: 'hidden',
overflow: 'scroll',
whiteSpace: 'pre',
} as CSSProperties;

export function getInputWidth(element: HTMLInputElement, container: HTMLDivElement | null) {
if (!element || !container) {
return 0;
}

const measure = document.createElement('span');
measure.innerText = element.value || element.placeholder || ' ';
Object.assign(measure.style, measureStyles);

const styles = window.getComputedStyle(element);

INHERITED_PROPERTIES.forEach((property) => {
measure.style.setProperty(property, String(styles[property]));
});

container.appendChild(measure);
const width = measure.scrollWidth;
container.removeChild(measure);
return width;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from '@linaria/core';

import { classes, tokens } from '../../TextField.tokens';
import { Input, InputContainer, Label, StyledTextAfter, StyledTextBefore } from '../../TextField.styles';
import { Input, InputContainer, Label } from '../../TextField.styles';

export const base = css`
&.${classes.outerLabelPlacement} {
Expand All @@ -17,10 +17,6 @@ export const base = css`
padding: var(${tokens.contentLabelInnerPadding});
}
${StyledTextAfter}, ${StyledTextBefore} {
padding: var(${tokens.contentLabelInnerPadding});
}
/* поднимает label вверх при фокусе, наличии значения */
${Input}:focus ~ ${Label}, ${Input}.${classes.hasValue} ~ ${Label}, ${Input}.${classes.keepPlaceholder} ~ ${Label} {
color: var(${tokens.placeholderColor});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ export const config = {
${tokens.clearIndicatorLabelPlacementInner}: 1.5rem auto auto -0.875rem;
${tokens.clearIndicatorLabelPlacementInnerRight}: 1.5rem -0.875rem auto auto;
${tokens.clearIndicatorHintInnerRight}: 1.5rem -2.25rem auto auto;
${tokens.textBeforeMargin}: 0 0.25rem 0 0;
${tokens.textAfterMargin}: 0 0 0 0.25rem;
`,
m: css`
${tokens.height}: 3rem;
Expand Down Expand Up @@ -289,6 +292,9 @@ export const config = {
${tokens.clearIndicatorLabelPlacementInner}: 1.25rem auto auto -0.875rem;
${tokens.clearIndicatorLabelPlacementInnerRight}: 1.25rem -0.875rem auto auto;
${tokens.clearIndicatorHintInnerRight}: 1.25rem -2.25rem auto auto;
${tokens.textBeforeMargin}: 0 0.25rem 0 0;
${tokens.textAfterMargin}: 0 0 0 0.25rem;
`,
s: css`
${tokens.height}: 2.5rem;
Expand Down Expand Up @@ -364,6 +370,9 @@ export const config = {
${tokens.clearIndicatorLabelPlacementInner}: 1.063rem auto auto -0.75rem;
${tokens.clearIndicatorLabelPlacementInnerRight}: 1.063rem -0.75rem auto auto;
${tokens.clearIndicatorHintInnerRight}: 1.063rem -2.125rem auto auto;
${tokens.textBeforeMargin}: 0 0.25rem 0 0;
${tokens.textAfterMargin}: 0 0 0 0.25rem;
`,
xs: css`
${tokens.height}: 2rem;
Expand Down Expand Up @@ -439,6 +448,9 @@ export const config = {
${tokens.clearIndicatorLabelPlacementInner}: 0.813rem auto auto -0.625rem;
${tokens.clearIndicatorLabelPlacementInnerRight}: 0.813rem -0.625rem auto auto;
${tokens.clearIndicatorHintInnerRight}: 0.813rem -1.875rem auto auto;
${tokens.textBeforeMargin}: 0 0.25rem 0 0;
${tokens.textAfterMargin}: 0 0 0 0.25rem;
`,
},
labelPlacement: {
Expand Down
Loading

0 comments on commit 4c753d5

Please sign in to comment.