radio.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* eslint-disable prefer-destructuring */
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import cls from 'classnames';
  5. import { noop } from 'lodash';
  6. import RadioFoundation, { RadioAdapter } from '@douyinfe/semi-foundation/radio/radioFoundation';
  7. import { RadioChangeEvent } from '@douyinfe/semi-foundation/radio/radioInnerFoundation';
  8. import { strings, radioClasses as css } from '@douyinfe/semi-foundation/radio/constants';
  9. import '@douyinfe/semi-foundation/radio/radio.scss';
  10. import BaseComponent from '../_base/baseComponent';
  11. import RadioInner from './radioInner';
  12. import Context, { RadioContextValue, RadioMode } from './context';
  13. export type RadioDisplayMode = 'vertical' | '';
  14. export type RadioType =
  15. typeof strings.TYPE_DEFAULT |
  16. typeof strings.TYPE_BUTTON |
  17. typeof strings.TYPE_CARD |
  18. typeof strings.TYPE_PURECARD;
  19. export type RadioProps = {
  20. autoFocus?: boolean;
  21. checked?: boolean;
  22. defaultChecked?: boolean;
  23. value?: string | number;
  24. disabled?: boolean;
  25. prefixCls?: string;
  26. displayMode?: RadioDisplayMode;
  27. onChange?: (e: RadioChangeEvent) => void;
  28. onMouseEnter?: (e: React.MouseEvent<HTMLLabelElement>) => void;
  29. onMouseLeave?: (e: React.MouseEvent<HTMLLabelElement>) => void;
  30. mode?: RadioMode;
  31. extra?: React.ReactNode;
  32. style?: React.CSSProperties;
  33. className?: string;
  34. addonStyle?: React.CSSProperties;
  35. addonClassName?: string;
  36. type?: RadioType;
  37. };
  38. export interface RadioState {
  39. hover?: boolean;
  40. }
  41. export { RadioChangeEvent };
  42. class Radio extends BaseComponent<RadioProps, RadioState> {
  43. static contextType = Context;
  44. static propTypes = {
  45. autoFocus: PropTypes.bool,
  46. checked: PropTypes.bool,
  47. defaultChecked: PropTypes.bool,
  48. value: PropTypes.any, // Compare according to value to determine whether to select
  49. style: PropTypes.object,
  50. className: PropTypes.string,
  51. disabled: PropTypes.bool,
  52. prefixCls: PropTypes.string,
  53. displayMode: PropTypes.oneOf<RadioDisplayMode>(['vertical', '']),
  54. onChange: PropTypes.func,
  55. onMouseEnter: PropTypes.func,
  56. onMouseLeave: PropTypes.func,
  57. mode: PropTypes.oneOf(strings.MODE),
  58. extra: PropTypes.node, // extra info
  59. addonStyle: PropTypes.object,
  60. addonClassName: PropTypes.string,
  61. type: PropTypes.oneOf([strings.TYPE_DEFAULT, strings.TYPE_BUTTON, strings.TYPE_CARD, strings.TYPE_PURECARD]), // Button style type
  62. };
  63. static defaultProps: Partial<RadioProps> = {
  64. autoFocus: false,
  65. defaultChecked: false,
  66. value: undefined as undefined,
  67. style: undefined as undefined,
  68. onMouseEnter: noop,
  69. onMouseLeave: noop,
  70. mode: '',
  71. type: 'default'
  72. };
  73. radioEntity: RadioInner;
  74. context!: RadioContextValue;
  75. constructor(props: RadioProps) {
  76. super(props);
  77. this.state = {
  78. hover: false,
  79. };
  80. this.foundation = new RadioFoundation(this.adapter);
  81. this.radioEntity = null;
  82. }
  83. get adapter(): RadioAdapter {
  84. return {
  85. ...super.adapter,
  86. setHover: (hover: boolean) => {
  87. this.setState({ hover });
  88. }
  89. };
  90. }
  91. isInGroup() {
  92. // eslint-disable-next-line react/destructuring-assignment
  93. return this.context && this.context.radioGroup;
  94. }
  95. focus() {
  96. this.radioEntity.focus();
  97. }
  98. blur() {
  99. this.radioEntity.blur();
  100. }
  101. onChange = (e: RadioChangeEvent) => {
  102. const { onChange } = this.props;
  103. if (this.isInGroup()) {
  104. const { radioGroup } = this.context;
  105. radioGroup.onChange && radioGroup.onChange(e);
  106. }
  107. onChange && onChange(e);
  108. };
  109. handleMouseEnter = (e: React.MouseEvent<HTMLLabelElement>) => {
  110. this.props.onMouseEnter(e);
  111. this.foundation.setHover(true);
  112. };
  113. handleMouseLeave = (e: React.MouseEvent<HTMLLabelElement>) => {
  114. this.props.onMouseLeave(e);
  115. this.foundation.setHover(false);
  116. };
  117. render() {
  118. const {
  119. addonClassName,
  120. addonStyle,
  121. checked,
  122. disabled,
  123. style,
  124. className,
  125. prefixCls,
  126. displayMode,
  127. children,
  128. extra,
  129. mode,
  130. type,
  131. value: propValue
  132. } = this.props;
  133. let realChecked,
  134. isDisabled,
  135. realMode,
  136. isButtonRadioGroup,
  137. isCardRadioGroup,
  138. isPureCardRadioGroup,
  139. isButtonRadioComponent,
  140. buttonSize,
  141. realPrefixCls;
  142. const isHover = this.state.hover;
  143. let props = {};
  144. if (this.isInGroup()) {
  145. realChecked = this.context.radioGroup.value === propValue;
  146. isDisabled = disabled || this.context.radioGroup.disabled;
  147. realMode = this.context.mode;
  148. isButtonRadioGroup = this.context.radioGroup.isButtonRadio;
  149. isCardRadioGroup = this.context.radioGroup.isCardRadio;
  150. isPureCardRadioGroup = this.context.radioGroup.isPureCardRadio;
  151. buttonSize = this.context.radioGroup.buttonSize;
  152. realPrefixCls = prefixCls || this.context.radioGroup.prefixCls;
  153. props = { checked: realChecked, disabled: isDisabled };
  154. } else {
  155. realChecked = checked;
  156. isDisabled = disabled;
  157. realMode = mode;
  158. isButtonRadioComponent = type === 'button';
  159. realPrefixCls = prefixCls;
  160. }
  161. const isButtonRadio = typeof isButtonRadioGroup === 'undefined' ? isButtonRadioComponent : isButtonRadioGroup;
  162. const prefix = realPrefixCls || css.PREFIX;
  163. const wrapper = cls(prefix, {
  164. [`${prefix}-disabled`]: isDisabled,
  165. [`${prefix}-checked`]: realChecked,
  166. [`${prefix}-${displayMode}`]: Boolean(displayMode),
  167. [`${prefix}-buttonRadioComponent`]: isButtonRadioComponent,
  168. [`${prefix}-buttonRadioGroup`]: isButtonRadioGroup,
  169. [`${prefix}-buttonRadioGroup-${buttonSize}`]: isButtonRadioGroup && buttonSize,
  170. [`${prefix}-cardRadioGroup`]: isCardRadioGroup,
  171. [`${prefix}-cardRadioGroup_disabled`]: isDisabled && isCardRadioGroup,
  172. [`${prefix}-cardRadioGroup_checked`]: isCardRadioGroup && realChecked && !isDisabled,
  173. [`${prefix}-cardRadioGroup_checked_disabled`]: isCardRadioGroup && realChecked && isDisabled,
  174. [`${prefix}-cardRadioGroup_hover`]: isCardRadioGroup && !realChecked && isHover && !isDisabled,
  175. [className]: Boolean(className),
  176. });
  177. const name = this.isInGroup() && this.context.radioGroup.name;
  178. const addonCls = cls({
  179. [`${prefix}-addon`]: !isButtonRadio,
  180. [`${prefix}-addon-buttonRadio`]: isButtonRadio,
  181. [`${prefix}-addon-buttonRadio-checked`]: isButtonRadio && realChecked,
  182. [`${prefix}-addon-buttonRadio-disabled`]: isButtonRadio && isDisabled,
  183. [`${prefix}-addon-buttonRadio-hover`]: isButtonRadio && !realChecked && !isDisabled && isHover,
  184. [`${prefix}-addon-buttonRadio-${buttonSize}`]: isButtonRadio && buttonSize,
  185. }, addonClassName);
  186. const renderContent = () => (
  187. <>
  188. {children ? <span className={addonCls} style={addonStyle}>{children}</span> : null}
  189. {extra && !isButtonRadio ? <div className={`${prefix}-extra`}>{extra}</div> : null}
  190. </>
  191. );
  192. return (
  193. <label
  194. style={style}
  195. className={wrapper}
  196. onMouseEnter={this.handleMouseEnter}
  197. onMouseLeave={this.handleMouseLeave}
  198. >
  199. <RadioInner
  200. {...this.props}
  201. {...props}
  202. mode={realMode}
  203. name={name}
  204. isButtonRadio={isButtonRadio}
  205. isPureCardRadioGroup={isPureCardRadioGroup}
  206. onChange={this.onChange}
  207. ref={(ref: RadioInner) => {
  208. this.radioEntity = ref;
  209. }}
  210. />
  211. {
  212. isCardRadioGroup ?
  213. <div className={`${prefix}-isCardRadioGroup_content`}>{renderContent()}</div> :
  214. renderContent()
  215. }
  216. </label>
  217. );
  218. }
  219. }
  220. export default Radio;