radioGroup.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import classnames from 'classnames';
  2. import { noop } from 'lodash';
  3. import PropTypes from 'prop-types';
  4. import React from 'react';
  5. import { radioGroupClasses as css, strings } from '@douyinfe/semi-foundation/radio/constants';
  6. import RadioGroupFoundation, { RadioGroupAdapter } from '@douyinfe/semi-foundation/radio/radioGroupFoundation';
  7. import { RadioChangeEvent } from '@douyinfe/semi-foundation/radio/radioInnerFoundation';
  8. import { ArrayElement } from '../_base/base';
  9. import BaseComponent from '../_base/baseComponent';
  10. import Context, { RadioGroupButtonSize, RadioMode } from './context';
  11. import Radio, { RadioType } from './radio';
  12. export interface OptionItem {
  13. label?: React.ReactNode;
  14. value?: string | number;
  15. disabled?: boolean;
  16. extra?: React.ReactNode;
  17. style?: React.CSSProperties;
  18. className?: string;
  19. addonId?: string;
  20. addonStyle?: React.CSSProperties;
  21. addonClassName?: string;
  22. extraId?: string
  23. }
  24. export type Options = string[] | Array<OptionItem>;
  25. export type RadioGroupProps = {
  26. defaultValue?: string | number;
  27. disabled?: boolean;
  28. name?: string;
  29. options?: Options;
  30. value?: string | number;
  31. onChange?: (event: RadioChangeEvent) => void;
  32. className?: string;
  33. children?: React.ReactNode;
  34. style?: React.CSSProperties;
  35. direction?: ArrayElement<typeof strings.DIRECTION_SET>;
  36. mode?: RadioMode;
  37. type?: RadioType;
  38. buttonSize?: RadioGroupButtonSize;
  39. prefixCls?: string;
  40. 'aria-label'?: React.AriaAttributes['aria-label'];
  41. 'aria-describedby'?: React.AriaAttributes['aria-describedby'];
  42. 'aria-errormessage'?: React.AriaAttributes['aria-errormessage'];
  43. 'aria-invalid'?: React.AriaAttributes['aria-invalid'];
  44. 'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];
  45. 'aria-required'?: React.AriaAttributes['aria-required'];
  46. id?: string
  47. };
  48. export interface RadioGroupState {
  49. value?: any
  50. }
  51. class RadioGroup extends BaseComponent<RadioGroupProps, RadioGroupState> {
  52. static propTypes = {
  53. defaultValue: PropTypes.any,
  54. disabled: PropTypes.bool,
  55. name: PropTypes.string,
  56. options: PropTypes.array,
  57. buttonSize: PropTypes.oneOf(strings.BUTTON_SIZE),
  58. type: PropTypes.oneOf([strings.TYPE_DEFAULT, strings.TYPE_BUTTON, strings.TYPE_CARD, strings.TYPE_PURECARD]),
  59. value: PropTypes.any,
  60. onChange: PropTypes.func,
  61. children: PropTypes.node,
  62. prefixCls: PropTypes.string,
  63. className: PropTypes.string,
  64. style: PropTypes.object,
  65. direction: PropTypes.oneOf(strings.DIRECTION_SET),
  66. mode: PropTypes.oneOf(strings.MODE),
  67. 'aria-label': PropTypes.string,
  68. 'aria-describedby': PropTypes.string,
  69. 'aria-errormessage': PropTypes.string,
  70. 'aria-invalid': PropTypes.bool,
  71. 'aria-labelledby': PropTypes.string,
  72. 'aria-required': PropTypes.bool,
  73. id: PropTypes.string,
  74. };
  75. static defaultProps: Partial<RadioGroupProps> = {
  76. disabled: false,
  77. onChange: noop,
  78. direction: strings.DEFAULT_DIRECTION,
  79. mode: '',
  80. type: strings.TYPE_DEFAULT,
  81. buttonSize: 'middle'
  82. };
  83. foundation: RadioGroupFoundation;
  84. constructor(props: RadioGroupProps) {
  85. super(props);
  86. this.state = {
  87. value: props.value || props.defaultValue,
  88. };
  89. this.foundation = new RadioGroupFoundation(this.adapter);
  90. }
  91. componentDidMount() {
  92. this.foundation.init();
  93. }
  94. componentDidUpdate(prevProps: RadioGroupProps) {
  95. if (typeof prevProps.value === 'number'
  96. && isNaN(prevProps.value)
  97. && typeof this.props.value === 'number'
  98. && isNaN(this.props.value)
  99. ) {
  100. // `NaN === NaN` returns false, and this will fail the next if check
  101. // therefore triggering an infinite loop
  102. return;
  103. }
  104. if (prevProps.value !== this.props.value) {
  105. this.foundation.handlePropValueChange(this.props.value);
  106. }
  107. }
  108. componentWillUnmount() {
  109. this.foundation.destroy();
  110. }
  111. get adapter(): RadioGroupAdapter {
  112. return {
  113. ...super.adapter,
  114. setValue: (value: any) => {
  115. this.setState({ value });
  116. },
  117. getProps: () => this.props,
  118. isInProps: (name: string) => Boolean(name in this.props),
  119. notifyChange: (evt: RadioChangeEvent) => {
  120. this.props.onChange && this.props.onChange(evt);
  121. },
  122. };
  123. }
  124. onChange = (evt: RadioChangeEvent) => {
  125. this.foundation.handleChange(evt);
  126. };
  127. getFormatName = () => this.props.name || 'default';
  128. render() {
  129. const {
  130. children,
  131. options,
  132. mode,
  133. prefixCls,
  134. className,
  135. style,
  136. direction,
  137. type,
  138. buttonSize,
  139. id,
  140. ...rest
  141. } = this.props;
  142. const isButtonRadio = type === strings.TYPE_BUTTON;
  143. const isPureCardRadio = type === strings.TYPE_PURECARD;
  144. const isCardRadio = type === strings.TYPE_CARD || isPureCardRadio;
  145. const isDefaultRadio = type === strings.TYPE_DEFAULT;
  146. const prefix = prefixCls || css.PREFIX;
  147. const prefixClsDisplay = classnames(className, {
  148. [prefix]: true,
  149. [`${prefix}-wrapper`]: true,
  150. [`${prefix}-${direction}`]: direction && !isButtonRadio,
  151. [`${prefix}-${direction}-default`]: direction && isDefaultRadio,
  152. [`${prefix}-${direction}-card`]: direction && isCardRadio,
  153. [`${prefix}-buttonRadio`]: isButtonRadio,
  154. });
  155. const realValue = this.state.value;
  156. let inner;
  157. if (options) {
  158. inner = (options || []).map((option, index) => {
  159. if (typeof option === 'string') {
  160. return (
  161. <Radio
  162. key={index}
  163. disabled={this.props.disabled}
  164. value={option}
  165. >
  166. {option}
  167. </Radio>
  168. );
  169. } else {
  170. return (
  171. <Radio
  172. key={index}
  173. disabled={option.disabled || this.props.disabled}
  174. value={option.value}
  175. extra={option.extra}
  176. className={option.className}
  177. style={option.style}
  178. addonId={option.addonId}
  179. addonStyle={option.addonStyle}
  180. addonClassName={option.addonClassName}
  181. extraId={option.extraId}
  182. >
  183. {option.label}
  184. </Radio>
  185. );
  186. }
  187. });
  188. } else if (children) {
  189. inner = React.Children.map(children, (itm, index) => (React.isValidElement(itm) ?
  190. React.cloneElement(itm, { key: index }) :
  191. null));
  192. }
  193. return (
  194. <div
  195. className={prefixClsDisplay}
  196. style={style}
  197. id={id}
  198. aria-label={this.props['aria-label']}
  199. aria-invalid={this.props['aria-invalid']}
  200. aria-errormessage={this.props['aria-errormessage']}
  201. aria-labelledby={this.props['aria-labelledby']}
  202. aria-describedby={this.props['aria-describedby']}
  203. aria-required={this.props['aria-required']}
  204. {...this.getDataAttr(rest)}
  205. >
  206. <Context.Provider
  207. value={{
  208. radioGroup: {
  209. onChange: this.onChange,
  210. value: realValue,
  211. disabled: this.props.disabled,
  212. name: this.getFormatName(),
  213. isButtonRadio,
  214. isCardRadio,
  215. isPureCardRadio,
  216. buttonSize,
  217. prefixCls
  218. },
  219. mode
  220. }}
  221. >
  222. {inner}
  223. </Context.Provider>
  224. </div>
  225. );
  226. }
  227. }
  228. export default RadioGroup;