@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
304 lines (286 loc) • 9.08 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 */
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;