@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
277 lines (257 loc) • 7.93 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/no-autofocus */
// # Textarea Component
// Implements the [Textarea design pattern](https://lightningdesignsystem.com/components/textarea).
// Based on SLDS v2.4.0
//
// ### React
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from '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
// 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_TEXTAREA } from '../../../utilities/constants';
// ## TextareaDefinition
const Textarea = createReactClass({
// ### Display Name
// Always use the canonical component name as the React display name.
displayName: FORMS_TEXTAREA,
// ### Prop Types
propTypes: {
'aria-activedescendant': PropTypes.string,
/**
* An HTML ID that is shared with ARIA-supported devices with the
* `aria-controls` attribute in order to relate the textarea 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 textarea 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,
/**
* Specifies is the textarea should automatically get focus when the page loads.
*/
autoFocus: PropTypes.bool,
/**
* If present, the label associated with this `textarea` is overwritten
* by this text and is visually not shown.
*/
assistiveText: PropTypes.string,
children: PropTypes.node,
/**
* Class names to be added to the textarea component.
*/
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string
]),
/** Allows for ability to apply classNames to outer textarea div.
*/
classNameContainer: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string
]),
/**
* Message to display when the textarea is in an error state. When this is present, also visually highlights the component as in error.
*/
errorText: PropTypes.string,
/**
* Every textarea must have a unique ID in order to support keyboard navigation and ARIA support.
*/
id: PropTypes.string,
/**
* This callback exposes the textarea reference / DOM node to parent components. `<Parent textareaRef={(textareaComponent) => this.textarea = textareaComponent} />
*/
textareaRef: PropTypes.func,
/**
* This label appears above the textarea.
*/
label: PropTypes.string,
onBlur: PropTypes.func,
/**
* This callback fires when the textarea 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 textarea 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,
/**
* Disables the textarea and prevents editing the contents.
*/
disabled: PropTypes.bool,
/**
* Maximum number of characters allowed.
*/
maxLength: PropTypes.string,
/**
* Name of the submitted form parameter.
*/
name: PropTypes.string,
/**
* Text that will appear in an empty textarea.
*/
placeholder: PropTypes.string,
/**
* Highlights the textarea as a required field (does not perform any validation).
*/
required: PropTypes.bool,
/**
* The textarea is a controlled component, and will always display this value.
*/
value: PropTypes.string,
/**
* Specifies how the text in a text area is to be wrapped when submitted in a form.
*/
wrap: PropTypes.oneOf(['soft', 'hard'])
},
componentWillMount () {
// `checkProps` issues warnings to developers about properties (similar to React's built in development tools)
checkProps(FORMS_TEXTAREA, 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;
},
// ### Render
render () {
const {
autoFocus,
assistiveText,
children,
className,
classNameContainer,
disabled,
errorText,
textareaRef, // eslint-disable-line react/prop-types
label,
onBlur,
onChange,
onClick,
onFocus,
onInput,
onInvalid,
onKeyDown,
onKeyPress,
onKeyUp,
onSelect,
onSubmit,
maxLength,
name,
placeholder,
required,
role,
value,
wrap
// ### Additional properties
// Using [object destructuring](https://facebook.github.io/react/docs/transferring-props.html#transferring-with-...-in-jsx) to pass on any properties which are not explicitly defined.
// ...props // Uncomment this if you actually need to send the rest of the props to other elements
} = this.props;
const labelText = label || assistiveText; // One of these is required to pass accessibility tests
return (
<div
className={classNames(
'slds-form-element',
{
'slds-has-error': errorText
},
classNameContainer
)}
>
{labelText && (
<label
className={classNames('slds-form-element__label', {
'slds-assistive-text': assistiveText && !label
})}
htmlFor={this.getId()}
>
{required && (
<abbr className="slds-required" title="required">
*
</abbr>
)}
{labelText}
</label>
)}
<div className={classNames('slds-form-element__control')}>
<textarea
aria-activedescendant={this.props['aria-activedescendant']}
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']}
className={classNames('slds-textarea', className)}
autoFocus={autoFocus}
disabled={disabled}
id={this.getId()}
maxLength={maxLength}
name={name}
onBlur={onBlur}
onChange={onChange}
onClick={onClick}
onFocus={onFocus}
onInput={onInput}
onInvalid={onInvalid}
onKeyDown={onKeyDown}
onKeyPress={onKeyPress}
onKeyUp={onKeyUp}
onSelect={onSelect}
onSubmit={onSubmit}
placeholder={placeholder}
ref={textareaRef}
role={role}
required={required}
wrap={wrap}
value={value}
/>
</div>
{errorText && (
<div id={this.getErrorId()} className="slds-form-element__help">
{errorText}
</div>
)}
{children}
</div>
);
}
});
export default Textarea;