Skip to content

Commit

Permalink
Merge branch 'feature' of https://github.com/ant-design/ant-design in…
Browse files Browse the repository at this point in the history
…to feat-app
  • Loading branch information
heiyu4585 committed Dec 12, 2022
2 parents 561155f + 11a5866 commit 9a4849f
Show file tree
Hide file tree
Showing 269 changed files with 6,849 additions and 3,014 deletions.
10 changes: 8 additions & 2 deletions .dumi/hooks/useMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,14 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
});
}
} else {
const list = group.children || [];
// 如果有 date 字段,我们就对其进行排序
if (list.every((info) => info?.frontmatter?.date)) {
list.sort((a, b) => (a.frontmatter.date > b.frontmatter.date ? -1 : 1));
}

result.push(
...(group.children?.map((item) => ({
...list.map((item) => ({
label: (
<Link to={`${item.link}${search}`}>
{before}
Expand All @@ -125,7 +131,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] =>
</Link>
),
key: item.link.replace(/(-cn$)/g, ''),
})) ?? []),
})),
);
}
return result;
Expand Down
131 changes: 125 additions & 6 deletions .dumi/pages/theme-editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,140 @@
import React, { useState } from 'react';
import { ThemeEditor } from 'antd-token-previewer';
import { ConfigProvider } from 'antd';
import React, { useEffect } from 'react';
import { enUS, ThemeEditor, zhCN } 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 useLocale from '../../hooks/useLocale';

const locales = {
cn: {
title: '主题编辑器',
save: '保存',
reset: '重置',
export: '导出',
exportDesc: '将下面的 JSON 对象复制到 ConfigProvider 的 theme 属性中即可。',
saveSuccessfully: '保存成功',
},
en: {
title: 'Theme Editor',
save: 'Save',
reset: 'Reset',
export: 'Export',
exportDesc: 'Copy the following JSON object to the theme prop of ConfigProvider.',
saveSuccessfully: 'Saved successfully',
},
};

const useStyle = () => ({
header: css({
display: 'flex',
height: 56,
alignItems: 'center',
padding: '0 24px',
justifyContent: 'space-between',
borderBottom: '1px solid #F0F0F0',
}),
});

const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme';

const CustomTheme = () => {
const [theme, setTheme] = useState<ThemeConfig>({});
const [messageApi, contextHolder] = message.useMessage();
const [modalApi, modalContextHolder] = Modal.useModal();
const [locale, lang] = useLocale(locales);

const [theme, setTheme] = React.useState<ThemeConfig>({});

useEffect(() => {
const storedConfig = localStorage.getItem(ANT_DESIGN_V5_THEME_EDITOR_THEME);
if (storedConfig) {
setTheme(() => JSON.parse(storedConfig));
}
}, []);

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 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({});
};

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}
</Button>
<Button onClick={handleReset} style={{ marginRight: 8 }}>
{locale.reset}
</Button>
<Button type="primary" onClick={handleSave}>
{locale.save}
</Button>
</div>
</div>
<ThemeEditor
theme={{ name: 'Custom Theme', key: 'test', config: theme }}
simple
style={{ height: 'calc(100vh - 64px)' }}
style={{ height: 'calc(100vh - 64px - 56px)' }}
onThemeChange={(newTheme) => {
setTheme(newTheme.config);
}}
locale={lang === 'cn' ? zhCN : enUS}
/>
</ConfigProvider>
</div>
Expand Down
50 changes: 50 additions & 0 deletions .dumi/theme/builtins/ColorChunk/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import { TinyColor, type ColorInput } from '@ctrl/tinycolor';
import { css } from '@emotion/react';
import useSiteToken from '../../../hooks/useSiteToken';

interface ColorChunkProps {
children?: React.ReactNode;
color?: ColorInput;
}

const useStyle = () => {
const { token } = useSiteToken();

return {
codeSpan: css`
padding: 0.2em 0.4em;
font-size: 0.9em;
background: ${token.siteMarkdownCodeBg};
border-radius: ${token.borderRadius}px;
font-family: monospace;
`,
dot: css`
display: inline-block;
width: 6px;
height: 6px;
border-radius: ${token.borderRadiusSM}px;
margin-right: 4px;
border: 1px solid ${token.colorSplit};
`,
};
};

