| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- /* eslint-disable prefer-destructuring */
- import React from 'react';
- import PropTypes from 'prop-types';
- import cls from 'classnames';
- import { noop, isUndefined, isBoolean } from 'lodash';
- import RadioFoundation, { RadioAdapter } from '@douyinfe/semi-foundation/radio/radioFoundation';
- import { RadioChangeEvent } from '@douyinfe/semi-foundation/radio/radioInnerFoundation';
- import { strings, radioClasses as css } from '@douyinfe/semi-foundation/radio/constants';
- import { getUuidShort } from '@douyinfe/semi-foundation/utils/uuid';
- import '@douyinfe/semi-foundation/radio/radio.scss';
- import BaseComponent from '../_base/baseComponent';
- import RadioInner from './radioInner';
- import Context, { RadioContextValue, RadioMode } from './context';
- export type RadioDisplayMode = 'vertical' | '';
- export type RadioType =
- typeof strings.TYPE_DEFAULT |
- typeof strings.TYPE_BUTTON |
- typeof strings.TYPE_CARD |
- typeof strings.TYPE_PURECARD;
- export type RadioProps = {
- autoFocus?: boolean;
- checked?: boolean;
- children?: React.ReactNode | undefined;
- defaultChecked?: boolean;
- value?: string | number;
- disabled?: boolean;
- prefixCls?: string;
- displayMode?: RadioDisplayMode;
- onChange?: (e: RadioChangeEvent) => void;
- onMouseEnter?: (e: React.MouseEvent<HTMLLabelElement>) => void;
- onMouseLeave?: (e: React.MouseEvent<HTMLLabelElement>) => void;
- mode?: RadioMode;
- extra?: React.ReactNode;
- style?: React.CSSProperties;
- className?: string;
- addonStyle?: React.CSSProperties;
- addonClassName?: string;
- type?: RadioType;
- 'aria-label'?: React.AriaAttributes['aria-label'];
- addonId?: string;
- extraId?: string;
- name?: string;
- preventScroll?: boolean
- };
- export interface RadioState {
- hover?: boolean;
- addonId?: string;
- extraId?: string;
- focusVisible?: boolean;
- checked?: boolean
- }
- export type { RadioChangeEvent };
- class Radio extends BaseComponent<RadioProps, RadioState> {
- static contextType = Context;
- static propTypes = {
- autoFocus: PropTypes.bool,
- checked: PropTypes.bool,
- defaultChecked: PropTypes.bool,
- value: PropTypes.any, // Compare according to value to determine whether to select
- style: PropTypes.object,
- className: PropTypes.string,
- disabled: PropTypes.bool,
- prefixCls: PropTypes.string,
- displayMode: PropTypes.oneOf<RadioDisplayMode>(['vertical', '']),
- onChange: PropTypes.func,
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func,
- mode: PropTypes.oneOf(strings.MODE),
- extra: PropTypes.node, // extra info
- addonStyle: PropTypes.object,
- addonClassName: PropTypes.string,
- type: PropTypes.oneOf([strings.TYPE_DEFAULT, strings.TYPE_BUTTON, strings.TYPE_CARD, strings.TYPE_PURECARD]), // Button style type
- 'aria-label': PropTypes.string,
- preventScroll: PropTypes.bool,
- };
- static defaultProps: Partial<RadioProps> = {
- autoFocus: false,
- defaultChecked: false,
- value: undefined as undefined,
- style: undefined as undefined,
- onMouseEnter: noop,
- onMouseLeave: noop,
- mode: '',
- type: 'default'
- };
- static elementType: string;
- radioEntity: RadioInner;
- context!: RadioContextValue;
- foundation: RadioFoundation;
- addonId: string;
- extraId: string;
- constructor(props: RadioProps) {
- super(props);
- this.state = {
- hover: false,
- addonId: props.addonId,
- extraId: props.extraId,
- checked: props.checked || props.defaultChecked || false,
- };
- this.foundation = new RadioFoundation(this.adapter);
- this.radioEntity = null;
- }
- componentDidUpdate(prevProps: RadioProps) {
- if (this.props.checked !== prevProps.checked) {
- if (isUndefined(this.props.checked)) {
- this.foundation.setChecked(false);
- } else if (isBoolean(this.props.checked)) {
- this.foundation.setChecked(this.props.checked);
- }
- }
- }
- get adapter(): RadioAdapter {
- return {
- ...super.adapter,
- setHover: (hover: boolean) => {
- this.setState({ hover });
- },
- setAddonId: () => {
- this.setState({ addonId: getUuidShort({ prefix: 'addon' }) });
- },
- setChecked: (checked: boolean) => {
- this.setState({ checked });
- },
- setExtraId: () => {
- this.setState({ extraId: getUuidShort({ prefix: 'extra' }) });
- },
- setFocusVisible: (focusVisible: boolean): void => {
- this.setState({ focusVisible });
- },
- };
- }
- isInGroup() {
- // eslint-disable-next-line react/destructuring-assignment
- return this.context && this.context.radioGroup;
- }
- focus() {
- this.radioEntity.focus();
- }
- blur() {
- this.radioEntity.blur();
- }
- onChange = (e: RadioChangeEvent) => {
- const { onChange } = this.props;
- if (this.isInGroup()) {
- const { radioGroup } = this.context;
- radioGroup.onChange && radioGroup.onChange(e);
- }
- !('checked' in this.props) && this.foundation.setChecked(e.target.checked);
- onChange && onChange(e);
- };
- handleMouseEnter = (e: React.MouseEvent<HTMLLabelElement>) => {
- this.props.onMouseEnter(e);
- this.foundation.setHover(true);
- };
- handleMouseLeave = (e: React.MouseEvent<HTMLLabelElement>) => {
- this.props.onMouseLeave(e);
- this.foundation.setHover(false);
- };
- handleFocusVisible = (event: React.FocusEvent) => {
- this.foundation.handleFocusVisible(event);
- }
- handleBlur = (event: React.FocusEvent) => {
- this.foundation.handleBlur();
- }
- render() {
- const {
- addonClassName,
- addonStyle,
- disabled,
- style,
- className,
- prefixCls,
- displayMode,
- children,
- extra,
- mode,
- type,
- value: propValue,
- name
- } = this.props;
- let realChecked,
- isDisabled,
- realMode,
- isButtonRadioGroup,
- isCardRadioGroup,
- isPureCardRadioGroup,
- isButtonRadioComponent,
- buttonSize,
- realPrefixCls;
- const { hover: isHover, addonId, extraId, focusVisible, checked, } = this.state;
- const props: Record<string, any> = {
- checked,
- disabled,
- };
- if (this.isInGroup()) {
- realChecked = this.context.radioGroup.value === propValue;
- isDisabled = disabled || this.context.radioGroup.disabled;
- realMode = this.context.mode;
- isButtonRadioGroup = this.context.radioGroup.isButtonRadio;
- isCardRadioGroup = this.context.radioGroup.isCardRadio;
- isPureCardRadioGroup = this.context.radioGroup.isPureCardRadio;
- buttonSize = this.context.radioGroup.buttonSize;
- realPrefixCls = prefixCls || this.context.radioGroup.prefixCls;
- props.checked = realChecked;
- props.disabled = isDisabled;
- } else {
- realChecked = checked;
- isDisabled = disabled;
- realMode = mode;
- isButtonRadioComponent = type === 'button';
- realPrefixCls = prefixCls;
- isButtonRadioGroup = type === strings.TYPE_BUTTON;
- isPureCardRadioGroup = type === strings.TYPE_PURECARD;
- isCardRadioGroup = type === strings.TYPE_CARD || isPureCardRadioGroup;
- }
- const isButtonRadio = typeof isButtonRadioGroup === 'undefined' ? isButtonRadioComponent : isButtonRadioGroup;
- const prefix = realPrefixCls || css.PREFIX;
- const focusOuter = isCardRadioGroup || isPureCardRadioGroup || isButtonRadio;
- const wrapper = cls(prefix, {
- [`${prefix}-disabled`]: isDisabled,
- [`${prefix}-checked`]: realChecked,
- [`${prefix}-${displayMode}`]: Boolean(displayMode),
- [`${prefix}-buttonRadioComponent`]: isButtonRadioComponent,
- [`${prefix}-buttonRadioGroup`]: isButtonRadioGroup,
- [`${prefix}-buttonRadioGroup-${buttonSize}`]: isButtonRadioGroup && buttonSize,
- [`${prefix}-cardRadioGroup`]: isCardRadioGroup,
- [`${prefix}-cardRadioGroup_disabled`]: isDisabled && isCardRadioGroup,
- [`${prefix}-cardRadioGroup_checked`]: isCardRadioGroup && realChecked && !isDisabled,
- [`${prefix}-cardRadioGroup_checked_disabled`]: isCardRadioGroup && realChecked && isDisabled,
- [`${prefix}-cardRadioGroup_hover`]: isCardRadioGroup && !realChecked && isHover && !isDisabled,
- [className]: Boolean(className),
- [`${prefix}-focus`]: focusVisible && (isCardRadioGroup || isPureCardRadioGroup),
- });
- const groupName = this.isInGroup() && this.context.radioGroup.name;
- const addonCls = cls({
- [`${prefix}-addon`]: !isButtonRadio,
- [`${prefix}-addon-buttonRadio`]: isButtonRadio,
- [`${prefix}-addon-buttonRadio-checked`]: isButtonRadio && realChecked,
- [`${prefix}-addon-buttonRadio-disabled`]: isButtonRadio && isDisabled,
- [`${prefix}-addon-buttonRadio-hover`]: isButtonRadio && !realChecked && !isDisabled && isHover,
- [`${prefix}-addon-buttonRadio-${buttonSize}`]: isButtonRadio && buttonSize,
- [`${prefix}-focus`]: focusVisible && isButtonRadio,
- }, addonClassName);
- const renderContent = () => {
- if (!children && !extra) {
- return null;
- }
- return (
- <div className={cls([`${prefix}-content`, { [`${prefix}-isCardRadioGroup_content`]: isCardRadioGroup }])}>
- {children ? (
- <span className={addonCls} style={addonStyle} id={addonId} x-semi-prop="children">
- {children}
- </span>
- ) : null}
- {extra && !isButtonRadio ? (
- <div className={`${prefix}-extra`} id={extraId} x-semi-prop="extra">
- {extra}
- </div>
- ) : null}
- </div>
- );
- };
- return (
- <label
- style={style}
- className={wrapper}
- onMouseEnter={this.handleMouseEnter}
- onMouseLeave={this.handleMouseLeave}
- >
- <RadioInner
- {...this.props}
- {...props}
- mode={realMode}
- name={name ?? groupName}
- isButtonRadio={isButtonRadio}
- isPureCardRadioGroup={isPureCardRadioGroup}
- onChange={this.onChange}
- ref={(ref: RadioInner) => {
- this.radioEntity = ref;
- }}
- addonId={children && addonId}
- extraId={extra && extraId}
- focusInner={focusVisible && !focusOuter}
- onInputFocus={this.handleFocusVisible}
- onInputBlur={this.handleBlur}
- />
- {renderContent()}
- </label>
- );
- }
- }
- Radio.elementType = 'Radio';
- export default Radio;
|