diff --git a/packages/codemod/README.md b/packages/codemod/README.md
index 16d5efc11..e4db204c6 100644
--- a/packages/codemod/README.md
+++ b/packages/codemod/README.md
@@ -133,3 +133,102 @@ import utils and hooks from `@alipay/ob-util` to `@oceanbase/util`. Additionally
export default Demo;
```
+
+### `style-to-token`
+
+transform fixed css style to antd v5 design token.
+
+- React function components:
+
+```diff
+ import React from 'react';
+- import { Alert, Button } from '@oceanbase/design';
++ import { Alert, Button, theme } from '@oceanbase/design';
+
+ const Demo = () => {
++ const { token } = theme.useToken();
+ return (
+-
++ ()
+ );
+ };
+
+export default Demo;
+```
+
+- React class components:
+
+```diff
+ import React from 'react';
+- import { Alert, Button } from '@oceanbase/design';
++ import { Alert, Button, token } from '@oceanbase/design';
+
+ class Demo extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+
+ render() {
+ return (
+-
++ ()
+ );
+ }
+ }
+
+ export default Demo;
+```
+
+- Static file (not react components):
+
+```diff
++ import { token } from '@oceanbase/design';
+ const colorMap = {
+- info: '#1890ff',
+- success: '#52c41a',
+- warning: '#faad14',
+- error: '#ff4D4F',
++ info: token.colorInfo,
++ success: token.colorSuccess,
++ warning: token.colorWarning,
++ error: token.colorError,
+ };
+
+ function getColorList() {
+ return [
+ {
+ type: 'info',
+- color: '#1890ff',
++ color: token.colorInfo,
+ },
+ {
+ type: 'success',
+- color: '#52c41a',
++ color: token.colorSuccess,
+ },
+ {
+ type: 'warning',
+- color: '#faad14',
++ color: token.colorWarning,
+ },
+ {
+ type: 'error',
+- color: '#ff4D4F',
++ color: token.colorError,
+ }
+ ];
+ }
+```
diff --git a/packages/codemod/bin/cli.js b/packages/codemod/bin/cli.js
index 399d2bb91..21b732718 100644
--- a/packages/codemod/bin/cli.js
+++ b/packages/codemod/bin/cli.js
@@ -30,6 +30,7 @@ const transformers = [
'obui-to-oceanbase-design-and-ui',
'obutil-to-oceanbase-util',
'page-container-to-oceanbase-ui',
+ 'style-to-token',
];
const dependencyProperties = [
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js
new file mode 100644
index 000000000..f696fb88d
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.input.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Alert, Button } from '@oceanbase/design';
+
+class Demo extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Demo;
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js
new file mode 100644
index 000000000..64f5d3ae9
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/class-component.output.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Alert, Button, token } from '@oceanbase/design';
+
+class Demo extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+
+ render() {
+ return (
+ ()
+ );
+ }
+}
+
+export default Demo;
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js
new file mode 100644
index 000000000..f3ccf3613
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.input.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { Alert, Button } from '@oceanbase/design';
+
+const Demo = () => {
+ return (
+
+ );
+};
+
+export default Demo;
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js
new file mode 100644
index 000000000..b660aafc2
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/function-component.output.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import { Alert, Button, theme } from '@oceanbase/design';
+
+const Demo = () => {
+ const { token } = theme.useToken();
+ return (
+ ()
+ );
+};
+
+export default Demo;
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js b/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js
new file mode 100644
index 000000000..88f0ee732
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/static.input.js
@@ -0,0 +1,27 @@
+const colorMap = {
+ info: '#1890ff',
+ success: '#52c41a',
+ warning: '#faad14',
+ error: '#ff4D4F',
+};
+
+function getColorList() {
+ return [
+ {
+ type: 'info',
+ color: '#1890ff',
+ },
+ {
+ type: 'success',
+ color: '#52c41a',
+ },
+ {
+ type: 'warning',
+ color: '#faad14',
+ },
+ {
+ type: 'error',
+ color: '#ff4D4F',
+ }
+ ];
+}
diff --git a/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js b/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js
new file mode 100644
index 000000000..cbde84094
--- /dev/null
+++ b/packages/codemod/transforms/__testfixtures__/style-to-token/static.output.js
@@ -0,0 +1,28 @@
+import { token } from '@oceanbase/design';
+const colorMap = {
+ info: token.colorInfo,
+ success: token.colorSuccess,
+ warning: token.colorWarning,
+ error: token.colorError,
+};
+
+function getColorList() {
+ return [
+ {
+ type: 'info',
+ color: token.colorInfo,
+ },
+ {
+ type: 'success',
+ color: token.colorSuccess,
+ },
+ {
+ type: 'warning',
+ color: token.colorWarning,
+ },
+ {
+ type: 'error',
+ color: token.colorError,
+ }
+ ];
+}
diff --git a/packages/codemod/transforms/__tests__/style-to-token.test.ts b/packages/codemod/transforms/__tests__/style-to-token.test.ts
new file mode 100644
index 000000000..351bbdb25
--- /dev/null
+++ b/packages/codemod/transforms/__tests__/style-to-token.test.ts
@@ -0,0 +1,10 @@
+import { defineTest } from 'jscodeshift/src/testUtils';
+
+const testUnit = 'style-to-token';
+const tests = ['function-component', 'class-component', 'static'];
+
+describe(testUnit, () => {
+ tests.forEach(test =>
+ defineTest(__dirname, testUnit, {}, `${testUnit}/${test}`, { parser: 'babylon' })
+ );
+});
diff --git a/packages/codemod/transforms/style-to-token.js b/packages/codemod/transforms/style-to-token.js
new file mode 100644
index 000000000..c3fef5d7d
--- /dev/null
+++ b/packages/codemod/transforms/style-to-token.js
@@ -0,0 +1,110 @@
+const { toLower } = require('lodash');
+const { addSubmoduleImport } = require('./utils');
+const { printOptions } = require('./utils/config');
+
+const TOKEN_MAP = {
+ // antd fixed style => antd v5 token
+ '#f0f2f5': 'colorBgLayout',
+ '#fafafa': 'colorBgLayout',
+ '#fff': 'colorBgContainer',
+ '#ffffff': 'colorBgContainer',
+ '#1890ff': 'colorInfo',
+ '#52c41a': 'colorSuccess',
+ '#73d13d': 'colorSuccess',
+ '#faad14': 'colorWarning',
+ '#ff4d4f': 'colorError',
+ '#F5222D': 'colorError',
+ '#F8636B': 'colorError',
+ '#d9d9d9': 'colorBorder',
+ '#bfbfbf': 'colorBorder',
+ 'rgba(0,0,0,0.85)': 'colorText',
+ 'rgba(0,0,0,0.65)': 'colorTextSecondary',
+ 'rgba(0,0,0,0.45)': 'colorTextTertiary',
+ 'rgba(0,0,0,0.25)': 'colorTextQuaternary',
+ 'rgba(0,0,0,0.2)': 'colorFillQuaternary',
+ 'rgba(0,0,0,0.04)': 'colorBgLayout',
+};
+
+function trimAll(str) {
+ return str?.replace(/(\s)*/g, '');
+}
+
+function formatValue(value) {
+ return trimAll(toLower(value));
+}
+
+function importComponent(j, root, options) {
+ let hasChanged = false;
+
+ const stringList = root.find(j.StringLiteral, {
+ value: value => TOKEN_MAP[formatValue(value)],
+ });
+ if (stringList.length > 0) {
+ // replace inline style to token
+ stringList.replaceWith(path => {
+ hasChanged = true;
+ const stringValue = path.value.value;
+ const mapToken = TOKEN_MAP[formatValue(stringValue)];
+ return j.identifier(`token.${mapToken}`);
+ });
+
+ root.find(j.BlockStatement).forEach(path => {
+ const includeToken =
+ j(path).find(j.Identifier, {
+ name: name => name?.includes('token.'),
+ }).length > 0;
+ if (includeToken) {
+ const includeJsxElementList = j(path).find(j.JSXElement).length > 0;
+ const parentType = path.parentPath.value?.type;
+ // React function component
+ if (includeJsxElementList && parentType !== 'ClassMethod') {
+ const importString = `const { token } = theme.useToken()`;
+ path.get('body').value.unshift(j.expressionStatement(j.identifier(importString)));
+ // import theme from @oceanbase/design
+ addSubmoduleImport(j, root, {
+ moduleName: '@oceanbase/design',
+ importedName: 'theme',
+ importKind: 'value',
+ });
+ } else {
+ // React class component and static file (not react component)
+ // import token from @oceanbase/design
+ addSubmoduleImport(j, root, {
+ moduleName: '@oceanbase/design',
+ importedName: 'token',
+ importKind: 'value',
+ });
+ }
+ }
+
+ // const name = path.value.declarations[0].id.name;
+ // if (/^[A-Z]/.test(name)) {
+ // const init = path.value.declarations[0].init;
+ // const initCode = generate(path.value).code;
+ // if (
+ // init &&
+ // initCode.includes('token.') &&
+ // // avoid duplicate statement
+ // !initCode.includes('useToken()') &&
+ // init.type === 'ArrowFunctionExpression'
+ // ) {
+ // const codeBody = init.body;
+ // const importStyleString = `const { token } = theme.useToken();`;
+ // codeBody.body.unshift(j.blockStatement(importStyleString).program.body[0]);
+ // }
+ // }
+ });
+ }
+
+ return hasChanged;
+}
+
+module.exports = (file, api, options) => {
+ const j = api.jscodeshift;
+ const root = j(file.source);
+
+ let hasChanged = false;
+ hasChanged = importComponent(j, root, options) || hasChanged;
+
+ return hasChanged ? root.toSource(options.printOptions || printOptions) : null;
+};
diff --git a/packages/codemod/transforms/utils/index.js b/packages/codemod/transforms/utils/index.js
index 5f12c0eaf..c970c2cd5 100644
--- a/packages/codemod/transforms/utils/index.js
+++ b/packages/codemod/transforms/utils/index.js
@@ -151,7 +151,7 @@ function addModuleImport(j, root, { pkgName, importSpecifier, importKind, before
if (before) {
insertImportBefore(j, root, { importStatement, importKind, beforeModule: before });
- } else if (after) {
+ } else {
insertImportAfter(j, root, { importStatement, importKind, afterModule: after });
}