Bläddra i källkod

Merge branch 'release'

point.halo 6 månader sedan
förälder
incheckning
b7a3843938
100 ändrade filer med 5060 tillägg och 752 borttagningar
  1. 1 1
      content/feedback/banner/index-en-US.md
  2. 1 1
      content/feedback/banner/index.md
  3. 1 1
      content/feedback/notification/index-en-US.md
  4. 1 1
      content/feedback/notification/index.md
  5. 1 1
      content/feedback/popconfirm/index-en-US.md
  6. 1 1
      content/feedback/popconfirm/index.md
  7. 1 1
      content/feedback/progress/index-en-US.md
  8. 1 1
      content/feedback/progress/index.md
  9. 1 1
      content/feedback/skeleton/index-en-US.md
  10. 1 1
      content/feedback/skeleton/index.md
  11. 1 1
      content/feedback/spin/index-en-US.md
  12. 1 1
      content/feedback/spin/index.md
  13. 1 1
      content/feedback/toast/index-en-US.md
  14. 1 1
      content/feedback/toast/index.md
  15. 194 0
      content/input/inputnumber/index-en-US.md
  16. 189 0
      content/input/inputnumber/index.md
  17. 1 0
      content/order.js
  18. 1 1
      content/other/configprovider/index-en-US.md
  19. 1 1
      content/other/configprovider/index.md
  20. 1 1
      content/other/locale/index-en-US.md
  21. 1 1
      content/other/locale/index.md
  22. 1 1
      content/plus/audioPlayer/index-en-US.md
  23. 1 1
      content/plus/audioPlayer/index.md
  24. 1 1
      content/show/chart/index-en-US.md
  25. 1 1
      content/show/chart/index.md
  26. 553 0
      content/show/userGuide/index-en-US.md
  27. 553 0
      content/show/userGuide/index.md
  28. 16 0
      content/start/changelog/index-en-US.md
  29. 176 159
      content/start/changelog/index.md
  30. 32 96
      content/start/design-to-code/index-en-US.md
  31. 34 101
      content/start/design-to-code/index.md
  32. 1 0
      content/start/overview/index.md
  33. 5 5
      content/start/tailwind/index.md
  34. 1 1
      lerna.json
  35. 3 3
      packages/semi-animation-react/package.json
  36. 1 1
      packages/semi-animation-styled/package.json
  37. 1 1
      packages/semi-animation/package.json
  38. 1 1
      packages/semi-eslint-plugin/package.json
  39. 176 10
      packages/semi-foundation/inputNumber/foundation.ts
  40. 3 3
      packages/semi-foundation/package.json
  41. 2 0
      packages/semi-foundation/userGuide/animation.scss
  42. 35 0
      packages/semi-foundation/userGuide/constants.ts
  43. 87 0
      packages/semi-foundation/userGuide/foundation.ts
  44. 147 0
      packages/semi-foundation/userGuide/userGuide.scss
  45. 48 0
      packages/semi-foundation/userGuide/variables.scss
  46. 1 1
      packages/semi-icons-lab/package.json
  47. 1 1
      packages/semi-icons/package.json
  48. 1 1
      packages/semi-illustrations/package.json
  49. 1 1
      packages/semi-json-viewer-core/package.json
  50. 2 2
      packages/semi-next/package.json
  51. 1 1
      packages/semi-rspack/package.json
  52. 1 1
      packages/semi-scss-compile/package.json
  53. 1 1
      packages/semi-theme-default/package.json
  54. 1 0
      packages/semi-ui/index.ts
  55. 2 2
      packages/semi-ui/input/index.tsx
  56. 132 0
      packages/semi-ui/inputNumber/__test__/inputNumber.test.js
  57. 174 13
      packages/semi-ui/inputNumber/_story/inputNumber.stories.jsx
  58. 35 3
      packages/semi-ui/inputNumber/index.tsx
  59. 91 76
      packages/semi-ui/jsonViewer/index.tsx
  60. 11 2
      packages/semi-ui/locale/_story/locale.stories.jsx
  61. 14 0
      packages/semi-ui/locale/interface.ts
  62. 3 2
      packages/semi-ui/locale/localeConsumer.tsx
  63. 13 0
      packages/semi-ui/locale/source/ar.ts
  64. 13 0
      packages/semi-ui/locale/source/de.ts
  65. 14 1
      packages/semi-ui/locale/source/en_GB.ts
  66. 14 1
      packages/semi-ui/locale/source/en_US.ts
  67. 13 0
      packages/semi-ui/locale/source/es.ts
  68. 13 0
      packages/semi-ui/locale/source/fr.ts
  69. 13 0
      packages/semi-ui/locale/source/id_ID.ts
  70. 13 0
      packages/semi-ui/locale/source/it.ts
  71. 13 0
      packages/semi-ui/locale/source/ja_JP.ts
  72. 13 0
      packages/semi-ui/locale/source/ko_KR.ts
  73. 13 0
      packages/semi-ui/locale/source/ms_MY.ts
  74. 14 1
      packages/semi-ui/locale/source/nl_NL.ts
  75. 14 1
      packages/semi-ui/locale/source/pl_PL.ts
  76. 13 0
      packages/semi-ui/locale/source/pt_BR.ts
  77. 13 0
      packages/semi-ui/locale/source/ro.ts
  78. 13 0
      packages/semi-ui/locale/source/ru_RU.ts
  79. 14 1
      packages/semi-ui/locale/source/sv_SE.ts
  80. 13 0
      packages/semi-ui/locale/source/th_TH.ts
  81. 13 0
      packages/semi-ui/locale/source/tr_TR.ts
  82. 13 0
      packages/semi-ui/locale/source/vi_VN.ts
  83. 13 0
      packages/semi-ui/locale/source/zh_CN.ts
  84. 13 0
      packages/semi-ui/locale/source/zh_TW.ts
  85. 58 0
      packages/semi-ui/markdownRender/__test__/markdown.test.js
  86. 33 0
      packages/semi-ui/markdownRender/_story/markdownRender.stories.jsx
  87. 8 9
      packages/semi-ui/markdownRender/components/table.tsx
  88. 7 7
      packages/semi-ui/package.json
  89. 610 0
      packages/semi-ui/userGuide/_story/userGuide.stories.jsx
  90. 42 0
      packages/semi-ui/userGuide/_story/userGuide.stories.tsx
  91. 545 0
      packages/semi-ui/userGuide/index.tsx
  92. 1 1
      packages/semi-webpack/package.json
  93. 201 200
      sitemap.xml
  94. 125 0
      src/components/DesignToCodeFeature/index.jsx
  95. 333 0
      src/components/DesignToCodeFeature/index.scss
  96. 3 5
      src/html.js
  97. 6 0
      src/images/docIcons/doc-userGuide.svg
  98. 16 2
      src/locale/en-US.js
  99. 26 13
      src/locale/zh-CN.js
  100. 2 0
      src/templates/postTemplate.js

+ 1 - 1
content/feedback/banner/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 81
+order: 82
 category: Feedback
 title:  Banner
 subTitle: Banner

+ 1 - 1
content/feedback/banner/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 81
+order: 82
 category: 反馈类
 title:  Banner 通知横幅
 icon: doc-banner

+ 1 - 1
content/feedback/notification/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 82
+order: 83
 category: Feedback
 title:  Notification
 subTitle: Notification

+ 1 - 1
content/feedback/notification/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 82
+order: 83
 category: 反馈类
 title: Notification 通知
 icon: doc-notification

+ 1 - 1
content/feedback/popconfirm/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 83
+order: 84
 category: Feedback
 title:  Popconfirm
 subTitle: Popconfirm

+ 1 - 1
content/feedback/popconfirm/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 83
+order: 84
 category: 反馈类
 title:  Popconfirm 气泡确认框
 icon: doc-popconfirm

+ 1 - 1
content/feedback/progress/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 84
+order: 85
 category: Feedback
 title: Progress
 subTitle: Progress

+ 1 - 1
content/feedback/progress/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 84
+order: 85
 category: 反馈类
 title: Progress 进度条
 icon: doc-progress

+ 1 - 1
content/feedback/skeleton/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 85
+order: 86
 category: Feedback
 title: Skeleton
 subTitle: Skeleton

+ 1 - 1
content/feedback/skeleton/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 85
+order: 86
 category: 反馈类
 title: Skeleton 骨架屏
 icon: doc-skeleton

+ 1 - 1
content/feedback/spin/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 86
+order: 87
 category: Feedback
 title: Spin
 subTitle: Spin

+ 1 - 1
content/feedback/spin/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 86
+order: 87
 category: 反馈类
 title: Spin 加载器
 icon: doc-spin

+ 1 - 1
content/feedback/toast/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 87
+order: 88
 category: Feedback
 title: Toast
 subTitle: Toast

+ 1 - 1
content/feedback/toast/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 87
+order: 88
 category: 反馈类
 title: Toast 提示
 icon: doc-toast

+ 194 - 0
content/input/inputnumber/index-en-US.md

