Browse Source

[Feat] Support auto append form label optional text (#1049)

* feat: form label support optional text auto show, close #869

* docs: update form usage

* docs: update form demo
pointhalo 3 years ago
parent
commit
cbcc54ad24

+ 20 - 9
content/input/form/index-en-US.md

@@ -56,7 +56,12 @@ Semi Form supports multiple writing at the same time.
 Add `field` property to each field component.
 You can also set label` properties for each field, by default is the same as field
 
-> Note: The field attribute is required props
+`label` can be passed in a string directly, or declared in the form of an object, configure `extra`, `required`, `optional` and other attributes to deal with more complex scenarios
+
+<Notice type='primary' title='Notice'>
+    The field attribute is required props
+</Notice>
+
 
 ```jsx live=true dir="column"
 import React from 'react';
@@ -65,21 +70,26 @@ import { IconHelpCircle } from '@douyinfe/semi-icons';
 
 () => (
     <Form layout='horizontal'>
-        <Form.Select field="role" label='UserRole' style={{ width:120 }}>
-            <Form.Select.Option value="admin">Admin</Form.Select.Option>
-            <Form.Select.Option value="user">User</Form.Select.Option>
-            <Form.Select.Option value="guest">Guest</Form.Select.Option>
-        </Form.Select>
-        <Form.Input field='userName' label='UserName' />
-        <Form.Input field='password' label='Password' />
+        <Form.Input field='username' label='UserName' style={{ width:80 }}/>
         <Form.Input
             field='password'
             label={{ 
                 text: 'Password',
-                extra: <Tooltip content='more detail'><IconHelpCircle style={{ color: '--semi-color-text-1' }}/></Tooltip> 
+                extra: <Tooltip content='More info xxx'><IconHelpCircle style={{ color: 'var(--semi-color-text-2)' }}/></Tooltip> 
             }}
             style={{ width:176 }}
         />
+        <Form.Select
+            field="role"
+            label={{ text: 'Role', optional: true }}
+            style={{ width:176 }}
+            optionList={[
+                { label: 'Admin', value: 'admin' },
+                { label: 'User', value: 'user' },
+                { label: 'Guest', value: 'guest' },
+            ]}
+        >
+        </Form.Select>
     </Form>
 );
 ```
@@ -1781,6 +1791,7 @@ const { Label } = Form;
 | className  | Classname of label wrapper      | string    |         |
 | style      | Inline style                    | string    |         |
 | width      | Label width                     | number    |         |
+| optional  | Whether to automatically append the "(optional)" text mark after the text (automatically switch the same semantic text according to different languages configured by Locale). When this item is true, the required \* will no longer be displayed.  | boolean    | false | v2.18.0 |
 
 ## Form.Slot
 

+ 11 - 9
content/input/form/index.md

@@ -58,7 +58,8 @@ Semi Form 同时支持多种写法
 #### 基本写法
 
 从 Form 中导出表单控件,给表单控件添加`field`属性,将其放置于 Form 内部即可  
-还可以给每个表单控件设置`label`属性,不传入时默认与 field 相同
+还可以给每个表单控件设置`label`属性,不传入时默认与 field 相同  
+`label`可以直接传入字符串,亦可以以 object 方式声明,配置 `extra`、`required`、`optional`等属性应对更复杂的场景 
 
 <Notice type='primary' title='注意事项'>
 对于Field级别组件来说,field 属性是必填项!
@@ -73,21 +74,21 @@ import { IconHelpCircle } from '@douyinfe/semi-icons';
     const { Option } = Form.Select;
 
     return (
-        <Form layout='horizontal'  onValueChange={values=>console.log(values)}>
-            <Form.Select field="Role" label='角色' style={{ width:176 }}>
-                <Option value="admin">管理员</Option>
-                <Option value="user">普通用户</Option>
-                <Option value="guest">访客</Option>
-            </Form.Select>
+        <Form layout='horizontal' onValueChange={values=>console.log(values)}>
             <Form.Input field='UserName' label='用户名' style={{ width:80 }}/>
             <Form.Input
                 field='Password'
                 label={{ 
                     text: '密码',
-                    extra: <Tooltip content='详情'><IconHelpCircle style={{ color: '--semi-color-text-1' }}/></Tooltip> 
+                    extra: <Tooltip content='详情'><IconHelpCircle style={{ color: 'var(--semi-color-text-2)' }}/></Tooltip> 
                 }}
                 style={{ width:176 }}
             />
+            <Form.Select field="Role" label={{ text: '角色', optional: true }} style={{ width:176 }}>
+                <Option value="admin">管理员</Option>
+                <Option value="user">普通用户</Option>
+                <Option value="guest">访客</Option>
+            </Form.Select>
         </Form>
     );
 };
@@ -2123,7 +2124,8 @@ const { Label } = Form;
 | align     | text-align               | string    | 'left' |  |
 | className | 样式类名                 | string    |        |  |
 | style     | 内联样式                 | string    |        |  |
-| width     | label 宽度               | number\/string    |        |  |
+| width     | label 宽度               | number/string    |        |  |
+| optional  | 是否自动在text后追加"(可选)"文字标识(根据Locale配置的不同语言自动切换相同语义文本)。当该项为true时,required的\*号将不再展示。若当表单项多数均为必填时,仅强调可选项会更使得整体视觉更简洁  | boolean    | false | v2.18.0 |
 
 ## Form.Slot
 

+ 4 - 3
packages/semi-foundation/form/form.scss

@@ -109,14 +109,15 @@ $rating: #{$prefix}-rating;
                     font-weight: $font-form_requiredMark-fontWeight;
                 }
             }
-            // .#{$field}-label-extra {
-            // }
-
             &-disabled {
                 color: $color-form_requiredMark_disabled-text-default;
             }
         }
 
+        &-optional-text {
+            color: $color-form_label_optional-text-default;
+        }
+
         &-left {
             text-align: left;
         }

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

@@ -32,6 +32,7 @@ $font-form_label-fontWeight: $font-weight-bold; // 表单标题字重
 $font-form_requiredMark-fontWeight: $font-weight-bold; // 表单必填标识字重
 
 $color-form_label-text-default: var(--semi-color-text-0); // 表单标题文字颜色
+$color-form_label_optional-text-default: var(--semi-color-tertiary); // 表单可选标记文字颜色
 $color-form_label_extra-text-default: var(--semi-color-tertiary); // 表单额外信息辅助文字颜色
 
 $color-form_label_disabled-text-default: var(--semi-color-disabled-text); // 禁用表单项标题文字颜色

+ 30 - 0
packages/semi-ui/form/_story/FieldProps/labelOptional.jsx

@@ -0,0 +1,30 @@
+/* eslint-disable no-unused-vars */
+import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
+import { storiesOf } from '@storybook/react';
+import { Button, Modal, TreeSelect, Row, Col, Avatar, Tabs, TabPane, Badge, LocaleProvider } from '@douyinfe/semi-ui';
+import {
+    Form,
+} from '../../index';
+
+const { Input, Select, DatePicker, Switch, Slider, CheckboxGroup, Checkbox, RadioGroup, Radio, TimePicker } = Form;
+
+
+const LableOptionalDemo = () => {
+    const helpText = <span style={{ color: 'var(--semi-color-warning)' }}>密码强度:弱</span>;
+    return (
+        <Form>
+            <Form.Input
+                field="semi"
+                label={{ text: 'semi', optional: true }}
+                helpText={helpText}
+            />
+            <Form.Input
+                field="label2"
+                label={{ text: 'semi', optional: true }}
+                helpText={helpText}
+            />
+        </Form>
+    );
+};
+
+export { LableOptionalDemo };

+ 7 - 0
packages/semi-ui/form/_story/form.stories.js

@@ -70,6 +70,7 @@ import { HelpAndExtra, ExtraPositionDemo } from './FieldProps/helpAndExtra';
 import { BigNumberFieldDemo } from './FieldProps/bigNumberFieldPath';
 import { UpdateDemo, RuleupdateDemo } from './FieldProps/rulesUpdateDemo';
 import { FieldRefDemo } from './FieldProps/fieldRef';
+import { LableOptionalDemo } from './FieldProps/labelOptional';
 
 // arrayField
 import {
@@ -129,6 +130,12 @@ LayoutInsetLabel.story = {
   name: 'Layout-insetLabel',
 };
 
+export const LableOptional = () => <LableOptionalDemo></LableOptionalDemo>;
+
+LableOptional.story = {
+  name: 'Layout-label show optional',
+};
+
 export const LayoutSlotErrorMessageLabel = () => <AssistComponent />;
 
 LayoutSlotErrorMessageLabel.story = {

+ 1 - 0
packages/semi-ui/form/hoc/withField.tsx

@@ -110,6 +110,7 @@ function withField<
             }
         } catch (err) {}
 
+        // FIXME typeof initVal
         const [value, setValue, getVal] = useStateWithGetter(typeof initVal !== undefined ? initVal : null);
         const validateOnMount = trigger.includes('mount');
 

+ 21 - 7
packages/semi-ui/form/label.tsx

@@ -2,26 +2,29 @@ import React, { PureComponent } from 'react';
 import classNames from 'classnames';
 import PropTypes from 'prop-types';
 import { cssClasses } from '@douyinfe/semi-foundation/form/constants';
+import LocaleConsumer from '../locale/localeConsumer';
+import { Locale } from '../locale/interface';
 
 const prefixCls = cssClasses.PREFIX;
 
 export interface LabelProps {
+    /** text-align of label */
+    align?: string;
+    className?: string;
+    children?: React.ReactNode;
+    disabled?: boolean;
     id?: string;
     /** Whether to display the required * symbol */
     required?: boolean;
     /** Content of label */
     text?: React.ReactNode;
-    disabled?: boolean;
     /** Used to configure the htmlFor attribute of the label tag */
     name?: string;
-    /** text-align of label */
-    align?: string;
     /** width of label */
     width?: number | string;
     style?: React.CSSProperties;
-    className?: string;
-    children?: React.ReactNode;
     extra?: React.ReactNode;
+    optional?: boolean;
 }
 
 export default class Label extends PureComponent<LabelProps> {
@@ -29,7 +32,8 @@ export default class Label extends PureComponent<LabelProps> {
         required: false,
         name: '',
         align: 'left',
-        className: ''
+        className: '',
+        optional: false,
     };
 
     static propTypes = {
@@ -44,10 +48,11 @@ export default class Label extends PureComponent<LabelProps> {
         style: PropTypes.object,
         className: PropTypes.string,
         extra: PropTypes.node,
+        optional: PropTypes.bool,
     };
 
     render() {
-        const { children, required, text, disabled, name, width, align, style, className, extra, id } = this.props;
+        const { children, required, text, disabled, name, width, align, style, className, extra, id, optional } = this.props;
 
         const labelCls = classNames(className, {
             [`${prefixCls}-field-label`]: true,
@@ -60,9 +65,18 @@ export default class Label extends PureComponent<LabelProps> {
         const labelStyle = style ? style : {};
         width ? labelStyle.width = width : null;
 
+        const optionalText = (
+            <LocaleConsumer<Locale['Form']> componentName="Form" >
+                {(locale: Locale['Form']) => (
+                    <span className={`${prefixCls}-field-label-optional-text`}>{locale.optional}</span>
+                )}
+            </LocaleConsumer>
+        );
+
         const textContent = (
             <div className={`${prefixCls}-field-label-text`} x-semi-prop="label">
                 {typeof text !== 'undefined' ? text : children}
+                {optional ? optionalText : null}
             </div>
         );
 

+ 3 - 0
packages/semi-ui/locale/interface.ts

@@ -149,4 +149,7 @@ export interface Locale {
         total: string;
         selected: string;
     };
+    Form: {
+        optional: string;
+    }
 }

+ 3 - 0
packages/semi-ui/locale/source/ar.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'مجموع ${total} العناصر',
         selected: '${total} العناصر المحدد',
     },
+    Form: {
+        optional: '(اختياري)',
+    },
 };
 
 // [i18n-Arabic]

+ 3 - 0
packages/semi-ui/locale/source/de.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Gesamt ${total} Artikel',
         selected: '${total} ausgewählte Artikel',
     },
+    Form: {
+        optional: '(Optional)',
+    },
 };
 
 // [i18n-German]

+ 3 - 0
packages/semi-ui/locale/source/en_GB.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Total ${total} items',
         selected: '${total} items selected',
     },
+    Form: {
+        optional: '(optional)',
+    },
 };
 
 // [i18n-English(GB)]

+ 3 - 0
packages/semi-ui/locale/source/en_US.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Total ${total} items',
         selected: '${total} items selected',
     },
+    Form: {
+        optional: '(optional)',
+    },
 };
 
 // [i18n-English(US)]

+ 3 - 0
packages/semi-ui/locale/source/es.ts

@@ -155,6 +155,9 @@ const locale: Locale = {
         total: 'Total ${total} objetos',
         selected: '${total} objetos seleccionados',
     },
+    Form: {
+        optional: '(opcional)',
+    },
 };
 
 export default locale;

+ 3 - 0
packages/semi-ui/locale/source/fr.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Totale ${total} articles',
         selected: '${total} articles sélectionnés',
     },
+    Form: {
+        optional: '(optionnel)',
+    },
 };
 
 // [i18n-French]

+ 3 - 0
packages/semi-ui/locale/source/id_ID.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Total ${total} proyek',
         selected: '${total} item dipilih',
     },
+    Form: {
+        optional: '(opsional)',
+    },
 };
 
 // [i18n-Indonesia(ID)]

+ 3 - 0
packages/semi-ui/locale/source/it.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Totale ${total} elementi',
         selected: '${total} elementi selezionati',
     },
+    Form: {
+        optional: '(opzionale)',
+    },
 };
 
 // [i18n-Italian]

+ 3 - 0
packages/semi-ui/locale/source/ja_JP.ts

@@ -151,6 +151,9 @@ const local: Locale = {
         total: '合計 ${total} アイテム',
         selected: '選択済み ${total} アイテム',
     },
+    Form: {
+        optional: '(オプション)',
+    },
 };
 
 // [i18n-Japan]

+ 3 - 0
packages/semi-ui/locale/source/ko_KR.ts

@@ -151,6 +151,9 @@ const local: Locale = {
         total: '총 {total} 개 항목',
         selected: '선택된 {Total} 개 항목',
     },
+    Form: {
+        optional: '(선택 과목)',
+    },
 };
 
 // [i18n-Korea]

+ 3 - 0
packages/semi-ui/locale/source/ms_MY.ts

@@ -150,6 +150,9 @@ const local: Locale = {
         total: 'Jumlah ${total} item',
         selected: '${total} projek dipilih',
     },
+    Form: {
+        optional: '(pilihan)',
+    },
 };
 
 // [i18n-Malaysia(MY)]

+ 3 - 0
packages/semi-ui/locale/source/pt_BR.ts

@@ -158,6 +158,9 @@ const local: Locale = {
         total: 'Total de ${total} itens',
         selected: '${total} itens selecionados',
     },
+    Form: {
+        optional: '(opcional)',
+    },
 };
 
 // 葡萄牙语

+ 3 - 0
packages/semi-ui/locale/source/ru_RU.ts

@@ -153,6 +153,9 @@ const local: Locale = {
         total: 'Всего ${total} элементов',
         selected: 'Выбрано ${total} элементов',
     },
+    Form: {
+        optional: '(по желанию)',
+    },
 };
 
 // [i18n-Russia] 俄罗斯语

+ 3 - 0
packages/semi-ui/locale/source/th_TH.ts

@@ -154,6 +154,9 @@ const local: Locale = {
         total: 'รวม ${total} รายการ',
         selected: 'เลือก ${total} รายการ',
     },
+    Form: {
+        optional: '(ไม่จำเป็น)',
+    },
 };
 
 // [i18n-Thai]

+ 4 - 1
packages/semi-ui/locale/source/tr_TR.ts

@@ -149,7 +149,10 @@ const local: Locale = {
         clearSelectAll: 'Tümünün seçimini kaldır',
         total: 'Toplam ${total} öğe',
         selected: '${total} öğe seçildi'
-    }
+    },
+    Form: {
+        optional: '(isteğe bağlı)',
+    },
 };
 
 // [i18n-Turkish] 

+ 3 - 0
packages/semi-ui/locale/source/vi_VN.ts

@@ -153,6 +153,9 @@ const local: Locale = {
         total: 'Tổng số ${total} mặt hàng',
         selected: '${total} mục được chọn',
     },
+    Form: {
+        optional: '(không bắt buộc)',
+    },
 };
 
 // [i18n-Vietnam] 越南语

+ 3 - 0
packages/semi-ui/locale/source/zh_CN.ts

@@ -151,6 +151,9 @@ const local: Locale = {
         total: '共 ${total} 项',
         selected: '已选 ${total} 项',
     },
+    Form: {
+        optional: '(可选)',
+    },
 };
 
 // 中文

+ 3 - 0
packages/semi-ui/locale/source/zh_TW.ts

@@ -151,6 +151,9 @@ const local: Locale = {
         total: '共 ${total} 項',
         selected: '已選 ${total} 項',
     },
+    Form: {
+        optional: '(可選)',
+    },
 };
 
 // 中文