UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

304 lines (286 loc) 9.08 kB
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ import React from 'react'; import PropTypes from 'prop-types'; import shortid from 'shortid'; import classNames from 'classnames'; import KEYS from '../../utilities/key-code'; import { RADIO } from '../../utilities/constants'; import getDataProps from '../../utilities/get-data-props'; import Swatch from '../../components/color-picker/private/swatch'; import Icon from '../icon'; // 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 componentDoc from './docs.json'; const propTypes = { /** * The ID of an element that describes this radio input. Often used for error messages. */ 'aria-describedby': PropTypes.string, /** * This is a controlled component. This radio is checked according to this value. */ checked: PropTypes.bool, /** * Class name to be passed to radio input wrapper ( `span` element) */ className: PropTypes.oneOfType([ PropTypes.array, PropTypes.object, PropTypes.string, ]) /** * 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. */, defaultChecked: PropTypes.bool, /** * Disable this radio input. */ disabled: PropTypes.bool, /** * A unique ID that is used to associating a label to the `input` element. This ID is added to the `input` element. */ id: PropTypes.string, /** * **Text labels for internationalization** * This object is merged with the default props object on every render. * * `heading`: Heading for the visual picker variant * * `label`: Label for the radio input */ labels: PropTypes.shape({ heading: PropTypes.string, label: PropTypes.string, }), /** * The name of the radio input group. */ name: PropTypes.string, /** * This event fires when the radio selection changes. Passes in `event, { checked }`. */ onChange: PropTypes.func, /** * The value of this radio input. */ value: PropTypes.string, /** * Variant of the Radio button. Base is the default and button-group makes the radio button look like a normal button (should be a child of <RadioButtonGroup>). */ variant: PropTypes.oneOf(['base', 'button-group', 'swatch', 'visual-picker']), /** * Determines whether visual picker is coverable when selected (only for visual picker variant) */ coverable: PropTypes.bool, /** * Determines whether the visual picker should be vertical or horizontal (only for visual picker variant) */ vertical: PropTypes.bool, /** * Allows icon to shown if radio is not selected (only for non-coverable visual picker variant) */ onRenderVisualPicker: PropTypes.func, /** * Allows icon to shown if radio is not selected (only for visual picker variant) */ onRenderVisualPickerSelected: PropTypes.func, /** * Allows icon to shown if radio is not selected (only for visual picker variant) */ onRenderVisualPickerNotSelected: PropTypes.func, /** * Shows description for radio option (only for visual picker variant) */ description: PropTypes.string, /** * Allows icon to shown if radio is not selected (only for visual picker variant) */ size: PropTypes.oneOf(['medium', 'large']), /** * Ref callback that will pass in the radio's `input` tag */ refs: PropTypes.shape({ input: PropTypes.func, }), }; const defaultProps = { variant: 'base', coverable: false, }; /** * A radio input that can have a single input checked at any one time. Radios should be wrapped with * a [RadioGroup](/components/radio-group) or [RadioButtonGroup](/components/radio-button-group) */ class Radio extends React.Component { constructor(props) { super(props); this.preventDuplicateChangeEvent = false; } componentWillMount() { checkProps(RADIO, this.props, componentDoc); this.generatedId = shortid.generate(); } getId() { return this.props.id || this.generatedId; } handleChange = (event, preventDuplicateChangeEvent) => { if (!this.preventDuplicateChangeEvent) { this.preventDuplicateChangeEvent = Boolean(preventDuplicateChangeEvent); if (this.props.onChange) { this.props.onChange(event, { checked: !this.props.checked, }); } } else { this.preventDuplicateChangeEvent = false; } }; render() { const dataProps = getDataProps(this.props); let radio; const labels = { ...defaultProps.labels, /* Remove backward compatibility at next breaking change */ ...(this.props.label ? { label: this.props.label } : {}), ...this.props.labels, }; if (this.props.variant === 'swatch') { radio = ( <label style={{ border: '1px' }} className="slds-radio_button__label" htmlFor={this.getId()} > <span> <Swatch label={labels.label} style={this.props.style} color={this.props.value} /> </span> </label> ); } else if (this.props.variant === 'button-group') { radio = ( <label className="slds-radio_button__label" htmlFor={this.getId()}> <span className="slds-radio_faux">{labels.label}</span> </label> ); } else if (this.props.variant === 'visual-picker') { radio = ( <label htmlFor={this.getId()}> {this.props.coverable ? ( <div className="slds-visual-picker__figure slds-visual-picker__icon slds-align_absolute-center"> <span className="slds-is-selected"> {this.props.onRenderVisualPickerSelected()} </span> <span className="slds-is-not-selected"> {this.props.onRenderVisualPickerNotSelected()} </span> </div> ) : ( <span className="slds-visual-picker__figure slds-visual-picker__text slds-align_absolute-center"> {this.props.onRenderVisualPicker()} </span> )} {!this.props.vertical ? ( <span className="slds-visual-picker__body"> {labels.heading ? ( <span className="slds-text-heading_small"> {labels.heading} </span> ) : null} <span className="slds-text-title">{labels.label}</span> </span> ) : null} {!this.props.coverable ? ( <span className="slds-icon_container slds-visual-picker__text-check"> <Icon assistiveText={this.props.assistiveText} category="utility" name="check" colorVariant="base" size="x-small" /> </span> ) : null} </label> ); } else { radio = ( <label className="slds-radio__label" htmlFor={this.getId()}> <span className="slds-radio_faux" /> <span className="slds-form-element__label">{labels.label}</span> </label> ); } return ( <span className={classNames( this.props.variant === 'visual-picker' ? `slds-visual-picker_${this.props.size}` : null, { 'slds-radio': this.props.variant === 'base' || this.props.variant === 'swatch', 'slds-button slds-radio_button': this.props.variant === 'button-group', 'slds-visual-picker': this.props.variant === 'visual-picker', 'slds-visual-picker_vertical': this.props.variant === 'visual-picker' && this.props.vertical, }, this.props.className )} > <input type="radio" id={this.getId()} name={this.props.name} value={this.props.value} /* A form element should not have both checked and defaultChecked props. */ {...(this.props.checked !== undefined ? { checked: this.props.checked } : { defaultChecked: this.props.defaultChecked })} onChange={(event) => { this.handleChange(event); }} onClick={(event) => { if (this.props.checked && this.props.deselectable) { this.handleChange(event); } }} onKeyPress={(event) => { const { charCode } = event; if ( charCode === KEYS.SPACE && this.props.checked && this.props.deselectable ) { this.handleChange(event, true); } else if ( (charCode === KEYS.ENTER && (this.props.checked && this.props.deselectable)) || !this.props.checked ) { this.handleChange(event); } }} aria-describedby={this.props['aria-describedby']} disabled={this.props.disabled} {...dataProps} ref={(input) => { if (this.props.refs && this.props.refs.input) { this.props.refs.input(input); } }} /> {radio} </span> ); } } Radio.displayName = RADIO; Radio.propTypes = propTypes; Radio.defaultProps = defaultProps; export default Radio;