UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

590 lines (589 loc) 20.1 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; var _AlignmentHelper; import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { warn, isTrue, makeUniqueId, extendPropsWithContextInClassComponent, validateDOMAttributes, processChildren, getStatusState, convertStatusToStateOnly, combineDescribedBy, dispatchCustomElementEvent, convertJsxToString } from "../../shared/component-helper.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import { spacingPropTypes, createSpacingClasses } from "../space/SpacingHelper.js"; import { skeletonDOMAttributes, createSkeletonClass } from "../skeleton/SkeletonHelper.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js"; import Button, { buttonVariantPropType } from "../button/Button.js"; import FormLabel from "../form-label/FormLabel.js"; import FormStatus from "../form-status/FormStatus.js"; import IconPrimary from "../icon-primary/IconPrimary.js"; import Context from "../../shared/Context.js"; import Suffix from "../../shared/helpers/Suffix.js"; export const inputPropTypes = { type: PropTypes.string, size: PropTypes.oneOfType([PropTypes.oneOf(['default', 'small', 'medium', 'large']), PropTypes.number]), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), id: PropTypes.string, label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), label_direction: PropTypes.oneOf(['horizontal', 'vertical']), label_sr_only: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), status: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func, PropTypes.node]), globalStatus: PropTypes.shape({ id: PropTypes.string, message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]) }), status_state: PropTypes.string, status_props: PropTypes.object, status_no_animation: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), input_state: PropTypes.string, autocomplete: PropTypes.string, submit_button_title: PropTypes.string, clear_button_title: PropTypes.string, placeholder: PropTypes.node, clear: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), keep_placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), align: PropTypes.oneOf(['left', 'center', 'right']), selectall: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), stretch: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), disabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), input_class: PropTypes.string, input_attributes: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), input_element: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), icon_size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), icon_position: PropTypes.oneOf(['left', 'right']), inner_ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), readOnly: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), inner_element: PropTypes.node, submit_element: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), submit_button_variant: buttonVariantPropType.variant, submit_button_icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), submit_button_status: PropTypes.string, ...spacingPropTypes, className: PropTypes.string, children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), on_change: PropTypes.func, on_key_down: PropTypes.func, on_submit: PropTypes.func, on_focus: PropTypes.func, on_blur: PropTypes.func, on_submit_focus: PropTypes.func, on_submit_blur: PropTypes.func, on_state_update: PropTypes.func, on_clear: PropTypes.func }; export default class Input extends React.PureComponent { static contextType = Context; static defaultProps = { type: 'text', size: null, value: 'initval', id: null, label: null, label_direction: null, label_sr_only: null, status: null, globalStatus: null, status_state: 'error', status_props: null, status_no_animation: null, input_state: null, autocomplete: 'off', placeholder: null, clear: null, keep_placeholder: null, suffix: null, align: null, selectall: null, stretch: null, disabled: null, skeleton: null, input_class: null, input_attributes: null, input_element: null, inner_ref: null, icon: null, icon_size: null, icon_position: 'left', readOnly: false, inner_element: null, submit_element: null, submit_button_title: null, clear_button_title: null, submit_button_variant: 'secondary', submit_button_icon: 'loupe', submit_button_status: null, className: null, children: null, on_change: null, on_key_down: null, on_submit: null, on_focus: null, on_blur: null, on_submit_focus: null, on_submit_blur: null, on_state_update: null, on_clear: null }; static getDerivedStateFromProps(props, state) { const value = Input.getValue(props); if (value !== 'initval' && value !== state.value && value !== state._value) { if (value !== state.value && typeof props.on_state_update === 'function') { dispatchCustomElementEvent({ props }, 'on_state_update', { value }); } state.value = value; } if (props.input_state) { state.inputState = props.input_state; } state._value = props.value; return state; } static hasValue(value) { return (typeof value === 'string' || typeof value === 'number') && String(value).length > 0 || false; } static getValue(props) { const value = processChildren(props); if (value === '' || Input.hasValue(value)) { return value; } return props.value; } state = { inputState: 'virgin', value: null, _value: null }; constructor(props, context) { super(props); this._ref = props.inner_ref || React.createRef(); this._id = props.id || context.FormRow && typeof context.FormRow.useId === 'function' && context.FormRow.useId() || context.formElement && typeof context.formElement.useId === 'function' && context.formElement.useId() || makeUniqueId(); if (isTrue(props.clear) && props.icon_position === 'right') { warn('You cannot have a clear button and icon_position="right"'); } } componentWillUnmount() { clearTimeout(this._selectallTimeout); } componentDidMount() { this.updateInputValue(); } componentDidUpdate() { this.updateInputValue(); } updateInputValue = () => { if (this._ref.current && !this.props.input_element) { const value = this.state.value; const hasValue = Input.hasValue(value); this._ref.current.value = hasValue ? value : ''; } }; onFocusHandler = event => { const { value } = event.target; this.setState({ inputState: 'focus' }); dispatchCustomElementEvent(this, 'on_focus', { value, event }); if (isTrue(this.props.selectall) && this._ref.current) { clearTimeout(this._selectallTimeout); this._selectallTimeout = setTimeout(() => { try { this._ref.current.select(); } catch (e) { warn(e); } }, 1); } }; onBlurHandler = event => { const { value } = event.target; const result = dispatchCustomElementEvent(this, 'on_blur', { value, event }); if (result !== false) { this.setState({ inputState: Input.hasValue(value) && value !== this.state._value ? 'dirty' : 'initial' }); } }; onChangeHandler = event => { const { value } = event.target; const result = dispatchCustomElementEvent(this, 'on_change', { value, event }); if (result === false) { this.updateInputValue(); return; } if (typeof result === 'string') { this.setState({ value: result }); } else { this.setState({ value }); } }; onKeyDownHandler = event => { const value = event.target.value; dispatchCustomElementEvent(this, 'on_key_down', { value, event }); if (event.key === 'Enter') { dispatchCustomElementEvent(this, 'on_submit', { value, event }); } }; clearValue = event => { const previousValue = this.state.value; const value = ''; this.setState({ value }); dispatchCustomElementEvent(this, 'on_change', { value, event }); dispatchCustomElementEvent(this, 'on_clear', { value, previousValue, event }); this._ref.current.focus({ preventScroll: true }); }; render() { const props = extendPropsWithContextInClassComponent(this.props, Input.defaultProps, { skeleton: this.context?.skeleton }, this.context.getTranslation(this.props).Input, pickFormElementProps(this.context?.FormRow), pickFormElementProps(this.context?.formElement), this.context.Input); const { type, size, label, label_direction, label_sr_only, status, globalStatus, status_state, status_props, status_no_animation, disabled, skeleton, placeholder, clear, keep_placeholder, suffix, align, input_class, submit_button_title, clear_button_title, submit_button_variant, submit_button_icon, submit_button_status, submit_element, inner_element, autocomplete, readOnly, stretch, input_attributes, icon, icon_position, icon_size, className, id: _id, children, value: _value, selectall, on_submit, input_element: _input_element, ...attributes } = props; let { value, focusState, inputState } = this.state; if (isTrue(disabled) || isTrue(skeleton)) { inputState = 'disabled'; } const sizeIsNumber = parseFloat(size) > 0; const id = this._id; const showStatus = getStatusState(status); const hasSubmitButton = submit_element || submit_element !== false && type === 'search'; const hasValue = Input.hasValue(value); const iconSize = size === 'large' && (icon_size === 'default' || !icon_size) ? 'medium' : icon_size; const mainParams = { className: classnames("dnb-input dnb-input__border--tokens dnb-form-component", createSpacingClasses(props), className, icon && `dnb-input--icon-position-${icon_position} dnb-input--has-icon` + (iconSize ? ` dnb-input--icon-size-${iconSize}` : ""), type && `dnb-input--${type}`, size && !sizeIsNumber && `dnb-input--${size}`, hasSubmitButton && 'dnb-input--has-submit-element', inner_element && 'dnb-input--has-inner-element', isTrue(clear) && 'dnb-input--has-clear-button', align && `dnb-input__align--${align}`, status && `dnb-input__status--${status_state}`, disabled && 'dnb-input--disabled', label_direction && `dnb-input--${label_direction}`, isTrue(stretch) && `dnb-input--stretch`, isTrue(keep_placeholder) && 'dnb-input--keep-placeholder'), 'data-input-state': inputState, 'data-has-content': hasValue ? 'true' : 'false' }; const innerParams = { className: 'dnb-input__inner' }; let { input_element: InputElement } = props; const inputAttributes = input_attributes ? typeof input_attributes === 'string' ? JSON.parse(input_attributes) : input_attributes : {}; const inputParams = { className: classnames('dnb-input__input', input_class), autoComplete: autocomplete, type, id, disabled: isTrue(disabled), name: id, 'aria-placeholder': placeholder ? convertJsxToString(placeholder) : undefined, ...attributes, ...inputAttributes, onChange: this.onChangeHandler, onKeyDown: this.onKeyDownHandler, onFocus: this.onFocusHandler, onBlur: this.onBlurHandler }; if (sizeIsNumber) { inputParams.size = size; } if (showStatus || suffix || hasSubmitButton) { inputParams['aria-describedby'] = combineDescribedBy(inputParams, hasSubmitButton && !submit_element ? id + '-submit-button' : null, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null); } if (readOnly) { inputParams['aria-readonly'] = inputParams.readOnly = true; } const shellParams = { className: classnames("dnb-input__shell dnb-input__border", createSkeletonClass('shape', skeleton, this.context)) }; skeletonDOMAttributes(inputParams, skeleton, this.context); validateDOMAttributes(this.props, inputParams); validateDOMAttributes(null, shellParams); if (InputElement && typeof InputElement === 'function') { InputElement = InputElement({ ...inputParams, value }, this._ref); } else if (!InputElement && _input_element) { InputElement = _input_element; } return React.createElement("span", mainParams, label && React.createElement(FormLabel, { id: id + '-label', forId: id, text: label, labelDirection: label_direction, srOnly: label_sr_only, disabled: disabled, skeleton: skeleton }), React.createElement("span", innerParams, _AlignmentHelper || (_AlignmentHelper = React.createElement(AlignmentHelper, null)), React.createElement(FormStatus, _extends({ show: showStatus, id: id + '-form-status', globalStatus: globalStatus, label: label, text: status, state: status_state, text_id: id + '-status', no_animation: status_no_animation, skeleton: skeleton }, status_props)), React.createElement("span", { className: "dnb-input__row" }, React.createElement("span", shellParams, InputElement || React.createElement("input", _extends({ ref: this._ref }, inputParams)), inner_element && React.createElement("span", { className: "dnb-input__inner__element dnb-p" }, inner_element), icon && React.createElement(InputIcon, { className: "dnb-input__icon", icon: icon, size: iconSize }), !hasValue && placeholder && focusState !== 'focus' && React.createElement("span", { id: id + '-placeholder', className: 'dnb-input__placeholder' + (align ? ` dnb-input__align--${align}` : ""), role: "presentation", "aria-hidden": true }, placeholder), isTrue(clear) && icon_position !== 'right' && React.createElement("span", { className: "dnb-input--clear dnb-input__submit-element" }, React.createElement(InputSubmitButton, { "aria-hidden": !hasValue, attributes: { className: 'dnb-input__clear-button' }, id: id + '-clear-button', type: "button", variant: "tertiary", "aria-controls": id, "aria-label": clear_button_title, tooltip: hasValue && clear_button_title, icon: "close", icon_size: size === 'small' ? 'small' : undefined, skeleton: skeleton, disabled: isTrue(disabled) || !hasValue, onClick: this.clearValue }))), hasSubmitButton && React.createElement("span", { className: "dnb-input__submit-element" }, submit_element ? submit_element : React.createElement(InputSubmitButton, _extends({}, attributes, { id: id + '-submit-button', value: hasValue ? value : '', icon: submit_button_icon, status: convertStatusToStateOnly(submit_button_status || status, status_state), status_state: status_state, icon_size: size === 'medium' || size === 'large' ? 'medium' : 'default', title: submit_button_title, variant: submit_button_variant, disabled: isTrue(disabled), skeleton: isTrue(skeleton), size: size, on_submit: on_submit }, status_props))), suffix && React.createElement(Suffix, { className: "dnb-input__suffix", id: id + '-suffix', context: props }, suffix)))); } } process.env.NODE_ENV !== "production" ? Input.propTypes = { ...inputPropTypes } : void 0; class InputSubmitButton extends React.PureComponent { static defaultProps = { id: null, value: null, title: null, disabled: false, skeleton: false, variant: 'secondary', icon: 'loupe', icon_size: null, status: null, status_state: 'error', status_props: null, className: null, on_submit: null, on_submit_focus: null, on_submit_blur: null }; state = { focusState: 'virgin' }; onSubmitFocusHandler = event => { const value = this.props.value; this.setState({ focusState: 'focus' }); dispatchCustomElementEvent(this, 'on_submit_focus', { value, event }); }; onSubmitBlurHandler = event => { const value = this.props.value; this.setState({ focusState: 'dirty' }); dispatchCustomElementEvent(this, 'on_submit_blur', { value, event }); }; onSubmitHandler = event => { const value = this.props.value; dispatchCustomElementEvent(this, 'on_submit', { value, event }); }; render() { const { id, title, disabled, skeleton, variant, icon, icon_size, status, status_state, status_props, className, ...rest } = this.props; const params = { id, type: 'submit', 'aria-label': title, disabled, ...rest }; skeletonDOMAttributes(params, skeleton, this.context); validateDOMAttributes(this.props, params); return React.createElement("span", { className: "dnb-input__submit-button", "data-input-state": this.state.focusState }, React.createElement(Button, _extends({ className: classnames("dnb-input__submit-button__button dnb-button--input-button", className), variant: variant, icon: icon, icon_size: icon_size, status: status, status_state: status_state, onClick: this.onSubmitHandler, onFocus: this.onSubmitFocusHandler, onBlur: this.onSubmitBlurHandler }, params, status_props))); } } process.env.NODE_ENV !== "production" ? InputSubmitButton.propTypes = { id: PropTypes.string, value: PropTypes.string, title: PropTypes.string, variant: buttonVariantPropType.variant, disabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), icon_size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), status: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func, PropTypes.node]), status_state: PropTypes.string, status_props: PropTypes.object, className: PropTypes.string, on_submit: PropTypes.func, on_submit_focus: PropTypes.func, on_submit_blur: PropTypes.func } : void 0; const SubmitButton = React.forwardRef((props, ref) => React.createElement(InputSubmitButton, _extends({ innerRef: ref }, props))); export { SubmitButton }; const InputIcon = React.memo(props => React.createElement(IconPrimary, props), ({ icon: prev }, { icon: next }) => { if (typeof prev === 'string' && typeof next === 'string') { return prev === next; } const isProgressIndicator = icon => { return React.isValidElement(icon) && (icon.type?.displayName === 'ProgressIndicator' || icon.type?.name === 'ProgressIndicator'); }; if (isProgressIndicator(prev) && isProgressIndicator(next)) { return typeof prev === typeof next; } return false; }); InputIcon.propTypes = { icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]).isRequired }; Input._formElement = true; Input._supportsSpacingProps = true; //# sourceMappingURL=Input.js.map