@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
347 lines (329 loc) • 10 kB
JSX
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
/* eslint-disable jsx-a11y/aria-activedescendant-has-tabindex */
// ### React
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Spinner from '../../../components/spinner';
import getAriaProps from '../../../utilities/get-aria-props';
const COUNTER = 'counter';
const propTypes = {
'aria-activedescendant': PropTypes.string,
'aria-autocomplete': PropTypes.string,
/**
* An HTML ID that is shared with ARIA-supported devices with the
* `aria-controls` attribute in order to relate the input with
* another region of the page. An example would be a select box
* that shows or hides a panel.
*/
'aria-controls': PropTypes.string,
'aria-describedby': PropTypes.string,
'aria-expanded': PropTypes.bool,
'aria-haspopup': PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
'aria-labelledby': PropTypes.string,
/**
* An HTML ID that is shared with ARIA-supported devices with the
* `aria-controls` attribute in order to relate the input with
* another region of the page. An example would be a search field
* that shows search results.
*/
'aria-owns': PropTypes.string,
'aria-required': PropTypes.bool,
/**
* **Assistive text for accessibility.**
* This object is merged with the default props object on every render.
* * `spinner`: Assistive text on the spinner.
*/
assistiveText: PropTypes.shape({
spinner: PropTypes.string,
}),
/**
* Disabled brower's autocomplete when "off" is used.
*/
autoComplete: PropTypes.string,
/**
* Class names to be added to the `input` element.
*/
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string,
]),
/**
* Class names to be added to the outer container `div` of the input.
*/
containerClassName: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string,
]),
/**
* Props to be added to the outer container `div` of the input (excluding `containerClassName`).
*/
containerProps: PropTypes.object,
/**
* Disables the input and prevents editing the contents.
*/
disabled: PropTypes.bool,
/**
* Displays text or node to the left of the input. This follows the fixed text input UX pattern.
*/
fixedTextLeft: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
/**
* Displays text or node to the right of the input. This follows the fixed text input UX pattern.
*/
fixedTextRight: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
/**
* If true, loading spinner appears inside input on right hand side.
*/
hasSpinner: PropTypes.bool,
/**
* Left aligned icon, must be instance of `design-system-react/components/icon/input-icon`
*/
iconLeft: PropTypes.node,
/**
* Right aligned icon, must be instance of `design-system-react/components/icon/input-icon`
*/
iconRight: PropTypes.node,
/**
* Every input must have a unique ID in order to support keyboard navigation and ARIA support.
*/
id: PropTypes.string.isRequired,
/**
* This callback exposes the input reference / DOM node to parent components. `<Parent inputRef={(inputComponent) => this.input = inputComponent} />
*/
inputRef: PropTypes.func,
/**
* Displays the value of the input statically. This follows the static input UX pattern.
*/
isStatic: PropTypes.bool,
/**
* This label appears above the input.
*/
label: PropTypes.string,
onBlur: PropTypes.func,
/**
* This callback fires when the input changes. The synthetic React event will be the first parameter to the callback. You will probably want to reference `event.target.value` in your callback. No custom data object is provided.
*/
onChange: PropTypes.func,
/**
* This event fires when the input is clicked.
*/
onClick: PropTypes.func,
onFocus: PropTypes.func,
onInput: PropTypes.func,
onInvalid: PropTypes.func,
onKeyDown: PropTypes.func,
onKeyPress: PropTypes.func,
onKeyUp: PropTypes.func,
onSelect: PropTypes.func,
onSubmit: PropTypes.func,
/**
* Text that will appear in an empty input.
*/
placeholder: PropTypes.string,
minLength: PropTypes.string,
/**
* Specifies minimum accepted value for an input of type "number"
*/
minValue: PropTypes.number,
maxLength: PropTypes.string,
/**
* Specifies maximum accepted value for an input of type "number"
*/
maxValue: PropTypes.number,
/**
* Name of the submitted form parameter.
*/
name: PropTypes.string,
/**
* Specifies `readOnly` for `input` node.
*/
readOnly: PropTypes.bool,
/**
* Highlights the input as a required field (does not perform any validation).
*/
required: PropTypes.bool,
/**
* `role` to be added to `input` node
*/
role: PropTypes.string,
/**
* Determines the step size upon increment or decrement. Can be set to decimal values.
*/
step: PropTypes.number,
/**
* Style object to be added to `input` node
*/
style: PropTypes.object,
/**
* Specifies `tabIndex` for `input` node
*/
tabIndex: PropTypes.string,
/**
* The `<Input>` element includes support for all HTML5 types.
*/
type: PropTypes.oneOf([
'text',
'password',
'datetime',
'datetime-local',
'date',
'month',
'time',
'week',
'number',
'email',
'url',
'search',
'tel',
'color',
]),
/**
* The input is a controlled component, and will always display this value.
*/
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* Which UX pattern of input? The default is `base` while other option is `counter`
*/
variant: PropTypes.oneOf(['base', COUNTER]),
/**
* This is the initial value of an uncontrolled form element and is present only to provide
* compatibility with hybrid framework applications that are not entirely React. It should only
* be used in an application without centralized state (Redux, Flux). "Controlled components"
* with centralized state is highly recommended.
* See [Code Overview](https://github.com/salesforce/design-system-react/blob/master/docs/codebase-overview.md#controlled-and-uncontrolled-components) for more information.
*/
defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
const defaultProps = {
assistiveText: {
spinner: 'Loading ...',
},
type: 'text',
};
/*
* This component was created to allow the DIV wrapped input to be used within other components such as combobox. This components API is not public.
*/
const InnerInput = ({
assistiveText = defaultProps.assistiveText,
type = defaultProps.type,
...props
}) => {
const ariaProps = getAriaProps({
assistiveText,
type,
...props,
});
ariaProps['aria-describedby'] = props.hasSpinner
? `loading-status-icon ${props['aria-describedby']}`
: props['aria-describedby'];
const {
className: containerClassName,
...containerProps
} = props.containerProps;
const mergedAssistiveText = {
...defaultProps.assistiveText,
...assistiveText,
};
return (
<div
className={classNames(containerClassName, {
'slds-input-has-icon':
props.variant !== COUNTER && (props.iconLeft || props.iconRight),
'slds-input-has-icon_left': props.iconLeft && !props.iconRight,
'slds-input-has-icon_right': !props.iconLeft && props.iconRight,
'slds-input-has-icon_left-right':
props.variant !== COUNTER && props.iconLeft && props.iconRight,
'slds-input-has-fixed-addon':
props.fixedTextLeft || props.fixedTextRight,
'slds-has-divider_bottom': props.isStatic,
})}
{...containerProps}
>
{props.iconLeft && props.iconLeft}
{props.fixedTextLeft && (
<span className="slds-form-element__addon">{props.fixedTextLeft}</span>
)}
{!props.isStatic && (
<input
autoComplete={props.autoComplete}
className={classNames(
'slds-input',
{
'slds-text-align_left':
props.variant === COUNTER && props.readOnly,
},
props.className
)}
disabled={props.disabled}
id={props.id}
min={props.minValue}
minLength={props.minLength}
max={props.maxValue}
maxLength={props.maxLength}
name={props.name}
onBlur={props.onBlur}
onChange={props.onChange}
onClick={props.onClick}
onFocus={props.onFocus}
onInput={props.onInput}
onInvalid={props.onInvalid}
onKeyDown={props.onKeyDown}
onKeyPress={props.onKeyPress}
onKeyUp={props.onKeyUp}
onSelect={props.onSelect}
onSubmit={props.onSubmit}
placeholder={props.placeholder}
readOnly={props.readOnly}
ref={props.inputRef}
required={props.required}
role={props.role}
step={props.step}
style={props.style}
tabIndex={props.tabIndex}
type={type}
{...ariaProps}
/* A form element should not have both value and defaultValue props. */
{...(props.value !== undefined
? { value: props.value }
: { defaultValue: props.defaultValue })}
/>
)}
{props.hasSpinner ? (
<div className="slds-input__icon-group slds-input__icon-group_right">
<Spinner
assistiveText={{ label: mergedAssistiveText.spinner }}
id="loading-status-icon"
isInput
size="x-small"
variant="brand"
/>
{props.iconRight && props.iconRight}
</div>
) : (
props.iconRight && props.iconRight
)}
{props.fixedTextRight && (
<span className="slds-form-element__addon">{props.fixedTextRight}</span>
)}
{/* eslint-disable jsx-a11y/no-static-element-interactions */}
{props.isStatic && (
<span
className={classNames('slds-form-element__static', 'slds-grid', {
'slds-grid_align-spread': props.variant !== COUNTER,
})}
onClick={props.onClick}
>
{props.value}
{props.inlineEditTrigger}
</span>
)}
{/* eslint-enable jsx-a11y/no-static-element-interactions */}
</div>
);
};
InnerInput.displayName = 'SLDSInnerInput';
InnerInput.propTypes = propTypes;
export default InnerInput;