group.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import React, { Component } from 'react';
  2. import classNames from 'classnames';
  3. import { isString } from 'lodash';
  4. import { isValid } from '@douyinfe/semi-foundation/form/utils';
  5. import { cssClasses } from '@douyinfe/semi-foundation/form/constants';
  6. import * as ObjectUtil from '@douyinfe/semi-foundation/utils/object';
  7. import ErrorMessage, { ReactFieldError } from './errorMessage';
  8. import Label, { LabelProps } from './label';
  9. import { FormUpdaterContext } from './context';
  10. import { useFormState } from './hooks/index';
  11. import InputGroup, { InputGroupProps as BacisInputGroupProps } from '../input/inputGroup';
  12. import { BaseFormProps, FormState } from './interface';
  13. import { FormUpdaterContextType } from '@douyinfe/semi-foundation/form/interface';
  14. import { Col, Row } from '../grid/index';
  15. interface GroupErrorProps {
  16. showValidateIcon?: boolean;
  17. isInInputGroup?: boolean;
  18. error?: ReactFieldError;
  19. fieldSet?: string[]
  20. }
  21. export interface InputGroupProps extends BacisInputGroupProps {
  22. label?: LabelProps;
  23. labelPosition?: 'left' | 'top';
  24. extraText?: React.ReactNode;
  25. extraTextPosition?: 'bottom' | 'middle'
  26. }
  27. const prefix = cssClasses.PREFIX;
  28. // Group component to remove Labels and ErrorMessages from its child fields
  29. // Unified insertion of Labels and ErrorMessages from the group level
  30. // Get Errors of all field in this group
  31. const GroupError = (props: GroupErrorProps) => {
  32. const { fieldSet } = props;
  33. const formState: FormState = useFormState();
  34. const error = fieldSet.map((field: string) => ObjectUtil.get(formState.errors, field));
  35. if (isValid(error)) {
  36. return null;
  37. }
  38. return (
  39. <ErrorMessage error={error} showValidateIcon={props.showValidateIcon} isInInputGroup={props.isInInputGroup} />
  40. );
  41. };
  42. class FormInputGroup extends Component<InputGroupProps> {
  43. static contextType = FormUpdaterContext;
  44. context: FormUpdaterContextType;
  45. static defaultProps = {
  46. extraTextPosition: 'bottom'
  47. }
  48. renderLabel(label: LabelProps, formProps: BaseFormProps) {
  49. if (label) {
  50. if (isString(label)) {
  51. return (<Label width={formProps.labelWidth} text={label} />);
  52. } else {
  53. return (<Label width={formProps.labelWidth} {...label} />);
  54. }
  55. }
  56. return null;
  57. }
  58. render() {
  59. const { children, label, extraText, extraTextPosition, ...rest } = this.props;
  60. const updater = this.context;
  61. const formProps = updater.getFormProps(['labelPosition', 'labelWidth', 'labelAlign', 'showValidateIcon', 'wrapperCol', 'labelCol', 'disabled']);
  62. const labelPosition = this.props.labelPosition || formProps.labelPosition;
  63. const groupFieldSet: Array<string> = [];
  64. const inner = React.Children.map(children, (child: any) => {
  65. if (child && child.props && child.props.field) {
  66. groupFieldSet.push(child.props.field);
  67. return React.cloneElement(child, {
  68. isInInputGroup: true,
  69. // noErrorMessage: true,
  70. // noLabel: true
  71. });
  72. }
  73. return null;
  74. });
  75. const groupCls = classNames({
  76. [`${prefix}-field-group`]: true
  77. });
  78. const labelCol = formProps.labelCol;
  79. const wrapperCol = formProps.wrapperCol;
  80. const labelAlign = formProps.labelAlign;
  81. const appendCol = labelCol && wrapperCol;
  82. const labelColCls = labelCol ? `${prefix}-col-${labelAlign}` : '';
  83. const labelContent = this.renderLabel(label, formProps);
  84. const inputGroupContent = (
  85. <InputGroup disabled={formProps.disabled} {...rest}>
  86. {inner}
  87. </InputGroup>
  88. );
  89. const groupErrorContent = (<GroupError fieldSet={groupFieldSet} showValidateIcon={formProps.showValidateIcon} isInInputGroup />);
  90. const extraCls = classNames(`${prefix}-field-extra`, {
  91. [`${prefix}-field-extra-string`]: typeof extraText === 'string',
  92. [`${prefix}-field-extra-middle`]: extraTextPosition === 'middle',
  93. [`${prefix}-field-extra-bottom`]: extraTextPosition === 'bottom',
  94. });
  95. const extraContent = extraText ? <div className={extraCls} x-semi-prop="extraText">{extraText}</div> : null;
  96. let content: any;
  97. switch (true) {
  98. case !appendCol:
  99. content = (
  100. <>
  101. {labelContent}
  102. <div>
  103. {extraTextPosition === 'middle' ? extraContent : null}
  104. {inputGroupContent}
  105. {extraTextPosition === 'bottom' ? extraContent : null}
  106. {groupErrorContent}
  107. </div>
  108. </>
  109. );
  110. break;
  111. case appendCol && labelPosition === 'top':
  112. // When labelPosition is top, you need to add an overflow hidden div to the label, otherwise it will be arranged horizontally
  113. content = (
  114. <>
  115. <div style={{ overflow: 'hidden' }}>
  116. <Col {...labelCol} className={labelColCls}>
  117. {labelContent}
  118. </Col>
  119. </div>
  120. <Col {...wrapperCol}>
  121. {extraTextPosition === 'middle' ? extraContent : null}
  122. {inputGroupContent}
  123. {extraTextPosition === 'bottom' ? extraContent : null}
  124. {groupErrorContent}
  125. </Col>
  126. </>
  127. );
  128. break;
  129. case appendCol && labelPosition !== 'top':
  130. content = (
  131. <>
  132. <Col {...labelCol} className={labelColCls}>
  133. {labelContent}
  134. </Col>
  135. <Col {...wrapperCol}>
  136. {extraTextPosition === 'middle' ? extraContent : null}
  137. {inputGroupContent}
  138. {extraTextPosition === 'bottom' ? extraContent : null}
  139. {groupErrorContent}
  140. </Col>
  141. </>
  142. );
  143. break;
  144. default:
  145. break;
  146. }
  147. return (
  148. <div x-label-pos={labelPosition} className={groupCls}>
  149. {content}
  150. </div>
  151. );
  152. }
  153. }
  154. export default FormInputGroup;