Selaa lähdekoodia

Merge branch 'release'

DaiQiangReal 3 vuotta sitten
vanhempi
sitoutus
b13dba1a10
100 muutettua tiedostoa jossa 3840 lisäystä ja 603 poistoa
  1. 3 8
      .codesandbox/examples/pr-story/src/App.jsx
  2. 1 1
      .eslintrc.js
  3. 2 2
      .github/workflows/chromatic.yml
  4. 30 0
      .github/workflows/lighthouse.yml
  5. 18 0
      .lighthouserc.js
  6. 1 0
      .storybook/base/base.js
  7. 2 2
      content/basic/icon/index-en-US.md
  8. 2 2
      content/basic/icon/index.md
  9. 194 68
      content/input/cascader/index-en-US.md
  10. 197 68
      content/input/cascader/index.md
  11. 2 0
      content/input/datepicker/index-en-US.md
  12. 3 0
      content/input/datepicker/index.md
  13. 12 11
      content/input/select/index-en-US.md
  14. 35 34
      content/input/select/index.md
  15. 39 6
      content/input/upload/index-en-US.md
  16. 38 24
      content/input/upload/index.md
  17. 27 23
      content/other/locale/index-en-US.md
  18. 13 6
      content/other/locale/index.md
  19. 12 10
      content/show/timeline/index-en-US.md
  20. 12 10
      content/show/timeline/index.md
  21. 43 0
      content/start/changelog/index-en-US.md
  22. 43 0
      content/start/changelog/index.md
  23. 3 3
      gatsby-config.js
  24. 1 1
      lerna.json
  25. 3 1
      package.json
  26. 4 4
      packages/semi-animation-react/package.json
  27. 2 2
      packages/semi-animation-styled/package.json
  28. 2 2
      packages/semi-animation/package.json
  29. 1 1
      packages/semi-foundation/cascader/cascader.scss
  30. 4 0
      packages/semi-foundation/cascader/constants.ts
  31. 29 15
      packages/semi-foundation/cascader/foundation.ts
  32. 13 0
      packages/semi-foundation/cascader/util.ts
  33. 4 3
      packages/semi-foundation/datePicker/_utils/parser.ts
  34. 18 0
      packages/semi-foundation/datePicker/datePicker.scss
  35. 101 8
      packages/semi-foundation/datePicker/monthsGridFoundation.ts
  36. 3 1
      packages/semi-foundation/gulpfile.js
  37. 4 4
      packages/semi-foundation/package.json
  38. 11 9
      packages/semi-foundation/select/foundation.ts
  39. 6 2
      packages/semi-foundation/tree/treeUtil.ts
  40. 81 0
      packages/semi-foundation/upload/foundation.ts
  41. 0 4
      packages/semi-foundation/upload/rtl.scss
  42. 31 8
      packages/semi-foundation/upload/upload.scss
  43. 5 1
      packages/semi-foundation/upload/variables.scss
  44. 4 3
      packages/semi-icons/package.json
  45. 1 1
      packages/semi-icons/src/components/Icon.tsx
  46. 33 0
      packages/semi-icons/src/icons/IconDoubleChevronLeft.tsx
  47. 33 0
      packages/semi-icons/src/icons/IconDoubleChevronRight.tsx
  48. 3 1
      packages/semi-icons/src/icons/index.ts
  49. 1 0
      packages/semi-icons/src/svgs/double_chevron_left.svg
  50. 1 0
      packages/semi-icons/src/svgs/double_chevron_right.svg
  51. 8 0
      packages/semi-icons/src/svgs/meta.json
  52. 2 2
      packages/semi-illustrations/package.json
  53. 4 3
      packages/semi-next/package.json
  54. 3 2
      packages/semi-scss-compile/package.json
  55. 2 2
      packages/semi-theme-default/package.json
  56. 1302 0
      packages/semi-ui/_base/_story/a11y.jsx
  57. 49 0
      packages/semi-ui/_base/_story/a11y.scss
  58. 3 1
      packages/semi-ui/_base/_story/index.stories.js
  59. 9 4
      packages/semi-ui/_utils/index.ts
  60. 221 0
      packages/semi-ui/cascader/__test__/cascader.test.js
  61. 138 0
      packages/semi-ui/cascader/_story/cascader.stories.js
  62. 37 21
      packages/semi-ui/cascader/index.tsx
  63. 4 2
      packages/semi-ui/cascader/item.tsx
  64. 85 2
      packages/semi-ui/datePicker/__test__/datePicker.test.js
  65. 29 1
      packages/semi-ui/datePicker/_story/datePicker.stories.js
  66. 17 0
      packages/semi-ui/datePicker/_story/v2/YearButton.jsx
  67. 1 0
      packages/semi-ui/datePicker/_story/v2/index.js
  68. 12 1
      packages/semi-ui/datePicker/monthsGrid.tsx
  69. 55 29
      packages/semi-ui/datePicker/navigation.tsx
  70. 27 1
      packages/semi-ui/descriptions/__test__/descriptions.test.js
  71. 52 2
      packages/semi-ui/descriptions/_story/descriptions.stories.js
  72. 1 1
      packages/semi-ui/descriptions/item.tsx
  73. 2 1
      packages/semi-ui/gulpfile.js
  74. 160 0
      packages/semi-ui/locale/source/es.ts
  75. 12 12
      packages/semi-ui/package.json
  76. 1 1
      packages/semi-ui/popover/Arrow.tsx
  77. 1 1
      packages/semi-ui/rating/item.tsx
  78. 25 0
      packages/semi-ui/select/_story/select.stories.js
  79. 17 6
      packages/semi-ui/select/index.tsx
  80. 50 0
      packages/semi-ui/timeline/_story/timeline.stories.js
  81. 7 2
      packages/semi-ui/timeline/item.tsx
  82. 50 1
      packages/semi-ui/upload/__test__/upload.test.js
  83. 110 95
      packages/semi-ui/upload/fileCard.tsx
  84. 147 53
      packages/semi-ui/upload/index.tsx
  85. 3 0
      packages/semi-ui/upload/interface.ts
  86. 3 2
      packages/semi-webpack/package.json
  87. 1 1
      packages/semi-webpack/src/semi-theme-loader.ts
  88. 2 2
      src/components/IconList/index.scss
  89. 0 2
      src/html.js
  90. 2 1
      src/templates/scope.js
  91. BIN
      static/editor/base/browser/ui/codicons/codicon/codicon.ttf
  92. 5 0
      static/editor/base/worker/workerMain.js
  93. 6 0
      static/editor/basic-languages/abap/abap.js
  94. 6 0
      static/editor/basic-languages/apex/apex.js
  95. 7 0
      static/editor/basic-languages/azcli/azcli.js
  96. 7 0
      static/editor/basic-languages/bat/bat.js
  97. 6 0
      static/editor/basic-languages/bicep/bicep.js
  98. 6 0
      static/editor/basic-languages/cameligo/cameligo.js
  99. 6 0
      static/editor/basic-languages/clojure/clojure.js
  100. 6 0
      static/editor/basic-languages/coffee/coffee.js

+ 3 - 8
.codesandbox/examples/pr-story/src/App.jsx

@@ -1,7 +1,7 @@
 import React from "react";
 
 import { Button, Empty } from "@douyinfe/semi-ui";
