UNPKG

wix-style-react

Version:
299 lines (255 loc) • 8.46 kB
import PropTypes from 'prop-types'; import React from 'react'; import InfoIcon from '../InfoIcon'; 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 { WixStyleReactContext } from '../WixStyleReactProvider/context'; const PLACEMENT = { top: 'top', right: 'right', left: 'left', }; const ALIGN = { middle: 'middle', top: 'top', }; const asterisk = ( <div data-hook={dataHooks.asterisk} className={classes.asterisk} children="*" /> ); const charactersLeft = lengthLeft => { const colorProps = lengthLeft >= 0 ? { light: true, secondary: true } : { skin: SKINS.error }; return ( <Text size={SIZES.small} weight={WEIGHTS.normal} {...colorProps} dataHook={dataHooks.counter} children={lengthLeft} /> ); }; class FormField extends React.Component { static displayName = 'FormField'; static 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, /** Adds a string used to match text labels with FormField children items. E.g. * * ```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. Check <Tooltip/> API for a full list. */ 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 label size */ labelSize: PropTypes.oneOf(['tiny', 'small', 'medium']), /** 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, }; static defaultProps = { required: false, stretchContent: true, labelSize: 'medium', labelPlacement: PLACEMENT.top, labelAlignment: ALIGN.middle, }; state = { lengthLeft: undefined, }; childrenRenderPropInterface = { setCharactersLeft: lengthLeft => this.setState({ lengthLeft }), }; _renderChildren() { const { children } = this.props; if (typeof children === 'function') { return children(this.childrenRenderPropInterface); } return children; } _hasCharCounter = () => this.props.charCount !== undefined || typeof this.state.lengthLeft === 'number'; _renderCharCounter = () => { if (!this._hasCharCounter()) { return; } const { charCount } = this.props; return charactersLeft( charCount !== undefined ? charCount : this.state.lengthLeft, ); }; _renderInfoIcon = ({ labelSize }) => { const { infoContent, infoTooltipProps } = this.props; const iconSize = labelSize === 'tiny' ? 'small' : labelSize; return ( infoContent && ( <InfoIcon dataHook={dataHooks.infoIcon} className={classes.infoIcon} content={infoContent} tooltipProps={infoTooltipProps} size={iconSize} /> ) ); }; _renderLabelIndicators = ({ labelSize }) => { const { required, suffix } = this.props; return ( <div data-hook={dataHooks.labelIndicators} className={st(classes.labelIndicators, { inlineWithSuffix: Boolean(suffix || this._hasCharCounter()), })} > {this._renderLabel({ trimLongText: false, labelSize })} {required && asterisk} {this._renderInfoIcon({ labelSize })} </div> ); }; _renderSuffix = () => { const { suffix } = this.props; return ( (suffix || this._hasCharCounter()) && ( <div data-hook={dataHooks.suffix} className={classes.suffix}> {suffix ? suffix : this._renderCharCounter()} </div> ) ); }; _hasInlineLabel = (label, labelPlacement) => label && (labelPlacement === PLACEMENT.left || labelPlacement === PLACEMENT.right); _renderLabel = ({ trimLongText, labelSize }) => { const { label, id, labelId } = this.props; const weight = labelSize === 'tiny' ? 'normal' : undefined; return ( <Text size={labelSize} weight={weight} htmlFor={id} id={labelId} tagName={'label'} dataHook={dataHooks.label} ellipsis={trimLongText} style={{ display: 'block' }} // allows the label to middle vertically secondary > {label} </Text> ); }; render() { const { label, labelPlacement, labelAlignment, required, infoContent, dataHook, children, classNames, stretchContent, } = this.props; let { labelSize } = this.props; const rootStyles = label ? { labelPlacement, labelAlignment, stretchContent, required, minLabelHeight: !children, } : { stretchContent, required, minLabelHeight: !children, }; return ( <WixStyleReactContext.Consumer> {({ reducedSpacingAndImprovedLayout }) => { if (reducedSpacingAndImprovedLayout && labelSize === 'medium') { labelSize = 'small'; } return ( <div data-hook={dataHook} className={st( classes.root, { ...rootStyles, ...(label ? { labelSize } : {}) }, classNames, )} > {label && labelPlacement === PLACEMENT.top && ( <div className={classes.label}> {this._renderLabel({ trimLongText: true, labelSize })} {required && asterisk} {this._renderInfoIcon({ labelSize })} {this._renderSuffix()} </div> )} {children && ( <div data-hook={dataHooks.children} className={st(classes.children, { childrenWithInlineLabel: !label || this._hasInlineLabel(label, labelPlacement), })} > {(!label || labelPlacement !== PLACEMENT.top) && this._renderSuffix()} {this._renderChildren()} </div> )} {!label && (required || infoContent) && this._renderLabelIndicators({ labelSize })} {this._hasInlineLabel(label, labelPlacement) && this._renderLabelIndicators({ labelSize })} </div> ); }} </WixStyleReactContext.Consumer> ); } } export default FormField;