Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PLASMA-4007: TextField input width with textAfter #1611

Merged
merged 1 commit into from
Dec 21, 2024
Merged
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
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
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
Loading