UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

343 lines (321 loc) 10.6 kB
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ // # Input Component // Implements the [Input design pattern](https://lightningdesignsystem.com/components/forms/#flavor-input) in React. Does not yet implement [fixed text](https://lightningdesignsystem.com/components/forms/#flavor-input-input-fixed-text). // Based on SLDS v2.2.1 // // ### React import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { shape } from 'airbnb-prop-types'; // ### classNames // [github.com/JedWatson/classnames](https://github.com/JedWatson/classnames) // This project uses `classnames`, "a simple javascript utility for conditionally // joining classNames together." import classNames from 'classnames'; // ### shortid // [npmjs.com/package/shortid](https://www.npmjs.com/package/shortid) // shortid is a short, non-sequential, url-friendly, unique id generator import shortid from 'shortid'; // ## Children import InputIcon from '../../icon/input-icon'; import InnerInput from './private/inner-input'; import Label from '../private/label'; // This component's `checkProps` which issues warnings to developers about properties when in development mode (similar to React's built in development tools) import checkProps from './check-props'; import { FORMS_INPUT } from '../../../utilities/constants'; // ## InputDefinition const Input = createReactClass({ // ### Display Name // Always use the canonical component name as the React display name. displayName: FORMS_INPUT, // ### Prop Types 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.bool, '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** * * `label`: Visually hidden label but read out loud by screen readers. * * `spinner`: Text for loading spinner icon. */ assistiveText: shape({ label: PropTypes.string, spinner: PropTypes.string }), children: PropTypes.node, /** * Class names to be added to the outer container of the input. */ className: PropTypes.oneOfType([ PropTypes.array, PropTypes.object, PropTypes.string ]), /** * Disables the input and prevents editing the contents. */ disabled: PropTypes.bool, /** * Message to display when the input is in an error state. When this is present, also visually highlights the component as in error. */ errorText: PropTypes.string, /** * 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 instace of `design-system-react/components/icon/input-icon` */ iconLeft: PropTypes.node, /** * Right aligned icon, must be instace 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, /** * 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, maxLength: PropTypes.string, /** * Name of the submitted form parameter. */ name: PropTypes.string, /** * Displays the value of the input as readOnly. */ readOnly: PropTypes.bool, /** * Highlights the input as a required field (does not perform any validation). */ required: PropTypes.bool, /** * 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.string, iconPosition: PropTypes.string, inlineEditTrigger: PropTypes.node, role: PropTypes.string }, getDefaultProps () { return { type: 'text' }; }, componentWillMount () { // `checkProps` issues warnings to developers about properties (similar to React's built in development tools) checkProps(FORMS_INPUT, this.props); this.generatedId = shortid.generate(); if (this.props.errorText) { this.generatedErrorId = shortid.generate(); } }, getId () { return this.props.id || this.generatedId; }, getErrorId () { return this.props['aria-describedby'] || this.generatedErrorId; }, // This is convuluted to maintain backwards compatibility. Please remove deprecatedProps on next breaking change. getIconRender (position, iconPositionProp) { let icon; /* eslint-disable react/prop-types */ const deprecatedProps = { assistiveText: (this.props[iconPositionProp] && this.props[iconPositionProp].props.assistiveText) || this.props.iconAssistiveText, category: (this.props[iconPositionProp] && this.props[iconPositionProp].props.category) || this.props.iconCategory, name: (this.props[iconPositionProp] && this.props[iconPositionProp].props.name) || this.props.iconName, onClick: (this.props[iconPositionProp] && this.props[iconPositionProp].props.onClick) || this.props.onIconClick }; /* eslint-enable react/prop-types */ if ( this.props[iconPositionProp] && position && this.props[iconPositionProp] ) { icon = React.cloneElement(this.props[iconPositionProp], { iconPosition: `${position}` }); } else if (deprecatedProps.name) { icon = <InputIcon iconPosition={position} {...deprecatedProps} />; } return icon; }, // ### Render render () { // this is a hack to make left the default prop unless overwritten by `iconPosition="right"` const hasLeftIcon = !!this.props.iconLeft || ((this.props.iconPosition === 'left' || this.props.iconPosition === undefined) && !!this.props.iconName); const hasRightIcon = !!this.props.iconRight || (this.props.iconPosition === 'right' && !!this.props.iconName); return ( <div className={classNames( 'slds-form-element', { 'slds-has-error': this.props.errorText }, this.props.className )} > <Label assistiveText={this.props.assistiveText} htmlFor={this.props.isStatic ? undefined : this.getId()} label={this.props.label} required={this.props.required} variant={this.props.isStatic ? 'static' : 'base'} /> <InnerInput aria-activedescendant={this.props['aria-activedescendant']} aria-autocomplete={this.props['aria-autocomplete']} aria-controls={this.props['aria-controls']} aria-labelledby={this.props['aria-labelledby']} aria-describedby={this.getErrorId()} aria-expanded={this.props['aria-expanded']} aria-owns={this.props['aria-owns']} aria-required={this.props['aria-required']} containerProps={{ className: 'slds-form-element__control' }} disabled={this.props.disabled} fixedTextLeft={this.props.fixedTextLeft} fixedTextRight={this.props.fixedTextRight} hasSpinner={this.props.hasSpinner} id={this.getId()} iconLeft={hasLeftIcon ? this.getIconRender('left', 'iconLeft') : null} iconRight={ hasRightIcon ? this.getIconRender('right', 'iconRight') : null } inlineEditTrigger={this.props.inlineEditTrigger} isStatic={this.props.isStatic} minLength={this.props.minLength} maxLength={this.props.maxLength} name={this.props.name} onBlur={this.props.onBlur} onChange={this.props.onChange} onClick={this.props.onClick} onFocus={this.props.onFocus} onInput={this.props.onInput} onInvalid={this.props.onInvalid} onKeyDown={this.props.onKeyDown} onKeyPress={this.props.onKeyPress} onKeyUp={this.props.onKeyUp} onSelect={this.props.onSelect} onSubmit={this.props.onSubmit} placeholder={this.props.placeholder} inputRef={this.props.inputRef} readOnly={this.props.readOnly} required={this.props.required} role={this.props.role} spinnerAssistiveText={ this.props.assistiveText && this.props.assistiveText.spinner } type={this.props.type} value={this.props.value} /> {this.props.errorText && ( <div id={this.getErrorId()} className="slds-form-element__help"> {this.props.errorText} </div> )} {this.props.children} </div> ); } }); export default Input;