ソースを参照

Feat/x prop 2 (#825)

* feat: 18 components add x-prop

* feat: other components support x-semi-prop

* feat(modal): okText and cancelText add x-prop

Co-authored-by: shijia.me <[email protected]>
走鹃 3 年 前
コミット
dab60c502b
52 ファイル変更616 行追加348 行削除
  1. 2 2
      packages/semi-ui/avatar/index.tsx
  2. 11 7
      packages/semi-ui/backtop/index.tsx
  3. 1 1
      packages/semi-ui/badge/index.tsx
  4. 5 5
      packages/semi-ui/banner/index.tsx
  5. 5 3
      packages/semi-ui/breadcrumb/index.tsx
  6. 10 8
      packages/semi-ui/button/Button.tsx
  7. 43 41
      packages/semi-ui/card/index.tsx
  8. 2 0
      packages/semi-ui/carousel/CarouselArrow.tsx
  9. 1 0
      packages/semi-ui/carousel/index.tsx
  10. 98 120
      packages/semi-ui/cascader/index.tsx
  11. 1 1
      packages/semi-ui/cascader/item.tsx
  12. 13 2
      packages/semi-ui/checkbox/checkbox.tsx
  13. 8 1
      packages/semi-ui/collapsible/index.tsx
  14. 1 0
      packages/semi-ui/datePicker/dateInput.tsx
  15. 13 5
      packages/semi-ui/datePicker/datePicker.tsx
  16. 8 4
      packages/semi-ui/divider/index.tsx
  17. 1 1
      packages/semi-ui/dropdown/index.tsx
  18. 13 5
      packages/semi-ui/empty/index.tsx
  19. 1 1
      packages/semi-ui/form/hoc/withField.tsx
  20. 1 1
      packages/semi-ui/form/label.tsx
  21. 1 1
      packages/semi-ui/grid/col.tsx
  22. 1 1
      packages/semi-ui/grid/row.tsx
  23. 2 1
      packages/semi-ui/iconButton/index.tsx
  24. 38 11
      packages/semi-ui/input/index.tsx
  25. 16 4
      packages/semi-ui/list/index.tsx
  26. 1 1
      packages/semi-ui/modal/ConfirmModal.tsx
  27. 2 0
      packages/semi-ui/modal/Modal.tsx
  28. 27 14
      packages/semi-ui/modal/ModalContent.tsx
  29. 16 4
      packages/semi-ui/notification/notice.tsx
  30. 16 2
      packages/semi-ui/pagination/index.tsx
  31. 11 3
      packages/semi-ui/popconfirm/index.tsx
  32. 10 2
      packages/semi-ui/radio/radio.tsx
  33. 1 1
      packages/semi-ui/rating/item.tsx
  34. 19 3
      packages/semi-ui/scrollList/index.tsx
  35. 12 4
      packages/semi-ui/select/index.tsx
  36. 5 1
      packages/semi-ui/select/option.tsx
  37. 3 3
      packages/semi-ui/sideSheet/SideSheetContent.tsx
  38. 1 1
      packages/semi-ui/skeleton/index.tsx
  39. 1 1
      packages/semi-ui/space/index.tsx
  40. 15 9
      packages/semi-ui/spin/index.tsx
  41. 9 14
      packages/semi-ui/switch/index.tsx
  42. 5 3
      packages/semi-ui/table/Table.tsx
  43. 1 1
      packages/semi-ui/tabs/TabBar.tsx
  44. 9 4
      packages/semi-ui/tabs/TabPane.tsx
  45. 22 4
      packages/semi-ui/tagInput/index.tsx
  46. 6 1
      packages/semi-ui/timePicker/Combobox.tsx
  47. 3 3
      packages/semi-ui/toast/toast.tsx
  48. 1 0
      packages/semi-ui/transfer/index.tsx
  49. 1 1
      packages/semi-ui/tree/treeNode.tsx
  50. 15 3
      packages/semi-ui/treeSelect/index.tsx
  51. 1 1
      packages/semi-ui/typography/base.tsx
  52. 107 38
      packages/semi-ui/upload/index.tsx

+ 2 - 2
packages/semi-ui/avatar/index.tsx

@@ -135,7 +135,7 @@ export default class Avatar extends BaseComponent<AvatarProps, AvatarState> {
             className
         );
         let content = children;
-        const hoverRender = hoverContent ? (<div className={`${prefixCls}-hover`}>{hoverContent}</div>) : null;
+        const hoverRender = hoverContent ? (<div className={`${prefixCls}-hover`} x-semi-prop="hoverContent">{hoverContent}</div>) : null;
         if (isImg) {
             content = (
                 <img src={src} srcSet={srcSet} onError={this.handleError} alt={alt} {...imgAttr} />
@@ -143,7 +143,7 @@ export default class Avatar extends BaseComponent<AvatarProps, AvatarState> {
         } else if (typeof children === 'string') {
             content = (
                 <span className={`${prefixCls}-content`}>
-                    <span className={`${prefixCls}-label`}>{children}</span>
+                    <span className={`${prefixCls}-label`} x-semi-prop="children">{children}</span>
                 </span>
             );
         }

+ 11 - 7
packages/semi-ui/backtop/index.tsx

@@ -101,13 +101,17 @@ export default class BackTop extends BaseComponent<BackTopProps, BackTopState> {
             className
         );
         const backtopBtn = children ? children : this.renderDefault();
-        const content = visible ?
-            (
-                <div {...others} className={preCls} style={style} onClick={e => this.handleClick(e)}>
-                    {backtopBtn}
-                </div>
-            ) :
-            null;
+        const content = visible ? (
+            <div
+                {...others} 
+                className={preCls} 
+                style={style} 
+                onClick={e => this.handleClick(e)} 
+                x-semi-prop="children"
+            >
+                {backtopBtn}
+            </div>
+        ) : null;
         return content;
     }
 }

+ 1 - 1
packages/semi-ui/badge/index.tsx

@@ -82,7 +82,7 @@ export default class Badge extends PureComponent<BadgeProps> {
         return (
             <span className={prefixCls} {...rest}>
                 {children}
-                <span className={wrapper} style={style}>
+                <span className={wrapper} style={style} x-semi-prop="count">
                     {dot ? null : content}
                 </span>
             </span>

+ 5 - 5
packages/semi-ui/banner/index.tsx

@@ -109,7 +109,7 @@ export default class Banner extends BaseComponent<BannerProps, BannerState> {
             <Button
                 className={`${prefixCls}-close`}
                 onClick={this.remove}
-                icon={closeIcon || <IconClose aria-hidden={true}/>}
+                icon={closeIcon || <IconClose x-semi-prop="closeIcon" aria-hidden={true}/>}
                 theme="borderless"
                 size="small"
                 type="tertiary"
@@ -137,7 +137,7 @@ export default class Banner extends BaseComponent<BannerProps, BannerState> {
         }
         if (iconType) {
             return (
-                <div className={iconCls}>
+                <div className={iconCls} x-semi-prop="icon">
                     {iconType}
                 </div>
             );
@@ -160,13 +160,13 @@ export default class Banner extends BaseComponent<BannerProps, BannerState> {
                     <div className={`${prefixCls}-content`}>
                         {this.renderIcon()}
                         <div className={`${prefixCls}-content-body`}>
-                            {title ? <Typography.Title heading={5} className={`${prefixCls}-title`} component="div">{title}</Typography.Title> : null}
-                            {description ? <Typography.Paragraph className={`${prefixCls}-description`} component="div">{description}</Typography.Paragraph> : null}
+                            {title ? <Typography.Title heading={5} className={`${prefixCls}-title`} component="div" x-semi-prop="title">{title}</Typography.Title> : null}
+                            {description ? <Typography.Paragraph className={`${prefixCls}-description`} component="div" x-semi-prop="description">{description}</Typography.Paragraph> : null}
                         </div>
                     </div>
                     {this.renderCloser()}
                 </div>
-                {children ? <div className={`${prefixCls}-extra`}>{children}</div> : null}
+                {children ? <div className={`${prefixCls}-extra`} x-semi-prop="children">{children}</div> : null}
             </div>
         ) : null;
         return banner;

+ 5 - 3
packages/semi-ui/breadcrumb/index.tsx

@@ -167,9 +167,9 @@ class Breadcrumb extends BaseComponent<BreadcrumbProps, BreadcrumbState> {
             <span className={`${clsPrefix}-collapse`} key={`more-${itemsLen}`}>
                 <span className={`${clsPrefix}-item-wrap`}>
                     <span
-                        role='button'
+                        role="button"
                         tabIndex={0}
-                        aria-label='Expand breadcrumb items'
+                        aria-label="Expand breadcrumb items"
                         className={`${clsPrefix}-item ${clsPrefix}-item-more`}
                         onClick={item => this.foundation.handleExpand(item)}
                         onKeyPress={e => this.foundation.handleExpandEnterPress(e)}
@@ -178,7 +178,9 @@ class Breadcrumb extends BaseComponent<BreadcrumbProps, BreadcrumbState> {
                         {!hasRenderMore && moreType === 'default' && <IconMore />}
                         {!hasRenderMore && moreType === 'popover' && this.renderPopoverMore(restItem)}
                     </span>
-                    <span className={`${clsPrefix}-separator`}>{this.props.separator}</span>
+                    <span className={`${clsPrefix}-separator`} x-semi-prop="separator">
+                        {this.props.separator}
+                    </span>
                 </span>
             </span>
         );

+ 10 - 8
packages/semi-ui/button/Button.tsx

@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/button/constants';
 import '@douyinfe/semi-foundation/button/button.scss';
 import { noop } from '@douyinfe/semi-foundation/utils/function';
+import { omit } from 'lodash';
 
 const btnSizes = strings.sizes;
 const { htmlTypes, btnTypes } = strings;
@@ -93,7 +94,7 @@ export default class Button extends PureComponent<ButtonProps> {
 
         const baseProps = {
             disabled,
-            ...attr,
+            ...omit(attr, ['x-semi-children-alias']),
             className: classNames(
                 prefixCls,
                 {
@@ -113,16 +114,17 @@ export default class Button extends PureComponent<ButtonProps> {
             'aria-disabled': disabled,
         };
 
+        const xSemiProps = {};
+
+        if (!(className && className.includes('-with-icon'))) {
+            xSemiProps['x-semi-prop'] = this.props['x-semi-children-alias'] || 'children';
+        }
+
         return (
             // eslint-disable-next-line react/button-has-type
-            <button
-                {...baseProps}
-                onClick={this.props.onClick}
-                onMouseDown={this.props.onMouseDown}
-                style={style}
-            >
+            <button {...baseProps} onClick={this.props.onClick} onMouseDown={this.props.onMouseDown} style={style}>
                 {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-                <span className={`${prefixCls}-content`} onClick={e => disabled && e.stopPropagation()}>
+                <span className={`${prefixCls}-content`} onClick={e => disabled && e.stopPropagation()} {...xSemiProps}>
                     {children}
                 </span>
             </button>

+ 43 - 41
packages/semi-ui/card/index.tsx

@@ -55,7 +55,6 @@ export interface CardProps {
     'aria-label'?: string;
 }
 
-
 class Card extends PureComponent<CardProps> {
     static Meta = Meta;
 
@@ -106,37 +105,33 @@ class Card extends PureComponent<CardProps> {
         if (header || headerExtraContent || title) {
             return (
                 <div style={headerStyle} className={headerCls}>
-                    {
-                        header || ( // Priority of header over title and headerExtraContent
-                            <div className={headerWrapperCls}>
-                                {headerExtraContent &&
-                                    (
-                                        <div className={`${prefixcls}-header-wrapper-extra`}>
-                                            {headerExtraContent}
-                                        </div>
-                                    )
-                                }
-                                {title &&
-                                    (
-                                        <div className={titleCls}>
-                                            {
-                                                isString(title) ?
-                                                    (
-                                                        <Typography.Title
-                                                            heading={6}
-                                                            ellipsis={{ showTooltip: true, rows: 1 }}
-                                                        >
-                                                            {title}
-                                                        </Typography.Title>
-                                                    ) :
-                                                    title
-                                            }
-                                        </div>
-                                    )
-                                }
-                            </div>
-                        )
-                    }
+                    {header || ( // Priority of header over title and headerExtraContent
+                        <div className={headerWrapperCls}>
+                            {headerExtraContent && (
+                                <div
+                                    className={`${prefixcls}-header-wrapper-extra`}
+                                    x-semi-prop="headerExtraContent"
+                                >
+                                    {headerExtraContent}
+                                </div>
+                            )}
+                            {title && (
+                                <div className={titleCls}>
+                                    {isString(title) ? (
+                                        <Typography.Title
+                                            heading={6}
+                                            ellipsis={{ showTooltip: true, rows: 1 }}
+                                            x-semi-prop="title"
+                                        >
+                                            {title}
+                                        </Typography.Title>
+                                    ) : (
+                                        title
+                                    )}
+                                </div>
+                            )}
+                        </div>
+                    )}
                 </div>
             );
         }
@@ -149,16 +144,17 @@ class Card extends PureComponent<CardProps> {
         } = this.props;
         const coverCls = cls(`${prefixcls}-cover`);
 
-        return cover && <div className={coverCls}>{cover}</div>;
+        return (
+            cover && (
+                <div className={coverCls} x-semi-prop="cover">
+                    {cover}
+                </div>
+            )
+        );
     };
 
     renderBody = (): ReactNode => {
-        const {
-            bodyStyle,
-            children,
-            actions,
-            loading
-        } = this.props;
+        const { bodyStyle, children, actions, loading } = this.props;
         const bodyCls = cls(`${prefixcls}-body`);
         const actionsCls = cls(`${prefixcls}-body-actions`);
         const actionsItemCls = cls(`${prefixcls}-body-actions-item`);
@@ -182,7 +178,7 @@ class Card extends PureComponent<CardProps> {
                         <div className={actionsCls}>
                             <Space spacing={12}>
                                 {actions.map((item, idx) => (
-                                    <div key={idx} className={actionsItemCls}>{item}</div>
+                                    <div key={idx} className={actionsItemCls} x-semi-prop={`actions.${idx}`}>{item}</div>
                                 ))}
                             </Space>
                         </div>
@@ -202,7 +198,13 @@ class Card extends PureComponent<CardProps> {
             [`${prefixcls}-footer-bordered`]: footerLine
         });
 
-        return footer && <div style={footerStyle} className={footerCls}>{footer}</div>;
+        return (
+            footer && (
+                <div style={footerStyle} className={footerCls} x-semi-prop="footer">
+                    {footer}
+                </div>
+            )
+        );
     };
 
     render(): ReactNode {

+ 2 - 0
packages/semi-ui/carousel/CarouselArrow.tsx

@@ -41,6 +41,7 @@ class CarouselArrow extends React.PureComponent<CarouselArrowProps> {
                     className={leftClassNames} 
                     onClick={prev}
                     {...get(this.props, 'arrowProps.leftArrow.props')}
+                    x-semi-prop="arrowProps.leftArrow.children"
                 >
                     {this.renderLeftIcon()}
                 </div>
@@ -50,6 +51,7 @@ class CarouselArrow extends React.PureComponent<CarouselArrowProps> {
                     className={rightClassNames} 
                     onClick={next}
                     {...get(this.props, 'arrowProps.rightArrow.props')}
+                    x-semi-prop="arrowProps.rightArrow.children"
                 >
                     {this.renderRightIcon()}
                 </div>

+ 1 - 0
packages/semi-ui/carousel/index.tsx

@@ -279,6 +279,7 @@ class Carousel extends BaseComponent<CarouselProps, CarouselState> {
                         [`${cssClasses.CAROUSEL_CONTENT}`]: true,
                         [`${cssClasses.CAROUSEL_CONTENT}-reverse`]: slideDirection === 'left' ? isReverse : !isReverse,
                     })}
+                    x-semi-prop="children"
                 >
                     {this.renderChildren()}
                 </div>

+ 98 - 120
packages/semi-ui/cascader/index.tsx

@@ -122,7 +122,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             PropTypes.shape({
                 value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
                 label: PropTypes.any,
-            }),
+            })
         ),
         treeNodeFilterProp: PropTypes.string,
         suffix: PropTypes.node,
@@ -199,7 +199,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         onDropdownVisibleChange: noop,
         onListScroll: noop,
         enableLeafClick: false,
-        'aria-label': 'Cascader'
+        'aria-label': 'Cascader',
     };
 
     options: any;
@@ -247,7 +247,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             /* Keys of loading item */
             loadingKeys: new Set(),
             /* Mark whether this rendering has triggered asynchronous loading of data */
-            loading: false
+            loading: false,
         };
         this.options = {};
         this.isEmpty = false;
@@ -274,10 +274,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 }
             },
         };
-        const cascaderAdapter: Pick<CascaderAdapter,
-        'registerClickOutsideHandler'
-        | 'unregisterClickOutsideHandler'
-        | 'rePositionDropdown'
+        const cascaderAdapter: Pick<
+            CascaderAdapter,
+            'registerClickOutsideHandler' | 'unregisterClickOutsideHandler' | 'rePositionDropdown'
         > = {
             registerClickOutsideHandler: cb => {
                 const clickOutsideHandler = (e: Event) => {
@@ -368,17 +367,10 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     }
 
     static getDerivedStateFromProps(props: CascaderProps, prevState: CascaderState) {
-        const {
-            multiple,
-            value,
-            defaultValue,
-            onChangeWithObject,
-            leafOnly,
-            autoMergeValue,
-        } = props;
+        const { multiple, value, defaultValue, onChangeWithObject, leafOnly, autoMergeValue } = props;
         const { prevProps } = prevState;
         let keyEntities = prevState.keyEntities || {};
-        const newState: Partial<CascaderState> = { };
+        const newState: Partial<CascaderState> = {};
         const needUpdate = (name: string) => {
             const firstInProps = isEmpty(prevProps) && name in props;
             const nameHasChange = prevProps && !isEqual(prevProps[name], props[name]);
@@ -408,9 +400,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                     const realValue = needUpdate('value') ? value : defaultValue;
                     // eslint-disable-next-line max-depth
                     if (Array.isArray(realValue)) {
-                        normallizedValue = Array.isArray(realValue[0]) ?
-                            realValue as SimpleValueType[][] :
-                            [realValue] as SimpleValueType[][];
+                        normallizedValue = Array.isArray(realValue[0])
+                            ? (realValue as SimpleValueType[][])
+                            : ([realValue] as SimpleValueType[][]);
                     } else {
                         if (realValue !==  undefined) {
                             normallizedValue = [[realValue]];
@@ -482,12 +474,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     renderTagItem = (value: string | Array<string>, idx: number, type: string) => {
         const { keyEntities, disabledKeys } = this.state;
         const { size, disabled, displayProp, displayRender, disableStrictly } = this.props;
-        const nodeKey = type === strings.IS_VALUE ?
-            findKeysForValues(value, keyEntities)[0] :
-            value;
-        const isDsiabled = disabled ||
-            keyEntities[nodeKey].data.disabled ||
-            (disableStrictly && disabledKeys.has(nodeKey));
+        const nodeKey = type === strings.IS_VALUE ? findKeysForValues(value, keyEntities)[0] : value;
+        const isDsiabled =
+            disabled || keyEntities[nodeKey].data.disabled || (disableStrictly && disabledKeys.has(nodeKey));
         if (!isEmpty(keyEntities) && !isEmpty(keyEntities[nodeKey])) {
             const tagCls = cls(`${prefixcls}-selection-tag`, {
                 [`${prefixcls}-selection-tag-disabled`]: isDsiabled,
@@ -495,7 +484,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             // custom render tags
             if (isFunction(displayRender)) {
                 return displayRender(keyEntities[nodeKey], idx);
-            // default render tags
+                // default render tags
             } else {
                 return (
                     <Tag
@@ -519,25 +508,11 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     };
 
     renderTagInput() {
-        const {
-            size,
-            disabled,
-            placeholder,
-            maxTagCount,
-            showRestTagsPopover,
-            restTagsPopoverProps,
-        } = this.props;
-        const {
-            inputValue,
-            checkedKeys,
-            keyEntities,
-            resolvedCheckedKeys
-        } = this.state;
+        const { size, disabled, placeholder, maxTagCount, showRestTagsPopover, restTagsPopoverProps } = this.props;
+        const { inputValue, checkedKeys, keyEntities, resolvedCheckedKeys } = this.state;
         const tagInputcls = cls(`${prefixcls}-tagInput-wrapper`);
         const tagValue: Array<Array<string>> = [];
-        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE
-            ? checkedKeys
-            : resolvedCheckedKeys;
+        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE ? checkedKeys : resolvedCheckedKeys;
         [...realKeys].forEach(checkedKey => {
             if (!isEmpty(keyEntities[checkedKey])) {
                 tagValue.push(keyEntities[checkedKey].valuePath);
@@ -550,7 +525,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 disabled={disabled}
                 size={size}
                 // TODO Modify logic, not modify type
-                value={tagValue as unknown as string[]}
+                value={(tagValue as unknown) as string[]}
                 showRestTagsPopover={showRestTagsPopover}
                 restTagsPopoverProps={restTagsPopoverProps}
                 maxTagCount={maxTagCount}
@@ -558,7 +533,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 inputValue={inputValue}
                 onInputChange={this.handleInputChange}
                 // TODO Modify logic, not modify type
-                onRemove={v => this.handleTagRemove(null, v as unknown as (string | number)[])}
+                onRemove={v => this.handleTagRemove(null, (v as unknown) as (string | number)[])}
                 placeholder={placeholder}
             />
         );
@@ -580,11 +555,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         });
         return (
             <div className={wrappercls}>
-                <Input
-                    ref={this.inputRef as any}
-                    size={size}
-                    {...inputProps}
-                />
+                <Input ref={this.inputRef as any} size={size} {...inputProps} />
             </div>
         );
     }
@@ -614,7 +585,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             checkedKeys,
             halfCheckedKeys,
             loadedKeys,
-            loadingKeys
+            loadingKeys,
         } = this.state;
         const {
             filterTreeNode,
@@ -626,7 +597,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             topSlot,
             bottomSlot,
             showNext,
-            multiple
+            multiple,
         } = this.props;
         const searchable = Boolean(filterTreeNode) && isSearching;
         const popoverCls = cls(dropdownClassName, `${prefixcls}-popover`);
@@ -663,37 +634,29 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     renderPlusN = (hiddenTag: Array<ReactNode>) => {
         const { disabled, showRestTagsPopover, restTagsPopoverProps } = this.props;
         const plusNCls = cls(`${prefixcls}-selection-n`, {
-            [`${prefixcls}-selection-n-disabled`]: disabled
+            [`${prefixcls}-selection-n-disabled`]: disabled,
         });
-        const renderPlusNChildren = (
-            <span className={plusNCls}>
-                +{hiddenTag.length}
-            </span>
-        );
-        return (
-            showRestTagsPopover && !disabled ?
-                (
-                    <Popover
-                        content={hiddenTag}
-                        showArrow
-                        trigger="hover"
-                        position="top"
-                        autoAdjustOverflow
-                        {...restTagsPopoverProps}
-                    >
-                        {renderPlusNChildren}
-                    </Popover>
-                ) :
-                renderPlusNChildren
+        const renderPlusNChildren = <span className={plusNCls}>+{hiddenTag.length}</span>;
+        return showRestTagsPopover && !disabled ? (
+            <Popover
+                content={hiddenTag}
+                showArrow
+                trigger="hover"
+                position="top"
+                autoAdjustOverflow
+                {...restTagsPopoverProps}
+            >
+                {renderPlusNChildren}
+            </Popover>
+        ) : (
+            renderPlusNChildren
         );
     };
 
     renderMultipleTags = () => {
         const { autoMergeValue, maxTagCount } = this.props;
         const { checkedKeys, resolvedCheckedKeys } = this.state;
-        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE
-            ? checkedKeys
-            : resolvedCheckedKeys;
+        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE ? checkedKeys : resolvedCheckedKeys;
         const displayTag: Array<ReactNode> = [];
         const hiddenTag: Array<ReactNode> = [];
         [...realKeys].forEach((checkedKey, idx) => {
@@ -722,19 +685,22 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             if (displayRender && typeof displayRender === 'function') {
                 displayText = displayRender(displayPath);
             } else {
-                displayText = displayPath.map((path: ReactNode, index: number)=>(
+                displayText = displayPath.map((path: ReactNode, index: number) => (
                     <Fragment key={`${path}-${index}`}>
-                        {
-                            index<displayPath.length-1
-                                ? <>{path}{separator}</>
-                                : path
-                        }
+                        {index < displayPath.length - 1 ? (
+                            <>
+                                {path}
+                                {separator}
+                            </>
+                        ) : (
+                            path
+                        )}
                     </Fragment>
                 ));
             }
         }
         return displayText;
-    }
+    };
 
     renderSelectContent = () => {
         const { placeholder, filterTreeNode, multiple } = this.props;
@@ -765,7 +731,11 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             [`${prefixcls}-suffix-text`]: suffix && isString(suffix),
             [`${prefixcls}-suffix-icon`]: isSemiIcon(suffix),
         });
-        return <div className={suffixWrapperCls}>{suffix}</div>;
+        return (
+            <div className={suffixWrapperCls} x-semi-prop="suffix">
+                {suffix}
+            </div>
+        );
     };
 
     renderPrefix = () => {
@@ -780,7 +750,11 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             [`${prefixcls}-prefix-icon`]: isSemiIcon(labelNode),
         });
 
-        return <div className={prefixWrapperCls} id={insetLabelId}>{labelNode}</div>;
+        return (
+            <div className={prefixWrapperCls} id={insetLabelId} x-semi-prop="prefix,insetLabel">
+                {labelNode}
+            </div>
+        );
     };
 
     renderCustomTrigger = () => {
@@ -850,7 +824,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                     className={clearCls}
                     onClick={this.handleClear}
                     onKeyPress={this.handleClearEnterPress}
-                    role='button'
+                    role="button"
                     tabIndex={0}
                 >
                     <IconClear />
@@ -866,7 +840,11 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         if (showClearBtn) {
             return null;
         }
-        return arrowIcon ? <div className={cls(`${prefixcls}-arrow`)}>{arrowIcon}</div> : null;
+        return arrowIcon ? (
+            <div className={cls(`${prefixcls}-arrow`)} x-semi-prop="arrowIcon">
+                {arrowIcon}
+            </div>
+        ) : null;
     };
 
     renderSelection = () => {
@@ -888,40 +866,40 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         const { isOpen, isFocus, isInput, checkedKeys } = this.state;
         const filterable = Boolean(filterTreeNode);
         const useCustomTrigger = typeof triggerRender === 'function';
-        const classNames = useCustomTrigger ?
-            cls(className) :
-            cls(prefixcls, className, {
-                [`${prefixcls}-focus`]: isFocus || (isOpen && !isInput),
-                [`${prefixcls}-disabled`]: disabled,
-                [`${prefixcls}-single`]: true,
-                [`${prefixcls}-filterable`]: filterable,
-                [`${prefixcls}-error`]: validateStatus === 'error',
-                [`${prefixcls}-warning`]: validateStatus === 'warning',
-                [`${prefixcls}-small`]: size === 'small',
-                [`${prefixcls}-large`]: size === 'large',
-                [`${prefixcls}-with-prefix`]: prefix || insetLabel,
-                [`${prefixcls}-with-suffix`]: suffix,
-            });
-        const mouseEvent = showClear ?
-            {
-                onMouseEnter: () => this.handleMouseOver(),
-                onMouseLeave: () => this.handleMouseLeave(),
-            } :
-            {};
+        const classNames = useCustomTrigger
+            ? cls(className)
+            : cls(prefixcls, className, {
+                  [`${prefixcls}-focus`]: isFocus || (isOpen && !isInput),
+                  [`${prefixcls}-disabled`]: disabled,
+                  [`${prefixcls}-single`]: true,
+                  [`${prefixcls}-filterable`]: filterable,
+                  [`${prefixcls}-error`]: validateStatus === 'error',
+                  [`${prefixcls}-warning`]: validateStatus === 'warning',
+                  [`${prefixcls}-small`]: size === 'small',
+                  [`${prefixcls}-large`]: size === 'large',
+                  [`${prefixcls}-with-prefix`]: prefix || insetLabel,
+                  [`${prefixcls}-with-suffix`]: suffix,
+              });
+        const mouseEvent = showClear
+            ? {
+                  onMouseEnter: () => this.handleMouseOver(),
+                  onMouseLeave: () => this.handleMouseLeave(),
+              }
+            : {};
         const sectionCls = cls(`${prefixcls}-selection`, {
             [`${prefixcls}-selection-multiple`]: multiple && !isEmpty(checkedKeys),
         });
-        const inner = useCustomTrigger ?
-            this.renderCustomTrigger() :
-            [
-                <Fragment key={'prefix'}>{prefix || insetLabel ? this.renderPrefix() : null}</Fragment>,
-                <Fragment key={'selection'}>
-                    <div className={sectionCls}>{this.renderSelectContent()}</div>
-                </Fragment>,
-                <Fragment key={'clearbtn'}>{this.renderClearBtn()}</Fragment>,
-                <Fragment key={'suffix'}>{suffix ? this.renderSuffix() : null}</Fragment>,
-                <Fragment key={'arrow'}>{this.renderArrow()}</Fragment>,
-            ];
+        const inner = useCustomTrigger
+            ? this.renderCustomTrigger()
+            : [
+                  <Fragment key={'prefix'}>{prefix || insetLabel ? this.renderPrefix() : null}</Fragment>,
+                  <Fragment key={'selection'}>
+                      <div className={sectionCls}>{this.renderSelectContent()}</div>
+                  </Fragment>,
+                  <Fragment key={'clearbtn'}>{this.renderClearBtn()}</Fragment>,
+                  <Fragment key={'suffix'}>{suffix ? this.renderSuffix() : null}</Fragment>,
+                  <Fragment key={'arrow'}>{this.renderArrow()}</Fragment>,
+              ];
         /**
          * Reasons for disabling the a11y eslint rule:
          * The following attributes(aria-controls,aria-expanded) will be automatically added by Tooltip, no need to declare here
@@ -937,12 +915,12 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 aria-errormessage={this.props['aria-errormessage']}
                 aria-label={this.props['aria-label']}
                 aria-labelledby={this.props['aria-labelledby']}
-                aria-describedby={this.props["aria-describedby"]}
+                aria-describedby={this.props['aria-describedby']}
                 aria-required={this.props['aria-required']}
                 id={id}
                 {...mouseEvent}
                 // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
-                role='combobox'
+                role="combobox"
                 tabIndex={0}
             >
                 {inner}

+ 1 - 1
packages/semi-ui/cascader/item.tsx

@@ -291,7 +291,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
             <LocaleConsumer componentName="Cascader">
                 {(locale: Locale['Cascader']) => (
                     <ul className={`${prefixcls} ${prefixcls}-empty`} key={'empty-list'}>
-                        <span className={`${prefixcls}-label`}>
+                        <span className={`${prefixcls}-label`} x-semi-prop="emptyContent">
                             {emptyContent || locale.emptyText}
                         </span>
                     </ul>

+ 13 - 2
packages/semi-ui/checkbox/checkbox.tsx

@@ -220,10 +220,21 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             [`${prefix}-cardType_extra_noChildren`]: props.isCardType && !children,
         });
 
+        const name = inGroup && this.context.checkboxGroup.name;
+        const xSemiPropChildren = this.props['x-semi-children-alias'] || 'children';
+
         const renderContent = () => (
             <>
-                {children ? <span id={addonId} className={`${prefix}-addon`}>{children}</span> : null}
-                {extra ? <div id={extraId} className={extraCls}>{extra}</div> : null}
+                {children ? (
+                    <span id={addonId} className={`${prefix}-addon`} x-semi-prop={xSemiPropChildren}>
+                        {children}
+                    </span>
+                ) : null}
+                {extra ? (
+                    <div id={extraId} className={extraCls} x-semi-prop="extra">
+                        {extra}
+                    </div>
+                ) : null}
             </>
         );
         return (

+ 8 - 1
packages/semi-ui/collapsible/index.tsx

@@ -92,7 +92,14 @@ const Collapsible = (props: CollapsibleProps) => {
         const wrapperCls = cls(`${cssClasses.PREFIX}-wrapper`, className);
         return (
             <div style={wrapperstyle} className={wrapperCls} ref={ref}>
-                <div ref={setHeight} style={{ overflow: 'hidden' }} id={id}>{children}</div>
+                <div
+                    ref={setHeight}
+                    style={{ overflow: 'hidden' }} 
+                    id={id}
+                    x-semi-prop="children"
+                >
+                    {children}
+                </div>
             </div>
         );
     };

+ 1 - 0
packages/semi-ui/datePicker/dateInput.tsx

@@ -172,6 +172,7 @@ export default class DateInput extends BaseComponent<DateInputProps, {}> {
             <div
                 className={`${prefixCls}-range-input-prefix`}
                 onClick={e => !disabled && !rangeInputFocus && this.handleRangeStartFocus(e)}
+                x-semi-prop="prefix,insetLabel"
             >
                 {labelNode}
             </div>

+ 13 - 5
packages/semi-ui/datePicker/datePicker.tsx

@@ -653,13 +653,21 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
 
         return (
             <div ref={this.panelRef} className={wrapCls} style={dropdownStyle}>
-                {topSlot && <div className={`${cssClasses.PREFIX}-topSlot`}>{topSlot}</div>}
+                {topSlot && (
+                    <div className={`${cssClasses.PREFIX}-topSlot`} x-semi-prop="topSlot">
+                        {topSlot}
+                    </div>
+                )}
                 {insetInput && <DateInput {...insetInputProps} insetInput={true} />}
-                {this.adapter.typeIsYearOrMonth() ?
-                    this.renderYearMonthPanel(locale, localeCode) :
-                    this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
+                {this.adapter.typeIsYearOrMonth()
+                    ? this.renderYearMonthPanel(locale, localeCode)
+                    : this.renderMonthGrid(locale, localeCode, dateFnsLocale)}
                 {this.renderQuickControls()}
-                {bottomSlot && <div className={`${cssClasses.PREFIX}-bottomSlot`}>{bottomSlot}</div>}
+                {bottomSlot && (
+                    <div className={`${cssClasses.PREFIX}-bottomSlot`} x-semi-prop="bottomSlot">
+                        {bottomSlot}
+                    </div>
+                )}
                 {this.renderFooter(locale, localeCode)}
             </div>
         );

+ 8 - 4
packages/semi-ui/divider/index.tsx

@@ -60,10 +60,14 @@ const Divider: React.FC<DividerProps> = props => {
 
     return (
         <div {...rest} className={dividerClassNames} style={{ ...overrideDefaultStyle, ...style }}>
-            {(children && layout === 'horizontal') ? (
-                typeof children === 'string' ? <span className={`${prefixCls}-divider_inner-text`}>
-                    {children}
-                </span> : children
+            {children && layout === 'horizontal' ? (
+                typeof children === 'string' ? (
+                    <span className={`${prefixCls}-divider_inner-text`} x-semi-prop="children">
+                        {children}
+                    </span>
+                ) : (
+                    children
+                )
             ) : null}
         </div>
     );

+ 1 - 1
packages/semi-ui/dropdown/index.tsx

@@ -144,7 +144,7 @@ class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
         return (
             <DropdownContext.Provider value={contextValue}>
                 <div className={className} style={style}>
-                    <div className={`${prefixCls}-content`}>{content}</div>
+                    <div className={`${prefixCls}-content`} x-semi-prop="render">{content}</div>
                 </div>
             </DropdownContext.Provider>
         );

+ 13 - 5
packages/semi-ui/empty/index.tsx

@@ -109,17 +109,25 @@ export default class Empty extends BaseComponent<EmptyProps, EmptyState> {
             };
         return (
             <div className={wrapperCls} style={style}>
-                <div className={`${prefixCls}-image`} style={imageStyle} >
+                <div className={`${prefixCls}-image`} style={imageStyle} x-semi-prop="image,darkModeImage">
                     {imageNode}
                 </div>
-                <div className={`${prefixCls}-content`} >
+                <div className={`${prefixCls}-content`}>
                     {title ? (
-                        <Typography.Title {...(titleProps as any)} className={`${prefixCls}-title`} >
+                        <Typography.Title {...(titleProps as any)} className={`${prefixCls}-title`} x-semi-prop="title">
                             {title}
                         </Typography.Title>
                     ) : null}
-                    {description ? <div className={`${prefixCls}-description`} >{description}</div> : null}
-                    {children ? <div className={`${prefixCls}-footer`}>{children}</div> : null}
+                    {description ? (
+                        <div className={`${prefixCls}-description`} x-semi-prop="description">
+                            {description}
+                        </div>
+                    ) : null}
+                    {children ? (
+                        <div className={`${prefixCls}-footer`} x-semi-prop="children">
+                            {children}
+                        </div>
+                    ) : null}
                 </div>
             </div>
         );

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

@@ -439,7 +439,7 @@ function withField<
                 [`${prefix}-field-extra-botttom`]: mergeExtraPos === 'bottom',
             });
 
-            const extraContent = extraText ? <div className={extraCls} id={extraTextId}>{extraText}</div> : null;
+            const extraContent = extraText ? <div className={extraCls} id={extraTextId} x-semi-prop="extraText">{extraText}</div> : null;
 
             let newProps: Record<string, any> = {
                 id: a11yId,

+ 1 - 1
packages/semi-ui/form/label.tsx

@@ -61,7 +61,7 @@ export default class Label extends PureComponent<LabelProps> {
         width ? labelStyle.width = width : null;
 
         const textContent = (
-            <div className={`${prefixCls}-field-label-text`}>
+            <div className={`${prefixCls}-field-label-text`} x-semi-prop="label">
                 {typeof text !== 'undefined' ? text : children}
             </div>
         );

+ 1 - 1
packages/semi-ui/grid/col.tsx

@@ -125,7 +125,7 @@ class Col extends React.Component<ColProps> {
         };
 
         return (
-            <div {...others} style={style} className={classes}>
+            <div {...others} style={style} className={classes} x-semi-prop="children">
                 {children}
             </div>
         );

+ 1 - 1
packages/semi-ui/grid/row.tsx

@@ -167,7 +167,7 @@ class Row extends React.Component<RowProps, RowState> {
                     gutters,
                 }}
             >
-                <div {...otherProps} className={classes} style={rowStyle}>
+                <div {...otherProps} className={classes} style={rowStyle} x-semi-prop="children">
                     {children}
                 </div>
             </RowContext.Provider>

+ 2 - 1
packages/semi-ui/iconButton/index.tsx

@@ -97,7 +97,8 @@ class IconButton extends PureComponent<IconButtonProps> {
             [`${prefixCls}-content-right`]: iconPosition === 'left',
         });
 
-        const children = originChildren != null ? <span className={btnTextCls}>{originChildren}</span> : null;
+        const xSemiProp = this.props['x-semi-children-alias'] || 'children';
+        const children = originChildren != null ? <span className={btnTextCls} x-semi-prop={xSemiProp}>{originChildren}</span> : null;
 
         if (iconPosition === 'left') {
             finalChildren = (

+ 38 - 11
packages/semi-ui/input/index.tsx

@@ -141,7 +141,7 @@ class Input extends BaseComponent<InputProps, InputState> {
         onKeyUp: noop,
         onKeyPress: noop,
         onEnterPress: noop,
-        validateStatus: 'default'
+        validateStatus: 'default',
     };
 
     inputRef!: React.RefObject<HTMLInputElement>;
@@ -195,7 +195,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             notifyClear: (e: React.MouseEvent<HTMLDivElement>) => this.props.onClear(e),
             setPaddingLeft: (paddingLeft: string) => this.setState({ paddingLeft }),
             setMinLength: (minLength: number) => this.setState({ minLength }),
-            isEventTarget: (e: React.MouseEvent) => e && e.target === e.currentTarget
+            isEventTarget: (e: React.MouseEvent) => e && e.target === e.currentTarget,
         };
     }
 
@@ -256,7 +256,7 @@ class Input extends BaseComponent<InputProps, InputState> {
 
     handleModeEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
         this.foundation.handleModeEnterPress(e);
-    }
+    };
 
     handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {
         this.foundation.handleClickPrefixOrSuffix(e);
@@ -274,7 +274,11 @@ class Input extends BaseComponent<InputProps, InputState> {
                 [`${prefixCls}-prepend-text`]: addonBefore && isString(addonBefore),
                 [`${prefixCls}-prepend-icon`]: isSemiIcon(addonBefore),
             });
-            return <div className={prefixWrapperCls}>{addonBefore}</div>;
+            return (
+                <div className={prefixWrapperCls} x-semi-prop="addonBefore">
+                    {addonBefore}
+                </div>
+            );
         }
         return null;
     }
@@ -287,7 +291,11 @@ class Input extends BaseComponent<InputProps, InputState> {
                 [`${prefixCls}-append-text`]: addonAfter && isString(addonAfter),
                 [`${prefixCls}-append-icon`]: isSemiIcon(addonAfter),
             });
-            return <div className={prefixWrapperCls}>{addonAfter}</div>;
+            return (
+                <div className={prefixWrapperCls} x-semi-prop="addonAfter">
+                    {addonAfter}
+                </div>
+            );
         }
         return null;
     }
@@ -353,7 +361,17 @@ class Input extends BaseComponent<InputProps, InputState> {
         });
 
         // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
-        return <div className={prefixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix} id={insetLabelId}>{labelNode}</div>;
+        return (
+            <div
+                className={prefixWrapperCls}
+                onMouseDown={this.handlePreventMouseDown}
+                onClick={this.handleClickPrefixOrSuffix}
+                id={insetLabelId}
+                x-semi-prop="prefix,insetLabel"
+            >
+                {labelNode}
+            </div>
+        );
     }
 
     showClearBtn() {
@@ -368,13 +386,22 @@ class Input extends BaseComponent<InputProps, InputState> {
             return null;
         }
         const suffixWrapperCls = cls({
-            [`${prefixCls }-suffix`]: true,
-            [`${prefixCls }-suffix-text`]: suffix && isString(suffix),
-            [`${prefixCls }-suffix-icon`]: isSemiIcon(suffix),
+            [`${prefixCls}-suffix`]: true,
+            [`${prefixCls}-suffix-text`]: suffix && isString(suffix),
+            [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
             [`${prefixCls}-suffix-hidden`]: suffixAllowClear && Boolean(hideSuffix),
         });
         // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
-        return <div className={suffixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix}>{suffix}</div>;
+        return (
+            <div
+                className={suffixWrapperCls}
+                onMouseDown={this.handlePreventMouseDown}
+                onClick={this.handleClickPrefixOrSuffix}
+                x-semi-prop="suffix"
+            >
+                {suffix}
+            </div>
+        );
     }
 
     render() {
@@ -461,7 +488,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             inputProps.minLength = stateMinLength;
         }
         if (validateStatus === 'error') {
-            inputProps['aria-invalid'] = "true";
+            inputProps['aria-invalid'] = 'true';
         }
         return (
             // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions

+ 16 - 4
packages/semi-ui/list/index.tsx

@@ -71,7 +71,11 @@ class List<T = any> extends BaseComponent<ListProps<T>> {
     renderEmpty = () => {
         const { emptyContent } = this.props;
         if (emptyContent) {
-            return (<div className={`${cssClasses.PREFIX}-empty`}>{emptyContent}</div>);
+            return (
+                <div className={`${cssClasses.PREFIX}-empty`} x-semi-prop="emptyContent">
+                    {emptyContent}
+                </div>
+            );
         } else {
             return (
                 <LocaleConsumer componentName="List">
@@ -152,19 +156,27 @@ class List<T = any> extends BaseComponent<ListProps<T>> {
         }
         return (
             <div className={wrapperCls} style={style}>
-                {header ? <div className={`${cssClasses.PREFIX}-header`}>{header}</div> : null}
+                {header ? (
+                    <div className={`${cssClasses.PREFIX}-header`} x-semi-prop="header">
+                        {header}
+                    </div>
+                ) : null}
                 <ListContext.Provider
                     value={{
                         grid,
                         onRightClick,
-                        onClick
+                        onClick,
                     }}
                 >
                     <Spin spinning={loading} size="large">
                         {this.wrapChildren(childrenList, children)}
                     </Spin>
                 </ListContext.Provider>
-                {footer ? <div className={`${cssClasses.PREFIX}-footer`}>{footer}</div> : null}
+                {footer ? (
+                    <div className={`${cssClasses.PREFIX}-footer`} x-semi-prop="footer">
+                        {footer}
+                    </div>
+                ) : null}
                 {loadMore ? loadMore : null}
             </div>
         );

+ 1 - 1
packages/semi-ui/modal/ConfirmModal.tsx

@@ -81,7 +81,7 @@ const ConfirmModal = (props: ConfirmProps) => {
             visible={visible}
             {...rest}
         >
-            <div className={contentCls}>{content}</div>
+            <div className={contentCls} x-semi-prop="content">{content}</div>
         </Modal>
     );
 };

+ 2 - 0
packages/semi-ui/modal/Modal.tsx

@@ -292,6 +292,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                         type="tertiary"
                         autoFocus={true}
                         {...this.props.cancelButtonProps}
+                        x-semi-children-alias="cancelText"
                     >
                         {cancelText || locale.cancel}
                     </Button>
@@ -311,6 +312,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                             loading={confirmLoading}
                             onClick={this.handleOk}
                             {...this.props.okButtonProps}
+                            x-semi-children-alias="okText"
                         >
                             {okText || locale.confirm}
                         </Button>

+ 27 - 14
packages/semi-ui/modal/ModalContent.tsx

@@ -163,7 +163,7 @@ export default class ModalContent extends BaseComponent<ModalContentReactProps,
         } = this.props;
         let closer;
         if (closable) {
-            const iconType = closeIcon || <IconClose/>;
+            const iconType = closeIcon || <IconClose x-semi-prop="closeIcon" />;
             closer = (
                 <Button
                     aria-label="close"
@@ -182,7 +182,7 @@ export default class ModalContent extends BaseComponent<ModalContentReactProps,
 
     renderIcon = () => {
         const { icon } = this.props;
-        return icon ? <span className={`${cssClasses.DIALOG}-icon-wrapper`}>{icon}</span> : null;
+        return icon ? <span className={`${cssClasses.DIALOG}-icon-wrapper`} x-semi-prop="icon">{icon}</span> : null;
     };
 
     renderHeader = () => {
@@ -197,8 +197,14 @@ export default class ModalContent extends BaseComponent<ModalContentReactProps,
             (
                 <div className={`${cssClasses.DIALOG}-header`}>
                     {icon}
-                    <Typography.Title heading={5} className={`${cssClasses.DIALOG}-title`}
-                        id={`${cssClasses.DIALOG}-title`}>{title}</Typography.Title>
+                    <Typography.Title
+                        heading={5}
+                        className={`${cssClasses.DIALOG}-title`}
+                        id={`${cssClasses.DIALOG}-title`}
+                        x-semi-prop="title"
+                    >
+                        {title}
+                    </Typography.Title>
                     {closer}
                 </div>
             );
@@ -216,16 +222,19 @@ export default class ModalContent extends BaseComponent<ModalContentReactProps,
         const closer = this.renderCloseBtn();
         const icon = this.renderIcon();
         const hasHeader = title !== null && title !== undefined || 'header' in this.props;
-        return hasHeader ?
-            <div className={bodyCls} id={`${cssClasses.DIALOG}-body`} style={bodyStyle}>{children}</div> :
-            (
-                <div className={`${cssClasses.DIALOG}-body-wrapper`}>
-                    {icon}
-                    <div className={bodyCls} style={bodyStyle}>{children}</div>
-                    {closer}
+        return hasHeader ? (
+            <div className={bodyCls} id={`${cssClasses.DIALOG}-body`} style={bodyStyle} x-semi-prop="children">
+                {children}
+            </div>
+        ) : (
+            <div className={`${cssClasses.DIALOG}-body-wrapper`}>
+                {icon}
+                <div className={bodyCls} style={bodyStyle} x-semi-prop="children">
+                    {children}
                 </div>
-            );
-
+                {closer}
+            </div>
+        );
     };
 
     getDialogElement = () => {
@@ -248,7 +257,11 @@ export default class ModalContent extends BaseComponent<ModalContentReactProps,
         }
         const body = this.renderBody();
         const header = this.renderHeader();
-        const footer = props.footer ? <div className={`${cssClasses.DIALOG}-footer`}>{props.footer}</div> : null;
+        const footer = props.footer ? (
+            <div className={`${cssClasses.DIALOG}-footer`} x-semi-prop="footer">
+                {props.footer}
+            </div>
+        ) : null;
         const dialogElement = (
             // eslint-disable-next-line jsx-a11y/no-static-element-interactions
             <div

+ 16 - 4
packages/semi-ui/notification/notice.tsx

@@ -106,7 +106,7 @@ class Notice extends BaseComponent<NoticeReactProps, NoticeState> {
         }
         if (iconType) {
             return (
-                <div className={iconCls}>
+                <div className={iconCls} x-semi-prop="icon">
                     {isSemiIcon(iconType) ? React.cloneElement(iconType, { size: iconType.props.size || 'large' }) : iconType}
                 </div>
             );
@@ -170,14 +170,26 @@ class Notice extends BaseComponent<NoticeReactProps, NoticeState> {
                 <div>{this.renderTypeIcon()}</div>
                 <div className={`${prefixCls}-inner`}>
                     <div className={`${prefixCls}-content-wrapper`}>
-                        {title ? <div id={titleID} className={`${prefixCls}-title`}>{title}</div> : ''}
-                        {content ? <div className={`${prefixCls}-content`}>{content}</div> : ''}
+                        {title ? (
+                            <div id={titleID} className={`${prefixCls}-title`} x-semi-prop="title">
+                                {title}
+                            </div>
+                        ) : (
+                            ''
+                        )}
+                        {content ? (
+                            <div className={`${prefixCls}-content`} x-semi-prop="content">
+                                {content}
+                            </div>
+                        ) : (
+                            ''
+                        )}
                     </div>
                     {showClose && (
                         <Button
                             className={`${prefixCls}-icon-close`}
                             type="tertiary"
-                            icon={<IconClose/>}
+                            icon={<IconClose />}
                             theme="borderless"
                             size="small"
                             onClick={this.close}

+ 16 - 2
packages/semi-ui/pagination/index.tsx

@@ -216,7 +216,14 @@ export default class Pagination extends BaseComponent<PaginationProps, Paginatio
             [`${prefixCls}-item-disabled`]: prevDisabled,
         });
         return (
-            <li role="button" aria-disabled={prevDisabled ? true : false} aria-label="Previous" onClick={e => !prevDisabled && this.foundation.goPrev(e)} className={preClassName}>
+            <li
+                role="button"
+                aria-disabled={prevDisabled ? true : false}
+                aria-label="Previous"
+                onClick={e => !prevDisabled && this.foundation.goPrev(e)}
+                className={preClassName}
+                x-semi-prop="prevText"
+            >
                 {prevText || <IconChevronLeft size="large" />}
             </li>
         );
@@ -231,7 +238,14 @@ export default class Pagination extends BaseComponent<PaginationProps, Paginatio
             [`${prefixCls}-next`]: true,
         });
         return (
-            <li role="button" aria-disabled={nextDisabled ? true : false} aria-label="Next" onClick={e => !nextDisabled && this.foundation.goNext(e)} className={nextClassName}>
+            <li
+                role="button"
+                aria-disabled={nextDisabled ? true : false}
+                aria-label="Next"
+                onClick={e => !nextDisabled && this.foundation.goNext(e)}
+                className={nextClassName}
+                x-semi-prop="prevText"
+            >
                 {nextText || <IconChevronRight size="large" />}
             </li>
         );

+ 11 - 3
packages/semi-ui/popconfirm/index.tsx

@@ -174,12 +174,20 @@ export default class Popconfirm extends BaseComponent<PopconfirmProps, Popconfir
             <div className={popCardCls} onClick={this.stopImmediatePropagation} style={style}>
                 <div className={`${prefixCls}-inner`}>
                     <div className={`${prefixCls}-header`}>
-                        <i className={`${prefixCls}-header-icon`}>
+                        <i className={`${prefixCls}-header-icon`} x-semi-prop="icon">
                             {React.isValidElement(icon) ? icon : null}
                         </i>
                         <div className={`${prefixCls}-header-body`}>
-                            {showTitle ? <div className={`${prefixCls}-header-title`}>{title}</div> : null}
-                            {showContent ? <div className={`${prefixCls}-header-content`}>{content}</div> : null}
+                            {showTitle ? (
+                                <div className={`${prefixCls}-header-title`} x-semi-prop="title">
+                                    {title}
+                                </div>
+                            ) : null}
+                            {showContent ? (
+                                <div className={`${prefixCls}-header-content`} x-semi-prop="content">
+                                    {content}
+                                </div>
+                            ) : null}
                         </div>
                         <Button
                             className={`${prefixCls}-btn-close`}

+ 10 - 2
packages/semi-ui/radio/radio.tsx

@@ -246,8 +246,16 @@ class Radio extends BaseComponent<RadioProps, RadioState> {
         }, addonClassName);
         const renderContent = () => (
             <>
-                {children ? <span className={addonCls} style={addonStyle} id={addonId}>{children}</span> : null}
-                {extra && !isButtonRadio ? <div className={`${prefix}-extra`} id={extraId}>{extra}</div> : null}
+                {children ? (
+                    <span className={addonCls} style={addonStyle} id={addonId} x-semi-prop="children">
+                        {children}
+                    </span>
+                ) : null}
+                {extra && !isButtonRadio ? (
+                    <div className={`${prefix}-extra`} id={extraId} x-semi-prop="extra">
+                        {extra}
+                    </div>
+                ) : null}
             </>
         );
 

+ 1 - 1
packages/semi-ui/rating/item.tsx

@@ -110,7 +110,7 @@ export default class Item extends PureComponent<RatingItemProps> {
                     className={`${prefixCls}-wrapper`}
                 >
                     <div className={`${prefixCls}-first`} style={{ width: `${firstWidth * 100}%` }}>{content}</div>
-                    <div className={`${prefixCls}-second`}>{content}</div>
+                    <div className={`${prefixCls}-second`} x-semi-prop="character">{content}</div>
                 </div>
             </li>
         );

+ 19 - 3
packages/semi-ui/scrollList/index.tsx

@@ -49,14 +49,30 @@ class ScrollList extends BaseComponent<ScrollListProps, {}> {
             <div className={clsWrapper} style={style}>
                 {header ? (
                     <div className={clsHeader}>
-                        <div className={`${clsHeader}-title`}>{header}</div>
+                        <div 
+                            className={`${clsHeader}-title`} 
+                            x-semi-prop={this.props['x-semi-header-alias'] || "header"}
+                        >
+                            {header}
+                        </div>
                         <div className={`${clsWrapper}-line`} />
                     </div>
                 ) : null}
-                <div className={`${clsWrapper}-body`} style={{ height: bodyHeight ? bodyHeight : '' }}>
+                <div
+                    className={`${clsWrapper}-body`}
+                    style={{ height: bodyHeight ? bodyHeight : '' }}
+                    x-semi-prop="children"
+                >
                     {children}
                 </div>
-                {footer ? <div className={`${clsWrapper}-footer`}>{footer}</div> : null}
+                {footer ? (
+                    <div
+                        className={`${clsWrapper}-footer`}
+                        x-semi-prop={this.props['x-semi-footer-alias'] || "footer"}
+                    >
+                        {footer}
+                    </div>
+                ) : null}
             </div>
         );
     }

+ 12 - 4
packages/semi-ui/select/index.tsx

@@ -879,7 +879,11 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         return (
             <>
                 <div className={contentWrapperCls}>
-                    {<span className={spanCls}>{renderText || renderText === 0 ? renderText : placeholder}</span>}
+                    {
+                        <span className={spanCls} x-semi-prop="placeholder">
+                            {renderText || renderText === 0 ? renderText : placeholder}
+                        </span>
+                    }
                     {filterable && showInput ? this.renderInput() : null}
                 </div>
             </>
@@ -1007,7 +1011,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             [`${prefixcls}-suffix-text`]: suffix && isString(suffix),
             [`${prefixcls}-suffix-icon`]: isSemiIcon(suffix),
         });
-        return <div className={suffixWrapperCls}>{suffix}</div>;
+        return <div className={suffixWrapperCls} x-semi-prop="suffix">{suffix}</div>;
     }
 
     renderPrefix() {
@@ -1021,7 +1025,11 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             [`${prefixcls}-prefix-icon`]: isSemiIcon(labelNode),
         });
 
-        return <div className={prefixWrapperCls} id={insetLabelId}>{labelNode}</div>;
+        return (
+            <div className={prefixWrapperCls} id={insetLabelId} x-semi-prop="prefix,insetLabel">
+                {labelNode}
+            </div>
+        );
     }
 
     renderSelection() {
@@ -1068,7 +1076,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
       (selections.size || inputValue) && !disabled && (isHovering || isOpen);
 
         const arrowContent = showArrow ? (
-            <div className={`${prefixcls}-arrow`}>
+            <div className={`${prefixcls}-arrow`} x-semi-prop="arrowIcon">
                 {arrowIcon}
             </div>
         ) : (

+ 5 - 1
packages/semi-ui/select/option.tsx

@@ -105,7 +105,11 @@ class Option extends PureComponent<OptionProps> {
             }
             return (
                 <LocaleConsumer<Locale['Select']> componentName="Select">
-                    {(locale: Locale['Select']) => <div className={optionClassName}>{emptyContent || locale.emptyText}</div>}
+                    {(locale: Locale['Select']) => (
+                        <div className={optionClassName} x-semi-prop="emptyContent">
+                            {emptyContent || locale.emptyText}
+                        </div>
+                    )}
                 </LocaleConsumer>
             );
         }

+ 3 - 3
packages/semi-ui/sideSheet/SideSheetContent.tsx

@@ -88,7 +88,7 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
         let header, closer;
         if (title) {
             header = (
-                <div className={`${prefixCls}-title`}>
+                <div className={`${prefixCls}-title`} x-semi-prop="title">
                     {this.props.title}
                 </div>
             );
@@ -140,11 +140,11 @@ export default class SideSheetContent extends React.PureComponent<SideSheetConte
             >
                 <div className={`${prefixCls}-content`}>
                     {header}
-                    <div className={`${prefixCls}-body`} style={props.bodyStyle}>
+                    <div className={`${prefixCls}-body`} style={props.bodyStyle} x-semi-prop="children">
                         {props.children}
                     </div>
                     {props.footer ? (
-                        <div className={`${prefixCls}-footer`}>
+                        <div className={`${prefixCls}-footer`} x-semi-prop="footer">
                             {props.footer}
                         </div>
                     ) : null}

+ 1 - 1
packages/semi-ui/skeleton/index.tsx

@@ -48,7 +48,7 @@ class Skeleton extends PureComponent<SkeletonProps> {
         let content;
         if (loading) {
             content = (
-                <div className={skCls} style={style} {...others}>
+                <div className={skCls} style={style} {...others} x-semi-prop="placeholder">
                     {placeholder}
                 </div>
             );

+ 1 - 1
packages/semi-ui/space/index.tsx

@@ -85,7 +85,7 @@ class Space extends PureComponent<SpaceProps> {
         });
         const childrenNodes = flatten(children);
         return (
-            <div className={classNames} style={realStyle}>
+            <div className={classNames} style={realStyle} x-semi-prop="children">
                 {childrenNodes}
             </div>
         );

+ 15 - 9
packages/semi-ui/spin/index.tsx

@@ -95,14 +95,18 @@ class Spin extends BaseComponent<SpinProps, SpinState> {
             [`${prefixCls}-animate`]: loading,
         });
 
-        return (
-            loading ? (
-                <div className={`${prefixCls}-wrapper`}>
-                    {indicator ? <div className={spinIconCls}>{indicator}</div> : <SpinIcon />}
-                    {tip ? <div>{tip}</div> : null}
-                </div>
-            ) : null
-        );
+        return loading ? (
+            <div className={`${prefixCls}-wrapper`}>
+                {indicator ? (
+                    <div className={spinIconCls} x-semi-prop="indicator">
+                        {indicator}
+                    </div>
+                ) : (
+                    <SpinIcon />
+                )}
+                {tip ? <div x-semi-prop="tip">{tip}</div> : null}
+            </div>
+        ) : null;
     }
 
     render() {
@@ -122,7 +126,9 @@ class Spin extends BaseComponent<SpinProps, SpinState> {
         return (
             <div className={spinCls} style={style}>
                 {this.renderSpin()}
-                <div className={`${prefixCls}-children`} style={childStyle}>{children}</div>
+                <div className={`${prefixCls}-children`} style={childStyle} x-semi-prop="children">
+                    {children}
+                </div>
             </div>
         );
     }

+ 9 - 14
packages/semi-ui/switch/index.tsx

@@ -146,23 +146,18 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
         const showUncheckedText = uncheckedText && !nativeControlChecked && size !== 'small';
         return (
             <div className={wrapperCls} style={style} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
-                {
-                    loading
-                        ? (
-                            <Spin
-                                wrapperClassName={cssClasses.LOADING_SPIN}
-                                size={size === 'default' ? 'middle' : size}
-                            />
-                        )
-                        : <div className={cssClasses.KNOB} aria-hidden={true} />
-                }
+                {loading ? (
+                    <Spin wrapperClassName={cssClasses.LOADING_SPIN} size={size === 'default' ? 'middle' : size} />
+                ) : (
+                    <div className={cssClasses.KNOB} aria-hidden={true} />
+                )}
                 {showCheckedText ? (
-                    <div className={cssClasses.CHECKED_TEXT}>
+                    <div className={cssClasses.CHECKED_TEXT} x-semi-prop="checkedText">
                         {checkedText}
                     </div>
                 ) : null}
                 {showUncheckedText ? (
-                    <div className={cssClasses.UNCHECKED_TEXT}>
+                    <div className={cssClasses.UNCHECKED_TEXT} x-semi-prop="uncheckedText">
                         {uncheckedText}
                     </div>
                 ) : null}
@@ -170,13 +165,13 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
                     {...switchProps}
                     ref={this.switchRef}
                     id={id}
-                    role='switch'
+                    role="switch"
                     aria-checked={nativeControlChecked}
                     aria-invalid={this.props['aria-invalid']}
                     aria-errormessage={this.props['aria-errormessage']}
                     aria-label={this.props['aria-label']}
                     aria-labelledby={this.props['aria-labelledby']}
-                    aria-describedby={this.props["aria-describedby"]}
+                    aria-describedby={this.props['aria-describedby']}
                     aria-disabled={this.props['disabled']}
                     onChange={e => this.foundation.handleChange(e.target.checked, e)}
                     onFocus={e => this.handleFocusVisible(e)}

+ 5 - 3
packages/semi-ui/table/Table.tsx

@@ -1015,7 +1015,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         }
 
         return isValidElement(title) || typeof title === 'string' ? (
-            <div className={`${prefixCls}-title`}>{title}</div>
+            <div className={`${prefixCls}-title`} x-semi-prop="title">{title}</div>
         ) : null;
     };
 
@@ -1032,7 +1032,9 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
             <LocaleConsumer componentName="Table" key={'emptyText'}>
                 {(locale: TableLocale, localeCode: string) => (
                     <div className={wrapCls}>
-                        <div className={`${prefixCls}-empty`}>{empty || locale.emptyText}</div>
+                        <div className={`${prefixCls}-empty`} x-semi-prop="empty">
+                            {empty || locale.emptyText}
+                        </div>
                     </div>
                 )}
             </LocaleConsumer>
@@ -1048,7 +1050,7 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
         }
 
         return isValidElement(footer) || typeof footer === 'string' ? (
-            <div className={`${prefixCls}-footer`} key="footer">
+            <div className={`${prefixCls}-footer`} key="footer" x-semi-prop="footer">
                 {footer}
             </div>
         ) : null;

+ 1 - 1
packages/semi-ui/tabs/TabBar.tsx

@@ -70,7 +70,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
         if (tabBarExtraContent) {
             const tabBarStyle = { ...tabBarExtraContentDefaultStyle, ...tabBarExtraContentStyle };
             return (
-                <div className={extraCls} style={tabBarStyle}>
+                <div className={extraCls} style={tabBarStyle} x-semi-prop="tabBarExtraContent">
                     {tabBarExtraContent}
                 </div>
             );

+ 9 - 4
packages/semi-ui/tabs/TabPane.tsx

@@ -98,6 +98,7 @@ class TabPane extends PureComponent<TabPaneProps> {
                 aria-hidden={active ? 'false' : 'true'}
                 tabIndex={0}
                 {...getDataAttr(restProps)}
+                x-semi-prop="children"
             >
                 {motion ? (
                     <TabPaneTransition
@@ -107,14 +108,18 @@ class TabPane extends PureComponent<TabPaneProps> {
                         state={active ? 'enter' : 'leave'}
                     >
                         {(transitionStyle): ReactNode => (
-                            <div className={`${cssClasses.TABS_PANE_MOTION_OVERLAY}`} style={{ ...transitionStyle }}>
+                            <div
+                                className={`${cssClasses.TABS_PANE_MOTION_OVERLAY}`}
+                                style={{ ...transitionStyle }}
+                                x-semi-prop="children"
+                            >
                                 {shouldRender ? children : null}
                             </div>
                         )}
                     </TabPaneTransition>
-                ) : (
-                    shouldRender ? children : null
-                )}
+                ) : shouldRender ? (
+                    children
+                ) : null}
             </div>
         );
     }

+ 22 - 4
packages/semi-ui/tagInput/index.tsx

@@ -299,8 +299,17 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
             // eslint-disable-next-line max-len
             [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
         });
-        // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
-        return <div className={prefixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix} id={insetLabelId}>{labelNode}</div>;
+        return (
+            // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
+            <div 
+                className={prefixWrapperCls} 
+                onMouseDown={this.handlePreventMouseDown} 
+                onClick={this.handleClickPrefixOrSuffix} 
+                id={insetLabelId} x-semi-prop="prefix"
+            >
+                {labelNode}
+            </div>
+        );
     }
 
     renderSuffix() {
@@ -313,8 +322,17 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
             // eslint-disable-next-line max-len
             [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
         });
-        // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
-        return <div className={suffixWrapperCls} onMouseDown={this.handlePreventMouseDown} onClick={this.handleClickPrefixOrSuffix}>{suffix}</div>;
+        return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
+            <div
+                className={suffixWrapperCls}
+                onMouseDown={this.handlePreventMouseDown}
+                onClick={this.handleClickPrefixOrSuffix}
+                x-semi-prop="suffix"
+            >
+                {suffix}
+            </div>
+        );
     }
 
     renderTags() {

+ 6 - 1
packages/semi-ui/timePicker/Combobox.tsx

@@ -317,7 +317,12 @@ class Combobox extends BaseComponent<ComboboxProps, ComboboxState> {
         return (
             <LocaleConsumer componentName="TimePicker">
                 {(locale: Locale['TimePicker'], localeCode: Locale['code']) => (
-                    <ScrollList header={panelHeader} footer={panelFooter}>
+                    <ScrollList
+                        header={panelHeader}
+                        footer={panelFooter} 
+                        x-semi-header-alias="panelHeader"
+                        x-semi-footer-alias="panelFooter"
+                    >
                         {this.renderAMPMSelect(locale, localeCode)}
                         {this.renderHourSelect(value.getHours(), locale)}
                         {this.renderMinuteSelect(value.getMinutes(), locale)}

+ 3 - 3
packages/semi-ui/toast/toast.tsx

@@ -120,7 +120,7 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
         const btnSize = 'small';
         return (
             <div
-                role='alert'
+                role="alert"
                 aria-label={`${type ? type : 'default'} type`}
                 className={toastCls}
                 style={style}
@@ -129,7 +129,7 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
             >
                 <div className={`${prefixCls}-content`}>
                     {this.renderIcon()}
-                    <span className={`${prefixCls}-content-text`} style={textStyle}>
+                    <span className={`${prefixCls}-content-text`} style={textStyle} x-semi-prop="content">
                         {content}
                     </span>
                     {showClose && (
@@ -137,7 +137,7 @@ class Toast extends BaseComponent<ToastReactProps, ToastState> {
                             <Button
                                 onClick={e => this.close(e)}
                                 type="tertiary"
-                                icon={<IconClose />}
+                                icon={<IconClose x-semi-prop="icon" />}
                                 theme={btnTheme}
                                 size={btnSize}
                             />

+ 1 - 0
packages/semi-ui/transfer/index.tsx

@@ -353,6 +353,7 @@ class Transfer extends BaseComponent<TransferProps, TransferState> {
                 checked={checked}
                 role="listitem"
                 onChange={() => this.onSelectOrRemove(item)}
+                x-semi-children-alias={`dataSource[${index}].label`}
             >
                 {item.label}
             </Checkbox>

+ 1 - 1
packages/semi-ui/tree/treeNode.tsx

@@ -269,7 +269,7 @@ export default class TreeNode extends PureComponent<TreeNodeProps, TreeNodeState
         });
         return (
             <ul className={wrapperCls}>
-                <li className={`${prefixcls}-label ${prefixcls}-label-empty`}>
+                <li className={`${prefixcls}-label ${prefixcls}-label-empty`} x-semi-prop="emptyContent">
                     {emptyContent}
                 </li>
             </ul>

+ 15 - 3
packages/semi-ui/treeSelect/index.tsx

@@ -646,7 +646,11 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             [`${prefixcls}-suffix-text`]: suffix && isString(suffix),
             [`${prefixcls}-suffix-icon`]: isSemiIcon(suffix),
         });
-        return <div className={suffixWrapperCls}>{suffix}</div>;
+        return (
+            <div className={suffixWrapperCls} x-semi-prop="suffix">
+                {suffix}
+            </div>
+        );
     };
 
     renderPrefix = () => {
@@ -660,7 +664,11 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             [`${prefixcls}-prefix-icon`]: isSemiIcon(labelNode),
         });
 
-        return <div className={prefixWrapperCls} id={insetLabelId}>{labelNode}</div>;
+        return (
+            <div className={prefixWrapperCls} id={insetLabelId} x-semi-prop="prefix,insetLabel">
+                {labelNode}
+            </div>
+        );
     };
 
     renderContent = () => {
@@ -869,7 +877,11 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         if (showClearBtn) {
             return null;
         }
-        return arrowIcon ? <div className={cls(`${prefixcls}-arrow`)}>{arrowIcon}</div> : null;
+        return arrowIcon ? (
+            <div className={cls(`${prefixcls}-arrow`)} x-semi-prop="arrowIcon">
+                {arrowIcon}
+            </div>
+        ) : null;
     };
 
     renderClearBtn = () => {

+ 1 - 1
packages/semi-ui/typography/base.tsx

@@ -526,7 +526,7 @@ export default class Base extends Component<BaseTypographyProps, BaseTypographyS
         }
         const iconSize: Size = size === 'small' ? 'small' : 'default';
         return (
-            <span className={`${prefixCls}-icon`}>
+            <span className={`${prefixCls}-icon`} x-semi-prop="icon">
                 {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), { size: iconSize }) : icon}
             </span>
         );

+ 107 - 38
packages/semi-ui/upload/index.tsx

@@ -3,25 +3,54 @@ import React, { ReactNode, CSSProperties, RefObject, ChangeEvent, DragEvent } fr
 import cls from 'classnames';
 import PropTypes from 'prop-types';
 import { noop, pick } from 'lodash';
-import UploadFoundation, { CustomFile, UploadAdapter, BeforeUploadObjectResult, AfterUploadResult } from '@douyinfe/semi-foundation/upload/foundation';
+import UploadFoundation, {
+    CustomFile,
+    UploadAdapter,
+    BeforeUploadObjectResult,
+    AfterUploadResult,
+} from '@douyinfe/semi-foundation/upload/foundation';
 import { strings, cssClasses } from '@douyinfe/semi-foundation/upload/constants';
 import FileCard from './fileCard';
 import BaseComponent, { ValidateStatus } from '../_base/baseComponent';
 import LocaleConsumer from '../locale/localeConsumer';
 import { IconUpload } from '@douyinfe/semi-icons';
-import { FileItem, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError } from './interface';
+import {
+    FileItem,
+    RenderFileItemProps,
+    UploadListType,
+    PromptPositionType,
+    BeforeUploadProps,
+    AfterUploadProps,
+    OnChangeProps,
+    customRequestArgs,
+    CustomError,
+} from './interface';
 import { Locale } from '../locale/interface';
 import '@douyinfe/semi-foundation/upload/upload.scss';
 
 const prefixCls = cssClasses.PREFIX;
 
-export { FileItem, RenderFileItemProps, UploadListType, PromptPositionType, BeforeUploadProps, AfterUploadProps, OnChangeProps, customRequestArgs, CustomError, BeforeUploadObjectResult, AfterUploadResult };
+export {
+    FileItem,
+    RenderFileItemProps,
+    UploadListType,
+    PromptPositionType,
+    BeforeUploadProps,
+    AfterUploadProps,
+    OnChangeProps,
+    customRequestArgs,
+    CustomError,
+    BeforeUploadObjectResult,
+    AfterUploadResult,
+};
 
 export interface UploadProps {
     accept?: string;
     action: string;
     afterUpload?: (object: AfterUploadProps) => AfterUploadResult;
-    beforeUpload?: (object: BeforeUploadProps) => BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> | boolean;
+    beforeUpload?: (
+        object: BeforeUploadProps
+    ) => BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> | boolean;
     beforeClear?: (fileList: Array<FileItem>) => boolean | Promise<boolean>;
     beforeRemove?: (file: FileItem, fileList: Array<FileItem>) => boolean | Promise<boolean>;
     capture?: boolean | 'user' | 'environment' | undefined;
@@ -114,7 +143,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         fileList: PropTypes.array, // files had been uploaded
         fileName: PropTypes.string, // same as name, to avoid props conflict in Form.Upload
         headers: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
-        hotSpotLocation: PropTypes.oneOf(['start','end']),
+        hotSpotLocation: PropTypes.oneOf(['start', 'end']),
         itemStyle: PropTypes.object,
         limit: PropTypes.number, // 最大允许上传文件个数
         listType: PropTypes.oneOf<UploadProps['listType']>(strings.LIST_TYPE),
@@ -218,7 +247,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         const { fileList } = props;
         if ('fileList' in props) {
             return {
-                fileList: fileList || []
+                fileList: fileList || [],
             };
         }
         return null;
@@ -228,7 +257,8 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         return {
             ...super.adapter,
             notifyFileSelect: (files): void => this.props.onFileChange(files),
-            notifyError: (error, fileInstance, fileList, xhr): void => this.props.onError(error, fileInstance, fileList, xhr),
+            notifyError: (error, fileInstance, fileList, xhr): void =>
+                this.props.onError(error, fileInstance, fileList, xhr),
             notifySuccess: (responseBody, file, fileList): void => this.props.onSuccess(responseBody, file, fileList),
             notifyProgress: (percent, file, fileList): void => this.props.onProgress(percent, file, fileList),
             notifyRemove: (file, fileList, fileItem): void => this.props.onRemove(file, fileList, fileItem),
@@ -241,19 +271,25 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                     this.setState({ fileList });
                 }
             },
-            notifyBeforeUpload: ({ file, fileList }): boolean | BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> => this.props.beforeUpload({ file, fileList }),
-            notifyAfterUpload: ({ response, file, fileList }): AfterUploadResult => this.props.afterUpload({ response, file, fileList }),
+            notifyBeforeUpload: ({
+                file,
+                fileList,
+            }): boolean | BeforeUploadObjectResult | Promise<BeforeUploadObjectResult> =>
+                this.props.beforeUpload({ file, fileList }),
+            notifyAfterUpload: ({ response, file, fileList }): AfterUploadResult =>
+                this.props.afterUpload({ response, file, fileList }),
             resetInput: (): void => {
                 this.setState(prevState => ({
-                    inputKey: Math.random()
+                    inputKey: Math.random(),
                 }));
             },
             resetReplaceInput: (): void => {
                 this.setState(prevState => ({
-                    replaceInputKey: Math.random()
+                    replaceInputKey: Math.random(),
                 }));
             },
-            updateDragAreaStatus: (dragAreaStatus: string): void => this.setState({ dragAreaStatus } as { dragAreaStatus: 'default' | 'legal' | 'illegal' }),
+            updateDragAreaStatus: (dragAreaStatus: string): void =>
+                this.setState({ dragAreaStatus } as { dragAreaStatus: 'default' | 'legal' | 'illegal' }),
             notifyChange: ({ currentFile, fileList }): void => this.props.onChange({ currentFile, fileList }),
             updateLocalUrls: (urls): void => this.setState({ localUrls: urls }),
             notifyClear: (): void => this.props.onClear(),
@@ -295,7 +331,6 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         this.setState({ replaceIdx: index }, () => {
             this.replaceInputRef.current.click();
         });
-
     };
 
     onReplaceChange = (e: ChangeEvent<HTMLInputElement>): void => {
@@ -316,11 +351,11 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
      * insert files at index
      * @param files Array<CustomFile>
      * @param index number
-     * @returns 
+     * @returns
      */
     insert = (files: Array<CustomFile>, index: number): void => {
         return this.foundation.insertFileToList(files, index);
-    }
+    };
 
     /**
      * ref method
@@ -333,7 +368,19 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
 
     renderFile = (file: FileItem, index: number, locale: Locale['Upload']): ReactNode => {
         const { name, status, validateMessage, _sizeInvalid, uid } = file;
-        const { previewFile, listType, itemStyle, showPicInfo, renderPicInfo, renderPicPreviewIcon, renderFileOperation, renderFileItem, renderThumbnail, disabled, onPreviewClick } = this.props;
+        const {
+            previewFile,
+            listType,
+            itemStyle,
+            showPicInfo,
+            renderPicInfo,
+            renderPicPreviewIcon,
+            renderFileOperation,
+            renderFileItem,
+            renderThumbnail,
+            disabled,
+            onPreviewClick,
+        } = this.props;
         const onRemove = (): void => this.remove(file);
         const onRetry = (): void => {
             this.foundation.retry(file);
@@ -358,7 +405,10 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             renderFileOperation,
             renderThumbnail,
             onReplace,
-            onPreviewClick: typeof onPreviewClick !== 'undefined' ? (): void => this.foundation.handlePreviewClick(file) : undefined,
+            onPreviewClick:
+                typeof onPreviewClick !== 'undefined'
+                    ? (): void => this.foundation.handlePreviewClick(file)
+                    : undefined,
         };
 
         if (status === strings.FILE_STATUS_UPLOAD_FAIL && !validateMessage) {
@@ -404,7 +454,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         });
         const dragAreaCls = cls({
             [`${dragAreaBaseCls}-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
-            [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL
+            [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL,
         });
         const mainCls = `${prefixCls}-file-list-main`;
         const addContentProps = {
@@ -413,7 +463,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             onClick: this.onClick,
         };
         const containerProps = {
-            className: fileListCls
+            className: fileListCls,
         };
         const draggableProps = {
             onDrop: this.onDrop,
@@ -425,7 +475,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             Object.assign(addContentProps, draggableProps, { className: cls(uploadAddCls, dragAreaCls) });
         }
         const addContent = (
-            <div {...addContentProps}>
+            <div {...addContentProps} x-semi-prop="children">
                 {children}
             </div>
         );
@@ -450,7 +500,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                 )}
             </LocaleConsumer>
         );
-    }
+    };
 
     renderFileListDefault = () => {
         const { showUploadList, limit, disabled } = this.props;
@@ -462,7 +512,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         const showTitle = limit !== 1 && fileList.length;
         const showClear = this.props.showClear && !disabled;
         const containerProps = {
-            className: fileListCls
+            className: fileListCls,
         };
 
         if (!showUploadList || !fileList.length) {
@@ -477,7 +527,12 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                             <div className={titleCls}>
                                 <span className={`${titleCls}-choosen`}>{locale.selectedFiles}</span>
                                 {showClear ? (
-                                    <span role="button" tabIndex={0} onClick={this.clear} className={`${titleCls}-clear`}>
+                                    <span
+                                        role="button"
+                                        tabIndex={0}
+                                        onClick={this.clear}
+                                        className={`${titleCls}-clear`}
+                                    >
                                         {locale.clear}
                                     </span>
                                 ) : null}
@@ -491,7 +546,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                 )}
             </LocaleConsumer>
         );
-    }
+    };
 
     onDrop = (e: DragEvent<HTMLDivElement>): void => {
         this.foundation.handleDrop(e);
@@ -524,7 +579,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                 {children}
             </div>
         );
-    }
+    };
 
     renderDragArea = (): ReactNode => {
         const { dragAreaStatus } = this.state;
@@ -554,14 +609,16 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                             children
                         ) : (
                             <>
-                                <div className={`${dragAreaBaseCls}-icon`}>
+                                <div className={`${dragAreaBaseCls}-icon`} x-semi-prop="dragIcon">
                                     {dragIcon || <IconUpload size="extra-large" />}
                                 </div>
                                 <div className={`${dragAreaBaseCls}-text`}>
-                                    <div className={`${dragAreaBaseCls}-main-text`}>
+                                    <div className={`${dragAreaBaseCls}-main-text`} x-semi-prop="dragMainText">
                                         {dragMainText || locale.mainText}
                                     </div>
-                                    <div className={`${dragAreaBaseCls}-sub-text`}>{dragSubText}</div>
+                                    <div className={`${dragAreaBaseCls}-sub-text`} x-semi-prop="dragSubText">
+                                        {dragSubText}
+                                    </div>
                                     <div className={`${dragAreaBaseCls}-tips`}>
                                         {dragAreaStatus === strings.DRAG_AREA_LEGAL && (
                                             <span className={`${dragAreaBaseCls}-tips-legal`}>{locale.legalTips}</span>
@@ -598,14 +655,18 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             validateStatus,
             directory,
         } = this.props;
-        const uploadCls = cls(prefixCls, {
-            [`${prefixCls}-picture`]: listType === strings.FILE_LIST_PIC,
-            [`${prefixCls}-disabled`]: disabled,
-            [`${prefixCls}-default`]: validateStatus === 'default',
-            [`${prefixCls}-error`]: validateStatus === 'error',
-            [`${prefixCls}-warning`]: validateStatus === 'warning',
-            [`${prefixCls}-success`]: validateStatus === 'success',
-        }, className);
+        const uploadCls = cls(
+            prefixCls,
+            {
+                [`${prefixCls}-picture`]: listType === strings.FILE_LIST_PIC,
+                [`${prefixCls}-disabled`]: disabled,
+                [`${prefixCls}-default`]: validateStatus === 'default',
+                [`${prefixCls}-error`]: validateStatus === 'error',
+                [`${prefixCls}-warning`]: validateStatus === 'warning',
+                [`${prefixCls}-success`]: validateStatus === 'success',
+            },
+            className
+        );
         const inputCls = cls(`${prefixCls}-hidden-input`);
         const inputReplaceCls = cls(`${prefixCls}-hidden-input-replace`);
         const promptCls = cls(`${prefixCls}-prompt`);
@@ -640,9 +701,17 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                     ref={this.replaceInputRef}
                 />
                 {this.renderAddContent()}
-                {prompt ? <div className={promptCls}>{prompt}</div> : null}
+                {prompt ? (
+                    <div className={promptCls} x-semi-prop="prompt">
+                        {prompt}
+                    </div>
+                ) : null}
 
-                {validateMessage ? <div className={validateMsgCls}>{validateMessage}</div> : null}
+                {validateMessage ? (
+                    <div className={validateMsgCls} x-semi-prop="validateMessage">
+                        {validateMessage}
+                    </div>
+                ) : null}
                 {this.renderFileList()}
             </div>
         );