mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat: add Theme Config Editor (#39621)
* feat: add Theme Editor theme upload * verision 2 * 移除lodash、同步当前Config * remove export,add into devDependencies,add tool link
This commit is contained in:
parent
7cb8b79a6a
commit
ec900c937b
33
.dumi/pages/theme-editor/components/JSONEditor.tsx
Normal file
33
.dumi/pages/theme-editor/components/JSONEditor.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { JSONEditor as Editor, Mode, type JSONEditorPropsOptional } from 'vanilla-jsoneditor';
|
||||
import React from 'react';
|
||||
|
||||
const JSONEditor = (props: JSONEditorPropsOptional) => {
|
||||
const refContainer = React.useRef(null);
|
||||
const refEditor = React.useRef(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
refEditor.current = new Editor({
|
||||
target: refContainer.current,
|
||||
props: {
|
||||
mode: Mode.text,
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (refEditor.current) {
|
||||
refEditor.current.destroy();
|
||||
refEditor.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (refEditor.current) {
|
||||
refEditor.current.updateProps(props);
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
return <div className="vanilla-jsoneditor-react" ref={refContainer} />;
|
||||
};
|
||||
|
||||
export default JSONEditor;
|
4
.dumi/pages/theme-editor/components/utils.tsx
Normal file
4
.dumi/pages/theme-editor/components/utils.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export function isObject(target: any) {
|
||||
return Object.prototype.toString.call(target) === '[object Object]';
|
||||
}
|
@ -1,28 +1,36 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { enUS, zhCN, ThemeEditor } from 'antd-token-previewer';
|
||||
import { Button, ConfigProvider, message, Modal, Typography } from 'antd';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import { Helmet } from 'dumi';
|
||||
import { css } from '@emotion/react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import type { JSONContent, TextContent } from 'vanilla-jsoneditor';
|
||||
import useLocale from '../../hooks/useLocale';
|
||||
import JSONEditor from './components/JSONEditor';
|
||||
import { isObject } from './components/utils';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
title: '主题编辑器',
|
||||
save: '保存',
|
||||
reset: '重置',
|
||||
export: '导出',
|
||||
exportDesc: '将下面的 JSON 对象复制到 ConfigProvider 的 theme 属性中即可。',
|
||||
edit: '代码',
|
||||
editModelTitle: '编辑主题配置',
|
||||
editTitle: '在下方编辑你的主题 JSON 即可',
|
||||
editJsonContentTypeError: '主题 JSON 格式错误',
|
||||
editSuccessfully: '编辑成功',
|
||||
saveSuccessfully: '保存成功',
|
||||
},
|
||||
en: {
|
||||
title: 'Theme Editor',
|
||||
save: 'Save',
|
||||
reset: 'Reset',
|
||||
export: 'Export',
|
||||
exportDesc: 'Copy the following JSON object to the theme prop of ConfigProvider.',
|
||||
edit: 'Code',
|
||||
editModelTitle: 'edit Theme Config',
|
||||
editTitle: 'Edit your theme JSON below',
|
||||
editJsonContentTypeError: 'The theme of the JSON format is incorrect',
|
||||
editSuccessfully: 'Edited successfully',
|
||||
saveSuccessfully: 'Saved successfully',
|
||||
},
|
||||
};
|
||||
@ -42,11 +50,17 @@ const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme';
|
||||
|
||||
const CustomTheme = () => {
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const [modalApi, modalContextHolder] = Modal.useModal();
|
||||
const [locale, lang] = useLocale(locales);
|
||||
|
||||
const [theme, setTheme] = React.useState<ThemeConfig>({});
|
||||
|
||||
const [editModelOpen, setEditModelOpen] = useState<boolean>(false);
|
||||
const [editThemeFormatRight, setEditThemeFormatRight] = useState<boolean>(true);
|
||||
const [themeConfigContent, setThemeConfigContent] = useState<JSONContent & TextContent>({
|
||||
text: '{}',
|
||||
json: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const storedConfig = localStorage.getItem(ANT_DESIGN_V5_THEME_EDITOR_THEME);
|
||||
if (storedConfig) {
|
||||
@ -54,6 +68,14 @@ const CustomTheme = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (editModelOpen === true) return;
|
||||
setThemeConfigContent({
|
||||
json: theme as any,
|
||||
text: undefined,
|
||||
});
|
||||
}, [theme, editModelOpen]);
|
||||
|
||||
const styles = useStyle();
|
||||
|
||||
const handleSave = () => {
|
||||
@ -61,48 +83,47 @@ const CustomTheme = () => {
|
||||
messageApi.success(locale.saveSuccessfully);
|
||||
};
|
||||
|
||||
const onCopy = (text: string, result: boolean) => {
|
||||
if (result) {
|
||||
messageApi.success('Copy theme config successfully!');
|
||||
} else {
|
||||
messageApi.error('Copy failed, please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleOutput = () => {
|
||||
modalApi.info({
|
||||
title: locale.export,
|
||||
width: 600,
|
||||
content: (
|
||||
<div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.65)' }}>{locale.exportDesc}</div>
|
||||
<pre
|
||||
style={{
|
||||
padding: 12,
|
||||
background: '#f5f5f5',
|
||||
borderRadius: 4,
|
||||
marginTop: 12,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<CopyToClipboard text={JSON.stringify(theme, null, 2)} onCopy={onCopy}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CopyOutlined />}
|
||||
style={{ position: 'absolute', right: 8, top: 8 }}
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
{JSON.stringify(theme, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setTheme({});
|
||||
};
|
||||
|
||||
const handleEditConfig = () => {
|
||||
setEditModelOpen(true);
|
||||
};
|
||||
|
||||
const editModelClose = useCallback(() => {
|
||||
setEditModelOpen(false);
|
||||
}, [themeConfigContent]);
|
||||
|
||||
const handleEditConfigChange = (newcontent, preContent, status) => {
|
||||
setThemeConfigContent(newcontent);
|
||||
if (
|
||||
Array.isArray(status.contentErrors.validationErrors) &&
|
||||
status.contentErrors.validationErrors.length === 0
|
||||
) {
|
||||
setEditThemeFormatRight(true);
|
||||
} else {
|
||||
setEditThemeFormatRight(false);
|
||||
}
|
||||
};
|
||||
|
||||
const editSave = useCallback(() => {
|
||||
if (!editThemeFormatRight) {
|
||||
message.error(locale.editJsonContentTypeError);
|
||||
return;
|
||||
}
|
||||
const themeConfig = themeConfigContent.text
|
||||
? JSON.parse(themeConfigContent.text)
|
||||
: themeConfigContent.json;
|
||||
if (!isObject(themeConfig)) {
|
||||
message.error(locale.editJsonContentTypeError);
|
||||
return;
|
||||
}
|
||||
setTheme(themeConfig);
|
||||
editModelClose();
|
||||
messageApi.success(locale.editSuccessfully);
|
||||
}, [themeConfigContent]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
@ -110,15 +131,31 @@ const CustomTheme = () => {
|
||||
<meta property="og:title" content={`${locale.title} - Ant Design`} />
|
||||
</Helmet>
|
||||
{contextHolder}
|
||||
{modalContextHolder}
|
||||
<ConfigProvider theme={{ inherit: false }}>
|
||||
<div css={styles.header}>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>
|
||||
{locale.title}
|
||||
</Typography.Title>
|
||||
<div>
|
||||
<Button onClick={handleOutput} style={{ marginRight: 8 }}>
|
||||
{locale.export}
|
||||
<Modal
|
||||
open={editModelOpen}
|
||||
title={locale.editModelTitle}
|
||||
width={600}
|
||||
okText={locale.save}
|
||||
onOk={editSave}
|
||||
onCancel={editModelClose}
|
||||
>
|
||||
<div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.65)' }}>{locale.editTitle}</div>
|
||||
<JSONEditor
|
||||
content={themeConfigContent}
|
||||
onChange={handleEditConfigChange}
|
||||
mainMenuBar={false}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
<Button onClick={handleEditConfig} icon={<EditOutlined />} style={{ marginRight: 8 }}>
|
||||
{locale.edit}
|
||||
</Button>
|
||||
<Button onClick={handleReset} style={{ marginRight: 8 }}>
|
||||
{locale.reset}
|
||||
|
@ -15,6 +15,7 @@ title: 社区精选组件
|
||||
| 拖拽 | [dnd-kit](https://github.com/clauderic/dnd-kit) [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd/) [react-dnd](https://github.com/gaearon/react-dnd) [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) |
|
||||
| 代码编辑器 | [react-codemirror2](https://github.com/scniro/react-codemirror2) [react-monaco-editor](https://github.com/superRaytin/react-monaco-editor) |
|
||||
| 富文本编辑器 | [react-quill](https://github.com/zenoamaro/react-quill) [braft-editor](https://github.com/margox/braft-editor) |
|
||||
| JSON 编辑器 | [vanilla-jsoneditor](https://github.com/josdejong/svelte-jsoneditor) |
|
||||
| JSON 显示器 | [react-json-view](https://github.com/mac-s-g/react-json-view) |
|
||||
| 拾色器 | [react-colorful](https://github.com/omgovich/react-colorful) [react-color](http://casesandberg.github.io/react-color/) |
|
||||
| 响应式 | [react-responsive](https://github.com/contra/react-responsive) [react-media](https://github.com/ReactTraining/react-media) |
|
||||
|
@ -282,6 +282,7 @@
|
||||
"ts-node": "^10.8.2",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "~4.9.3",
|
||||
"vanilla-jsoneditor": "^0.11.4",
|
||||
"webpack-bundle-analyzer": "^4.1.0",
|
||||
"xhr-mock": "^2.4.1",
|
||||
"yaml-front-matter": "^4.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user