@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
590 lines (589 loc) • 20.1 kB
JavaScript
"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