radioGroup.tsx 7.6 KB

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