Browse Source

feat: Form.InputGroup support extraText、extraTextPosition, #1313 (#1407)

pointhalo 2 years ago
parent
commit
ae8e4748d7

File diff suppressed because it is too large
+ 509 - 253
content/input/form/index-en-US.md


+ 42 - 31
content/input/form/index.md

@@ -183,11 +183,6 @@ import { Form } from '@douyinfe/semi-ui';
 
 ### 已支持的表单控件
 
-> Form.TreeSelect、Form.Cascader、Form.Rating 在 v0.22.0 及之后的版本开始提供;  
-> Form.AutoComplete 在 v0.28.0 及之后的版本开始提供  
-> Form.Upload 在 v1.0.0 及之后的版本开始提供  
-> Form.TagInput 在 v1.21.0 及之后的版本开始提供  
-
 ```jsx live=true dir="column"
 import React from 'react';
 import { Form, Col, Row, Button } from '@douyinfe/semi-ui';
@@ -234,11 +229,8 @@ class BasicDemoWithInit extends React.Component {
                 ]
             }
         };
-        this.getFormApi = this.getFormApi.bind(this);
     }
 
-    getFormApi(formApi) { this.formApi = formApi; }
-
     render() {
         const { Section, Input, InputNumber, AutoComplete, Select, TreeSelect, Cascader, DatePicker, TimePicker, TextArea, CheckboxGroup, Checkbox, RadioGroup, Radio, Slider, Rating, Switch, TagInput } = Form;
         const { initValues } = this.state;
@@ -278,7 +270,6 @@ class BasicDemoWithInit extends React.Component {
 
         return (
             <Form
-                getFormApi={this.getFormApi}
                 initValues={initValues}
                 style={{ padding: 10, width: '100%' }}
                 onValueChange={(v)=>console.log(v)}
@@ -408,8 +399,8 @@ class BasicDemoWithInit extends React.Component {
                                 { type: 'boolean' },
                                 { required: true, message: '必须选择是否独占 ' }
                             ]}>
-                                <Radio value={true}>是</Radio>
-                                <Radio value={false}>否</Radio>
+                                <Radio value={1}>是</Radio>
+                                <Radio value={0}>否</Radio>
                             </RadioGroup>
                         </Col>
                     </Row>
@@ -948,7 +939,7 @@ import { Form } from '@douyinfe/semi-ui';
                 validateStatus={validateStatus}
                 helpText={helpText}
                 extraText={
-                    <div 
+                    <div
                         style={{
                             color: 'var(--semi-color-link)',
                             fontSize: 14,
@@ -1506,6 +1497,7 @@ import { Form, Button } from '@douyinfe/semi-ui';
 
 针对动态增删的数组类表单项,我们提供了 ArrayField 作用域来简化 add/remove 的操作  
 ArrayField 自带了 add、remove、addWithInitValue 等 api 用来执行新增行,删除行,新增带有初始值的行等操作  
+ArrayField 详细的 API请查阅下方 [ArrayField Props](#arrayfield-props)
 注意:ArrayField 的 initValue 类型必须是数组
 
 ```jsx live=true dir="column" hideInDSM
