@gravity-ui/uikit
Version:
Gravity UI base styling and components
114 lines (113 loc) • 6.68 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from 'react';
import { TriangleExclamation } from '@gravity-ui/icons';
import { useControlledState, useForkRef, useUniqId } from "../../../hooks/index.js";
import { useElementSize, useFormResetHandler } from "../../../hooks/private/index.js";
import { Icon } from "../../Icon/index.js";
import { Popover } from "../../legacy/index.js";
import { block } from "../../utils/cn.js";
import { ClearButton, mapTextInputSizeToButtonSize } from "../common/index.js";
import { OuterAdditionalContent } from "../common/OuterAdditionalContent/OuterAdditionalContent.js";
import { CONTROL_ERROR_ICON_QA, errorPropsMapper, getInputControlState, prepareAutoComplete, } from "../utils.js";
import { AdditionalContent } from "./AdditionalContent.js";
import { TextInputControl } from "./TextInputControl.js";
import "./TextInput.css";
const b = block('text-input');
export const TextInput = React.forwardRef(function TextInput(props, ref) {
const { view = 'normal', size = 'm', pin = 'round-round', name, value, defaultValue, label, disabled, readOnly, hasClear = false, error, errorMessage: errorMessageProp, errorPlacement: errorPlacementProp = 'outside', validationState: validationStateProp, autoComplete, id: idProp, tabIndex, style, className, qa, controlProps: controlPropsProp, startContent, endContent, note, onUpdate, onChange, } = props;
const { errorMessage, errorPlacement, validationState } = errorPropsMapper({
error,
errorMessage: errorMessageProp,
errorPlacement: errorPlacementProp,
validationState: validationStateProp,
});
const [inputValue, setInputValue] = useControlledState(value, defaultValue ?? '', onUpdate);
const innerControlRef = React.useRef(null);
const fieldRef = useFormResetHandler({ initialValue: inputValue, onReset: setInputValue });
const handleRef = useForkRef(props.controlRef, innerControlRef, fieldRef);
const labelRef = React.useRef(null);
const startContentRef = React.useRef(null);
const state = getInputControlState(validationState);
const isLabelVisible = Boolean(label);
const isErrorMsgVisible = validationState === 'invalid' && Boolean(errorMessage) && errorPlacement === 'outside';
const isErrorIconVisible = validationState === 'invalid' && Boolean(errorMessage) && errorPlacement === 'inside';
const isClearControlVisible = Boolean(hasClear && !disabled && !readOnly && inputValue);
const isStartContentVisible = Boolean(startContent);
const isEndContentVisible = Boolean(endContent);
const isAutoCompleteOff = isLabelVisible && !idProp && !name && typeof autoComplete === 'undefined';
const innerId = useUniqId();
const id = isLabelVisible ? idProp || innerId : idProp;
const labelSize = useElementSize(isLabelVisible ? labelRef : null, size);
const startContentSize = useElementSize(isStartContentVisible ? startContentRef : null, size);
const errorMessageId = useUniqId();
const noteId = useUniqId();
const ariaDescribedBy = [
controlPropsProp?.['aria-describedby'],
note ? noteId : undefined,
isErrorMsgVisible ? errorMessageId : undefined,
]
.filter(Boolean)
.join(' ');
const controlProps = {
...controlPropsProp,
style: {
...controlPropsProp?.style,
...(isLabelVisible && labelSize.width
? { paddingInlineStart: `${labelSize.width}px` }
: {}),
},
'aria-invalid': validationState === 'invalid' || undefined,
'aria-describedby': ariaDescribedBy || undefined,
};
const commonProps = {
id,
tabIndex,
name,
onChange(event) {
setInputValue(event.target.value);
if (onChange) {
onChange(event);
}
},
autoComplete: isAutoCompleteOff ? 'off' : prepareAutoComplete(autoComplete),
controlProps,
};
const handleClear = (event) => {
setInputValue('');
const control = innerControlRef.current;
if (control) {
const syntheticEvent = Object.create(event);
syntheticEvent.target = control;
syntheticEvent.currentTarget = control;
control.value = '';
if (onChange) {
onChange(syntheticEvent);
}
}
};
const handleAdditionalContentClick = (event) => {
const needActivateInput = !event.currentTarget.contains(document.activeElement) &&
event.currentTarget.contains(event.target);
const hasSelection = Boolean(document.getSelection()?.toString());
if (needActivateInput && !hasSelection) {
innerControlRef.current?.focus();
}
};
return (_jsxs("span", { ref: ref, style: style, className: b({
view,
size,
disabled,
state,
pin: view === 'clear' ? undefined : pin,
'has-clear': isClearControlVisible,
'has-start-content': isStartContentVisible,
'has-end-content': isClearControlVisible || isEndContentVisible,
}, className), "data-qa": qa, children: [_jsxs("span", { className: b('content'), children: [isStartContentVisible && (_jsx(AdditionalContent, { ref: startContentRef, placement: "start", onClick: handleAdditionalContentClick, children: startContent })), isLabelVisible && (_jsx("label", { ref: labelRef, style: {
insetInlineStart: isStartContentVisible
? startContentSize.width
: undefined,
maxWidth: `calc(50% - ${startContentSize.width}px)`,
}, className: b('label'), title: label, htmlFor: id, children: `${label}` })), _jsx(TextInputControl, { ...props, ...commonProps, controlRef: handleRef }), isClearControlVisible && (_jsx(ClearButton, { size: mapTextInputSizeToButtonSize(size), onClick: handleClear, className: b('clear', { size }) })), isErrorIconVisible && (_jsx(Popover, { content: errorMessage, children: _jsx("span", { "data-qa": CONTROL_ERROR_ICON_QA, children: _jsx(Icon, { data: TriangleExclamation, className: b('error-icon'), size: size === 's' ? 12 : 16 }) }) })), isEndContentVisible && (_jsx(AdditionalContent, { placement: "end", onClick: handleAdditionalContentClick, children: endContent }))] }), _jsx(OuterAdditionalContent, { note: note, errorMessage: isErrorMsgVisible ? errorMessage : null, noteId: noteId, errorMessageId: errorMessageId })] }));
});
//# sourceMappingURL=TextInput.js.map