wix-style-react
Version:
wix-style-react
217 lines • 10.7 kB
JavaScript
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