@@ -197,6 +197,196 @@ function Demo () {
 }
 ```
 
+### Currency Display
+Version 2.77.0 supports currency display. In internationalization mode, enable currency={true} and the component will automatically display the corresponding currency type according to localeCode. (Note that the component key value needs to be updated after switching the language type)
+
+```jsx live=true
+import React from 'react';
+import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
+import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
+import ja_JP from '@douyinfe/semi-ui/lib/es/locale/source/ja_JP';
+import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import vi_VN from '@douyinfe/semi-ui/lib/es/locale/source/vi_VN';
+import ru_RU from '@douyinfe/semi-ui/lib/es/locale/source/ru_RU';
+import id_ID from '@douyinfe/semi-ui/lib/es/locale/source/id_ID';
+import ms_MY from '@douyinfe/semi-ui/lib/es/locale/source/ms_MY';
+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 sv_SE from '@douyinfe/semi-ui/lib/es/locale/source/sv_SE';
+import pl_PL from '@douyinfe/semi-ui/lib/es/locale/source/pl_PL';
+import nl_NL from '@douyinfe/semi-ui/lib/es/locale/source/nl_NL';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
+import it from '@douyinfe/semi-ui/lib/es/locale/source/it';
+import de from '@douyinfe/semi-ui/lib/es/locale/source/de';
+import fr from '@douyinfe/semi-ui/lib/es/locale/source/fr';
+import ro from '@douyinfe/semi-ui/lib/es/locale/source/ro';
+import { LocaleProvider, InputNumber, Select } from '@douyinfe/semi-ui';
+
+class I18nDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            locale: zh_CN,
+            localeCode: 'zh_CN',
+        };
+        this.onLanguageChange = this.onLanguageChange.bind(this);
+    }
+
+    onLanguageChange(code) {
+        let language = {
+            'zh_CN': zh_CN,
+            'en_GB': en_GB,
+            'en_US': en_US,
+            'ko_KR': ko_KR,
+            'ja_JP': ja_JP,
+            'ar': ar,
+            'vi_VN': vi_VN,
+            'ru_RU': ru_RU,
+            'id_ID': id_ID,
+            'ms_MY': ms_MY,
+            'th_TH': th_TH,
+            'tr_TR': tr_TR,
+            'pt_BR': pt_BR,
+            'zh_TW': zh_TW,
+            'es': es,
+            'sv_SE': sv_SE,
+            'pl_PL': pl_PL,
+            'nl_NL': nl_NL,
+            de,
+            it,
+            fr,
+            ro
+        };
+        this.setState({ locale: language[code], localeCode: code });
+    }
+
+    render() {
+        const { locale, localeCode } = this.state;
+        return (
+            <>
+                <div style={{ paddingBottom: 20 }}>
+                    <Select onChange={this.onLanguageChange} insetLabel='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
+                        <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.Option value='de'>German</Select.Option>
+                        <Select.Option value='it'>Italian</Select.Option>
+                        <Select.Option value='fr'>French</Select.Option>
+                        <Select.Option value='ro'>Romanian</Select.Option>
+                        <Select.Option value='sv_SE'>Swedish</Select.Option>
+                        <Select.Option value='pl_PL'>Polish</Select.Option>
+                        <Select.Option value='nl_NL'>Dutch</Select.Option>
+                    </Select>
+                </div>
+                <LocaleProvider locale={locale}>
+                    <InputNumber key={localeCode} currency={true} defaultValue={123456.78} />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+
+```
+You can also specify the currency to be displayed by manually passing localeCode and currency.
+
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY</div>
+            <InputNumber localeCode="zh-CN" currency="CNY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇪🇺 EUR</div>
+            <InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇯🇵 JPY</div>
+            <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇻🇳 VND</div>
+            <InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+Supports three display modes: symbol, code, and name. It is controlled by the currencyDisplay property. The currency symbol is displayed by default. Set showCurrencySymbol to false to hide the display of currency symbol/code/name
+
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber currency="CNY" currencyDisplay="code" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber currency="CNY" currencyDisplay="symbol" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>Hide display of currency symbols/codes/names</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} showCurrencySymbol={false}/>
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
+Hide the display of currency symbols/codes/names, and display the currency symbol through the prefix/suffix
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="CNY" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="¥" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" suffix="人民币" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
 ## API Reference
 
 | Properties   | Instructions                                                                                    | type                              | Default   | Version    |
@@ -204,12 +394,15 @@ function Demo () {
 | autofocus    | Automatic access to focus                                                                       | boolean                           | false     |            |
 | className    | class name of InputNumber                                                               | string  | -      |
 | clearIcon    | Can be used to customize the clear button, valid when showClear is true                       | ReactNode                       |     | 2.25.0 |
+| currency | Currency type. In international mode, currency={true} is enabled. The component will automatically display the corresponding currency type according to the locale. You can also manually pass in localeCode and currency to specify the currency type to display. The optional values ​​of currency are `CNY`,`EUR`,`USD`, etc. | boolean\|string | false | **2.77.0** |
+| currencyDisplay | Currency display method. Optional values: symbol, code, name | string | symbol | **2.77.0** |
 | defaultValue | Default                                                                                         | number                            |           |            |
 | disabled     | Disabled status                                                                                 | boolean                           | false     |            |
 | formatter    | Specifies the format of the input box to display the value                                      | (value: number\|string) => string | -         |            |
 | hideButtons  | Hide the "up/down" button when passing `true`                                                   | boolean                           | false     | **1.0.0**  |
 | innerButtons  | Show the "up/down" button in input box when passing `true`                                 | boolean                           | false         | **1.5.0** |
 | keepFocus    | Keep the input box focused when you click the button                                        | boolean                 |     false               | **1.10.0** |
+| localeCode | Used to specify the country code in currency mode. Optional values ​​include `zh-CN`, `en-US`, `en-GB`, `ja-JP`, `ko-KR`, `ar`, `vi-VN`, `ru-RU`, `id-ID`, `ms-MY`, `th-TH`, `tr-TR`, `pt-BR`, `zh-TW`, `es`, `de`, `it`, `fr`, `ro`, `sv-SE`, `pl-PL`, `nl-NL`, etc. | string | - | **2.77.0** |
 | max          | Limit maximum value                                                                             | number                            | Infinity  |            |
 | min          | Limit minimum value                                                                             | number                            | -Infinity |            |
 | parser       | Specifies how to convert back number string from formatter and use them in conjunction with formatter | (value: string) => string         | -         |      |
@@ -220,6 +413,7 @@ function Demo () {
 | preventScroll | Indicates whether the browser should scroll the document to display the newly focused element, acting on the focus method inside the component, excluding the component passed in by the user | boolean |  |  |
 | shiftStep    | Step size for pressing the shift key, it can be a decimal. The default value was adjusted from 1 to 10 in v2.13                     | number                            | 10         | **1.5.0** |
 | showClear    | Do you show the clear button?                                                                   | boolean                           | false     | **0.35.0** |
+| showCurrencySymbol | Whether to display the currency symbol/code/name, only valid in currency mode | boolean | true | **2.77.0** |
 | size         | Enter box size, optional value: "default"\|"small"\|"large"                                     | string                            | 'default' |            |
 | step         | Each time you change the number of steps, it can be a decimal.                                  | number                            | 1         |            |
 | style        | Inline style of InputNumber                                                             | CSSProperties  | -      |

+ 189 - 0
content/input/inputnumber/index.md

@@ -171,6 +171,191 @@ function Demo () {
 }
 ```
 
+### 货币展示
+2.77.0 版本开始支持货币展示,国际化模式下通过 currency={true} 开启,组件会自动根据 localeCode 展示对应货币种类。(注意切换语言类型后需要更新组件 key 值)
+```jsx live=true
+import React from 'react';
+import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
+import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB';
+import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
+import ko_KR from '@douyinfe/semi-ui/lib/es/locale/source/ko_KR';
+import ja_JP from '@douyinfe/semi-ui/lib/es/locale/source/ja_JP';
+import ar from '@douyinfe/semi-ui/lib/es/locale/source/ar';
+import vi_VN from '@douyinfe/semi-ui/lib/es/locale/source/vi_VN';
+import ru_RU from '@douyinfe/semi-ui/lib/es/locale/source/ru_RU';
+import id_ID from '@douyinfe/semi-ui/lib/es/locale/source/id_ID';
+import ms_MY from '@douyinfe/semi-ui/lib/es/locale/source/ms_MY';
+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 sv_SE from '@douyinfe/semi-ui/lib/es/locale/source/sv_SE';
+import pl_PL from '@douyinfe/semi-ui/lib/es/locale/source/pl_PL';
+import nl_NL from '@douyinfe/semi-ui/lib/es/locale/source/nl_NL';
+import es from '@douyinfe/semi-ui/lib/es/locale/source/es';
+import it from '@douyinfe/semi-ui/lib/es/locale/source/it';
+import de from '@douyinfe/semi-ui/lib/es/locale/source/de';
+import fr from '@douyinfe/semi-ui/lib/es/locale/source/fr';
+import ro from '@douyinfe/semi-ui/lib/es/locale/source/ro';
+import { LocaleProvider, InputNumber, Select } from '@douyinfe/semi-ui';
+
+class I18nDemo extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            locale: zh_CN,
+            localeCode: 'zh_CN',
+        };
+        this.onLanguageChange = this.onLanguageChange.bind(this);
+    }
+
+    onLanguageChange(code) {
+        let language = {
+            'zh_CN': zh_CN,
+            'en_GB': en_GB,
+            'en_US': en_US,
+            'ko_KR': ko_KR,
+            'ja_JP': ja_JP,
+            'ar': ar,
+            'vi_VN': vi_VN,
+            'ru_RU': ru_RU,
+            'id_ID': id_ID,
+            'ms_MY': ms_MY,
+            'th_TH': th_TH,
+            'tr_TR': tr_TR,
+            'pt_BR': pt_BR,
+            'zh_TW': zh_TW,
+            'es': es,
+            'sv_SE': sv_SE,
+            'pl_PL': pl_PL,
+            'nl_NL': nl_NL,
+            de,
+            it,
+            fr,
+            ro
+        };
+        this.setState({ locale: language[code], localeCode: code });
+    }
+
+    render() {
+        const { locale, localeCode } = this.state;
+        return (
+            <>
+                <div style={{ paddingBottom: 20 }}>
+                    <Select onChange={this.onLanguageChange} insetLabel='切换语言' style={{ width: 250 }} defaultValue='zh_CN'>
+                        <Select.Option value='zh_CN'>简体中文</Select.Option>
+                        <Select.Option value='en_US'>英语(美)</Select.Option>
+                        <Select.Option value='en_GB'>英语(英)</Select.Option>
+                        <Select.Option value='ja_JP'>日语</Select.Option>
+                        <Select.Option value='ko_KR'>韩语</Select.Option>
+                        <Select.Option value='ar'>阿拉伯语</Select.Option>
+                        <Select.Option value='vi_VN'>越南语</Select.Option>
+                        <Select.Option value='ru_RU'>俄罗斯语</Select.Option>
+                        <Select.Option value='id_ID'>印尼语</Select.Option>
+                        <Select.Option value='ms_MY'>马来语</Select.Option>
+                        <Select.Option value='th_TH'>泰语</Select.Option>
+                        <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.Option value='de'>德语</Select.Option>
+                        <Select.Option value='it'>意大利语</Select.Option>
+                        <Select.Option value='fr'>法语</Select.Option>
+                        <Select.Option value='ro'>罗马尼亚语</Select.Option>
+                        <Select.Option value='sv_SE'>瑞典语</Select.Option>
+                        <Select.Option value='pl_PL'>波兰语</Select.Option>
+                        <Select.Option value='nl_NL'>荷兰语</Select.Option>
+                    </Select>
+                </div>
+                <LocaleProvider locale={locale}>
+                    <InputNumber key={localeCode} currency={true} defaultValue={123456.78} />
+                </LocaleProvider>
+            </>
+        );
+    }
+}
+```
+也可以通过手动传 localeCode 和 currency 指定展示的货币种类
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 人民币</div>
+            <InputNumber localeCode="zh-CN" currency="CNY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇪🇺 欧元</div>
+            <InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇯🇵 日元</div>
+            <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇻🇳 越南盾</div>
+            <InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+支持 symbol、code、name 三种展示方式,通过 currencyDisplay 属性控制,默认以货币符号展示。showCurrencySymbol 设置为 false 隐藏货币符号/代码/名称的展示
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber currency="CNY" currencyDisplay="code" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber currency="CNY" currencyDisplay="symbol" defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber currency="CNY" currencyDisplay="name" defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
+隐藏货币符号、代码或名称的展示,通过前后缀展示货币符号
+```jsx live=true
+import React from 'react';
+import { InputNumber } from '@douyinfe/semi-ui';    
+
+() => {
+    const defaultValue = 123456.78;
+    return (
+        <div>
+            <div>🇨🇳 CNY ➕ code</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="CNY" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ symbol</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" prefix="¥" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+            <div>🇨🇳 CNY ➕ name</div>
+            <InputNumber style={{ width: 200 }} currency="CNY" suffix="人民币" showCurrencySymbol={false} defaultValue={defaultValue} />
+            <br />
+            <br />
+        </div>
+    );
+};
+```
+
 ## API 参考
 
 | 属性         | 说明                                                           | 类型                              | 默认值    | 版本      |
@@ -178,12 +363,15 @@ function Demo () {
 | autofocus    | 自动获取焦点                                                   | boolean                           | false     |           |
 | className | 类名                                                               | string  | -      |
 | clearIcon | 可用于自定义清除按钮, showClear为true时有效 | ReactNode |   | 2.25.0|
+| currency | 货币种类,国际化模式下通过 currency={true} 开启,组件会自动根据 locale 展示对应货币种类, 也可以手动传入 localeCode 和 currency 指定展示的货币种类, currency 的可选值有 `CNY`,`EUR`,`USD`等| boolean\|string | false | **2.77.0** |
+| currencyDisplay | 货币展示方式,可选值:symbol、code、name | string | symbol | **2.77.0** |
 | defaultValue | 默认值                                                         | number                            |           |           |
 | disabled     | 禁用                                                           | boolean                           | false     |           |
 | formatter    | 指定输入框展示值的格式                                         | (value: number\|string) => string | -         |           |
 | hideButtons  | 为 `true` 时隐藏 “上/下” 按钮                                  | boolean                           | false     | **1.0.0** |
 | innerButtons | 为 `true` 时 “上/下” 按钮显示在输入框内部                                  | boolean                           | false     | **1.5.0** |
 | keepFocus    | 点击按钮时保持输入框聚焦                                        | boolean                 |     false      |   **1.10.0**        |
+|  localeCode    | 货币模式下用于指定国家地区代码,可选值有 `zh-CN`, `en-US`, `en-GB`, `ja-JP`, `ko-KR`, `ar`, `vi-VN`, `ru-RU`, `id-ID`, `ms-MY`, `th-TH`, `tr-TR`, `pt-BR`, `zh-TW`, `es`, `de`, `it`, `fr`, `ro`, `sv-SE`, `pl-PL`, `nl-NL`等 | string                 |     -      |   **2.77.0**  |
 | max          | 限定最大值                                                     | number                            | Infinity  |           |
 | min          | 限定最小值                                                     | number                            | -Infinity |           |
 | parser       | 指定从 `formatter` 里转换回数字串的方式,和 `formatter` 搭配使用 | (str: string) => string           | -         |           |
@@ -194,6 +382,7 @@ function Demo () {
 | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法 | boolean |  |  |
 | shiftStep    | 按住 shift 键每次改变步数,可以为小数,v2.13 默认值由 1 调整为 10                           | number                            | 10         | **1.5.0** |
 | showClear    | 是否显示清除按钮                                               | boolean                           | false     | **0.35.0**   |
+| showCurrencySymbol | 是否显示货币符号/代码/名称,仅货币模式下生效 | boolean | true | **2.77.0** |
 | size         | 输入框大小,可选值:"default"\|"small"\|"large"                | string                            | 'default' |           |
 | step         | 每次改变步数,可以为小数                                       | number                            | 1         |           |
 | style     | 样式                                                               | CSSProperties  | -      |

+ 1 - 0
content/order.js

@@ -78,6 +78,7 @@ const order = [
     'tag',
     'timeline',
     'tooltip',
+    'userGuide',
     'chart',
     'banner',
     'notification',

+ 1 - 1
content/other/configprovider/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 88
+order: 89
 category: Other
 title: ConfigProvider
 icon: doc-configprovider

+ 1 - 1
content/other/configprovider/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 88
+order: 89
 category: 其他
 title:  ConfigProvider 全局配置
 icon: doc-configprovider

+ 1 - 1
content/other/locale/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 89
+order: 90
 category: Other
 title: LocaleProvider
 subTitle: LocaleProvider

+ 1 - 1
content/other/locale/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 89
+order: 90
 category: 其他
 title:  LocaleProvider 多语言
 icon: doc-i18n

+ 1 - 1
content/plus/audioPlayer/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 91
+order: 92
 category: Plus
 title: AudioPlayer
 icon: doc-audioplayer

+ 1 - 1
content/plus/audioPlayer/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 91
+order: 92
 category: Plus
 title: AudioPlayer 音频播放器
 icon: doc-audioplayer

+ 1 - 1
content/show/chart/index-en-US.md

@@ -1,6 +1,6 @@
 ---
 localeCode: en-US
-order: 80
+order: 81
 category: Show
 title: Data Visualization
 icon: doc-vchart

+ 1 - 1
content/show/chart/index.md

@@ -1,6 +1,6 @@
 ---
 localeCode: zh-CN
-order: 80
+order: 81
 category: 展示类
 title:  Data Visualization 数据可视化
 icon: doc-vchart

+ 553 - 0
content/show/userGuide/index-en-US.md

@@ -0,0 +1,553 @@
+---
+localeCode: en-US
+order: 80
+category: Show
+title: UserGuide
+icon: doc-userGuide
+brief: Used to guide new users through pages
+showNew: true
+---
+
+
+## Demos
+
+### How to import
+
+```jsx import
+import { UserGuide } from '@douyinfe/semi-ui';
+```
+
+### Basic Usage
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'basic-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'basic-demo-2'}> Default Tag </Tag>
+                <Button id={'basic-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#basic-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('Current guide step', current);
+                }}
+                onNext={(current) => {
+                    console.log('Next guide step');
+                }}
+                onPrev={(current) => {
+                    console.log('Previous guide step');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Theme
+`popup` bubble card mode provides two themes `default` and `primary`, set by the `theme` property.
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'theme-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'theme-demo-2'}> Default Tag </Tag>
+                <Button id={'theme-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                theme="primary"
+                steps={[
+                    {
+                        target: document.querySelector('#theme-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Popup position
+`popup` bubble card mode provides 12 positions, optional values are `top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight`, and can be set by the `showArrow` property to display the arrow.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'position-demo'} onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'top',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'New Position',
+                        description: 'This is Right Position',
+                        position: 'right',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'Hide Arrow',
+                        description: 'We hide the arrow',
+                        position: 'bottom',
+                        showArrow: false,
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Set the size of the highlight area
+Set by the `spotlightPadding` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'padding-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'padding-demo-2'}> Default Tag </Tag>
+                <Button id={'padding-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                spotlightPadding={10}
+                steps={[
+                    {
+                        target: document.querySelector('#padding-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-2'),
+                        title: 'New Padding',
+                        description: 'This is 10px padding',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-3'),
+                        title: 'Change Padding',
+                        spotlightPadding: 15,
+                        description: 'We change the Padding to 15px',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Customize the button
+Set by the `nextButtonProps` and `prevButtonProps` properties.
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'button-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'button-demo-2'}> Default Tag </Tag>
+                <Button id={'button-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                nextButtonProps={{
+                    children: 'Next',
+                }}
+                prevButtonProps={{
+                    children: 'Prev',
+                    theme: 'borderless',
+                }}
+                finishText="I know!"
+                steps={[
+                    {
+                        target: document.querySelector('#button-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-2'),
+                        title: 'New Button Style',
+                        description: 'Button text is Next',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-3'),
+                        title: 'New finish button text',
+                        description: 'Button text is I know',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### Controlled
+Set by the `current` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const [current, setCurrent] = useState(0);
+
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'controlled-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'controlled-demo-2'}> Default Tag </Tag>
+                <Button id={'controlled-demo-3'}>Confirm</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                current={current}
+                steps={[
+                    {
+                        target: document.querySelector('#controlled-demo-1'),
+                        title: "Beginner's Guide",
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    setCurrent(current);
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+
+### Modal guide
+Set by the `mode` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="modal"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        title: 'Welcome to Semi DSM!',
+                        description: <div>You can start from the published theme, or choose {<Typography.Text strong>Create Now</Typography.Text>} to create a new theme</div>,
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_welcome.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: 'High-available color palette',
+                        description: 'After selecting the main color, our color algorithm will generate a high-available color palette for you',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_console.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: 'Customize freely',
+                        description: 'Start customizing your design system!',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_palette.png" 
+                        />,
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('Current guide step', current);
+                }}
+                onNext={(current) => {
+                    console.log('Next guide step');
+                }}
+                onPrev={(current) => {
+                    console.log('Previous guide step');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### No mask
+Set by the `mask` property.
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'mask-demo'} onClick={showDialog}>Start Guide</Button>
+            <UserGuide
+                mode="popup"
+                mask={false}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#mask-demo'),
+                        title: 'No Mask',
+                        description: 'Hello ByteDancer!',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('Guide completed');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('Skip guide');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+## API Reference
+
+---
+| Properties | Instructions | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| className | Custom class name | string | - | |
+| current | Current step index | number | 0 | |
+| finishText | Text of the last step completion button | string | '完成' | |
+| mask | Whether to display the mask | boolean | true | |
+| mode | Guide mode, optional values: `popup` (bubble card) or `modal` (modal) | string | popup | |
+| nextButtonProps | The properties of the next button | ButtonProps | {} | |
+| onChange | Callback when the step changes | function(current: number) | () => void | |
+| onFinish | Callback when all steps are completed | function() | () => void | |
+| onNext | Callback when the next button is clicked | function(current: number) | () => void | |
+| onPrev | Callback when the previous button is clicked | function(current: number) | () => void | |
+| onSkip | Callback when the skip button is clicked | function() | () => void | |
+| position | The position of the pop-up layer relative to the target element, optional values: `top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight` | string | bottom | |
+| prevButtonProps | The properties of the previous button | ButtonProps | {} | |
+| showPrevButton | Whether to display the previous button | boolean | true | |
+| showSkipButton | Whether to display the skip button | boolean | true | |
+| spotlightPadding | The inner padding of the highlight area, in pixels | number | - | |
+| steps | Guide step configuration, required | StepItem[] | [] | |
+| style | Custom style | React.CSSProperties | - | |
+| theme | Theme style, optional values: `default` or `primary` | string | default | |
+| visible | Whether to display the guide | boolean | false | |
+| getPopupContainer | Specify the parent DOM, the pop-up layer will be rendered to the DOM | () => HTMLElement | - | |
+| zIndex | Pop-up layer level | number | 1030 | |
+
+### Steps.Step
+| Properties | Instructions | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| className | Custom class name | string | - | |
+| cover | The cover image of the step | ReactNode | - | |
+| target | The target element, the highlight area will focus on this element | (() => Element) \| Element | - | |
+| title | Step title | string \| ReactNode | - | |
+| description | Step description | ReactNode | - | |
+| mask | Whether to display the mask of this step, it will override the global configuration | boolean | - | |
+| showArrow | Whether to display the arrow (only valid when mode=`popup`) | boolean | true | | 
+| spotlightPadding | The inner padding of the highlight area of this step, it will override the global configuration | number | - | |
+| theme | The theme of this step, it will override the global configuration | `default` \| `primary` | - | |
+| position | The position of the pop-up layer of this step, it will override the global configuration | Position | - | |
+
+## Design Tokens
+
+<DesignToken/>
+

+ 553 - 0
content/show/userGuide/index.md

@@ -0,0 +1,553 @@
+---
+localeCode: zh-CN
+order: 80
+category: 展示类
+title:  UserGuide 用户引导
+icon: doc-userGuide
+brief: 用于页面对新用户进行功能引导
+showNew: true
+---
+
+
+## 代码演示
+
+### 如何引入
+
+```jsx import
+import { UserGuide } from '@douyinfe/semi-ui';
+```
+
+### 基本用法
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'basic-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'basic-demo-2'}> Default Tag </Tag>
+                <Button id={'basic-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#basic-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#basic-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('当前引导步骤', current);
+                }}
+                onNext={(current) => {
+                    console.log('下一步引导');
+                }}
+                onPrev={(current) => {
+                    console.log('上一步引导');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 主题
+`popup` 气泡卡片模式下提供两种主题 `default` 和 `primary`,通过 `theme` 属性设置。
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'theme-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'theme-demo-2'}> Default Tag </Tag>
+                <Button id={'theme-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                theme="primary"
+                steps={[
+                    {
+                        target: document.querySelector('#theme-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#theme-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 气泡卡片弹出位置
+`popup` 气泡卡片模式下提供 12 种弹出位置,可选值有`top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight`,还可以通过 `showArrow` 属性设置是否显示箭头。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'position-demo'} onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'top',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'New Position',
+                        description: 'This is Right Position',
+                        position: 'right',
+                    },
+                    {
+                        target: document.querySelector('#position-demo'),
+                        title: 'Hide Arrow',
+                        description: 'We hide the arrow',
+                        position: 'bottom',
+                        showArrow: false,
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 设置高亮区域大小
+通过 `spotlightPadding` 属性设置。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'padding-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'padding-demo-2'}> Default Tag </Tag>
+                <Button id={'padding-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                spotlightPadding={10}
+                steps={[
+                    {
+                        target: document.querySelector('#padding-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-2'),
+                        title: 'New Padding',
+                        description: 'This is 10px padding',
+                    },
+                    {
+                        target: document.querySelector('#padding-demo-3'),
+                        title: 'Change Padding',
+                        spotlightPadding: 15,
+                        description: 'We change the Padding to 15px',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 定制按钮
+通过 `nextButtonProps` 和 `prevButtonProps` 属性设置按钮的样式。
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'button-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'button-demo-2'}> Default Tag </Tag>
+                <Button id={'button-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                nextButtonProps={{
+                    children: 'Next',
+                }}
+                prevButtonProps={{
+                    children: 'Prev',
+                    theme: 'borderless',
+                }}
+                finishText="我知道啦!"
+                steps={[
+                    {
+                        target: document.querySelector('#button-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-2'),
+                        title: 'New Button Style',
+                        description: 'Button text is Next',
+                    },
+                    {
+                        target: document.querySelector('#button-demo-3'),
+                        title: 'New finish button text',
+                        description: 'Button text is I know',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 受控
+通过 `current` 属性设置当前引导步骤。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const [current, setCurrent] = useState(0);
+
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <br />
+            <br />
+            <Space>
+                <Switch id={'controlled-demo-1'} defaultChecked={true}></Switch>
+                <Tag id={'controlled-demo-2'}> Default Tag </Tag>
+                <Button id={'controlled-demo-3'}>确定</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                visible={visible}
+                current={current}
+                steps={[
+                    {
+                        target: document.querySelector('#controlled-demo-1'),
+                        title: '新手引导',
+                        description: 'Hello ByteDancer!',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-2'),
+                        title: 'Switch',
+                        description: 'This is a Semi Switch',
+                        position: 'bottom',
+                    },
+                    {
+                        target: document.querySelector('#controlled-demo-3'),
+                        title: 'Button',
+                        description: 'This is a Semi Button',
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    setCurrent(current);
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+
+### 弹窗式引导
+通过 `mode` 属性设置为 `modal` 开启弹窗式引导。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image, Typography } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="modal"
+                mask={true}
+                visible={visible}
+                steps={[
+                    {
+                        title: '欢迎使用 Semi DSM!',
+                        description: <div>你可以从已发布的主题出发,或者选择{<Typography.Text strong>立即创造</Typography.Text>}来创造一个新的主题</div>,
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_welcome.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: '高可用的色盘',
+                        description: '选取主色后,我们的颜色算法会为你生成一套高可用的色盘',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_console.png"
+                        />,
+                        position: 'bottom',
+                    },
+                    {
+                        title: '自由定制',
+                        description: '开始定制属于你的设计系统吧!',
+                        cover: <Image 
+                            width={'600px'} 
+                            height={'100%'} 
+                            src="https://lf9-static.bytednsdoc.com/obj/eden-cn/nuhpxphk/dsm/dsm_palette.png" 
+                        />,
+                        position: 'bottom',
+                    },
+                ]}
+                onChange={(current) => {
+                    console.log('当前引导步骤', current);
+                }}
+                onNext={(current) => {
+                    console.log('下一步引导');
+                }}
+                onPrev={(current) => {
+                    console.log('上一步引导');
+                }}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+### 无遮罩
+通过 `mask` 属性设置为 `false` 开启无遮罩引导。
+
+```jsx live=true
+import React from 'react';
+import { UserGuide, Button, Space, Tag, Switch, Image } from '@douyinfe/semi-ui';
+
+() => {
+    const [visible, setVisible] = useState(false);
+    const showDialog = () => {
+        setVisible(true);
+    };
+    return (
+        <div>
+            <Button id={'mask-demo'} onClick={showDialog}>开始引导</Button>
+            <UserGuide
+                mode="popup"
+                mask={false}
+                visible={visible}
+                steps={[
+                    {
+                        target: document.querySelector('#mask-demo'),
+                        title: 'No Mask',
+                        description: 'Hello ByteDancer!',
+                    },
+                ]}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成');
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('跳过引导');
+                }}
+            />  
+        </div>
+    );
+};
+```
+
+## API 参考
+
+---
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| className | 自定义类名 | string | - | |
+| current | 当前步骤的索引 | number | 0 | |
+| finishText | 最后一步完成按钮的文本 | string | '完成' | |
+| mask | 是否显示蒙层 | boolean | true | |
+| mode | 引导模式,可选值:`popup`(气泡卡片)或 `modal`(弹窗式) | string | popup | |
+| nextButtonProps | 下一步按钮的属性 | ButtonProps | {} | |
+| onChange | 步骤改变时的回调 | function(current: number) | () => void | |
+| onFinish | 完成所有步骤时的回调 | function() | () => void | |
+| onNext | 点击下一步按钮时的回调 | function(current: number) | () => void | |
+| onPrev | 点击上一步按钮时的回调 | function(current: number) | () => void | |
+| onSkip | 点击跳过按钮时的回调 | function() | () => void | |
+| position | 弹出层相对于目标元素的位置,可选值:`top`, `topLeft`, `topRight`, `left`, `leftTop`, `leftBottom`, `right`, `rightTop`, `rightBottom`, `bottom`, `bottomLeft`, `bottomRight` | string | bottom | |
+| prevButtonProps | 上一步按钮的属性 | ButtonProps | {} | |
+| showPrevButton | 是否显示上一步按钮 | boolean | true | |
+| showSkipButton | 是否显示跳过按钮 | boolean | true | |
+| spotlightPadding | 高亮区域的内边距,单位为像素 | number | - | |
+| steps | 引导步骤配置,必填 | StepItem[] | [] | |
+| style | 自定义样式 | React.CSSProperties | - | |
+| theme | 主题样式,可选值:`default` 或 `primary` | string | default | |
+| visible | 是否显示引导 | boolean | false | |
+| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中 | () => HTMLElement | - | |
+| zIndex | 弹层层级 | number | 1030 | |
+
+### Steps.Step
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| className | 步骤的自定义类名 | string | - | |
+| cover | 步骤的封面图 | ReactNode | - | |
+| target | 目标元素,高亮区域会聚焦到这个元素上 | (() => Element) \| Element | - | |
+| title | 步骤标题 | string \| ReactNode | - | |
+| description | 步骤描述 | ReactNode | - | |
+| mask | 是否显示此步骤的蒙层,会覆盖全局配置 | boolean | - | |
+| showArrow | 是否显示箭头(仅在 mode=`popup` 时有效) | boolean | true | |
+| spotlightPadding | 此步骤高亮区域区域的内边距,会覆盖全局配置 | number | - | |
+| theme | 此步骤的主题,会覆盖全局配置 | `default` \| `primary` | - | |
+| position | 此步骤弹出层的位置,会覆盖全局配置 | Position | - | |
+
+## 设计变量
+
+<DesignToken/>
+

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

@@ -16,6 +16,22 @@ Version:Major.Minor.Patch (follow the **Semver** specification)
 
 ---
 
+#### 🎉 2.77.0 (2025-03-25)
+- 【Fix】
+    - Fixed the issue of click-through when the UserGuide is in the bubble mode [#2764](https://github.com/DouyinFE/semi-design/pull/2764)
+    - Fixed the problem that the icon in the header of the Navigation is not centered in the collapsed mode.  [#2675](https://github.com/DouyinFE/semi-design/issues/2675)
+    - Fixed the problem that the search box of JsonViewer does not support i18n multilingual adaptation. [#2766](https://github.com/DouyinFE/semi-design/pull/2766)
+    - Fixed the problem of long text folding when the auto-wrap function of JsonViewer is not enabled, and fixed the issue of line number display. [#2756](https://github.com/DouyinFE/semi-design/pull/2756)
+
+#### 🎉 2.77.0-beta.0 (2025-03-18)
+- 【New Component】
+    - support new component UserGuide
+- 【Feat】
+    - InputNumber supports currency mode
+- 【Fix】
+    - fix MarkdownRender table duplicate column elements when header containing more than two components or bold texts [@ByteLan](https://github.com/ByteLan)
+    - Correct locale codes for Netherlands, Poland and Sweden (nl_NL -> nl-NL, pl_PL -> pl-PL, sv_SE -> sv-SE)
+
 #### 🎉 2.76.1 (2025-03-17)
 - 【Style】
     - Style: For input type components, the styles of insetLabel and prefix remain consistent. Removed unnecessary tokens related to insetLabel, Added $font-cascader_prefix_suffix_fontWeight, $spacing-input_prefix_suffix-marginX, $font-input_prefix_suffix-fontWeight, $font-select_prefix_suffix-fontWeight, $spacing-tagInput_prefix_suffix-marginX, $font-tagInput_prefix_suffix-fontWeight, $font-treeSelect_prefix_suffix_fontWeight to manage prefix and suffix weights and margins.[#2752](https://github.com/DouyinFE/semi-design/issues/2752)

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 176 - 159
content/start/changelog/index.md


+ 32 - 96
content/start/design-to-code/index-en-US.md

@@ -9,88 +9,28 @@ order: 4
 
 ## Introduction
 
-Design to code (D2C) is a design draft conversion code tool provided by `Semi Design`, which supports one-click recognition of layer layout + Semi components in Figma pages, restores design drafts at the pixel level, translates them into JSX and CSS codes, and quickly previews them.
-No need to start from 0.
-
-From now, you can let the tool take care of UI restoration, focus more on your business logic.
-
-## Abilities
-
-<div>
-     <Row gutter={[20, 80]}>
-         <Col span={8}>
-             <FeatureCard title='Support basic UI layout transcode'>
-                 Support flex layout, absolute layout
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Support Semi components'>
-                Support the identification of Semi components and Semi Icons, covering 28+ components in form and table scenarios
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Support exporting different stack codes' >
-                 Multiple code style output:React + Scss, React + Tailwind and JSON Schema
-             </FeatureCard>
-         </Col>
-     </Row>
-</div>
-<div>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Support to identify other theme components'>
-                 Support for identifying components behind custom themes
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Nested components and custom content recognition'>
-                 Recognize what the designer has modified to identify it as a ReactNode
-             </FeatureCard>
-         </Col>
-     </Row>
-</div>
-
-## How to use
-
-![](https://lf3-files.qingfuwucdn.net/obj/inspirecloud-file/baas/tt38q7/1aaf72252f553443_1676606724044.png)
-
-Please move directly to <a href="https://semi.design/code" target="_blank">semi.design/code</a>
-## Application scenario
-
-<div>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Basic page transcode' >
-                Restore the layout of the basic page, suitable for landing page rapid development
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Card transcode' >
-                One-click to restore the card, no need to care about the card layout
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Form page transcode' >
-                Use Semi Variants to build the operation table page, D2C helps you automatically identify the table column content
-             </FeatureCard>
-         </Col>
-     </Row>
-     <Row gutter={[20, 20]}>
-         <Col span={8}>
-             <FeatureCard title='Form page transcode' >
-                Use Semi Variants to build an operation form page, and restore the Semi Form form with one click
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard title='Access to custom build platform'>
-                Customize consumption JSON Schema and create template code suitable for your project
-             </FeatureCard>
-         </Col>
-         <Col span={8}>
-             <FeatureCard />
-         </Col>
-     </Row>
-</div>
+D2C is the abbreviation of Design to Code, which means converting design drafts into code. As an auxiliary tool for frontend engineers, it can effectively improve the efficiency of design draft restoration and reduce the cost of manual Html/CSS coding.
+
+Thanks to the continuous enhancement of the native capabilities of the Figma platform ecosystem (such as Variant, DevMode, CodeConnect, etc.), the usability of D2C tools in the production environment has been greatly improved in recent years. They can support the access of complete design systems and achieve component - level identification and code output.  
+At the same time, with the help of AI models, it is possible to effectively rewrite the template style code translated from Figma to supplement business logic.  
+Semi D2C provides out-of-the-box design to code abilities: it supports one-click identification of layer layouts and design system components on Figma pages, achieves pixel perfect reproduction of design drafts, and translation into React JSX and CSS code. In addition, it also provides rich expansion capabilities. Based on a flexible plugin system, it can quickly create team specific design and R&D collaboration tools without starting from scratch.
+
+With the help of D2C, you can delegate the UI restoration work to the tool and focus more on implementing business logic.
+
+<DesignToCodeFeature /> 
+
+## Invocation Methods
+
+We provide multiple forms of invocation methods:  
+
+**Figma Plugin**: Quickly launched through Figma DevMode. You can directly obtain the corresponding code by clicking on the layer, and it supports the output of different code formats. It also supports freely consuming the Abstract Syntax Tree (AST) through a custom plugin and customizing the code generation results.  
+
+**OpenApi**: An open Http service that provides the ability to parse the corresponding code based on the Figma URL. It can be used to integrate D2C into business processes, such as LowCode building platforms and the construction of MCP services, etc. (Available internally in ByteDance. This capability is not yet provided in the community version.)  
+
+**NodeSdk**: It has strong customization and can be used to encapsulate the team's private D2C capabilities, such as CLI/HTTP services/VS Code plugins (Available internally in ByteDance. This capability is not yet provided in the community version.)  
+
+For more detailed usage instructions, you can visit <a href="/code" target="_blank"> the D2C Official Website</a> for reference.
+
 
 ## Examples
 
@@ -104,22 +44,18 @@ Here is a link to the Figma example mockup and its corresponding Codesandbox tra
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo%EF%BC%88Figma-Community%EF%BC%89?node-id=419%3A128959&t=PMnGQ3VQIoGQZZPl-4' target="_blank"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/semi-code-site/Simple Table.png' style={{ width:  400 }} /></a> | Module with Semi Table Components                                                                 | Can be used to quickly identify table columns, create Table                                         | <a href='https://codesandbox.io/s/happy-browser-dt34sr' target="_blank">Link</a>             |
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo?node-id=1%3A276' target="_blank" rel="noreferrer noopener"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/d2c-landing-example.png' style={{ width:  400 }} /></a>                                          | Full landing page                                                                                 | Can be used to quickly restore layout and content                                                   | <a href='https://codesandbox.io/s/cvhhqt' target="_blank" rel="noreferrer noopener">Link</a> |
 
-## Comparisons
-
-### More in line with the designer's usage habits
-
-We investigated the common C2D tools in the industry in 2021~2022. They all draw the component as an instance on the canvas. Whether it is creating an instance or switching variants, it needs to be operated within the plug-in.
-
-However, in the actual design process, designers are not used to opening a plugin to create and update components. The solution provided by Semi is that designers continue to use variants in the native way of Figma, which is consistent with the original design process.
-
-### Powerful Design Components
 
-There are some tools in the industry that support converting arbitrary component libraries into design components, but these components currently do not have auto layout capabilities and cannot meet the standards available to designers. In addition, their components do not use design variables, which makes it difficult for designers to customize this set of components.
 
-### Component identification does not depend on annotations
+## Differences from Other Technical Approaches
+Generally speaking, in addition to relying on Figma To Code/Sketch To Code on specific design platforms for design to code conversion, there is also Image to Code. Code generation based on AI models can also be considered a variant of Image to Code.
 
-Semi focuses on supporting Semi components. The Semi D2C solution does not require R&D to manually label components. If you have custom requirements, you can also use labeling to identify layers as components. There are some tools in the industry that provide powerful component labeling capabilities, but since there is no component library provided by default, it is necessary to manually label components and component properties in actual use.
+- Image To Code:
+    - The traditional Image To Code approach relies on methods such as YOLO to cut image elements and then identify them. This type of technical approach has poor recognition of similar elements (for example, when Select and TreeSelect have similar styles, the recognition may be confused). At the same time, it is difficult to further optimize the restoration degree of the design draft after it reaches a certain threshold.
+    - After the emergence of large-scale language models (LLMs) in the field of AI, relying on vllm such as GPT-4V can also achieve the toCode restoration of simple design drafts. However, this path is still difficult to support a specific design system (or a derivative of a specific design system + theme customization) and restore it based on specific design specifications. At the same time, there are many minor differences in aspects such as spacing, alignment, and element recognition. For designers, the acceptance cost will increase significantly. For scenarios with high requirements for Pixel Perfect visual restoration effects, its practicality remains poor. The improvement costs for these cases are still high at present.
 
-### Transcoding supports flex layout
+- Figma To Code:
+    - In 2023/2024, mainstream D2C products in the industry are basically based on this technical approach. Since it belongs to the mutual conversion of structured information, it has a great advantage in recognition accuracy in cases where Image to Code cannot solve problems, such as design systems and similar elements. Semi's D2C is also based on this.
+    - Different from other D2C solution providers, we are also the maintainers of the design system ourselves. We pay more attention to the connection with specific design systems. The D2C we provide natively supports component - level transcoding recognition of the Design System and also supports third - party design systems. Based on the Code To Design capability, it has been integrated with Figma's Variant. We also update various functions on the Figma platform more promptly.
+    - We are combining the above two approaches, integrating their advantages to achieve generation closer to practical applications. First, generate basic code based on Figma To Code, and then combine image information. With the help of AI models, rewrite the basic code more reasonably. In the internal version of the D2C plugin of ByteDance, we provide a Quality mode. Based on Doubao/Deepseek, the following out - of - the - box capabilities are integrated: more intelligent sub - component splitting, implementation of list loop structures based on map render, semantic classname, and addition of jsx -> tsx type declarations.
 
-We have researched some common D2C tools on the market, and some of them have relatively good flex layout support, but there are certain problems in some details. Semi supports reverting Figma's auto layout to flex layout. In addition, if certain rules are met between layers, it will also be automatically recognized as a flex layout.
+**D2C + AI is the technical approach that can best balance the design restoration degree and the high availability of generated code at the current stage (D2C + AI = 🚀 Pixel Perfect + 🔩 highly maintainable code)** 

+ 34 - 101
content/start/design-to-code/index.md

@@ -8,91 +8,29 @@ order: 4
 
 ## 简介
 
-Design to code(简称D2C) 是 Semi Design 提供的设计稿转代码功能,支持一键识别 Figma 页面中图层布局 + Semi 组件,像素级还原设计稿,转译为 JSX 和 CSS 代码,快捷预览,
-无需从 0 开发。
-
-从此,你可以将 UI 还原的工作交给工具,更专注于实现业务逻辑。
-
-## 基础能力
-
-<div>
-    <Row gutter={[20, 60]}>
-        <Col span={8}>
-            <FeatureCard title='基础 UI 布局还原' >
-                支持 flex 布局、absolute 布局
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='精准的Semi 组件识别' >
-                已覆盖表单和表格场景 28+ 组件
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='支持输出不同技术栈代码' >
-                多种代码风格输出: React + Scss、React + Tailwind 和 JSON Schema
-            </FeatureCard>
-        </Col>
-    </Row>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='支持识别其他主题' >
-                支持识别自定义主题后的组件
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='嵌套组件和自定义内容识别' >
-                识别设计师修改后的内容,将其识别为 ReactNode
-            </FeatureCard>
-        </Col>
-    </Row>
-</div>
-
-## 如何使用
-
-
-![](https://lf3-files.qingfuwucdn.net/obj/inspirecloud-file/baas/tt38q7/82069cd816533f91_1676604095341.png)
-
-更详细的使用说明,可访问 <a href="/code" target="_blank">https://semi.design/code</a> 查阅
-
-
-## 使用场景
-
-<div>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='基础页面转码' >
-                将基础页面的布局进行还原,适合落地页快速开发
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='卡片转码' >
-                一键将复杂样式的卡片进行还原,无需关心卡片布局
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='表格页转码' >
-                利用 Semi Variants 搭建运营表格页面,D2C 帮助你自动识别表格列内容
-            </FeatureCard>
-        </Col>
-    </Row>
-    <Row gutter={[20, 20]}>
-        <Col span={8}>
-            <FeatureCard title='表单页转码' >
-                利用 Semi Variants 搭建运营表单页,一键将 Semi Form 表单进行还原
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard title='接入自定义搭建平台' >
-                自定义消费 JSON Schema,创建适合你项目的模板代码
-            </FeatureCard>
-        </Col>
-        <Col span={8}>
-            <FeatureCard />
-        </Col>
-    </Row>
-</div>
-
-## 实际示例
+D2C 为 Design to Code 的缩写,即设计稿转代码。作为前端工程师的辅助工具,能有效加速设计稿还原的效率,降低人工编写 Html / CSS 的成本。
+
+得益于 Figma 平台生态原生能力(Variant 变体、DevMode、CodeConnect 等)的不断增强,D2C 类工具近些年在生产环境下的可用性已经有了非常大的提升,可以支撑完整的设计系统接入,做到组件级的识别与代码输出。
+同时借助 AI 大模型,可以有效对 Figma 转译出的模板样式代码做进一步改写,实现业务逻辑补充。
+
+Semi D2C 提供开箱即用的设计稿转代码:支持一键识别 Figma 页面中图层布局 + 设计系统组件,像素级还原设计稿,转译为 React JSX 和 CSS 代码。此外还提供了丰富的扩展能力,基于自定义插件系统快速打造团队专属的设计研发协作工具,无需从 0 开发,
+
+借助 D2C,你可以将 UI 还原的工作交给工具,更专注于实现业务逻辑。
+
+<DesignToCodeFeature /> 
+
+## 调用方式
+
+我们提供了多种形态的调用方式:
+
+- Figma 插件:通过 Figma DevMode 快速启动,点击图层直接获取对应代码,支持不同代码格式 Output。支持通过自定义插件自由消费 AST,自定义出码结果
+- OpenApi:开放式 Http 服务,提供基于 Figma URL 解析出对应代码的能力,可用于将 D2C 集成到业务流程,如 LowCode 搭建平台,构建 MCP 服务等(字节内部可用,社区版本暂未提供该能力)
+- NodeSdk:定制性强,可用于封装团队私有的 D2C 能力,如 CLI/HTTP 服务/VS Code 插件(字节内部可用,社区版本暂未提供该能力)
+
+更详细的使用说明,可访问 <a href="/code" target="_blank">D2C 官网</a> 查阅
+
+
+## 示例
 
 我们准备了一些 Figma 示例设计稿,以及使用 Semi Figma 插件实际转译的代码 Codesandbox 链接。   
 
@@ -107,26 +45,21 @@ Design to code(简称D2C) 是 Semi Design 提供的设计稿转代码功能
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo%EF%BC%88Figma-Community%EF%BC%89?node-id=419%3A128959&t=PMnGQ3VQIoGQZZPl-4' target="_blank"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/semi-code-site/Simple Table.png' style={{ width:  400 }} /></a> | 含 Semi Table 组件                  | 可用于快速识别表格列、创建 Table                     | <a href='https://codesandbox.io/s/happy-browser-dt34sr' target="_blank">Link</a>             |
 | <a href='https://www.figma.com/file/TlLeWouyImYUexTmhdLiIn/D2C-Getting-Start-Demo?node-id=1%3A276' target="_blank" rel="noreferrer noopener"><img src='https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/semi-linker/d2c-landing-example.png' style={{ width:  400 }} /></a>                                          | 整页 Landing Page                   | 可用于快速还原布局及内容                            | <a href='https://codesandbox.io/s/cvhhqt' target="_blank" rel="noreferrer noopener">Link</a> |
 
-## 与其他方案的差异
-
-### 更加符合设计师使用习惯
-
-我们在 2021 ~ 2022 年调研了业界常见的 C2D 工具,它们将组件作为一个实例绘制在画布上,无论是创建实例还是切换变体都需要在插件内操作。
-
-然而,在实际的设计流程中,设计师可能并不习惯打开一个插件创建和更新组件。Semi 提供的方案允许设计师继续通过 Figma 原生的方式使用变体,与原有的设计流程一致。
-
-### 可用的设计组件
-
-业界有一些工具支持任意的组件库转为设计组件,但是这些组件目前不具有 auto layout 能力,无法达到设计师可用的标准。另外它们的组件也没有使用设计变量,这导致设计师很难通过 design token 自定义这些组件库。
 
-### 组件识别不依赖标注
+## 与其他技术路线的差异
+通常而言,设计转代码除了依赖于特定设计平台的 Figma To Code / Sketch To Code 外,还有 Image to Code,基于多模态 AI 大模型的 Code 生成 也可以认为是 Image to Code 的一种变体。
 
-Semi D2C 方案提供了精准到组件级 props 的识别能力,并且不需要研发手动对组件进行标注。同时对于自定义组件,我们也额外提供了标注方式将图层识别为组件。业界有一些工具提供了强大的组件标注能力,但由于没有默认提供一套组件库,所以在实际的使用中需要人工将组件以及组件属性进行标注,整体的使用成本较高。
+- Image to Code:
+    - 传统的 ImageToCode 路线依赖 yolo 等手段对图片元素进行切割,然后识别。这类技术路线对相似元素的识别不佳(例如 Select 与 TreeSelect 在样式相似的情况下,识别可能会混淆),同时设计稿还原度在达到一定阈值后很难再优化
+    - 大模型时代,依赖 LLM 多模态能力如 GPT-4V 等手段亦能做到对简单设计稿的 toCode 还原,但该路径目前依然很难承载一个特定的设计系统(或者一个特定设计系统 + 主题定制的衍生体),并基于特定的设计规范去还原。同时对于间距、对齐、元素识别等存在非常多边边角角的 diff,对设计师来说,验收成本会剧增,对于 Pixel Perfect 视觉还原效果要求高的场景而言,实用性依然不佳。针对这些 case 的改善成本,目前依然居高不下
 
-### 更好的 Flex 布局支持
+- Figma to Code:  
+    - 23/24年业界主流的 D2C 产品基本都是基于该技术路线,它由于属于结构化信息的互相转换,所以对设计系统,相似元素等 Image to Code 无法解决的 case 里,**识别精准度上会有非常大的优势**。Semi 的 D2C 亦基于此。
+    - 与其他 D2C 方案提供者不同,我们本身亦是设计系统的维护者,会更注重与特定设计系统的联通,**提供的 D2C 原生支持 Design System 组件级转码识别,也支持第三方设计系统**,基于 Code To Design 能力 跟 Figma 的 Variant 做了打通。对 Figma 平台上各类功能的更新也更及时
+    - 我们正在将上述两个路线做结合,结合两者优点去做更靠近实际应用的生成,先基于 Figma To Code 生成基础代码,再结合 Image 图像信息,借助多模态 AI 大模型将基础代码做更合理的改写,在字节跳动内部版本的 D2C 插件中,我们提供了 Quality 模式,基于 Doubao / Deepseek 集成以下开箱即用的能力:更智能的子组件拆分、列表循环结构基于 map render 实现、classname 语义化、jsx -> tsx 类型声明补充。  
 
-市场上常见的 D2C 工具,有一些具有比较好的 Flex 布局能力支持,但在一些细节上有一定问题。Semi 支持将 Figma 的 auto layout 布局还原为 Flex 布局。另外,如果图层间符合一定规则,也将自动识别为 Flex 布局。
+**D2C + AI 是当下阶段能最好兼顾设计还原度 + 生成代码高可用性的技术路线(D2C + AI  =  🚀 Pixel Perfect + 🔩 高可维护代码)**
 
 
 ## 更多说明
-更多关于Figma 插件的安装,使用,标注自定义组件,Figma 设计变体的使用,D2C的使用受限等细节信息,请查阅 https://semi.design/code
+更多关于 Figma 插件的安装,使用,标注自定义组件,Figma 设计变体的使用,D2C 的使用受限等细节信息,请查阅 https://semi.design/code

+ 1 - 0
content/start/overview/index.md

@@ -94,6 +94,7 @@ Table 表格,
 Tag 标签,
 Timeline 时间轴,
 Tooltip 工具提示,
+UserGuide 用户引导,
 VChart 图表
 ```
 

+ 5 - 5
content/start/tailwind/index.md

@@ -39,7 +39,7 @@ Semi 不依赖任何第三方样式库,没有安装 Tailwind 一样可以运
 如果是 1,则会出现 Tailwind 在添加某些原子类时,如果组件样式已经定义了某个 css 属性,原子类的优先级比 Semi 优先级低,此时原子类失效。
 例如在 1 的前提下,对 Button 组件设置 padding,会出现失效的情况。
 
-如果是2,因为 Tailwind 优先级较高,其对浏览器默认样式覆盖的 Preflight 会同时覆盖掉 Semi 的样式。
+如果是 2,因为 Tailwind 优先级较高,其对浏览器默认样式覆盖的 Preflight 会同时覆盖掉 Semi 的样式。
 例如在 2 的前提下,light 的 Button 的背景色会被覆盖为 transparent,导致样式表现异常。
 
 
@@ -52,7 +52,7 @@ yarn add -D @douyinfe/semi-webpack-plugin
 ```
 ** 2. 在项目中的配置文件中 **
 
-- webpack 用户: 在webpack.config.js 引入Semi webpack 插件并开启 cssLayer
+- webpack 用户:在 webpack.config.js 引入 Semi webpack 插件并开启 cssLayer
 
 ```js
 const SemiPlugin = require('@douyinfe/semi-webpack-plugin').default;
@@ -69,7 +69,7 @@ module.exports = {
 };
 
 ```
-- rspack 用户: 在 rspack.config.js  引入Semi webpack 插件并开启 cssLayer
+- rspack 用户:在 rspack.config.js  引入 Semi webpack 插件并开启 cssLayer
 
 ```js
 const {SemiRspackPlugin} = require('@douyinfe/semi-rspack-plugin');
@@ -127,7 +127,7 @@ CSS Layer 要求浏览器版本高于 Chromium 99 <a target="_blank" href="https
 ```css
 @layer tailwind-base,semi,tailwind-components,tailwind-utils;
 ```
-上述 CSS 的含义为, base (含 Preflight)优先级最低,Semi 次之,用户设置的原子类样式(padding-[xxx] 等)优先级最高,这样即可解决上面遇到的问题。
+上述 CSS 的含义为,base(含 Preflight)优先级最低,Semi 次之,用户设置的原子类样式(padding-[xxx] 等)优先级最高,这样即可解决上面遇到的问题。
 
 
 ### 2.解决在 Tailwind 原子类中使用 Semi Token 的问题 (可选)
@@ -137,7 +137,7 @@ Tailwind 支持用户配置自己的 Token 来实现主题。同时 Semi 也提
 
 Semi 提供了 Tailwind 的主题配置文件,用于将 Semi 的 Token 映射为原子类 Token,上述需求可以直接给 span 设置 `text-semi-color-text-0` 即可。
 
-在 Tailwind 配置中(即 `tainwind.config.js`)配置以下内容即可:
+在 Tailwind 配置中 (即 `tainwind.config.js`) 配置以下内容即可:
 
 ```js
 module.export = {

+ 1 - 1
lerna.json

@@ -1,5 +1,5 @@
 {
     "useWorkspaces": true,
     "npmClient": "yarn",
-    "version": "2.76.1"
+    "version": "2.77.0"
 }

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-react",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "motion library for semi-ui-react",
     "keywords": [
         "motion",
@@ -25,8 +25,8 @@
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.76.1",
-        "@douyinfe/semi-animation-styled": "2.76.1",
+        "@douyinfe/semi-animation": "2.77.0",
+        "@douyinfe/semi-animation-styled": "2.77.0",
         "classnames": "^2.2.6"
     },
     "devDependencies": {

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation-styled",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "semi styled animation",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-animation",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "animation base library for semi-ui",
     "keywords": [
         "animation",

+ 1 - 1
packages/semi-eslint-plugin/package.json

@@ -1,6 +1,6 @@
 {
     "name": "eslint-plugin-semi-design",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "semi ui eslint plugin",
     "keywords": [
         "semi",

+ 176 - 10
packages/semi-foundation/inputNumber/foundation.ts

@@ -23,7 +23,8 @@ export interface InputNumberAdapter extends DefaultAdapter {
     restoreCursor: (str?: string) => boolean;
     fixCaret: (start: number, end: number) => void;
     setClickUpOrDown: (clicked: boolean) => void;
-    updateStates: (states: BaseInputNumberState, callback?: () => void) => void
+    updateStates: (states: BaseInputNumberState, callback?: () => void) => void;
+    getInputCharacter: (index: number) => string
 }
 
 export interface BaseInputNumberState {
@@ -38,8 +39,13 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
     _interval: any;
     _timerHasRegistered: boolean;
     _timer: any;
+    _decimalPointSymbol: string = undefined;
+    _currencySymbol: string = '';
 
     init() {
+        if (this._isCurrency()) {
+            this._setCurrencySymbol();
+        }
         this._setInitValue();
     }
 
@@ -53,6 +59,16 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return this._isControlledComponent('value');
     }
 
+    _isCurrency() {
+        const { currency } = this.getProps();
+        return currency === true || (typeof currency === 'string' && currency.trim() !== '');
+    }
+
+    _getFinalCurrency() {
+        const { currency } = this.getProps();
+        return currency === true ? this.getProp('defaultCurrency') : currency;
+    }
+
     _doInput(v = '', event: any = null, updateCb: any = null) {
         let notifyVal = v;
         let number = v;
@@ -122,6 +138,28 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         }
     }
 
+    _setCurrencySymbol() {
+        const { localeCode, currencyDisplay } = this.getProps();
+        const parts = new Intl.NumberFormat(localeCode, {
+            style: 'currency',
+            currency: this._getFinalCurrency() || this.getCurrencyByLocaleCode(),
+            currencyDisplay
+        }).formatToParts(1234.5);
+
+        for (const part of parts) {
+            if (part.type === 'decimal') {
+                this._decimalPointSymbol = part.value;
+                console.log('this._decimalPointSymbol: ', this._decimalPointSymbol);
+            }
+            // if (part.type === 'group') {
+            //     groupSeparator = part.value;
+            // }
+            if (part.type === 'currency') {
+                this._currencySymbol = part.value;
+            }
+        }
+    }
+
     handleInputFocus(e: any) {
         const value = this.getState('value');
 
@@ -198,7 +236,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
             this._adapter.setNumber(num);
         }
 
-        this._adapter.setValue(this.isControlled() ? formattedNum : this.doFormat(valueAfterParser as unknown as number, false), () => {
+        this._adapter.setValue(this.isControlled() && !this._isCurrency() ? formattedNum : this.doFormat(valueAfterParser as unknown as number, false), () => {
             this._adapter.restoreCursor();
         });
 
@@ -242,7 +280,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
                 numHasChanged = true;
             }
 
-            const currentFormattedNum = this.doFormat(currentNumber, true);
+            const currentFormattedNum = this.doFormat(currentNumber, true, true);
 
             if (currentFormattedNum !== currentValue) {
                 willSetVal = currentFormattedNum;
@@ -366,14 +404,15 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         const { defaultValue, value } = this.getProps();
 
         const propsValue = this._isControlledComponent('value') ? value : defaultValue;
-        const tmpNumber = this.doParse(toString(propsValue), false, true, true);
+
+        const tmpNumber = this.doParse(this._isCurrency() ? propsValue : toString(propsValue), false, true, true);
 
         let number = null;
         if (typeof tmpNumber === 'number' && !isNaN(tmpNumber)) {
             number = tmpNumber;
         }
 
-        const formattedValue = typeof number === 'number' ? this.doFormat(number, true) : '';
+        const formattedValue = typeof number === 'number' ? this.doFormat(number, true, true) : '';
 
         this._adapter.setNumber(number);
         this._adapter.setValue(formattedValue);
@@ -417,7 +456,7 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
 
         // console.log('scale: ', scale, 'curNum: ', curNum);
 
-        return this.doFormat(curNum, true);
+        return this.doFormat(curNum, true, true);
     }
 
     minus(step?: number, event?: any): string {
@@ -448,19 +487,43 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return toString(num);
     }
 
+    formatCurrency(value: number | string) {
+        const { localeCode, minimumFractionDigits, precision, maximumFractionDigits, currencyDisplay, showCurrencySymbol } = this.getProps();
+
+        let formattedValue = value;
+        if (typeof value === 'string' && Number.isNaN(Number(value))) {
+            formattedValue = this.parseInternationalCurrency(value);
+        }
+
+        const formatter = new Intl.NumberFormat(localeCode, {
+            style: 'currency',
+            currency: this._getFinalCurrency() || this.getCurrencyByLocaleCode(),
+            currencyDisplay: currencyDisplay,
+            minimumFractionDigits: minimumFractionDigits || precision || undefined,
+            maximumFractionDigits: maximumFractionDigits || precision || undefined,
+        });
+
+        const formatted = formatter.format(Number(formattedValue)); 
+        return showCurrencySymbol ? formatted : formatted.replace(this._currencySymbol, '').trim();
+    }
+
     /**
      * format number to string
      * @param {string|number} value
      * @param {boolean} needAdjustPrec
      * @returns {string}
      */
-    doFormat(value: string | number = 0, needAdjustPrec = true): string {
+    doFormat(value: string | number = 0, needAdjustPrec = true, needAdjustCurrency = false): string {
         // if (typeof value === 'string') {
         //     return value;
         // }
+        const { formatter } = this.getProps();
         let str;
-        const formatter = this.getProp('formatter');
-        if (needAdjustPrec) {
+
+        // AdjustCurrency conversion is done only in blur situation, otherwise it is just converted to normal string
+        if (this._isCurrency() && needAdjustCurrency) {
+            str = this.formatCurrency(value);
+        } else if (needAdjustPrec) {
             str = this._adjustPrec(value);
         } else {
             str = toString(value);
@@ -487,6 +550,22 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
         return current;
     }
 
+    // 将货币模式的货币转化为纯数字
+    // Convert currency in currency mode to pure numbers
+    // eg:¥123456.78 to 123456.78
+    // eg:123456.78 to 123456.78
+    parseInternationalCurrency(currencyString: string) {
+        let cleaned = currencyString
+            .replace(this._currencySymbol, '')
+            .replace(new RegExp(`[^\\d${this._decimalPointSymbol}\\-]`, 'g'), '');
+
+        // Convert the localized decimal point to the standard decimal point
+        if (this._decimalPointSymbol && this._decimalPointSymbol !== '.') {
+            cleaned = cleaned.replace(this._decimalPointSymbol, '.');
+        }
+        return parseFloat(cleaned);
+    }
+      
     /**
      * parse to number
      * @param {string|number} value
@@ -496,6 +575,11 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
      * @returns {number}
      */
     doParse(value: string | number, needCheckPrec = true, needAdjustPrec = false, needAdjustMaxMin = false) {
+
+        if (this._isCurrency() && typeof value === 'string') {
+            value = this.parseInternationalCurrency(value);
+        }
+
         if (typeof value === 'number') {
             if (needAdjustMaxMin) {
                 value = this.fetchMinOrMax(value);
@@ -557,7 +641,11 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
             return value;
         }
         if (typeof value === 'string') {
-            const parser = this.getProp('parser');
+            const { parser } = this.getProps();
+
+            if (this._isCurrency()) {
+                value = this.parseInternationalCurrency(value);
+            }
 
             if (typeof parser === 'function') {
                 value = parser(value);
@@ -630,6 +718,84 @@ class InputNumberFoundation extends BaseFoundation<InputNumberAdapter> {
     updateStates(states: BaseInputNumberState, callback?: () => void) {
         this._adapter.updateStates(states, callback);
     }
+
+    /**
+     * Get currency by locale code
+     * @param {string} localeCode
+     * @returns {string}
+     */
+    getCurrencyByLocaleCode() {
+        const { localeCode } = this.getProps();
+
+        // Mapping table of region codes to currency codes
+        const localeToCurrency: Record<string, string> = {
+            // Asia
+            'zh-CN': 'CNY', // China
+            'zh-HK': 'HKD', // Hong Kong
+            'zh-TW': 'TWD', // Taiwan
+            'ja-JP': 'JPY', // Japan
+            'ko-KR': 'KRW', // Korea
+            'th-TH': 'THB', // Thailand
+            'vi-VN': 'VND', // Vietnam
+            'ms-MY': 'MYR', // Malaysia
+            'id-ID': 'IDR', // Indonesia
+            'hi-IN': 'INR', // India
+            'ar-SA': 'SAR', // Saudi Arabia
+        
+            // Europe
+            'en-GB': 'GBP', // United Kingdom
+            'de-DE': 'EUR', // Germany
+            'fr-FR': 'EUR', // France
+            'it-IT': 'EUR', // Italy
+            'es-ES': 'EUR', // Spain
+            'pt-PT': 'EUR', // Portugal
+            'ru-RU': 'RUB', // 俄罗斯
+        
+            // North America
+            'en-US': 'USD', // United States
+            'en-CA': 'CAD', // Canada
+            'es-MX': 'MXN', // Mexico
+        
+            // South America
+            'pt-BR': 'BRL', // Brazil
+            'es-AR': 'ARS', // Argentina
+        
+            // Oceania
+            'en-AU': 'AUD', // Australia
+            'en-NZ': 'NZD', // New Zealand
+        
+            // Africa
+            'en-ZA': 'ZAR', // South Africa
+            'ar-EG': 'EGP', // Egypt
+        };
+    
+        // Try to match the full region code directly
+        if (localeToCurrency[localeCode]) {
+            return localeToCurrency[localeCode];
+        }
+    
+        // If no direct match, try to match the language part (the first two characters)
+        const languageCode = localeCode.split('-')[0];
+        const fallbackMap: Record<string, string> = {
+            'en': 'USD', // English defaults to USD
+            'zh': 'CNY', // Chinese defaults to CNY
+            'es': 'EUR', // Spanish defaults to EUR
+            'fr': 'EUR', // French defaults to EUR
+            'de': 'EUR', // German defaults to EUR
+            'it': 'EUR', // Italian defaults to EUR
+            'ja': 'JPY', // Japanese defaults to JPY
+            'ko': 'KRW', // Korean defaults to KRW
+            'ru': 'RUB', // Russian defaults to RUB
+            'ar': 'SAR', // Arabic defaults to SAR
+        };
+    
+        if (fallbackMap[languageCode]) {
+            return fallbackMap[languageCode];
+        }
+    
+        // If no match, return USD as the default value
+        return 'USD';
+    }
 }
 
 export default InputNumberFoundation;

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

@@ -1,14 +1,14 @@
 {
     "name": "@douyinfe/semi-foundation",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "",
     "scripts": {
         "build:lib": "node ./scripts/compileLib.js",
         "prepublishOnly": "npm run build:lib"
     },
     "dependencies": {
-        "@douyinfe/semi-animation": "2.76.1",
-        "@douyinfe/semi-json-viewer-core": "2.76.1",
+        "@douyinfe/semi-animation": "2.77.0",
+        "@douyinfe/semi-json-viewer-core": "2.77.0",
         "@mdx-js/mdx": "^3.0.1",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",

+ 2 - 0
packages/semi-foundation/userGuide/animation.scss

@@ -0,0 +1,2 @@
+$transition_duration-userGuide_spotLight: 200ms; // spotLight 动画持续时间
+$transition_function-userGuide_spotLight: cubic-bezier(0.4, 0, 0.2, 1); // spotLight 过渡曲线

+ 35 - 0
packages/semi-foundation/userGuide/constants.ts

@@ -0,0 +1,35 @@
+import { BASE_CLASS_PREFIX } from '../base/constants';
+
+const cssClasses = {
+    PREFIX: `${BASE_CLASS_PREFIX}-userGuide`,
+    PREFIX_MODAL: `${BASE_CLASS_PREFIX}-userGuide-modal`,
+};
+
+const strings = {
+    MODE: ['popup', 'modal'],
+    POSITION_SET: [
+        'top',
+        'topLeft',
+        'topRight',
+        'left',
+        'leftTop',
+        'leftBottom',
+        'right',
+        'rightTop',
+        'rightBottom',
+        'bottom',
+        'bottomLeft',
+        'bottomRight',
+        'leftTopOver',
+        'rightTopOver',
+    ],
+    THEME: ['default', 'primary'],
+};
+
+const numbers = {
+    DEFAULT_CURRENT: 0,
+    DEFAULT_SPOTLIGHT_PADDING: 5,
+    DEFAULT_Z_INDEX: 1030,
+};
+
+export { cssClasses, strings, numbers }; 

+ 87 - 0
packages/semi-foundation/userGuide/foundation.ts

@@ -0,0 +1,87 @@
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import { numbers } from './constants';
+
+export interface UserGuideAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S>{
+    notifyChange: (current: number) => void;
+    notifyPrev: (current: number) => void;
+    notifyNext: (current: number) => void;
+    notifySkip: () => void;
+    notifyFinish: () => void;
+    setCurrent: (current: number) => void;
+    disabledBodyScroll: () => void;
+    enabledBodyScroll: () => void
+}
+
+
+export default class UserGuideFoundation <P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<UserGuideAdapter<P, S>, P, S> {
+    constructor(adapter: UserGuideAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    init() {
+    }
+
+    destroy() {
+        this._adapter.enabledBodyScroll();
+    }
+
+    _notifyChange(current: number): void {
+        const { current: stateCurrent } = this.getStates();
+        if (stateCurrent !== current) {
+            this._adapter.notifyChange(current);
+        }
+    }
+
+    getIsControlledComponent(): boolean {
+        return this._isInProps('current');
+    }
+
+    beforeShow() {
+        this._adapter.disabledBodyScroll();
+    }
+
+    afterHide() {
+        this._adapter.enabledBodyScroll();
+    }
+
+    getFinalPaading() {
+        const { spotlightPadding, steps } = this.getProps();
+        const { current } = this.getStates();
+        const stepPadding = steps[current]?.spotlightPadding;
+        const padding = typeof stepPadding === 'number' ? stepPadding : 
+            typeof spotlightPadding === 'number' ? spotlightPadding : 
+                numbers.DEFAULT_SPOTLIGHT_PADDING;
+        return padding;
+    }
+
+    handlePrev = () => {
+        const { current } = this.getStates();
+        const newCurrent = current - 1;
+        if (!this.getIsControlledComponent()) {
+            this._adapter.setCurrent(newCurrent);
+        } 
+        this._notifyChange(newCurrent);
+        this._adapter.notifyPrev(newCurrent);
+    };
+
+    handleNext = () => {
+        const { steps } = this.getProps();
+        const { current } = this.getStates();
+        const isLastStep = current === steps.length - 1;
+        const newCurrent = isLastStep ? current : current + 1;
+        if (isLastStep) {
+            this._adapter.notifyFinish();
+        } else {
+            this._notifyChange(newCurrent);
+            this._adapter.notifyNext(newCurrent);
+            if (!this.getIsControlledComponent()) {
+                this._adapter.setCurrent(newCurrent);
+            }
+        }
+    };
+
+    handleSkip = () => {
+        this._adapter.notifySkip();
+    };
+
+} 

+ 147 - 0
packages/semi-foundation/userGuide/userGuide.scss

@@ -0,0 +1,147 @@
+@import './variables.scss';
+@import './animation.scss';
+
+$module: #{$prefix}-userGuide;
+
+
+.#{$module} {
+
+    &-spotlight {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100vw;
+        height: 100vh;
+        pointer-events: none;
+
+        &-transparent-rect {
+            pointer-events: auto;
+        }
+
+        &-rect {
+            transition: all $transition_duration-userGuide_spotLight $transition_function-userGuide_spotLight;
+        }
+    }
+
+    &-popover {
+        max-width: fit-content;
+        width: $width-userGuide_popover-default;
+    }
+
+    &-popup-content {
+        color: $color-userGuide_popup-text-default;
+
+        &-primary {
+            color: $color-userGuide_popup-text-primary;
+        }
+
+        &-cover {
+            img {
+                display: block;
+                height: $height-userGuide_popup_content_cover-default;
+                width: 100%;
+                border-radius: $radius-userGuide_popup_content_cover;
+            }
+        }
+
+        &-body {
+            padding: $spacing-userGuide_popup_content_body-padding;
+        }
+    
+        &-title {
+            font-size: $font-userGuide_popup_content_title-fontSize;
+            font-weight: $font-userGuide_popup_content_title-fontWeight;
+            line-height: $font-userGuide_popup_content_title-lineHeight;
+            margin-bottom: $spacing-userGuide_popup_content_title-marginBottom;
+        }
+
+        &-description {
+            font-size: $font-userGuide_popup_content_description-fontSize;
+            line-height: $font-userGuide_popup_content_description-lineHeight;
+            margin-bottom: $spacing-userGuide_popup_content_description-marginBottom;
+        }
+
+        &-footer {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+    
+        &-buttons {
+            display: flex;
+            gap: $spacing-userGuide_popup_content_button-gap;
+            margin-left: $spacing-userGuide_popup_content_button-marginLeft;
+        }
+
+        &-indicator {
+            font-size: $font-size-regular; 
+            line-height: $font-userGuide_popup_content_indicator-lineHeight;
+        }
+    }
+
+    &-modal {
+
+        .#{$prefix}-modal-small {
+            width: fit-content;
+        }
+
+        // Override the modal's original padding
+        .#{$prefix}-modal-content {
+            padding: 0;
+            width: $width-userGuide_modal_content_cover-default;
+            max-width: fit-content;
+        }
+
+        &-cover {
+            height: $height-userGuide_modal_content_cover-default;
+        }
+
+        &-indicator {
+            height: $height-userGuide_modal_content_indicator-default;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            column-gap: $spacing-userGuide_popup_content_indicator-gap;
+
+            &-item {
+                width: $width-userGuide_modal_content_indicator_item-default;
+                height: $height-userGuide_modal_content_indicator_item-default;
+                border-radius: $radius-userGuide_modal_content_indicator;
+                background-color: $color-userGuide_modal_content_indicator-bg;
+
+                &-active {
+                    background: $color-userGuide_modal_content_indicator-bg-active
+                }
+            }
+        }
+
+        &-body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            flex-direction: column;
+            padding: $spacing-userGuide_modal_content_body-padding;
+
+            &-title {
+                font-size: $font-userGuide_modal_content_title-fontSize;
+                font-weight: $font-userGuide_modal_content_title-fontWeight;
+                line-height:$font-userGuide_modal_content_title-lineHeight;
+                margin-bottom: $spacing-userGuide_modal_content_title-marginBottom;
+            }
+
+            &-description {
+                font-size: $font-userGuide_modal_content_description-fontSize;
+                line-height: $font-userGuide_modal_content_description-lineHeight;
+            }
+        }
+
+        &-footer {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding: $spacing-userGuide_modal_content_footer-padding;
+            column-gap: $spacing-userGuide_modal_content_button-gap
+        }
+    }
+} 
+

+ 48 - 0
packages/semi-foundation/userGuide/variables.scss

@@ -0,0 +1,48 @@
+// Color
+$color-userGuide_popup-text-default: var(--semi-color-text-0); // 气泡卡片文字颜色 - 默认
+$color-userGuide_popup-text-primary: var(--semi-color-tertiary-light-default); // 气泡卡片文字颜色 - primary
+$color-userGuide_modal_content_indicator-bg: var(--semi-color-primary-light-active); // 弹窗指示器背景颜色
+$color-userGuide_modal_content_indicator-bg-active: var(--semi-color-primary);  // 弹窗指示器背景颜色 - 选中态
+
+// Width/Height
+$width-userGuide_popover-default: 400px; // 默认气泡卡片宽度
+$height-userGuide_popup_content_cover-default: 200px; // 默认气泡卡片封面高度
+$width-userGuide_modal_content_cover-default: 600px; // 默认弹窗式卡片封面宽度
+$height-userGuide_modal_content_cover-default: 300px; // 默认弹窗式卡片封面高度
+$height-userGuide_modal_content_indicator-default: 30px; // 默认弹窗式卡片指示器整体高度
+$width-userGuide_modal_content_indicator_item-default: 6px; // 默认弹窗式卡片单个指示器宽度
+$height-userGuide_modal_content_indicator_item-default: 6px; // 默认弹窗式卡片单个指示器高度
+$font-userGuide_popup_content_title-lineHeight: 24px; // 气泡卡片标题行高
+$font-userGuide_popup_content_description-lineHeight: 20px; // 气泡卡片详情行高
+$font-userGuide_popup_content_indicator-lineHeight: 20px; // 气泡卡片指示器行高
+$font-userGuide_modal_content_title-lineHeight: 24px; // 弹窗式卡片标题行高
+$font-userGuide_modal_content_description-lineHeight: 20px; // 弹窗式卡片详情行高
+
+// Spacing
+$spacing-userGuide_popup_content_body-padding: 24px; // 气泡卡片内边距
+$spacing-userGuide_popup_content_title-marginBottom: 8px; // 气泡卡片标题底部外边距
+$spacing-userGuide_popup_content_description-marginBottom: 48px; // 气泡卡片详情底部外边距
+$spacing-userGuide_popup_content_button-gap: 12px; // 气泡卡片底部按钮间距
+$spacing-userGuide_popup_content_button-marginLeft: 120px; // 气泡卡片按钮组左边距
+$spacing-userGuide_modal_content_title-marginBottom: 8px; // 弹窗式卡片标题底部外边距
+$spacing-userGuide_modal_content_body-padding: 24px 48px; // 弹窗式卡片body内边距
+$spacing-userGuide_modal_content_footer-padding: 24px; // 弹窗式卡片footer内边距
+$spacing-userGuide_popup_content_indicator-gap: 8px; // 弹窗式片指示器间距
+$spacing-userGuide_modal_content_button-gap: 12px; // 弹窗式卡片底部按钮间距
+
+
+// Radius
+$radius-userGuide_popup_content_cover: var(--semi-border-radius-medium) var(--semi-border-radius-medium) 0 0; // 气泡卡片封面圆角
+$radius-userGuide_modal_content_indicator: var(--semi-border-radius-large); // 气泡卡片指示器圆角
+
+
+// font
+$font-userGuide_popup_content_title-fontSize: $font-size-header-5; // 气泡卡片标题字体大小
+$font-userGuide_popup_content_description-fontSize: $font-size-regular; // 气泡卡片详情字体大小
+$font-userGuide_popup_content_indicator-fontSize: $font-size-regular; // 气泡卡片指示器字体大小
+$font-userGuide_popup_content_title-fontWeight: $font-weight-bold; // 气泡卡片标题字重
+$font-userGuide_modal_content_title-fontSize: $font-size-header-5; // 弹窗式卡片标题字体大小
+$font-userGuide_modal_content_description-fontSize: $font-size-regular; // 弹窗式卡详情字体大小
+$font-userGuide_modal_content_title-fontWeight: $font-weight-bold; // 弹窗式卡片标题字重
+
+

+ 1 - 1
packages/semi-icons-lab/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@douyinfe/semi-icons-lab",
-  "version": "2.76.1",
+  "version": "2.77.0",
   "description": "semi icons lab",
   "keywords": [
     "semi",

+ 1 - 1
packages/semi-icons/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-icons",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "semi icons",
     "keywords": [
         "semi",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-illustrations",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "semi illustrations",
     "keywords": [
         "semi",

+ 1 - 1
packages/semi-json-viewer-core/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-json-viewer-core",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "",
     "main": "lib/index.js",
     "module": "lib/index.js",

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

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

+ 1 - 1
packages/semi-rspack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-rspack-plugin",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "",
     "homepage": "",
     "license": "MIT",

+ 1 - 1
packages/semi-scss-compile/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-scss-compile",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "compile semi scss to css",
     "author": "[email protected]",
     "license": "MIT",

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-theme-default",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "semi-theme-default",
     "keywords": [
         "semi-theme",

+ 1 - 0
packages/semi-ui/index.ts

@@ -127,3 +127,4 @@ export { default as JsonViewer } from './jsonViewer';
 export { default as DragMove } from './dragMove';
 export { default as Cropper } from './cropper';
 export { default as AudioPlayer } from './audioPlayer';
+export { default as UserGuide } from './userGuide';

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

@@ -479,7 +479,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             [`${wrapperPrefix}-hidden`]: type === 'hidden',
             [`${wrapperPrefix}-${size}`]: size,
             [`${prefixCls}-borderless`]: borderless,
-            [`${prefixCls}-only_border`]: onlyBorder!==undefined && onlyBorder!==null,
+            [`${prefixCls}-only_border`]: onlyBorder !== undefined && onlyBorder !== null,
         });
         const inputCls = cls(prefixCls, {
             [`${prefixCls}-${size}`]: size,
@@ -516,7 +516,7 @@ class Input extends BaseComponent<InputProps, InputState> {
         }
 
         let wrapperStyle = { ...style };
-        if (onlyBorder!==undefined) {
+        if (onlyBorder !== undefined) {
             wrapperStyle = {
                 borderWidth: onlyBorder,
                 ...style

+ 132 - 0
packages/semi-ui/inputNumber/__test__/inputNumber.test.js

@@ -431,5 +431,137 @@ describe(`InputNumber`, () => {
         expect(spyChange.getCall(0).args[0]).toEqual(1);
         expect(formApi.getValue('minControlled')).toBe(1);
     });
+
+    it('Common currency display', () => {
+        const defaultValue = 123456.78;
+        let inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('¥123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="en-US" currency="USD" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('$123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.456,78 €');
+
+        inputNumber = mount(<InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('¥123,457');
+
+        inputNumber = mount(<InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.457 ₫');  
+
+        inputNumber = mount(<InputNumber localeCode="th-TH" currency="THB" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('฿123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="id-ID" currency="IDR" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('Rp 123.456,78');
+        
+    });
+
+    it('Common currency display defaultValue is string', () => {
+        const defaultValue = 123456.78;
+        let inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('¥123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="en-US" currency="USD" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('$123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.456,78 €');
+
+        inputNumber = mount(<InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('¥123,457');
+
+        inputNumber = mount(<InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.457 ₫');  
+
+        inputNumber = mount(<InputNumber localeCode="th-TH" currency="THB" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('฿123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="id-ID" currency="IDR" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('Rp 123.456,78');
+        
+    });
+
+    it('Uncontrolled + add/minus button', () => {
+        const defaultValue = 123459.78;
+        let inputNumber = mount(<InputNumber localeCode="en-US" currency="USD" defaultValue={defaultValue} />);
+
+        const inputElem = inputNumber.find('input');
+        const btns = inputNumber.find(`.${BASE_CLASS_PREFIX}-input-number-suffix-btns .${BASE_CLASS_PREFIX}-input-number-button`);
+        const addBtn = btns.first();
+        const minusBtn = btns.last();
+
+        _.times(1, () => addBtn.simulate('mousedown', { button: numbers.MOUSE_BUTTON_LEFT  }));
+        expect(inputElem.instance().value).toBe('$123,460.78');
+        _.times(3, () => minusBtn.simulate('mousedown', { button: numbers.MOUSE_BUTTON_LEFT }));
+        expect(inputElem.instance().value).toBe('$123,457.78');
+        
+    });
+
+     it('Controlled + add/minus button', () => {
+        const defaultValue = 123459.78;
+        const spyChange = sinon.spy();
+        let inputNumber = mount(<InputNumber localeCode="en-US" currency="USD" value={defaultValue} onChange={spyChange} />);
+
+        const inputElem = inputNumber.find('input');
+        const btns = inputNumber.find(`.${BASE_CLASS_PREFIX}-input-number-suffix-btns .${BASE_CLASS_PREFIX}-input-number-button`);
+        const addBtn = btns.first();
+        const minusBtn = btns.last();
+
+        _.times(1, () => {
+            addBtn.simulate('mousedown', { button: numbers.MOUSE_BUTTON_LEFT  })
+            const updatedValue = spyChange.lastCall.args[0];
+            inputNumber.setProps({ value: updatedValue });
+        });
+        expect(spyChange.callCount).toBe(2);
+        expect(inputElem.instance().value).toBe('$123,460.78');
+
+        _.times(3, () => {
+            minusBtn.simulate('mousedown', { button: numbers.MOUSE_BUTTON_LEFT });
+            // 每次点击后模拟值更新
+            const updatedValue = spyChange.lastCall.args[0];
+            inputNumber.setProps({ value: updatedValue });
+        });
+        expect(spyChange.callCount).toBe(5);
+        expect(inputElem.instance().value).toBe('$123,457.78');
+     });
+
+     it('Common currency display with showCurrencySymbol=false', () => {
+        const defaultValue = 123456.78;
+        let inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="en-US" currency="USD" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="de-DE" currency="EUR" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.456,78');
+
+        inputNumber = mount(<InputNumber localeCode="ja-JP" currency="JPY" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123,457');
+
+        inputNumber = mount(<InputNumber localeCode="vi-VN" currency="VND" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.457');  
+
+        inputNumber = mount(<InputNumber localeCode="th-TH" currency="THB" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123,456.78');
+
+        inputNumber = mount(<InputNumber localeCode="id-ID" currency="IDR" defaultValue={defaultValue} showCurrencySymbol={false} />);
+        expect(inputNumber.find('input').instance().value).toBe('123.456,78');
+    });
+
+    it('CNY currency with different currencyDisplay options', () => {
+        const defaultValue = 123456.78;
+        
+        let inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} />);
+        expect(inputNumber.find('input').instance().value).toBe('¥123,456.78');
+        
+        inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} currencyDisplay="code" />);
+        expect(inputNumber.find('input').instance().value).toBe('CNY 123,456.78');
+        
+        inputNumber = mount(<InputNumber currency="CNY" defaultValue={defaultValue} currencyDisplay="name" />);
+        expect(inputNumber.find('input').instance().value).toBe('123,456.78人民币');
+    });
+
 });
  

+ 174 - 13
packages/semi-ui/inputNumber/_story/inputNumber.stories.jsx

@@ -73,7 +73,7 @@ export const _InputNumber = () => {
         <InputNumber defaultValue={10.08} precision={2} onChange={log} />
         <br />
 
-        <label>格式化+小数</label>
+        <label>格式化 + 小数</label>
         <InputNumber
           defaultValue={1000}
           precision={2}
@@ -87,15 +87,15 @@ export const _InputNumber = () => {
         <InputNumber value={controlledValue} onChange={controlledOnChange} />
         <br />
 
-        <label>受控+上下界</label>
+        <label>受控 + 上下界</label>
         <InputNumber min={1} max={10} value={controlledValue2} onChange={controlledOnChange2} />
         <br />
 
-        <label>小数+受控</label>
+        <label>小数 + 受控</label>
         <InputNumber value={decimal} precision={2} onChange={decimalOnChange} />
         <br />
 
-        <label>格式化+受控</label>
+        <label>格式化 + 受控</label>
         <InputNumber
           defaultValue={1000}
           precision={0}
@@ -106,7 +106,7 @@ export const _InputNumber = () => {
         />
         <br />
 
-        <label>格式化+小数+受控</label>
+        <label>格式化 + 小数 + 受控</label>
         <InputNumber
           precision={2}
           formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
@@ -340,10 +340,10 @@ export const OnChangeLimit = () => {
 
     return (
       <>
-        <h3>数值没有持续变化说明正常,v1.10.0修复</h3>
+        <h3>数值没有持续变化说明正常,v1.10.0 修复</h3>
         <br />
         <br />
-        <div>点击2后点击输入框,然后点击按钮会触发</div>
+        <div>点击 2 后点击输入框,然后点击按钮会触发</div>
         {disabled ? (
           <div
             onClick={() => {
@@ -379,7 +379,7 @@ export const OnChangeLimit = () => {
 };
 
 OnChangeLimit.story = {
-  name: 'onChange无限触发问题',
+  name: 'onChange 无限触发问题',
 };
 
 export const ClearIconPosition = () => {
@@ -595,7 +595,7 @@ export const FormCustomInput = () => {
           onChange={(...args) => console.log('inputNumber change', ...args)}
           onNumberChange={(...args) => console.log('inputNumber number change', ...args)}
         />
-        <h4>not controlled + formatter(onChange会包括英文字符,onNumberChange不包括)</h4>
+        <h4>not controlled + formatter(onChange 会包括英文字符,onNumberChange 不包括)</h4>
         <InputNumber
           formatter={value => `${value}`.replace(/\D/g, '')}
           onChange={(...args) => console.log('inputNumber change', ...args)}
@@ -603,7 +603,7 @@ export const FormCustomInput = () => {
         />
         <Form onValueChange={v => console.log(v)}>
           <h4>
-            Form + Form.InputNumber + formatter + onChange(onChange包括英文字符,显示没有英文字符)
+            Form + Form.InputNumber + formatter + onChange(onChange 包括英文字符,显示没有英文字符)
           </h4>
           <Form.InputNumber
             field="formOriginalInputNumber"
@@ -613,7 +613,7 @@ export const FormCustomInput = () => {
           />
           <h4>
             Form + withField InputNumber + formatter +
-            onNumberChange(onNumberChange不包括英文字符,显示也不包括英文字符)
+            onNumberChange(onNumberChange 不包括英文字符,显示也不包括英文字符)
           </h4>
           <CustomInput
             field="formCustomInputNumber"
@@ -622,7 +622,7 @@ export const FormCustomInput = () => {
           />
         </Form>
         <h4>
-          type=number (TODO:需要关注内置的按钮+不同浏览器对type=number的支持情况,比如 safari
+          type=number(TODO:需要关注内置的按钮 + 不同浏览器对 type=number 的支持情况,比如 safari
           貌似就不支持)
         </h4>
         <InputNumber
@@ -797,6 +797,167 @@ export const Fix1772 = () => {
   );
 }
 
+export const BasicCurrency = () => {
+  return (
+    <div>
+      <div>🇨🇳 人民币 ➕ 非受控</div>
+      <InputNumber 
+      currency="CNY" 
+      defaultValue={123456.78} 
+      onChange={v => {console.log('onChange', v); }}
+      onNumberChange={v => {console.log('onNumberChange', v);}}
+      onBlur={() => {console.log('onBlur');}}
+      onFocus={() => {console.log('onFocus');}}
+    />
+    </div>
+  );
+}
+export const TypicalCurrency = () => {
+  return (
+    <div>
+      <div>🇨🇳 人民币</div>
+      <InputNumber localeCode="zh-CN" currency="CNY" defaultValue={123456.78} />
+      <br />
+      <br />
+      <div>🇪🇺 欧元</div>
+      <InputNumber localeCode="de-DE" currency="EUR" defaultValue={123456.78} />
+      <br />
+      <br />
+      <div>🇯🇵 日元</div>
+      <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={123456.78} />
+      <br />
+      <br />
+      <div>🇻🇳 越南盾</div>
+      <InputNumber localeCode="vi-VN" currency="VND" defaultValue={123456.78} />
+      <br />
+      <br />
+    </div>
+  );  
+}
+
+export const CommonCurrency = () => {
+  const [value1, setValue1] = useState(123456.78);
+  const [value2, setValue2] = useState(123456.78);
+
+  return (
+    <div>
+      <div>🇺🇸 美元 + 受控</div>
+      <InputNumber  localeCode="en-US" currency="USD" value={value1} 
+        onChange={v => {console.log('onChange', v); 
+          setValue1(v)
+        }}
+      />
+      <br />
+      <br />
+      <div>🇨🇳 人民币 + 受控</div>
+      <InputNumber  currency="CNY" value={value2} onChange={v => {console.log('onChange', v); setValue2(v)}}/>
+      <br />
+      <br />
+      <div>🇨🇳 人民币 + 非受控</div>
+      <InputNumber  currency="CNY" defaultValue={'123456.78'} onChange={v => {console.log('onChange', v);}}/>
+      <br />
+      <br />
+      <div>🇪🇺 欧元</div>
+      <InputNumber  localeCode="de-DE" currency="EUR" defaultValue={123456.78} />
+      <br />
+      <br />
+      <div>🇹🇭 泰铢</div>
+      <InputNumber  localeCode="th-TH" currency="THB" defaultValue={123456.78} onChange={v => console.log('onChange', v)}/>
+      <br />
+      <br />
+      <div>🇮🇩 印尼盾</div> 
+      <InputNumber  localeCode="id-ID" currency="IDR" defaultValue={123456.78} />
+      <br />
+      <br />
+      <div>🇯🇵 日元</div>
+      <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={123456.78}  onChange={v => console.log('onChange', v)}/>
+      <br />
+      <br />
+      <div>🇻🇳 越南盾</div>
+      <InputNumber  localeCode="vi-VN" currency="VND" defaultValue={123456.78} />
+      <br />
+      <br />
+    </div>
+  );
+}
+
+export const ShowCurrencySymbol = () => {
+  const [value1, setValue1] = useState(123456.78);
+  const [value2, setValue2] = useState(123456.78);
+
+  return (
+    <div>
+      <div>🇺🇸 美元 + 受控</div>
+      <InputNumber  localeCode="en-US" currency="USD" value={value1} 
+        onChange={v => {console.log('onChange', v); 
+          setValue1(v)
+        }}
+        showCurrencySymbol={false}
+      />
+      <br />
+      <br />
+      <div>🇨🇳 人民币 + 受控</div>
+      <InputNumber currency="CNY" value={value2} showCurrencySymbol={false} onChange={v => {console.log('onChange', v); setValue2(v)}}/>
+      <br />
+      <br />
+      <div>🇨🇳 人民币 + 非受控</div>
+      <InputNumber currency="CNY" defaultValue={'123456.78'} showCurrencySymbol={false} onChange={v => {console.log('onChange', v);}}/>
+      <br />
+      <br />
+      <div>🇪🇺 欧元</div>
+      <InputNumber  localeCode="de-DE" currency="EUR" defaultValue={123456.78} showCurrencySymbol={false} />
+      <br />
+      <br />
+      <div>🇹🇭 泰铢</div>
+      <InputNumber  localeCode="th-TH" currency="THB" defaultValue={123456.78} showCurrencySymbol={false} onChange={v => console.log('onChange', v)}/>
+      <br />
+      <br />
+      <div>🇮🇩 印尼盾</div> 
+      <InputNumber  localeCode="id-ID" currency="IDR" defaultValue={123456.78} showCurrencySymbol={false} />
+      <br />
+      <br />
+      <div>🇯🇵 日元</div>
+      <InputNumber localeCode="ja-JP" currency="JPY" defaultValue={123456.78}  showCurrencySymbol={false} onChange={v => console.log('onChange', v)}/>
+      <br />
+      <br />
+      <div>🇻🇳 越南盾</div>
+      <InputNumber  localeCode="vi-VN" currency="VND" defaultValue={123456.78} showCurrencySymbol={false} />
+      <br />
+      <br />
+    </div>
+  );
+}
+
+export const CurrencyDisplay = () => {
+  return (
+    <div>
+      <div>🇨🇳 CNY ➕ code</div>
+      <InputNumber 
+        currency="CNY" 
+        currencyDisplay="code"
+        defaultValue={123456.78}
+      />
+      <br />
+      <br />
+      <div>🇨🇳 CNY ➕ symbol</div>
+      <InputNumber 
+        currency="CNY" 
+        currencyDisplay="symbol" 
+        defaultValue={123456.78}
+      />
+      <br />
+      <br />
+      <div>🇨🇳 CNY ➕ name</div>
+      <InputNumber 
+        currency="CNY" 
+        currencyDisplay="name" 
+        defaultValue={123456.78}
+      />
+      <br />
+      <br />
+    </div>
+  );
+}
 export const TestInputNumber = () => {
 
   return (
@@ -807,4 +968,4 @@ export const TestInputNumber = () => {
       <br/><br/>
   </div>
   )
-}
+}

+ 35 - 3
packages/semi-ui/inputNumber/index.tsx

@@ -14,12 +14,17 @@ import { IconChevronUp, IconChevronDown } from '@douyinfe/semi-icons';
 import '@douyinfe/semi-foundation/inputNumber/inputNumber.scss';
 import { isNaN, isString, noop } from 'lodash';
 import { ArrayElement } from '../_base/base';
+import LocaleConsumer from '../locale/localeConsumer';
+import { Locale } from '../locale/interface';
 
 export interface InputNumberProps extends InputProps {
     autofocus?: boolean;
     className?: string;
     clearIcon?: React.ReactNode;
+    currency?: string | boolean;
+    currencyDisplay?: 'code' | 'symbol' | 'name';
     defaultValue?: number | string;
+    defaultCurrency?: string;
     disabled?: boolean;
     formatter?: (value: number | string) => string;
     forwardedRef?: React.MutableRefObject<HTMLInputElement> | ((instance: HTMLInputElement) => void);
@@ -28,8 +33,11 @@ export interface InputNumberProps extends InputProps {
     insetLabel?: React.ReactNode;
     insetLabelId?: string;
     keepFocus?: boolean;
+    localeCode?: string;
     max?: number;
     min?: number;
+    minimumFractionDigits?: number;
+    maximumFractionDigits?: number;
     parser?: (value: string) => string;
     precision?: number;
     prefixCls?: string;
@@ -37,6 +45,7 @@ export interface InputNumberProps extends InputProps {
     pressTimeout?: number;
     shiftStep?: number;
     showClear?: boolean;
+    showCurrencySymbol?: boolean;
     size?: ArrayElement<typeof strings.SIZE>;
     step?: number;
     style?: React.CSSProperties;
@@ -82,6 +91,7 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
         pressTimeout: PropTypes.number,
         preventScroll: PropTypes.bool,
         shiftStep: PropTypes.number,
+        showCurrencySymbol: PropTypes.bool,
         step: PropTypes.number,
         style: PropTypes.object,
         suffix: PropTypes.any,
@@ -104,6 +114,7 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
         pressInterval: numbers.DEFAULT_PRESS_TIMEOUT,
         pressTimeout: numbers.DEFAULT_PRESS_TIMEOUT,
         shiftStep: numbers.DEFAULT_SHIFT_STEP,
+        showCurrencySymbol: true,
         size: strings.DEFAULT_SIZE,
         step: numbers.DEFAULT_STEP,
         onBlur: noop,
@@ -144,6 +155,9 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                     this.adapter.setCache(eventName, null);
                 }
             },
+            getInputCharacter: (index: number) => {
+                return this.inputNode.value[index];
+            },
             recordCursorPosition: () => {
                 // Record position
                 try {
@@ -316,7 +330,7 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                         this.foundation.updateStates({ value: valueStr });
                     }
                 } else if (this.foundation.isValidNumber(parsedNum)) {
-                    newValue = this.foundation.doFormat(parsedNum);
+                    newValue = this.foundation.doFormat(parsedNum, true, true);
                     this.foundation.updateStates({ number: parsedNum, value: newValue });
                 } else {
                     // Invalid digital analog blurring effect instead of controlled failure
@@ -325,7 +339,19 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
                 }
             }
             if (newValue && isString(newValue) && newValue !== String(this.props.value)) {
-                this.foundation.notifyChange(newValue, null);
+                if (this.foundation._isCurrency()) {
+                    // 仅在解析后的数值而不是格式化的字符串变化时 notifyChange
+                    // notifyChange only when the parsed value changes, not the formatted string
+                    const parsedNewValue = this.foundation.doParse(newValue);
+                    const parsedPropValue = typeof this.props.value === 'string' ? 
+                        this.foundation.doParse(this.props.value) : this.props.value;
+                    
+                    if (parsedNewValue !== parsedPropValue) {
+                        this.foundation.notifyChange(newValue, null);
+                    }
+                } else {
+                    this.foundation.notifyChange(newValue, null);
+                }
             }
         }
 
@@ -505,7 +531,13 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
 
 export default forwardStatics(
     React.forwardRef<HTMLInputElement, InputNumberProps>(function SemiInputNumber(props, ref) {
-        return <InputNumber {...props} forwardedRef={ref} />;
+        return (
+            <LocaleConsumer<Locale['InputNumber']> componentName="InputNumber">
+                {(locale: Locale['InputNumber'], localeCode: string, dateFnsLocale, currency: string) => (
+                    <InputNumber localeCode={localeCode} defaultCurrency={currency} {...props} forwardedRef={ref}/>
+                )}
+            </LocaleConsumer>
+        );
     }),
     InputNumber
 );

+ 91 - 76
packages/semi-ui/jsonViewer/index.tsx

@@ -21,7 +21,9 @@ import {
 } from '@douyinfe/semi-icons';
 import BaseComponent, { BaseProps } from '../_base/baseComponent';
 import { createPortal } from 'react-dom';
-import {isEqual} from "lodash";
+import { isEqual } from "lodash";
+import LocaleConsumer from '../locale/localeConsumer';
+import { Locale } from '../locale/interface';
 const prefixCls = cssClasses.PREFIX;
 
 export type { JsonViewerOptions };
@@ -197,89 +199,102 @@ class JsonViewerCom extends BaseComponent<JsonViewerProps, JsonViewerState> {
 
     renderSearchBar() {
         return (
-            <div className={`${prefixCls}-search-bar`}>
-                <Input
-                    placeholder="查找"
-                    className={`${prefixCls}-search-bar-input`}
-                    onChange={(_value, e) => {
-                        e.preventDefault();
-                        if (!this.isComposing) {
-                            this.searchHandler();
-                        }
-                        this.searchInputRef.current?.focus();
-                    }}
-                    onCompositionStart={() => {
-                        this.isComposing = true;
-                    }}
-                    onCompositionEnd={() => {
-                        this.isComposing = false;
-                        this.searchHandler();
-                        this.searchInputRef.current?.focus();
-                    }}
-                    ref={this.searchInputRef}
-                />
-                {this.renderSearchOptions()}
-                <ButtonGroup>
-                    <Button
-                        icon={<IconChevronLeft />}
-                        onClick={e => {
-                            e.preventDefault();
-                            this.foundation.prevSearch();
-                        }}
-                    />
-                    <Button
-                        icon={<IconChevronRight />}
-                        onClick={e => {
-                            e.preventDefault();
-                            this.foundation.nextSearch();
-                        }}
-                    />
-                </ButtonGroup>
-                <Button
-                    icon={<IconClose />}
-                    size="small"
-                    theme={'borderless'}
-                    type={'tertiary'}
-                    onClick={() => this.foundation.showSearchBar()}
-                />
-            </div>
+            <LocaleConsumer
+                componentName="JsonViewer"
+            >
+                {(locale: Locale['JsonViewer'], localeCode: Locale['code']) => (
+                    <div className={`${prefixCls}-search-bar`}>
+                        <Input
+                            placeholder={locale.search}
+                            className={`${prefixCls}-search-bar-input`}
+                            onChange={(_value, e) => {
+                                e.preventDefault();
+                                if (!this.isComposing) {
+                                    this.searchHandler();
+                                }
+                                this.searchInputRef.current?.focus();
+                            }}
+                            onCompositionStart={() => {
+                                this.isComposing = true;
+                            }}
+                            onCompositionEnd={() => {
+                                this.isComposing = false;
+                                this.searchHandler();
+                                this.searchInputRef.current?.focus();
+                            }}
+                            ref={this.searchInputRef}
+                        />
+                        {this.renderSearchOptions()}
+                        <ButtonGroup>
+                            <Button
+                                icon={<IconChevronLeft />}
+                                onClick={e => {
+                                    e.preventDefault();
+                                    this.foundation.prevSearch();
+                                }}
+                            />
+                            <Button
+                                icon={<IconChevronRight />}
+                                onClick={e => {
+                                    e.preventDefault();
+                                    this.foundation.nextSearch();
+                                }}
+                            />
+                        </ButtonGroup>
+                        <Button
+                            icon={<IconClose />}
+                            size="small"
+                            theme={'borderless'}
+                            type={'tertiary'}
+                            onClick={() => this.foundation.showSearchBar()}
+                        />
+                    </div>
+                )}
+            </LocaleConsumer>
         );
     }
 
     renderReplaceBar() {
         const { readOnly } = this.props.options;
         return (
-            <div className={`${prefixCls}-replace-bar`}>
-                <Input
-                    placeholder="替换"
-                    className={`${prefixCls}-replace-bar-input`}
-                    onChange={(value, e) => {
-                        e.preventDefault();
-                    }}
-                    ref={this.replaceInputRef}
-                />
-                <Button
-                    disabled={readOnly}
-                    onClick={() => {
-                        const value = this.replaceInputRef.current?.value;
-                        this.foundation.replace(value);
-                    }}
-                >
-                    替换
-                </Button>
-                <Button
-                    disabled={readOnly}
-                    onClick={() => {
-                        const value = this.replaceInputRef.current?.value;
-                        this.foundation.replaceAll(value);
-                    }}
-                >
-                    全部替换
-                </Button>
-            </div>
+            <LocaleConsumer
+                componentName="JsonViewer"
+            >
+                {(locale: Locale['JsonViewer'], localeCode: Locale['code']) => (
+                    <div className={`${prefixCls}-replace-bar`}>
+                        <Input
+                            placeholder={locale.replace}
+                            className={`${prefixCls}-replace-bar-input`}
+                            onChange={(value, e) => {
+                                e.preventDefault();
+                            }}
+                            ref={this.replaceInputRef}
+                        />
+                        <Button
+                            style={{ width: 'fit-content' }}
+                            disabled={readOnly}
+                            onClick={() => {
+                                const value = this.replaceInputRef.current?.value;
+                                this.foundation.replace(value);
+                            }}
+                        >
+                            {locale.replace}    
+                        </Button>
+                        <Button
+                            style={{ width: 'fit-content' }}
+                            disabled={readOnly}
+                            onClick={() => {
+                                const value = this.replaceInputRef.current?.value;
+                                this.foundation.replaceAll(value);
+                            }}
+                        >
+                            {locale.replaceAll}
+                        </Button>
+                    </div>
+                )}
+            </LocaleConsumer>
         );
     }
-
     render() {
         let isDragging = false;
         const { width, className, style, showSearch = true, ...rest } = this.props;

+ 11 - 2
packages/semi-ui/locale/_story/locale.stories.jsx

@@ -21,6 +21,8 @@ import {
     Image,
     Form,
     Nav,
+    InputNumber,
+    JsonViewer
 } from '../../index';
 
 import zh_CN from '@douyinfe/semi-ui/locale/source/zh_CN';
@@ -327,7 +329,7 @@ const treeData = [
     },
 ];
 
-const I18nComponent2 = () => {
+const I18nComponent2 = (props) => {
     const [modalVisible, setModalVisible] = useState(false);
     const columns = useMemo(() => [
         {
@@ -395,6 +397,8 @@ const I18nComponent2 = () => {
                     <p>More content...</p>
                 </Modal>
             </div>
+            <h5>InputNumber</h5>
+            <InputNumber mode="currency" key={props.localeCode} defaultValue={1234567.89} />
             <h5>Select & Cascader</h5>
             <div style={style}>
                 <Select filter style={{ width: '180px' }}>
@@ -482,6 +486,11 @@ const I18nComponent2 = () => {
                     collapseButton: true,
                 }}
             />
+            <h5>JsonViewer</h5>
+            <JsonViewer height={100} width={700} value={`{
+                "name": "Semi",
+                "version": "0.0.0"
+            }`} />
         </>
     );
 };
@@ -556,7 +565,7 @@ class I18nDemo extends React.Component {
                 </div>
                 <LocaleProvider locale={locale}>
                     <ConfigProvider direction={localeCode === 'ar' ? 'rtl' : 'ltr'} locale={locale}>
-                        <I18nComponent2 />
+                        <I18nComponent2 localeCode={localeCode}/>
                     </ConfigProvider>
                 </LocaleProvider>
             </>

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

@@ -3,6 +3,7 @@ import { Locale as dateFnsLocale } from 'date-fns';
 export interface Locale {
     code: string;
     dateFnsLocale: dateFnsLocale;
+    currency: string;
     Pagination: {
         pageSize: string;
         total: string;
@@ -180,5 +181,18 @@ export interface Locale {
         copy: string;
         copied: string;
         dropAreaText: string
+    };
+    UserGuide: {
+        skip: string;
+        next: string;
+        prev: string;
+        finish: string
+    };
+    InputNumber: {
+    };
+    JsonViewer: {
+        search: string;
+        replace: string;
+        replaceAll: string
     }
 }

+ 3 - 2
packages/semi-ui/locale/localeConsumer.tsx

@@ -7,7 +7,7 @@ import ConfigContext from '../configProvider/context';
 import DefaultLocale from './source/zh_CN';
 import { Locale } from './interface';
 
-type ChildrenRender<T> = (componentLocal: T, localeCode: string, dateFnsLocale: dateFns) => React.ReactNode;
+type ChildrenRender<T> = (componentLocal: T, localeCode: string, dateFnsLocale: dateFns, currency: string) => React.ReactNode;
 export interface LocaleConsumerProps<T> {
     componentName: string;
     children?: ChildrenRender<T>
@@ -40,7 +40,8 @@ export default class LocaleConsumer<T> extends Component<LocaleConsumerProps<T>>
          */
         const defaultFnsLocale = get(DefaultLocale, 'dateFnsLocale');
         const dateFnsLocale = get(locale, 'dateFnsLocale', defaultFnsLocale);
-        return children(locale[componentName], locale.code, dateFnsLocale);
+        const currency = get(locale, 'currency');  
+        return children(locale[componentName], locale.code, dateFnsLocale, currency);
     }
 
     render() {

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ar',
     dateFnsLocale: arSA,
+    currency: 'SAR',
     Pagination: {
         pageSize: 'العناصر في كل صفحة: ${pageSize}',
         total: 'إجمالي الصفحات: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'نسخ',
         dropAreaText: 'ضع الملف هنا',
     },
+    UserGuide: {
+        skip: 'تخطي',
+        next: 'التالي',
+        prev: 'السابق',
+        finish: 'إنهاء',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'بحث',
+        replace: 'استبدل',
+        replaceAll: 'استبدل الكل',
+    },
 };
 
 // [i18n-Arabic]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'de',
     dateFnsLocale: de,
+    currency: 'EUR',
     Pagination: {
         pageSize: 'Elemente pro Seite: ${pageSize}',
         total: 'Seiten gesamt: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Kopiert',
         dropAreaText: 'Datei hier ablegen',
     },
+    UserGuide: {
+        skip: 'Überspringen',
+        next: 'Weiter',
+        prev: 'Zurück',
+        finish: 'Fertig',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Suchen',
+        replace: 'Ersetzen',
+        replaceAll: 'Alle ersetzen',
+    },
 };
 
 // [i18n-German]

+ 14 - 1
packages/semi-ui/locale/source/en_GB.ts

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'en-GB',
     dateFnsLocale: enGB,
+    currency: 'GBP',
     Pagination: {
         pageSize: 'Items per page: ${pageSize}',
         total: 'Total pages: ${total}',
@@ -181,7 +182,19 @@ const local: Locale = {
         copy: 'Copy',
         copied: 'Copied',
         dropAreaText: 'Put the file here',
-    }
+    },
+    UserGuide: {
+        skip: 'Skip',
+        next: 'Next',
+        prev: 'Prev',
+        finish: 'Finish',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Search',
+        replace: 'Replace',
+        replaceAll: 'Replace All',
+    },
 };
 
 // [i18n-English(GB)]

+ 14 - 1
packages/semi-ui/locale/source/en_US.ts

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'en-US',
     dateFnsLocale: enUS,
+    currency: 'USD',
     Pagination: {
         pageSize: 'Items per page: ${pageSize}',
         total: 'Total pages: ${total}',
@@ -181,7 +182,19 @@ const local: Locale = {
         copy: 'Copy',
         copied: 'Copied',
         dropAreaText: 'Put the file here',
-    }
+    },
+    UserGuide: {
+        skip: 'Skip',
+        next: 'Next',
+        prev: 'Prev',
+        finish: 'Finish',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Search',
+        replace: 'Replace',
+        replaceAll: 'Replace All',
+    },
 };
 
 // [i18n-English(US)]

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

@@ -9,6 +9,7 @@ import { Locale } from '../interface';
 const locale: Locale = {
     code: 'es',
     dateFnsLocale: es,
+    currency: 'EUR',
     Pagination: {
         pageSize: 'Elementos por página: ${pageSize}',
         total: 'Páginas totales: ${total}',
@@ -187,6 +188,18 @@ const locale: Locale = {
         copied: 'Copiado',
         dropAreaText: 'Coloca el archivo aquí',
     },
+    UserGuide: {
+        skip: 'Omitir',
+        next: 'Siguiente',
+        prev: 'Anterior',
+        finish: 'Finalizar',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Buscar',
+        replace: 'Reemplazar',
+        replaceAll: 'Reemplazar todo',
+    },
 };
 
 export default locale;

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'fr',
     dateFnsLocale: fr,
+    currency: 'EUR',
     Pagination: {
         pageSize: 'Éléments par page : ${pageSize}',
         total: 'Total des pages : ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Copié',
         dropAreaText: 'Déposez le fichier ici',
     },
+    UserGuide: {
+        skip: 'Passer',
+        next: 'Suivant',
+        prev: 'Précédent',
+        finish: 'Terminer',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Rechercher',
+        replace: 'Remplacer',
+        replaceAll: 'Remplacer tout',
+    },
 };
 
 // [i18n-French]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'id-ID',
     dateFnsLocale: id,
+    currency: 'IDR',
     Pagination: {
         pageSize: 'Item per halaman: ${pageSize}',
         total: 'Total halaman: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Disalin',
         dropAreaText: 'Letakkan file di sini',
     },
+    UserGuide: {
+        skip: 'Lewati',
+        next: 'Selanjutnya',
+        prev: 'Sebelumnya',
+        finish: 'Selesai',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Cari',
+        replace: 'Ganti',
+        replaceAll: 'Ganti Semua',
+    },
 };
 
 // [i18n-Indonesia(ID)]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'it',
     dateFnsLocale: it,
+    currency: 'EUR',
     Pagination: {
         pageSize: 'Elementi per pagina: ${pageSize}',
         total: 'Pagine totali: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Copiato',
         dropAreaText: 'Metti il file qui',
     },
+    UserGuide: {
+        skip: 'Salta',
+        next: 'Avanti',
+        prev: 'Indietro',
+        finish: 'Fine',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Cerca',
+        replace: 'Sostituisci',
+        replaceAll: 'Sostituisci tutto',
+    },
 };
 
 // [i18n-Italian]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ja-JP',
     dateFnsLocale: ja,
+    currency: 'JPY',
     Pagination: {
         pageSize: '1ページあたりのアイテム数:${pageSize}',
         total: '合計ページ数:${total}',
@@ -183,6 +184,18 @@ const local: Locale = {
         copied: 'コピーしました',
         dropAreaText: 'ファイルをここに置いてください',
     },
+    UserGuide: {
+        skip: 'スキップ',
+        next: '次へ',
+        prev: '前へ',
+        finish: '完了',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: '検索',
+        replace: '置換',
+        replaceAll: 'すべて置換',
+    },
 };
 
 // [i18n-Japan]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ko-KR',
     dateFnsLocale: ko,
+    currency: 'KRW',
     Pagination: {
         pageSize: '페이지당 항목: ${pageSize}',
         total: '총 페이지: ${total}',
@@ -183,6 +184,18 @@ const local: Locale = {
         copied: '복사했습니다',
         dropAreaText: '파일을 여기에 놓으세요',
     },
+    UserGuide: {
+        skip: '건너뛰기',
+        next: '다음',
+        prev: '이전',
+        finish: '완료',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: '검색',
+        replace: '교체',
+        replaceAll: '모두 교체',
+    },
 };
 
 // [i18n-Korea]

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ms-MY',
     dateFnsLocale: ms,
+    currency: 'MYR',
     Pagination: {
         pageSize: 'Item setiap halaman: ${pageSize}',
         total: 'Jumlah halaman: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Disalin',
         dropAreaText: 'Letakkan fail di sini',
     },
+    UserGuide: {
+        skip: 'Lewati',
+        next: 'Selanjutnya',
+        prev: 'Sebelumnya',
+        finish: 'Selesai',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Cari',
+        replace: 'Ganti',
+        replaceAll: 'Ganti Semua',
+    },
 };
 
 // [i18n-Malaysia(MY)]

+ 14 - 1
packages/semi-ui/locale/source/nl_NL.ts

@@ -9,8 +9,9 @@ import { Locale } from '../interface';
  */
 
 const local: Locale = {
-    code: 'nl_NL',
+    code: 'nl-NL',
     dateFnsLocale: nl, // locale code to dateFns locale
+    currency: 'EUR',
     Pagination: {
         page: 'pagina',
         pageSize: 'Items per pagina: ${pageSize}',
@@ -189,6 +190,18 @@ const local: Locale = {
         copied: 'Gekopieerd',
         dropAreaText: 'Plaats het bestand hier',
     },
+    UserGuide: {
+        skip: 'Overslaan',
+        next: 'Volgende',
+        prev: 'Vorige',
+        finish: 'Voltooien',
+    },  
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Zoeken',
+        replace: 'Vervangen',
+        replaceAll: 'Alle vervangen',
+    },
 };
 
 export default local;

+ 14 - 1
packages/semi-ui/locale/source/pl_PL.ts

@@ -10,8 +10,9 @@ import { Locale } from '../interface';
  */
 
 const local: Locale = {
-    code: 'pl_PL',
+    code: 'pl-PL',
     dateFnsLocale: pl, // locale code to dateFns locale
+    currency: 'PLN',
     Pagination: {
         pageSize: 'Liczba pozycji na stronie: ${pageSize}',
         total: 'Strony ogółem: ${total}',
@@ -190,6 +191,18 @@ const local: Locale = {
         copied: 'Skopiowano',
         dropAreaText: 'Umieść plik tutaj',
     },
+    UserGuide: {
+        skip: 'Pomiń',
+        next: 'Następny',
+        prev: 'Poprzedni',
+        finish: 'Zakończ',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Szukaj',
+        replace: 'Zastąp',
+        replaceAll: 'Zastąp wszystko',
+    },
 };
 
 export default local;

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'pt-BR',
     dateFnsLocale: ptBR,
+    currency: 'BRL',
     Pagination: {
         pageSize: 'Itens por página: ${pageSize}',
         total: 'Total de páginas: ${total}',
@@ -190,6 +191,18 @@ const local: Locale = {
         copied: 'Cópia bem sucedida',
         dropAreaText: 'Coloque o arquivo aqui',
     },
+    UserGuide: {
+        skip: 'Pular',
+        next: 'Próximo',
+        prev: 'Anterior',
+        finish: 'Finalizar',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Pesquisar',
+        replace: 'Substituir',
+        replaceAll: 'Substituir tudo',
+    },
 };
 
 // 葡萄牙语

+ 13 - 0
packages/semi-ui/locale/source/ro.ts

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ro',
     dateFnsLocale: ro,
+    currency: 'RON',
     Pagination: {
         pageSize: 'Elemente per pagină: ${pageSize}',
         total: 'Total pagini: ${total}',
@@ -182,6 +183,18 @@ const local: Locale = {
         copied: 'Copiat',
         dropAreaText: 'Puneți fișierul aici',
     },
+    UserGuide: {
+        skip: 'Omite',
+        next: 'Următorul',
+        prev: 'Anterior',
+        finish: 'Finalizare',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Căutare',
+        replace: 'Înlocuiește',
+        replaceAll: 'Înlocuiește toate',
+    },
 };
 
 // [i18n-Romanian] 罗马尼亚语

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'ru-RU',
     dateFnsLocale: ru,
+    currency: 'RUB',
     Pagination: {
         pageSize: 'Позиции на странице: ${pageSize}',
         total: 'Всего страниц: ${total}',
@@ -185,6 +186,18 @@ const local: Locale = {
         copied: 'Скопировано',
         dropAreaText: 'Положите файл здесь',
     },
+    UserGuide: {
+        skip: 'Пропустить',
+        next: 'Следующий',
+        prev: 'Предыдущий',
+        finish: 'Завершить',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Поиск',
+        replace: 'Заменить',
+        replaceAll: 'Заменить все',
+    },
 };
 
 // [i18n-Russia] 俄罗斯语

+ 14 - 1
packages/semi-ui/locale/source/sv_SE.ts

@@ -7,8 +7,9 @@ import { Locale } from '../interface';
  */
 
 const local: Locale = {
-    code: 'sv_SE',
+    code: 'sv-SE',
     dateFnsLocale: sv, 
+    currency: 'SEK',
     Pagination: {
         pageSize: 'Artiklar per sida: ${pageSize}',
         total: 'Totalt antal sidor: ${total}',
@@ -187,6 +188,18 @@ const local: Locale = {
         copied: 'Kopierad',
         dropAreaText: 'Placera filen här',   
     }, 
+    UserGuide: {
+        skip: 'Hoppa över',
+        next: 'Nästa',
+        prev: 'Föregående',
+        finish: 'Slutför',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Sök',
+        replace: 'Ersätt',
+        replaceAll: 'Ersätt alla',
+    },
 };
 
 export default local;

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'th-TH',
     dateFnsLocale: th,
+    currency: 'THB',
     Pagination: {
         pageSize: 'รายการต่อหน้า: ${pageSize}',
         total: 'หน้าทั้งหมด: ${total}',
@@ -186,6 +187,18 @@ const local: Locale = {
         copied: 'คัดลอกสำเร็จ',
         dropAreaText: 'วางไฟล์ที่นี่',
     },
+    UserGuide: {
+        skip: 'ข้าม',
+        next: 'ถัดไป',
+        prev: 'ก่อนหน้า',
+        finish: 'สำเร็จ',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'ค้นหา',
+        replace: 'แทนที่',
+        replaceAll: 'แทนที่ทั้งหมด',
+    },
 };
 
 // [i18n-Thai]

+ 13 - 0
packages/semi-ui/locale/source/tr_TR.ts

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'tr-TR',
     dateFnsLocale: tr,
+    currency: 'TRY',
     Pagination: {
         page: 'Sayfa',
         pageSize: 'Sayfa başı öğe: ${pageSize}',
@@ -183,6 +184,18 @@ const local: Locale = {
         copied: 'Kopyalama başarılı',
         dropAreaText: 'Dosyayı buraya yerleştirin',
     },
+    UserGuide: {
+        skip: 'Atla',
+        next: 'Sonraki',
+        prev: 'Önceki',
+        finish: 'Tamamla',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Ara',
+        replace: 'Değiştir',
+        replaceAll: 'Tümünü değiştir',
+    },
 };
 
 // [i18n-Turkish] 

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'vi-VN',
     dateFnsLocale: vi,
+    currency: 'VND',
     Pagination: {
         pageSize: 'Số mục mỗi trang: ${pageSize}',
         total: 'Tổng số trang: ${total}',
@@ -185,6 +186,18 @@ const local: Locale = {
         copied: 'Đã sao chép',
         dropAreaText: 'Đặt tệp vào đây',
     }, 
+    UserGuide: {
+        skip: 'Bỏ qua',
+        next: 'Tiếp theo',
+        prev: 'Trước đó',
+        finish: 'Hoàn tất',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: 'Tìm kiếm',
+        replace: 'Thay thế',
+        replaceAll: 'Thay thế tất cả',
+    },
 };
 
 // [i18n-Vietnam] 越南语

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

@@ -3,6 +3,7 @@ import { Locale } from '../interface';
 
 const local: Locale = {
     code: 'zh-CN',
+    currency: 'CNY',
     dateFnsLocale: zhCN, // locale code to dateFns locale
     Pagination: {
         pageSize: '每页条数:${pageSize}',
@@ -183,6 +184,18 @@ const local: Locale = {
         copied: '复制成功',
         dropAreaText: '将文件放到这里',
     },
+    UserGuide: {
+        skip: '跳过',
+        next: '下一步',
+        prev: '上一步',
+        finish: '完成',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: '查找',
+        replace: '替换',
+        replaceAll: '全部替换',
+    },
 };
 
 // 中文

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

@@ -4,6 +4,7 @@ import { Locale } from '../interface';
 const local: Locale = {
     code: 'zh-TW',
     dateFnsLocale: zhTW, // locale code to dateFns locale
+    currency: 'TWD',
     Pagination: {
         pageSize: '每頁項目數:${pageSize}',
         total: '總頁數:${total}',
@@ -183,6 +184,18 @@ const local: Locale = {
         copied: '複制成功',
         dropAreaText: '將文件放到這裡',
     },
+    UserGuide: {
+        skip: '跳過',
+        next: '下一步',
+        prev: '上一步',
+        finish: '完成',
+    },
+    InputNumber: {},
+    JsonViewer: {
+        search: '查找',
+        replace: '替換',
+        replaceAll: '全部替換',
+    },
 };
 
 // 中文

+ 58 - 0
packages/semi-ui/markdownRender/__test__/markdown.test.js

@@ -42,4 +42,62 @@ describe(`MarkdownRender`, () => {
         expect(render.exists(`.${BASE_CLASS_PREFIX}-table-thead`)).toEqual(true);
         expect(render.exists(`.${BASE_CLASS_PREFIX}-table-tbody`)).toEqual(true);
     });
+
+    it(`test table with bold header`, async () => {
+        const content = `
+        | Name | **Brand** | Count | **Price** |
+        | - | :- | -: | :-: |
+        | Book | Semi | 10 | ¥100 |
+        | Pen | Semi Design | 20 | ¥200 |
+        `;
+
+        const render = mount(
+            <MarkdownRender raw={content} />
+        );
+
+        // check if has table container
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-container`)).toEqual(true);
+        // check if has table head & body
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-thead`)).toEqual(true);
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-tbody`)).toEqual(true);
+        // check has row is two
+        expect(render.find(`.${BASE_CLASS_PREFIX}-table-tbody .${BASE_CLASS_PREFIX}-table-row`).length).toBe(2);
+        // exist 'Semi Design' text
+        expect(render.contains('Semi Design')).toEqual(true);
+        // exist 'Semi' text
+        expect(render.contains('Semi')).toEqual(true);
+        // exist '¥100' text
+        expect(render.contains('¥100')).toEqual(true);
+        // exist '¥200' text
+        expect(render.contains('¥200')).toEqual(true);
+    });
+
+    it(`test table with bold and component header`, async () => {
+        const content = `
+        | Name | <h1>Brand</h1> | Count | **Price** |
+        | - | :- | -: | :-: |
+        | Book | Semi | 10 | ¥100 |
+        | Pen | Semi Design | 20 | ¥200 |
+        `;
+
+        const render = mount(
+            <MarkdownRender raw={content} format="mdx"/>
+        );
+
+        // check if has table container
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-container`)).toEqual(true);
+        // check if has table head & body
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-thead`)).toEqual(true);
+        expect(render.exists(`.${BASE_CLASS_PREFIX}-table-tbody`)).toEqual(true);
+        // check has row is two
+        expect(render.find(`.${BASE_CLASS_PREFIX}-table-tbody .${BASE_CLASS_PREFIX}-table-row`).length).toBe(2);
+        // exist 'Semi Design' text
+        expect(render.contains('Semi Design')).toEqual(true);
+        // exist 'Semi' text
+        expect(render.contains('Semi')).toEqual(true);
+        // exist '¥100' text
+        expect(render.contains('¥100')).toEqual(true);
+        // exist '¥200' text
+        expect(render.contains('¥200')).toEqual(true);
+    });
 });

+ 33 - 0
packages/semi-ui/markdownRender/_story/markdownRender.stories.jsx

@@ -35,6 +35,39 @@ export const Table = ()=>{
     `} components={semiComponents}/>
 }
 
+export const TableWithBoldHeader = ()=>{
+    return <MarkdownRender raw={`
+| a | **b**  |  c |  **d**  |
+| - | :- | -: | :-: |
+| 1 | 2 | 3 | 4 |
+| 11 | 22 | 33 | 44 |
+| 111 | 222 | 333 | 444 |
+| 1111 | 2222 | 3333 | 4444 |
+    `} components={semiComponents}/>
+}
+
+export const TableWithComponentHeader = ()=>{
+    return <MarkdownRender raw={`
+| a | <h1>b</h1>  |  c |  **d**  |
+| - | :- | -: | :-: |
+| 1 | 2 | 3 | 4 |
+| 11 | 22 | 33 | 44 |
+| 111 | 222 | 333 | 444 |
+| 1111 | 2222 | 3333 | 4444 |
+    `} components={semiComponents} format="mdx"/>
+}
+
+export const TableWithComponent = ()=>{
+    return <MarkdownRender raw={`
+| a | <h1>b</h1>  |  c |  **d**  |
+| - | :- | -: | :-: |
+| 1 | 2 | 3 | 4 |
+| 11 | <h4>22</h4> | <h3>33</h3> | <h2>44</h2> |
+| <h3>111</h3> | ![Semi Design](https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/colorful.jpg) | 333 | <h2>444</h2> |
+| 1111 | 2222 | 3333 | 4444 |
+    `} components={semiComponents} format="mdx"/>
+}
+
 export const WithSymbol = ()=>{
     return <MarkdownRender raw={`
 test \\\\{ cxode } test

+ 8 - 9
packages/semi-ui/markdownRender/components/table.tsx

@@ -13,25 +13,24 @@ const table = (props: PropsWithChildren<TableProps>) => {
     const columnsFiber = toArray(get(children[0], 'props.children.props.children'));
     const dataFiber = toArray(get(children[1], 'props.children'));
 
-    const titles: string[] = columnsFiber.map(item => item?.props?.children || "");
+    const titlesColumns = columnsFiber.map((column, i) => {
+        return {
+            dataIndex: String(i),
+            title: column?.props?.children || ""
+        };
+    });
     const tableDataSource: any[] = [];
     for (let i = 0;i < dataFiber.length;i++) {
         let item: Record<string, string> = {
             key: String(i)
         };
         dataFiber[i]?.props.children?.forEach?.((child, index) => {
-            item[titles[index]] = child?.props?.children ?? "";
+            item[String(index)] = child?.props?.children ?? "";
         });
         tableDataSource.push(item);
     }
 
-
-    return <Table dataSource={tableDataSource} columns={titles.map(title => {
-        return {
-            title,
-            dataIndex: title
-        };
-    })} {...omit(props, 'children')}/>;
+    return <Table dataSource={tableDataSource} columns={titlesColumns} {...omit(props, 'children')}/>;
 };
 
 export default table;

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

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-ui",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.",
     "main": "lib/cjs/index.js",
     "module": "lib/es/index.js",
@@ -20,12 +20,12 @@
         "@dnd-kit/core": "^6.0.8",
         "@dnd-kit/sortable": "^7.0.2",
         "@dnd-kit/utilities": "^3.2.1",
-        "@douyinfe/semi-animation": "2.76.1",
-        "@douyinfe/semi-animation-react": "2.76.1",
-        "@douyinfe/semi-foundation": "2.76.1",
-        "@douyinfe/semi-icons": "2.76.1",
-        "@douyinfe/semi-illustrations": "2.76.1",
-        "@douyinfe/semi-theme-default": "2.76.1",
+        "@douyinfe/semi-animation": "2.77.0",
+        "@douyinfe/semi-animation-react": "2.77.0",
+        "@douyinfe/semi-foundation": "2.77.0",
+        "@douyinfe/semi-icons": "2.77.0",
+        "@douyinfe/semi-illustrations": "2.77.0",
+        "@douyinfe/semi-theme-default": "2.77.0",
         "async-validator": "^3.5.0",
         "classnames": "^2.2.6",
         "copy-text-to-clipboard": "^2.1.1",

+ 610 - 0
packages/semi-ui/userGuide/_story/userGuide.stories.jsx

@@ -0,0 +1,610 @@
+/* argus-disable unPkgSensitiveInfo */
+import React, { useState } from 'react';
+import { Button, UserGuide, Toast, Tag, Switch, Space } from '@douyinfe/semi-ui/index';
+
+export default {
+    title: 'UserGuide',
+};
+
+export const BasicUsage = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <br />
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary" onClick={() => {
+                    console.log('次要')
+                }}>次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        cover: (
+                            <img 
+                                alt="example"
+                                height={200} 
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                    {
+                        target: document.querySelector('#step-3'),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-4'),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+};
+
+export const PrimaryTheme = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                theme="primary"
+                mask={true}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                    {
+                        target: document.querySelector('#step-3'),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-4'),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+};
+
+export const HideButtons = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                showPrevButton={false}
+                showSkipButton={false}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        cover: (
+                            <img 
+                                alt="example"
+                                height={200}
+                                width={'100%'} 
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                    {
+                        target: document.querySelector('#step-3'),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-4'),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+};
+
+export const NoMask = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                theme="primary"
+                mask={false}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                    {
+                        target: document.querySelector('#step-3'),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-4'),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+};
+
+export const ControlledDemo = () => {
+    const [visible, setVisible] = useState(false);
+    const [current, setCurrent] = useState(1);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                theme="primary"
+                current={current}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={200}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                    {
+                        target: document.querySelector('#step-3'),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-4'),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onPrev={(current) => {
+                    console.log('onPrev', current)
+                    setCurrent(current - 1);
+                }}
+                onNext={(current) => {
+                    console.log('onNext', current)
+                    setCurrent(current + 1);
+                }}
+                onFinish={(current) => {
+                    console.log('onFinish', current)
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('引导完成')
+                }}
+                onSkip={(current) => {
+                    console.log('onSkip', current)
+                    setVisible(false);
+                    setCurrent(0);
+                    console.log('引导跳过')
+                }}
+                onChange={(current) => {
+                    console.log('onChange', current)
+                    console.log('changel', current)
+                }}
+        />
+        </div>
+    );
+};
+
+export const ModalMode = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="modal"
+                mask={true}
+                steps={[
+                    {
+                        cover: (
+                            <img 
+                                alt="example" 
+                                height={'100%'}
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                    },
+                    {
+                        cover: (
+                            <img 
+                                alt="example"
+                                height={'100%'} 
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo2.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                    },
+                    {
+                        cover: (
+                            <img 
+                                alt="example"
+                                height={'100%'} 
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo2.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题3",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                    },
+                    {
+                        cover: (
+                            <img 
+                                alt="example"
+                                height={'100%'} 
+                                width={'100%'}
+                                src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/card-cover-docs-demo2.jpeg" 
+                            />
+                        ),
+                        title: "这里是标题4",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+};
+
+export const CustomButton = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                finishText='开始使用'
+                prevButtonProps={{ theme: 'outline', type: 'primary' }}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        title: "这里是标题1",
+                        description: "一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+}
+
+export const MixedTheme = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        theme: 'primary',
+                        title: "这里是标题1",
+                        description: "一些描述文案",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        title: "这里是标题2",
+                        description: "这是第二步的说明一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案一些描述文案",
+                        position: "right"
+                    },
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+        />
+        </div>
+    );
+}
+
+export const OneStep = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Space spacing='medium'>
+                <Switch id={'step-1'} defaultChecked={true}/>     
+                <Button id={'step-2'} type="secondary">次要</Button>
+                <Button id={'step-3'} type="tertiary">第三</Button>
+                <Button id={'step-4'} type="warning">警告</Button>
+            </Space>
+            <div style={{ marginTop: '20px' }}>
+                <Button onClick={() => setVisible(true)}>显示引导</Button>
+            </div>
+            <UserGuide
+                mode="popup"
+                mask={true}
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        theme: 'primary',
+                        title: "这里是标题",
+                        description: "一些描述文案",
+                        position: "bottom"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+                onSkip={() => {
+                    setVisible(false);
+                    console.log('引导跳过')
+                }}
+            />
+        </div>
+    )
+}

+ 42 - 0
packages/semi-ui/userGuide/_story/userGuide.stories.tsx

@@ -0,0 +1,42 @@
+/* argus-disable unPkgSensitiveInfo */
+import React, { useState } from 'react';
+import { Button, UserGuide, Toast, Tag, Switch, SideSheet } from '@douyinfe/semi-ui/index';
+
+export default {
+    title: 'UserGuide',
+};
+
+export const BasicUsage = () => {
+    const [visible, setVisible] = useState(false);
+
+    return (
+        <div>
+            <Button onClick={() => setVisible(true)}>显示引导</Button>
+            <div className="step-1">第一步目标</div>
+            <div className="step-2">第二步目标</div>
+            <UserGuide
+                mode="popup"
+                steps={[
+                    {
+                        target: document.querySelector('#step-1'),
+                        title: "第一步",
+                        description: "这是第一步的说明",
+                        position: "bottom"
+                    },
+                    {
+                        target: document.querySelector('#step-2'),
+                        title: "第二步",
+                        description: "这是第二步的说明",
+                        position: "right"
+                    }
+                ]}
+                visible={visible}
+                onFinish={() => {
+                    setVisible(false);
+                    console.log('引导完成')
+                }}
+        />
+        </div>
+    );
+};
+

+ 545 - 0
packages/semi-ui/userGuide/index.tsx

@@ -0,0 +1,545 @@
+import React, { ReactNode } from 'react';
+import cls from 'classnames';
+import PropTypes from 'prop-types';
+import { cssClasses, numbers, strings } from '@douyinfe/semi-foundation/userGuide/constants';
+import UserGuideFoundation, { UserGuideAdapter } from '@douyinfe/semi-foundation/userGuide/foundation';
+import { Position } from '../tooltip/index';
+import BaseComponent from '../_base/baseComponent';
+import Popover from '../popover';
+import Button, { ButtonProps } from '../button';
+import Modal from '../modal';
+import { noop } from '@douyinfe/semi-foundation/utils/function';
+import '@douyinfe/semi-foundation/userGuide/userGuide.scss';
+import { BaseProps } from '../_base/baseComponent';
+import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
+import { getUuidShort } from '@douyinfe/semi-foundation/utils/uuid';
+import { Locale } from '../locale/interface';
+import LocaleConsumer from '../locale/localeConsumer';
+import { getScrollbarWidth } from '../_utils';
+
+
+const prefixCls = cssClasses.PREFIX;
+
+export interface UserGuideProps extends BaseProps {
+    className?: string;
+    current?: number;
+    finishText?: string;
+    mask?: boolean;
+    mode?: 'popup' | 'modal';
+    nextButtonProps?: ButtonProps;
+    onChange?: (current: number) => void;
+    onFinish?: () => void;
+    onNext?: (current: number) => void;
+    onPrev?: (current: number) => void;
+    onSkip?: () => void;
+    position?: Position;
+    prevButtonProps?: ButtonProps;
+    showPrevButton?: boolean;
+    showSkipButton?: boolean;
+    spotlightPadding?: number;
+    steps: StepItem[];
+    style?: React.CSSProperties;
+    theme?: 'default' | 'primary';
+    visible?: boolean;
+    getPopupContainer?: () => HTMLElement;
+    zIndex?: number
+}
+
+export interface StepItem {
+    className?: string;
+    cover?: ReactNode;
+    target?: (() => Element) | Element;
+    title?: string | ReactNode;
+    description?: React.ReactNode;
+    mask?: boolean;
+    showArrow?: boolean;
+    spotlightPadding?: number;
+    theme?: 'default' | 'primary';
+    position?: Position
+}
+
+export interface UserGuideState {
+    current: number;
+    spotlightRect: DOMRect | null
+}
+
+class UserGuide extends BaseComponent<UserGuideProps, UserGuideState> {
+    static propTypes = {
+        mask: PropTypes.bool,
+        mode: PropTypes.oneOf(strings.MODE),
+        onChange: PropTypes.func,
+        onFinish: PropTypes.func,
+        onNext: PropTypes.func,
+        onPrev: PropTypes.func,
+        onSkip: PropTypes.func,
+        position: PropTypes.oneOf(strings.POSITION_SET),
+        showPrevButton: PropTypes.bool,
+        showSkipButton: PropTypes.bool,
+        theme: PropTypes.oneOf(strings.THEME),
+        visible: PropTypes.bool,
+        getPopupContainer: PropTypes.func,
+        zIndex: PropTypes.number,
+    };
+
+    static defaultProps: UserGuideProps = {
+        mask: true,
+        mode: 'popup',
+        nextButtonProps: {},
+        onChange: noop,
+        onFinish: noop,
+        onNext: noop,
+        onPrev: noop,
+        onSkip: noop,
+        position: 'bottom',
+        prevButtonProps: {},
+        showPrevButton: true,
+        showSkipButton: true,
+        steps: [],
+        theme: 'default',
+        visible: false,
+        zIndex: numbers.DEFAULT_Z_INDEX,
+    };
+
+    private bodyOverflow: string;
+    private scrollBarWidth: number;
+    private originBodyWidth: string;
+    foundation: UserGuideFoundation;
+    userGuideId: string;
+
+    constructor(props: UserGuideProps) {
+        super(props);
+        this.foundation = new UserGuideFoundation(this.adapter);
+        this.state = {
+            current: props.current || numbers.DEFAULT_CURRENT,
+            spotlightRect: null,
+        };
+        this.scrollBarWidth = 0;
+        this.userGuideId = '';
+    }
+
+    get adapter(): UserGuideAdapter<UserGuideProps, UserGuideState> {
+        return {
+            ...super.adapter,
+            disabledBodyScroll: () => {
+                const { getPopupContainer } = this.props;
+                this.bodyOverflow = document.body.style.overflow || '';
+                if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
+                    document.body.style.overflow = 'hidden';
+                    document.body.style.width = `calc(${this.originBodyWidth || '100%'} - ${this.scrollBarWidth}px)`;
+                }
+            },
+            enabledBodyScroll: () => {
+                const { getPopupContainer } = this.props;
+                if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
+                    document.body.style.overflow = this.bodyOverflow;
+                    document.body.style.width = this.originBodyWidth;
+                }
+            },
+            notifyChange: (current: number) => {
+                this.props.onChange(current);
+            },
+            notifyFinish: () => {
+                this.props.onFinish();
+            },
+            notifyNext: (current: number) => {
+                this.props.onNext(current);
+            },
+            notifyPrev: (current: number) => {
+                this.props.onPrev(current);
+            },
+            notifySkip: () => {
+                this.props.onSkip();
+            },
+            setCurrent: (current: number) => {
+                this.setState({ current });
+            }
+        };
+    }
+
+    static getDerivedStateFromProps(props: UserGuideProps, state: UserGuideState): Partial<UserGuideState> {
+        const states: Partial<UserGuideState> = {};
+        if (!isNullOrUndefined(props.current) && props.current !== state.current) {
+            states.current = props.current;
+        }
+        return states;
+    }
+
+    componentDidMount() {
+        this.foundation.init();
+        this.scrollBarWidth = getScrollbarWidth();
+        this.userGuideId = getUuidShort();
+    }
+
+    componentDidUpdate(prevProps: UserGuideProps, prevStates: UserGuideState) {
+        const { steps, mode, visible } = this.props;
+        const { current } = this.state;
+
+        if (visible !== prevProps.visible) {
+            if (visible) {
+                this.foundation.beforeShow();
+                this.setState({ current: 0 });
+            } else {
+                this.foundation.afterHide();
+            }
+        }
+
+        if (mode === 'popup' && (prevStates.current !== current) && steps[current] || (prevProps.visible !== visible)) {
+            this.updateSpotlightRect();
+        }
+    }
+
+    componentWillUnmount() {
+        this.foundation.destroy();
+    }
+
+    scrollTargetIntoViewIfNeeded(target: Element) {
+        if (!target) {
+            return ;
+        }
+        
+        const rect = target.getBoundingClientRect();
+        const isInViewport = 
+            rect.top >= 0 &&
+            rect.left >= 0 &&
+            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
+            rect.right <= (window.innerWidth || document.documentElement.clientWidth);
+        
+        if (!isInViewport) {
+            target.scrollIntoView({
+                behavior: 'auto',
+                block: 'center'
+            });
+
+        }
+    }
+
+
+    async updateSpotlightRect() {
+        const { steps, spotlightPadding } = this.props;
+        const { current } = this.state;
+        const step = steps[current];
+
+        if (step.target) {
+            const target = typeof step.target === 'function' ? step.target() : step.target;
+            // Checks if the target element is within the viewport, and scrolls it into view if not
+            this.scrollTargetIntoViewIfNeeded(target);
+
+            const rect = target?.getBoundingClientRect();
+            const padding = step?.spotlightPadding || spotlightPadding || numbers.DEFAULT_SPOTLIGHT_PADDING;
+
+            const newRects = new DOMRect(
+                rect.x - padding,
+                rect.y - padding,
+                rect.width + padding * 2,
+                rect.height + padding * 2
+            );
+
+            requestAnimationFrame(() => {
+                this.setState({ spotlightRect: newRects });
+            });
+        }
+    }
+
+    renderPopupContent(step: StepItem, index: number) {
+        const { showPrevButton, showSkipButton, theme, steps, finishText, nextButtonProps, prevButtonProps } = this.props;
+        const { current } = this.state;
+
+        const isFirst = index === 0;
+        const isLast = index === steps.length - 1;
+        const popupPrefixCls = `${prefixCls}-popup-content`;
+        const isPrimaryTheme = theme === 'primary' || step?.theme === 'primary';
+        const { cover, title, description } = step;
+
+        return (
+            <LocaleConsumer componentName="UserGuide">
+                {(locale: Locale['UserGuide'], localeCode: Locale['code']) => (
+                    <div className={cls(`${popupPrefixCls}`, {
+                        [`${popupPrefixCls}-primary`]: isPrimaryTheme,
+                    })}
+                    >
+                        {cover && <div className={`${popupPrefixCls}-cover`}>{cover}</div>}
+                        <div className={`${popupPrefixCls}-body`}>
+                            {title && <div className={`${popupPrefixCls}-title`}>{title}</div>}
+                            {description && <div className={`${popupPrefixCls}-description`}>{description}</div>}
+                            <div className={`${popupPrefixCls}-footer`}>
+                                {steps.length > 1 && (
+                                    <div className={`${popupPrefixCls}-indicator`}>
+                                        {current + 1}/{steps.length}
+                                    </div>
+                                )}
+                                <div className={`${popupPrefixCls}-buttons`}>
+                                    {showSkipButton && !isLast && (
+                                        <Button 
+                                            style={isPrimaryTheme ? { backgroundColor: 'var(--semi-color-fill-2)' } : {}}
+                                            theme={isPrimaryTheme ? 'solid' : 'light'} 
+                                            type={isPrimaryTheme ? 'primary' : 'tertiary'} 
+                                            onClick={this.foundation.handleSkip}
+                                        >
+                                            {locale.skip}
+                                        </Button>
+                                    )}
+                                    {showPrevButton && !isFirst && (
+                                        <Button 
+                                            style={isPrimaryTheme ? { backgroundColor: 'var(--semi-color-fill-2)' } : {}}
+                                            theme={isPrimaryTheme ? 'solid' : 'light'} 
+                                            type={isPrimaryTheme ? 'primary' : 'tertiary'} 
+                                            onClick={this.foundation.handlePrev}
+                                            {...prevButtonProps}
+                                        >
+                                            {prevButtonProps?.children || locale.prev}
+                                        </Button>
+                                    )}
+                                    <Button 
+                                        style={isPrimaryTheme ? { backgroundColor: '#FFF' } : {}}
+                                        theme={isPrimaryTheme ? 'borderless' : 'solid'} 
+                                        type={'primary'} 
+                                        onClick={this.foundation.handleNext}
+                                        {...nextButtonProps}
+                                    >
+                                        {isLast ? (finishText || locale.finish) : (nextButtonProps?.children || locale.next)}
+                                    </Button>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                )}
+            </LocaleConsumer>
+        );
+    }
+
+    renderStep = (step: StepItem, index: number) => {
+        const { theme, position, visible, className, style, spotlightPadding } = this.props;
+        const { current } = this.state;
+
+        const isCurrentStep = current === index;
+        if (!step.target) {
+            return null;
+        }
+
+        const basePopoverStyle = { padding: 0 };
+
+        const target = typeof step.target === 'function' ? step.target() : step.target;
+        const rect = target.getBoundingClientRect();
+        const padding = step?.spotlightPadding || spotlightPadding || numbers.DEFAULT_SPOTLIGHT_PADDING;
+        const isPrimaryTheme = theme === 'primary' || step?.theme === 'primary';
+        const primaryStyle = isPrimaryTheme ? { backgroundColor: 'var(--semi-color-primary)' } : {};
+
+
+        return (
+            <Popover
+                key={`userGuide-popup-${index}`}
+                className={cls(`${prefixCls}-popover`, className)}
+                style={{ ...basePopoverStyle, ...primaryStyle, ...style }}
+                content={this.renderPopupContent(step, index)}
+                position={step.position || position}
+                trigger="custom"
+                visible={visible && isCurrentStep}
+                showArrow={step.showArrow !== false}
+            >
+                <div
+                    style={{
+                        position: 'fixed',
+                        left: rect.x - padding,
+                        top: rect.y - padding,
+                        width: rect.width + padding * 2,
+                        height: rect.height + padding * 2,
+                        pointerEvents: 'none',
+                    }}
+                >
+                </div>
+            </Popover>
+        );
+    };
+
+    renderSpotlight() {
+        const { steps, mask, zIndex } = this.props;
+        const { spotlightRect, current } = this.state;
+        const step = steps[current];
+
+        if (!step.target) {
+            return null;
+        }
+
+        if (!spotlightRect) {
+            this.updateSpotlightRect();
+        }
+
+        return (
+            <>
+                {
+                    spotlightRect ? (
+                        <svg className={`${prefixCls}-spotlight`} style={{ zIndex }}>
+                            <defs>
+                                <mask id={`spotlight-${this.userGuideId}`}>
+                                    <rect width="100%" height="100%" fill="white"/>
+                                    <rect
+                                        className={`${prefixCls}-spotlight-rect`}
+                                        x={spotlightRect.x}
+                                        y={spotlightRect.y}
+                                        width={spotlightRect.width}
+                                        height={spotlightRect.height}
+                                        rx={4}
+                                        fill="black"
+                                    />
+                                </mask>
+                            </defs>
+                            {
+                                mask && (
+                                    <>
+                                        <rect
+                                            width="100%"
+                                            height="100%"
+                                            fill="var(--semi-color-overlay-bg)"
+                                            mask={`url(#spotlight-${this.userGuideId})`} />
+                                        <rect
+                                            x={0}
+                                            y={0}
+                                            width="100%"
+                                            height={spotlightRect.y}
+                                            fill="transparent"
+                                            className={`${prefixCls}-spotlight-transparent-rect`} />
+                                        <rect
+                                            x={0}
+                                            y={spotlightRect.y}
+                                            width={spotlightRect.x}
+                                            height={spotlightRect.height}
+                                            fill="transparent"
+                                            className={`${prefixCls}-spotlight-transparent-rect`} />
+                                        <rect
+                                            x={spotlightRect.x + spotlightRect.width}
+                                            y={spotlightRect.y}
+                                            width={`calc(100% - ${spotlightRect.x + spotlightRect.width}px)`}
+                                            height={spotlightRect.height}
+                                            fill="transparent"
+                                            className={`${prefixCls}-spotlight-transparent-rect`} />
+                                        <rect
+                                            y={spotlightRect.y + spotlightRect.height}
+                                            width="100%"
+                                            height={`calc(100% - ${spotlightRect.y + spotlightRect.height}px)`}
+                                            fill="transparent"
+                                            className={`${prefixCls}-spotlight-transparent-rect`} />
+                                    </>
+                                )
+                            }
+                        </svg>
+                    ) : null
+                }
+            </>
+        );
+    }
+
+    renderIndicator = () => {
+        const { steps } = this.props;
+        const { current } = this.state;
+        const indicatorContent: ReactNode[] = [];
+        for (let i = 0; i < steps.length; i++) {
+            indicatorContent.push(
+                <span
+                    key={i}
+                    data-index={i}
+                    className={cls([`${cssClasses.PREFIX_MODAL}-indicator-item`], {
+                        [`${cssClasses.PREFIX_MODAL}-indicator-item-active`]: i === current
+                    })}
+                ></span>
+            );
+        }
+        return indicatorContent;
+    }
+
+    renderModal = () => {
+        const { visible, steps, showSkipButton, showPrevButton, finishText, nextButtonProps, prevButtonProps, mask } = this.props;
+        const { current } = this.state;
+        const step = steps[current];
+
+        const isFirst = current === 0;
+        const isLast = current === steps.length - 1;
+        const { cover, title, description } = step;
+
+        return (
+            <LocaleConsumer componentName="UserGuide">
+                {(locale: Locale['UserGuide'], localeCode: Locale['code']) => (
+                    <Modal
+                        className={cssClasses.PREFIX_MODAL}
+                        bodyStyle={{ padding: 0 }}
+                        header={null}
+                        visible={visible}
+                        maskClosable={false}
+                        mask={mask}
+                        centered
+                        footer={null}
+                    >
+                        {cover && 
+                        <>
+                            <div className={`${cssClasses.PREFIX_MODAL}-cover`}>
+                                {cover}
+                            </div>
+                            <div className={`${cssClasses.PREFIX_MODAL}-indicator`}>
+                                {this.renderIndicator()}
+                            </div>
+                        </>
+                        }
+                        {
+                            (title || description) && (
+                                <div className={`${cssClasses.PREFIX_MODAL}-body`}>
+                                    {title && <div className={`${cssClasses.PREFIX_MODAL}-body-title`}>{title}</div>}
+                                    {description && <div className={`${cssClasses.PREFIX_MODAL}-body-description`}>{description}</div>}
+                                </div>
+                            )
+                        }
+                        <div className={`${cssClasses.PREFIX_MODAL}-footer`}>
+                            {showSkipButton && !isLast && (
+                                <Button 
+                                    type='tertiary'
+                                    onClick={this.foundation.handleSkip}
+                                >
+                                    {locale.skip}
+                                </Button>
+                            )}
+                            {showPrevButton && !isFirst && (
+                                <Button 
+                                    type='tertiary'
+                                    onClick={this.foundation.handlePrev}
+                                    {...prevButtonProps}
+                                >
+                                    {prevButtonProps?.children || locale.prev}
+                                </Button>
+                            )}
+                            <Button 
+                                theme='solid'
+                                onClick={this.foundation.handleNext}
+                                {...nextButtonProps}
+                            >
+                                {isLast ? (finishText || locale.finish) : (nextButtonProps?.children || locale.next)}
+                            </Button>
+                        </div>
+                    </Modal>
+                )}
+            </LocaleConsumer>
+        );
+
+    }
+
+    render() {
+        const { mode, steps, visible } = this.props;
+
+        if (!visible || !steps.length) {
+            return null;
+        }
+
+        return (
+            <>
+                {
+                    mode === 'popup' ? (
+                        <React.Fragment>
+                            {steps?.map((step, index) => this.renderStep(step, index))}
+                            {this.renderSpotlight()}
+                        </React.Fragment>
+                    ) : null
+                }
+                { mode === 'modal' && this.renderModal()}
+            </>
+        );
+    }
+}
+
+export default UserGuide; 

+ 1 - 1
packages/semi-webpack/package.json

@@ -1,6 +1,6 @@
 {
     "name": "@douyinfe/semi-webpack-plugin",
-    "version": "2.76.1",
+    "version": "2.77.0",
     "description": "",
     "author": "伍浩威 <[email protected]>",
     "homepage": "",

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 201 - 200
sitemap.xml


+ 125 - 0
src/components/DesignToCodeFeature/index.jsx

@@ -0,0 +1,125 @@
+/* eslint-disable jsx-a11y/anchor-is-valid */
+/* eslint-disable jsx-a11y/alt-text */
+import React from 'react';
+import classNames from 'classnames';
+import { _t } from 'utils/locale';
+import './index.scss';
+
+const features = [
+    {
+        title: _t('d2c.advanced.feature1.title'),
+        description: _t('d2c.advanced.feature1.description'),
+        // url: '/d2c/mark-library',
+        backgroundImage: 'https://lf3-static.semi.design/obj/semi-tos/images/c341b1a0-bb8d-11ef-92a1-f9d591dc8ece.svg',
+        blurBackground: '#1bc47d',
+        FrontCom: ({ className }) => (
+            <div className={classNames('d2c-feature-ds-support', className)}>
+                <img
+                    src="https://lf6-static.semi.design/obj/semi-tos/images/c33aacc0-bb8d-11ef-b0ca-191cf7612a7b.svg"
+                    className={'d2c-feature-ds-support-img'}
+                />
+                <p className={'code'}>code</p>
+            </div>
+        ),
+    },
+    {
+        title: _t('d2c.advanced.feature2.title'),
+        description: _t('d2c.advanced.feature2.description'),
+        // url: '/d2c/transform-plugin',
+        backgroundImage: 'https://lf9-static.semi.design/obj/semi-tos/images/1b74be30-be0e-11ef-b10f-69903fa0351e.svg',
+        blurBackground: '#38bbc6',
+        FrontCom: ({ className }) => (
+            <div className={classNames('d2c-feature-dsl-support', className)}>
+                <div className={'dsl-wrapper'}>
+                    <div className={'dsl-symbol'}>
+                        <p className={'a'}>&#123; &#125;</p>
+                    </div>
+                    <p className={'dsl-text'}>DSL</p>
+                </div>
+                <div className={'d2c-feature-card-img2'}>
+                    <div className={'dsl-dsl-vue-text'}>
+                        <img
+                            src="https://lf26-static.semi.design/obj/semi-tos/images/c33ccfa0-bb8d-11ef-b0ca-191cf7612a7b.svg"
+                            className={'d2c-feature-dsl-support-img'}
+                        />
+                        <p className={'dsl-vue-text'}>Vue</p>
+                    </div>
+                    <div className={'dsl-react'}>
+                        <img
+                            src="https://lf9-static.semi.design/obj/semi-tos/images/c33d44d0-bb8d-11ef-b0ca-191cf7612a7b.svg"
+                            className={'react'}
+                        />
+                        <p className={'dsl-react-text'}>React</p>
+                    </div>
+                </div>
+            </div>
+        ),
+    },
+    {
+        title: _t('d2c.advanced.feature3.title'),
+        description: _t('d2c.advanced.feature3.description'),
+        backgroundImage: 'https://lf6-static.semi.design/obj/semi-tos/images/c341ffc0-bb8d-11ef-92a1-f9d591dc8ece.svg',
+        blurBackground: '#a593ff',
+        FrontCom: ({ className }) => (
+            <div className={classNames('d2c-feature-ecosystem-support', className)}>
+                <div className={'frame2117130723'}>
+                    <img
+                        src="https://lf3-static.semi.design/obj/semi-tos/images/c33d1dc0-bb8d-11ef-b0ca-191cf7612a7b.svg"
+                        className={'d2c-feature-ds-support-img'}
+                    />
+                    <p className={'openApi'}>Open API</p>
+                </div>
+                <div className={'frame2117130722'}>
+                    <img
+                        src="https://lf9-static.semi.design/obj/semi-tos/images/c33d6be0-bb8d-11ef-b0ca-191cf7612a7b.svg"
+                        className={'frame2117130688'}
+                    />
+                    <p className={'nodeSdk'}>Node SDK</p>
+                </div>
+            </div>
+        ),
+    },
+];
+
+export const Advanced = ({ location }) => {
+    const [hoveredIndex, setHoveredIndex] = React.useState(0);
+    return (
+        <div className='d2c-feature-wrapper'>
+            <div className={'d2c-feature-list'}>
+                <div className={'hover'}>
+                    {features.map((feature, index) => {
+                        const { title, description, url, backgroundImage, blurBackground, FrontCom } = feature;
+                        return (
+                            <a
+                                key={index}
+                                className={'d2c-feature-card'}
+                                onMouseEnter={e => setHoveredIndex(index)}
+                                onMouseLeave={e => setHoveredIndex(-1)}
+                                style={{ transition: 'transform 0.3s ease' }}
+                            >
+                                <div
+                                    className={'d2c-feature-card-img'}
+                                    style={{ backgroundImage: `url(${backgroundImage})` }}
+                                >
+                                    <div className={'rectangle279334588'} style={{ background: blurBackground }} />
+                                    <FrontCom className={hoveredIndex === index ? 'hovered' : ''} />
+                                </div>
+                                <div className={'d2c-feature-card-detail'}>
+                                    <div className={'frame2117130798'}>
+                                        <p className={'d2c-feature-card-detail-title'}>{title}</p>
+                                        <p className={'d2c-feature-card-detail-description'}>{description}</p>
+                                    </div>
+                                    {/* <p className={classNames('text3', { ['d2c-feature-card-disabled']: isForbidden })}>
+                                        {isForbidden ? _t('d2c.advanced.comingSoon') : _t('d2c.advanced.view')}
+                                    </p> */}
+                                </div>
+                            </a>
+                        );
+                    })}
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default Advanced;

+ 333 - 0
src/components/DesignToCodeFeature/index.scss

@@ -0,0 +1,333 @@
+.d2c-feature-wrapper {
+    display: flex;
+    align-items: flex-start;
+    overflow: hidden;
+    position: relative;
+    z-index: 1;
+
+    .hovered {
+        transform: translateY(-10px);
+    }
+
+    .d2c-feature-list {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding: 30px 0;
+
+        .hover {
+            display: flex;
+            flex-shrink: 0;
+            align-items: flex-start;
+            column-gap: 8px;
+
+            .d2c-feature-ds-support-img {
+                flex-shrink: 0;
+                width: 24px;
+                height: 17px;
+            }
+
+            .d2c-feature-ds-support {
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                column-gap: 8px;
+                border: 1px solid #72727e73;
+                border-radius: 4px;
+                box-shadow: 0px 4px 4px 0px #00000040;
+                background: #000000e5;
+                padding: 5px 23px;
+                backdrop-filter: blur(50px);
+                transition: transform 0.3s ease;
+                margin-top: 19px;
+
+                .code {
+                    flex-shrink: 0;
+                    min-width: 37px;
+                    line-height: 28px;
+                    letter-spacing: 0;
+                    color: #ffffff;
+                    font-family: 'PingFang SC';
+                    font-size: 16px;
+                }
+            }
+            .d2c-feature-dsl-support {
+                display: flex;
+                flex-direction: column;
+                align-items: flex-start;
+                transition: transform 0.3s ease;
+
+                .dsl-wrapper {
+                    display: inline-flex;
+                    align-items: center;
+                    justify-content: center;
+                    column-gap: 8px;
+                    margin: 0px 0px 0px 65px;
+                    border: 1px solid #72727e73;
+                    border-radius: 4px;
+                    box-shadow: 0px 4px 4px 0px #00000040;
+                    background: #000000e5;
+                    padding: 5px 23px;
+                    backdrop-filter: blur(50px);
+
+                    .dsl-symbol {
+                        display: flex;
+                        flex-shrink: 0;
+                        align-items: center;
+                        padding: 0px 3px;
+                        height: 22px;
+                        overflow: hidden;
+
+                        .a {
+                            min-width: 16px;
+                            line-height: 28px;
+                            letter-spacing: 0;
+                            color: #38bbc6;
+                            font-family: 'PingFang SC';
+                            font-size: 16px;
+                        }
+                    }
+
+                    .dsl-text {
+                        flex-shrink: 0;
+                        min-width: 31px;
+                        line-height: 28px;
+                        letter-spacing: 0;
+                        color: #ffffff;
+                        font-family: 'PingFang SC';
+                        font-size: 16px;
+                    }
+                }
+
+                .d2c-feature-card-img2 {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+                    margin: 27px 0px 0px;
+                    width: 231px;
+                    height: 40px;
+
+                    .dsl-dsl-vue-text {
+                        display: inline-flex;
+                        align-items: center;
+                        justify-content: center;
+                        column-gap: 8px;
+                        border: 1px solid #72727e73;
+                        border-radius: 4px;
+                        box-shadow: 0px 4px 4px 0px #00000040;
+                        background: #000000e5;
+                        padding: 5px 23px;
+                        height: 28px;
+                        backdrop-filter: blur(50px);
+
+                        .d2c-feature-dsl-support-img {
+                            flex-shrink: 0;
+                            width: 14px;
+                            height: 12px;
+                        }
+
+                        .dsl-vue-text {
+                            flex-shrink: 0;
+                            min-width: 29px;
+                            line-height: 28px;
+                            letter-spacing: 0;
+                            color: #ffffff;
+                            font-family: 'PingFang SC';
+                            font-size: 16px;
+                        }
+                    }
+
+                    .dsl-react {
+                        display: inline-flex;
+                        align-items: center;
+                        justify-content: center;
+                        column-gap: 8px;
+                        border: 1px solid #72727e73;
+                        border-radius: 4px;
+                        box-shadow: 0px 4px 4px 0px #00000040;
+                        background: #000000e5;
+                        padding: 5px 23px;
+                        backdrop-filter: blur(50px);
+
+                        .react {
+                            flex-shrink: 0;
+                            width: 18px;
+                            height: 16px;
+                            overflow: hidden;
+                        }
+
+                        .dsl-react-text {
+                            flex-shrink: 0;
+                            min-width: 44px;
+                            line-height: 28px;
+                            letter-spacing: 0;
+                            color: #ffffff;
+                            font-family: 'PingFang SC';
+                            font-size: 16px;
+                        }
+                    }
+                }
+            }
+
+            .d2c-feature-card {
+                flex-basis: 0;
+                flex-direction: column;
+                flex-grow: 1;
+                align-items: flex-start;
+                border: 1px solid var(--semi-color-border);
+                border-radius: 10px;
+                padding: 0px 18px 32px 20px;
+                overflow: hidden;
+                height: 443px;
+                -webkit-backdrop-filter: blur(50px);
+                // background-image: linear-gradient(180deg, #47474759 0%, #4d4d4d0b 100%);
+                /* backdrop-filter: blur(50px); */
+                cursor: pointer;
+                transform: scale(0.95);
+
+                .d2c-feature-card-detail {
+                    margin-top: 32px;
+                    display: flex;
+                    flex-direction: column;
+                    align-items: flex-start;
+                    justify-content: space-between;
+                    width: 336px;
+                    flex-grow: 1;
+                }
+
+                .frame2117130798 {
+                    display: flex;
+                    flex-direction: column;
+                    flex-shrink: 0;
+                    align-items: flex-start;
+                    align-self: stretch;
+                    row-gap: 16px;
+                }
+
+                .d2c-feature-card-detail-title {
+                    flex-shrink: 0;
+                    align-self: stretch;
+                    letter-spacing: 0;
+                    color: var(--semi-color-text-0);
+                    font-family: 'PingFang SC';
+                    font-size: 20px;
+                    font-weight: 600;
+                    line-height: 28px;
+                }
+
+                .d2c-feature-card-detail-description {
+                    flex-shrink: 0;
+                    align-self: stretch;
+                    line-height: 28px;
+                    letter-spacing: 0;
+                    color: var(--semi-color-text-2);
+                    font-family: 'PingFang SC';
+                    font-size: 18px;
+                }
+
+                .text3 {
+                    flex-shrink: 0;
+                    align-self: stretch;
+                    line-height: 28px;
+                    letter-spacing: 0;
+                    color: var(--semi-color-primary);
+                    font-family: 'PingFang SC';
+                    font-size: 18px;
+                    cursor: pointer;
+                }
+
+                .d2c-feature-card-disabled {
+                    color: var(--semi-color-d2c-feature-card-detail-title-3);
+                    cursor: not-allowed;
+                }
+                .d2c-feature-card-img {
+                    position: relative;
+                    margin: 0px 0px 0px 1px;
+                    width: 336px;
+                    height: 179px;
+                    flex-shrink: 0;
+                    background-position: center;
+                    background-size: cover;
+                    background-repeat: no-repeat;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+
+                    .rectangle279334588 {
+                        position: absolute;
+                        top: 0;
+                        right: -24px;
+                        opacity: 0.4;
+                        border: 1px solid #72727e73;
+                        border-radius: 4px;
+                        background: #1bc47d;
+                        width: 382px;
+                        height: 133px;
+                        filter: blur(55px);
+                    }
+                }
+            }
+
+            .d2c-feature-ecosystem-support {
+                display: flex;
+                flex-direction: column;
+                align-items: flex-start;
+                transition: transform 0.3s ease;
+
+                .frame2117130723 {
+                    display: inline-flex;
+                    align-items: center;
+                    justify-content: center;
+                    column-gap: 8px;
+                    border: 1px solid #72727e73;
+                    border-radius: 4px;
+                    box-shadow: 0px 4px 4px 0px #00000040;
+                    background: #000000e5;
+                    padding: 5px 23px;
+                    backdrop-filter: blur(50px);
+
+                    .openApi {
+                        flex-shrink: 0;
+                        min-width: 70px;
+                        line-height: 28px;
+                        letter-spacing: 0;
+                        color: #ffffff;
+                        font-family: 'PingFang SC';
+                        font-size: 16px;
+                    }
+                }
+
+                .frame2117130722 {
+                    display: inline-flex;
+                    align-items: center;
+                    justify-content: center;
+                    column-gap: 8px;
+                    margin: 21px 0px 0px 86px;
+                    border: 1px solid #72727e73;
+                    border-radius: 4px;
+                    box-shadow: 0px 4px 4px 0px #00000040;
+                    background: #000000e5;
+                    padding: 5px 23px;
+                    backdrop-filter: blur(50px);
+
+                    .frame2117130688 {
+                        flex-shrink: 0;
+                        width: 20px;
+                        height: 20px;
+                        overflow: hidden;
+                    }
+
+                    .nodeSdk {
+                        flex-shrink: 0;
+                        min-width: 77px;
+                        line-height: 28px;
+                        letter-spacing: 0;
+                        color: #ffffff;
+                        font-family: 'PingFang SC';
+                        font-size: 16px;
+                    }
+                }
+            }
+        }
+    }
+}

+ 3 - 5
src/html.js

@@ -167,7 +167,6 @@ export default function HTML(props) {
                     "react library",
                     "Design component",
                     "Design System",
-                    "quick develop",
                     "easy customize",
                     "modern design system",
                     "front-end",
@@ -175,18 +174,17 @@ export default function HTML(props) {
                     "用户界面组件",
                     "反应库",
                     "设计组件",
-                    "设计系统",
-                    "快速发展",
+                    "抖音设计系统",
                     "轻松定制",
                     "现代设计体系",
                     "前端"
                 ].join(", ")}/>
                 <script src="https://lf1-cdn-tos.bytescm.com/goofy/semi_convenience/semi-analyze.js" defer={true} />
                 {
-                    THEME_SWITCHER_URL?<script src={THEME_SWITCHER_URL} defer={true}/>:<script src="https://unpkg.byted-static.com/latest/ies/semi-theme-switcher-opensource/dist/semi-theme-switcher.js" defer={true}/>
+                    THEME_SWITCHER_URL ? <script src={THEME_SWITCHER_URL} defer={true}/> : <script src="https://unpkg.byted-static.com/latest/ies/semi-theme-switcher-opensource/dist/semi-theme-switcher.js" defer={true}/>
                 }
                 {
-                    SEMI_SEARCH_URL?<script src={SEMI_SEARCH_URL} defer={true}/>:<script src={"https://unpkg.byted-static.com/latest/ies/semi-search-opensource/dist/semi-search.js"} defer={true}/>
+                    SEMI_SEARCH_URL ? <script src={SEMI_SEARCH_URL} defer={true}/> : <script src={"https://unpkg.byted-static.com/latest/ies/semi-search-opensource/dist/semi-search.js"} defer={true}/>
                 }
                 {
                     MATERIAL_LIST_URL ? <script src={MATERIAL_LIST_URL} defer={true} /> : null

+ 6 - 0
src/images/docIcons/doc-userGuide.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="2" y="2" width="20" height="20" rx="3" fill="#AAB2BF"/>
+<rect x="4" y="5" width="16" height="14" rx="1" fill="white"/>
+<rect x="6" y="14" width="5" height="3" rx="1" fill="#AAB2BF"/>
+<rect x="13" y="14" width="5" height="3" rx="1" fill="#4CC3FA"/>
+</svg>

+ 16 - 2
src/locale/en-US.js

@@ -105,7 +105,7 @@ const appLocale = {
         "adapter_webComponents": "Easily compatible with web components",
         "adapter_webComponents_description": "Provides a complete adaptation solution. All components can work normally in shadow DOM, which is more suitable for building SDKs, browser plugins and other scenarios that require DOM isolation.",
         "feature_SSR": "SSR support",
-        "feature_SSR_description": "Semi component library supports SSR scenarios and can be used in frameworks like Next.js、Gatsby、Remix",
+        "feature_SSR_description": "Semi component library supports SSR scenarios and can be used in frameworks like Next.js, Gatsby, Remix",
         "feature_D2C": "Design to code",
         "feature_D2C_description": "Offical provides powerful design to code capabilities, 3-5s, one-click, generate real code from Figma design draft",
         // page four: Theme
@@ -252,7 +252,21 @@ const appLocale = {
         "video_compression_algorithm": "Video compression algorithm",
         "ulike_assistant": "Ulike Assistant",
         "theme_store": "Theme Store",
-        "semi_dsm": "Managing Themes"
+        "semi_dsm": "Managing Themes",
+
+        // Advanced
+        'd2c.advanced.title': 'Modern Development Delivery Method',
+        'd2c.advanced.feature1.title': 'Support Component Recognition',
+        'd2c.advanced.feature1.description':
+            'Get component code quickly without frequently referring to component documentation. Supports Semi Design component by default. ',
+        'd2c.advanced.feature2.title': 'Easy Code Customization',
+        'd2c.advanced.feature2.description':
+            'Provides DSL intermediate output and 19+ hook functions, supporting user-customized code generation results, such as converting to Vue code.',
+        'd2c.advanced.feature3.title': 'Quick Tool Integration',
+        'd2c.advanced.feature3.description':
+            'Support using D2C capabilities through OPEN API or Node SDK without the Figma plugin, enabling integration into self-built platforms.',
+        'd2c.advanced.view': 'View Now',
+        'd2c.advanced.comingSoon': 'Coming Soon',
     },
 };
 

+ 26 - 13
src/locale/zh-CN.js

@@ -53,7 +53,7 @@ const appLocale = {
         language: '语言',
         materialSite: '物料平台',
         changelog: '更新日志',
-        'changelog.internal.link': '该类地址为Semi未开源时使用地址,外部用户可能无法访问,如有问题可以提issue~',
+        'changelog.internal.link': '该类地址为 Semi 未开源时使用地址,外部用户可能无法访问,如有问题可以提 issue~',
 
         // header
         'header.switchLanguage': 'Switch Language',
@@ -70,8 +70,8 @@ const appLocale = {
         // changelog diff
         'changelog.diff.button': '版本对比',
         'changelog.diff.title': '版本对比',
-        'changelog.diff.version1.insetLabel': '版本1',
-        'changelog.diff.version2.insetLabel': '版本2',
+        'changelog.diff.version1.insetLabel': '版本 1',
+        'changelog.diff.version2.insetLabel': '版本 2',
         'changelog.diff.version.placeholder': '请选择版本',
         'changelog.diff.change.insetLabel': '变更',
         'changelog.diff.change.all': '全部',
@@ -100,20 +100,20 @@ const appLocale = {
         "feature_a11y": "A11y 无障碍友好",
         "feature_a11y_description": "Semi 遵循 W3C 标准为所有组件提供键盘交互、焦点管理和语义化支持",
         "feature_language": "国际化与多语言",
-        "feature_language_description": "Semi 提供完备的多语言、多时区、RTL支持,助你轻松打造全球化应用",
+        "feature_language_description": "Semi 提供完备的多语言、多时区、RTL 支持,助你轻松打造全球化应用",
         "feature_live_code": "Live Code 组件",
         "feature_live_code_description": "LiveCode 允许你使用在线代码编辑器即时演示你的 UI 组件",
         "feature_test": "稳定的质量保障",
-        "feature_test_description": "Semi 稳定迭代5年+,使用了单元测试、E2E测试、视觉对比测试等多种方法保证组件的稳定和质量,测试覆盖率达到 90%",
+        "feature_test_description": "Semi 稳定迭代 5 年+,使用了单元测试、E2E 测试、视觉对比测试等多种方法保证组件的稳定和质量,测试覆盖率达到 90%",
         "adapter_webComponents": "轻松兼容 web components",
         "adapter_webComponents_description": "提供完整的适配方案,所有的组件在 shadow DOM 中均可正常工作,更适合用于构建 SDK、浏览器插件等需要 DOM 隔离的场景",
         "feature_SSR": "支持 SSR",
-        "feature_SSR_description": "Semi 组件库支持 SSR 场景,可以在 Next.js  Gatsby 和 Remix 等框架中使用",
+        "feature_SSR_description": "Semi 组件库支持 SSR 场景,可以在 Next.js、Gatsby 和 Remix 等框架中使用",
         "feature_D2C": "设计稿转代码",
         "feature_D2C_description": "Semi 提供高效的 design to code 能力,3-5s,一键点击,从 Figma 设计稿生成真实代码",
         // page four: theme
         "home.theme": "百变主题",
-        "home.theme.desc": "提供高达3000+ Design Token,快速克隆或深度定制,灵活调配符合品牌调性的设计风格",
+        "home.theme.desc": "提供高达 3000+ Design Token,快速克隆或深度定制,灵活调配符合品牌调性的设计风格",
         // page five: DSM
         "semi_dsm": "Semi 设计系统管理",
         "powerful_theme_editor__real_time_effect__one_click_synchronization_of_design_too_d77f5776bf126331e801d6d6aa0146f2": "强大的主题编辑器,实时生效,设计工具一键同步",
@@ -159,7 +159,7 @@ const appLocale = {
         "content_d2c_openday": "字节跳动开源 OpenDay 主题分享",
         "content_d2c_info_openday": "D2C 设计稿转代码深度解读",
         "content_test": "Semi Design 如何做质量保障",
-        "content_test_info": "综合运用 Unit Test、E2E Test、Visual Test保障组件库稳定性",
+        "content_test_info": "综合运用 Unit Test、E2E Test、Visual Test 保障组件库稳定性",
 
         // page nine: comment
         "grow_with_users": "与用户共同成长",
@@ -170,23 +170,23 @@ const appLocale = {
         "access_is_simple_and_easy_to_use": "接入简单易上手;Semi UI 样式美观,主题统一;API 丰富全面。",
         "beautiful_style_and_unified_theme": "样式美观,主题统一;",
         "rich_and_comprehensive_": "丰富全面。",
-        "front_end__bytedance": "前端, 字节跳动",
+        "front_end__bytedance": "前端字节跳动",
         "the_components_are_quite_complete__covering_a_wide_range_and_the_overall_style_i_e10d9214b403886d249f00b8c4dbb975": "Semi 组件挺全的,覆盖的比较广泛,整体风格也不错。",
         "design_resources_are_obviously_helpful_to_improve_efficiency_": "设计资源对提效有明显帮助。",
-        "design__bytedance": "设计, 字节跳动",
+        "design__bytedance": "设计字节跳动",
         "there_are_many_other_business_uses_within_the_company__there_are_more_sample_ref_c5fe7051d5fbf1a547084c91f7c4fd8e": "Semi 有很多公司内的其他业务使用,有比较多的样例参考,我们依托 Semi 的组件设计,参考其他业务平台的设计方案,总结了自己的前端规范,统一平台的交付标准。",
         "component_design__referring_to_the_design_schemes_of_other_business_platforms__s_e47da10ff860ba8b9db1268c06011006": "的组件设计,参考其他业务平台的设计方案,总结了自己的前端规范,统一平台的交付标准。",
-        "product_manager__bytedance": "产品经理, 字节跳动",
+        "product_manager__bytedance": "产品经理字节跳动",
         "super_good!_strong_push_": "Semi 超级好用!强推。",
         "as_an_excellent_benchmarking_industry": "Semi 作为对标业界优秀的 UI 库来说做的确实很不错了,对于问题的响应速度,问题的解决效率都有很好的保障。",
         "the_library_did_a_really_good_job__it_has_a_good_guarantee_for_the_corresponding_61cb34deb7e27e6561dd9a5bce00de72": "库来说做的确实很不错了,对于问题的响应速度,问题的解决效率都有很好的保障。",
-        "easy_to_use__beautiful_style_": "Semi Design使用方便,样式美观。",
+        "easy_to_use__beautiful_style_": "Semi Design 使用方便,样式美观。",
         "uniform_style_and_high_fidelity_prototype_facilitate_communication_with_front_en_95d0c0ccece05c104b98ac0dae9fb53b": "Semi 统一的样式,高保真的原型便于与前端同学进行沟通。",
         "the_documentation_is_very_detailed_and_the_details_of_the_components_are_well_th_aadc51a1122c41cf69ebd4b15e83e864": "文档非常详细,对组件的细节思考非常充足。",
         // page ten: resource
         "resource_subtitle": "今天起,在下一个项目中使用 Semi Design",
         "home.resource.rd": "我是开发者",
-        "home.resource.rd.desc": "基于设计语言开发,能够在线调试的 React UI 组件库, 帮助开发者高效构建应用",
+        "home.resource.rd.desc": "基于设计语言开发,能够在线调试的 React UI 组件库帮助开发者高效构建应用",
         "component_documentation": "组件文档",
         "home.resource.design": "我是设计师",
         "home.resource.design.desc": "基于 Figma 构建,与组件库代码完全对齐的设计资源,使用 Semi 设计出色的中后台企业应用",
@@ -238,6 +238,19 @@ const appLocale = {
         "private_message_content": "私信内容",
         "time_of_dispatch": "发信时间",
         "theme_store": "主题商店",
+
+        'd2c.advanced.title': '现代化的研发交付方式',
+        'd2c.advanced.feature1.title': '支持识别设计系统组件',
+        'd2c.advanced.feature1.description':
+            '无需频繁翻阅组件文档,即可快速得到组件代码。支持 Semi 组件识别,也支持接入其他设计系统组件。',
+        'd2c.advanced.feature2.title': '轻松定制转码结果',
+        'd2c.advanced.feature2.description':
+            '提供了中间产物 DSL 和 19+ 钩子函数,支持用户定制化转码结果,比如转码为 Vue、Svelte 代码,自定义模板代码等。',
+        'd2c.advanced.feature3.title': '快速封装提效工具',
+        'd2c.advanced.feature3.description':
+            '支持 OPEN API 或 Node SDK 形式脱离 Figma 插件使用 D2C 能力,可将 D2C 能力嵌入自建平台或工具,比如搭建平台,VS Code 插件等。',
+        'd2c.advanced.view': '立即查看',
+        'd2c.advanced.comingSoon': '暂未开放,敬请期待',
     },
 };
 

+ 2 - 0
src/templates/postTemplate.js

@@ -34,6 +34,7 @@ import { itemsArr } from '../utils/category';
 import FullPalette from 'components/FullPalette';
 import ColorConverter from 'components/ColorConverter';
 import JumpToToken from 'components/JumpToToken';
+import DesignToCodeFeature from 'components/DesignToCodeFeature';
 import copy from 'copy-text-to-clipboard';
 import '@douyinfe/semi-site-doc-style';
 import SemiSiteChangeLogDiff from 'components/SemiSiteChangeLogDiff';
@@ -237,6 +238,7 @@ const components = {
     ColorConverter,
     FullPalette,
     JumpToToken,
+    DesignToCodeFeature,
     ...SemiComponents,
     ...Blocks,
     code,

Vissa filer visades inte eftersom för många filer har ändrats