@@ -1517,15 +1509,15 @@ class ArrayFieldDemo extends React.Component {
     constructor() {
         super();
         this.state = {
-            menu: [
-                { name: '脸部贴纸', type: '2D' },
-                { name: '前景贴纸', type: '3D' },
+            data: [
+                { name: 'Semi D2C', role: 'Engineer' },
+                { name: 'Semi C2D', role: 'Designer' },
             ]
         };
     }
 
     render() {
-        let { menu } = this.state;
+        let { data } = this.state;
         const ComponentUsingFormState = () => {
             const formState = useFormState();
             return (
@@ -1533,30 +1525,38 @@ class ArrayFieldDemo extends React.Component {
             );
         };
         return (
-            <Form style={{ width: 500 }} labelPosition='left' labelWidth='220px' allowEmpty>
-                <ArrayField field='effects' initValue={menu}>
+            <Form style={{ width: 800 }} labelPosition='left' labelWidth='100px' allowEmpty>
+                <ArrayField field='rules' initValue={data}>
                     {({ add, arrayFields, addWithInitValue }) => (
                         <React.Fragment>
-                            <Button onClick={add} icon={<IconPlusCircle />} theme='light'>新增空白行</Button>
-                            <Button icon={<IconPlusCircle />} onClick={() => {addWithInitValue({ name: '自定义贴纸', type: '2D' });}} style={{ marginLeft: 8 }}>新增带有初始值的行</Button>
+                            <Button onClick={add} icon={<IconPlusCircle />} theme='light'>Add new line</Button>
+                            <Button icon={<IconPlusCircle />} onClick={() => {addWithInitValue({ name: 'Semi DSM', type: 'Designer' });}} style={{ marginLeft: 8 }}>Add new line with init value</Button>
                             {
                                 arrayFields.map(({ field, key, remove }, i) => (
                                     <div key={key} style={{ width: 1000, display: 'flex' }}>
                                         <Form.Input
                                             field={`${field}[name]`}
-                                            label={`特效类型:(${field}.name`}
+                                            label={`${field}.name`}
                                             style={{ width: 200, marginRight: 16 }}
                                         >
                                         </Form.Input>
                                         <Form.Select
-                                            field={`${field}[type]`}
-                                            label={`素材类型:(${field}.type)`}
-                                            style={{ width: 90 }}
+                                            field={`${field}[role]`}
+                                            label={`${field}.role`}
+                                            style={{ width: 120 }}
+                                            optionList={[
+                                                { label: 'Engineer', value: 'Engineer' },
+                                                { label: 'Designer', value: 'Designer' },
+                                            ]}
                                         >
-                                            <Form.Select.Option value='2D'>2D</Form.Select.Option>
-                                            <Form.Select.Option value='3D'>3D</Form.Select.Option>
                                         </Form.Select>
-                                        <Button type='danger' theme='borderless' icon={<IconMinusCircle />} onClick={remove} style={{ margin: 12 }}></Button>
+                                        <Button
+                                            type='danger'
+                                            theme='borderless'
+                                            icon={<IconMinusCircle />}
+                                            onClick={remove}
+                                            style={{ margin: 12 }}
+                                        />
                                     </div>
                                 ))
                             }
@@ -2127,9 +2127,20 @@ const { Label } = Form;
 | width     | label 宽度               | number/string    |        |  |
 | optional  | 是否自动在text后追加"(可选)"文字标识(根据Locale配置的不同语言自动切换相同语义文本)。当该项为true时,required的\*号将不再展示。若当表单项多数均为必填时,仅强调可选项会更使得整体视觉更简洁  | boolean    | false | v2.18.0 |
 
-## Form.Slot
+## Form.InputGroup
+
+| 属性             | 说明                                                      | 类型                     | 默认值 | 版本 |
+| ---------------- | --------------------------------------------------------- | ------------------------ |--- |--- |
+| className        | 样式类名                                                  | string                   | |
+| style            | 内联样式                                                  | object                   ||
+| label            | InputGroup 的 label 标签文本                      |  Label \| string                 | |
+| labelPosition    | 该表单控件的 label 位置,可选'top'/'left'/'inset'。在 Form 与 InputGroup 同时传入时,以 InputGroup props为准 | string     | 'top'|
+| extraText        | 额外的提示信息,当需要错误信息和提示文案同时出现时,可以使用这个,位于 errorMessage 后 | ReactNode | | v2.29.0 |
+| extraTextPosition| 控制extraText的显示位置,可选`middle`(垂直方向以Label、extraText、Group的顺序显示)、`bottom` (垂直方向以Label、Group、extraText的顺序显示)| string | 'bottom' | v2.29.0|
 
-> Form.Slot 在 v0.27.0 开始提供
+当 extraTextPositon 为 middle,且 labelPosition 为 left时。由于 extraText允许为 ReactNode,内容高度不定,Label将不再确保能与 Field / InputGroup 中的首行文本对齐。 
+
+## Form.Slot
 
 ```jsx
 import { Form } from '@douyinfe/semi-ui';
@@ -2147,8 +2158,6 @@ const { Slot } = Form;
 
 ## Form.ErrorMessage
 
-> Form.ErrorMessage 在 v0.27.0 开始提供
-
 ```jsx
 import { Form } from '@douyinfe/semi-ui';
 const { ErrorMessage } = Form;
@@ -2165,6 +2174,8 @@ const { ErrorMessage } = Form;
 | showValidateIcon | 是否自动加上 validateStatus 对应的 icon                       | boolean                  |
 | validateStatus   | 信息所属的校验状态,可选 default/error/warning/success(success一般建议与default样式相同) | string                  |
 
+
+
 ## withFieldOption
 
 | key               | 描述                                                                                                                                                                                                                                          | 默认值     |

+ 1 - 1
packages/semi-foundation/form/form.scss

@@ -257,11 +257,11 @@ $rating: #{$prefix}-rating;
         margin-bottom: 0;
         padding-top: $spacing-form_field_group_vertical-paddingTop;
         padding-bottom: $spacing-form_field_group_vertical-paddingBottom;
+        overflow: hidden;
     }
 }
 
 .#{$form}-field-group {
-
     &[x-label-pos="top"] {
     }
 

+ 186 - 0
packages/semi-ui/form/_story/InputGroup/groupProps.jsx

@@ -0,0 +1,186 @@
+import React, { useState, useLayoutEffect, Component } from 'react';
+import { storiesOf } from '@storybook/react';
+import { Button, Modal, TreeSelect, Row, Col, Avatar, Icon, Select as BasicSelect,
+    Form,
+    useFormState,
+    useFormApi,
+    useFieldApi,
+    useFieldState,
+    withFormState,
+    withFormApi,
+    withField,
+    ArrayField,
+    AutoComplete,
+    Collapse } from '../../../index';
+
+
+const InputGroupDemo = () => {
+
+    const selectProps = {
+        style: { width: '100px' },
+        placeholder: '国家',
+        field: 'country',
+        rules: [
+            { required: true }
+        ]
+    };
+    return (
+        <>
+            <Form onSubmit={(values) => console.log(values)} labelPosition='top' style={{ width: 600 }}>
+                <Form.InputGroup label={{ text: 'Movie', required: true }} labelPosition="left" style={{ width: 500 }}>
+                    <Form.Select {...selectProps} field='area'>
+                        <Form.Select.Option value="crime">+86</Form.Select.Option>
+                        <Form.Select.Option value="comedy">+1</Form.Select.Option>
+                        <Form.Select.Option value="tragedy">+83</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input placeholder="手机号码" style={{ width: 100 }} field="phone" noLabel rules={[{ required: true }]} />
+                    <Form.InputNumber placeholder="评分" style={{ width: 140 }} field="MovieScore" noLabel />
+                </Form.InputGroup>
+                <Form.InputGroup label={{ text: (<span>手机号码</span>), required: true }} labelPosition='top' extraText='i am extraText of Form.InputGroup'>
+                    <Form.Select style={{ width: 150 }} field='phonePrefix1' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input initValue='18912345678' style={{ width: 250 }} field='phoneNumber1' trigger={['mount', 'change']} validate={val => 'always errors'} showClear />
+                </Form.InputGroup>
+            
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='top' 
+                    extraTextPosition='middle'
+                    extraText='i am extraText of Form.InputGroup'>
+                    <Form.Select style={{ width: 150 }} field='phonePrefix1' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input initValue='18912345678' style={{ width: 250 }} field='phoneNumber1' trigger={['mount', 'change']} validate={val => 'always errors'} showClear />
+                </Form.InputGroup>
+
+                <Form.InputGroup label={{ text: (<span>手机号码</span>), required: true }} labelPosition='left' style={{ width: 400 }}>
+                    <Form.Select style={{ width: 150 }} field='phonePrefix2' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input initValue='18912345678' style={{ width: 250 }} field='phoneNumber2' showClear />
+                </Form.InputGroup>
+
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='left'
+                    extraTextPosition='bottom'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix3' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber3' trigger={['mount', 'change']} validate={val => 'always errors'} showClear />
+                </Form.InputGroup>
+
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='left'
+                    extraTextPosition='middle'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix4' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber4' trigger={['mount', 'change']} validate={val => 'always errors'} rules={[{ required: true }]} showClear />
+                </Form.InputGroup>
+
+                <Button htmlType='submit'>提交</Button>
+            </Form>
+
+            <Form 
+                labelCol={{ span: 2 }}
+                wrapperCol={{ span: 22 }}
+            >
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='left'
+                    extraTextPosition='bottom'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix3' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber3' trigger={['mount', 'change']} validate={val => 'always errors'} showClear />
+                </Form.InputGroup>
+
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='left'
+                    extraTextPosition='middle'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix4' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber4' trigger={['mount', 'change']} validate={val => 'always errors'} rules={[{ required: true }]} showClear />
+                </Form.InputGroup>
+            </Form>
+
+            <Form 
+                labelCol={{ span: 4 }}
+                wrapperCol={{ span: 20 }}
+            >
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='top'
+                    extraTextPosition='bottom'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix3' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber3' trigger={['mount', 'change']} validate={val => 'always errors'} showClear />
+                </Form.InputGroup>
+
+                <Form.InputGroup
+                    label={{ text: (<span>手机号码</span>), required: true }}
+                    labelPosition='top'
+                    extraTextPosition='middle'
+                    extraText='i am extraText of Form.InputGroup'
+                    style={{ width: 400 }}
+                >
+                    <Form.Select style={{ width: 150 }} field='phonePrefix4' initValue='+86' rules={[{ required: true }]} showClear>
+                        <Form.Select.Option value='+1'>美国+1</Form.Select.Option>
+                        <Form.Select.Option value='+852'>香港+852</Form.Select.Option>
+                        <Form.Select.Option value='+86'>中国+86</Form.Select.Option>
+                        <Form.Select.Option value='+81'>日本+81</Form.Select.Option>
+                    </Form.Select>
+                    <Form.Input style={{ width: 250 }} field='phoneNumber4' trigger={['mount', 'change']} validate={val => 'always errors'} rules={[{ required: true }]} showClear />
+                </Form.InputGroup>
+            </Form>
+        </>
+    );
+};
+
+
+export { InputGroupDemo };

+ 1 - 56
packages/semi-ui/form/_story/Layout/layoutDemo.jsx

@@ -165,61 +165,6 @@ class InsetLabelDemo extends React.Component {
     }
 }
 
-
-
-class GroupFormDemo extends React.Component {
-
-    constructor() {
-        super();
-        this.saveFormApi = this.saveFormApi.bind(this);
-        this.manualSubmit = this.manualSubmit.bind(this);
-    }
-    saveFormApi(formApi) {
-        this.formApi = formApi;
-    }
-
-    manualSubmit() {
-        this.formApi.submitForm();
-    }
-
-    render() {
-        const selectProps = {
-            style: { width: '100px' },
-            placeholder: '国家',
-            field: 'country',
-            rules: [
-                { required: true }
-            ]
-        };
-        return (
-            <>
-                <Form onSubmit={values => console.log(values)} labelPosition="left" getFormApi={this.saveFormApi}>
-                    <InputGroup label={{ text: 'Movie', required: true }} labelPosition="left">
-                        <Select {...selectProps}>
-                            <Select.Option value="crime">+86</Select.Option>
-                            <Select.Option value="comedy">+1</Select.Option>
-                            <Select.Option value="tragedy">+83</Select.Option>
-                        </Select>
-                        <Input placeholder="手机号码" style={{ width: 100 }} field="phone" noLabel rules={[{ required: true }]} />
-                        <InputNumber placeholder="评分" style={{ width: 140 }} field="MovieScore" noLabel />
-                    </InputGroup>
-                    <Input field="name" trigger="blur" rules={[{ required: true }]} />
-                    <Input field="familyName[0].before" trigger="blur" />
-                    <Input field="familyName[0].after" trigger="blur" />
-                    <Select field="Sex">
-                        <Option value="female">female</Option>
-                        <Option value="male">male</Option>
-                    </Select>
-
-                    <Button htmlType="submit">提交</Button>
-                    <Button onClick={this.manualSubmit}>手动提交</Button>
-                </Form>
-            </>
-        );
-    }
-}
-
-
 class LayoutForm extends React.Component {
     constructor() {
         super();
@@ -349,4 +294,4 @@ class LayoutForm extends React.Component {
     }
 }
 
-export { LayoutDemo, InsetLabelDemo, GroupFormDemo };
+export { LayoutDemo, InsetLabelDemo };

+ 15 - 27
packages/semi-ui/form/_story/form.stories.jsx

@@ -1,27 +1,9 @@
 import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
 import {
   Button,
-  Modal,
-  TreeSelect,
-  Row,
-  Col,
-  Avatar,
-  Tabs,
-  TabPane,
-  Badge,
-  Notification,
 } from '../../index';
 import {
   Form,
-  useFormState,
-  useFormApi,
-  useFieldApi,
-  useFieldState,
-  withFormState,
-  withFormApi,
-  withField,
-  ArrayField,
-  Icon,
 } from '../../index';
 import { BasicDemoWithInit, LinkFieldForm, DifferentDeclareUsage } from './demo';
 const {
@@ -49,7 +31,7 @@ import {
 } from './Hook/hookDemo';
 
 // layout
-import { LayoutDemo, InsetLabelDemo, GroupFormDemo } from './Layout/layoutDemo';
+import { LayoutDemo, InsetLabelDemo } from './Layout/layoutDemo';
 import { AssistComponent } from './Layout/slotDemo';
 import { ModalFormDemo } from './Layout/modalFormDemo';
 
@@ -74,6 +56,9 @@ import { UpdateDemo, RuleupdateDemo } from './FieldProps/rulesUpdateDemo';
 import { FieldRefDemo } from './FieldProps/fieldRef';
 import { LableOptionalDemo } from './FieldProps/labelOptional';
 
+// form inputGroup
+import { InputGroupDemo } from './InputGroup/groupProps';
+
 // arrayField
 import {
   ArrayFieldCollapseDemo,
@@ -96,6 +81,7 @@ import { FieldPathWithArrayDemo } from './Debug/bugDemo';
 import ChildDidMount from './Debug/childDidMount';
 export { default as FormSubmit } from './FormSubmit';
 
+
 export default {
   title: 'Form'
 }
@@ -114,12 +100,6 @@ BasicDemo.story = {
   name: 'BasicDemo',
 };
 
-export const LayoutFormInputGroup = () => <GroupFormDemo />;
-
-LayoutFormInputGroup.story = {
-  name: 'Layout-Form.InputGroup',
-};
-
 export const LayoutFormWrapperColLabelCol = () => <LayoutDemo />;
 
 LayoutFormWrapperColLabelCol.story = {
@@ -249,10 +229,11 @@ ValidateUseRules.story = {
   name: 'Validate-use rules',
 };
 
+
+export const RaceAsync = () => <RaceAsyncDemo />;
 RaceAsyncDemo.story = {
   name: 'Validate - race async'
 }
-export const RaceAsync = () => <RaceAsyncDemo />;
 
 export const HooksUseFormApi = () => <UseFormApiDemo />;
 
@@ -340,7 +321,6 @@ export const FiledPropHelpTextExtraTextExtraTextPosition = () => (
   <>
     <HelpAndExtra />
     <ExtraPositionDemo />
-    <GroupFormDemo />
   </>
 );
 
@@ -388,6 +368,14 @@ FieldPropRef.story = {
   name: 'Field Prop-ref',
 };
 
+
+export const GroupProp = () => <InputGroupDemo />
+
+GroupProp.story = {
+  name: 'InputGroup Prop - basic'
+};
+
+
 const InitEmptyStringDemo = () => {
   return (
     <Form allowEmpty>

+ 18 - 4
packages/semi-ui/form/group.tsx

@@ -21,7 +21,9 @@ interface GroupErrorProps {
 }
 export interface InputGroupProps extends BacisInputGroupProps {
     label?: LabelProps;
-    labelPosition?: 'left' | 'top'
+    labelPosition?: 'left' | 'top';
+    extraText?: React.ReactNode;
+    extraTextPosition?: 'bottom' | 'middle'
 }
 
 const prefix = cssClasses.PREFIX;
@@ -58,7 +60,7 @@ class FormInputGroup extends Component<InputGroupProps> {
     }
 
     render() {
-        const { children, label, ...rest } = this.props;
+        const { children, label, extraText, extraTextPosition, ...rest } = this.props;
         const updater = this.context;
         const formProps = updater.getFormProps(['labelPosition', 'labelWidth', 'labelAlign', 'showValidateIcon', 'wrapperCol', 'labelCol']);
         const labelPosition = this.props.labelPosition || formProps.labelPosition;
@@ -94,6 +96,14 @@ class FormInputGroup extends Component<InputGroupProps> {
         );
         const groupErrorContent = (<GroupError fieldSet={groupFieldSet} showValidateIcon={formProps.showValidateIcon} isInInputGroup />);
 
+        const extraCls = classNames(`${prefix}-field-extra`, {
+            [`${prefix}-field-extra-string`]: typeof extraText === 'string',
+            [`${prefix}-field-extra-middle`]: extraTextPosition === 'middle',
+            [`${prefix}-field-extra-bottom`]: extraTextPosition === 'bottom',
+        });
+
+        const extraContent = extraText ? <div className={extraCls} x-semi-prop="extraText">{extraText}</div> : null;
+
         let content: any;
 
         switch (true) {
@@ -102,7 +112,9 @@ class FormInputGroup extends Component<InputGroupProps> {
                     <>
                         {labelContent}
                         <div>
+                            {extraTextPosition === 'middle' ? extraContent : null}
                             {inputGroupContent}
+                            {extraTextPosition === 'bottom' ? extraContent : null}
                             {groupErrorContent}
                         </div>
                     </>
@@ -118,7 +130,9 @@ class FormInputGroup extends Component<InputGroupProps> {
                             </Col>
                         </div>
                         <Col {...wrapperCol}>
+                            {extraTextPosition === 'middle' ? extraContent : null}
                             {inputGroupContent}
+                            {extraTextPosition === 'bottom' ? extraContent : null}
                             {groupErrorContent}
                         </Col>
                     </>
@@ -131,7 +145,9 @@ class FormInputGroup extends Component<InputGroupProps> {
                             {labelContent}
                         </Col>
                         <Col {...wrapperCol}>
+                            {extraTextPosition === 'middle' ? extraContent : null}
                             {inputGroupContent}
+                            {extraTextPosition === 'bottom' ? extraContent : null}
                             {groupErrorContent}
                         </Col>
                     </>
@@ -141,8 +157,6 @@ class FormInputGroup extends Component<InputGroupProps> {
                 break;
         }
 
-
-
         return (
             <div x-label-pos={labelPosition} className={groupCls}>
                 {content}

Some files were not shown because too many files changed in this diff