UNPKG

wix-style-react

Version:
217 lines 10.7 kB
import PropTypes from 'prop-types'; import React from 'react'; import InfoIcon from '../InfoIcon'; import uniqueId from 'lodash/uniqueId'; import Text, { SIZES, SKINS, WEIGHTS } from '../Text'; import { dataHooks } from './constants'; import { st, classes } from './FormField.st.css'; import { TooltipCommonProps } from '../common/PropTypes/TooltipCommon'; import { StatusAlertFilledSmall, StatusWarningFilledSmall, } from '@wix/wix-ui-icons-common'; import { StatusContext } from './StatusContext'; const PLACEMENT = { top: 'top', right: 'right', left: 'left', }; const ALIGN = { middle: 'middle', top: 'top', }; const LABEL_STYLE = { display: 'block', // allows the label to middle vertically }; const STATUS = { error: 'error', warning: 'warning', loading: 'loading', }; const asterisk = (React.createElement("div", { "data-hook": dataHooks.asterisk, className: classes.asterisk, children: "*" })); class FormField extends React.Component { constructor(props) { super(props); this.state = { lengthLeft: undefined, }; this.childrenRenderPropInterface = { setCharactersLeft: lengthLeft => this.setState({ lengthLeft }), }; this._hasCharCounter = () => this.props.charCount !== undefined || typeof this.state.lengthLeft === 'number'; this.charactersLeft = lengthLeft => { const { labelSize } = this.props; const colorProps = lengthLeft >= 0 ? { light: true, secondary: true } : { skin: SKINS.error }; return (React.createElement(Text, { className: st(classes.charCount, { labelSize }), size: SIZES.small, weight: WEIGHTS.normal, ...colorProps, dataHook: dataHooks.counter, children: lengthLeft })); }; this._renderCharCounter = () => { if (!this._hasCharCounter()) { return; } const { charCount } = this.props; return this.charactersLeft(charCount !== undefined ? charCount : this.state.lengthLeft); }; this._renderInfoIcon = () => { const { infoContent, infoTooltipProps } = this.props; return (infoContent && (React.createElement(InfoIcon, { dataHook: dataHooks.infoIcon, className: classes.infoIcon, content: infoContent, tooltipProps: infoTooltipProps, size: "small" }))); }; this._renderLabelWithIndicators = ({ labelSize }) => { const { required, suffix } = this.props; return (React.createElement("div", { "data-hook": dataHooks.labelIndicators, className: st(classes.labelIndicators, { inlineWithSuffix: Boolean(suffix || this._hasCharCounter()), }) }, this._renderLabel({ trimLongText: false, labelSize }), required && asterisk, this._renderInfoIcon())); }; this._renderSuffix = () => { const { suffix } = this.props; return ((suffix || this._hasCharCounter()) && (React.createElement("div", { "data-hook": dataHooks.suffix, className: st(classes.suffix, { noLabel: !this.props.label || this.props.labelPlacement !== PLACEMENT.top, }) }, suffix ? suffix : this._renderCharCounter()))); }; this._hasInlineElements = (label, labelPlacement) => { const hasInlineLabel = label && (labelPlacement === PLACEMENT.left || labelPlacement === PLACEMENT.right); const hasInlineIndicator = !label && (this.props.required || !!this.props.infoContent); return hasInlineLabel || hasInlineIndicator; }; this._renderLabel = ({ trimLongText, labelSize }) => { const { label, id } = this.props; const weight = labelSize === 'tiny' ? 'normal' : undefined; return (React.createElement(Text, { size: labelSize, weight: weight, htmlFor: id, id: this.labelId, tagName: "label", dataHook: dataHooks.label, ellipsis: trimLongText, style: LABEL_STYLE, secondary: true, className: st(classes.label, { labelSize, }) }, label)); }; this._renderStatusIcon = () => { const iconByStatus = { [STATUS.error]: StatusAlertFilledSmall, [STATUS.warning]: StatusWarningFilledSmall, }; const Icon = iconByStatus[this.props.status]; return (React.createElement("div", { className: classes.statusIcon }, React.createElement(Icon, null))); }; this._renderStatusMessage = () => { const { statusMessage, status, labelSize } = this.props; return (React.createElement("div", { className: st(classes.statusMessage, { status, labelSize, }) }, (status === STATUS.error || status === STATUS.warning) && this._renderStatusIcon(), React.createElement(Text, { id: this.statusId, dataHook: dataHooks.statusMessage, skin: status === STATUS.error ? 'error' : 'standard', secondary: status !== STATUS.error, size: labelSize }, statusMessage))); }; this.labelId = props.labelId || uniqueId('formfield-'); this.statusId = props.labelId ? `${props.labelId}-status` : uniqueId('formfield-status-'); } _renderChildren() { const { children } = this.props; if (typeof children === 'function') { return children(this.childrenRenderPropInterface); } return children; } render() { const { label, labelPlacement, labelAlignment, labelSize, required, dataHook, children, classNames, stretchContent, statusMessage, status, } = this.props; const rootStyles = label ? { labelPlacement, labelAlignment, stretchContent, required, minLabelHeight: !children, } : { stretchContent, required, minLabelHeight: !children, }; const hasInlineElements = this._hasInlineElements(label, labelPlacement); return (React.createElement("div", { "data-hook": dataHook, "data-status": status, className: st(classes.root, { ...rootStyles, ...(label ? { labelSize } : {}), hasInlineElements }, classNames) }, label && labelPlacement === PLACEMENT.top && (React.createElement("div", { className: classes.labelRow }, React.createElement("div", { className: classes.labelRowMain }, this._renderLabel({ trimLongText: true, labelSize }), required && asterisk, this._renderInfoIcon()), this._renderSuffix())), children && (React.createElement("div", { "data-hook": dataHooks.children, className: st(classes.children, { childrenWithInlineLabel: hasInlineElements, }) }, (!label || labelPlacement !== PLACEMENT.top) && this._renderSuffix(), React.createElement(StatusContext.Provider, { value: { status, ariaLabelledBy: this.labelId, ariaDescribedBy: this.statusId, } }, this._renderChildren()))), hasInlineElements && this._renderLabelWithIndicators({ labelSize }), statusMessage && this._renderStatusMessage())); } } FormField.displayName = 'FormField'; FormField.propTypes = { /** when function, it receives object with: * * `setCharactersLeft` - function accepts a number and will display it on top right of `FormField` component * * Note that alternatively you can also use `charCount` prop to display character count * instead of using the render function method. */ /** Accept any kind of component as a child element. A child should be a form element like an Input, InputArea, Dropdown or RichTextArea. */ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), /** Applies a data-hook HTML attribute that can be used in tests. */ dataHook: PropTypes.string, /** Input id used for connecting label to the input element using native ```for``` attribute * * ```js * <FormField id="myFormField" label="Hello"> * <Input id="myFormField"/> * </FormField> * ``` */ id: PropTypes.string, /** Displays a passed info message in a tooltip. Default value is a text string, but it can also be overridden with any other component. */ infoContent: PropTypes.node, /** Allows control over the tooltip style and behaviour by passed tooltip properties. * @linkTypeTo components-overlays--tooltip * @setTypeName TooltipCommonProps */ infoTooltipProps: PropTypes.shape(TooltipCommonProps), /** Sets a field label. It’s default value is a text string, but it can be overridden with any other component. */ label: PropTypes.node, /** Controls the label alignment */ labelAlignment: PropTypes.oneOf([ALIGN.middle, ALIGN.top]), /** Controls the label placement */ labelPlacement: PropTypes.oneOf([ PLACEMENT.top, PLACEMENT.right, PLACEMENT.left, ]), /** Controls the size of label */ labelSize: PropTypes.oneOf(['tiny', 'small']), /** Marks a field as mandatory with an asterisk (*) at the end of a label. */ required: PropTypes.bool, /** Defines whether or not the content (children container) grows when there's space available. Otherwise, it only uses the necessary space. */ stretchContent: PropTypes.bool, /** Adds a custom element at the end of the label row (it overrides the charCount in case it's provided). */ suffix: PropTypes.node, /** Sets the maximum length for the field value. Character count is displayed in the top right corner of a component. */ charCount: PropTypes.number, /** Sets the id of the label */ labelId: PropTypes.string, /** Sets the status message. It is displayed bellow the child component*/ statusMessage: PropTypes.node, /** Sets the type of status message, to give it appropriate colors and icons */ status: PropTypes.oneOf([STATUS.error, STATUS.warning, STATUS.loading]), }; FormField.defaultProps = { required: false, stretchContent: true, labelSize: 'small', labelPlacement: PLACEMENT.top, labelAlignment: ALIGN.middle, }; export default FormField; //# sourceMappingURL=FormField.js.map