-import { IconSemiLogo } from "@douyinfe/semi-icons";
+import { IconDoubleChevronLeft, IconDoubleChevronRight  } from "@douyinfe/semi-icons";
 import {
   IllustrationConstruction,
   IllustrationConstructionDark,
@@ -16,13 +16,8 @@ export default function App() {
   return (
     <div className="app">
       {/* ------- your code start ------- DON'T DELETE THIS LINE -------  */}
-      <Button icon={<IconSemiLogo />}>hello semi</Button>
-      <Empty
-        image={<IllustrationConstruction />}
-        darkModeImage={<IllustrationConstructionDark />}
-        title={"Write a demo about this pull request"}
-        description="Semi build on this PR"
-      />
+        <IconDoubleChevronLeft />
+        <IconDoubleChevronRight />
       {/* ------- your code end ------- DON'T DELETE THIS LINE ------- */}
     </div>
   );

+ 1 - 1
.eslintrc.js

@@ -14,7 +14,7 @@ module.exports = {
     overrides: [
         {
             files: ['*.js', '*.jsx'],
-            extends: ['jest-enzyme', 'plugin:react/recommended', 'plugin:import/recommended', 'plugin:import/errors', 'plugin:import/warnings'],
+            extends: ['jest-enzyme', 'plugin:react/recommended', 'plugin:import/recommended', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:jsx-a11y/recommended'],
             parser: '@babel/eslint-parser',
             plugins: ['react', 'react-hooks', 'jest', 'import'],
             rules: {

+ 2 - 2
.github/workflows/chromatic.yml

@@ -6,7 +6,7 @@ name: 'test:chromatic'
 # Event for the workflow
 on:
   pull_request:
-    branches: [ main, release, test-chromatic ]
+    branches: [ main, release, milestone**, test-chromatic ]
     paths:
       - 'packages/**/*.scss'
       - '!packages/**/_story/**'
@@ -16,7 +16,7 @@ on:
       - '!packages/**/*.test.[tj]sx?'
       - '!packages/**/*.md'
   push:
-    branches: [ main, release, test-chromatic ]
+    branches: [ main, release, milestone**, test-chromatic ]
     paths:
       - 'packages/**/*.scss'
       - '!packages/**/*.md'

+ 30 - 0
.github/workflows/lighthouse.yml

@@ -0,0 +1,30 @@
+name: 'lighthouse test'
+
+on:
+  pull_request:
+    branches: [ main, release, milestone** ]
+  push:
+    branches: [ main, release, milestone**, feat/a11y-ci ]
+
+jobs:
+  lhci:
+    name: Lighthouse
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Use Node.js 14.x
+        uses: actions/setup-node@v1
+        with:
+          node-version: 14.x
+      - name: npm install, build
+        run: |
+          npm i -g lerna gulp
+          lerna bootstrap
+          lerna run build:lib
+          npm run build-storybook
+      - name: run Lighthouse CI
+        env:
+          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
+        run: |
+          npm install -g @lhci/[email protected]
+          lhci autorun

+ 18 - 0
.lighthouserc.js

@@ -0,0 +1,18 @@
+
+/**
+ * lighthouse config
+ * https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/configuration.md
+ */
+ module.exports = {
+    ci: {
+      collect: {
+          staticDistDir: './storybook-static',
+          url: ['http://localhost/iframe.html?id=base--semi-a-11-y&args=&viewMode=story'],
+          isSinglePageApplication: true,
+      },
+      upload: {
+        target: "temporary-public-storage",
+      },
+    },
+  };
+  

+ 1 - 0
.storybook/base/base.js

@@ -14,6 +14,7 @@ module.exports = {
         optimizationLevel: 3,
       },
     },
+    '@storybook/addon-a11y'
   ],
   webpackFinal: async (config) => {
     const rules =

+ 2 - 2
content/basic/icon/index-en-US.md

@@ -54,7 +54,7 @@ import { IconHome, IconEmoji, IconSpin } from '@douyinfe/semi-icons';
 You can change the `font-size` to change the icon size
 >
 
-The Icon component encapsulates the size attribute, which makes it easier to define the icon size. It supports `extra-small` (8x8), `small` (12x12), `default` (16x16), `large` (20x20), `extra-large `(24x24).
+The Icon component encapsulates the size attribute, which makes it easier to define the icon size. It supports `extra-small` (8x8), `small` (12x12), `default` (16x16), `large` (20x20), `extra-large `(24x24), When size is specified as `inherit`, the icon size inherits the current context font size.
 
 
 ```jsx live=true
@@ -156,7 +156,7 @@ import StarIcon from './star.svg';
 | onMouseMove | Callback event of moving the mouse >=v1.21 | (e: Event) => void | None |
 | onMouseUp | Callback event when the mouse button is raised >=v1.21 | (e: Event) => void | None |
 | rotate | degree of rotation | number | |
-| size | Size, supports `extra-small`, `small`, `default`, `large`, `extra-large` | string | `default` |
+| size | Size, supports `inherit`, `extra-small`, `small`, `default`, `large`, `extra-large` | string | `default` |
 | spin | spin animation | boolean | |
 | style | Icon style | CSSProperties | None |
 | svg | Icon content | ReactNode | None |

+ 2 - 2
content/basic/icon/index.md

@@ -53,7 +53,7 @@ import { IconHome, IconEmoji, IconSpin } from '@douyinfe/semi-icons';
 可以改变`font-size`来更改图标大小
 >
 
-Icon组件封装了size属性,可以更方便地定义图标尺寸,支持 `extra-small` (8x8),`small` (12x12), `default` (16x16), `large` (20x20), `extra-large` (24x24)
+Icon组件封装了size属性,可以更方便地定义图标尺寸,支持 `extra-small` (8x8),`small` (12x12), `default` (16x16), `large` (20x20), `extra-large` (24x24),当size指定为`inherit`时,图标大小继承当前上下文字体大小
 
 
 ```jsx live=true
@@ -155,7 +155,7 @@ import StarIcon from './star.svg';
 | onMouseMove | 移动鼠标的回调事件 >=v1.21 | (e: Event) => void | 无    |
 | onMouseUp | 鼠标按钮抬起的回调事件 >=v1.21 | (e: Event) => void | 无    |
 | rotate | 旋转度数 | number |   |
-| size | 尺寸,支持`extra-small`,`small`, `default`, `large`, `extra-large` | string | `default`  |
+| size | 尺寸,支持`inherit`,`extra-small`,`small`, `default`, `large`, `extra-large` | string | `default`  |
 | spin | 旋转动画 | boolean |   |
 | style | 图标样式 | CSSProperties | 无    |
 | svg | 图标内容 | ReactNode | 无    |

+ 194 - 68
content/input/cascader/index-en-US.md

@@ -666,6 +666,68 @@ import { Cascader, Tag, Typography } from '@douyinfe/semi-ui';
 };
 ```
 
+### Custom Separator
+Version: >=2.2.0
+
+You can use `separator` to set the separator, including: the separator of the content displayed in the dropdown during search and displayed in the Trigger during single selection.
+
+```jsx live=true
+import React from 'react';
+import { Cascader } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = [
+        {
+            label: 'Impressionism',
+            value: 'impressionism',
+            children: [
+                {
+                    label: 'Visual Arts',
+                    value: 'visualArts',
+                    children: [
+                        {
+                            label: 'Claude Monet',
+                            value: 'Monet',
+                        },
+                        {
+                            label: 'Pierre-Auguste Renoir',
+                            value: 'Renoir',
+                        },
+                        {
+                            label: 'Édouard Manet',
+                            value: 'Manet',
+                        },
+                    ],
+                },
+                {
+                    label: 'Music',
+                    value: 'music',
+                    children: [
+                        {
+                            label: 'Claude Debussy',
+                            value: 'Debussy',
+                        },
+                        {
+                            label: 'Maurice Ravel',
+                            value: 'Ravel',
+                        }
+                    ]
+                }
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 400 }}
+            treeData={treeData}
+            defaultValue={['impressionism', 'visualArts', 'Monet']}
+            filterTreeNode
+            separator=' > '
+        />
+    );
+};
+```
+
 ### Disabled
 
 ```jsx live=true
@@ -1009,76 +1071,137 @@ class Demo extends React.Component {
 
 ### Auto Merge Value
 
-In the multi-selection (multiple=true) scenario, when we select the ancestor node, if we want the value not to include its corresponding descendant nodes, we can set it by `autoMergeValue`, and the default is true.
+In the multi-selection (multiple=true) scenario, when we select the ancestor node, if we want the value not to include its corresponding descendant nodes, we can set it by `autoMergeValue`, and the default is true. When `autoMergeValue` and `leafOnly` are turned on at the same time, the latter has a higher priority.
 
 ```jsx live=true
-import React from 'react';
+import React, { useState } from 'react';
 import { Cascader } from '@douyinfe/semi-ui';
 
-class Demo extends React.Component {
-    constructor() {
-        super();
-        this.state = {
-            value: ['impressionism','visualArts']
-        };
-    }
-    onChange(value) {
-        this.setState({value});
-    }
-    render() {
-        const treeData = [
-            {
-                label: 'Impressionism',
-                value: 'impressionism',
-                children: [
-                    {
-                        label: 'Visual Arts',
-                        value: 'visualArts',
-                        children: [
-                            {
-                                label: 'Claude Monet',
-                                value: 'Monet',
-                            },
-                            {
-                                label: 'Pierre-Auguste Renoir',
-                                value: 'Renoir',
-                            },
-                            {
-                                label: 'Édouard Manet',
-                                value: 'Manet',
-                            },
-                        ],
-                    },
-                    {
-                        label: 'Music',
-                        value: 'music',
-                        children: [
-                            {
-                                label: 'Claude Debussy',
-                                value: 'Debussy',
-                            },
-                            {
-                                label: 'Maurice Ravel',
-                                value: 'Ravel',
-                            }
-                        ]
-                    }
-                ],
-            }
-        ];
-        return (
-            <Cascader
-                style={{ width: 300 }}
-                treeData={treeData}
-                placeholder="Please select"
-                value={this.state.value}
-                multiple
-                autoMergeValue={false}
-                onChange={e => this.onChange(e)}
-            />
-        );
-    }
-}
+() => {
+    const [value, setValue] = useState(['impressionism','visualArts']);
+    const onChange = value => {
+        setValue(value);
+    };
+    const treeData = [
+        {
+            label: 'Impressionism',
+            value: 'impressionism',
+            children: [
+                {
+                    label: 'Visual Arts',
+                    value: 'visualArts',
+                    children: [
+                        {
+                            label: 'Claude Monet',
+                            value: 'Monet',
+                        },
+                        {
+                            label: 'Pierre-Auguste Renoir',
+                            value: 'Renoir',
+                        },
+                        {
+                            label: 'Édouard Manet',
+                            value: 'Manet',
+                        },
+                    ],
+                },
+                {
+                    label: 'Music',
+                    value: 'music',
+                    children: [
+                        {
+                            label: 'Claude Debussy',
+                            value: 'Debussy',
+                        },
+                        {
+                            label: 'Maurice Ravel',
+                            value: 'Ravel',
+                        }
+                    ]
+                }
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 300 }}
+            treeData={treeData}
+            placeholder="Please select"
+            value={value}
+            multiple
+            autoMergeValue={false}
+            onChange={e => onChange(e)}
+        />
+    );
+};
+```
+
+### Leaf Only
+version: >=2.2.0
+
+In multiple selection, you can set the value to include only leaf nodes by turning on leafOnly, that is, the displayed Tag and onChange parameter values only include value. 
+
+```jsx live=true
+import React, { useState } from 'react';
+import { Cascader } from '@douyinfe/semi-ui';
+
+() => {
+    const [value, setValue] = useState(['impressionism','visualArts']);
+    const onChange = value => {
+        setValue(value);
+    };
+    const treeData = [
+        {
+            label: 'Impressionism',
+            value: 'impressionism',
+            children: [
+                {
+                    label: 'Visual Arts',
+                    value: 'visualArts',
+                    children: [
+                        {
+                            label: 'Claude Monet',
+                            value: 'Monet',
+                        },
+                        {
+                            label: 'Pierre-Auguste Renoir',
+                            value: 'Renoir',
+                        },
+                        {
+                            label: 'Édouard Manet',
+                            value: 'Manet',
+                        },
+                    ],
+                },
+                {
+                    label: 'Music',
+                    value: 'music',
+                    children: [
+                        {
+                            label: 'Claude Debussy',
+                            value: 'Debussy',
+                        },
+                        {
+                            label: 'Maurice Ravel',
+                            value: 'Ravel',
+                        }
+                    ]
+                }
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 300 }}
+            treeData={treeData}
+            placeholder="Please select"
+            value={value}
+            multiple
+            leafOnly
+            onChange={e => onChange(e)}
+        />
+    );
+};
 ```
 
 ### Dynamic Update of Data
@@ -1400,7 +1523,7 @@ function Demo() {
 | ------------------ | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------- | ------- |
 | arrowIcon     | Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon                                                                                   | ReactNode                                                              |                            | 1.15.0       |
 | autoAdjustOverflow | Whether to automatically adjust the expansion direction of the dropdown for automatic adjustment of the expansion direction during edge occlusion | boolean | true | - |
-| autoMergeValue | Auto merge value. Specifically, after opening, when a parent node is selected, the value will not include the descendants of the node | boolean | true |  1.28.0 |
+| autoMergeValue | Auto merge value. Specifically, after opening, when a parent node is selected, the value will not include the descendants of the node. Does not support dynamic switching | boolean | true |  1.28.0 |
 | bottomSlot | bottom slot | ReactNode | - |  1.27.0 |
 | changeOnSelect     | Toggle whether non-leaf nodes are selectable                                                                                   | boolean                                                              | false                           | -       |
 | className          | ClassName                                                                                                                    | string                                                               | -                               | -       |
@@ -1416,6 +1539,7 @@ function Demo() {
 | filterTreeNode     | Set filter, the value of treeNodeFilterProp is used for searching                | ((inputValue: string, treeNodeString: string) => boolean) \| boolean | false                           | -       |
 | getPopupContainer | Specify the parent DOM, the drop-down box will be rendered into the DOM, the customization needs to set position: relative |() => HTMLElement|() => document.body|-|
 | insetLabel         | Prefix alias, used mainly in Form                                                                                            | ReactNode                                                            | -                               | 0.28.0  |
+| leafOnly         | When multiple selections, the set value only includes leaf nodes, that is, the displayed Tag and onChange value parameters only include leaf nodes. Does not support dynamic switching                             | boolean                                                            | false                               | 2.2.0  |
 | loadData | Load data asynchronously and the return value should be a promise | (selectOptions: TreeNode[]) => Promise< void > |-| 1.8.0|
 | max| In the case of multiple selections, the number of multiple selections is limited, and the onExceed callback will be triggered when max is exceeded | number |-|1.28.0|
 | maxTagCount| When multiple selections, the maximum number of labels to be displayed will be displayed in the form of +N after exceeding| number |-|1.28.0|
@@ -1427,6 +1551,7 @@ function Demo() {
 | prefix             | Prefix label                                                                                                                 | ReactNode                                                            | -                               | 0.28.0  |
 |restTagsPopoverProps |The configuration properties of the [Popover](/en-US/show/popover#API%20Reference)     |PopoverProps     | {}        |1.28.0|
 | searchPlaceholder  | Placeholder for search input                                                                                                 | string                                                               | -                               | -       |
+| separator  | Custom separator, including: the separator of the content displayed in the dropdown during search and displayed in the Trigger during single selection        | string                                                               | ' / '                               | 2.2.0       |
 | showClear       |  Toggle whether to show clear button   | boolean    | false    | 0.35.0    |
 | showNext| Set the way to expand the Dropdown submenu, one of: `click`、`hover` | string |`click`|1.29.0|
 | showRestTagsPopover| When the number of tags exceeds maxTagCount and hover reaches +N, whether to display the remaining content through Popover| boolean |false|1.28.0|
@@ -1442,6 +1567,7 @@ function Demo() {
 | value       | Selected value (controlled mode)    | string\|number\|TreeNode\|(string\|number\|TreeNode)[][]                                                   | -                               | -       |
 | validateStatus |The validation status of the trigger only affects the display style. Optional: default、error、warning | string | `default` | - |
 | zIndex | zIndex for dropdown menu | number | 1030 | - |
+| enableLeafClick | Multiple mode, click the leaf option enable trigger check | boolean | false | 2.2.0 |
 | onBlur | Out of focus Cascader's callback | (e: MouseEvent) => void | - | - |
 | onChange           | Callback function when the tree node is selected                                                                             | (value: string\|number\|TreeNode\|(string\|number\|TreeNode)[]) => void                                | -                               | -       |
 | onClear| When showClear is true, click the clear button to trigger the callback | () => void |-|1.29.0|
@@ -1466,4 +1592,4 @@ function Demo() {
 | value      | Value property (required)      | string\|number | -       |
 
 ## Design Tokens
-<DesignToken/>
+<DesignToken/>

+ 197 - 68
content/input/cascader/index.md

@@ -671,6 +671,69 @@ import { Cascader, Tag, Typography } from '@douyinfe/semi-ui';
 };
 ```
 
+### 自定义分隔符
+
+版本: >=2.2.0
+
+可以使用 `separator` 设置分隔符, 包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符。
+
+```jsx live=true
+import React from 'react';
+import { Cascader } from '@douyinfe/semi-ui';
+
+() => {
+    const treeData = [
+        {
+            label: '浙江省',
+            value: 'zhejiang',
+            children: [
+                {
+                    label: '杭州市',
+                    value: 'hangzhou',
+                    children: [
+                        {
+                            label: '西湖区',
+                            value: 'xihu',
+                        },
+                        {
+                            label: '萧山区',
+                            value: 'xiaoshan',
+                        },
+                        {
+                            label: '临安区',
+                            value: 'linan',
+                        },
+                    ],
+                },
+                {
+                    label: '宁波市',
+                    value: 'ningbo',
+                    children: [
+                        {
+                            label: '海曙区',
+                            value: 'haishu',
+                        },
+                        {
+                            label: '江北区',
+                            value: 'jiangbei',
+                        }
+                    ]
+                },
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 300 }}
+            treeData={treeData}
+            defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
+            filterTreeNode
+            separator=' > '
+        />
+    );
+};
+```
+
 ### 禁用
 
 ```jsx live=true
@@ -990,79 +1053,142 @@ class Demo extends React.Component {
 ### 自动合并 value
 版本: >=1.28.0
 
-在多选(multiple=true)场景中,当我们选中祖先节点时,如果希望 value 不包含它对应的子孙节点,则可以通过 `autoMergeValue` 来设置,默认为 true。
+在多选(multiple=true)场景中,当我们选中祖先节点时,如果希望 value 不包含它对应的子孙节点,则可以通过 `autoMergeValue` 来设置,默认为 true。当 autoMergeValue 和 leafOnly 同时开启时,后者优先级更高。
 
 ```jsx live=true
-import React from 'react';
+import React, { useState } from 'react';
 import { Cascader } from '@douyinfe/semi-ui';
 
-class Demo extends React.Component {
-    constructor() {
-        super();
-        this.state = {
-            value: ['zhejiang','ningbo']
-        };
-    }
-    onChange(value) {
+() => {
+    const [value, setValue] = useState([]);
+    const onChange = value => {
         console.log(value);
-        this.setState({value});
-    }
-    render() {
-        const treeData = [
-            {
-                label: '浙江省',
-                value: 'zhejiang',
-                children: [
-                    {
-                        label: '杭州市',
-                        value: 'hangzhou',
-                        children: [
-                            {
-                                label: '西湖区',
-                                value: 'xihu',
-                            },
-                            {
-                                label: '萧山区',
-                                value: 'xiaoshan',
-                            },
-                            {
-                                label: '临安区',
-                                value: 'linan',
-                            },
-                        ],
-                    },
-                    {
-                        label: '宁波市',
-                        value: 'ningbo',
-                        children: [
-                            {
-                                label: '海曙区',
-                                value: 'haishu',
-                            },
-                            {
-                                label: '江北区',
-                                value: 'jiangbei',
-                            }
-                        ]
-                    },
-                ],
-            }
-        ];
-        return (
-            <Cascader
-                style={{ width: 300 }}
-                treeData={treeData}
-                placeholder="请选择所在地区"
-                value={this.state.value}
-                multiple
-                autoMergeValue={false}
-                onChange={e => this.onChange(e)}
-            />
-        );
-    }
-}
+        setValue(value);
+    };
+    const treeData = [
+        {
+            label: '浙江省',
+            value: 'zhejiang',
+            children: [
+                {
+                    label: '杭州市',
+                    value: 'hangzhou',
+                    children: [
+                        {
+                            label: '西湖区',
+                            value: 'xihu',
+                        },
+                        {
+                            label: '萧山区',
+                            value: 'xiaoshan',
+                        },
+                        {
+                            label: '临安区',
+                            value: 'linan',
+                        },
+                    ],
+                },
+                {
+                    label: '宁波市',
+                    value: 'ningbo',
+                    children: [
+                        {
+                            label: '海曙区',
+                            value: 'haishu',
+                        },
+                        {
+                            label: '江北区',
+                            value: 'jiangbei',
+                        }
+                    ]
+                },
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 300 }}
+            treeData={treeData}
+            placeholder="autoMergeValue 为 false"
+            value={value}
+            multiple
+            autoMergeValue={false}
+            onChange={e => onChange(e)}
+        />
+    );
+};
 ```
 
+### 仅叶子节点
+版本: >=2.2.0
+
+在多选时,可以通过开启 leafOnly 来设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的参数 value 只包含 value。
+
+```jsx live=true
+import React, { useState } from 'react';
+import { Cascader } from '@douyinfe/semi-ui';
+
+() => {
+    const [value, setValue] = useState([]);
+    const onChange = value => {
+        console.log(value);
+        setValue(value);
+    };
+    const treeData = [
+        {
+            label: '浙江省',
+            value: 'zhejiang',
+            children: [
+                {
+                    label: '杭州市',
+                    value: 'hangzhou',
+                    children: [
+                        {
+                            label: '西湖区',
+                            value: 'xihu',
+                        },
+                        {
+                            label: '萧山区',
+                            value: 'xiaoshan',
+                        },
+                        {
+                            label: '临安区',
+                            value: 'linan',
+                        },
+                    ],
+                },
+                {
+                    label: '宁波市',
+                    value: 'ningbo',
+                    children: [
+                        {
+                            label: '海曙区',
+                            value: 'haishu',
+                        },
+                        {
+                            label: '江北区',
+                            value: 'jiangbei',
+                        }
+                    ]
+                },
+            ],
+        }
+    ];
+    return (
+        <Cascader
+            style={{ width: 300 }}
+            treeData={treeData}
+            placeholder="开启 leafOnly"
+            value={value}
+            multiple
+            leafOnly
+            onChange={e => onChange(e)}
+        />
+    );
+};
+```
+
+
 ### 动态更新数据
 
 ```jsx live=true hideInDSM
@@ -1384,7 +1510,7 @@ function Demo() {
 | ------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------------------------------- | ------ |
 | arrowIcon     |   自定义右侧下拉箭头 Icon,当 showClear 开关打开且当前有选中值时,hover 会优先显示 clear icon                                                              | ReactNode                                                                          |                             | 1.15.0      |
 | autoAdjustOverflow | 是否自动调整下拉框展开方向,用于边缘遮挡时自动调整展开方向 | boolean | true | - |
-| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点 | boolean | true |  1.28.0 |
+| autoMergeValue | 设置自动合并 value。具体而言是,开启后,当某个父节点被选中时,value 将不包括该节点的子孙节点。不支持动态切换 | boolean | true |  1.28.0 |
 | bottomSlot | 底部插槽 | ReactNode | - |  1.27.0 |
 | changeOnSelect     | 是否允许选择非叶子节点                                                                   | boolean                                                                          | false                            | -      |
 | className          | 选择框的 className 属性                                                              | string                                                                           | -                                | -      |
@@ -1400,6 +1526,7 @@ function Demo() {
 | filterTreeNode     | 设置筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值 | ((inputValue: string, treeNodeString: string) => boolean) \| boolean | false                            | -      |
 | getPopupContainer | 指定父级 DOM,下拉框将会渲染至该 DOM 中,自定义需要设置 position: relative |() => HTMLElement|() => document.body|-|
 | insetLabel         | 前缀标签别名,主要用于 Form                                                          | ReactNode                                                                        | -                                | 0.28.0 |
+| leafOnly | 多选时设置 value 只包含叶子节点,即显示的 Tag 和 onChange 的 value 参数只包含叶子节点。不支持动态切换 | boolean | false |  2.2.0|
 | loadData | 异步加载数据,需要返回一个Promise | (selectOptions: TreeNode[]) => Promise< void > |- |  1.8.0|
 | max| 多选时,限制多选选中的数量,超出 max 后将触发 onExceed 回调 | number |-|1.28.0|
 | maxTagCount| 多选时,标签的最大展示数量,超出后将以 +N 形式展示| number |-|1.28.0|
@@ -1411,6 +1538,7 @@ function Demo() {
 | prefix             | 前缀标签                                                                             | ReactNode                                                                        | -                                | 0.28.0 |
 |restTagsPopoverProps |Popover 的配置属性,可以控制 position、zIndex、trigger 等,具体参考[Popover](/zh-CN/show/popover#API%20%E5%8F%82%E8%80%83)           |PopoverProps     | {}        |1.28.0|
 | searchPlaceholder  | 搜索框默认文字                                                                       | string                                                                           | -                                | -      |
+| separator  | 自定义分隔符,包括:搜索时显示在下拉框的内容以及单选时回显到 Trigger 的内容的分隔符    | string                                                                           | ' / '                                | 2.2.0      |
 | showClear       |  是否展示清除按钮   | boolean    | false    | 0.35.0    |
 | showNext| 设置展开 Dropdown 子菜单的方式,可选: `click`、`hover` | string |`click`|1.29.0|
 | showRestTagsPopover| 当超过 maxTagCount,hover 到 +N 时,是否通过 Popover 显示剩余内容| boolean |false|1.28.0|
@@ -1426,6 +1554,7 @@ function Demo() {
 | validateStatus | trigger 的校验状态,仅影响展示样式。可选: default、error、warning | string | `default` | - |
 | value       | (受控)选中的条目                                                                   | string\|number\|TreeNode\|(string\|number\|TreeNode)[]                                                                           | -                                | -      |
 | zIndex | 下拉菜单的 zIndex | number | 1030 | - |
+| enableLeafClick | 多选时,是否启动点击叶子节点选项触发勾选 | boolean | false | 2.2.0 |
 | onBlur | 失焦 Cascader 的回调 | (e: MouseEvent) => void | - | - |
 | onChange           | 选中树节点时调用此函数,默认返回选中项 path 的 value 数组                            | (value: string\|number\|TreeNode\|(string\|number\|TreeNode)[]) => void                                                                         | -                                | -      |
 | onChangeWithObject | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string/number 变为 TreeNode。此时如果是受控,也需要把 value 设置成 TreeNode 类型,且必须含有 value 的键值,defaultValue 同理 | boolean | false | 1.16.0 |
@@ -1450,4 +1579,4 @@ function Demo() {
 | value    | 属性值(必填)           | string\|number | -     |
 
 ## 设计变量
-<DesignToken/>
+<DesignToken/>

+ 2 - 0
content/input/datepicker/index-en-US.md

@@ -143,6 +143,8 @@ version:>= 1.28.0
 
 In the scenario of range selection, turning on `syncSwitchMonth` means to switch the two panels simultaneously. The default is false.
 
+> Note: Clicking the year button will also switch the two panels synchronously. Switching the year and month from the scroll wheel will not switch the panels synchronously. This ensures the user's ability to select months at non-fixed intervals.
+
 ```jsx live=true
 import React from 'react';
 import { DatePicker } from '@douyinfe/semi-ui';

+ 3 - 0
content/input/datepicker/index.md

@@ -15,6 +15,7 @@ brief: 日期选择器用于帮助用户选择一个符合要求的、格式化
 
 ### 如何引入
 
+
 ```jsx import
 import { DatePicker } from '@douyinfe/semi-ui';
 ```
@@ -128,6 +129,8 @@ version: >= 1.28.0
 
 在范围选择的场景中, 开启 `syncSwitchMonth` 则允许双面板同步切换。默认为 false。
 
+> Note:点击年份按钮也会同步切换两个面板,从滚轮里面切换年月不会同步切换面板,这保证了用户选择非固定间隔月份的能力。
+
 ```jsx live=true
 import React from 'react';
 import { DatePicker } from '@douyinfe/semi-ui';

+ 12 - 11
content/input/select/index-en-US.md

@@ -719,14 +719,14 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
         { "name": "Yue Shen", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg" },
         { "name": "Chenyi Qu", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/8bd8224511db085ed74fea37205aede5.jpg" },
         { "name": "Jiamao Wen", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png" },
-    ]
+    ];
 
     const renderSelectedItem = optionNode => (
         <div key={optionNode.email} style={{ display: 'flex', alignItems: 'center' }}>
             <Avatar src={optionNode.avatar} size="small">{optionNode.abbr}</Avatar>
             <span style={{ marginLeft: 8 }}>{optionNode.email}</span>
         </div>
-    )
+    );
 
     // avatarSrc & avatarShape are supported after 1.6.0-beta
     const renderMultipleWithCustomTag = (optionNode, { onClose }) => {
@@ -746,7 +746,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             isRenderInTag: false,
             content
         };
-    }
+    };
 
     const renderMultipleWithCustomTag2 = (optionNode, { onClose }) => {
         const content = (
@@ -765,7 +765,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             isRenderInTag: false,
             content
         };
-    }
+    };
 
     const renderCustomOption = (item, index) => {
         const optionStyle = {
@@ -773,17 +773,17 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             paddingLeft: 24,
             paddingTop: 10,
             paddingBottom: 10
-        }
+        };
         return (
-            <Select.Option key={index} value={item.name} style={optionStyle} showTick={true}  {...item} key={item.email}>
+            <Select.Option value={item.name} style={optionStyle} showTick={true}  {...item} key={item.email}>
                 <Avatar size="small" src={item.avatar} />
                 <div style={{ marginLeft: 8 }}>
                     <div style={{ fontSize: 14 }}>{item.name}</div>
                     <div style={{ color: 'var(--color-text-2)', fontSize: 12, lineHeight: '16px', fontWeight: 'normal' }}>{item.email}</div>
                 </div>
             </Select.Option>
-        )
-    }
+        );
+    };
 
     return (
         <>
@@ -820,7 +820,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             </Select>
         </>
     );
-}
+};
 ```
 
 ### Custom pop-up layer style
@@ -1298,7 +1298,8 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | emptyContent | Content displayed when there is no result. When set to null, the drop-down list will not be displayed | string | ReactNode |  |
 | filter | Whether searchable or not, the default is false. When `true` is passed, it means turn on search ability, default filtering policy is whether the label matches search input<br/>When the input type is function, the function arguments are searchInput, option. It should return true when the option meets the filtering conditions, otherwise it returns false. | false | boolean\|function |  |
 | getPopupContainer | Specifies the parent DOM, and the popup layer will be rendered to the DOM, you need to set 'position: relative`| function(): HTMLElement | () => document.body |
-| innerTopSlot | Render at the top of the pop-up layer, custom slot inside the optionList <br/>** supported after v1.6.0 ** | ReactNode |  |
+| inputProps | When filter is true, the additional configuration parameters of the input, please refer to the Input component for specific configurable properties (note: please do not pass in `value`, `ref`, `onChange`, `onFocus`, otherwise it will override Select related callbacks and affect component behavior) <br/>**supported after v2.2.0** | object | 
+| innerTopSlot | Render at the top of the pop-up layer, custom slot inside the optionList | ReactNode |  |
 | innerBottomSlot | Render at the bottom of the pop-up layer, custom slot inside the optionList | ReactNode |  |
 | insetLabel | Same to `prefix`, just an alias | ReactNode |  |
 | loading | Does the drop-down list show the loading animation | boolean | false |
@@ -1321,7 +1322,7 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | spacing | Spacing between popup layer and trigger | number | 4 |
 | style | Inline Style | object |  |
 | suffix | An input helper rendered after | ReactNode |  |
-| triggerRender | Custom DOM of trigger <br/>**supported after v0.34.0** | function |  |
+| triggerRender | Custom DOM of trigger | function |  |
 | virtualize | List virtualization, used to optimize performance in the case of a large number of nodes, composed of height, width, and itemSize <br/>** supported after v0.37.0 ** | object |  |
 | validateStatus | Verification result, optional `warning`, `error`, `default` (only affect the style background color) | string | 'default' |
 | value | The currently selected value is passed as a controlled component, used in conjunction with `onchange` | string\|number\|array |  |

+ 35 - 34
content/input/select/index.md

@@ -774,14 +774,14 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
         { "name": "申悦", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg" },
         { "name": "曲晨一", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/8bd8224511db085ed74fea37205aede5.jpg" },
         { "name": "文嘉茂", "email": "[email protected]", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png" },
-    ]
+    ];
 
     const renderSelectedItem = optionNode => (
         <div style={{ display: 'flex', alignItems: 'center' }}>
             <Avatar src={optionNode.avatar} size="small">{optionNode.abbr}</Avatar>
             <span style={{ marginLeft: 8 }}>{optionNode.email}</span>
         </div>
-    )
+    );
 
     // avatarSrc & avatarShape are supported after 1.6.0-beta
     const renderMultipleWithCustomTag = (optionNode, { onClose }) => {
@@ -800,7 +800,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             isRenderInTag: false,
             content
         };
-    }
+    };
 
     const renderMultipleWithCustomTag2 = (optionNode, { onClose }) => {
         const content = (
@@ -818,7 +818,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             isRenderInTag: false,
             content
         };
-    }
+    };
 
     const renderCustomOption = (item, index) => {
         const optionStyle = {
@@ -826,17 +826,17 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             paddingLeft: 24,
             paddingTop: 10,
             paddingBottom: 10
-        }
+        };
         return (
-            <Select.Option key={index} value={item.name} style={optionStyle} showTick={true}  {...item} key={item.email}>
+            <Select.Option value={item.name} style={optionStyle} showTick={true}  {...item} key={item.email}>
                 <Avatar size="small" src={item.avatar} />
                 <div style={{ marginLeft: 8 }}>
                     <div style={{ fontSize: 14 }}>{item.name}</div>
                     <div style={{ color: 'var(--color-text-2)', fontSize: 12, lineHeight: '16px', fontWeight: 'normal' }}>{item.email}</div>
                 </div>
             </Select.Option>
-        )
-    }
+        );
+    };
 
     return (
         <>
@@ -873,7 +873,7 @@ import { Select, Avatar, Tag } from '@douyinfe/semi-ui';
             </Select>
         </>
     );
-}
+};
 ```
 
 ### 自定义弹出层样式
@@ -1297,10 +1297,10 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | --------------------------------- |
 | allowCreate              | 是否允许用户创建新条目,需配合 filter 使用                                                                                                                                                                | boolean                               | false                             |
 | arrowIcon            | 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon <br/>**v1.15.0 后提供**                                                                                                                                                                 | ReactNode     |                             |
-| autoAdjustOverflow       | 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)<br/>**v0.27.0 后提供**                                                                                                             | boolean                               | true                              |
+| autoAdjustOverflow       | 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)                                                                                                            | boolean                               | true                              |
 | autoFocus                | 初始渲染时是否自动 focus                                                                                                                                                                                  | boolean                               | false                             |
 | className                | 类名                                                                                                                                                                                                      | string                                |                                   |
-| clickToHide              | 已展开时,点击选择框是否自动收起下拉列表 <br/>**v0.23.0 后提供**                                                                                                                                           | boolean                               | false                             |
+| clickToHide              | 已展开时,点击选择框是否自动收起下拉列表                                                                                                                                          | boolean                               | false                             |
 | defaultValue             | 初始选中的值                                                                                                                                                                                              | string\|number\|array                 |                                   |
 | defaultOpen              | 是否默认展开下拉列表                                                                                                                                                                                      | boolean                               | false                             |
 | disabled                 | 是否禁用                                                                                                                                                                                                  | boolean                               | false                             |
@@ -1311,46 +1311,47 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | emptyContent             | 无结果时展示的内容。设为 null 时,下拉列表将不展示                                                                                                                                                        | string\|ReactNode                     |                                   |
 | filter                   | 是否可搜索,默认为 false。传入 true 时,代表开启搜索并采用默认过滤策略(label 是否与 sugInput 匹配),传入值为函数时,会接收 sugInput, option 两个参数,当 option 符合筛选条件应返回 true,否则返回 false | boolean \|function(sugInput, option)                    | false                             |
 | getPopupContainer        | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative`                                                                                                                                                                     | function():HTMLElement                | () => document.body               |
-| innerTopSlot             | 渲染在弹出层顶部,在 optionList 内部的自定义 slot <br/>**v1.6.0 后提供**                                                                                                                              | ReactNode                             |                                   |
+| inputProps               | filter为true时, input输入框的额外配置参数,具体可配置属性请参考Input组件(注意:请不要传入value、ref、onChange、onFocus,否则会覆盖Select相关回调,影响组件行为) <br/>**v2.2.0 后提供**   | object   |               |
+| innerTopSlot             | 渲染在弹出层顶部,在 optionList 内部的自定义 slot                                                                                                                     | ReactNode                             |                                   |
 | innerBottomSlot          | 渲染在弹出层底部,在 optionList 内部的自定义 slot                                                                                                                                                         | ReactNode                             |                                   |
-| insetLabel               | 同上,与 prefix 区别是 fontWeight 更大 <br/>**v0.23.0 后提供**                                                                                                                                             | ReactNode                             |                                   |
+| insetLabel               | 同上,与 prefix 区别是 fontWeight 更大                                                                                                                                             | ReactNode                             |                                   |
 | loading                  | 下拉列表是否展示加载动画                                                                                                                                                                                  | boolean                               | false                             |
 | maxTagCount              | 多选模式下,已选项超出 maxTagCount 时,后续选项会被渲染成+N 的形式                                                                                                                                        | number                                |                                   |
 | max                      | 最多可选几项,仅在多选模式下生效                                                                                                                                                                          | number                                |                                   |
 | maxHeight                | 下拉菜单中 `optionList` 的最大高度                                                                                                                                                                        | string\|number                        | 300                               |
 | multiple                 | 是否多选                                                                                                                                                                                                  | boolean                               | false                             |
-| outerTopSlot             | 渲染在弹出层顶部,与 optionList 平级的自定义 slot <br/>**v1.6.0 后提供**                                                                                                                              | ReactNode                             |                                   |
+| outerTopSlot             | 渲染在弹出层顶部,与 optionList 平级的自定义 slot                                                                                                                          | ReactNode                             |                                   |
 | outerBottomSlot          | 渲染在弹出层底部,与 optionList 平级的自定义 slot                                                                                                                                                         | ReactNode                             |                                   |
-| onBlur                   | 失去焦点时的回调 <br/>**v0.32.0 后提供**                                                                                                                                                                   | function(event)                       |                                   |
+| onBlur                   | 失去焦点时的回调                                                                                                                                                                 | function(event)                       |                                   |
 | onChange                 | 变化时回调函数                                                                                                                                                                                            | function(value:string\|number\|array) |                                   |
-| onCreate                 | allowCreate 为 true,创建备选项时的回调 <br/>**v0.23.0 后提供**                                                                                                                                            | function(option)                              |                                   |
-| onClear                  | 清除按钮的回调 <br/>**v0.34.0 后提供**                                                                                                                                                                     | function                              |                                   |
+| onCreate                 | allowCreate 为 true,创建备选项时的回调                                                                                                                                          | function(option)                              |                                   |
+| onClear                  | 清除按钮的回调                                                                                                                                                                  | function                              |                                   |
 | onChangeWithObject       | 是否将选中项 option 的其他属性作为回调。设为 true 时,onChange 的入参类型会从 string 变为 object: { value, label, ...rest }                                                                               | boolean                               | false                             |
 | onDropdownVisibleChange  | 下拉菜单展开/收起时的回调                                                                                                                                                                                 | function(visible:boolean)             |                                   |
 | onListScroll             | 候选项列表滚动时的回调                                                                                                                                                                                 | function(e)             |                                   |
 | onSearch                 | input 输入框内容发生改变时回调函数                                                                                                                                                                        | function(sugInput:string)             |                                   |
-| onSelect                 | 被选中时的回调<br/>**v0.26.0 后提供**                                                                                                                                                                      | function(value, option)               |                                   |
-| onDeselect               | 取消选中时的回调,仅在多选时有效<br/>**v0.26.0 后提供**                                                                                                                                                    | function(value, option)               |                                   |
-| onExceed                 | 当试图选择数超出 max 限制时的回调,仅在多选时生效 <br/> **v0.23.0** 后提供;入参在v1.16.0后提供                                                                                                                                   | function(option)                              |                                   |
-| onFocus                  | 获得焦点时的回调 <br/>**v0.32.0 后提供**                                                                                                                                                                   | function(event)                       |                                   |
+| onSelect                 | 被选中时的回调                                                                                                                                                                     | function(value, option)               |                                   |
+| onDeselect               | 取消选中时的回调,仅在多选时有效                                                                                                                                              | function(value, option)               |                                   |
+| onExceed                 | 当试图选择数超出 max 限制时的回调,仅在多选时生效 <br/> 入参在v1.16.0后提供                                                                                                                                   | function(option)                              |                                   |
+| onFocus                  | 获得焦点时的回调                                                                                                                                                                 | function(event)                       |                                   |
 | optionList               | 可以通过该属性传入 Option,请确保数组内每个元素都具备 label、value 属性                                                                                                                                    | array(\[{value, label}\])             |                                   |
 | placeholder              | 选择框默认文字                                                                                                                                                                                            | ReactNode                                |                                   |
 | position                 | 菜单展开的位置,可选项同Tooltip position                                                                                                                  | string                                | 'bottomLeft'                      |
-| prefix                   | 选择框的前缀标签 <br/>**v0.23.0 后提供**                                                                                                                                                                   | ReactNode                             |                                   |
-| renderCreateItem         | allowCreate 为 true 时,可自定义创建标签的渲染 <br/>**v0.23.0 后提供**                                                                                                                                     | function(inputValue:string)           | inputValue => '创建' + inputValue |
-| renderSelectedItem       | 通过 renderSelectedItem 自定义选择框中已选项标签的渲染<br/>**v0.23.0 后提供**                                                                                                                              | function(option)                      |                                   |
-| renderOptionItem         | 通过 renderOptionItem 完全自定义下拉列表中候选项的渲染<br/>**v1.10.0 后提供**                                                                                                                              | function(props) 入参详见Demo                      |                                   |
-| remote                   | 是否开启远程搜索,当 remote 为 true 时,input 内容改变后不会进行本地筛选匹配<br/>**v0.24.0 后提供**                                                                                                        | boolean                               | false                             |
-| size                     | 大小,可选值 `default`/`small`/`large`                                                                                                                                                                    | string                                | 'default'                         |
-| style                    | 样式                                                                                                                                                                                                      | object                                |                                   |
-| suffix                   | 选择框的后缀标签 <br/>**v0.23.0 后提供**                                                                                                                                                                   | ReactNode                             |                                   |
-| showClear                | 是否展示清除按钮 <br/>**v0.23.0 后提供**                                                                                                                                                                   | boolean                               | false                             |
-| showArrow                | 是否展示下拉箭头 <br/>**v0.23.0 后提供**                                                                                                                                                                   | boolean                               | true                              |
-| spacing                  | 浮层与选择器的距离 <br/>**v0.29.0 后提供**                                                                                                                                                                 | number                                | 4                                 |
-| triggerRender            | 自定义触发器渲染 <br/>**v0.34.0 后提供**                                                                                                                                                                   | function                              |                                   |
+| prefix                   | 选择框的前缀标签                                                                                                                                                                | ReactNode                             |                                   |
+| renderCreateItem         | allowCreate 为 true 时,可自定义创建标签的渲染                                                                                                                                 | function(inputValue:string)           | inputValue => '创建' + inputValue |
+| renderSelectedItem       | 通过 renderSelectedItem 自定义选择框中已选项标签的渲染                                                                                                                          | function(option)                      |                                   |
+| renderOptionItem         | 通过 renderOptionItem 完全自定义下拉列表中候选项的渲染                                                                                                                          | function(props) 入参详见Demo                      |                                   |
+| remote                   | 是否开启远程搜索,当 remote 为 true 时,input 内容改变后不会进行本地筛选匹配                                                                                                     | boolean                               | false                             |
+| size                     | 大小,可选值 `default`/`small`/`large`                                                                                                                                                                    | string                                | 'default'       |
+| style                    | 样式                                                                                                                                                                                                      | object                                |                 |
+| suffix                   | 选择框的后缀标签                                                                                                                                                                  | ReactNode                             |                                   |
+| showClear                | 是否展示清除按钮                                                                                                                                                                 | boolean                               | false                             |
+| showArrow                | 是否展示下拉箭头                                                                                                                                                                | boolean                               | true                              |
+| spacing                  | 浮层与选择器的距离                                                                                                                                                             | number                                | 4                                 |
+| triggerRender            | 自定义触发器渲染                                                                                                                                                          | function                              |                                   |
 | value                    | 当前选中的的值,传入该值时将作为受控组件,配合 `onChange` 使用                                                                                                                                             | string\|number\|array                 |                                   |
 | validateStatus           | 校验结果,可选`warning`、`error`、 `default`(只影响样式背景色)                                                                                                                                          | string                                | 'default'                         |
-| virtualize               | 列表虚拟化,用于大量节点的情况优化性能表现,由 height, width, itemSize 组成<br/>**v0.37.0 后提供**                                                                                                         | object                                |                                   |
+| virtualize               | 列表虚拟化,用于大量节点的情况优化性能表现,由 height, width, itemSize 组成                                                                                                   | object                                |                                   |
 | zIndex                   | 弹层的 zIndex                                                                                                                                                                                             | number                                | 1030                              |
 
 

+ 39 - 6
content/input/upload/index-en-US.md

@@ -637,6 +637,36 @@ import { IconPlus } from '@douyinfe/semi-icons';
 };
 ```
 
+Set `showPicInfo`, you can view the basic information of the picture
+
+```jsx live=true width=48%
+import React from 'react';
+import { Upload } from '@douyinfe/semi-ui';
+import { IconPlus } from '@douyinfe/semi-icons';
+
+() => {
+    let action = '//semi.design/api/upload';
+    const defaultFileList = [
+        {
+            uid: '1',
+            name: 'jiafang.png',
+            status: 'success',
+            size: '130KB',
+            preview: true,
+            url:
+                'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/e82f3b261133d2b20d85e8483c203112.jpg',
+        },
+    ];
+    return (
+        <>
+            <Upload action={action} listType="picture" showPicInfo accept="image/*" multiple defaultFileList={defaultFileList}>
+                <IconPlus size="extra-large" />
+            </Upload>
+        </>
+    );
+};
+```
+
 ### Disabled
 
 ```jsx live=true width=48%
@@ -1102,7 +1132,10 @@ import { IconUpload } from '@douyinfe/semi-icons';
 |prompt | Custom slot, which can be used to insert prompt text. Different from writing directly in `children`, the content of `prompt` will not trigger upload when clicked.<br/>(In the picture wall mode, the incoming prompt is only supported after v1.3.0) | ReactNode | | |
 |promptPosition | The position of the prompt text. When the listType is list, the reference object is the children element; when the listType is picture, the reference object is the picture list. Optional values ​​`left`, `right`, `bottom`<br/> (In picture wall mode, promptPosition is only supported after v1.3.0) | string |'right' | |
 |renderFileItem | Custom rendering of fileCard | (renderProps: RenderFileItemProps) => ReactNode | | 1.0.0 |
+|renderPicInfo| Custom photo wall information, only valid in photo wall mode| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
+|renderThumbnail| Custom picture wall thumb, only valid in photo wall mode| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
 |showClear | When limit is not 1 and the current number of uploaded files is greater than 1, whether to show the clear button | boolean | true | 1.0.0 |
+|showPicInfo| Whether to display picture information, only valid in photo wall mode | boolean| false | 2.2.0 |
 |showReplace | When the upload is successful, whether to display the replace button inside the fileCard | boolean | false | 1.21.0 |
 |showRetry | When uploading fails, whether to display the retry button inside the fileCard | boolean | true | 1.0.0 |
 |showUploadList | Whether to display the file list | boolean | true | |
@@ -1141,6 +1174,12 @@ interface FileItem {
 }
 ```
 
+## Methods
+|Name | Description | Type | Version|
+|----|----|----|----|
+| insert | Upload file, when index is passed, it will be inserted at the specified position, if not passed, it will be inserted at the end | (files: Array<File\>, index?: number) => void | 2.2.0 |
+| upload | Start upload manually, use with uploadTrigger="custom" | () => void | |
+
 ## Design Tokens
 <DesignToken/>
 
@@ -1156,9 +1195,3 @@ interface FileItem {
     - If you set `accept`, you can try to remove the accept attribute, and then see if the modified method is called. After removing it, the method is called to explain that the file type obtained by accept in the current environment does not match the set accept, and the upload behavior is terminated early. You can make a breakpoint to upload/foundation.js checkFileFormat function to see if the actual value of file.type obtained meets expectations.
 
 <Notice title={"About the progress bar"}>The progress bar indicates the upload progress. The upload progress is divided into two parts: data upload and server return. If all the data has been sent, but the server does not return a response, the progress bar will stay at 90%. The user upload is not completed. At this time, the request in the developer tool will be pending, which is normal. </Notice>
-
-<!-- ## Related Material
-
-```material
-82
-``` -->

+ 38 - 24
content/input/upload/index.md

@@ -624,6 +624,36 @@ import { IconPlus } from '@douyinfe/semi-icons';
 };
 ```
 
+设置 `showPicInfo`,可以查看图片基础信息
+
+```jsx live=true width=48%
+import React from 'react';
+import { Upload } from '@douyinfe/semi-ui';
+import { IconPlus } from '@douyinfe/semi-icons';
+
+() => {
+    let action = 'https://run.mocky.io/v3/d6ac5c9e-4d39-4309-a747-7ed3b5694859';
+    const defaultFileList = [
+        {
+            uid: '1',
+            name: 'jiafang.png',
+            status: 'success',
+            size: '130KB',
+            preview: true,
+            url:
+                'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/e82f3b261133d2b20d85e8483c203112.jpg',
+        },
+    ];
+    return (
+        <>
+            <Upload action={action} listType="picture" showPicInfo accept="image/*" multiple defaultFileList={defaultFileList}>
+                <IconPlus size="extra-large" />
+            </Upload>
+        </>
+    );
+};
+```
+
 ### 禁用
 
 ```jsx live=true width=48%
@@ -1089,7 +1119,10 @@ import { IconUpload } from '@douyinfe/semi-icons';
 |prompt | 自定义插槽,可用于插入提示文本。与直接在 `children` 中写的区别时,`prompt` 的内容在点击时不会触发上传<br/>(图片墙模式下,v1.3.0 后才支持传入 prompt) | ReactNode |  |  |
 |promptPosition | 提示文本的位置,当 listType 为 list 时,参照物为 children 元素;当 listType 为 picture 时,参照物为图片列表。可选值 `left`、`right`、`bottom`<br/>(图片墙模式下,v1.3.0 后才支持使用 promptPosition) | string | 'right' |  |
 |renderFileItem | fileCard 的自定义渲染 | (renderProps: RenderFileItemProps) => ReactNode |  | 1.0.0 |
+|renderPicInfo| 自定义照片墙信息,只在照片墙模式下有效| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
+|renderThumbnail| 自定义图片墙缩略图,只在照片墙模式下有效| (renderProps: RenderFileItemProps)=>ReactNode | | 2.2.0 |
 |showClear | 在 limit 不为 1 且当前已上传文件数大于 1 时,是否展示清空按钮 | boolean | true | 1.0.0 |
+|showPicInfo| 是否显示图片信息,只在照片墙模式下有效| boolean| false | 2.2.0 |
 |showReplace | 上传成功时,是否展示在 fileCard 内部展示替换按钮 | boolean | false | 1.21.0 |
 |showRetry | 上传失败时,是否展示在 fileCard 内部展示重试按钮 | boolean | true | 1.0.0 |
 |showUploadList | 是否显示文件列表 | boolean | true |  |
@@ -1128,23 +1161,11 @@ interface FileItem {
 }
 ```
 
-### RenderFileItemProps Interface
-
-```ts
-interface RenderFileItemProps extends FileItem {
-    previewFile: (fileItem: FileItem) => ReactNode; // 自定义预览元素
-    listType: 'picture' | 'list'; // 文件列表展示类型
-    onRemove: () => void; // 移除
-    onRetry: () => void; // 重试
-    onReplace: () => void; // 替换文件
-    key: string; // Item key
-    showRetry: boolean; // 是否展示重试
-    showReplace: boolean; // 是否展示替换
-    style: CSSProperties; // 传入的itemStyle
-    disabled: boolean; // 是否禁用
-    onPreviewClick: () => void; // 点击预览
-}
-```
+## Methods
+|名称 | 描述 | 类型 | 版本 |
+|----|----|----|----|
+| insert | 上传文件,当index传入时,会插入到指定位置,不传则插入到最后 | (files: Array<File\>, index?: number) => void | 2.2.0 |
+| upload | 手动开始上传,配合uploadTrigger="custom"使用 | () => void | |
 
 ## 设计变量
 <DesignToken/>
@@ -1164,10 +1185,3 @@ interface RenderFileItemProps extends FileItem {
     - 如果你设置了 `accept`,可以尝试把 accept 属性去掉,然后再看是否调用了改方法。去掉后调用了该方法说明,accept 在当前环境下获取的 file type 与设置的 accept 不符,上传行为提前终止。可以打个断点到 upload/foundation.js checkFileFormat 函数,看下获取的 file.type 真实值是否符合预期。
 
 <Notice title={"关于进度条"}>进度条表示上传进度,上传进度分为数据上载和服务器返回两部分,如果数据已经全部发出,但是服务器没有返回响应,进度条会停留在90%提示用户上传并没有完成,此时开发者工具中请求会处于 pending, 这是正常现象。仅当服务器返回响应,上传流程才真正结束,上传进度会达到100%</Notice>
-
-
-<!-- ## 相关物料
-
-```material
-82
-``` -->

+ 27 - 23
content/other/locale/index-en-US.md

@@ -20,6 +20,7 @@ brief: Internationalized components to provide multilingual support for Semi com
 | v1.11.0     | Vietnamese: vi_VN、Russian: ru_RU、Indonesian: id_ID、Malay: ms_MY、Thai: th_TH、Turkish: tr_TR |
 | v1.17.0     | Portuguese: pt_BR       |
 | v1.28.0     | Traditional Chinese: zh_TW       |
+| v2.2.0     | Spanish: es       |
 
 ## Components supported
 
@@ -46,16 +47,18 @@ import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
 import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
 import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
 import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
 
 import { LocaleProvider } from '@douyinfe/semi-ui';
 
-
-return (
-    <LocaleProvider locale={en_GB}>
-        {/* eslint-disable-next-line react/jsx-no-undef */}
-        <App />
-    </LocaleProvider>
-);
+() => {
+    return (
+        <LocaleProvider locale={en_GB}>
+            {/* eslint-disable-next-line react/jsx-no-undef */}
+            <App />
+        </LocaleProvider>
+    );
+};
 ```
 
 ## Code example
@@ -107,10 +110,9 @@ import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
 import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
 import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
 import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
 import { LocaleProvider, ConfigProvider, Pagination, Modal, Button, Select, Cascader, DatePicker, TreeSelect, Table, TimePicker, List, Calendar, Typography } from '@douyinfe/semi-ui';
 
-const { Option } = Select;
-
 class I18nDemo extends React.Component {
     constructor(props) {
         super(props);
@@ -134,6 +136,7 @@ class I18nDemo extends React.Component {
             'ms_MY': ms_MY,
             'th_TH': th_TH,
             'tr_TR': tr_TR,
+            es,
         };
         this.setState({ locale: language[code], localeCode: code });
     }
@@ -218,9 +221,9 @@ class I18nDemo extends React.Component {
                     <h5>Select & Cascader</h5>
                     <div style={style}>
                         <Select filter style={{ width: '180px' }}>
-                            <Option value='abc'>abc</Option>
-                            <Option value='vigo' disabled>vigo</Option>
-                            <Option value='hotsoon'>hotsoon</Option>
+                            <Select.Option value='abc'>abc</Select.Option>
+                            <Select.Option value='vigo' disabled>vigo</Select.Option>
+                            <Select.Option value='hotsoon'>hotsoon</Select.Option>
                         </Select>
                         <Cascader
                             style={{ width: 300, margin: 10 }}
@@ -255,17 +258,18 @@ class I18nDemo extends React.Component {
             <>
                 <div style={{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: 20 }}>
                     <Select onChange={this.onLanguageChange} insetLabel='Switch Language' style={{width: 250}} defaultValue='en_GB'>
-                        <Option value='zh_CN'>Chinese</Option>
-                        <Option value='en_GB'>English</Option>
-                        <Option value='ja_JP'>Japanese</Option>
-                        <Option value='ko_KR'>Korean</Option>
-                        <Option value='ar'>Arabic</Option>
-                        <Option value='vi_VN'>Vietnamese</Option>
-                        <Option value='ru_RU'>Russian</Option>
-                        <Option value='id_ID'>Indonesian</Option>
-                        <Option value='ms_MY'>Malay</Option>
-                        <Option value='th_TH'>Thai</Option>
-                        <Option value='tr_TR'>Turkish</Option>
+                        <Select.Option value='zh_CN'>Chinese</Select.Option>
+                        <Select.Option value='en_GB'>English</Select.Option>
+                        <Select.Option value='ja_JP'>Japanese</Select.Option>
+                        <Select.Option value='ko_KR'>Korean</Select.Option>
+                        <Select.Option value='ar'>Arabic</Select.Option>
+                        <Select.Option value='vi_VN'>Vietnamese</Select.Option>
+                        <Select.Option value='ru_RU'>Russian</Select.Option>
+                        <Select.Option value='id_ID'>Indonesian</Select.Option>
+                        <Select.Option value='ms_MY'>Malay</Select.Option>
+                        <Select.Option value='th_TH'>Thai</Select.Option>
+                        <Select.Option value='tr_TR'>Turkish</Select.Option>
+                        <Select.Option value='es'>Spanish</Select.Option>
                     </Select>
                 </div>
                 <LocaleProvider locale={locale}>

+ 13 - 6
content/other/locale/index.md

@@ -19,6 +19,7 @@ brief: 国际化组件,为 Semi 组件提供多语言支持
 | v1.11.0     | 越南语: vi_VN、俄罗斯语: ru_RU、印尼语: id_ID、马来语: ms_MY、泰语: th_TH、土耳其语: tr_TR |
 | v1.17.0     | 葡萄牙语(巴西): pt_BR       |
 | v1.28.0     | 繁体中文: zh_TW       |
+| v2.2.0     | 西班牙语: es       |
 ## 已支持组件
 
 > DatePicker、TimePicker、Modal、Pagination、Select、Table、Cascader、Calendar、TreeSelect、List、Typography、Transfer、Nav、Upload
@@ -44,16 +45,19 @@ import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
 import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
 import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
 import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
 
 import { LocaleProvider } from '@douyinfe/semi-ui';
 
 // 在locale中传入相应的语言包即可
-return (
-    <LocaleProvider locale={en_GB}>
-        {/* eslint-disable-next-line react/jsx-no-undef */}
-        <App />
-    </LocaleProvider>
-);
+() => {
+    return (
+        <LocaleProvider locale={en_GB}>
+            {/* eslint-disable-next-line react/jsx-no-undef */}
+            <App />
+        </LocaleProvider>
+    );
+};
 ```
 
 ## 代码示例
@@ -108,6 +112,7 @@ import th_TH from '@douyinfe/semi-ui/lib/es/locale/source/th_TH';
 import tr_TR from '@douyinfe/semi-ui/lib/es/locale/source/tr_TR';
 import pt_BR from '@douyinfe/semi-ui/lib/es/locale/source/pt_BR';
 import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
 import { LocaleProvider, ConfigProvider, Pagination, Modal, Button, Select, Cascader, DatePicker, TreeSelect, Table, TimePicker, List, Calendar, Typography } from '@douyinfe/semi-ui';
 
 class I18nDemo extends React.Component {
@@ -135,6 +140,7 @@ class I18nDemo extends React.Component {
             'tr_TR': tr_TR,
             'pt_BR': pt_BR,
             'zh_TW': zh_TW,
+            'es': es,
         };
         this.setState({ locale: language[code], localeCode: code });
     }
@@ -275,6 +281,7 @@ class I18nDemo extends React.Component {
                         <Select.Option value='tr_TR'>土耳其语</Select.Option>
                         <Select.Option value='pt_BR'>葡萄牙语(巴西)</Select.Option>
                         <Select.Option value='zh_TW'>繁体中文</Select.Option>
+                        <Select.Option value='es'>西班牙语</Select.Option>
                     </Select>
                 </div>
                 <LocaleProvider locale={locale}>

+ 12 - 10
content/show/timeline/index-en-US.md

@@ -190,16 +190,18 @@ import { IconAlertTriangle } from '@douyinfe/semi-icons';
 
 ### TimeLine.Item
 
-| Properties | Instruction                                              | type                                              | Default   |
-| ---------- | -------------------------------------------------------- | ------------------------------------------------- | --------- |
-| className  | Class name                                               | string                                            | -         |
-| color      | Color of dot                                             | string                                            | -         |
-| dot        | Custom dot                                               | ReactNode                                         | -         |
-| extra      | Custom extra content                                     | ReactNode                                         | -         |
-| position   | Custom node location to override TimeLine's mode setting | `left`\|`right`                                     | -         |
-| style      | Inline style                                             | CSSProperties                                           | -         |
-| time       | Time value                                               | ReactNode                                              | -         |
-| type       | Pattern of dot                                           | `default`\|`ongoing`\|`success`\|`warning`\|`error` | `default` |
+| Properties | Instruction                                              | type                                                | Default   | Version   |
+| ---------- | -------------------------------------------------------- | --------------------------------------------------- | --------- | --------- |
+| className  | Class name                                               | string                                              | -         | -         |
+| color      | Color of dot                                             | string                                              | -         | -         |
+| dot        | Custom dot                                               | React Node                                          | -         | -         |
+| extra      | Custom extra content                                     | React Node                                          | -         | -         |
+| position   | Custom node location to override TimeLine's mode setting | `left`\|`right`                                     | -         | -         |
+| style      | Inline style                                             | CSSProperties                                       | -         | -         |
+| time       | Time value                                               | string                                              | -         | -         |
+| type       | Pattern of dot                                           | `default`\|`ongoing`\|`success`\|`warning`\|`error` | `default` | -         |
+| onClick    | Click event                                              | (e: MouseEvent) => void                             | -         | 2.2.0     |
+
 
 ## Design Tokens
 <DesignToken/>

+ 12 - 10
content/show/timeline/index.md

@@ -232,16 +232,18 @@ import { IconAlertTriangle } from '@douyinfe/semi-icons';
 
 ### TimeLine.Item
 
-| 属性 | 说明 | 类型 | 默认值 |
-| --- | --- | --- | --- |
-| className | 类名 | string | - |
-| color | 自定义的圆圈色值 | string | - |
-| dot | 自定义时间轴点 | ReactNode | - |
-| extra | 自定义辅助内容 | ReactNode | - |
-| position | 自定义节点位置,可以覆盖 TimeLine 的模式选项 | `left`\|`right` | - |
-| style | 样式 | CSSProperties | - |
-| time | 时间文本 | ReactNode | - |
-| type | 当前圆圈的模式 | `default`\|`ongoing`\|`success`\|`warning`\|`error` | `default` |
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| className | 类名 | string | - | - |
+| color | 自定义的圆圈色值 | string | - | - |
+| dot | 自定义时间轴点 | ReactNode | - | - |
+| extra | 自定义辅助内容 | ReactNode | - | - |
+| position | 自定义节点位置,可以覆盖 TimeLine 的模式选项 | `left`\|`right` | - | - |
+| style | 样式 | CSSProperties | - | - |
+| time | 时间文本 | string | - | - |
+| type | 当前圆圈的模式 | `default`\|`ongoing`\|`success`\|`warning`\|`error` | `default` | - |
+| onClick | 鼠标点击事件的回调 | (e: MouseEvent) => void | - | 2.2.0 |
+
 
 ## 设计变量
 

+ 43 - 0
content/start/changelog/index-en-US.md

@@ -16,6 +16,49 @@ Version:Major.Minor.Patch
 
 ---
 
+#### 🎉 2.2.0-beta.1 (2021-12-23)
+
+- 【Fix】
+     - Fixed DatePicker input illegal year causing component crash [#422](https://github.com/DouyinFE/semi-design/issues/422)
+     - Fixed the content penetration caused by transparent background color in Notification when `theme='light'` [#430](https://github.com/DouyinFE/semi-design/issues/430)
+     - Fixed the issue of `@charset utf-8` related warning when Vite builds CSS [#403](https://github.com/DouyinFE/semi-design/issues/403)
+     - Fixed the problem that the data in the drop-down list is not displayed correctly after using `backSpace` to delete the selected item in Select multiple selection [#444](https://github.com/DouyinFE/semi-design/issues/444)
+     - Fix Empty display `this.updateMode` `undefined` problem when switching dark mode [#452](https://github.com/DouyinFE/semi-design/issues/452)
+     - Fixed the responsive error reporting issue of `Safari<=13` version [#442](https://github.com/DouyinFE/semi-design/issues/442)
+
+#### 🎉 2.2.0-beta.0 (2021-12-17)
+
+- 【Feat】
+    - Timeline.Item supports `onClick` [#402](https://github.com/DouyinFE/semi-design/issues/402)
+    - Cascader
+        - Support only echoing leaf nodes with `leafOnly` API [#256](https://github.com/DouyinFE/semi-design/issues/256)
+        - Support click the leaf node to select it in multiple selection with `enableLeafClick` API [#302](https://github.com/DouyinFE/semi-design/issues/302) [@btea](https://github.com/btea)
+        - Support custom separator with `separator` API [#408](https://github.com/DouyinFE/semi-design/issues/408)
+    - Upload [#342](https://github.com/DouyinFE/semi-design/issues/342)
+        - Support `ref.current.insert` method
+        - Support props `showPicInfo`
+        - Use `gap` to control FileCard interval
+    - Icon [#260](https://github.com/DouyinFE/semi-design/issues/260)
+        - Add `double_chevron_left`, `double_chevron_right` icons
+        - Icon supports following the current context font size
+    - LocaleProvider added `Spanish` language pack [@chenjunxyf](https://github.com/chenjunxyf)
+    - Select added `inputProps`, which is convenient for users to realize some special functions when filter is true. For example, incoming `onCompositionEnd`, `onKeyDown` event monitoring, etc.
+    - DatePicker [#260](https://github.com/DouyinFE/semi-design/issues/260)
+        - Add year switch buttons
+        - Optimize the scope selection interaction logic to avoid scenes where the two panels are the same month
+- 【Fix】
+    - Select
+        - Fixed the issue of key warning when `isRenderInTag` returned by Select `renderSelectedItem` is `false` [#320](https://github.com/DouyinFE/semi-design/issues/320)
+        - Fixed the problem that the `mark` tag `key` is missing when the warning prompts the mark when searching for the highlighted keyword in Select
+    - Fixed that the label style does not match the design draft when Cascader is `multiple`+`disabled` [#400](https://github.com/DouyinFE/semi-design/issues/400)
+    - Fixed the problem of incorrect rendering when the key or itemKey is node when Description `type='plain'` [#406](https://github.com/DouyinFE/semi-design/issues/406)
+    - Fixed the issue that when Pagination uses `hideOnSingePage` and `showSizeChanger` at the same time, when the total number of pages is only 1, the `sizeChanger` will disappear and can no longer be switched [#252](https://github.com/DouyinFE/semi-design/issues/252)
+    - Fixed the issue that the design token of the Select component does not take effect when defining the Select component through Webpack plugin variables [#375](https://github.com/DouyinFE/semi-design/issues/375) [@summerstream](https://github.com/summerstream)
+    - Fixed the UI error after setting the `size` of the Rating component to `number`
+    - Fixed the horizontal alignment of Timeline custom dot [#395](https://github.com/DouyinFE/semi-design/issues/395) [@chenc041](https://github.com/chenc041)
+- 【Docs】
+    - Improve `semi-ui` package.json [@chenc041](https://github.com/chenc041)
+
 #### 🎉 2.1.5 (2021-12-10)
 
 - 【Fix】

+ 43 - 0
content/start/changelog/index.md

@@ -15,6 +15,49 @@ Semi 版本号遵循**Semver**规范(主版本号-次版本号-修订版本号
 
 ---
 
+#### 🎉 2.2.0-beta.1 (2021-12-23)
+
+- 【Fix】
+    - 修复 DatePicker 输入非法年份导致组件崩溃问题 [#422](https://github.com/DouyinFE/semi-design/issues/422)
+    - 修复 Notification 多色模式下,背景色透明导致的内容穿透 [#430](https://github.com/DouyinFE/semi-design/issues/430)
+    - 修复 Vite 构建 CSS 时抛出 @charset utf-8 相关 warning 的问题 [#403](https://github.com/DouyinFE/semi-design/issues/403)
+    - 修复 Select 多选使用 backSpace 删除已选项后,下拉列表数据显示不正确的问题 [#444](https://github.com/DouyinFE/semi-design/issues/444)
+    - 修复 Empty 在切换暗色模式时显示 this.updateMode 未定义问题 [#452](https://github.com/DouyinFE/semi-design/issues/452)
+    - 修复 Safari<=13 版本的响应式报错问题 [#442](https://github.com/DouyinFE/semi-design/issues/442)
+
+#### 🎉 2.2.0-beta.0 (2021-12-17)
+
+- 【Feat】
+    - Timeline.Item 支持 onClick [#402](https://github.com/DouyinFE/semi-design/issues/402)
+    - Cascader 
+        - 支持仅回显叶子节点,提供 leafOnly API [#256](https://github.com/DouyinFE/semi-design/issues/256)
+        - 支持多选时点击叶子节点即可选中,提供 enableLeafClick API [#302](https://github.com/DouyinFE/semi-design/issues/302) [@btea](https://github.com/btea)
+        - 支持自定义分隔符,提供 separator API [#408](https://github.com/DouyinFE/semi-design/issues/408)
+    - Upload [#342](https://github.com/DouyinFE/semi-design/issues/342)
+        - 支持通过 ref 调用 insert 方法 
+        - 支持 props showPicInfo
+        - 使用 gap 控制 FileCard 间隔
+    - Icon [#260](https://github.com/DouyinFE/semi-design/issues/260)
+        - 添加 double_chevron_left,double_chevron_right 图标
+        - Icon 支持跟随当前上下文字体大小
+    - LocaleProvider 新增西班牙语语言包 [@chenjunxyf](https://github.com/chenjunxyf)
+    - Select 新增 inputProps ,便于用户在 filter 为 true 时可实现一些特殊功能。例如传入 onCompositionEnd,onKeyDown 事件监听等
+    - DatePicker [#260](https://github.com/DouyinFE/semi-design/issues/260)
+        - 新增年份切换按钮
+        - 优化范围选择交互逻辑,避免出现两个面板是相同月份场景
+- 【Fix】
+    - Select
+        - 修复 Select renderSelectedItem 返回的 isRenderInTag 为 false 时会报 key 的 warning 的问题 [#320](https://github.com/DouyinFE/semi-design/issues/320)
+        - 修复 Select 搜索高亮关键字时 warning 提示 mark 标签 key 缺失的问题
+    - 修复 Cascader multiple+disabled 时标签样式与设计稿不符 [#400](https://github.com/DouyinFE/semi-design/issues/400)
+    - 修复 Description type='plain' 时,key 或 itemKey 为 node 时渲染不正确的问题 [#406](https://github.com/DouyinFE/semi-design/issues/406)
+    - 修复 Pagination 同时使用 hideOnSingePage 与 showSizeChanger 时,总页数只有1时,sizeChanger 会消失无法再切换的问题 [#252](https://github.com/DouyinFE/semi-design/issues/252)
+    - 修复 通过 webpack plugin variables 方式定义 Select 组件 Design Token 时不生效的问题 [#375](https://github.com/DouyinFE/semi-design/issues/375) [@summerstream](https://github.com/summerstream)
+    - 修复 Rating 组件设置 size 为 number 后 UI 错误
+    - 修复 Timeline 自定义 dot 水平对齐的问题 [#395](https://github.com/DouyinFE/semi-design/issues/395) [@chenc041](https://github.com/chenc041)
+- 【Docs】
+    - 完善 semi-ui package.json [@chenc041](https://github.com/chenc041)
+
 #### 🎉 2.1.5 (2021-12-10)
 
 - 【Fix】

+ 3 - 3
gatsby-config.js

@@ -36,9 +36,9 @@ module.exports = {
             options: {
                 extensions: ['.mdx', '.md'],
                 gatsbyRemarkPlugins: [
-                    {
-                        resolve: require.resolve('./plugins/gatsby-remark-unwrap'),
-                    },
+                    // {
+                    //     resolve: require.resolve('./plugins/gatsby-remark-unwrap'),
+                    // },
                     {
                         resolve: require.resolve('./plugins/gatsby-remark-wrap-in-section'),
                     },

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.1.5"
+    "version": "2.2.0-beta.1"
 }

+ 3 - 1
package.json

@@ -39,7 +39,7 @@
     "@douyinfe/semi-site-banner": "0.0.1",
     "@douyinfe/semi-site-doc-style": "0.0.1",
     "@douyinfe/semi-site-header": "0.0.3",
-    "@douyinfe/semi-site-markdown-blocks": "0.0.2",
+    "@douyinfe/semi-site-markdown-blocks": "0.0.1",
     "@mdx-js/react": "^1.6.22",
     "@svgr/core": "^5.5.0",
     "aos": "^2.3.4",
@@ -108,6 +108,7 @@
     "@commitlint/config-conventional": "^7.6.0",
     "@octokit/rest": "^18.12.0",
     "@shopify/jest-dom-mocks": "^2.11.7",
+    "@storybook/addon-a11y": "^6.3.12",
     "@storybook/addon-actions": "^6.3.7",
     "@storybook/addon-knobs": "^6.3.1",
     "@storybook/builder-webpack5": "^6.4.0-alpha.29",
@@ -150,6 +151,7 @@
     "eslint-plugin-import": "^2.24.0",
     "eslint-plugin-jest": "^24.4.0",
     "eslint-plugin-markdown": "^2.2.1",
+    "eslint-plugin-jsx-a11y": "^6.5.1",
     "eslint-plugin-react": "^7.24.0",
     "eslint-plugin-react-hooks": "^4.2.0",
     "fs-extra": "^8.1.0",

+ 4 - 4
packages/semi-animation-react/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-react",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "motion library for semi-ui-react",
   "keywords": [
     "motion",
@@ -26,8 +26,8 @@
   },
   "dependencies": {
     "@babel/runtime-corejs3": "^7.15.4",
-    "@douyinfe/semi-animation": "2.1.5",
-    "@douyinfe/semi-animation-styled": "2.1.5",
+    "@douyinfe/semi-animation": "2.2.0-beta.1",
+    "@douyinfe/semi-animation-styled": "2.2.0-beta.1",
     "classnames": "^2.2.6"
   },
   "peerDependencies": {
@@ -51,5 +51,5 @@
     "prop-types": "15.7.2",
     "react-storybook-addon-props-combinations": "^1.1.0"
   },
-  "gitHead": "5344f767711f1677a6113bc7fc38d1853bcc7f5a"
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 2 - 2
packages/semi-animation-styled/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation-styled",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "semi styled animation",
   "keywords": [
     "semi",
@@ -42,5 +42,5 @@
     "gulp-typescript": "^6.0.0-alpha.1",
     "merge2": "^1.4.1"
   },
-  "gitHead": "5344f767711f1677a6113bc7fc38d1853bcc7f5a"
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 2 - 2
packages/semi-animation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-animation",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "animation base library for semi-ui",
   "keywords": [
     "animation",
@@ -44,5 +44,5 @@
     "merge2": "^1.4.1",
     "react-storybook-addon-props-combinations": "^1.1.0"
   },
-  "gitHead": "5344f767711f1677a6113bc7fc38d1853bcc7f5a"
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

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

@@ -116,7 +116,7 @@ $module: #{$prefix}-cascader;
                 margin-left: 0;
             }
 
-            &-disabled {
+            &-disabled.#{$prefix}-tag {
                 color: $color-cascader_input_disabled-text-default;
                 cursor: not-allowed;
                 

+ 4 - 0
packages/semi-foundation/cascader/constants.ts

@@ -12,6 +12,10 @@ const strings = {
     IS_VALUE: 'isValue',
     SHOW_NEXT_BY_CLICK: 'click',
     SHOW_NEXT_BY_HOVER: 'hover',
+    /* Merge Type */
+    LEAF_ONLY_MERGE_TYPE: 'leafOnly',
+    AUTO_MERGE_VALUE_MERGE_TYPE: 'autoMergeValue',
+    NONE_MERGE_TYPE: 'none',
 } as const;
 
 const numbers = {};

+ 29 - 15
packages/semi-foundation/cascader/foundation.ts

@@ -14,8 +14,11 @@ import {
     convertDataToEntities,
     findKeysForValues,
     normalizedArr,
-    isValid
+    isValid,
+    calcMergeType
 } from './util';
+import { strings } from './constants';
+
 export interface BasicData {
     data: BasicCascaderData;
     disabled: boolean;
@@ -99,6 +102,7 @@ export interface BasicScrollPanelProps {
 export interface BasicCascaderProps {
     mouseEnterDelay?: number;
     mouseLeaveDelay?: number;
+    separator?: string;
     arrowIcon?: any;
     changeOnSelect?: boolean;
     multiple?: boolean;
@@ -138,6 +142,8 @@ export interface BasicCascaderProps {
     topSlot?: any;
     showNext?: ShowNextType;
     disableStrictly?: boolean;
+    leafOnly?: boolean;
+    enableLeafClick?: boolean;
     onClear?: () => void;
     triggerRender?: (props: BasicTriggerRenderProps) => any;
     onListScroll?: (e: any, panel: BasicScrollPanelProps) => void;
@@ -168,7 +174,7 @@ export interface BasicCascaderInnerData {
     isHovering: boolean;
     checkedKeys: Set<string>;
     halfCheckedKeys: Set<string>;
-    mergedCheckedKeys: Set<string>;
+    resolvedCheckedKeys: Set<string>;
     loadedKeys: Set<string>;
     loadingKeys: Set<string>;
     loading: boolean;
@@ -601,10 +607,11 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
     }
 
     _defaultRenderText(path: any[], displayRender?: BasicCascaderProps['displayRender']) {
+        const separator = this.getProp('separator');
         if (displayRender && typeof displayRender === 'function') {
             return displayRender(path);
         } else {
-            return path.join(' / ');
+            return path.join(separator);
         }
     }
 
@@ -649,7 +656,7 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
     }
 
     handleSingleSelect(e: any, item: BasicEntity | BasicData) {
-        const { changeOnSelect: allowChange, filterLeafOnly, multiple } = this.getProps();
+        const { changeOnSelect: allowChange, filterLeafOnly, multiple, enableLeafClick } = this.getProps();
         const { keyEntities, selectedKeys, isSearching } = this.getStates();
         const filterable = this._isFilterable();
         const { data, key } = item;
@@ -667,6 +674,9 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
         }
         if (multiple) {
             this._adapter.updateStates({ activeKeys: new Set(activeKeys) });
+            if (isLeaf && enableLeafClick) {
+                this.onItemCheckboxClick(item);
+            }
         } else {
             this._adapter.notifySelect(data.value);
             if (hasChanged) {
@@ -703,8 +713,8 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
 
     _handleMultipleSelect(item: BasicEntity | BasicData) {
         const { key } = item;
-        const { checkedKeys, keyEntities, mergedCheckedKeys } = this.getStates();
-        const { autoMergeValue, max, disableStrictly } = this.getProps();
+        const { checkedKeys, keyEntities, resolvedCheckedKeys } = this.getStates();
+        const { autoMergeValue, max, disableStrictly, leafOnly } = this.getProps();
         // prev checked status
         const prevCheckedStatus = checkedKeys.has(key);
         // next checked status
@@ -719,18 +729,22 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
             this.calcNonDisabedCheckedKeys(key, curCheckedStatus) :
             this.calcCheckedKeys(key, curCheckedStatus);
 
-        const curMergedCheckedKeys = new Set(normalizeKeyList(curCheckedKeys, keyEntities));
+        const mergeType = calcMergeType(autoMergeValue, leafOnly);
+        const isLeafOnlyMerge = mergeType === strings.LEAF_ONLY_MERGE_TYPE;
+        const isNoneMerge = mergeType === strings.NONE_MERGE_TYPE;
+
+        const curResolvedCheckedKeys = new Set(normalizeKeyList(curCheckedKeys, keyEntities, isLeafOnlyMerge));
 
-        const curRealCheckedKeys = autoMergeValue ?
-            curMergedCheckedKeys :
-            curCheckedKeys;
+        const curRealCheckedKeys = isNoneMerge
+            ? curCheckedKeys
+            : curResolvedCheckedKeys;
 
         if (isNumber(max)) {
-            if (autoMergeValue) {
+            if (!isNoneMerge) {
                 // When it exceeds max, the quantity is allowed to be reduced, and no further increase is allowed
-                if (mergedCheckedKeys.size < curMergedCheckedKeys.size && curMergedCheckedKeys.size > max) {
+                if (resolvedCheckedKeys.size < curResolvedCheckedKeys.size && curResolvedCheckedKeys.size > max) {
                     const checkedEntities: BasicEntity[] = [];
-                    curMergedCheckedKeys.forEach(itemKey => {
+                    curResolvedCheckedKeys.forEach(itemKey => {
                         checkedEntities.push(keyEntities[itemKey]);
                     });
                     this._adapter.notifyOnExceed(checkedEntities);
@@ -752,7 +766,7 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
             this._adapter.updateStates({
                 checkedKeys: curCheckedKeys,
                 halfCheckedKeys: curHalfCheckedKeys,
-                mergedCheckedKeys: curMergedCheckedKeys
+                resolvedCheckedKeys: curResolvedCheckedKeys
             });
         }
 
@@ -868,7 +882,7 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
             newState.halfCheckedKeys = new Set([]);
             newState.selectedKeys = new Set([]);
             newState.activeKeys = new Set([]);
-            newState.mergedCheckedKeys = new Set([]);
+            newState.resolvedCheckedKeys = new Set([]);
             this._adapter.notifyChange([]);
         } else {
             // if click clearBtn when not searching, clear selected and active values as well

+ 13 - 0
packages/semi-foundation/cascader/util.ts

@@ -3,6 +3,7 @@ import {
     isUndefined,
     isEqual
 } from 'lodash';
+import { strings } from './constants';
 
 function getPosition(level: any, index: any) {
     return `${level}-${index}`;
@@ -79,4 +80,16 @@ export function findKeysForValues(value: any, keyEntities: any) {
         .filter((item: any) => isEqual(item.valuePath, valuePath))
         .map((item: any) => item.key);
     return res;
+}
+
+export function calcMergeType(autoMergeValue: boolean, leafOnly: boolean): string {
+    let mergeType: string;
+    if (leafOnly) {
+        mergeType = strings.LEAF_ONLY_MERGE_TYPE;
+    } else if (autoMergeValue) {
+        mergeType = strings.AUTO_MERGE_VALUE_MERGE_TYPE;
+    } else {
+        mergeType = strings.NONE_MERGE_TYPE;
+    }
+    return mergeType;
 }

+ 4 - 3
packages/semi-foundation/datePicker/_utils/parser.ts

@@ -2,7 +2,7 @@
  * @file
  * Various date-related analysis methods
  */
-import { isValid, parseISO, parse } from 'date-fns';
+import { isValid, parseISO, parse, Locale } from 'date-fns';
 
 /**
  * Parsing value to Date object
@@ -11,7 +11,7 @@ export function compatiableParse(
     value: string,
     formatToken?: string,
     baseDate?: Date,
-    locale?: any
+    locale?: Locale
 ): Date | null {
     let result = null;
     if (value) {
@@ -25,7 +25,8 @@ export function compatiableParse(
         if (!isValid(result)) {
             result = new Date(Date.parse(value));
         }
-        if (!isValid(result)) {
+        const yearInvalid = isValid(result) && String(result.getFullYear()).length > 4;
+        if (!isValid(result) || yearInvalid) {
             result = null;
         }
     }

+ 18 - 0
packages/semi-foundation/datePicker/datePicker.scss

@@ -94,6 +94,15 @@ $module: #{$prefix}-datepicker;
                 min-height: $height-datepicker_timepicker_header_min;
             }
         }
+
+        // 为了防止 scrollList 因为 weeks 变化高度发生变化导致年月可能发生滚动
+        // In order to prevent scrollList from scrolling due to changes in the height of weeks, the year and month may be scrolled
+        &[x-panel-yearandmonth-open-type="left"],
+        &[x-panel-yearandmonth-open-type="right"] {
+            .#{$module}-weeks {
+                min-height: 6 * $width-datepicker_day;
+            }
+        }
     }
 
     // 年月选择器
@@ -833,6 +842,15 @@ $module: #{$prefix}-datepicker;
                 }
             }
         }
+
+        // 为了防止 scrollList 因为 weeks 变化高度发生变化导致年月可能发生滚动
+        // In order to prevent scrollList from scrolling due to changes in the height of weeks, the year and month may be scrolled
+        &[x-panel-yearandmonth-open-type="left"],
+        &[x-panel-yearandmonth-open-type="right"] {
+            .#{$module}-weeks {
+                min-height: 6 * $width-datepicker_day_compact;
+            }
+        }
     }
 
     // 年月选择器

+ 101 - 8
packages/semi-foundation/datePicker/monthsGridFoundation.ts

@@ -47,6 +47,7 @@ interface MonthsGridElementProps {
 }
 
 export type PanelType = 'left' | 'right';
+export type YearMonthChangeType = 'prevMonth' | 'nextMonth' | 'prevYear' | 'nextYear';
 
 export interface MonthsGridFoundationProps extends MonthsGridElementProps {
     type?: Type;
@@ -268,9 +269,65 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     destroy() { }
 
+    /**
+     * sync change another panel month when change months from the else yam panel
+     * call it when
+     *  - current change panel targe date month is same with another panel date
+     * 
+     * @example
+     *  - panelType=right, target=new Date('2022-09-01') and left panel is in '2022-09' => call it, left panel minus one month to '2022-08'
+     *  - panelType=left, target=new Date('2021-12-01') and right panel is in '2021-12' => call it, right panel add one month to '2021-01'
+     */
+    handleSyncChangeMonths(options: { panelType: PanelType, target: Date }) {
+        const { panelType, target } = options;
+        const { type } = this._adapter.getProps();
+        const { monthLeft, monthRight } = this._adapter.getStates();
+        if (this.isRangeType(type)) {
+            if (panelType === 'right' && differenceInCalendarMonths(target, monthLeft.pickerDate) === 0) {
+                this.handleYearOrMonthChange('prevMonth', 'left', 1, true);
+            } else if (panelType === 'left' && differenceInCalendarMonths(monthRight.pickerDate, target) === 0) {
+                this.handleYearOrMonthChange('nextMonth', 'right', 1, true);
+            }
+        }
+    }
+
+    /**
+     * Get the target date based on the panel type and switch type
+     */
+    getTargetChangeDate(options: { panelType: PanelType, switchType: YearMonthChangeType }) {
+        const { panelType, switchType } = options;
+        const { monthRight, monthLeft } = this._adapter.getStates();
+        const currentDate = panelType === 'left' ? monthLeft.pickerDate : monthRight.pickerDate;
+        let target: Date;
+        
+        switch (switchType) {
+            case 'prevMonth':
+                target = addMonths(currentDate, -1);
+                break;
+            case 'nextMonth':
+                target = addMonths(currentDate, 1);
+                break;
+            case 'prevYear':
+                target = addYears(currentDate, -1);
+                break;
+            case 'nextYear':
+                target = addYears(currentDate, 1);
+                break;
+        }
+        return target;
+    }
+
+    /**
+     * Change month by yam panel
+     */
     toMonth(panelType: PanelType, target: Date) {
+        const { type } = this._adapter.getProps();
         const diff = this._getDiff('month', target, panelType);
         this.handleYearOrMonthChange(diff < 0 ? 'prevMonth' : 'nextMonth', panelType, Math.abs(diff), false);
+    
+        if (this.isRangeType(type)) {
+            this.handleSyncChangeMonths({ panelType, target });
+        }
     }
 
     toYear(panelType: PanelType, target: Date) {
@@ -289,30 +346,43 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
         return typeof realType === 'string' && /range/i.test(realType);
     }
 
-    handleSwitchMonth(switchType: 'prevMonth' | 'nextMonth', panelType: PanelType) {
+    handleSwitchMonthOrYear(switchType: YearMonthChangeType, panelType: PanelType) {
         const { type, syncSwitchMonth } = this.getProps();
-        if (this.isRangeType(type) && syncSwitchMonth) {
+        const rangeType = this.isRangeType(type);
+
+        // range type and syncSwitchMonth, we should change panels at same time
+        if (rangeType && syncSwitchMonth) {
             this.handleYearOrMonthChange(switchType, 'left', 1, true);
             this.handleYearOrMonthChange(switchType, 'right', 1, true);
         } else {
             this.handleYearOrMonthChange(switchType, panelType);
+
+            /**
+             * default behavior (v2.2.0)
+             * In order to prevent the two panels from being the same month, this will confuse the user when selecting the range
+             * https://github.com/DouyinFE/semi-design/issues/260
+             */
+            if (rangeType) {
+                const target = this.getTargetChangeDate({ panelType, switchType });
+                this.handleSyncChangeMonths({ panelType, target });
+            }
         }
     }
 
     prevMonth(panelType: PanelType) {
-        this.handleSwitchMonth('prevMonth', panelType);
+        this.handleSwitchMonthOrYear('prevMonth', panelType);
     }
 
     nextMonth(panelType: PanelType) {
-        this.handleSwitchMonth('nextMonth', panelType);
+        this.handleSwitchMonthOrYear('nextMonth', panelType);
     }
 
     prevYear(panelType: PanelType) {
-        this.handleYearOrMonthChange('prevYear', panelType);
+        this.handleSwitchMonthOrYear('prevYear', panelType);
     }
 
     nextYear(panelType: PanelType) {
-        this.handleYearOrMonthChange('nextYear', panelType);
+        this.handleSwitchMonthOrYear('nextYear', panelType);
     }
 
     /**
@@ -414,7 +484,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
     }
 
     handleYearOrMonthChange(
-        type: 'prevMonth' | 'nextMonth' | 'prevYear' | 'nextYear',
+        type: YearMonthChangeType,
         panelType: PanelType = strings.PANEL_TYPE_LEFT,
         step = 1,
         notSeparateInRange = false
@@ -458,7 +528,7 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
      * @param {*} targetDate
      */
     updateDateAfterChangeYM(
-        type: 'prevMonth' | 'nextMonth' | 'prevYear' | 'nextYear',
+        type: YearMonthChangeType,
         targetDate: Date
     ) {
         const { multiple, disabledDate } = this.getProps();
@@ -839,4 +909,27 @@ export default class MonthsGridFoundation extends BaseFoundation<MonthsGridAdapt
     showDatePanel(panelType: PanelType) {
         this._updatePanelDetail(panelType, { isTimePickerOpen: false, isYearPickerOpen: false });
     }
+
+    /**
+     * Get year and month panel open type
+     * 
+     * It is useful info to set minHeight of weeks.
+     *  - When yam open type is 'left' or 'right', weeks minHeight should be set
+     *    If the minHeight is not set, the change of the number of weeks will cause the scrollList to be unstable
+     */
+    getYAMOpenType() {
+        const { monthLeft, monthRight } = this._adapter.getStates();
+        const leftYearPickerOpen = monthLeft.isYearPickerOpen;
+        const rightYearPickerOpen = monthRight.isYearPickerOpen;
+
+        if (leftYearPickerOpen && rightYearPickerOpen) {
+            return 'both';
+        } else if (leftYearPickerOpen) {
+            return 'left';
+        } else if (rightYearPickerOpen) {
+            return 'right';
+        } else {
+            return 'none';
+        }
+    }
 }

+ 3 - 1
packages/semi-foundation/gulpfile.js

@@ -52,7 +52,9 @@ gulp.task('compileScss', function compileScss() {
                 cb(null, chunk);
             }
         ))
-        .pipe(sass().on('error', sass.logError))
+        .pipe(sass({
+            charset: false
+        }).on('error', sass.logError))
         .pipe(gulp.dest('lib/es'))
         .pipe(gulp.dest('lib/cjs'));
 });

+ 4 - 4
packages/semi-foundation/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.1.5",
+    "version": "2.2.0-beta.1",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
@@ -8,7 +8,7 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation": "2.1.5",
+        "@douyinfe/semi-animation": "2.2.0-beta.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "date-fns": "^2.9.0",
@@ -24,7 +24,7 @@
         "*.scss",
         "*.css"
     ],
-    "gitHead": "661ac2de772e28c3b6f934f70e4e08efbe2387b2",
+    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a",
     "devDependencies": {
         "@babel/plugin-proposal-decorators": "^7.15.8",
         "@babel/plugin-transform-runtime": "^7.15.8",
@@ -38,7 +38,7 @@
         "gulp-sass": "^5.0.0",
         "gulp-typescript": "^6.0.0-alpha.1",
         "merge2": "^1.4.1",
-        "sass": "1.32.13",
+        "sass": "1.45.0",
         "through2": "^4.0.2"
     }
 }

+ 11 - 9
packages/semi-foundation/select/foundation.ts

@@ -785,15 +785,17 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
     }
 
     _removeInternalKey(option: BasicOptionProps) {
-        delete option._parentGroup;
-        delete option._show;
-        delete option._selected;
-        delete option._scrollIndex;
-        if ('_keyInOptionList' in option) {
-            option.key = option._keyInOptionList;
-            delete option._keyInOptionList;
-        }
-        return option;
+        // eslint-disable-next-line
+        let newOption = { ...option };
+        delete newOption._parentGroup;
+        delete newOption._show;
+        delete newOption._selected;
+        delete newOption._scrollIndex;
+        if ('_keyInOptionList' in newOption) {
+            newOption.key = newOption._keyInOptionList;
+            delete newOption._keyInOptionList;
+        }
+        return newOption;
     }
 
     _notifySelect(value: BasicOptionProps['value'], option: BasicOptionProps) {

+ 6 - 2
packages/semi-foundation/tree/treeUtil.ts

@@ -427,7 +427,7 @@ export function normalizedArr(val: any) {
 }
 
 export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false) {
-    let res: string[] = [];
+    const res: string[] = [];
     const keyListSet = new Set(keyList);
     if (!leafOnly) {
         keyList.forEach((key: string) => {
@@ -441,7 +441,11 @@ export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnl
             res.push(key);
         });
     } else {
-        res = (Array.from(keyList) as string[]).filter(key => keyEntities[key] && !isValid(keyEntities[key].children));
+        keyList.forEach(key => {
+            if (keyEntities[key] && !isValid(keyEntities[key].children)) {
+                res.push(key);
+            }
+        });
     }
     return res;
 }

+ 81 - 0
packages/semi-foundation/upload/foundation.ts

@@ -320,6 +320,87 @@ class UploadFoundation<P = Record<string, any>, S = Record<string, any>> extends
         });
     }
 
+    // 插入多个文件到指定位置
+    // Insert files to the specified location
+    insertFileToList(files: Array<CustomFile>, index:number): void {
+        const { limit, transformFile, accept, uploadTrigger } = this.getProps();
+        const { fileList } = this.getStates();
+
+        const unAcceptFileList = [];
+
+        // 当次选中的文件
+        // current selected file
+        let currentFileList = Array.from(files);
+        if (typeof accept !== 'undefined') {
+            currentFileList = currentFileList.filter(item => {
+                const isValid = this.checkFileFormat(accept, item);
+                if (!isValid) {
+                    unAcceptFileList.push(item);
+                }
+                return isValid;
+            });
+            if (unAcceptFileList.length !== 0) {
+                this._adapter.notifyAcceptInvalid(unAcceptFileList);
+            }
+            if (currentFileList.length === 0) {
+                return;
+            }
+        }
+        currentFileList = currentFileList.map(file => {
+            if (!file.uid) {
+                file.uid = getUuidv4();
+            }
+
+            if (this.checkFileSize(file)) {
+                file._sizeInvalid = true;
+                file.status = FILE_STATUS_VALID_FAIL;
+                this._adapter.notifySizeError(file, fileList);
+            }
+
+            if (transformFile) {
+                file = transformFile(file);
+            }
+            return file;
+        });
+        const total = fileList.length + currentFileList.length;
+        if (typeof limit !== 'undefined') {
+            // 判断是否超出限制
+            // Determine whether the limit is exceeded
+            if (total > limit) {
+                if (limit === 1) {
+                    // 使用最后面的文件对当前文件进行替换
+                    // Use the last file to replace the current file
+                    currentFileList = currentFileList.slice(-1);
+                    this._adapter.notifyFileSelect(currentFileList);
+                    this._adapter.resetInput();
+                    this.replaceFileList(currentFileList);
+                    return;
+                }
+                // 如果超出了限制,则计算还能添加几个文件,将剩余的文件继续上传
+                // If the limit is exceeded, several files can be added to the calculation, and the remaining files will continue to be uploaded
+                const restNum = limit - fileList.length;
+                currentFileList = currentFileList.slice(0, restNum);
+                this._adapter.notifyExceed(currentFileList);
+            }
+        }
+
+        const fileItemList = currentFileList.map(file => this.buildFileItem(file, uploadTrigger));
+        const newFileList = fileList.slice();
+        if (typeof index !== 'undefined') {
+            newFileList.splice(index, 0, ...fileItemList);
+        } else {
+            newFileList.push(...fileItemList);
+        }
+
+        this._adapter.notifyFileSelect(currentFileList);
+        this._adapter.notifyChange({ fileList: newFileList, currentFile: null });
+        this._adapter.updateFileList(newFileList, () => {
+            if (uploadTrigger === TRIGGER_AUTO) {
+                this.startUpload(fileItemList);
+            }
+        });
+    }
+
     manualUpload(): void {
         // find the list of files that have not been uploaded
         const waitToUploadFileList = this.getState('fileList').filter((item: BaseFileItem) => item.status === FILE_STATUS_WAIT_UPLOAD);

+ 0 - 4
packages/semi-foundation/upload/rtl.scss

@@ -22,8 +22,6 @@ $module: #{$prefix}-upload;
         }
 
         &-file-card {
-            margin-right: 0;
-            margin-left: $spacing-upload_picture_file_card-marginRight;
 
             &-info {
 
@@ -53,8 +51,6 @@ $module: #{$prefix}-upload;
         &-picture {
 
             &-file-card {
-                margin-right: 0;
-                margin-left: $spacing-upload_picture_file_card-marginRight;
 
                 &-close {
                     right: auto;

+ 31 - 8
packages/semi-foundation/upload/upload.scss

@@ -99,6 +99,8 @@ $module: #{$prefix}-upload;
             display: flex;
             flex-wrap: wrap;
             flex-shrink: 0;
+            gap: $spacing-upload_picture_file_card-gap;
+            margin-bottom: $spacing-upload_picture_file_card-marginBottom;
 
             p {
                 @include ver-center;
@@ -130,8 +132,6 @@ $module: #{$prefix}-upload;
         justify-content: space-between;
         height: $height-upload_file_card;
         width: $width-upload_file_card;
-        margin-right: $spacing-tight;
-        margin-bottom: $spacing-tight;
         background-color: $color-upload_card-bg;
         cursor: pointer;
 
@@ -292,21 +292,21 @@ $module: #{$prefix}-upload;
             flex-direction: column;
 
             .#{$module}-prompt {
-                order: 2;
+                order: 1;
             }
 
             .#{$module}-add {
-                order: 1;
+                order: 0;
             }
         }
 
         &[x-prompt-pos="right"] {
             .#{$module}-prompt {
-                order: 2;
+                order: 1;
             }
 
             .#{$module}-add {
-                order: 1;
+                order: 0;
             }
         }
 
@@ -353,11 +353,14 @@ $module: #{$prefix}-upload;
         }
 
         &-file-card {
+            display: flex;
+            align-items: center;
+            justify-content: center;
             height: $height-upload_file_pic_card;
             width: $width-upload_file_pic_card;
+            border-radius: $radius-upload_picture_file_card_img;
             position: relative;
-            margin-right: $spacing-upload_picture_file_card-marginRight;
-            margin-bottom: $spacing-upload_picture_file_card-marginBottom;
+            overflow: hidden;
 
             img {
                 height: $width-upload_picture_file_card_img;
@@ -429,6 +432,22 @@ $module: #{$prefix}-upload;
                 color: $color-upload_replace-text;
                 transform: translate3D(-50%, -50%, 0);
             }
+            &-pic-info {
+                display: inline-flex;
+                box-sizing: border-box;
+                justify-content: space-between;
+                align-items: center;
+                position: absolute;
+                width: 100%;
+                height: 24px;
+                padding: 0 10px;
+                bottom: 0;
+                left: 0;
+                color: $color-upload_picture_file_card_pic_info-text;
+                font-size: $font-upload_picture_file_card_pic_info-fontSize;
+                font-weight: $font-upload_picture_file_card_pic_info-fontWeight;
+                background: linear-gradient(0deg, rgba(22, 22, 26, 0.3) 0%, rgba(22, 22, 26, 0) 77.08%);
+            }
 
             &-icon-loading,
             &-icon-error {
@@ -445,6 +464,10 @@ $module: #{$prefix}-upload;
             &-show-pointer {
                 cursor: pointer;
             }
+
+            &-error {
+                outline: 1px solid $color-upload_picture_file_card_error-border;
+            }
         }
     }
 

+ 5 - 1
packages/semi-foundation/upload/variables.scss

@@ -24,6 +24,8 @@ $color-upload_pic_add-bg-hover: var(--semi-color-fill-1); // 图片墙上传背
 $color-upload_pic_add-bg: var(--semi-color-fill-0); // 图片墙上传背景色 - 默认
 $color-upload_pic_remove-bg: var(--semi-color-overlay-bg); // 图片墙上传移除图标颜色
 $color-upload_picture_file_card_loading_error-icon: var(--semi-color-danger); // 图片墙上传移除图标颜色
+$color-upload_picture_file_card_error-border: var(--semi-color-danger); // 图片墙上传移除图标颜色
+$color-upload_picture_file_card_pic_info-text: var(--semi-color-white); // 图片墙图片信息(序号)文字颜色
 $color-upload_preview-icon: var(--semi-color-text-2); // 上传文件卡片文本颜色
 $color-upload_retry-text: var(--semi-color-primary); // 上传文件卡片重新上传按钮文本颜色
 $color-upload_replace-text: var(--semi-color-white); // 上传文件卡片重新替换按钮文本颜色
@@ -56,7 +58,7 @@ $spacing-upload_file_card_info_progress-marginTop: 4px; // 上传文件卡片进
 $spacing-upload_file_card_close-marginLeft: $spacing-tight; // 上传文件卡片删除按钮左侧外边距
 $spacing-upload_file_card_close-marginRight: $spacing-tight; // 上传文件卡片删除按钮右侧外边距
 $spacing-upload_file_card_icon-marginRight: $spacing-super-tight; // 上传文件卡片图标右侧外边距
-$spacing-upload_picture_file_card-marginRight: $spacing-tight; // 图片墙上传卡片右侧外边距
+$spacing-upload_picture_file_card-gap: $spacing-tight; // 图片墙卡片之间边距
 $spacing-upload_picture_file_card-marginBottom: $spacing-tight; // 图片墙上传卡片底部外边距
 $spacing-upload_picture_file_card_close-top: 8px; // 图片墙上传卡片删除按钮顶部位置
 $spacing-upload_picture_file_card_close-right: 8px; // 图片墙上传卡片删除右侧位置
@@ -79,3 +81,5 @@ $radius-upload_drag_area: var(--semi-border-radius-small); // 可拖拽上传拖
 $font-upload_file_card_info_name-fontWeight: $font-weight-bold; // 上传文件卡片文件名字重
 $font-upload_file_card_info_size-fontWeight: $font-weight-regular; // 上传文件卡片文件尺寸字重
 $font-upload_drag_area_tips-fontWeight: 600; // 可拖拽上传提示文本字重
+$font-upload_picture_file_card_pic_info-fontSize: 12px; // 图片墙图片信息字体大小
+$font-upload_picture_file_card_pic_info-fontWeight: 600; // 图片墙图片信息文本字重

+ 4 - 3
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "semi icons",
   "keywords": [
     "semi",
@@ -38,7 +38,7 @@
     "@babel/plugin-transform-runtime": "^7.15.8",
     "@babel/preset-env": "^7.15.8",
     "@babel/preset-react": "^7.14.5",
-    "@douyinfe/semi-webpack-plugin": "2.1.5",
+    "@douyinfe/semi-webpack-plugin": "2.2.0-beta.1",
     "babel-loader": "^8.2.2",
     "css-loader": "4.3.0",
     "del": "^6.0.0",
@@ -63,5 +63,6 @@
   "_unpkg": true,
   "unpkgFiles": [
     "*"
-  ]
+  ],
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-icons/src/components/Icon.tsx

@@ -3,7 +3,7 @@ import { BASE_CLASS_PREFIX } from '../env';
 import cls from 'classnames';
 import '../styles/icons.scss';
 
-export type IconSize = 'extra-small' | 'small' | 'default' | 'large' | 'extra-large';
+export type IconSize = 'inherit' | 'extra-small' | 'small' | 'default' | 'large' | 'extra-large';
 
 export interface IconProps extends DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
     svg: ReactNode;

+ 33 - 0
packages/semi-icons/src/icons/IconDoubleChevronLeft.tsx

@@ -0,0 +1,33 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M12.6185 4.39653C13.1272 4.92524 13.1272 5.78245 12.6185 6.31116L7.14483 12L12.6185 17.6888C13.1272 18.2176 13.1272 19.0748 12.6185 19.6035C12.1098 20.1322 11.285 20.1322 10.7763 19.6035L4.38153 12.9573C3.87282 12.4286 3.87282 11.5714 4.38153 11.0427L10.7763 4.39653C11.285 3.86782 12.1098 3.86782 12.6185 4.39653Z"
+                fill="currentColor"
+            />
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M19.6185 4.39653C20.1272 4.92524 20.1272 5.78245 19.6185 6.31116L14.1448 12L19.6185 17.6888C20.1272 18.2176 20.1272 19.0748 19.6185 19.6035C19.1098 20.1322 18.285 20.1322 17.7763 19.6035L11.3815 12.9573C10.8728 12.4286 10.8728 11.5714 11.3815 11.0427L17.7763 4.39653C18.285 3.86782 19.1098 3.86782 19.6185 4.39653Z"
+                fill="currentColor"
+            />
+        </svg>
+    );
+}
+
+const IconComponent = convertIcon(SvgComponent, 'double_chevron_left');
+export default IconComponent;

+ 33 - 0
packages/semi-icons/src/icons/IconDoubleChevronRight.tsx

@@ -0,0 +1,33 @@
+import * as React from 'react';
+import { convertIcon } from '../components/Icon';
+
+function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
+    return (
+        <svg
+            viewBox="0 0 24 24"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            width="1em"
+            height="1em"
+            focusable={false}
+            aria-hidden={true}
+            {...props}
+        >
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M4.38153 4.39653C4.89024 3.86782 5.71502 3.86782 6.22373 4.39653L12.6185 11.0427C13.1272 11.5714 13.1272 12.4286 12.6185 12.9573L6.22373 19.6035C5.71502 20.1322 4.89024 20.1322 4.38153 19.6035C3.87282 19.0748 3.87282 18.2176 4.38153 17.6888L9.85517 12L4.38153 6.31116C3.87282 5.78245 3.87282 4.92524 4.38153 4.39653Z"
+                fill="currentColor"
+            />
+            <path
+                fillRule="evenodd"
+                clipRule="evenodd"
+                d="M11.3815 4.39653C11.8902 3.86782 12.715 3.86782 13.2237 4.39653L19.6185 11.0427C20.1272 11.5714 20.1272 12.4286 19.6185 12.9573L13.2237 19.6035C12.715 20.1322 11.8902 20.1322 11.3815 19.6035C10.8728 19.0748 10.8728 18.2176 11.3815 17.6888L16.8552 12L11.3815 6.31116C10.8728 5.78245 10.8728 4.92524 11.3815 4.39653Z"
+                fill="currentColor"
+            />
+        </svg>
+    );
+}
+
+const IconComponent = convertIcon(SvgComponent, 'double_chevron_right');
+export default IconComponent;

+ 3 - 1
packages/semi-icons/src/icons/index.ts

@@ -122,6 +122,8 @@ export { default as IconDisc } from './IconDisc';
 export { default as IconDislikeThumb } from './IconDislikeThumb';
 export { default as IconDivide } from './IconDivide';
 export { default as IconDongchediLogo } from './IconDongchediLogo';
+export { default as IconDoubleChevronLeft } from './IconDoubleChevronLeft';
+export { default as IconDoubleChevronRight } from './IconDoubleChevronRight';
 export { default as IconDownCircleStroked } from './IconDownCircleStroked';
 export { default as IconDownload } from './IconDownload';
 export { default as IconDownloadStroked } from './IconDownloadStroked';
@@ -431,4 +433,4 @@ export { default as IconWifi } from './IconWifi';
 export { default as IconWindowAdaptionStroked } from './IconWindowAdaptionStroked';
 export { default as IconWrench } from './IconWrench';
 export { default as IconXiguaLogo } from './IconXiguaLogo';
-export { default as IconYoutube } from './IconYoutube';
+export { default as IconYoutube } from './IconYoutube';

+ 1 - 0
packages/semi-icons/src/svgs/double_chevron_left.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.6185 4.39653C13.1272 4.92524 13.1272 5.78245 12.6185 6.31116L7.14483 12L12.6185 17.6888C13.1272 18.2176 13.1272 19.0748 12.6185 19.6035C12.1098 20.1322 11.285 20.1322 10.7763 19.6035L4.38153 12.9573C3.87282 12.4286 3.87282 11.5714 4.38153 11.0427L10.7763 4.39653C11.285 3.86782 12.1098 3.86782 12.6185 4.39653Z" fill="black"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6185 4.39653C20.1272 4.92524 20.1272 5.78245 19.6185 6.31116L14.1448 12L19.6185 17.6888C20.1272 18.2176 20.1272 19.0748 19.6185 19.6035C19.1098 20.1322 18.285 20.1322 17.7763 19.6035L11.3815 12.9573C10.8728 12.4286 10.8728 11.5714 11.3815 11.0427L17.7763 4.39653C18.285 3.86782 19.1098 3.86782 19.6185 4.39653Z" fill="black"/></svg>

+ 1 - 0
packages/semi-icons/src/svgs/double_chevron_right.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.38153 4.39653C4.89024 3.86782 5.71502 3.86782 6.22373 4.39653L12.6185 11.0427C13.1272 11.5714 13.1272 12.4286 12.6185 12.9573L6.22373 19.6035C5.71502 20.1322 4.89024 20.1322 4.38153 19.6035C3.87282 19.0748 3.87282 18.2176 4.38153 17.6888L9.85517 12L4.38153 6.31116C3.87282 5.78245 3.87282 4.92524 4.38153 4.39653Z" fill="black"/><path fill-rule="evenodd" clip-rule="evenodd" d="M11.3815 4.39653C11.8902 3.86782 12.715 3.86782 13.2237 4.39653L19.6185 11.0427C20.1272 11.5714 20.1272 12.4286 19.6185 12.9573L13.2237 19.6035C12.715 20.1322 11.8902 20.1322 11.3815 19.6035C10.8728 19.0748 10.8728 18.2176 11.3815 17.6888L16.8552 12L11.3815 6.31116C10.8728 5.78245 10.8728 4.92524 11.3815 4.39653Z" fill="black"/></svg>

+ 8 - 0
packages/semi-icons/src/svgs/meta.json

@@ -95,6 +95,10 @@
         "name": "chevron_left",
         "category": "Arrow"
     },
+    {
+        "name": "double_chevron_left",
+        "category": "Arrow"
+    },
     {
         "name": "paperclip",
         "category": "Files&Folder"
@@ -167,6 +171,10 @@
         "name": "chevron_right",
         "category": "Arrow"
     },
+    {
+        "name": "double_chevron_right",
+        "category": "Arrow"
+    },
     {
         "name": "external_open",
         "category": "Writing"

+ 2 - 2
packages/semi-illustrations/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-illustrations",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "semi illustrations",
   "keywords": [
     "semi",
@@ -55,5 +55,5 @@
     "build:js": "npm run build:lib && node scripts/compileDist.js",
     "prepublishOnly": "npm run clean && npm run build:js"
   },
-  "gitHead": "5d5893482dd1f4c69333907f01fc66a8125ac9cd"
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 4 - 3
packages/semi-next/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-next",
-    "version": "2.1.5",
+    "version": "2.2.0-beta.1",
     "description": "Plugin that support Semi Design in Next.js",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -23,6 +23,7 @@
         "typescript": "^4"
     },
     "dependencies": {
-        "@douyinfe/semi-webpack-plugin": "2.1.5"
-    }
+        "@douyinfe/semi-webpack-plugin": "2.2.0-beta.1"
+    },
+    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 3 - 2
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-scss-compile",
-  "version": "2.1.5",
+  "version": "2.2.0-beta.1",
   "description": "compile semi scss to css",
   "author": "[email protected]",
   "license": "MIT",
@@ -31,5 +31,6 @@
     "@types/lodash": "^4.14.176",
     "rimraf": "^3.0.2",
     "typescript": "^4.4.4"
-  }
+  },
+  "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 2 - 2
packages/semi-theme-default/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.1.5",
+    "version": "2.2.0-beta.1",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",
@@ -13,7 +13,7 @@
     "sideEffects": "*.scss",
     "author": "[email protected]",
     "license": "MIT",
-    "gitHead": "661ac2de772e28c3b6f934f70e4e08efbe2387b2",
+    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a",
     "dependencies": {
         "glob": "^7.1.6"
     },

+ 1302 - 0
packages/semi-ui/_base/_story/a11y.jsx

@@ -0,0 +1,1302 @@
+import React, { useEffect, useState } from 'react';
+import {
+    Avatar,
+    Badge,
+    Breadcrumb,
+    Button,
+    Layout,
+    Nav,
+    Pagination,
+    Popover,
+    Rating,
+    Row,
+    Steps,
+    Space,
+    Tag,
+    Timeline,
+    Tooltip,
+    Col,
+    Typography,
+    Anchor,
+    BackTop,
+    Tabs,
+    TabPane,
+    Calendar,
+    Card,
+    Collapse,
+    Descriptions,
+    Dropdown,
+    Empty,
+    List,
+    ButtonGroup,
+    Modal,
+    OverflowList,
+    Slider,
+    SideSheet,
+    Table,
+    Banner,
+    Popconfirm,
+    Notification,
+    Progress,
+    Toast,
+    Spin,
+    Form,
+    Select,
+    Collapsible,
+    Skeleton,
+    Tree,
+    Transfer
+} from '@douyinfe/semi-ui';
+import {
+    IconSemiLogo,
+    IconBell,
+    IconHelpCircle,
+    IconBytedanceLogo,
+    IconHome,
+    IconHistogram,
+    IconLive,
+    IconSetting,
+    IconEdit,
+    IconCamera,
+    IconAlarm,
+    IconBookmark,
+    IconDuration,
+    IconFolder,
+    IconDelete,
+    IconUpload,
+    IconSun,
+    IconMoon,
+    IconSearch
+} from '@douyinfe/semi-icons';
+import {
+    IllustrationConstruction,
+    IllustrationConstructionDark,
+    IllustrationNoResult,
+    IllustrationNoResultDark,
+    IllustrationSuccess,
+    IllustrationSuccessDark,
+    IllustrationFailure,
+    IllustrationFailureDark,
+    IllustrationNoAccess,
+    IllustrationNoAccessDark,
+    IllustrationNoContent,
+    IllustrationNoContentDark,
+    IllustrationNotFound,
+    IllustrationNotFoundDark,
+    IllustrationIdle,
+    IllustrationIdleDark
+} from '@douyinfe/semi-illustrations';
+import './a11y.scss';
+
+SemiA11y.storyName = 'Semi A11y';
+
+const raw = [
+    {
+        key: '1',
+        name: 'Semi Design 设计稿标题可能有点长这时候应该显示 Tooltip.fig',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+        size: '2M',
+        owner: '姜鹏志',
+        updateTime: '2020-02-02 05:13',
+        avatarBg: 'grey',
+    },
+    {
+        key: '2',
+        name: 'Semi Design 分享演示文稿',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '2M',
+        owner: '郝宣',
+        updateTime: '2020-01-17 05:31',
+        avatarBg: 'red',
+    },
+    {
+        key: '3',
+        name: '设计文档',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '34KB',
+        owner: 'Zoey Edwards',
+        updateTime: '2020-01-26 11:01',
+        avatarBg: 'light-blue',
+    },
+    {
+        key: '4',
+        name: 'Semi Pro 设计文档可能也有点长所以也会显示Tooltip',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '34KB',
+        owner: '姜琪',
+        updateTime: '2020-01-26 11:01',
+        avatarBg: 'green',
+    },
+];
+
+const treeData = [
+    {
+        label: '亚洲',
+        value: 'Asia',
+        key: '0',
+        children: [
+            {
+                label: '中国',
+                value: 'China',
+                key: '0-0',
+                children: [
+                    {
+                        label: '北京',
+                        value: 'Beijing',
+                        key: '0-0-0',
+                    },
+                    {
+                        label: '上海',
+                        value: 'Shanghai',
+                        key: '0-0-1',
+                    },
+                ],
+            },
+        ],
+    },
+    {
+        label: '北美洲',
+        value: 'North America',
+        key: '1',
+    }
+];
+
+const listData = [
+    {
+        title: '审核管理平台',
+        rating: 4.5,
+        feedbacks: 124,
+    },
+    {
+        title: '扁鹊',
+        rating: 4,
+        feedbacks: 108,
+    },
+    {
+        title: '直播审核平台',
+        rating: 3.5,
+        feedbacks: 244,
+    },
+    {
+        title: '抖音安全测试',
+        feedbacks: 189,
+    },
+    {
+        title: '内容平台',
+        rating: 3,
+        feedbacks: 128,
+    },
+    {
+        title: '策略平台',
+        rating: 4,
+        feedbacks: 156,
+    },
+];
+
+const style = { width: '90%' };
+const initValues = {
+    name: 'semi',
+    business: ['ulikeCam'],
+    role: 'ued',
+    switch: true,
+    files: [
+        {
+            uid: '1',
+            name: 'vigo.png',
+            status: 'success',
+            size: '130KB',
+            preview: true,
+            url:
+                'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/vigo.png',
+        },
+        {
+            uid: '2',
+            name: 'jiafang1.jpeg',
+            status: 'validateFail',
+            size: '222KB',
+            percent: 50,
+            preview: true,
+            fileInstance: new File([new ArrayBuffer(2048)], 'jiafang1.jpeg', { type: 'image/jpeg' }),
+            url:
+                'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
+        },
+        {
+            uid: '3',
+            name: 'jiafang2.jpeg',
+            status: 'uploading',
+            size: '222KB',
+            percent: 50,
+            preview: true,
+            fileInstance: new File([new ArrayBuffer(2048)], 'jiafang2.jpeg', { type: 'image/jpeg' }),
+            url:
+                'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
+        },
+    ],
+};
+
+const listItemStyle = {
+    border: '1px solid var(--semi-color-border)',
+    backgroundColor: 'var(--semi-color-bg-2)',
+    borderRadius: '3px',
+    paddingLeft: '20px',
+    margin: '8px 2px',
+};
+
+const descriptionData = [
+    { key: '实际用户数量', value: '1,480,000' },
+    { key: '7天留存', value: '98%' },
+    { key: '安全等级', value: '3级' },
+    { key: '垂类标签', value: <Tag style={{ margin: 0 }}>电商</Tag> },
+    { key: '认证状态', value: '未认证' },
+];
+const descriptionStyle = {
+    boxShadow: 'var(--semi-shadow-elevated)',
+    backgroundColor: 'var(--semi-color-bg-2)',
+    borderRadius: '4px',
+    padding: '10px',
+    margin: '10px',
+    width: '200px',
+};
+
+const emptyStyle = {
+    padding: 30,
+};
+
+const transferData = Array.from({ length: 100 }, (v, i) => {
+    return {
+        label: `选项名称 ${i}`,
+        value: i,
+        disabled: false,
+        key: i,
+    };
+});
+
+const items = [
+    { icon: <IconAlarm style={{ marginRight: 4 }} />, key: 'alarm' },
+    { icon: <IconBookmark style={{ marginRight: 4 }} />, key: 'bookmark' },
+    { icon: <IconCamera style={{ marginRight: 4 }} />, key: 'camera' },
+    { icon: <IconDuration style={{ marginRight: 4 }} />, key: 'duration' },
+    { icon: <IconEdit style={{ marginRight: 4 }} />, key: 'edit' },
+    { icon: <IconFolder style={{ marginRight: 4 }} />, key: 'folder' },
+];
+
+const empty = (
+    <Empty
+        image={<IllustrationNoResult />}
+        darkModeImage={<IllustrationNoResultDark />}
+        description={'搜索无结果'}
+    />
+);
+
+const placeholder = (
+    <div style={style}>
+        <Skeleton.Avatar style={{ marginRight: 12 }} />
+        <div>
+            <Skeleton.Title style={{ width: 120, marginBottom: 12, marginTop: 12 }} />
+            <Skeleton.Paragraph style={{ width: 240 }} rows={3} />
+        </div>
+    </div>
+);
+
+export default function SemiA11y() {
+    const { Header, Footer, Sider, Content } = Layout;
+    const { Title, Text, Paragraph } = Typography;
+    const [modalVisible, setModalVisible] = useState(false);
+    const [sideSheetVisible, setSideSheetVisible] = useState(false);
+    const [overflowWidth, setOverflowWidth] = useState(100);
+    const [isOpen, setCollapsibleOpen] = useState(false);
+    const [dataSource, setData] = useState(raw);
+    const [mode, setMode] = useState('light');
+    const [stringData, setStringData] = useState([]);
+
+    const removeRecord = key => {
+        let newDataSource = [...dataSource];
+        if (key != null) {
+            let idx = newDataSource.findIndex(data => data.key === key);
+
+            if (idx > -1) {
+                newDataSource.splice(idx, 1);
+                setData(newDataSource);
+            }
+        }
+    };
+
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <span style={{ display: 'flex', alignItems: 'center' }}>
+                        <Avatar
+                            size="small"
+                            shape="square"
+                            src={record.nameIconSrc}
+                            style={{ marginRight: 12 }}
+                        ></Avatar>
+                        {/* 宽度计算方式为单元格设置宽度 - 非文本内容宽度 */}
+                        <Text heading={5} ellipsis={{ showTooltip: true }} style={{ width: 'calc(400px - 76px)' }}>
+                            {text}
+                        </Text>
+                    </span>
+                );
+            },
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            width: 150,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+        },
+        {
+            title: '',
+            width: 100,
+            dataIndex: 'operate',
+            render: (text, record) => (
+                <Button icon={<IconDelete />} theme="borderless" onClick={() => removeRecord(record.key)} />
+            ),
+        },
+    ];
+
+    const onConfirm = () => {
+        Toast.success('确认保存!');
+    };
+
+    const onCancel = () => {
+        Toast.warning('取消保存!');
+    };
+
+    const switchMode = () => {
+        const body = document.body;
+        if (body.hasAttribute('theme-mode')) {
+            body.removeAttribute('theme-mode');
+            setMode('light');
+        } else {
+            body.setAttribute('theme-mode', 'dark');
+            setMode('dark');
+        }
+    };
+
+    const handleStringSearch = (value) => {
+        let result;
+        if (value) {
+            result = ['gmail.com', '163.com', 'qq.com'].map(domain => `${value}@${domain}`);
+        } else {
+            result = [];
+        }
+        setStringData(result);
+    };
+
+    const renderOverflow = items => {
+        return items.length ? <Tag style={{ flex: '0 0 auto' }}>+{items.length}</Tag> : null;
+    };
+    const renderOverflowItem = (item, ind) => {
+        return (
+            <Tag color="blue" key={item.key} style={{ marginRight: 8, flex: '0 0 auto' }}>
+                {item.icon}
+                {item.key}
+            </Tag>
+        );
+    };
+
+    useEffect(() => {
+        document.body && document.body.setAttribute('data-page', 'a11y');
+
+        return () => {
+            document.body && document.body.removeAttribute('data-page');
+        };
+    }, []);
+
+    return (
+        <>
+            <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
+                <Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
+                    <div>
+                        <Nav mode="horizontal" defaultSelectedKeys={['Home']}>
+                            <Nav.Header>
+                                <IconSemiLogo style={{ width: '96px', height: '36px', fontSize: 36 }} />
+                                <span
+                                    style={{
+                                        color: 'var(--semi-color-text-2)',
+                                    }}
+                                >
+                                    <span
+                                        style={{
+                                            marginRight: '24px',
+                                            color: 'var(--semi-color-text-0)',
+                                            fontWeight: '600',
+                                        }}
+                                    >
+                                        模版推荐
+                                    </span>
+                                    <span style={{ marginRight: '24px' }}>所有模版</span>
+                                    <span>我的模版</span>
+                                </span>
+                            </Nav.Header>
+                            <Nav.Footer>
+                                <Button
+                                    theme="borderless"
+                                    icon={mode === 'dark' ? <IconSun size="large" /> : <IconMoon size="large" />}
+                                    style={{
+                                        color: 'var(--semi-color-text-2)',
+                                        marginRight: '12px',
+                                    }}
+                                    onClick={switchMode}
+                                />
+                                <Button
+                                    theme="borderless"
+                                    icon={<IconBell size="large" />}
+                                    style={{
+                                        color: 'var(--semi-color-text-2)',
+                                        marginRight: '12px',
+                                    }}
+                                />
+                                <Button
+                                    theme="borderless"
+                                    icon={<IconHelpCircle size="large" />}
+                                    style={{
+                                        color: 'var(--semi-color-text-2)',
+                                        marginRight: '12px',
+                                    }}
+                                />
+                                <Avatar color="orange" size="small">
+                                    YJ
+                                </Avatar>
+                            </Nav.Footer>
+                        </Nav>
+                    </div>
+                </Header>
+                <Layout>
+                    <Sider style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
+                        <Nav
+                            style={{ maxWidth: 220, height: '100%' }}
+                            defaultSelectedKeys={['Home']}
+                            items={[
+                                { itemKey: 'Home', text: '首页', icon: <IconHome size="large" /> },
+                                { itemKey: 'Histogram', text: '基础数据', icon: <IconHistogram size="large" /> },
+                                { itemKey: 'Live', text: '测试功能', icon: <IconLive size="large" /> },
+                                { itemKey: 'Setting', text: '设置', icon: <IconSetting size="large" /> },
+                            ]}
+                            footer={{
+                                collapseButton: true,
+                            }}
+                        />
+                    </Sider>
+                    <Content
+                        style={{
+                            padding: '24px',
+                            backgroundColor: 'var(--semi-color-bg-1)',
+                            maxHeight: 'calc(100vh - 120px)',
+                        }}
+                    >
+                        <Breadcrumb
+                            style={{
+                                marginBottom: '24px',
+                            }}
+                            routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']}
+                        />
+                        <div
+                            style={{
+                                borderRadius: '10px',
+                                border: '1px solid var(--semi-color-border)',
+                                padding: '32px',
+                            }}
+                            className="rows-container"
+                        >
+                            <Row id="基本示例">
+                                <Pagination total={80} showSizeChanger></Pagination>
+                                <Steps current={1}>
+                                    <Steps.Step title="Finished" description="This is a description." />
+                                    <Steps.Step title="In Progress" description="This is a description." />
+                                    <Steps.Step title="Waiting" description="This is a description." />
+                                </Steps>
+                                <Steps current={1} status="error">
+                                    <Steps.Step title="Finished" description="This is a description" />
+                                    <Steps.Step title="In Process" description="This is a description" />
+                                    <Steps.Step title="Waiting" description="This is a description" />
+                                </Steps>
+                            </Row>
+                            <Row>
+                                <div style={{ display: 'flex' }}>
+                                    <div style={{ padding: 8 }}>
+                                        <Badge count={5} theme="solid">
+                                            <Avatar color="blue" shape="square">
+                                                XZ
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                    <div style={{ padding: 8 }}>
+                                        <Badge count={5} theme="light">
+                                            <Avatar color="cyan" shape="square">
+                                                YB
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                    <div style={{ padding: 8 }}>
+                                        <Badge count={5} theme="inverted">
+                                            <Avatar color="indigo" shape="square">
+                                                LX
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                    <div style={{ padding: 8 }}>
+                                        <Badge dot theme="solid">
+                                            <Avatar color="light-blue" shape="square">
+                                                YZ
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                    <div style={{ padding: 8 }}>
+                                        <Badge dot theme="light">
+                                            <Avatar color="teal" shape="square">
+                                                HW
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                    <div
+                                        style={{
+                                            padding: '8px',
+                                            borderRadius: '4px',
+                                            backgroundColor: 'var(--semi-color-fill-0)',
+                                        }}
+                                    >
+                                        <Badge dot theme="inverted">
+                                            <Avatar color="green" shape="square">
+                                                XM
+                                            </Avatar>
+                                        </Badge>
+                                    </div>
+                                </div>
+                                <div>
+                                    <Space wrap>
+                                        <Tag color="grey">grey tag</Tag>
+                                        <Tag color="blue">blue tag</Tag>
+                                        <Tag color="blue" type="ghost">
+                                            ghost tag
+                                        </Tag>
+                                        <Tag color="blue" type="solid">
+                                            solid tag
+                                        </Tag>
+                                        <Tag color="red">red tag</Tag>
+                                        <Tag color="green">green tag</Tag>
+                                        <Tag color="orange">orange tag</Tag>
+                                        <Tag color="teal">teal tag</Tag>
+                                        <Tag color="violet">violet tag</Tag>
+                                        <Tag color="white">white tag</Tag>
+                                    </Space>
+                                </div>
+                                <div>
+                                    <Space>
+                                        <Popover content={'hi semi-design'} style={{ padding: 8 }}>
+                                            <Tag style={{ marginRight: 8 }}>I am Popover</Tag>
+                                        </Popover>
+                                        <Tooltip content={'hi semi-design'}>
+                                            <Tag style={{ marginRight: 8 }}>I am Tooltip</Tag>
+                                        </Tooltip>
+                                        <Dropdown
+                                            render={
+                                                <Dropdown.Menu>
+                                                    <Dropdown.Item>Menu Item 1</Dropdown.Item>
+                                                    <Dropdown.Item>Menu Item 2</Dropdown.Item>
+                                                    <Dropdown.Item>Menu Item 3</Dropdown.Item>
+                                                </Dropdown.Menu>
+                                            }
+                                        >
+                                            <Tag>I am dropdown</Tag>
+                                        </Dropdown>
+                                        <Rating defaultValue={3} size="small" style={{ marginRight: 8 }} />
+                                    </Space>
+                                </div>
+                                <div className="mt12">
+                                    <Space>
+                                        <Button onClick={() => setModalVisible(true)}>Modal</Button>
+                                        <Button type="tertiary" onClick={() => setSideSheetVisible(true)}>
+                                            SideSheet
+                                        </Button>
+                                        <Button
+                                            type="warning"
+                                            onClick={() =>
+                                                Notification.open({
+                                                    title: 'Hi, Bytedance',
+                                                    content: 'ies dance dance dance',
+                                                    duration: 3,
+                                                })
+                                            }
+                                        >
+                                            Notification
+                                        </Button>
+                                        <Popconfirm
+                                            title="确定是否要保存此修改?"
+                                            content="此修改将不可逆"
+                                            onConfirm={onConfirm}
+                                            onCancel={onCancel}
+                                        >
+                                            <Button type="danger">Popconfirm</Button>
+                                        </Popconfirm>
+                                        <Modal
+                                            title="基本对话框"
+                                            visible={modalVisible}
+                                            onOk={() => setModalVisible(false)}
+                                            onCancel={() => setModalVisible(false)}
+                                        >
+                                            This is the content of a basic modal.
+                                            More content...
+                                        </Modal>
+                                        <SideSheet
+                                            title="滑动侧边栏"
+                                            visible={sideSheetVisible}
+                                            onCancel={() => setSideSheetVisible(false)}
+                                        >
+                                            <p>This is the content of a basic side sheet.</p>
+                                            <p>Here is more content...</p>
+                                        </SideSheet>
+                                    </Space>
+                                </div>
+                            </Row>
+                            <Row>
+                                <Col span={12} style={{ paddingRight: 24 }}>
+                                    <div style={{ maxWidth: 640 }}>
+                                        <Banner
+                                            className="mb12"
+                                            fullMode={false}
+                                            type="info"
+                                            bordered
+                                            icon={null}
+                                            closeIcon={null}
+                                            title={
+                                                <div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
+                                                    不知道 AppKey?
+                                                </div>
+                                            }
+                                            description={
+                                                <div>
+                                                    你可先联系对应的研发同学,确认是否已在
+                                                    <Text link={{ href: 'https://semi.design/' }}>应用云平台</Text>
+                                                    申请了应用,并填写对应的信息。
+                                                </div>
+                                            }
+                                        />
+                                        <Banner
+                                            className="mb12"
+                                            fullMode={false}
+                                            type="warning"
+                                            bordered
+                                            icon={null}
+                                            closeIcon={null}
+                                            title={
+                                                <div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
+                                                    不知道 AppKey?
+                                                </div>
+                                            }
+                                            description={
+                                                <div>
+                                                    你可先联系对应的研发同学,确认是否已在
+                                                    <Text link={{ href: 'https://semi.design/' }}>应用云平台</Text>
+                                                    申请了应用,并填写对应的信息。
+                                                </div>
+                                            }
+                                        />
+                                        <Banner
+                                            className="mb12"
+                                            fullMode={false}
+                                            type="danger"
+                                            bordered
+                                            icon={null}
+                                            closeIcon={null}
+                                            title={
+                                                <div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
+                                                    不知道 AppKey?
+                                                </div>
+                                            }
+                                            description={
+                                                <div>
+                                                    你可先联系对应的研发同学,确认是否已在
+                                                    <Text link={{ href: 'https://semi.design/' }}>应用云平台</Text>
+                                                    申请了应用,并填写对应的信息。
+                                                </div>
+                                            }
+                                        />
+                                        <Banner
+                                            fullMode={false}
+                                            type="success"
+                                            bordered
+                                            icon={null}
+                                            closeIcon={null}
+                                            title={
+                                                <div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
+                                                    不知道 AppKey?
+                                                </div>
+                                            }
+                                            description={
+                                                <div>
+                                                    你可先联系对应的研发同学,确认是否已在
+                                                    <Text link={{ href: 'https://semi.design/' }}>应用云平台</Text>
+                                                    申请了应用,并填写对应的信息。
+                                                </div>
+                                            }
+                                        />
+                                    </div>
+                                </Col>
+                                <Col span={12}>
+                                    <Title style={{ margin: '8px 0' }}>h1. Semi Design</Title>
+                                    <Title heading={2} style={{ margin: '8px 0' }}>
+                                        h2. Semi Design
+                                    </Title>
+                                    <Title heading={3} style={{ margin: '8px 0' }}>
+                                        h3. Semi Design
+                                    </Title>
+                                    <Title heading={4} style={{ margin: '8px 0' }}>
+                                        h4. Semi Design
+                                    </Title>
+                                    <Title heading={5} style={{ margin: '8px 0' }}>
+                                        h5. Semi Design
+                                    </Title>
+                                    <Title heading={6} style={{ margin: '8px 0' }}>
+                                        h6. Semi Design
+                                    </Title>
+                                </Col>
+                            </Row>
+                            <Row id="组件">
+                                <div style={{ display: 'flex', flexWrap: 'wrap' }}>
+                                    <Empty
+                                        image={<IllustrationSuccess style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationSuccessDark style={{ width: 150, height: 150 }} />}
+                                        description={'创建成功'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationFailure style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationFailureDark style={{ width: 150, height: 150 }} />}
+                                        description={'加载失败'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationNoAccess style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationNoAccessDark style={{ width: 150, height: 150 }} />}
+                                        description={'没有权限'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationNoContent style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationNoContentDark style={{ width: 150, height: 150 }} />}
+                                        description={'暂无内容,请添加'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationNotFound style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationNotFoundDark style={{ width: 150, height: 150 }} />}
+                                        description={'页面404'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
+                                        description={'搜索无结果'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationConstruction style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationConstructionDark style={{ width: 150, height: 150 }} />}
+                                        description={'建设中'}
+                                        style={emptyStyle}
+                                    />
+                                    <Empty
+                                        image={<IllustrationIdle style={{ width: 150, height: 150 }} />}
+                                        darkModeImage={<IllustrationIdleDark style={{ width: 150, height: 150 }} />}
+                                        description={'神游四方'}
+                                        style={emptyStyle}
+                                    />
+                                </div>
+                            </Row>
+                            <Row>                                       
+                                <Timeline>
+                                    <Timeline.Item time="2019-07-14 10:35" type="ongoing">
+                                        审核中
+                                    </Timeline.Item>
+                                    <Timeline.Item time="2019-06-13 16:17" type="success">
+                                        发布成功
+                                    </Timeline.Item>
+                                    <Timeline.Item time="2019-05-14 18:34" type="error">
+                                        审核失败
+                                    </Timeline.Item>
+                                </Timeline>
+                                <div style={{ width: 200 }}>
+                                    <Progress className="mb12" percent={10} stroke="#fc8800" />
+                                    <Progress className="mb12" percent={25} stroke="#f93920" />
+                                    <Progress className="mb12" percent={50} />
+                                    <Progress className="mb12" percent={80} />
+                                    <Progress className="mb12" percent={80} size="large" />
+                                    <Progress className="mb12" percent={80} style={{ height: '8px' }} />
+                                </div>
+                                <div className="mb12">
+                                    <Skeleton placeholder={placeholder} loading={true} active>
+                                        <div style={{
+                                            display: 'flex',
+                                            alignItems: 'flex-start',
+                                        }}>
+                                            <Avatar color="blue" style={{ marginRight: 12 }}>
+                                                UI
+                                            </Avatar>
+                                            <div>
+                                                <h3>Semi UI</h3>
+                                                <p>Hi, Bytedance dance dance.</p>
+                                                <p>Hi, Bytedance dance dance.</p>
+                                                <p>Hi, Bytedance dance dance.</p>
+                                            </div>
+                                        </div>
+                                    </Skeleton>
+                                </div>
+                                <div>
+                                    <Spin size="small" />
+                                    <Spin size="middle" />
+                                    <Spin size="large" />
+                                </div>
+                            </Row>
+                            <Row>
+                                <div style={{ width: 300 }}>
+                                    <Slider
+                                        step={1}
+                                        value={overflowWidth}
+                                        onChange={value => setOverflowWidth(value)}
+                                    />
+                                    <div style={{ width: `${overflowWidth}%` }}>
+                                        <OverflowList
+                                            items={items}
+                                            overflowRenderer={renderOverflow}
+                                            visibleItemRenderer={renderOverflowItem}
+                                        />
+                                    </div>
+                                </div>
+                            </Row>
+                            <Row id="设计语言">
+                                <Table
+                                    style={{ minHeight: 350 }}
+                                    columns={columns}
+                                    dataSource={dataSource}
+                                    pagination={false}
+                                    empty={empty}
+                                />
+                            </Row>
+                            <Row id="物料平台">
+                                <Form
+                                    initValues={initValues}
+                                    style={{ padding: 10, width: '100%' }}
+                                    onValueChange={v => console.log(v)}
+                                >
+                                    <Form.Section text={'基本信息'}>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.Input
+                                                    field="name"
+                                                    label="名称(Input)"
+                                                    initValue={'mikeya'}
+                                                    style={style}
+                                                    trigger="blur"
+                                                />
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.DatePicker
+                                                    field="date"
+                                                    label="日期(DatePicker)"
+                                                    style={style}
+                                                    initValue={new Date()}
+                                                    placeholder="请选择生效日期"
+                                                />
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.Select
+                                                    field="role"
+                                                    style={style}
+                                                    label="角色(Select)"
+                                                    placeholder="请选择你的角色"
+                                                >
+                                                    <Select.Option value="operate">运营</Select.Option>
+                                                    <Select.Option value="rd">开发</Select.Option>
+                                                    <Select.Option value="pm">产品</Select.Option>
+                                                    <Select.Option value="ued">设计</Select.Option>
+                                                </Form.Select>
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.Select
+                                                    field="business"
+                                                    multiple
+                                                    style={style}
+                                                    placeholder="请选择业务线"
+                                                    label="业务线(多选Select)"
+                                                    extraText={
+                                                        <div
+                                                            style={{
+                                                                color: 'rgba(var(--semi-blue-5), 1)',
+                                                                fontSize: 14,
+                                                                userSelect: 'none',
+                                                                cursor: 'pointer',
+                                                            }}
+                                                        >
+                                                            没有找到合适的业务线?
+                                                        </div>
+                                                    }
+                                                >
+                                                    <Select.Option value="abc">Semi</Select.Option>
+                                                    <Select.Option value="ulikeCam">轻颜相机</Select.Option>
+                                                    <Select.Option value="toutiao">今日头条</Select.Option>
+                                                </Form.Select>
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.Cascader
+                                                    placeholder="请选择所在地区"
+                                                    treeData={treeData}
+                                                    field="area"
+                                                    label="地区(Cascader)"
+                                                    style={style}
+                                                ></Form.Cascader>
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.TreeSelect
+                                                    field="tree"
+                                                    style={style}
+                                                    label="节点(TreeSelect)"
+                                                    placeholder="请选择服务节点"
+                                                    treeData={treeData}
+                                                    filterTreeNode
+                                                ></Form.TreeSelect>
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.TagInput
+                                                    field="product"
+                                                    label="产品(TagInput)"
+                                                    placeholder="请输入产品"
+                                                    style={style}
+                                                />
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.AutoComplete
+                                                    field="email"
+                                                    label="邮箱(AutoComplete)"
+                                                    data={stringData}
+                                                    showClear
+                                                    prefix={<IconSearch />}
+                                                    placeholder="搜索... "
+                                                    onSearch={handleStringSearch}
+                                                    style={style}
+                                                />
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={24}>
+                                                <Form.Upload
+                                                    field="files"
+                                                    label="证明文件(Upload)"
+                                                    action="//semi.design/api/upload"
+                                                >
+                                                    <Button icon={<IconUpload />} theme="light">
+                                                        点击上传
+                                                    </Button>
+                                                </Form.Upload>
+                                            </Col>
+                                        </Row>
+                                    </Form.Section>
+                                    <Form.Section text="资源详情">
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.TextArea
+                                                    style={{ ...style, height: 120 }}
+                                                    field="description"
+                                                    label="申请理由(TextArea)"
+                                                    placeholder="请填写申请资源理由"
+                                                />
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.CheckboxGroup
+                                                    field="type"
+                                                    direction="horizontal"
+                                                    label="申请类型(CheckboxGroup)"
+                                                    initValue={['user', 'admin']}
+                                                    rules={[{ required: true }]}
+                                                >
+                                                    <Form.Checkbox value="admin">admin</Form.Checkbox>
+                                                    <Form.Checkbox value="user">user</Form.Checkbox>
+                                                    <Form.Checkbox value="guest">guest</Form.Checkbox>
+                                                    <Form.Checkbox value="root">root</Form.Checkbox>
+                                                </Form.CheckboxGroup>
+                                                <Form.RadioGroup
+                                                    field="isMonopolize"
+                                                    label="是否独占资源(Radio)"
+                                                    rules={[
+                                                        { type: 'boolean' },
+                                                        { required: true, message: '必须选择是否独占 ' },
+                                                    ]}
+                                                >
+                                                    <Form.Radio value={true}>是</Form.Radio>
+                                                    <Form.Radio value={false}>否</Form.Radio>
+                                                </Form.RadioGroup>
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.TimePicker
+                                                    field="time"
+                                                    label="截止时刻(TimePicker)"
+                                                    style={{ width: '90%' }}
+                                                />
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.InputNumber
+                                                    field="number"
+                                                    label="申请数量(InputNumber)"
+                                                    initValue={20}
+                                                    style={style}
+                                                />
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.Slider
+                                                    field="range"
+                                                    label="资源使用报警阈值(%)(Slider)"
+                                                    initValue={10}
+                                                    style={{ width: '90%' }}
+                                                />
+                                            </Col>
+                                            <Col span={12}>
+                                                <Form.Switch field="switch" label="开关(Switch)" />
+                                            </Col>
+                                        </Row>
+                                        <Row>
+                                            <Col span={12}>
+                                                <Form.Rating
+                                                    field="rating"
+                                                    label="满意度(Rating)"
+                                                    initValue={2}
+                                                    style={{ width: '90%' }}
+                                                />
+                                            </Col>
+                                        </Row>
+                                    </Form.Section>
+                                    <Form.Checkbox value="false" field="agree" noLabel={true}>
+                                        我已阅读并清楚相关规定(Checkbox)
+                                    </Form.Checkbox>
+                                    <Button type="primary" htmlType="submit" className="btn-margin-right">
+                                        提交(submit)
+                                    </Button>
+                                    <Button htmlType="reset">重置(reset)</Button>
+                                </Form>
+                            </Row>
+                            <Row>
+                                <Col span="12">
+                                    <Tree
+                                        treeData={treeData}
+                                        defaultExpandAll
+                                        style={{
+                                            width: 260,
+                                            height: 420,
+                                            border: '1px solid var(--semi-color-border)'
+                                        }}
+                                    />
+                                </Col>
+                                <Col span="12">
+                                    <Transfer
+                                        style={{ width: 568, height: 416 }}
+                                        dataSource={transferData}
+                                        onChange={(values, items) => console.log(values, items)}
+                                    />
+                                </Col>
+                            </Row>
+                            <Row id="主题商店">
+                                <List
+                                    grid={{
+                                        gutter: 12,
+                                        xs: 0,
+                                        sm: 0,
+                                        md: 12,
+                                        lg: 8,
+                                        xl: 8,
+                                        xxl: 6,
+                                    }}
+                                    dataSource={listData}
+                                    renderItem={item => (
+                                        <List.Item style={listItemStyle}>
+                                            <div>
+                                                <h3 style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>
+                                                    {item.title}
+                                                </h3>
+                                                <Descriptions
+                                                    align="center"
+                                                    size="small"
+                                                    row
+                                                    data={[
+                                                        {
+                                                            key: '满意度',
+                                                            value: (
+                                                                <Rating allowHalf size="small" value={item.rating} />
+                                                            ),
+                                                        },
+                                                        { key: '反馈数', value: item.feedbacks },
+                                                    ]}
+                                                />
+                                                <div
+                                                    style={{
+                                                        margin: '12px 0',
+                                                        display: 'flex',
+                                                        justifyContent: 'flex-end',
+                                                    }}
+                                                >
+                                                    <ButtonGroup theme="borderless" style={{ marginTop: 8 }}>
+                                                        <Button>编辑</Button>
+                                                        <Button>更多</Button>
+                                                    </ButtonGroup>
+                                                </div>
+                                            </div>
+                                        </List.Item>
+                                    )}
+                                />
+                                <div style={{ display: 'flex', flexWrap: 'wrap' }}>
+                                    <Descriptions align="center" data={descriptionData} style={descriptionStyle} />
+                                    <Descriptions align="justify" data={descriptionData} style={descriptionStyle} />
+                                    <Descriptions align="left" data={descriptionData} style={descriptionStyle} />
+                                    <Descriptions align="plain" data={descriptionData} style={descriptionStyle} />
+                                </div>
+                                <div
+                                    style={{
+                                        backgroundColor: 'var(--semi-color-fill-0)',
+                                        padding: 20,
+                                    }}
+                                >
+                                    <Row gutter={[16, 16]}>
+                                        <Col span={8}>
+                                            <Card title="Card Title" bordered={false}>
+                                                Card Content
+                                            </Card>
+                                        </Col>
+                                        <Col span={8}>
+                                            <Card title="Card Title" bordered={false}>
+                                                Card Content
+                                            </Card>
+                                        </Col>
+                                        <Col span={8}>
+                                            <Card title="Card Title" bordered={false}>
+                                                Card Content
+                                            </Card>
+                                        </Col>
+                                    </Row>
+                                    <Row gutter={[16, 16]}>
+                                        <Col span={16}>
+                                            <Card title="Card Title" bordered={false}>
+                                                Card Content
+                                            </Card>
+                                        </Col>
+                                        <Col span={8}>
+                                            <Card title="Card Title" bordered={false}>
+                                                Card Content
+                                            </Card>
+                                        </Col>
+                                    </Row>
+                                </div>
+                            </Row>
+                            <Row>
+                                <Calendar mode="month" />
+                            </Row>
+                            <Row>
+                                <Collapse className="mt12">
+                                    <Collapse.Panel header="This is panel header 1" itemKey="1">
+                                        <p>Hi, bytedance dance dance. This is the docsite of Semi UI. </p>
+                                    </Collapse.Panel>
+                                    <Collapse.Panel header="This is panel header 2" itemKey="2">
+                                        <p>Hi, bytedance dance dance. This is the docsite of Semi UI. </p>
+                                    </Collapse.Panel>
+                                    <Collapse.Panel header="This is panel header 3" itemKey="3">
+                                        <p>Hi, bytedance dance dance. This is the docsite of Semi UI. </p>
+                                    </Collapse.Panel>
+                                </Collapse>
+                                <Tabs type="button" className="mt12">
+                                    <TabPane tab="文档" itemKey="1">
+                                        文档
+                                    </TabPane>
+                                    <TabPane tab="快速起步" itemKey="2">
+                                        快速起步
+                                    </TabPane>
+                                    <TabPane tab="帮助" itemKey="3">
+                                        帮助
+                                    </TabPane>
+                                </Tabs>
+                                <div className="mt12" style={{ height: 200 }}>
+                                    <Button onClick={() => setCollapsibleOpen(!isOpen)}>Toggle</Button>
+                                    <Collapsible isOpen={isOpen}>
+                                        <ul>
+                                            <li>
+                                                <p>Semi Design 以内容优先进行设计。</p>
+                                            </li>
+                                            <li>
+                                                <p>更容易地自定义主题。</p>
+                                            </li>
+                                            <li>
+                                                <p>适用国际化场景。</p>
+                                            </li>
+                                            <li>
+                                                <p>效率场景加入人性化关怀。</p>
+                                            </li>
+                                        </ul>
+                                    </Collapsible>
+                                </div>
+                                <Anchor style={{ position: 'fixed', top: 150, right: 50 }}>
+                                    <Anchor.Link href="#基本示例" title="基本示例" />
+                                    <Anchor.Link href="#组件" title="组件" />
+                                    <Anchor.Link href="#设计语言" title="设计语言" />
+                                    <Anchor.Link href="#物料平台" title="物料平台" />
+                                    <Anchor.Link href="#主题商店" title="主题商店" />
+                                </Anchor>
+                                <BackTop style={{ bottom: 100, right: 50 }} target={() => document && document.querySelector('.semi-layout-content' || window)} visibilityHeight={-1} />
+                            </Row>
+                        </div>
+                    </Content>
+                </Layout>
+                <Footer
+                    style={{
+                        display: 'flex',
+                        justifyContent: 'space-between',
+                        padding: '20px',
+                        color: 'var(--semi-color-text-2)',
+                        backgroundColor: 'rgba(var(--semi-grey-0), 1)',
+                    }}
+                >
+                    <span
+                        style={{
+                            display: 'flex',
+                            alignItems: 'center',
+                        }}
+                    >
+                        <IconBytedanceLogo size="large" style={{ marginRight: '8px' }} />
+                        <span>{`Copyright © ${(new Date()).getFullYear()} Semi Design. All Rights Reserved. `}</span>
+                    </span>
+                    <span>
+                        <span style={{ marginRight: '24px' }}>平台客服</span>
+                        <span>反馈建议</span>
+                    </span>
+                </Footer>
+            </Layout>
+        </>
+    );
+}

+ 49 - 0
packages/semi-ui/_base/_story/a11y.scss

@@ -0,0 +1,49 @@
+body[data-page="a11y"] {
+    overflow: hidden;
+
+    #root {
+        padding: 0 !important;
+
+        .semi-slider {
+            padding: 0;
+        }
+
+        .rows-container > .semi-row {
+            margin-bottom: 24px;
+        }
+
+        .mt12 {
+            margin-top: 12px;
+        }
+
+        .mb12 {
+            margin-bottom: 12px;
+        }
+    }
+    
+    .grid {
+    
+        .semi-row,
+        .semi-row-flex {
+            text-align: center;
+    
+            .semi-col {
+                min-height: 30px;
+                line-height: 30px;
+                background: var(--semi-color-primary-light-default);
+                outline: 1px solid var(--semi-color-primary-light-active);
+            }
+        }
+    
+        &.grid-gutter {
+    
+            .semi-col {
+    
+                .gutter-box,
+                .col-content {
+                    background: var(--semi-color-primary-light-hover);
+                }
+            }
+        }
+    }
+}

+ 3 - 1
packages/semi-ui/_base/_story/index.stories.js

@@ -1,6 +1,7 @@
 import React, { useMemo } from 'react';
 import { Button, Typography, Card, Tooltip, Tag, Avatar, Rating, Nav, Layout } from '../../index';
 import { IconHelpCircle, IconUser, IconStar, IconSetting } from '@douyinfe/semi-icons';
+import SemiA11y from './a11y';
 import './index.scss';
 
 export default {
@@ -8,7 +9,8 @@ export default {
 };
 
 export {
-  TestAlwaysDarkLight
+  TestAlwaysDarkLight,
+  SemiA11y
 };
 
 const TestAlwaysDarkLight = () => {

+ 9 - 4
packages/semi-ui/_utils/index.ts

@@ -83,7 +83,7 @@ export const getHighLightTextHTML = ({
     const markEle = option.highlightTag || 'mark';
     const highlightClassName = option.highlightClassName || '';
     const highlightStyle = option.highlightStyle || {};
-    return chunks.map((chunk: HighLightTextHTMLChunk) => {
+    return chunks.map((chunk: HighLightTextHTMLChunk, index: number) => {
         const { end, start, highlight } = chunk;
         const text = sourceString.substr(start, end - start);
         if (highlight) {
@@ -92,6 +92,7 @@ export const getHighLightTextHTML = ({
                 {
                     style: highlightStyle,
                     className: highlightClassName,
+                    key: text + index
                 },
                 text
             );
@@ -124,10 +125,14 @@ export const registerMediaQuery = (media: string, { match, unmatch, callInInit =
             }
         }
         callInInit && handlerMediaChange(mediaQueryList);
-        mediaQueryList.addEventListener('change', handlerMediaChange);
-        return (): void => mediaQueryList.removeEventListener('change', handlerMediaChange);
+        if (Object.prototype.hasOwnProperty.call(mediaQueryList, 'addEventListener')) {
+            mediaQueryList.addEventListener('change', handlerMediaChange);
+            return (): void => mediaQueryList.removeEventListener('change', handlerMediaChange);
+        }
+        mediaQueryList.addListener(handlerMediaChange);
+        return (): void => mediaQueryList.removeListener(handlerMediaChange);
     }
-    return null;
+    return () => undefined;
 };
 export interface GetHighLightTextHTMLProps {
     sourceString?: string;

+ 221 - 0
packages/semi-ui/cascader/__test__/cascader.test.js

@@ -997,6 +997,149 @@ describe('Cascader', () => {
         ).toEqual('美国');
     });
 
+    it('multiple select enableLeafClick', () => {
+        const cascaderWithMultiple = render({
+            multiple: true,
+            enableLeafClick: true,
+            treeData: [
+                {
+                    label: '北美洲',
+                    value: 'Beimeizhou',
+                    key: 'beimeizhou',
+                    children: [
+                        {
+                            label: '美国',
+                            value: 'Meiguo',
+                            key: 'meiguo',
+                        },
+                        {
+                            label: '加拿大',
+                            value: 'Jianada',
+                            key: 'jianada',
+                        },
+                    ],
+                },
+                {
+                    label: '南美洲',
+                    value: 'Nanmeiguo',
+                    key: 'Nanmeiguo',
+                }
+            ]
+        })
+        const selectBox = cascaderWithMultiple.find(`.${BASE_CLASS_PREFIX}-cascader-selection`).at(0);
+        selectBox.simulate('click');
+        // click checkbox
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(1)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-label`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-checkbox`)
+            .at(0)
+            .simulate('click');
+        expect(cascaderWithMultiple.state().checkedKeys.size).toEqual(1);
+        // click option cancel checked
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(1)
+            .simulate('click')
+        expect(cascaderWithMultiple.state().checkedKeys.size).toEqual(0);
+        // click option select
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(0)
+            .simulate('click')
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(1)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(0)
+            .simulate('click')
+        expect(cascaderWithMultiple.state().checkedKeys.size).toEqual(1);
+        const states = cascaderWithMultiple.state()
+        cascaderWithMultiple.unmount();
+    })
+
+    it('multiple select disable enableLeafClick', () => {
+        const cascaderWithMultiple = render({
+            multiple: true,
+            treeData: [
+                {
+                    label: '北美洲',
+                    value: 'Beimeizhou',
+                    key: 'beimeizhou',
+                    children: [
+                        {
+                            label: '美国',
+                            value: 'Meiguo',
+                            key: 'meiguo',
+                        },
+                        {
+                            label: '加拿大',
+                            value: 'Jianada',
+                            key: 'jianada',
+                        },
+                    ],
+                },
+                {
+                    label: '南美洲',
+                    value: 'Nanmeiguo',
+                    key: 'Nanmeiguo',
+                }
+            ]
+        })
+        const selectBox = cascaderWithMultiple.find(`.${BASE_CLASS_PREFIX}-cascader-selection`).at(0);
+        selectBox.simulate('click');
+        // click checkbox
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(1)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-label`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-checkbox`)
+            .at(0)
+            .simulate('click');
+        expect(cascaderWithMultiple.state().checkedKeys.size).toEqual(1);
+        // click option can't cancel checked
+        cascaderWithMultiple
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list`)
+            .at(0)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option`)
+            .at(1)
+            .simulate('click')
+        expect(cascaderWithMultiple.state().checkedKeys.size).toEqual(1);
+        cascaderWithMultiple.unmount();
+    })
+
+    it('separator', () => {
+        const cascader = render({
+            filterTreeNode: true,
+            defaultValue: ['Yazhou', 'Zhongguo', 'Beijing'],
+            separator: ' > ',
+            defaultOpen: true,
+        });
+        const input = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection input`);
+        expect(input.props().placeholder).toEqual('亚洲 > 中国 > 北京'); 
+        const event = { target: { value: '中国' } };
+        input.simulate('change', event);
+        expect(
+            cascader
+            .find(`.${BASE_CLASS_PREFIX}-cascader-option-list li`)
+            .at(0)
+            .getDOMNode()
+            .textContent
+        ).toEqual('亚洲 > 中国 > 北京');
+        cascader.unmount();
+    });
+
     it('triggerRender', () => {
         const spyTriggerRender = sinon.spy(() => <span>123</span>);
         const cascaderAutoMerge = render({
@@ -1027,4 +1170,82 @@ describe('Cascader', () => {
         expect(args2.value).toEqual(new Set(['0','0-0','0-0-1','0-0-0']));
         cascaderNoAutoMerge.unmount();
     });
+
+    it('autoMergeValue', () => {
+        const cascader = render({
+            multiple: true,
+            autoMergeValue: false,
+            defaultValue: 'Yazhou',
+        });
+        const tags = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags.length).toEqual(4);
+        cascader.unmount();
+
+        const cascaderAutoMerge = render({
+            multiple: true,
+            autoMergeValue: true,
+            defaultValue: 'Yazhou',
+        });
+        const tags2 = cascaderAutoMerge.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags2.length).toEqual(1);
+        expect(
+            tags2
+                .find(`.${BASE_CLASS_PREFIX}-tag-content`)
+                .getDOMNode()
+                .textContent
+        ).toEqual('亚洲');
+        cascaderAutoMerge.unmount();
+    });
+
+    it('leafOnly', () => {
+        /* autoMergeValue and leafOnly are both false */
+        const cascader = render({
+            multiple: true,
+            autoMergeValue: false,
+            leafOnly: false,
+            defaultValue: 'Yazhou',
+        });
+        const tags = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags.length).toEqual(4);
+        cascader.unmount();
+
+        /* autoMergeValue and leafOnly are both true */
+        const cascader2 = render({
+            multiple: true,
+            autoMergeValue: true,
+            leafOnly: true,
+            defaultValue: 'Yazhou',
+        });
+        const tags2 = cascader2.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags2.length).toEqual(2);
+        cascader2.unmount();
+
+        /* autoMergeValue is false, leafOnly is true */
+        const cascader3 = render({
+            multiple: true,
+            autoMergeValue: false,
+            leafOnly: true,
+            defaultValue: 'Yazhou',
+        });
+        const tags3 = cascader3.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags3.length).toEqual(2);
+        cascader3.unmount();
+
+        /* autoMergeValue is true, leafOnly is false */
+        const cascader4 = render({
+            multiple: true,
+            autoMergeValue: true,
+            leafOnly: false,
+            defaultValue: 'Yazhou',
+        });
+        const tags4 = cascader4.find(`.${BASE_CLASS_PREFIX}-cascader-selection .${BASE_CLASS_PREFIX}-tag`)
+        expect(tags4.length).toEqual(1);
+        expect(
+            tags4
+                .find(`.${BASE_CLASS_PREFIX}-tag-content`)
+                .getDOMNode()
+                .textContent
+        ).toEqual('亚洲');
+        cascader4.unmount();
+    });
 });

+ 138 - 0
packages/semi-ui/cascader/_story/cascader.stories.js

@@ -273,6 +273,25 @@ export const _Cascader = () => {
       />
       <br />
       <br />
+      <Cascader
+        style={{ width: 300 }}
+        treeData={treeData1}
+        placeholder="Multiple select"
+        multiple
+        motion={false}
+      />
+      <br />
+      <br />
+      <Cascader
+        style={{ width: 300 }}
+        treeData={treeData1}
+        placeholder="Multiple select enableLeafClick"
+        multiple
+        enableLeafClick
+        motion={false}
+      />
+      <br />
+      <br />
       <Cascader
         style={{ width: 300 }}
         treeData={[]}
@@ -374,6 +393,23 @@ export const Disabled = () => {
         filterTreeNode
         disabled
       />
+      <br /><br />
+      <Cascader
+        defaultValue={['yazhou', 'zhongguo']}
+        style={{ width: 300 }}
+        treeData={treeData2}
+        multiple
+        filterTreeNode
+        disabled
+      />
+      <br /><br />
+      <Cascader
+        defaultValue={['yazhou', 'zhongguo']}
+        style={{ width: 300 }}
+        treeData={treeData2}
+        multiple
+        disabled
+      />
     </div>
   );
 };
@@ -420,6 +456,20 @@ export const DisabledOption = () => {
         defaultValue={['yazhou', 'riben']}
         placeholder="Japan node is disabled"
       />
+      <br />
+      <br />
+      <div>multiple selection + filterTreeNode + defaultValue is disabled option</div>
+      <Cascader
+          filterTreeNode
+          multiple
+          style={{ width: 300 }}
+          treeData={treeData2}
+          defaultValue={[
+              ['yazhou', 'riben'],
+              ['beimeizhou', 'jianada']
+          ]}
+          placeholder="Japan node is disabled"
+      />
     </div>
   );
 };
@@ -1211,3 +1261,91 @@ export const OnChangeWithObject = () => (
     />
   </>
 );
+
+export const LeafOnly = () => {
+  const [value, setValue] = useState([])
+  return (
+      <div>
+          <div>autoMergeValue=false,leafOnly=false</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              autoMergeValue={false}
+              leafOnly={false}
+              defaultValue={['zhejiang']}
+          />
+          <br />
+          <br />
+          <div>autoMergeValue=false,leafOnly=true, leafOnly生效</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              autoMergeValue={false}
+              leafOnly={true}
+              defaultValue={['zhejiang']}
+          />
+          <br />
+          <br />
+          <div>受控,autoMergeValue=false,leafOnly=true, leafOnly生效</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              onChange={v=>{
+                  console.log(v);
+                  setValue(v)
+              }}
+              autoMergeValue={false}
+              leafOnly={true}
+              value={value}
+          />
+          <br />
+          <br />
+          <div>受控 onChangeWithObject, autoMergeValue=false,leafOnly=true, leafOnly生效</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              onChange={v=>{
+                  console.log(v);
+                  setValue(v)
+              }}
+              onChangeWithObject
+              autoMergeValue={false}
+              leafOnly={true}
+              value={value}
+          />
+          <br />
+          <br />
+          <div>autoMergeValue=true,leafOnly=false</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              autoMergeValue={true}
+              leafOnly={false}
+              defaultValue={['zhejiang']}
+          />
+          <br />
+          <br />
+          <br />
+          <div>autoMergeValue=true,leafOnly=true</div>
+          <Cascader
+              style={{ width: 300 }}
+              treeData={treeData4}
+              placeholder="请选择所在地区"
+              multiple
+              autoMergeValue={true}
+              leafOnly={true}
+              defaultValue={['zhejiang']}
+          />
+      </div>
+  );
+}

+ 37 - 21
packages/semi-ui/cascader/index.tsx

@@ -14,10 +14,10 @@ import CascaderFoundation, {
 } from '@douyinfe/semi-foundation/cascader/foundation';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/cascader/constants';
 import { numbers as popoverNumbers } from '@douyinfe/semi-foundation/popover/constants';
-import { isEqual, isString, isEmpty, isFunction, isNumber, noop } from 'lodash';
+import { isEqual, isString, isEmpty, isFunction, isNumber, noop, flatten } from 'lodash';
 import '@douyinfe/semi-foundation/cascader/cascader.scss';
 import { IconClear, IconChevronDown } from '@douyinfe/semi-icons';
-import { findKeysForValues, convertDataToEntities } from '@douyinfe/semi-foundation/cascader/util';
+import { findKeysForValues, convertDataToEntities, calcMergeType } from '@douyinfe/semi-foundation/cascader/util';
 import { calcCheckedKeys, normalizeKeyList, calcDisabledKeys } from '@douyinfe/semi-foundation/tree/treeUtil';
 import ConfigContext from '../configProvider/context';
 import BaseComponent, { ValidateStatus } from '../_base/baseComponent';
@@ -143,15 +143,19 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         showRestTagsPopover: PropTypes.bool,
         restTagsPopoverProps: PropTypes.object,
         max: PropTypes.number,
+        separator: PropTypes.string,
         onExceed: PropTypes.func,
         onClear: PropTypes.func,
         loadData: PropTypes.func,
         onLoad: PropTypes.func,
         loadedKeys: PropTypes.array,
         disableStrictly: PropTypes.bool,
+        leafOnly: PropTypes.bool,
+        enableLeafClick: PropTypes.bool,
     };
 
     static defaultProps = {
+        leafOnly: false,
         arrowIcon: <IconChevronDown />,
         stopPropagation: true,
         motion: true,
@@ -168,6 +172,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         filterLeafOnly: true,
         showRestTagsPopover: false,
         restTagsPopoverProps: {},
+        separator: ' / ',
         size: 'default' as const,
         treeNodeFilterProp: 'label' as const,
         displayProp: 'label' as const,
@@ -177,6 +182,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         onClear: noop,
         onDropdownVisibleChange: noop,
         onListScroll: noop,
+        enableLeafClick: false,
     };
 
     options: any;
@@ -185,6 +191,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     triggerRef: React.RefObject<HTMLDivElement>;
     optionsRef: React.RefObject<any>;
     clickOutsideHandler: any;
+    mergeType: string;
 
     constructor(props: CascaderProps) {
         super(props);
@@ -215,8 +222,8 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             checkedKeys: new Set([]),
             /* Key of half checked node, when multiple */
             halfCheckedKeys: new Set([]),
-            /* Auto merged checkedKeys, when multiple */
-            mergedCheckedKeys: new Set([]),
+            /* Auto merged checkedKeys or leaf checkedKeys, when multiple */
+            resolvedCheckedKeys: new Set([]),
             /* Keys of loaded item */
             loadedKeys: new Set(),
             /* Keys of loading item */
@@ -226,6 +233,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         };
         this.options = {};
         this.isEmpty = false;
+        this.mergeType = calcMergeType(props.autoMergeValue, props.leafOnly);
         this.inputRef = React.createRef();
         this.triggerRef = React.createRef();
         this.optionsRef = React.createRef();
@@ -346,7 +354,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             multiple,
             value,
             defaultValue,
-            onChangeWithObject
+            onChangeWithObject,
+            leafOnly,
+            autoMergeValue,
         } = props;
         const { prevProps } = prevState;
         let keyEntities = prevState.keyEntities || {};
@@ -402,21 +412,18 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                     });
                     realKeys = formatKeys;
                 }
-                let checkedKeys = new Set([]);
-                let halfCheckedKeys = new Set([]);
-                realKeys.forEach(v => {
-                    const calRes = calcCheckedKeys(v, keyEntities);
-                    checkedKeys = new Set([...checkedKeys, ...calRes.checkedKeys]);
-                    halfCheckedKeys = new Set([...halfCheckedKeys, ...calRes.halfCheckedKeys]);
-                });
+                const calRes = calcCheckedKeys(flatten(realKeys as string[]), keyEntities);
+                const checkedKeys = new Set(calRes.checkedKeys);
+                const halfCheckedKeys = new Set(calRes.halfCheckedKeys);
                 // disableStrictly
                 if (props.disableStrictly) {
                     newState.disabledKeys = calcDisabledKeys(keyEntities);
                 }
+                const isLeafOnlyMerge = calcMergeType(autoMergeValue, leafOnly) === strings.LEAF_ONLY_MERGE_TYPE;
                 newState.prevProps = props;
                 newState.checkedKeys = checkedKeys;
                 newState.halfCheckedKeys = halfCheckedKeys;
-                newState.mergedCheckedKeys = new Set(normalizeKeyList(checkedKeys, keyEntities));
+                newState.resolvedCheckedKeys = new Set(normalizeKeyList(checkedKeys, keyEntities, isLeafOnlyMerge));
             }
         }
         return newState;
@@ -493,7 +500,6 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         const {
             size,
             disabled,
-            autoMergeValue,
             placeholder,
             maxTagCount,
             showRestTagsPopover,
@@ -503,11 +509,13 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             inputValue,
             checkedKeys,
             keyEntities,
-            mergedCheckedKeys
+            resolvedCheckedKeys
         } = this.state;
         const tagInputcls = cls(`${prefixcls}-tagInput-wrapper`);
         const tagValue: Array<Array<string>> = [];
-        const realKeys = autoMergeValue ? mergedCheckedKeys : checkedKeys;
+        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE
+            ? checkedKeys
+            : resolvedCheckedKeys;
         [...realKeys].forEach(checkedKey => {
             if (!isEmpty(keyEntities[checkedKey])) {
                 tagValue.push(keyEntities[checkedKey].valuePath);
@@ -592,6 +600,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             dropdownStyle,
             loadData,
             emptyContent,
+            separator,
             topSlot,
             bottomSlot,
             showNext,
@@ -606,6 +615,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 <Item
                     activeKeys={activeKeys}
                     selectedKeys={selectedKeys}
+                    separator={separator}
                     loadedKeys={loadedKeys}
                     loadingKeys={loadingKeys}
                     onItemClick={this.handleItemClick}
@@ -658,8 +668,10 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
 
     renderMultipleTags = () => {
         const { autoMergeValue, maxTagCount } = this.props;
-        const { checkedKeys, mergedCheckedKeys } = this.state;
-        const realKeys = autoMergeValue ? mergedCheckedKeys : checkedKeys;
+        const { checkedKeys, resolvedCheckedKeys } = this.state;
+        const realKeys = this.mergeType === strings.NONE_MERGE_TYPE
+            ? checkedKeys
+            : resolvedCheckedKeys;
         const displayTag: Array<ReactNode> = [];
         const hiddenTag: Array<ReactNode> = [];
         [...realKeys].forEach((checkedKey, idx) => {
@@ -729,11 +741,15 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     };
 
     renderCustomTrigger = () => {
-        const { disabled, triggerRender, multiple, autoMergeValue } = this.props;
-        const { selectedKeys, inputValue, inputPlaceHolder, mergedCheckedKeys, checkedKeys } = this.state;
+        const { disabled, triggerRender, multiple } = this.props;
+        const { selectedKeys, inputValue, inputPlaceHolder, resolvedCheckedKeys, checkedKeys } = this.state;
         let realValue;
         if (multiple) {
-            realValue = autoMergeValue ? mergedCheckedKeys : checkedKeys;
+            if (this.mergeType === strings.NONE_MERGE_TYPE) {
+                realValue = checkedKeys;
+            } else {
+                realValue = resolvedCheckedKeys;
+            }
         } else {
             realValue = [...selectedKeys][0];
         }

+ 4 - 2
packages/semi-ui/cascader/item.tsx

@@ -54,6 +54,7 @@ export interface CascaderItemProps {
     emptyContent: React.ReactNode;
     loadData: (selectOptions: CascaderData[]) => Promise<void>;
     data: Array<Data | Entity>;
+    separator: string;
     multiple: boolean;
     checkedKeys: Set<string>;
     halfCheckedKeys: Set<string>;
@@ -75,6 +76,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
         checkedKeys: PropTypes.object,
         halfCheckedKeys: PropTypes.object,
         onItemCheckboxClick: PropTypes.func,
+        separator: PropTypes.string,
         keyword: PropTypes.string
     };
 
@@ -142,7 +144,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
 
     highlight = (searchText: React.ReactNode[]) => {
         const content: React.ReactNode[] = [];
-        const { keyword } = this.props;
+        const { keyword, separator } = this.props;
         searchText.forEach((item, idx) => {
             if (typeof item === 'string' && includes(item, keyword)) {
                 item.split(keyword).forEach((node, index) => {
@@ -159,7 +161,7 @@ export default class Item extends PureComponent<CascaderItemProps> {
                 content.push(item);
             }
             if (idx !== searchText.length - 1) {
-                content.push(' / ');
+                content.push(separator);
             }
         });
         return content;

+ 85 - 2
packages/semi-ui/datePicker/__test__/datePicker.test.js

@@ -280,10 +280,12 @@ describe(`DatePicker`, () => {
         const rightPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-right`);
         const rightNavBtns = rightPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-navigation .${BASE_CLASS_PREFIX}-button`);
 
-        _.get(rightNavBtns, 1).click();
+        // 点击右边面板下一月
+        _.get(rightNavBtns, 2).click();
         await sleep();
 
-        _.times(leftPrevClickTimes).forEach(() => _.first(leftNavBtns).click());
+        // 点击左边面板上一月
+        _.times(leftPrevClickTimes).forEach(() => _.get(leftNavBtns, 1).click());
 
         const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
         const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
@@ -870,4 +872,85 @@ describe(`DatePicker`, () => {
         expect(allSeparators[0].textContent.trim()).toBe(rangeSeparator);
         expect(allSeparators[1].textContent.trim()).toBe(rangeSeparator);
     });
+
+    /**
+     * fix https://github.com/DouyinFE/semi-design/issues/422
+     */
+    it('test input year length larger than 4', async () => {
+        const props = {
+            motion: false,
+            defaultOpen: true,
+            defaultValue: '2021-12-21',
+        };
+        const handleChange = sinon.spy();
+        const elem = mount(
+            <DatePicker {...props} onChange={handleChange} />
+        );
+
+        elem.find('input').simulate('change', { target: { value: '20221-12-21' }});
+        expect(handleChange.called).toBeFalsy();
+    });
+
+    it('test click next/prev year buttons', () => {
+        let props = {
+          type: 'dateRange',
+          motion: false,
+          style: { width: 300 },
+          defaultPickerValue: new Date('2021-12-01'),
+          defaultOpen: true,
+        };
+        const elem = mount(<DatePicker {...props} />);
+
+        const leftPanel = document.querySelector(`.semi-datepicker-month-grid-left`);
+        const leftNavBtns = leftPanel.querySelector(`.semi-datepicker-navigation`).children;
+        const rightPanel = document.querySelector(`.semi-datepicker-month-grid-right`);
+        const rightNavBtns = rightPanel.querySelector(`.semi-datepicker-navigation`).children;
+
+        // 点击左边面板上一年
+        _.get(leftNavBtns, 0).click();
+        expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2020年 12月');
+        // 点击左边面板下一年
+        _.get(leftNavBtns, 4).click();
+        expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 12月');
+
+        // 点击右边面板下一年
+        _.get(rightNavBtns, 4).click();
+        expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2023年 1月');
+        // 点击右边面板上一年
+        _.get(rightNavBtns, 0).click();
+        expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
+    });
+
+    const testMonthSyncChange = type => {
+        let props = {
+            type,
+            motion: false,
+            style: { width: 300 },
+            defaultPickerValue: new Date('2021-12-01'),
+            defaultOpen: true,
+          };
+          const elem = mount(<DatePicker {...props} />);
+  
+          const leftPanel = document.querySelector(`.semi-datepicker-month-grid-left`);
+          const leftNavBtns = leftPanel.querySelector(`.semi-datepicker-navigation`).children;
+          const rightPanel = document.querySelector(`.semi-datepicker-month-grid-right`);
+          const rightNavBtns = rightPanel.querySelector(`.semi-datepicker-navigation`).children;
+  
+          // 点击左边面板下一月,自动切换右面板
+          _.get(leftNavBtns, 3).click();
+          expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
+          expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 2月');
+          // 点击右边面板上一月,自动切换左面板
+          _.get(rightNavBtns, 1).click();
+          expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 12月');
+          expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
+  
+          // 点击左边面板上一月,不需要自动切换右面板
+          _.get(leftNavBtns, 1).click();
+          expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 11月');
+          elem.unmount();
+    }
+
+    it('test month sync change dateRange type', () => { testMonthSyncChange('dateRange') });
+    it('test month sync change dateTimeRange type', () => { testMonthSyncChange('dateTimeRange')});
 });

+ 29 - 1
packages/semi-ui/datePicker/_story/datePicker.stories.js

@@ -36,6 +36,7 @@ import DatePickerSlot from './DatePickerSlot';
 import DatePickerTimeZone from './DatePickerTimeZone';
 import BetterRangePicker from './BetterRangePicker';
 import SyncSwitchMonth from './SyncSwitchMonth';
+import { YearButton } from './v2';
 
 export default {
   title: 'DatePicker',
@@ -65,7 +66,8 @@ export {
   DatePickerSlot,
   DatePickerTimeZone,
   BetterRangePicker,
-  SyncSwitchMonth
+  SyncSwitchMonth,
+  YearButton
 }
 
 const demoDiv = {
@@ -634,3 +636,29 @@ export const RangeSeparator = () => (
     </div>
   </Space>
 );
+
+/**
+ * 修复输入 '20221-12-20' 类似这种年份的日期会崩溃问题
+ * https://github.com/DouyinFE/semi-design/issues/422
+ * 
+ * 非法日期的来源
+ *  - 用户输入
+ *  - 受控传入
+ * @returns 
+ */
+export const FixParseISOBug = () => (
+  <div>
+    <label>
+      <div>选择一个合法值,然后输入一个非法年份</div>
+      <DatePicker defaultValue={'2021-12-20'} onChange={v => console.log('onChange', v)} />
+    </label>
+    <label>
+      <div>defaultValue='20221-12-20'</div>
+      <DatePicker defaultValue={'20221-12-20'} defaultOpen={true} motion={false} onChange={v => console.log('onChange', v)} />
+    </label>
+  </div>
+);
+FixParseISOBug.storyName = '修复 parseISO bug';
+FixParseISOBug.parameters = {
+  chromatic: { disableSnapshot: false },
+};

+ 17 - 0
packages/semi-ui/datePicker/_story/v2/YearButton.jsx

@@ -0,0 +1,17 @@
+import React from 'react';
+import { DatePicker } from '../../../index';
+
+export default function App() {
+    return (
+        <div>
+            <h4>type=date</h4>
+            <DatePicker />
+            <h4>type=dateRange</h4>
+            <DatePicker type="dateRange" defaultPickerValue="2021-12" />
+            <h4>type=dateTimeRange</h4>
+            <DatePicker type="dateTimeRange" />
+            <h4>type=dateRange + compact</h4>
+            <DatePicker type="dateRange" density="compact" />
+        </div>
+    );
+}

+ 1 - 0
packages/semi-ui/datePicker/_story/v2/index.js

@@ -0,0 +1 @@
+export { default as YearButton } from './YearButton';

+ 12 - 1
packages/semi-ui/datePicker/monthsGrid.tsx

@@ -446,6 +446,10 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         });
     };
 
+    getYAMOpenType = () => {
+        return this.foundation.getYAMOpenType();
+    }
+
     renderTimePicker(panelType: PanelType, panelDetail: MonthInfo) {
         const { type, locale, format, hideDisabledOptions, timePickerOpts, dateFnsLocale } = this.props;
         const { pickerDate } = panelDetail;
@@ -606,8 +610,15 @@ export default class MonthsGrid extends BaseComponent<MonthsGridProps, MonthsGri
         } else if (type === 'year' || type === 'month') {
             content = 'year month';
         }
+        const yearOpenType = this.getYAMOpenType();
+
         return (
-            <div className={monthGridCls} x-type={type} ref={current => this.cacheRefCurrent('monthGrid', current)}>
+            <div
+                className={monthGridCls}
+                x-type={type}
+                x-panel-yearandmonth-open-type={yearOpenType}
+                ref={current => this.cacheRefCurrent('monthGrid', current)}
+            >
                 {content}
             </div>
         );

+ 55 - 29
packages/semi-ui/datePicker/navigation.tsx

@@ -6,7 +6,7 @@ import { noop } from 'lodash';
 import IconButton from '../iconButton';
 import Button from '../button';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/datePicker/constants';
-import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
+import { IconChevronLeft, IconChevronRight, IconDoubleChevronLeft, IconDoubleChevronRight } from '@douyinfe/semi-icons';
 import { PanelType } from '@douyinfe/semi-foundation/datePicker/monthsGridFoundation';
 
 const prefixCls = cssClasses.NAVIGATION;
@@ -68,6 +68,8 @@ export default class Navigation extends PureComponent<NavigationProps> {
             onMonthClick,
             onNextMonth,
             onPrevMonth,
+            onPrevYear,
+            onNextYear,
             density,
             shouldBimonthSwitch,
             panelType
@@ -77,43 +79,67 @@ export default class Navigation extends PureComponent<NavigationProps> {
         const iconBtnSize = density === 'compact' ? 'default' : 'large';
         const btnNoHorizontalPadding = true;
         const buttonSize = density === 'compact' ? 'small' : 'default';
-        // Enable dual-panel synchronous switching, and the current panel is the left panel
-        const bimonthSwitchWithLeftPanel = shouldBimonthSwitch && panelType === strings.PANEL_TYPE_LEFT;
-        // Enable dual-panel synchronous switching, and the current panel is the right panel
-        const bimonthSwitchWithRightPanel = shouldBimonthSwitch && panelType === strings.PANEL_TYPE_RIGHT;
+        const isLeftPanel = panelType === strings.PANEL_TYPE_LEFT;
+        const isRightPanel =  panelType === strings.PANEL_TYPE_RIGHT;
+
+        // syncSwitchMonth and the current panel is the left
+        const hiddenLeftPanelRightButtons = shouldBimonthSwitch && isLeftPanel;
+        // syncSwitchMonth and the current panel is the right
+        const hiddenRightPanelLeftButtons = shouldBimonthSwitch && isRightPanel;
+        // `visibility: hidden` will keep the icon in position
+        const leftButtonStyle: React.CSSProperties = {};
+        const rightButtonStyle: React.CSSProperties = {};
+        if (hiddenRightPanelLeftButtons) {
+            leftButtonStyle.visibility = 'hidden';
+        }
+        if (hiddenLeftPanelRightButtons) {
+            rightButtonStyle.visibility = 'hidden';
+        }
 
         const ref = forwardRef || this.navRef;
         return (
             <div className={prefixCls} ref={ref}>
-                {
-                    !bimonthSwitchWithRightPanel &&
-                    (
-                        <IconButton
-                            icon={<IconChevronLeft size={iconBtnSize} />}
-                            size={buttonSize}
-                            onClick={onPrevMonth}
-                            theme={btnTheme}
-                            noHorizontalPadding={btnNoHorizontalPadding}
-                        />
-                    )
-                }
+                <IconButton
+                    key="double-chevron-left"
+                    icon={<IconDoubleChevronLeft size={iconBtnSize}/>} 
+                    size={buttonSize}
+                    theme={btnTheme}
+                    noHorizontalPadding={btnNoHorizontalPadding}
+                    onClick={onPrevYear}
+                    style={leftButtonStyle}
+                />
+                <IconButton
+                    key="chevron-left"
+                    icon={<IconChevronLeft size={iconBtnSize} />}
+                    size={buttonSize}
+                    onClick={onPrevMonth}
+                    theme={btnTheme}
+                    noHorizontalPadding={btnNoHorizontalPadding}
+                    style={leftButtonStyle}
+                />
                 <div className={`${prefixCls}-month`}>
                     <Button onClick={onMonthClick} theme={btnTheme} size={buttonSize}>
                         <span>{monthText}</span>
                     </Button>
                 </div>
-                {
-                    !bimonthSwitchWithLeftPanel &&
-                    (
-                        <IconButton
-                            icon={<IconChevronRight size={iconBtnSize} />}
-                            size={buttonSize}
-                            onClick={onNextMonth}
-                            theme={btnTheme}
-                            noHorizontalPadding={btnNoHorizontalPadding}
-                        />
-                    )
-                }
+                <IconButton
+                    key="chevron-right"
+                    icon={<IconChevronRight size={iconBtnSize} />}
+                    size={buttonSize}
+                    onClick={onNextMonth}
+                    theme={btnTheme}
+                    noHorizontalPadding={btnNoHorizontalPadding}
+                    style={rightButtonStyle}
+                />
+                <IconButton
+                    key="double-chevron-right"
+                    icon={<IconDoubleChevronRight size={iconBtnSize}/>} 
+                    size={buttonSize}
+                    theme={btnTheme}
+                    noHorizontalPadding={btnNoHorizontalPadding}
+                    onClick={onNextYear}
+                    style={rightButtonStyle}
+                />
             </div>
         );
     }

+ 27 - 1
packages/semi-ui/descriptions/__test__/descriptions.test.js

@@ -17,6 +17,14 @@ const dataWithHide = [
     { key: '认证状态', value: '未认证' },
 ];
 
+const dataWithKeyIsNode = [
+    { key: <strong>实际用户数量</strong>, value: '1,480,000' },
+    { key: '7天留存', value: '98%' },
+    { key: '安全等级', value: '3级' },
+    { key: '垂类标签', value: <Tag>电商</Tag> },
+    { key: '认证状态', value: '未认证' },
+];
+
 function renderDescriptions(props) {
     const realProps = {
         data,
@@ -134,7 +142,6 @@ describe('Descriptions', () => {
         largeDesc.unmount();
     });
 
-
     it('Descriptions with jsx', () => {
         const desc = mount(
             <Descriptions>
@@ -164,4 +171,23 @@ describe('Descriptions', () => {
         ).toEqual('1,480,000');
         desc.unmount();
     });
+
+    it('Descriptions with key is node', () => {
+        const desc = renderDescriptions({ data: dataWithKeyIsNode });
+        expect(
+            desc
+                .find(`.${BASE_CLASS_PREFIX}-descriptions-key strong`)
+                .at(0)
+                .getDOMNode()
+                .textContent
+        ).toEqual('实际用户数量');
+        expect(
+            desc
+                .find(`.${BASE_CLASS_PREFIX}-descriptions-key`)
+                .at(1)
+                .getDOMNode()
+                .textContent
+        ).toEqual('7天留存');
+        desc.unmount();
+    });
 })

+ 52 - 2
packages/semi-ui/descriptions/_story/descriptions.stories.js

@@ -1,7 +1,6 @@
 import React from 'react';
-// import { withKnobs, text, boolean } from '@storybook/addon-knobs';
-
 import Descriptions from '../index';
+import Tag from '../../tag';
 
 export default {
   title: 'Descriptions',
@@ -93,3 +92,54 @@ export const DescriptionsItem = () => (
   </div>
 );
 
+export const DescriptionsKeyIsNode = () => {
+  const data = [
+      { key: <strong style={{color: 'red'}}>实际用户数量</strong>, value: '1,480,000' },
+      { key: '7天留存', value: '98%' },
+      { key: '安全等级', value: '3级' },
+      { key: '垂类标签', value: <Tag style={{ margin: 0 }}>电商</Tag> },
+      { key: '认证状态', value: '未认证' },
+  ];
+  const style = {
+      boxShadow: 'var(--shadow-elevated)',
+      backgroundColor: 'var(--color-bg-2)',
+      borderRadius: '4px',
+      padding: '10px',
+      margin: '10px',
+      width: '200px',
+  };
+  return (
+    <>
+      <div>data 传入的写法</div>
+      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
+        <Descriptions align="center" data={data} style={style} />
+        <Descriptions align="justify" data={data} style={style} />
+        <Descriptions align="left" data={data} style={style} />
+        <Descriptions align="plain" data={data} style={style} />
+      </div>
+      <div>JSX 写法</div>
+      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
+        <Descriptions style={style} align="center" >
+          <Descriptions.Item itemKey={<strong style={{ color: 'red' }}>实际用户数量</strong>}>1,480,000</Descriptions.Item>
+          <Descriptions.Item itemKey="7天留存">98%</Descriptions.Item>
+          <Descriptions.Item itemKey="认证状态">未认证</Descriptions.Item>
+        </Descriptions>
+        <Descriptions style={style} align="justify">
+          <Descriptions.Item itemKey={<strong style={{ color: 'red' }}>实际用户数量</strong>}>1,480,000</Descriptions.Item>
+          <Descriptions.Item itemKey="7天留存">98%</Descriptions.Item>
+          <Descriptions.Item itemKey="认证状态">未认证</Descriptions.Item>
+        </Descriptions>
+        <Descriptions style={style} align="left">
+          <Descriptions.Item itemKey={<strong style={{ color: 'red' }}>实际用户数量</strong>}>1,480,000</Descriptions.Item>
+          <Descriptions.Item itemKey="7天留存">98%</Descriptions.Item>
+          <Descriptions.Item itemKey="认证状态">未认证</Descriptions.Item>
+        </Descriptions>
+        <Descriptions style={style} align="plain">
+          <Descriptions.Item itemKey={<strong style={{ color: 'red' }}>实际用户数量</strong>}>1,480,000</Descriptions.Item>
+          <Descriptions.Item itemKey="7天留存">98%</Descriptions.Item>
+          <Descriptions.Item itemKey="认证状态">未认证</Descriptions.Item>
+        </Descriptions>
+      </div>
+    </>
+  );
+};

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

@@ -36,7 +36,7 @@ export default class Item extends PureComponent<DescriptionsItemProps> {
                 <tr className={className} style={style}>
                     <td className={`${prefixCls}-item`}>
                         <span className={keyCls}>
-                            {`${itemKey}:`}
+                            {itemKey}:
                         </span>
                         <span className={valCls}>
                             {typeof children === 'function' ? children() : children}

+ 2 - 1
packages/semi-ui/gulpfile.js

@@ -81,7 +81,8 @@ gulp.task('compileScss', function compileScss() {
                     realUrl = url.replace(/~@douyinfe\/semi-foundation/, semiUIPath);
                 }
                 return { url: realUrl };
-            }
+            },
+            charset: false
         }).on('error', sass.logError))
         .pipe(gulp.dest('lib/es'))
         .pipe(gulp.dest('lib/cjs'));

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

@@ -0,0 +1,160 @@
+import { es } from 'date-fns/locale';
+import { Locale } from '../interface';
+
+/**
+ * [i18n-Spanish(es)]
+ *
+ */
+
+const locale: Locale = {
+    code: 'es',
+    dateFnsLocale: es,
+    Pagination: {
+        item: 'objeto',
+        pageSize: ' objetos / página',
+        page: ' páginas',
+        total: '',
+        jumpTo: 'Ir a',
+    },
+    Modal: {
+        confirm: 'Aceptar',
+        cancel: 'Cancelar',
+    },
+    TimePicker: {
+        placeholder: {
+            time: 'Seleccionar hora',
+            timeRange: 'Seleccionar rango de tiempo',
+        },
+        begin: 'Hora inicial',
+        end: 'Hora final',
+        hour: '',
+        minute: '',
+        second: '',
+        AM: 'AM',
+        PM: 'PM',
+    },
+    DatePicker: {
+        placeholder: {
+            date: 'Seleccionar fecha',
+            dateTime: 'Seleccionar hora y fecha',
+            dateRange: ['Fecha inicial', 'Fecha final'],
+            dateTimeRange: ['Fecha inicial', 'Fecha final'],
+        },
+        footer: {
+            confirm: 'Aceptar',
+            cancel: 'Cancelar',
+        },
+        selectDate: 'Seleccionar fecha',
+        selectTime: 'Seleccionar hora',
+        year: 'año',
+        month: 'mes',
+        day: 'día',
+        monthText: '${month} ${year}',
+        months: {
+            1: 'Ene',
+            2: 'Feb',
+            3: 'Mar',
+            4: 'Abr',
+            5: 'May',
+            6: 'Jun',
+            7: 'Jul',
+            8: 'Ago',
+            9: 'Sep',
+            10: 'Oct',
+            11: 'Nov',
+            12: 'Dic',
+        },
+        fullMonths: {
+            1: 'Enero',
+            2: 'Febrero',
+            3: 'Marzo',
+            4: 'Abril',
+            5: 'Mayo',
+            6: 'Junio',
+            7: 'Julio',
+            8: 'Agosto',
+            9: 'Septiembre',
+            10: 'Octubre',
+            11: 'Noviembre',
+            12: 'Diciembre',
+        },
+        weeks: {
+            Mon: 'Lun',
+            Tue: 'Mar',
+            Wed: 'Mie',
+            Thu: 'Jue',
+            Fri: 'Vie',
+            Sat: 'Sab',
+            Sun: 'Dom',
+        },
+        localeFormatToken: {
+            FORMAT_SWITCH_DATE: 'yyyy-MM-dd',
+        },
+    },
+    Popconfirm: {
+        confirm: 'Aceptar',
+        cancel: 'Cancelar',
+    },
+    Navigation: {
+        collapseText: 'Contraer barra lateral',
+        expandText: 'Expandir barra lateral',
+    },
+    Table: {
+        emptyText: 'Sin resultados',
+        pageText: 'Mostrando del ${currentStart} al ${currentEnd} de ${total}',
+    },
+    Select: {
+        emptyText: 'Sin resultados',
+        createText: 'Crear',
+    },
+    Tree: {
+        emptyText: 'Sin resultados',
+        searchPlaceholder: 'Búsqueda',
+    },
+    Cascader: {
+        emptyText: 'Sin resultados',
+    },
+    List: {
+        emptyText: 'Sin resultados',
+    },
+    Calendar: {
+        allDay: 'Todo el día',
+        AM: '${time} AM',
+        PM: '${time} PM',
+        datestring: '',
+        remaining: '${remained} mas',
+    },
+    Upload: {
+        mainText: 'Clic aquí para cargar archivo o arrastre aquí el archivo',
+        illegalTips: 'Este tipo de archivo no es compatible',
+        legalTips: 'Suelte y comience a cargar',
+        retry: 'Reintentar',
+        replace: 'Reemplazar archivo',
+        clear: 'Limpiar',
+        selectedFiles: 'Archivos seleccionados',
+        illegalSize: 'Tamaño de archivo inválido',
+        fail: 'Error al cargar',
+    },
+    TreeSelect: {
+        searchPlaceholder: 'Búsqueda',
+    },
+    Typography: {
+        copy: 'Copiar',
+        copied: 'Copiado',
+        expand: 'Expandir',
+        collapse: 'Contraer',
+    },
+    Transfer: {
+        emptyLeft: 'Sin datos',
+        emptySearch: 'Sin resultados de búsqueda',
+        emptyRight: 'Sin contenido, verifique desde la izquierda',
+        placeholder: 'Búsqueda',
+        clear: 'Limpiar',
+        selectAll: 'Seleccionar todo',
+        clearSelectAll: 'Deseleccionar todo',
+        total: 'Total ${total} objetos',
+        selected: '${total} objetos seleccionados',
+    },
+};
+
+export default locale;

+ 12 - 12
packages/semi-ui/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.1.5",
+    "version": "2.2.0-beta.1",
     "description": "",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -14,11 +14,11 @@
     },
     "dependencies": {
         "@babel/runtime-corejs3": "^7.15.4",
-        "@douyinfe/semi-animation-react": "2.1.5",
-        "@douyinfe/semi-foundation": "2.1.5",
-        "@douyinfe/semi-icons": "2.1.5",
-        "@douyinfe/semi-illustrations": "2.1.5",
-        "@douyinfe/semi-theme-default": "2.1.5",
+        "@douyinfe/semi-animation-react": "2.2.0-beta.1",
+        "@douyinfe/semi-foundation": "2.2.0-beta.1",
+        "@douyinfe/semi-icons": "2.2.0-beta.1",
+        "@douyinfe/semi-illustrations": "2.2.0-beta.1",
+        "@douyinfe/semi-theme-default": "2.2.0-beta.1",
         "@types/react-window": "^1.8.2",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
@@ -55,11 +55,11 @@
     ],
     "homepage": "https://semi.design",
     "bugs": {
-      "url": "https://github.com/DouyinFE/semi-design/issues"
+        "url": "https://github.com/DouyinFE/semi-design/issues"
     },
     "repository": {
-      "type": "git",
-      "url": "https://github.com/DouyinFE"
+        "type": "git",
+        "url": "https://github.com/DouyinFE"
     },
     "_unpkg": true,
     "unpkgFiles": [
@@ -68,13 +68,13 @@
     ],
     "author": "",
     "license": "MIT",
-    "gitHead": "661ac2de772e28c3b6f934f70e4e08efbe2387b2",
+    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a",
     "devDependencies": {
         "@babel/plugin-proposal-decorators": "^7.15.8",
         "@babel/plugin-transform-runtime": "^7.15.8",
         "@babel/preset-env": "^7.15.8",
         "@babel/preset-react": "^7.14.5",
-        "@douyinfe/semi-scss-compile": "2.1.5",
+        "@douyinfe/semi-scss-compile": "2.2.0-beta.1",
         "@storybook/addon-knobs": "^6.3.1",
         "@types/lodash": "^4.14.176",
         "babel-loader": "^8.2.2",
@@ -99,7 +99,7 @@
         "react-storybook-addon-props-combinations": "^1.1.0",
         "react-virtualized": "^9.22.3",
         "rimraf": "^3.0.2",
-        "sass": "1.32.13",
+        "sass": "1.45.0",
         "sinon": "^6.3.5",
         "terser-webpack-plugin": "^4.2.3",
         "through2": "^4.0.2",

+ 1 - 1
packages/semi-ui/popover/Arrow.tsx

@@ -55,7 +55,7 @@ const Arrow: React.FC<ArrowProps> = (props = {}) => {
             <path d="M0 0L1 0C1 4, 2 5.5, 4 7.5S7,10 7,12S6 14.5, 4 16.5S1,20 1,24L0 24L0 0z" fill={bgColor} />
         </svg>
     );
-}
+};
 
 Arrow.propTypes = {
     position: PropTypes.string,

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

@@ -88,7 +88,7 @@ export default class Item extends PureComponent<RatingItemProps> {
             height: size,
             fontSize: size
         } : {};
-        const iconSize = size === 'small' ? 'default' : 'extra-large';
+        const iconSize = isCustomSize ? 'inherit' : (size === 'small' ? 'default' : 'extra-large');
         const content = character ? character : <IconStar size={iconSize} />;
         return (
             <li className={starCls} style={{ ...sizeStyle }}>

+ 25 - 0
packages/semi-ui/select/_story/select.stories.js

@@ -2850,3 +2850,28 @@ export const ScrollIntoView = () => (
 ScrollIntoView.story = {
   name: 'scroll into view',
 };
+
+
+export const SelectInputPropsDemo = () => {
+
+  const inputProps = {
+    className: 'ttt',
+    onCompositionEnd: (v) => console.log(v.target.value)
+  };
+
+  return (
+    <Select 
+      // onSearch={(v) => console.log(v)}
+      optionList={list}
+      inputProps={inputProps}
+      multiple
+      filter
+      style={{ width: 200 }}
+    >
+    </Select>
+  )
+};
+SelectInputPropsDemo.story = {
+  name: 'inputProps',
+};
+

+ 17 - 6
packages/semi-ui/select/index.tsx

@@ -18,13 +18,14 @@ import { FixedSizeList as List } from 'react-window';
 import { getOptionsFromGroup } from './utils';
 import VirtualRow from './virtualRow';
 
-import Input from '../input/index';
+import Input, { InputProps } from '../input/index';
 import Option, { OptionProps } from './option';
 import OptionGroup from './optionGroup';
 import Spin from '../spin';
 import Trigger from '../trigger';
 import { IconChevronDown, IconClear } from '@douyinfe/semi-icons';
 import { isSemiIcon } from '../_utils';
+import { Subtract } from 'utility-types';
 
 import warning from '@douyinfe/semi-foundation/utils/warning';
 
@@ -40,6 +41,12 @@ const prefixcls = cssClasses.PREFIX;
 
 const key = 0;
 
+type ExcludeInputType = {
+    value?: InputProps['value'];
+    onFocus?: InputProps['onFocus'];
+    onChange?: InputProps['onChange'];
+}
+
 type OnChangeValueType = string | number | Record<string, any>;
 export interface optionRenderProps {
     key?: any;
@@ -115,6 +122,7 @@ export type SelectProps = {
     suffix?: React.ReactNode;
     prefix?: React.ReactNode;
     insetLabel?: React.ReactNode;
+    inputProps?: Subtract<InputProps, ExcludeInputType>;
     showClear?: boolean;
     showArrow?: boolean;
     renderSelectedItem?: RenderSelectedItemFn;
@@ -200,6 +208,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         dropdownStyle: PropTypes.object,
         outerTopSlot: PropTypes.node,
         innerTopSlot: PropTypes.node,
+        inputProps: PropTypes.object,
         outerBottomSlot: PropTypes.node,
         innerBottomSlot: PropTypes.node, // Options slot
         optionList: PropTypes.array,
@@ -558,18 +567,20 @@ class Select extends BaseComponent<SelectProps, SelectState> {
     handleInputChange = (value: string) => this.foundation.handleInputChange(value);
 
     renderInput() {
-        const { size, multiple, disabled } = this.props;
+        const { size, multiple, disabled, inputProps } = this.props;
+        const inputPropsCls = get(inputProps, 'className');
         const inputcls = cls(`${prefixcls}-input`, {
             [`${prefixcls}-input-single`]: !multiple,
             [`${prefixcls}-input-multiple`]: multiple,
-        });
+        }, inputPropsCls);
         const { inputValue } = this.state;
 
-        const inputProps: Record<string, any> = {
+        const selectInputProps: Record<string, any> = {
             value: inputValue,
             disabled,
             className: inputcls,
             onChange: this.handleInputChange,
+            ...inputProps,
         };
 
         let style = {};
@@ -578,18 +589,18 @@ class Select extends BaseComponent<SelectProps, SelectState> {
             style = {
                 width: inputValue ? `${inputValue.length * 16}px` : '2px',
             };
-            inputProps.style = style;
+            selectInputProps.style = style;
         }
         return (
             <Input
                 ref={this.inputRef as any}
                 size={size}
-                {...inputProps}
                 onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
                     // prevent event bubbling which will fire trigger onFocus event
                     e.stopPropagation();
                     // e.nativeEvent.stopImmediatePropagation();
                 }}
+                {...selectInputProps}
             />
         );
     }

+ 50 - 0
packages/semi-ui/timeline/_story/timeline.stories.js

@@ -183,3 +183,53 @@ export const DataSource = () => (
 DataSource.story = {
   name: 'dataSource',
 };
+
+const dataWithOnClick = [
+  {
+      time: '2019-07-14 10:35',
+      extra: '节点辅助说明信息',
+      content: '创建服务现场',
+      type: 'ongoing',
+      onClick: e => console.log(e, '创建服务现场'),
+  },
+  {
+      time: '2019-06-13 16:17',
+      extra: '节点辅助说明信息',
+      content: <span style={{ fontSize: '18px' }}>初步排除网络异常</span>,
+      color: 'pink',
+      onClick: e => console.log(e, '初步排除网络异常'),
+  },
+  {
+      time: '2019-05-14 18:34',
+      extra: '节点辅助说明信息',
+      dot: <Icon type="alert_triangle" />,
+      content: '技术测试异常',
+      type: 'warning',
+      onClick: e => console.log(e, '技术测试异常'),
+  },
+  {
+      time: '2019-05-09 09:12',
+      extra: '节点辅助说明信息',
+      content: '网络异常正在修复',
+      type: 'success',
+      onClick: e => console.log(e, '网络异常正在修复'),
+  }
+];
+
+export const OnClickDemo = () => (
+  <div style={{ width: '400px' }}>
+    <div style={{ width: '300px' }}>
+        <Timeline mode='center'>
+            <Timeline.Item time='2015-09-01' onClick={e=>console.log(e, '创建服务现场')}>创建服务现场</Timeline.Item>
+            <Timeline.Item time='2015-09-01' onClick={e=>console.log(e, '初步排除网络异常')}>初步排除网络异常</Timeline.Item>
+            <Timeline.Item time='2015-09-01' onClick={e=>console.log(e, '技术测试异常')}>技术测试异常</Timeline.Item>
+            <Timeline.Item time='2015-09-01' onClick={e=>console.log(e, '网络异常正在修复')}>网络异常正在修复</Timeline.Item>
+        </Timeline>
+        <Timeline mode='alternate' dataSource={dataWithOnClick} />
+    </div>
+  </div>
+);
+
+OnClickDemo.story = {
+  name: 'onClick',
+};

+ 7 - 2
packages/semi-ui/timeline/item.tsx

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import cls from 'classnames';
+import { noop } from 'lodash';
 import PropTypes from 'prop-types';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/timeline/constants';
 import '@douyinfe/semi-foundation/timeline/timeline.scss';
@@ -13,6 +14,7 @@ export interface TimelineItemProps {
     position?: 'left' | 'right';
     className?: string;
     style?: React.CSSProperties;
+    onClick?: React.MouseEventHandler<HTMLLIElement>;
 }
 
 const prefixCls = cssClasses.ITEM;
@@ -27,11 +29,13 @@ export default class Item extends PureComponent<TimelineItemProps> {
         position: PropTypes.oneOf(strings.ITEM_POS),
         className: PropTypes.string,
         style: PropTypes.object,
+        onClick: PropTypes.func,
     };
 
     static defaultProps = {
         type: 'default',
         time: '',
+        onClick: noop,
     };
 
     render() {
@@ -43,7 +47,8 @@ export default class Item extends PureComponent<TimelineItemProps> {
             type,
             style,
             time,
-            extra
+            extra,
+            onClick,
         } = this.props;
 
         const itemCls = cls(prefixCls,
@@ -57,7 +62,7 @@ export default class Item extends PureComponent<TimelineItemProps> {
         });
         const dotStyle = color ? { style: { backgroundColor: color } } : null;
         return (
-            <li className={itemCls} style={style}>
+            <li className={itemCls} style={style} onClick={onClick}>
                 <div className={`${prefixCls}-tail`} />
                 <div
                     className={dotCls}

+ 50 - 1
packages/semi-ui/upload/__test__/upload.test.js

@@ -869,7 +869,7 @@ describe('Upload', () => {
             beforeClear: () => Promise.reject(),
             onChange: spyOnChangeReject,
             onClear: spyOnClearReject,
-        })
+        });
 
         const clearBtn = upload.find(`.${BASE_CLASS_PREFIX}-upload-file-list-title-clear`).at(0);
         const clearBtnPass = uploadPass.find(`.${BASE_CLASS_PREFIX}-upload-file-list-title-clear`).at(0);
@@ -893,4 +893,53 @@ describe('Upload', () => {
             expect(spyOnClearReject.callCount).toEqual(0);
         });
     });
+
+    it('insert method', () => {
+        const props = {
+            defaultFileList: [],
+        };
+        const upload = getUpload(props);
+        const uploadInstance = upload.instance();
+
+        const file_0 = new File([new ArrayBuffer(1024)], 'chucknorris_0.png', { type: 'image/png' });
+        const file_1 = new File([new ArrayBuffer(1024)], 'chucknorris_1.png', { type: 'image/png' });
+        const file_2 = new File([new ArrayBuffer(1024)], 'chucknorris_2.png', { type: 'image/png' });
+
+        expect(uploadInstance instanceof Upload).toEqual(true);
+        expect(Object.prototype.hasOwnProperty.call(uploadInstance, 'insert')).toEqual(true);
+
+        /**
+         * test fileList state should be [] => [file_0] => [file_1, file_0] => [file_1, file_2, file_0]
+         */
+        upload.instance().insert([file_0]);
+        upload.instance().insert([file_1], 0);
+        upload.instance().insert([file_2], 1);
+
+        expect(Array.isArray(upload.state('fileList'))).toEqual(true);
+        expect(upload.state('fileList').length).toEqual(3);
+        expect(upload.state('fileList')[0].name).toEqual('chucknorris_1.png');
+        expect(upload.state('fileList')[1].name).toEqual('chucknorris_2.png');
+        expect(upload.state('fileList')[2].name).toEqual('chucknorris_0.png');
+    });
+
+    it('showPicInfo works', () => {
+        const props = {
+            listType: 'picture',
+            defaultFileList: [
+                {
+                    uid: '1',
+                    name: 'jiafang1.jpeg',
+                    status: 'success',
+                    size: '130kb',
+                    url: 'https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
+                },
+            ],
+            showPicInfo: true,
+        };
+        const upload = getUpload(props);
+
+        expect(upload.exists(`.${BASE_CLASS_PREFIX}-upload`)).toEqual(true);
+        expect(upload.exists(`.${BASE_CLASS_PREFIX}-upload-file-list-main`)).toEqual(true);
+        expect(upload.exists(`.${BASE_CLASS_PREFIX}-upload-picture-file-card-pic-info`)).toEqual(true);
+    });
 });

+ 110 - 95
packages/semi-ui/upload/fileCard.tsx

@@ -3,7 +3,7 @@ import cls from 'classnames';
 import PropTypes from 'prop-types';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/upload/constants';
 import { getFileSize } from '@douyinfe/semi-foundation/upload/utils';
-import { BaseFileItem } from '@douyinfe/semi-foundation/upload/foundation';
+import { IconAlertCircle, IconClose, IconFile, IconRefresh } from '@douyinfe/semi-icons';
 import LocaleConsumer from '../locale/localeConsumer';
 import { Locale } from '../locale/interface';
 
@@ -13,7 +13,6 @@ import Tooltip from '../tooltip/index';
 import Spin from '../spin/index';
 import { isElement } from '../_base/reactUtils';
 import { RenderFileItemProps } from './interface';
-import { IconAlertCircle, IconClose, IconFile, IconRefresh } from '@douyinfe/semi-icons';
 
 const prefixCls = cssClasses.PREFIX;
 
@@ -69,6 +68,7 @@ class FileCard extends PureComponent<FileCardProps> {
         style: PropTypes.object,
         url: PropTypes.string,
         validateMessage: PropTypes.node,
+        index: PropTypes.number
     };
 
     static defaultProps = {
@@ -92,10 +92,10 @@ class FileCard extends PureComponent<FileCardProps> {
         let content = null;
         switch (true) {
             case typeof validateMessage === 'string' && status === strings.FILE_STATUS_VALIDATING:
-                content = (<><Spin size="small" wrapperClassName={`${prefixCls }-file-card-icon-loading`} />{validateMessage}</>);
+                content = (<><Spin size="small" wrapperClassName={`${prefixCls}-file-card-icon-loading`} />{validateMessage}</>);
                 break;
             case typeof validateMessage === 'string':
-                content = (<><IconAlertCircle className={`${prefixCls }-file-card-icon-error`} />{validateMessage}</>);
+                content = (<><IconAlertCircle className={`${prefixCls}-file-card-icon-error`} />{validateMessage}</>);
                 break;
             case isElement(validateMessage):
                 content = validateMessage;
@@ -111,10 +111,10 @@ class FileCard extends PureComponent<FileCardProps> {
         let icon = null;
         switch (true) {
             case validateMessage && status === strings.FILE_STATUS_VALIDATING:
-                icon = (<Spin size="small" wrapperClassName={`${prefixCls }-picture-file-card-icon-loading`} />);
+                icon = (<Spin size="small" wrapperClassName={`${prefixCls}-picture-file-card-icon-loading`} />);
                 break;
             case validateMessage && (status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL):
-                icon = (<div className={`${prefixCls }-picture-file-card-icon-error`}><ErrorSvg /></div>);
+                icon = (<div className={`${prefixCls}-picture-file-card-icon-error`}><ErrorSvg /></div>);
                 break;
             default:
                 break;
@@ -123,41 +123,50 @@ class FileCard extends PureComponent<FileCardProps> {
     }
 
     renderPic(locale: Locale['Upload']): ReactNode {
-        const { url, percent, status, disabled, style, onPreviewClick } = this.props;
-        const filePicCardCls = cls({
-            [`${prefixCls }-picture-file-card`]: true,
-            [`${prefixCls }-picture-file-card-disabled`]: disabled,
-            [`${prefixCls }-picture-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined',
-        });
+        const { url, percent, status, disabled, style, onPreviewClick, showPicInfo, renderPicInfo, renderThumbnail, name, index } = this.props;
         const showProgress = status === strings.FILE_STATUS_UPLOADING && percent !== 100;
         const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && this.props.showRetry;
         const showReplace = status === strings.FILE_STATUS_SUCCESS && this.props.showReplace;
-        const closeCls = `${prefixCls }-picture-file-card-close`;
+        const filePicCardCls = cls({
+            [`${prefixCls}-picture-file-card`]: true,
+            [`${prefixCls}-picture-file-card-disabled`]: disabled,
+            [`${prefixCls}-picture-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined',
+            [`${prefixCls}-picture-file-card-error`]: status === strings.FILE_STATUS_UPLOAD_FAIL,
+            [`${prefixCls}-picture-file-card-uploading`]: showProgress
+        });
+        const closeCls = `${prefixCls}-picture-file-card-close`;
         const retry = (
             <div
-                className={`${prefixCls }-picture-file-card-retry`} onClick={e => this.onRetry(e)}>
-                <IconRefresh className={`${prefixCls }-picture-file-card-icon-retry`} />
+                className={`${prefixCls}-picture-file-card-retry`} onClick={e => this.onRetry(e)}>
+                <IconRefresh className={`${prefixCls}-picture-file-card-icon-retry`} />
             </div>
         );
         const replace = (
             <Tooltip trigger="hover" position="top" content={locale.replace} showArrow={false} spacing={4}>
                 <div
-                    className={`${prefixCls }-picture-file-card-replace`} onClick={(e): void => this.onReplace(e)}>
-                    <ReplaceSvg className={`${prefixCls }-picture-file-card-icon-replace`} />
+                    className={`${prefixCls}-picture-file-card-replace`} onClick={(e): void => this.onReplace(e)}>
+                    <ReplaceSvg className={`${prefixCls}-picture-file-card-icon-replace`} />
                 </div>
             </Tooltip>
 
         );
 
+        const picInfo = typeof renderPicInfo === 'function' ? renderPicInfo(this.props) : (
+            <div className={`${prefixCls }-picture-file-card-pic-info`}>{index + 1}</div>
+        );
+
+        const thumbnail = typeof renderThumbnail === 'function' ? renderThumbnail(this.props) : <img src={url} alt={`picture of ${name}`} />;
+
         return (
             <div className={filePicCardCls} style={style} onClick={onPreviewClick}>
-                <img src={url} />
+                {thumbnail}
                 {showProgress ? <Progress percent={percent} type="circle" size="small" orbitStroke={'#FFF'} /> : null}
                 {showRetry ? retry : null}
                 {showReplace && replace}
-                {disabled ? null : (
-                    <div className={closeCls} onClick={e => this.onRemove(e)}>
-                        <IconClose size="extra-small" />
+                {showPicInfo && picInfo}
+                {!disabled && (
+                    <div className={closeCls}>
+                        <IconClose size="extra-small" onClick={e => this.onRemove(e)} />
                     </div>
                 )}
                 {this.renderPicValidateMsg()}
@@ -165,6 +174,77 @@ class FileCard extends PureComponent<FileCardProps> {
         );
     }
 
+    renderFile(locale: Locale["Upload"]) {
+        const { name, size, percent, url, showRetry: propsShowRetry, showReplace: propsShowReplace, preview, previewFile, status, style, onPreviewClick } = this.props;
+        const fileCardCls = cls({
+            [`${prefixCls}-file-card`]: true,
+            [`${prefixCls}-file-card-fail`]: status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL,
+            [`${prefixCls}-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined',
+        });
+        const previewCls = cls({
+            [`${prefixCls}-file-card-preview`]: true,
+            [`${prefixCls}-file-card-preview-placeholder`]: !preview || previewFile
+        });
+        const infoCls = `${prefixCls}-file-card-info`;
+        const closeCls = `${prefixCls}-file-card-close`;
+        const replaceCls = `${prefixCls}-file-card-replace`;
+        const showProgress = !(percent === 100 || typeof percent === 'undefined') && status === strings.FILE_STATUS_UPLOADING;
+        // only show retry when upload fail & showRetry is true, no need to show during validate fail
+        const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && propsShowRetry;
+        const showReplace = status === strings.FILE_STATUS_SUCCESS && propsShowReplace;
+        const fileSize = this.transSize(size);
+        let previewContent: ReactNode = preview ? (<img src={url} />) : (<IconFile size="large" />);
+        if (previewFile) {
+            previewContent = previewFile(this.props);
+        }
+        return (
+            <div className={fileCardCls} style={style} onClick={onPreviewClick}>
+                <div className={previewCls}>
+                    {previewContent}
+                </div>
+                <div className={`${infoCls}-main`}>
+                    <div className={`${infoCls}-main-text`}>
+                        <span className={`${infoCls}-name`}>
+                            {name}
+                        </span>
+                        <span>
+                            <span className={`${infoCls}-size`}>{fileSize}</span>
+                            {showReplace && (
+                                <Tooltip trigger="hover" position="top" showArrow={false} content={locale.replace}>
+                                    <IconButton
+                                        onClick={e => this.onReplace(e)}
+                                        type="tertiary"
+                                        theme="borderless"
+                                        size="small"
+                                        icon={<DirectorySvg />}
+                                        className={replaceCls}
+                                    />
+                                </Tooltip>
+                            )}
+
+                        </span>
+
+                    </div>
+                    {showProgress ? (<Progress percent={percent} style={{ width: '100%' }} />) : null}
+                    <div className={`${infoCls}-main-control`}>
+                        <span className={`${infoCls}-validate-message`}>
+                            {this.renderValidateMessage()}
+                        </span>
+                        {showRetry ? <span className={`${infoCls}-retry`} onClick={e => this.onRetry(e)}>{locale.retry}</span> : null}
+                    </div>
+                </div>
+                <IconButton
+                    onClick={e => this.onRemove(e)}
+                    type="tertiary"
+                    icon={<IconClose />}
+                    theme="borderless"
+                    size="small"
+                    className={closeCls}
+                />
+            </div>
+        );
+    }
+
     onRemove(e: MouseEvent): void {
         e.stopPropagation();
         this.props.onRemove(this.props, e);
@@ -181,89 +261,24 @@ class FileCard extends PureComponent<FileCardProps> {
     }
 
     render() {
-        const { name, size, percent, url, listType, preview, previewFile, status, style, onPreviewClick } = this.props;
-        const fileCardCls = cls({
-            [`${prefixCls}-file-card`]: true,
-            [`${prefixCls}-file-card-fail`]: status === strings.FILE_STATUS_VALID_FAIL || status === strings.FILE_STATUS_UPLOAD_FAIL,
-            [`${prefixCls}-file-card-show-pointer`]: typeof onPreviewClick !== 'undefined',
-        });
-        const previewCls = cls({
-            [`${prefixCls}-file-card-preview`]: true,
-            [`${prefixCls}-file-card-preview-placeholder`]: !preview || previewFile
-        });
-        const infoCls = `${prefixCls}-file-card-info`;
-        const closeCls = `${prefixCls}-file-card-close`;
-        const replaceCls = `${prefixCls}-file-card-replace`;
-        const showProgress = !(percent === 100 || typeof percent === 'undefined') && status === strings.FILE_STATUS_UPLOADING;
-        // only show retry when upload fail & showRetry is true, no need to show during validate fail
-        const showRetry = status === strings.FILE_STATUS_UPLOAD_FAIL && this.props.showRetry;
-        const showReplace = status === strings.FILE_STATUS_SUCCESS && this.props.showReplace;
-
+        const { listType } = this.props;
         if (listType === strings.FILE_LIST_PIC) {
             return (
                 <LocaleConsumer componentName="Upload">
-                    {(locale: Locale['Upload']): ReactNode => (this.renderPic(locale))}
+                    {(locale: Locale["Upload"]) => (this.renderPic(locale))}
                 </LocaleConsumer>
             );
         }
 
-        const fileSize = this.transSize(size);
-        let previewContent: ReactNode = preview ? (<img src={url} />) : (<IconFile size="large" />);
-        if (previewFile) {
-            previewContent = previewFile(this.props);
+        if (listType === strings.FILE_LIST_DEFAULT) {
+            return (
+                <LocaleConsumer componentName="Upload">
+                    {(locale: Locale["Upload"]) => (this.renderFile(locale))}
+                </LocaleConsumer>
+            );
         }
-        return (
-            <LocaleConsumer componentName="Upload">
-                {(locale: Locale['Upload']): ReactNode => (
-                    <div className={fileCardCls} style={style} onClick={onPreviewClick}>
-                        {/* <a target='_blank' href={url} className={infoCls} rel="noopener noreferrer"> */}
-                        <div className={previewCls}>
-                            {previewContent}
-                        </div>
-                        <div className={`${infoCls}-main`}>
-                            <div className={`${infoCls}-main-text`}>
-                                <span className={`${infoCls}-name`}>
-                                    {name}
-                                </span>
-                                <span>
-                                    <span className={`${infoCls}-size`}>{fileSize}</span>
-                                    {showReplace && (
-                                        <Tooltip trigger="hover" position="top" showArrow={false} content={locale.replace}>
-                                            <IconButton
-                                                onClick={(e): void => this.onReplace(e)}
-                                                type="tertiary"
-                                                theme="borderless"
-                                                size="small"
-                                                icon={<DirectorySvg />}
-                                                className={replaceCls}
-                                            />
-                                        </Tooltip>
-                                    )}
 
-                                </span>
-
-                            </div>
-                            {showProgress ? (<Progress percent={percent} style={{ width: '100%' }} />) : null}
-                            <div className={`${infoCls}-main-control`}>
-                                <span className={`${infoCls}-validate-message`}>
-                                    {this.renderValidateMessage()}
-                                </span>
-                                {showRetry ? <span className={`${infoCls}-retry`} onClick={e => this.onRetry(e)}>{locale.retry}</span> : null}
-                            </div>
-                        </div>
-                        {/* </a> */}
-                        <IconButton
-                            onClick={(e): void => this.onRemove(e)}
-                            type="tertiary"
-                            icon={<IconClose />}
-                            theme="borderless"
-                            size="small"
-                            className={closeCls}
-                        />
-                    </div>
-                )}
-            </LocaleConsumer>
-        );
+        return null;
     }
 }
 

+ 147 - 53
packages/semi-ui/upload/index.tsx

@@ -3,7 +3,7 @@ import React, { ReactNode, CSSProperties, RefObject, ChangeEvent, DragEvent } fr
 import cls from 'classnames';
 import PropTypes from 'prop-types';
 import { noop } from 'lodash';
-import UploadFoundation, { BaseFileItem, 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';
@@ -64,7 +64,10 @@ export interface UploadProps {
     prompt?: ReactNode;
     promptPosition?: PromptPositionType;
     renderFileItem?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
+    renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
+    renderThumbnail?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
     showClear?: boolean;
+    showPicInfo?: boolean; // Show pic info in picture wall
     showReplace?: boolean; // Display replacement function
     showRetry?: boolean;
     showUploadList?: boolean;
@@ -133,7 +136,10 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         prompt: PropTypes.node,
         promptPosition: PropTypes.oneOf<UploadProps['promptPosition']>(strings.PROMPT_POSITION),
         renderFileItem: PropTypes.func,
+        renderPicInfo: PropTypes.func,
+        renderThumbnail: PropTypes.func,
         showClear: PropTypes.bool,
+        showPicInfo: PropTypes.bool,
         showReplace: PropTypes.bool,
         showRetry: PropTypes.bool,
         showUploadList: PropTypes.bool, // whether to show fileList
@@ -168,6 +174,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         onSuccess: noop,
         promptPosition: 'right' as const,
         showClear: true,
+        showPicInfo: false,
         showReplace: false,
         showRetry: true,
         showUploadList: true,
@@ -297,14 +304,29 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         this.foundation.handleRemove(fileItem);
     };
 
+    /**
+     * ref method
+     * insert files at index
+     * @param files Array<CustomFile>
+     * @param index number
+     * @returns 
+     */
+    insert = (files: Array<CustomFile>, index: number): void => {
+        return this.foundation.insertFileToList(files, index);
+    }
+
+    /**
+     * ref method
+     * manual upload by user
+     */
     upload = (): void => {
         const { fileList } = this.state;
         this.foundation.startUpload(fileList);
     };
 
     renderFile = (file: FileItem, index: number, locale: Locale['Upload']): ReactNode => {
-        const { name, status, validateMessage, _sizeInvalid } = file;
-        const { previewFile, listType, itemStyle, showRetry, renderFileItem, disabled, onPreviewClick, showReplace } = this.props;
+        const { name, status, validateMessage, _sizeInvalid, uid } = file;
+        const { previewFile, listType, itemStyle, showRetry, showPicInfo, renderPicInfo, renderFileItem, renderThumbnail, disabled, onPreviewClick, showReplace } = this.props;
         const onRemove = (): void => this.remove(file);
         const onRetry = (): void => {
             this.foundation.retry(file);
@@ -318,10 +340,14 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             listType,
             onRemove,
             onRetry,
-            key: `${name}${index}`,
+            index,
+            key: uid || `${name}${index}`,
             showRetry: typeof file.showRetry !== 'undefined' ? file.showRetry : showRetry,
             style: itemStyle,
             disabled,
+            showPicInfo,
+            renderPicInfo,
+            renderThumbnail,
             showReplace: typeof file.showReplace !== 'undefined' ? file.showReplace : showReplace,
             onReplace,
             onPreviewClick: typeof onPreviewClick !== 'undefined' ? (): void => this.foundation.handlePreviewClick(file) : undefined,
@@ -343,17 +369,54 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
     };
 
     renderFileList = (): ReactNode => {
-        const { showUploadList, listType, limit, disabled, children } = this.props;
-        const { fileList: stateFileList } = this.state;
+        const { listType } = this.props;
+        if (listType === strings.FILE_LIST_PIC) {
+            return this.renderFileListPic();
+        }
+
+        if (listType === strings.FILE_LIST_DEFAULT) {
+            return this.renderFileListDefault();
+        }
+
+        return null;
+    };
+
+    renderFileListPic = () => {
+        const { showUploadList, limit, disabled, children, draggable } = this.props;
+        const { fileList: stateFileList, dragAreaStatus } = this.state;
         const fileList = this.props.fileList || stateFileList;
-        const isPicType = listType === strings.FILE_LIST_PIC;
-        const showAddTriggerInList = isPicType && (limit ? limit > fileList.length : true);
-        const uploadAddCls = cls(`${prefixCls }-add`, {
-            [`${prefixCls }-picture-add`]: isPicType,
-            [`${prefixCls}-picture-add-disabled`]: disabled
+        const showAddTriggerInList = limit ? limit > fileList.length : true;
+        const dragAreaBaseCls = `${prefixCls}-drag-area`;
+        const uploadAddCls = cls(`${prefixCls}-add`, {
+            [`${prefixCls}-picture-add`]: true,
+            [`${prefixCls}-picture-add-disabled`]: disabled,
         });
+        const fileListCls = cls(`${prefixCls}-file-list`, {
+            [`${prefixCls}-picture-file-list`]: true,
+        });
+        const dragAreaCls = cls({
+            [`${dragAreaBaseCls}-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
+            [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL
+        });
+        const mainCls = `${prefixCls}-file-list-main`;
+        const addContentProps = {
+            className: uploadAddCls,
+            onClick: this.onClick,
+        };
+        const containerProps = {
+            className: fileListCls
+        };
+        const draggableProps = {
+            onDrop: this.onDrop,
+            onDragOver: this.onDragOver,
+            onDragLeave: this.onDragLeave,
+            onDragEnter: this.onDragEnter,
+        };
+        if (draggable) {
+            Object.assign(addContentProps, draggableProps, { className: cls(uploadAddCls, dragAreaCls) });
+        }
         const addContent = (
-            <div className={uploadAddCls} onClick={this.onClick}>
+            <div {...addContentProps}>
                 {children}
             </div>
         );
@@ -365,23 +428,46 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             return null;
         }
 
-        const fileListCls = cls(`${prefixCls }-file-list`, {
-            [`${prefixCls }-picture-file-list`]: isPicType,
-        });
-        const titleCls = `${prefixCls }-file-list-title`;
-        const mainCls = `${prefixCls }-file-list-main`;
-        const showTitle = limit !== 1 && fileList.length && listType !== strings.FILE_LIST_PIC;
+        return (
+            <LocaleConsumer componentName="Upload">
+                {(locale: Locale['Upload']) => (
+                    <div {...containerProps}>
+                        <div className={mainCls}>
+                            {fileList.map((file, index) => this.renderFile(file, index, locale))}
+                            {showAddTriggerInList ? addContent : null}
+                        </div>
+                    </div>
+                )}
+            </LocaleConsumer>
+        );
+    }
+
+    renderFileListDefault = () => {
+        const { showUploadList, limit, disabled } = this.props;
+        const { fileList: stateFileList } = this.state;
+        const fileList = this.props.fileList || stateFileList;
+        const fileListCls = cls(`${prefixCls}-file-list`);
+        const titleCls = `${prefixCls}-file-list-title`;
+        const mainCls = `${prefixCls}-file-list-main`;
+        const showTitle = limit !== 1 && fileList.length;
         const showClear = this.props.showClear && !disabled;
+        const containerProps = {
+            className: fileListCls
+        };
+
+        if (!showUploadList || !fileList.length) {
+            return null;
+        }
 
         return (
             <LocaleConsumer componentName="Upload">
-                {(locale: Locale['Upload']): ReactNode => (
-                    <div className={fileListCls}>
+                {(locale: Locale['Upload']) => (
+                    <div {...containerProps}>
                         {showTitle ? (
                             <div className={titleCls}>
-                                <span className={`${titleCls }-choosen`}>{locale.selectedFiles}</span>
+                                <span className={`${titleCls}-choosen`}>{locale.selectedFiles}</span>
                                 {showClear ? (
-                                    <span onClick={this.clear} className={`${titleCls }-clear`}>
+                                    <span onClick={this.clear} className={`${titleCls}-clear`}>
                                         {locale.clear}
                                     </span>
                                 ) : null}
@@ -390,13 +476,12 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
 
                         <div className={mainCls}>
                             {fileList.map((file, index) => this.renderFile(file, index, locale))}
-                            {showAddTriggerInList ? addContent : null}
                         </div>
                     </div>
                 )}
             </LocaleConsumer>
         );
-    };
+    }
 
     onDrop = (e: DragEvent<HTMLDivElement>): void => {
         this.foundation.handleDrop(e);
@@ -415,14 +500,30 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
         this.foundation.handleDragEnter(e);
     };
 
+    renderAddContent = () => {
+        const { draggable, children, listType } = this.props;
+        const uploadAddCls = cls(`${prefixCls}-add`);
+        if (listType === strings.FILE_LIST_PIC) {
+            return null;
+        }
+        if (draggable) {
+            return this.renderDragArea();
+        }
+        return (
+            <div className={uploadAddCls} onClick={this.onClick}>
+                {children}
+            </div>
+        );
+    }
+
     renderDragArea = (): ReactNode => {
         const { dragAreaStatus } = this.state;
         const { children, dragIcon, dragMainText, dragSubText } = this.props;
-        const dragAreaBaseCls = `${prefixCls }-drag-area`;
+        const dragAreaBaseCls = `${prefixCls}-drag-area`;
         const dragAreaCls = cls(dragAreaBaseCls, {
-            [`${dragAreaBaseCls }-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
-            [`${dragAreaBaseCls }-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL,
-            [`${dragAreaBaseCls }-custom`]: children,
+            [`${dragAreaBaseCls}-legal`]: dragAreaStatus === strings.DRAG_AREA_LEGAL,
+            [`${dragAreaBaseCls}-illegal`]: dragAreaStatus === strings.DRAG_AREA_ILLEGAL,
+            [`${dragAreaBaseCls}-custom`]: children,
         });
 
         return (
@@ -440,20 +541,20 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                             children
                         ) : (
                             <>
-                                <div className={`${dragAreaBaseCls }-icon`}>
+                                <div className={`${dragAreaBaseCls}-icon`}>
                                     {dragIcon || <IconUpload size="extra-large" />}
                                 </div>
-                                <div className={`${dragAreaBaseCls }-text`}>
-                                    <div className={`${dragAreaBaseCls }-main-text`}>
+                                <div className={`${dragAreaBaseCls}-text`}>
+                                    <div className={`${dragAreaBaseCls}-main-text`}>
                                         {dragMainText || locale.mainText}
                                     </div>
-                                    <div className={`${dragAreaBaseCls }-sub-text`}>{dragSubText}</div>
-                                    <div className={`${dragAreaBaseCls }-tips`}>
+                                    <div className={`${dragAreaBaseCls}-sub-text`}>{dragSubText}</div>
+                                    <div className={`${dragAreaBaseCls}-tips`}>
                                         {dragAreaStatus === strings.DRAG_AREA_LEGAL && (
-                                            <span className={`${dragAreaBaseCls }-tips-legal`}>{locale.legalTips}</span>
+                                            <span className={`${dragAreaBaseCls}-tips-legal`}>{locale.legalTips}</span>
                                         )}
                                         {dragAreaStatus === strings.DRAG_AREA_ILLEGAL && (
-                                            <span className={`${dragAreaBaseCls }-tips-illegal`}>
+                                            <span className={`${dragAreaBaseCls}-tips-illegal`}>
                                                 {locale.illegalTips}
                                             </span>
                                         )}
@@ -485,27 +586,20 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
             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',
+            [`${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 uploadAddCls = cls(`${prefixCls }-add`);
-        const inputCls = cls(`${prefixCls }-hidden-input`);
-        const inputReplaceCls = cls(`${prefixCls }-hidden-input-replace`);
-        const promptCls = cls(`${prefixCls }-prompt`);
-        const validateMsgCls = cls(`${prefixCls }-validate-message`);
+        const inputCls = cls(`${prefixCls}-hidden-input`);
+        const inputReplaceCls = cls(`${prefixCls}-hidden-input-replace`);
+        const promptCls = cls(`${prefixCls}-prompt`);
+        const validateMsgCls = cls(`${prefixCls}-validate-message`);
 
         const dirProps = directory ? { directory: 'directory', webkitdirectory: 'webkitdirectory' } : {};
 
-        const addContent =
-            listType !== strings.FILE_LIST_PIC ? (
-                <div className={uploadAddCls} onClick={this.onClick}>
-                    {children}
-                </div>
-            ) : null;
         return (
             <div className={uploadCls} style={style} x-prompt-pos={promptPosition}>
                 <input
@@ -532,7 +626,7 @@ class Upload extends BaseComponent<UploadProps, UploadState> {
                     className={inputReplaceCls}
                     ref={this.replaceInputRef}
                 />
-                {draggable ? this.renderDragArea() : addContent}
+                {this.renderAddContent()}
                 {prompt ? <div className={promptCls}>{prompt}</div> : null}
 
                 {validateMessage ? <div className={validateMsgCls}>{validateMessage}</div> : null}

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

@@ -45,12 +45,15 @@ export interface FileItem extends BaseFileItem {
 }
 
 export interface RenderFileItemProps extends FileItem {
+    index?: number;
     previewFile?: (fileItem: RenderFileItemProps) => ReactNode;
     listType: UploadListType;
     onRemove: (props: RenderFileItemProps, e: MouseEvent) => void;
     onRetry: (props: RenderFileItemProps, e: MouseEvent) => void;
     onReplace: (props: RenderFileItemProps, e: MouseEvent) => void;
     key: string;
+    showPicInfo?: boolean;
+    renderPicInfo?: (renderFileItemProps: RenderFileItemProps) => ReactNode;
     showRetry: boolean;
     showReplace: boolean;
     style?: CSSProperties;

+ 3 - 2
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.1.5",
+    "version": "2.2.0-beta.1",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",
@@ -36,5 +36,6 @@
         "@types/loader-utils": "^2.0.3",
         "rimraf": "^3.0.2",
         "typescript": "^4"
-    }
+    },
+    "gitHead": "eb34a4f25f002bb4cbcfa51f3df93bed868c831a"
 }

+ 1 - 1
packages/semi-webpack/src/semi-theme-loader.ts

@@ -16,7 +16,7 @@ export default function SemiThemeLoader(source: string) {
     let componentVariables: string | boolean;
     try {
         componentVariables = resolve.sync(this.context, `${theme}/scss/local.scss`);
-    } catch(e) {}
+    } catch (e) {}
 
     if (query.include || query.variables || componentVariables) {
         let localImport = '';

+ 2 - 2
src/components/IconList/index.scss

@@ -55,8 +55,8 @@
 
     .semi-icons-item-name {
         padding: 8px 16px 0 16px;
-        white-space: normal;
-        word-break: keep-all;
+        white-space: pre-wrap;
+        word-break: break-word;
         text-align: center;
     }
 

+ 0 - 2
src/html.js

@@ -166,8 +166,6 @@ export default function HTML(props) {
                 <div key={'body'} id="___gatsby" dangerouslySetInnerHTML={{ __html: props.body }} />
                 {props.postBodyComponents}
             </body>
-            <script src="https://unpkg.com/[email protected]/min/vs/loader.js"/>
-            <script src="https://unpkg.com/[email protected]/min/vs/editor/editor.main.js"/>
         </html>
     );
 }

+ 2 - 1
src/templates/scope.js

@@ -26,6 +26,7 @@ import th_TH from '@douyinfe/semi-ui/locale/source/th_TH';
 import tr_TR from '@douyinfe/semi-ui/locale/source/tr_TR';
 import pt_BR from '@douyinfe/semi-ui/locale/source/pt_BR';
 import zh_TW from '@douyinfe/semi-ui/locale/source/zh_TW';
+import es from '@douyinfe/semi-ui/locale/source/es';
 import { SortableContainer, SortableElement, sortableHandle } from 'react-sortable-hoc';
 import GraphemeSplitter from 'grapheme-splitter';
 
@@ -117,4 +118,4 @@ export {
 
 export { debounce, throttle, range, get, filter, map, some };
 
-export { zh_CN, en_GB, en_US, ko_KR, ja_JP, ar, vi_VN, ru_RU, id_ID, ms_MY, th_TH, tr_TR, pt_BR, zh_TW };
+export { zh_CN, en_GB, en_US, ko_KR, ja_JP, ar, vi_VN, ru_RU, id_ID, ms_MY, th_TH, tr_TR, pt_BR, zh_TW, es };

BIN
static/editor/base/browser/ui/codicons/codicon/codicon.ttf


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 5 - 0
static/editor/base/worker/workerMain.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/abap/abap.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/apex/apex.js


+ 7 - 0
static/editor/basic-languages/azcli/azcli.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 2.8.1(1b8581c9aa6b47fa0e3003498e3e756a3d55f2b1)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/azcli/azcli",["require","exports"],(function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.language=t.conf=void 0,t.conf={comments:{lineComment:"#"}},t.language={defaultToken:"keyword",ignoreCase:!0,tokenPostfix:".azcli",str:/[^#\s]/,tokenizer:{root:[{include:"@comment"},[/\s-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}],[/^-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":{token:"key.identifier",next:"@type"}}}]],type:[{include:"@comment"},[/-+@str*\s*/,{cases:{"@eos":{token:"key.identifier",next:"@popall"},"@default":"key.identifier"}}],[/@str+\s*/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}]],comment:[[/#.*$/,{cases:{"@eos":{token:"comment",next:"@popall"}}}]]}}}));

+ 7 - 0
static/editor/basic-languages/bat/bat.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 2.8.1(1b8581c9aa6b47fa0e3003498e3e756a3d55f2b1)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/bat/bat",["require","exports"],(function(e,s){"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.language=s.conf=void 0,s.conf={comments:{lineComment:"REM"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],folding:{markers:{start:new RegExp("^\\s*(::\\s*|REM\\s+)#region"),end:new RegExp("^\\s*(::\\s*|REM\\s+)#endregion")}}},s.language={defaultToken:"",ignoreCase:!0,tokenPostfix:".bat",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"delimiter.square",open:"[",close:"]"}],keywords:/call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/,symbols:/[=><!~?&|+\-*\/\^;\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^(\s*)(rem(?:\s.*|))$/,["","comment"]],[/(\@?)(@keywords)(?!\w)/,[{token:"keyword"},{token:"keyword.$2"}]],[/[ \t\r\n]+/,""],[/setlocal(?!\w)/,"keyword.tag-setlocal"],[/endlocal(?!\w)/,"keyword.tag-setlocal"],[/[a-zA-Z_]\w*/,""],[/:\w*/,"metatag"],[/%[^%]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],string:[[/[^\\"'%]+/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/%[\w ]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/$/,"string","@popall"]]}}}));

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/bicep/bicep.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/cameligo/cameligo.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/clojure/clojure.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
static/editor/basic-languages/coffee/coffee.js


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä