-
-
Notifications
You must be signed in to change notification settings - Fork 50.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Theme Config Editor #39621
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* eslint-disable import/prefer-default-export */ | ||
export function isObject(target: any) { | ||
return Object.prototype.toString.call(target) === '[object Object]'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里我用错了,只想要纯Object,所以这个就够了,不需要_.isObject There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK,那就更不需要 lodash 了,因为 antd 中用到 lodash 的地方不多,所以后面计划把这个库干掉,体积实在太大了 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
哈哈哈,看到你们的pr了 |
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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 即可', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 将 JSON 复制到 ConfigProvider 的 theme 属性中套用主题。 |
||||||
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,83 +50,112 @@ 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) { | ||||||
setTheme(() => JSON.parse(storedConfig)); | ||||||
} | ||||||
}, []); | ||||||
|
||||||
useEffect(() => { | ||||||
if (editModelOpen === true) return; | ||||||
setThemeConfigContent({ | ||||||
json: theme as any, | ||||||
text: undefined, | ||||||
}); | ||||||
}, [theme, editModelOpen]); | ||||||
|
||||||
const styles = useStyle(); | ||||||
|
||||||
const handleSave = () => { | ||||||
localStorage.setItem(ANT_DESIGN_V5_THEME_EDITOR_THEME, JSON.stringify(theme)); | ||||||
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 handleReset = () => { | ||||||
setTheme({}); | ||||||
}; | ||||||
|
||||||
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 handleEditConfig = () => { | ||||||
setEditModelOpen(true); | ||||||
}; | ||||||
|
||||||
const handleReset = () => { | ||||||
setTheme({}); | ||||||
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> | ||||||
<title>{`${locale.title} - Ant Design`}</title> | ||||||
<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 }}> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{locale.edit} | ||||||
</Button> | ||||||
<Button onClick={handleReset} style={{ marginRight: 8 }}> | ||||||
{locale.reset} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vanilla-jsoneditor 可以加到 https://ant.design/docs/react/recommendation-cn 里来
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
收到🫡