const ColorChunk: React.FC<ColorChunkProps> = (props) => {
const styles = useStyle();
const { color, children } = props;

const dotColor = React.useMemo(() => {
const _color = new TinyColor(color).toHex8String();
return _color.endsWith('ff') ? _color.slice(0, -2) : _color;
}, [color]);

return (
<span css={styles.codeSpan}>
<span css={styles.dot} style={{ backgroundColor: dotColor }} />
{children ?? dotColor}
</span>
);
};

export default ColorChunk;
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,39 @@ import React from 'react';
import classNames from 'classnames';
import { Modal, Carousel } from 'antd';

function isGood(className) {
function isGood(className: string): boolean {
return /\bgood\b/i.test(className);
}

function isBad(className) {
function isBad(className: string): boolean {
return /\bbad\b/i.test(className);
}

function isInline(className) {
function isInline(className: string): boolean {
return /\binline\b/i.test(className);
}

function PreviewImageBox({
cover,
coverMeta,
imgs,
style,
previewVisible,
comparable,
onClick,
onCancel,
}) {
function isGoodBadImg(imgMeta: any): boolean {
return imgMeta.isGood || imgMeta.isBad;
}

function isCompareImg(imgMeta: any): boolean {
return isGoodBadImg(imgMeta) || imgMeta.inline;
}

interface PreviewImageBoxProps {
coverMeta: any;
cover: React.ReactNode;
imgs: React.ReactNode[];
style: React.CSSProperties;
previewVisible: boolean;
comparable: boolean;
onClick: () => void;
onCancel: () => void;
}

const PreviewImageBox: React.FC<PreviewImageBoxProps> = (props) => {
const { cover, coverMeta, imgs, style, previewVisible, comparable, onClick, onCancel } = props;
const onlyOneImg = comparable || imgs.length === 1;
const imageWrapperClassName = classNames('preview-image-wrapper', {
good: coverMeta.isGood,
Expand Down Expand Up @@ -58,30 +69,27 @@ function PreviewImageBox({
</Modal>
</div>
);
}
};

function isGoodBadImg(imgMeta) {
return imgMeta.isGood || imgMeta.isBad;
interface ImagePreviewProps {
imgs: any[];
}

function isCompareImg(imgMeta) {
return isGoodBadImg(imgMeta) || imgMeta.inline;
interface ImagePreviewStates {
previewVisible?: Record<PropertyKey, boolean>;
}

export default class ImagePreview extends React.Component {
constructor(props) {
class ImagePreview extends React.Component<ImagePreviewProps, ImagePreviewStates> {
constructor(props: ImagePreviewProps) {
super(props);

this.state = {
previewVisible: {},
};
}

handleClick = (index) => {
handleClick = (index: number) => {
this.setState({
previewVisible: {
[index]: true,
},
previewVisible: { [index]: true },
});
};

Expand All @@ -107,7 +115,7 @@ export default class ImagePreview extends React.Component {
};
});

const imagesList = imgsMeta.map((meta, index) => {
const imagesList = imgsMeta.map<React.ReactNode>((meta, index) => {
const metaCopy = { ...meta };
delete metaCopy.description;
delete metaCopy.isGood;
Expand All @@ -125,7 +133,9 @@ export default class ImagePreview extends React.Component {
(imgs.length === 2 && imgsMeta.every(isCompareImg)) ||
(imgs.length >= 2 && imgsMeta.every(isGoodBadImg));

const style = comparable ? { width: `${(100 / imgs.length).toFixed(3)}%` } : null;
const style: React.CSSProperties = comparable
? { width: `${(100 / imgs.length).toFixed(3)}%` }
: {};

const hasCarousel = imgs.length > 1 && !comparable;
const previewClassName = classNames({
Expand All @@ -140,24 +150,25 @@ export default class ImagePreview extends React.Component {
if (!comparable && index !== 0) {
return null;
}

return (
<PreviewImageBox
key={index}
style={style}
comparable={comparable}
previewVisible={!!this.state.previewVisible[index]}
previewVisible={!!this.state.previewVisible?.[index]}
cover={imagesList[index]}
coverMeta={imgsMeta[index]}
imgs={imagesList}
onCancel={this.handleCancel}
onClick={() => {
this.handleClick(index);
}}
onCancel={this.handleCancel}
/>
);
})}
</div>
);
}
}

export default ImagePreview;

0 comments on commit 9a4849f

Please sign in to comment.