mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-23 18:50:06 +08:00
docs: v5 site upgrade (#38328)
* build: try to use dumi as doc tool
* docs: migrate demo structure to dumi way
* refactor: use type export & import
* docs: migrate demo previewer to dumi
* docs: create empty layout & components
* docs: apply custom rehype plugin
* docs: create empty extra pages
* docs: Add Banner component
* chore: move theme tsconfig.json
* docs: home page init
* docs: migrate header (#37896)
* docs: header
* docs: update
* docs: home init
* clean up
* test: fix site lint
* chore: tsc ignore demo
* chore: dumi demo migrate script
* chore: cards
* docs: home layout
* docs: Update locale logic
* docs: fix getLink logic
* chore: fix ci (#37899)
* chore: fix ci
* ci: remove check-ts-demo
* ci: preview build
* test: ignore demo.tsx
* chore: update script
* test: update snapshot
* test: update node and image test
* chore: add .surgeignore
* docs: layout providers (#37908)
* docs: add components sidebar (#37923)
* docs: sidebar
* docs: update docs title
* docs: update design doc
* chore: code clean
* docs: handle changelog page
* docs: add title
* docs: add subtitle
* docs: active header nav
* chore: code clean
* docs: overview
* chore: code clean
* docs: update intl (#37918)
* docs: update intl
* chore: code clean
* docs: update favicons
* chore: update testPathIgnorePatterns
* chore: code clean
* chore: code clean
* chore: copy 404.html (#37996)
* docs: Home page theme picker
* chore: Update migrate script
* docs: home page update
* docs: theme editor style
* docs: theme lang
* chore: update migrate.js
* docs: fix demo (#38094)
* chore: update migrate.js
* docs: update md
* docs: update demo
* test: fix snapshot
* chore: move debug to code attr in migrate script
* chore: update md
Co-authored-by: PeachScript <scdzwyxst@gmail.com>
* feat: overview page
* feat: Migrate `404` page (#38118)
* feat: migrate IconSearch component (#37916)
* feat<site/IconSearch>: copy IconDisplay from site to .dumi
* feat<site/IconSearch>: change docs of icon
* feat<site/IconSearch>: tweak
* feat<site/IconSearch>: use useIntl instead of injectIntl
* feat<site/IconSearch>: fix ts type error
* feat<site/IconSearch>: use intl.formatMessage to render text
* docs: Adjust home btn sizw
* docs: Update doc
* feat: v5 site overview page (#38131)
* feat: site
* fix: fix
* feat: v5 site overview page
* fix: fix path
* fix: fix
* fix: fix
* docs: fix margin logic
* feat: v5 site change-log page (#38137)
* feat: v5 site change-log page (#38162)
* docs: site redirect to home pag
* docs: theme picker
* docs: use react-intl from dumi (#38183)
* docs: Theme Picker
* docs: update dumi config
* docs: home back fix
* docs: picker colorful
* docs: locale of it
* docs: update components desc
* docs: site of links
* docs: update components list
* docs: update desc
* feat: Migrate `DemoWrapper` component (#38166)
* feat: Migrate `DemoWrapper` component
* feat: remove invalid comments and add comment for `key` prop
* docs: FloatButton pure panel
* chore: update demo
* chore: update dumi config
* Revert "chore: update demo"
This reverts commit 028265d3ba
.
* chore: test logic adjust to support cnpm modules
* chore: add locale alias
* docs: /index to /
* docs: add locale redirect head script
* chore: adjust compact
* docs: fix missing token
* feat: compact switch
* chore: code clean
* docs: update home
* docs: fix radius token
* docs: hash of it
* chore: adjust home page
* docs: Add background map
* docs: site theme bac logic
* docs: avatar
* docs: update logo color
* docs: home banner
* docs: adjust tour size
* docs: purepanl update
* docs: transfooter
* docs: update banner gif
* docs: content (#38361)
* docs: title & EditButton
* docs: content
* chore: fix toc
* docs: resource page
* docs: transform resource data from hast
* docs: filename & Resource Card
* chore: enable prerender
* chore: remove less
* docs: toc style
* chore: fix lint
* docs: fix Layout page
* docs: fix CP page
* chore: update demos
* docs: workaround for export dynamic html
* chore: enable demo eslint
* docs: table style
* fix: header shadow
* chore: update snapshot
* fix: toc style
* docs: add title
* docs: Adjust site
* feat: helmet
* docs: site css
* fix: description
* feat: toc debug
* docs: update config-provider
* feat: use colorPanel
* fix: colorPanel value
* feat: anchor ink ball style
* feat: apply theme editor
* fix: code block style
* chore: update demo
* chore: fix lint
* chore: code clean
* chore: update snapshot
* feat: ts2js
* chore: description
* docs: site ready for ssr
includes:
- move client render logic to useEffect in site theme
- extract antd cssinjs to a single css file like bisheng
- workaround to support react@18 pipeableStream for emotion
* chore: bump testing lib
* docs: font size of title
* chore: remove react-sortable-hoc
* chore: update snapshot
* chore: update script
Co-authored-by: PeachScript <scdzwyxst@gmail.com>
Co-authored-by: MadCcc <1075746765@qq.com>
Co-authored-by: zqran <uuxnet@gmail.com>
Co-authored-by: TrickyPi <530257315@qq.com>
Co-authored-by: lijianan <574980606@qq.com>
This commit is contained in:
parent
60d8e91bd2
commit
cbcfd38ca7
15
.dumi/hooks/useLocale.tsx
Normal file
15
.dumi/hooks/useLocale.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useLocale as useDumiLocale } from 'dumi';
|
||||||
|
|
||||||
|
export interface LocaleMap<Key extends string> {
|
||||||
|
cn: Record<Key, string>;
|
||||||
|
en: Record<Key, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useLocale<Key extends string>(
|
||||||
|
localeMap?: LocaleMap<Key>,
|
||||||
|
): [Record<Key, string>, 'cn' | 'en'] {
|
||||||
|
const { id } = useDumiLocale();
|
||||||
|
const localeType = id === 'zh-CN' ? 'cn' : ('en' as const);
|
||||||
|
return [localeMap?.[localeType]!, localeType];
|
||||||
|
}
|
47
.dumi/hooks/useLocation.ts
Normal file
47
.dumi/hooks/useLocation.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useLocation as useDumiLocation } from 'dumi';
|
||||||
|
import * as React from 'react';
|
||||||
|
import useLocale from './useLocale';
|
||||||
|
|
||||||
|
function clearPath(path: string) {
|
||||||
|
return path.replace('-cn', '').replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useLocation() {
|
||||||
|
const location = useDumiLocation();
|
||||||
|
const { search } = location;
|
||||||
|
const [, localeType] = useLocale();
|
||||||
|
|
||||||
|
const getLink = React.useCallback(
|
||||||
|
(path: string, hash?: string | { cn: string; en: string }) => {
|
||||||
|
let pathname = clearPath(path);
|
||||||
|
|
||||||
|
if (localeType === 'cn') {
|
||||||
|
pathname = `${pathname}-cn`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
pathname = `${pathname}${search}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash) {
|
||||||
|
let hashStr: string;
|
||||||
|
if (typeof hash === 'object') {
|
||||||
|
hashStr = hash[localeType];
|
||||||
|
} else {
|
||||||
|
hashStr = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathname = `${pathname}#${hashStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathname;
|
||||||
|
},
|
||||||
|
[localeType, search],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...location,
|
||||||
|
pathname: clearPath(location.pathname),
|
||||||
|
getLink,
|
||||||
|
};
|
||||||
|
}
|
138
.dumi/hooks/useMenu.tsx
Normal file
138
.dumi/hooks/useMenu.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import React, { ReactNode, useMemo } from 'react';
|
||||||
|
import { MenuProps } from 'antd';
|
||||||
|
import { Link, useFullSidebarData, useSidebarData } from 'dumi';
|
||||||
|
import useLocation from './useLocation';
|
||||||
|
|
||||||
|
export type UseMenuOptions = {
|
||||||
|
before?: ReactNode;
|
||||||
|
after?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => {
|
||||||
|
const fullData = useFullSidebarData();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const sidebarData = useSidebarData();
|
||||||
|
const { before, after } = options;
|
||||||
|
|
||||||
|
const menuItems = useMemo<MenuProps['items']>(() => {
|
||||||
|
const sidebarItems = [...(sidebarData ?? [])];
|
||||||
|
|
||||||
|
// 将设计文档未分类的放在最后
|
||||||
|
if (pathname.startsWith('/docs/spec')) {
|
||||||
|
const notGrouped = sidebarItems.splice(0, 1);
|
||||||
|
sidebarItems.push(...notGrouped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把 /changelog 拼到开发文档中
|
||||||
|
if (pathname.startsWith('/docs/react')) {
|
||||||
|
const changelogData = Object.entries(fullData).find(([key]) =>
|
||||||
|
key.startsWith('/changelog'),
|
||||||
|
)?.[1];
|
||||||
|
if (changelogData) {
|
||||||
|
sidebarItems.push(...changelogData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pathname.startsWith('/changelog')) {
|
||||||
|
const reactDocData = Object.entries(fullData).find(([key]) =>
|
||||||
|
key.startsWith('/docs/react'),
|
||||||
|
)?.[1];
|
||||||
|
if (reactDocData) {
|
||||||
|
sidebarItems.unshift(...reactDocData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
sidebarItems?.reduce<Exclude<MenuProps['items'], undefined>>((result, group) => {
|
||||||
|
if (group.title) {
|
||||||
|
// 设计文档特殊处理二级分组
|
||||||
|
if (pathname.startsWith('/docs/spec')) {
|
||||||
|
const childrenGroup = group.children.reduce<
|
||||||
|
Record<string, ReturnType<typeof useSidebarData>[number]['children']>
|
||||||
|
>((childrenResult, child) => {
|
||||||
|
const type = (child.frontmatter as any).type ?? 'default';
|
||||||
|
if (!childrenResult[type]) {
|
||||||
|
childrenResult[type] = [];
|
||||||
|
}
|
||||||
|
childrenResult[type].push(child);
|
||||||
|
return childrenResult;
|
||||||
|
}, {});
|
||||||
|
const childItems = [];
|
||||||
|
childItems.push(
|
||||||
|
...childrenGroup.default.map(item => ({
|
||||||
|
label: (
|
||||||
|
<Link to={item.link}>
|
||||||
|
{before}
|
||||||
|
{item.title}
|
||||||
|
{after}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
key: item.link.replace(/(-cn$)/g, ''),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
Object.entries(childrenGroup).forEach(([type, children]) => {
|
||||||
|
if (type !== 'default') {
|
||||||
|
childItems.push({
|
||||||
|
type: 'group',
|
||||||
|
label: type,
|
||||||
|
key: type,
|
||||||
|
children: children?.map(item => ({
|
||||||
|
label: (
|
||||||
|
<Link to={item.link}>
|
||||||
|
{before}
|
||||||
|
{item.title}
|
||||||
|
{after}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
key: item.link.replace(/(-cn$)/g, ''),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.push({
|
||||||
|
label: group.title,
|
||||||
|
key: group.title,
|
||||||
|
children: childItems,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.push({
|
||||||
|
type: 'group',
|
||||||
|
label: group.title,
|
||||||
|
key: group.title,
|
||||||
|
children: group.children?.map(item => ({
|
||||||
|
label: (
|
||||||
|
<Link to={item.link}>
|
||||||
|
{before}
|
||||||
|
<span key="english">{item.title}</span>
|
||||||
|
<span className="chinese" key="chinese">
|
||||||
|
{(item.frontmatter as any).subtitle}
|
||||||
|
</span>
|
||||||
|
{after}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
key: item.link.replace(/(-cn$)/g, ''),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(
|
||||||
|
...group.children?.map(item => ({
|
||||||
|
label: (
|
||||||
|
<Link to={item.link}>
|
||||||
|
{before}
|
||||||
|
{item.title}
|
||||||
|
{after}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
key: item.link.replace(/(-cn$)/g, ''),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, []) ?? []
|
||||||
|
);
|
||||||
|
}, [sidebarData, fullData, pathname]);
|
||||||
|
|
||||||
|
return [menuItems, pathname];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMenu;
|
35
.dumi/hooks/useSiteToken.ts
Normal file
35
.dumi/hooks/useSiteToken.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { theme } from 'antd';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { ConfigContext } from 'antd/es/config-provider';
|
||||||
|
|
||||||
|
const { useToken } = theme;
|
||||||
|
|
||||||
|
const useSiteToken = () => {
|
||||||
|
const result = useToken();
|
||||||
|
const { getPrefixCls, iconPrefixCls } = useContext(ConfigContext);
|
||||||
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
const { token } = result;
|
||||||
|
const siteMarkdownCodeBg = token.colorFillTertiary;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
token: {
|
||||||
|
...token,
|
||||||
|
headerHeight: 64,
|
||||||
|
menuItemBorder: 2,
|
||||||
|
mobileMaxWidth: 767.99,
|
||||||
|
siteMarkdownCodeBg,
|
||||||
|
antCls: `.${rootPrefixCls}`,
|
||||||
|
iconCls: `.${iconPrefixCls}`,
|
||||||
|
/** 56 */
|
||||||
|
marginFarXS: (token.marginXXL / 6) * 7,
|
||||||
|
/** 80 */
|
||||||
|
marginFarSM: (token.marginXXL / 3) * 5,
|
||||||
|
/** 96 */
|
||||||
|
marginFar: token.marginXXL * 2,
|
||||||
|
codeFamily: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSiteToken;
|
@ -1,20 +1,10 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Link } from 'bisheng/router';
|
|
||||||
import { Result, Button } from 'antd';
|
import { Result, Button } from 'antd';
|
||||||
import { HomeOutlined } from '@ant-design/icons';
|
import { HomeOutlined } from '@ant-design/icons';
|
||||||
import * as utils from './utils';
|
import { Link, useLocation } from 'dumi';
|
||||||
|
import * as utils from '../../theme/utils';
|
||||||
|
|
||||||
export interface NotFoundProps {
|
export interface NotFoundProps {
|
||||||
location: {
|
|
||||||
pathname: string;
|
|
||||||
search: string;
|
|
||||||
hash: string;
|
|
||||||
state: any;
|
|
||||||
action: string;
|
|
||||||
key: any;
|
|
||||||
basename: string;
|
|
||||||
query: Record<string, string>;
|
|
||||||
};
|
|
||||||
router: {
|
router: {
|
||||||
push: (pathname: string) => void;
|
push: (pathname: string) => void;
|
||||||
replace: (pathname: string) => void;
|
replace: (pathname: string) => void;
|
||||||
@ -26,11 +16,8 @@ const DIRECT_MAP: Record<string, string> = {
|
|||||||
'docs/spec/work-with-us': 'docs/resources',
|
'docs/spec/work-with-us': 'docs/resources',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NotFound(props: NotFoundProps) {
|
const NotFoundPage: React.FC<NotFoundProps> = ({ router }) => {
|
||||||
const {
|
const { pathname } = useLocation();
|
||||||
location: { pathname },
|
|
||||||
router,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const isZhCN = utils.isZhCN(pathname);
|
const isZhCN = utils.isZhCN(pathname);
|
||||||
|
|
||||||
@ -62,11 +49,8 @@ export default function NotFound(props: NotFoundProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<style
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: '#react-content { height: 100%; background-color: #fff }',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default NotFoundPage;
|
1
.dumi/pages/index-cn/index.tsx
Normal file
1
.dumi/pages/index-cn/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from '../index/index';
|
135
.dumi/pages/index/components/Banner.tsx
Normal file
135
.dumi/pages/index/components/Banner.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Space, Button, Typography, theme } from 'antd';
|
||||||
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { GroupMask } from './Group';
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
slogan: '助力设计开发者「更灵活」的搭建出「更美」的产品,让用户「快乐工作」~',
|
||||||
|
start: '开始使用',
|
||||||
|
designLanguage: '设计语言',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
slogan:
|
||||||
|
'Help design developers "more flexible" to build "more beautiful" products, helping users to "work happily"~',
|
||||||
|
start: 'Get Start',
|
||||||
|
designLanguage: 'Design Language',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface BannerProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Banner({ children }: BannerProps) {
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Banner Placeholder Motion */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 360,
|
||||||
|
// height: 200,
|
||||||
|
background: '#77C6FF',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*6d50SboraPIAAAAAAAAAAAAAARQnAQ)`,
|
||||||
|
flex: 'auto',
|
||||||
|
backgroundRepeat: 'repeat-x',
|
||||||
|
backgroundPosition: '100% 0',
|
||||||
|
backgroundSize: 'auto 100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <img
|
||||||
|
alt="banner"
|
||||||
|
src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*I9a5ToqP4x8AAAAAAAAAAAAAARQnAQ"
|
||||||
|
style={{ height: '100%', flex: 'none' }}
|
||||||
|
/> */}
|
||||||
|
<video
|
||||||
|
src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/file/A*XYYNQJ3NbmMAAAAAAAAAAAAAARQnAQ"
|
||||||
|
style={{ height: '100%', objectFit: 'contain' }}
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*8ILtRrQlVDMAAAAAAAAAAAAAARQnAQ)`,
|
||||||
|
flex: 'auto',
|
||||||
|
backgroundRepeat: 'repeat-x',
|
||||||
|
backgroundPosition: '0 0',
|
||||||
|
backgroundSize: 'auto 100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logo */}
|
||||||
|
<div style={{ position: 'relative', background: '#fff' }}>
|
||||||
|
{/* Image Bottom Right */}
|
||||||
|
<img
|
||||||
|
style={{ position: 'absolute', right: 0, top: 240, width: 240 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/b3b8dc41-dce8-471f-9d81-9a0204f27d03.svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GroupMask
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingTop: token.marginFar,
|
||||||
|
paddingBottom: token.marginFarSM,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Image Left Top */}
|
||||||
|
<img
|
||||||
|
style={{ position: 'absolute', left: 0, top: 0, width: 240 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/49f963db-b2a8-4f15-857a-270d771a1204.svg"
|
||||||
|
/>
|
||||||
|
{/* Image Left Top */}
|
||||||
|
<img
|
||||||
|
style={{ position: 'absolute', right: 120, top: 0, width: 240 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/e152223c-bcae-4913-8938-54fda9efe330.svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Typography.Title
|
||||||
|
level={1}
|
||||||
|
style={{
|
||||||
|
fontFamily: `AliPuHui, ${token.fontFamily}`,
|
||||||
|
fontSize: token.fontSizes[9],
|
||||||
|
lineHeight: token.lineHeights[9],
|
||||||
|
fontWeight: 900,
|
||||||
|
marginBottom: token.marginMD,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ant Design 5.0
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Paragraph
|
||||||
|
style={{
|
||||||
|
fontSize: token.fontSizeHeading5,
|
||||||
|
lineHeight: token.lineHeightHeading5,
|
||||||
|
marginBottom: token.marginMD * 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{locale.slogan}</div>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
|
||||||
|
<Space size="middle" style={{ marginBottom: token.marginFar }}>
|
||||||
|
<Button size="large" type="primary">
|
||||||
|
{locale.start}
|
||||||
|
</Button>
|
||||||
|
<Button size="large">{locale.designLanguage}</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</GroupMask>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
73
.dumi/pages/index/components/BannerRecommends.tsx
Normal file
73
.dumi/pages/index/components/BannerRecommends.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { Extra, Icon } from './util';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { Col, Row, Card, Typography } from 'antd';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
border: ${token.lineWidth}px solid ${token.colorBorderSecondary};
|
||||||
|
border-radius: ${token.borderRadiusLG}px;
|
||||||
|
padding-block: ${token.paddingMD}px;
|
||||||
|
padding-inline: ${token.paddingLG}px;
|
||||||
|
flex: 1 1 0;
|
||||||
|
width: 33%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
background: ${token.colorBgContainer};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: ${token.boxShadowCard};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface BannerRecommendsProps {
|
||||||
|
extras?: Extra[];
|
||||||
|
icons?: Icon[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BannerRecommends({ extras = [], icons = [] }: BannerRecommendsProps) {
|
||||||
|
const style = useStyle();
|
||||||
|
const first3 = extras.slice(0, 3);
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1208,
|
||||||
|
marginInline: 'auto',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
paddingInline: token.marginXXL,
|
||||||
|
display: 'flex',
|
||||||
|
columnGap: token.paddingMD * 2,
|
||||||
|
alignItems: 'stretch',
|
||||||
|
textAlign: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{first3.map((extra, index) => {
|
||||||
|
const icon = icons.find(icon => icon.name === extra.source);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a key={index} href={extra.href} target="_blank" css={style.card}>
|
||||||
|
<Typography.Title level={5}>{extra.title}</Typography.Title>
|
||||||
|
<Typography.Paragraph type="secondary" style={{ flex: 'auto' }}>
|
||||||
|
{extra.description}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Typography.Text>{extra.date}</Typography.Text>
|
||||||
|
{icon && <img src={icon.href} style={{ height: token.fontSize }} />}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
225
.dumi/pages/index/components/ComponentsList.tsx
Normal file
225
.dumi/pages/index/components/ComponentsList.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Tour,
|
||||||
|
Tag,
|
||||||
|
DatePicker,
|
||||||
|
Alert,
|
||||||
|
Modal,
|
||||||
|
FloatButton,
|
||||||
|
Progress,
|
||||||
|
} from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { CustomerServiceOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
|
const PLACEHOLDER_WIDTH = 400;
|
||||||
|
|
||||||
|
const SAMPLE_CONTENT =
|
||||||
|
'Ant Design 5.0 use CSS-in-JS technology to provide dynamic & mix theme ability. And which use component level CSS-in-JS solution get your application a better performance.';
|
||||||
|
|
||||||
|
const COMPONENTS: {
|
||||||
|
title: React.ReactNode;
|
||||||
|
type: 'new' | 'update';
|
||||||
|
node: React.ReactNode;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
title: 'Modal',
|
||||||
|
type: 'update',
|
||||||
|
node: (
|
||||||
|
<Modal._InternalPanelDoNotUseOrYouWillBeFired title="Ant Design 5.0" width={300}>
|
||||||
|
{SAMPLE_CONTENT}
|
||||||
|
</Modal._InternalPanelDoNotUseOrYouWillBeFired>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'DatePicker',
|
||||||
|
type: 'update',
|
||||||
|
node: (
|
||||||
|
<DatePicker._InternalPanelDoNotUseOrYouWillBeFired
|
||||||
|
showToday={false}
|
||||||
|
presets={[
|
||||||
|
{ label: 'Yesterday', value: dayjs().add(-1, 'd') },
|
||||||
|
{ label: 'Last Week', value: dayjs().add(-7, 'd') },
|
||||||
|
{ label: 'Last Month', value: dayjs().add(-1, 'month') },
|
||||||
|
{ label: 'Last Year', value: dayjs().add(-1, 'year') },
|
||||||
|
]}
|
||||||
|
value={dayjs('2022-11-18 14:00:00')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Progress',
|
||||||
|
type: 'update',
|
||||||
|
node: (
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Space>
|
||||||
|
<Progress type="circle" trailColor="#e6f4ff" percent={60} width={14} />
|
||||||
|
In Progress
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Progress type="circle" percent={100} width={14} />
|
||||||
|
Success
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Progress type="circle" status="exception" percent={88} width={14} />
|
||||||
|
Task Failed
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Tour',
|
||||||
|
type: 'new',
|
||||||
|
node: (
|
||||||
|
<Tour._InternalPanelDoNotUseOrYouWillBeFired
|
||||||
|
title="Ant Design 5.0"
|
||||||
|
description="A quick guide for new come user about how to use app."
|
||||||
|
style={{ width: 350 }}
|
||||||
|
current={3}
|
||||||
|
total={9}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'FloatButton',
|
||||||
|
type: 'new',
|
||||||
|
node: (
|
||||||
|
<Space size="large">
|
||||||
|
<FloatButton._InternalPanelDoNotUseOrYouWillBeFired
|
||||||
|
shape="square"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
icon: <QuestionCircleOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <CustomerServiceOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <SyncOutlined />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<FloatButton._InternalPanelDoNotUseOrYouWillBeFired backTop />
|
||||||
|
<FloatButton._InternalPanelDoNotUseOrYouWillBeFired
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
icon: <QuestionCircleOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <CustomerServiceOutlined />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <SyncOutlined />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// title: 'Steps',
|
||||||
|
// type: 'update',
|
||||||
|
// node: <Button style={{ width: PLACEHOLDER_WIDTH }}>Placeholder</Button>,
|
||||||
|
// },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Alert',
|
||||||
|
type: 'update',
|
||||||
|
node: (
|
||||||
|
<Alert
|
||||||
|
style={{ width: 400 }}
|
||||||
|
message="Ant Design 5.0"
|
||||||
|
description={SAMPLE_CONTENT}
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
background: #f5f8ff;
|
||||||
|
padding: ${token.paddingXL}px;
|
||||||
|
flex: none;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
cardCircle: css`
|
||||||
|
position: absolute;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
background: #1677ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(40px);
|
||||||
|
opacity: 0.1;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ComponentsList() {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', overflow: 'hidden', display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'stretch', columnGap: token.paddingLG }}>
|
||||||
|
{COMPONENTS.map(({ title, node, type }, index) => {
|
||||||
|
const tagColor = type === 'new' ? 'processing' : 'warning';
|
||||||
|
const tagText = type === 'new' ? 'New' : 'Update';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index} css={styles.card}>
|
||||||
|
{/* Decorator */}
|
||||||
|
<div
|
||||||
|
css={styles.cardCircle}
|
||||||
|
style={{
|
||||||
|
right: (index % 2) * -20 - 20,
|
||||||
|
bottom: (index % 3) * -40 - 20,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<Space>
|
||||||
|
<Typography.Title level={4} style={{ fontWeight: 'normal', margin: 0 }}>
|
||||||
|
{title}
|
||||||
|
</Typography.Title>
|
||||||
|
<Tag color={tagColor}>{tagText}</Tag>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: token.paddingLG,
|
||||||
|
flex: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{node}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
162
.dumi/pages/index/components/DesignFramework.tsx
Normal file
162
.dumi/pages/index/components/DesignFramework.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { Col, Row, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
|
||||||
|
const MAINLY_LIST = [
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/bmw-prod/36a89a46-4224-46e2-b838-00817f5eb364.svg',
|
||||||
|
key: 'values',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/bmw-prod/8379430b-e328-428e-8a67-666d1dd47f7d.svg',
|
||||||
|
key: 'guide',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/bmw-prod/1c363c0b-17c6-4b00-881a-bc774df1ebeb.svg',
|
||||||
|
key: 'lib',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const SECONDARY_LIST = [
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/bmw-prod/b874caa9-4458-412a-9ac6-a61486180a62.svg',
|
||||||
|
key: 'mobile',
|
||||||
|
url: 'https://mobile.ant.design/',
|
||||||
|
imgScale: 1.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||||
|
key: 'antv',
|
||||||
|
url: 'https://antv.vision/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/bmw-prod/af1ea898-bf02-45d1-9f30-8ca851c70a5b.svg',
|
||||||
|
key: 'kitchen',
|
||||||
|
url: 'https://kitchen.alipay.com/',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
values: '设计价值观',
|
||||||
|
valuesDesc: '确定性、意义感、生长性、自然',
|
||||||
|
guide: '设计指引',
|
||||||
|
guideDesc: '全局样式、设计模式',
|
||||||
|
lib: '组件库',
|
||||||
|
libDesc: 'Ant Design of React / Angular / Vue',
|
||||||
|
|
||||||
|
// Secondary
|
||||||
|
mobile: 'Ant Design Mobile',
|
||||||
|
mobileDesc: '基于 Preact / React / React Native 的 UI 组件库',
|
||||||
|
antv: 'AntV',
|
||||||
|
antvDesc: '全新一代数据可视化解决方案',
|
||||||
|
kitchen: 'Kitchen',
|
||||||
|
kitchenDesc: '一款为设计者提升工作效率的 Sketch 工具集',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
values: 'Design values',
|
||||||
|
valuesDesc: 'Certainty, Meaningfulness, Growth, Naturalness',
|
||||||
|
guide: 'Design guide',
|
||||||
|
guideDesc: 'Global style and design pattern',
|
||||||
|
lib: 'Components Libraries',
|
||||||
|
libDesc: 'Ant Design of React / Angular / Vue',
|
||||||
|
|
||||||
|
// Secondary
|
||||||
|
mobile: 'Ant Design Mobile',
|
||||||
|
mobileDesc: 'Mobile UI component library',
|
||||||
|
antv: 'AntV',
|
||||||
|
antvDesc: 'New generation of data visualization solutions',
|
||||||
|
kitchen: 'Kitchen',
|
||||||
|
kitchenDesc: 'Sketch Tool set for designers',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
padding: ${token.paddingSM}px;
|
||||||
|
border-radius: ${token.borderRadius * 2}px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.02);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
cardMini: css`
|
||||||
|
display: block;
|
||||||
|
border-radius: ${token.borderRadius * 2}px;
|
||||||
|
padding: ${token.paddingMD}px ${token.paddingLG}px;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DesignFramework() {
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={[token.marginXL, token.marginXL]}>
|
||||||
|
{MAINLY_LIST.map(({ img, key }, index) => {
|
||||||
|
const title = locale[key as keyof typeof locale];
|
||||||
|
const desc = locale[`${key}Desc` as keyof typeof locale];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col key={index} span={8}>
|
||||||
|
<div css={style.card}>
|
||||||
|
<img alt={title} src={img} />
|
||||||
|
|
||||||
|
<Typography.Title
|
||||||
|
level={4}
|
||||||
|
style={{ marginTop: token.margin, marginBottom: token.marginXS }}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>
|
||||||
|
{desc}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{SECONDARY_LIST.map(({ img, key, url, imgScale = 1 }, index) => {
|
||||||
|
const title = locale[key as keyof typeof locale];
|
||||||
|
const desc = locale[`${key}Desc` as keyof typeof locale];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col key={index} span={8}>
|
||||||
|
<a css={style.cardMini} target="_blank" href={url}>
|
||||||
|
<img alt={title} src={img} style={{ transform: `scale(${imgScale})` }} />
|
||||||
|
|
||||||
|
<Typography.Title
|
||||||
|
level={4}
|
||||||
|
style={{ marginTop: token.margin, marginBottom: token.marginXS }}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Paragraph type="secondary" style={{ margin: 0 }}>
|
||||||
|
{desc}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</a>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
108
.dumi/pages/index/components/Group.tsx
Normal file
108
.dumi/pages/index/components/Group.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
export interface GroupMaskProps {
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupMask({ children, style, disabled }: GroupMaskProps) {
|
||||||
|
const additionalStyle: React.CSSProperties = disabled
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
position: 'relative',
|
||||||
|
background: `rgba(255,255,255,0.1)`,
|
||||||
|
backdropFilter: `blur(25px)`,
|
||||||
|
zIndex: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="site-mask"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
...style,
|
||||||
|
...additionalStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupProps {
|
||||||
|
id?: string;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
titleColor?: string;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
background?: string;
|
||||||
|
|
||||||
|
/** 是否不使用两侧 margin */
|
||||||
|
collapse?: boolean;
|
||||||
|
|
||||||
|
decoration?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Group(props: GroupProps) {
|
||||||
|
const { id, title, titleColor, description, children, decoration, background, collapse } = props;
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const marginStyle: React.CSSProperties = collapse
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
maxWidth: 1208,
|
||||||
|
marginInline: 'auto',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
paddingInline: token.marginXXL,
|
||||||
|
};
|
||||||
|
let childNode = (
|
||||||
|
<>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<Typography.Title
|
||||||
|
id={id}
|
||||||
|
level={1}
|
||||||
|
style={{
|
||||||
|
fontWeight: 900,
|
||||||
|
color: titleColor,
|
||||||
|
// Special for the title
|
||||||
|
fontFamily: `AliPuHui, ${token.fontFamily}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Paragraph style={{ marginBottom: token.marginFarXS, color: titleColor }}>
|
||||||
|
{description}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={marginStyle}>
|
||||||
|
{children ? (
|
||||||
|
<div>{children}</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{ borderRadius: token.borderRadiusLG, minHeight: 300, background: '#e9e9e9' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ position: 'relative', background, transition: `all ${token.motionDurationSlow}` }}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'absolute', inset: 0 }}>{decoration}</div>
|
||||||
|
<GroupMask
|
||||||
|
disabled={!!background}
|
||||||
|
style={{
|
||||||
|
paddingBlock: token.marginFarSM,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{childNode}
|
||||||
|
</GroupMask>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
104
.dumi/pages/index/components/RecommendsOld.tsx
Normal file
104
.dumi/pages/index/components/RecommendsOld.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Row, Col, Typography } from 'antd';
|
||||||
|
import type { Recommendation } from './util';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
height: 300px;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
background: linear-gradient(
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0.25) 40%,
|
||||||
|
rgba(0, 0, 0, 0.65) 100%
|
||||||
|
);
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: all 0.5s;
|
||||||
|
content: '';
|
||||||
|
pointer-events: none;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&:before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
transform: translateY(0);
|
||||||
|
|
||||||
|
h4${token.antCls}-typography {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
|
||||||
|
${token.antCls}-typography {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: normal;
|
||||||
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
}
|
||||||
|
|
||||||
|
h4${token.antCls}-typography {
|
||||||
|
position: absolute;
|
||||||
|
padding: 0 ${token.paddingMD}px ${token.paddingMD}px;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
div${token.antCls}-typography {
|
||||||
|
padding: ${token.paddingXS}px ${token.paddingMD}px ${token.paddingLG}px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RecommendsProps {
|
||||||
|
recommendations?: Recommendation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Recommends({ recommendations = [] }: RecommendsProps) {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={token.marginLG}>
|
||||||
|
{new Array(3).fill(null).map((_, index) => {
|
||||||
|
const data = recommendations[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col key={index} span={8}>
|
||||||
|
{data ? (
|
||||||
|
<div css={style.card} style={{ backgroundImage: `url(${data.img})` }}>
|
||||||
|
<div className="intro">
|
||||||
|
<Typography.Title level={4}>{data.title}</Typography.Title>
|
||||||
|
<Typography.Paragraph>{data.description}</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
54
.dumi/pages/index/components/Theme/BackgroundImage.tsx
Normal file
54
.dumi/pages/index/components/Theme/BackgroundImage.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||||
|
import { COLOR_IMAGES, DEFAULT_COLOR, getClosetColor } from './colorUtil';
|
||||||
|
|
||||||
|
export interface BackgroundImageProps {
|
||||||
|
colorPrimary?: string;
|
||||||
|
isLight?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BackgroundImage({ colorPrimary, isLight }: BackgroundImageProps) {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const activeColor = React.useMemo(() => getClosetColor(colorPrimary), [colorPrimary]);
|
||||||
|
|
||||||
|
const sharedStyle: React.CSSProperties = {
|
||||||
|
transition: `all ${token.motionDurationSlow}`,
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{COLOR_IMAGES.map(({ color, url }) => {
|
||||||
|
if (!url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
key={color}
|
||||||
|
style={{
|
||||||
|
...sharedStyle,
|
||||||
|
opacity: isLight && activeColor === color ? 1 : 0,
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'right top',
|
||||||
|
}}
|
||||||
|
src={url}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* <div
|
||||||
|
style={{
|
||||||
|
...sharedStyle,
|
||||||
|
opacity: isLight || !activeColor || activeColor === DEFAULT_COLOR ? 0 : 1,
|
||||||
|
background: 'rgba(0,0,0,0.79)',
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
127
.dumi/pages/index/components/Theme/ColorPicker.tsx
Normal file
127
.dumi/pages/index/components/Theme/ColorPicker.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||||
|
import { Input, Space, Popover } from 'antd';
|
||||||
|
import React, { FC, useEffect, useState } from 'react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import { TinyColor } from '@ctrl/tinycolor';
|
||||||
|
import { PRESET_COLORS } from './colorUtil';
|
||||||
|
import ColorPanel, { ColorPanelProps } from 'antd-token-previewer/es/ColorPanel';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
color: css`
|
||||||
|
width: ${token.controlHeightLG / 2}px;
|
||||||
|
height: ${token.controlHeightLG / 2}px;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all ${token.motionDurationFast};
|
||||||
|
`,
|
||||||
|
|
||||||
|
colorActive: css`
|
||||||
|
box-shadow: 0 0 0 1px ${token.colorBgContainer},
|
||||||
|
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebouncedColorPanel: FC<ColorPanelProps> = ({ color, onChange }) => {
|
||||||
|
const [value, setValue] = useState(color);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
onChange?.(value);
|
||||||
|
}, 200);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(color);
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
return <ColorPanel color={value} onChange={setValue} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RadiusPickerProps {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ColorPicker({ value, onChange }: RadiusPickerProps) {
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
const matchColors = React.useMemo(() => {
|
||||||
|
const valueStr = new TinyColor(value).toRgbString();
|
||||||
|
let existActive = false;
|
||||||
|
|
||||||
|
const colors = PRESET_COLORS.map(color => {
|
||||||
|
const colorStr = new TinyColor(color).toRgbString();
|
||||||
|
const active = colorStr === valueStr;
|
||||||
|
existActive = existActive || active;
|
||||||
|
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
active,
|
||||||
|
picker: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...colors,
|
||||||
|
{
|
||||||
|
color: 'conic-gradient(red, yellow, lime, aqua, blue, magenta, red)',
|
||||||
|
picker: true,
|
||||||
|
active: !existActive,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space size="large">
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={event => {
|
||||||
|
onChange?.(event.target.value);
|
||||||
|
}}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space size="middle">
|
||||||
|
{matchColors.map(({ color, active, picker }) => {
|
||||||
|
let colorNode = (
|
||||||
|
<div
|
||||||
|
key={color}
|
||||||
|
css={[style.color, active && style.colorActive]}
|
||||||
|
style={{
|
||||||
|
background: color,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!picker) {
|
||||||
|
onChange?.(color);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picker) {
|
||||||
|
colorNode = (
|
||||||
|
<Popover
|
||||||
|
key={color}
|
||||||
|
overlayInnerStyle={{ padding: 0 }}
|
||||||
|
content={
|
||||||
|
<DebouncedColorPanel color={value || ''} onChange={color => onChange?.(color)} />
|
||||||
|
}
|
||||||
|
trigger="click"
|
||||||
|
showArrow={false}
|
||||||
|
>
|
||||||
|
{colorNode}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorNode;
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
31
.dumi/pages/index/components/Theme/RadiusPicker.tsx
Normal file
31
.dumi/pages/index/components/Theme/RadiusPicker.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { InputNumber, Space, Slider } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface RadiusPickerProps {
|
||||||
|
value?: number;
|
||||||
|
onChange?: (value: number | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RadiusPicker({ value, onChange }: RadiusPickerProps) {
|
||||||
|
return (
|
||||||
|
<Space size="large">
|
||||||
|
<InputNumber
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
min={0}
|
||||||
|
formatter={val => `${val}px`}
|
||||||
|
parser={str => (str ? parseFloat(str) : (str as any))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
tooltip={{ open: false }}
|
||||||
|
style={{ width: 128 }}
|
||||||
|
min={0}
|
||||||
|
value={value}
|
||||||
|
max={20}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
96
.dumi/pages/index/components/Theme/ThemePicker.tsx
Normal file
96
.dumi/pages/index/components/Theme/ThemePicker.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { css } from '@emotion/react';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
import * as React from 'react';
|
||||||
|
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||||
|
import useLocale from '../../../../hooks/useLocale';
|
||||||
|
|
||||||
|
export const THEMES = {
|
||||||
|
default: 'https://gw.alipayobjects.com/zos/bmw-prod/ae669a89-0c65-46db-b14b-72d1c7dd46d6.svg',
|
||||||
|
dark: 'https://gw.alipayobjects.com/zos/bmw-prod/0f93c777-5320-446b-9bb7-4d4b499f346d.svg',
|
||||||
|
lark: 'https://gw.alipayobjects.com/zos/bmw-prod/3e899b2b-4eb4-4771-a7fc-14c7ff078aed.svg',
|
||||||
|
comic: 'https://gw.alipayobjects.com/zos/bmw-prod/ed9b04e8-9b8d-4945-8f8a-c8fc025e846f.svg',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type THEME = keyof typeof THEMES;
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
default: '默认',
|
||||||
|
dark: '暗黑',
|
||||||
|
lark: '知识协作',
|
||||||
|
comic: '二次元',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
default: 'Default',
|
||||||
|
dark: 'Dark',
|
||||||
|
lark: 'Document',
|
||||||
|
comic: 'Comic',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
themeCard: css`
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: top;
|
||||||
|
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||||
|
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
themeCardActive: css`
|
||||||
|
box-shadow: 0 0 0 1px ${token.colorBgContainer},
|
||||||
|
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ThemePickerProps {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ThemePicker({ value, onChange }: ThemePickerProps) {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space size={token.paddingLG}>
|
||||||
|
{Object.keys(THEMES).map(theme => {
|
||||||
|
const url = THEMES[theme as THEME];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space key={theme} direction="vertical" align="center">
|
||||||
|
<div css={[style.themeCard, value === theme && style.themeCardActive]}>
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
onClick={() => {
|
||||||
|
onChange?.(theme);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>{locale[theme as keyof typeof locale]}</span>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
78
.dumi/pages/index/components/Theme/colorUtil.ts
Normal file
78
.dumi/pages/index/components/Theme/colorUtil.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { TinyColor } from '@ctrl/tinycolor';
|
||||||
|
|
||||||
|
export const DEFAULT_COLOR = '#1677FF';
|
||||||
|
|
||||||
|
export const COLOR_IMAGES = [
|
||||||
|
{
|
||||||
|
color: DEFAULT_COLOR,
|
||||||
|
// url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*QEAoSL8uVi4AAAAAAAAAAAAAARQnAQ',
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#5A54F9',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*MtVDSKukKj8AAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#9E339F',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*FMluR4vJhaQAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#FB7299',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*xJOWQZDpSjkAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#E0282E',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*w6xcR7MriwEAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#F4801A',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*VWFOTbEyU9wAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#F2BD27',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*1yydQLzw5nYAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '#00B96B',
|
||||||
|
url: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*XpGeRoZKGycAAAAAAAAAAAAAARQnAQ',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const PRESET_COLORS = COLOR_IMAGES.map(({ color }) => color);
|
||||||
|
|
||||||
|
const DISTANCE = 33;
|
||||||
|
export function getClosetColor(colorPrimary?: string | null) {
|
||||||
|
if (!colorPrimary) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorPrimaryRGB = new TinyColor(colorPrimary).toRgb();
|
||||||
|
|
||||||
|
const distance = COLOR_IMAGES.map(({ color }) => {
|
||||||
|
const colorObj = new TinyColor(color).toRgb();
|
||||||
|
const dist = Math.sqrt(
|
||||||
|
Math.pow(colorObj.r - colorPrimaryRGB.r, 2) +
|
||||||
|
Math.pow(colorObj.g - colorPrimaryRGB.g, 2) +
|
||||||
|
Math.pow(colorObj.b - colorPrimaryRGB.b, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { color, dist };
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstMatch = distance.sort((a, b) => a.dist - b.dist)[0];
|
||||||
|
|
||||||
|
return firstMatch.dist <= DISTANCE ? firstMatch.color : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAvatarURL(color?: string | null) {
|
||||||
|
const closestColor = getClosetColor(color);
|
||||||
|
|
||||||
|
if (!closestColor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
COLOR_IMAGES.find(obj => obj.color === closestColor)?.url ||
|
||||||
|
'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*CLp0Qqc11AkAAAAAAAAAAAAAARQnAQ'
|
||||||
|
);
|
||||||
|
}
|
521
.dumi/pages/index/components/Theme/index.tsx
Normal file
521
.dumi/pages/index/components/Theme/index.tsx
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import { TinyColor } from '@ctrl/tinycolor';
|
||||||
|
import {
|
||||||
|
HomeOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
BellOutlined,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import useLocale from '../../../../hooks/useLocale';
|
||||||
|
import useSiteToken from '../../../../hooks/useSiteToken';
|
||||||
|
import {
|
||||||
|
Typography,
|
||||||
|
Layout,
|
||||||
|
Menu,
|
||||||
|
Breadcrumb,
|
||||||
|
MenuProps,
|
||||||
|
Space,
|
||||||
|
ConfigProvider,
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Radio,
|
||||||
|
theme,
|
||||||
|
} from 'antd';
|
||||||
|
import ThemePicker, { THEME } from './ThemePicker';
|
||||||
|
import ColorPicker from './ColorPicker';
|
||||||
|
import RadiusPicker from './RadiusPicker';
|
||||||
|
import Group from '../Group';
|
||||||
|
import BackgroundImage from './BackgroundImage';
|
||||||
|
import { PRESET_COLORS, getClosetColor, DEFAULT_COLOR, getAvatarURL } from './colorUtil';
|
||||||
|
|
||||||
|
const { Header, Content, Sider } = Layout;
|
||||||
|
|
||||||
|
const TokenChecker = () => {
|
||||||
|
console.log('Demo Token:', theme.useToken());
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================= Theme =============================
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
themeTitle: '定制主题,随心所欲',
|
||||||
|
themeDesc: 'Ant Design 5.0 开放更多样式算法,让你定制主题更简单',
|
||||||
|
|
||||||
|
customizeTheme: '定制主题',
|
||||||
|
myTheme: '我的主题',
|
||||||
|
titlePrimaryColor: '主色',
|
||||||
|
titleBorderRadius: '圆角',
|
||||||
|
titleCompact: '宽松度',
|
||||||
|
default: '默认',
|
||||||
|
compact: '紧凑',
|
||||||
|
titleTheme: '主题',
|
||||||
|
light: '亮色',
|
||||||
|
dark: '暗黑',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
themeTitle: 'Flexible theme customization',
|
||||||
|
themeDesc: 'Ant Design 5.0 enable extendable algorithm, make custom theme easier',
|
||||||
|
|
||||||
|
customizeTheme: 'Customize Theme',
|
||||||
|
myTheme: 'My Theme',
|
||||||
|
titlePrimaryColor: 'Primary Color',
|
||||||
|
titleBorderRadius: 'Border Radius',
|
||||||
|
titleCompact: 'Compact',
|
||||||
|
titleTheme: 'Theme',
|
||||||
|
default: 'Default',
|
||||||
|
compact: 'Compact',
|
||||||
|
light: 'Light',
|
||||||
|
dark: 'Dark',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================= Style =============================
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
demo: css`
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(240, 242, 245, 0.25);
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
box-shadow: 0 2px 10px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
`,
|
||||||
|
|
||||||
|
otherDemo: css`
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(247, 247, 247, 0.5);
|
||||||
|
`,
|
||||||
|
|
||||||
|
darkDemo: css`
|
||||||
|
// background: green;
|
||||||
|
`,
|
||||||
|
|
||||||
|
larkDemo: css`
|
||||||
|
// background: #f7f7f7;
|
||||||
|
background: rgba(240, 242, 245, 0.65);
|
||||||
|
`,
|
||||||
|
comicDemo: css`
|
||||||
|
// background: #ffe4e6;
|
||||||
|
background: rgba(240, 242, 245, 0.65);
|
||||||
|
`,
|
||||||
|
|
||||||
|
menu: css`
|
||||||
|
margin-left: auto;
|
||||||
|
`,
|
||||||
|
|
||||||
|
header: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid ${token.colorSplit};
|
||||||
|
padding-inline: ${token.paddingLG}px !important;
|
||||||
|
height: ${token.controlHeightLG * 1.2}px;
|
||||||
|
line-height: ${token.controlHeightLG * 1.2}px;
|
||||||
|
`,
|
||||||
|
|
||||||
|
avatar: css`
|
||||||
|
width: ${token.controlHeight}px;
|
||||||
|
height: ${token.controlHeight}px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: rgba(240, 240, 240, 0.75);
|
||||||
|
`,
|
||||||
|
|
||||||
|
avatarDark: css`
|
||||||
|
background: rgba(200, 200, 200, 0.3);
|
||||||
|
`,
|
||||||
|
|
||||||
|
logo: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: ${token.padding}px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
logoImg: css`
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
logoImgPureColor: css`
|
||||||
|
img {
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
side: css``,
|
||||||
|
|
||||||
|
form: css`
|
||||||
|
width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PickerProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================== Menu Config ==========================
|
||||||
|
const subMenuItems: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: `Design Values`,
|
||||||
|
label: `Design Values`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `Global Styles`,
|
||||||
|
label: `Global Styles`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `Themes`,
|
||||||
|
label: `Themes`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `DesignPatterns`,
|
||||||
|
label: `Design Patterns`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const sideMenuItems: MenuProps['items'] = [
|
||||||
|
{
|
||||||
|
key: `Design`,
|
||||||
|
label: `Design`,
|
||||||
|
icon: <FolderOutlined />,
|
||||||
|
children: subMenuItems,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `Development`,
|
||||||
|
label: `Development`,
|
||||||
|
icon: <FolderOutlined />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ============================= Theme =============================
|
||||||
|
|
||||||
|
function getTitleColor(colorPrimary: string, isLight?: boolean) {
|
||||||
|
if (!isLight) {
|
||||||
|
return '#FFF';
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = new TinyColor(colorPrimary);
|
||||||
|
const closestColor = getClosetColor(colorPrimary);
|
||||||
|
|
||||||
|
switch (closestColor) {
|
||||||
|
case DEFAULT_COLOR:
|
||||||
|
case '#FB7299':
|
||||||
|
case '#F2BD27':
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return color.toHsl().l < 0.7 ? '#FFF' : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeData {
|
||||||
|
themeType: THEME;
|
||||||
|
colorPrimary: string;
|
||||||
|
borderRadius: number;
|
||||||
|
compact: 'default' | 'compact';
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeDefault: ThemeData = {
|
||||||
|
themeType: 'default',
|
||||||
|
colorPrimary: '#1677FF',
|
||||||
|
borderRadius: 6,
|
||||||
|
compact: 'default',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemesInfo: Record<THEME, Partial<ThemeData>> = {
|
||||||
|
default: {},
|
||||||
|
dark: {
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
lark: {
|
||||||
|
colorPrimary: '#00B96B',
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
comic: {
|
||||||
|
colorPrimary: '#fb7299',
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Theme() {
|
||||||
|
const style = useStyle();
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
|
||||||
|
const [themeData, setThemeData] = React.useState<ThemeData>(ThemeDefault);
|
||||||
|
|
||||||
|
const onThemeChange = (_: Partial<ThemeData>, nextThemeData: ThemeData) => {
|
||||||
|
setThemeData(nextThemeData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { compact, themeType, ...themeToken } = themeData;
|
||||||
|
const isLight = themeType !== 'dark';
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// const algorithmFn = isLight ? theme.defaultAlgorithm : theme.darkAlgorithm;
|
||||||
|
const algorithmFn = React.useMemo(() => {
|
||||||
|
const algorithms = [isLight ? theme.defaultAlgorithm : theme.darkAlgorithm];
|
||||||
|
|
||||||
|
if (compact === 'compact') {
|
||||||
|
algorithms.push(theme.compactAlgorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return algorithms;
|
||||||
|
}, [isLight, compact]);
|
||||||
|
|
||||||
|
// ================================ Themes ================================
|
||||||
|
React.useEffect(() => {
|
||||||
|
const mergedData = {
|
||||||
|
...ThemeDefault,
|
||||||
|
themeType,
|
||||||
|
...ThemesInfo[themeType],
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
setThemeData(mergedData);
|
||||||
|
form.setFieldsValue(mergedData);
|
||||||
|
}, [themeType]);
|
||||||
|
|
||||||
|
// ================================ Tokens ================================
|
||||||
|
const closestColor = getClosetColor(themeData.colorPrimary);
|
||||||
|
|
||||||
|
const [backgroundColor, avatarColor] = React.useMemo(() => {
|
||||||
|
let bgColor = 'transparent';
|
||||||
|
|
||||||
|
const mapToken = theme.defaultAlgorithm({
|
||||||
|
...theme.defaultConfig.token,
|
||||||
|
colorPrimary: themeData.colorPrimary,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (themeType === 'dark') {
|
||||||
|
bgColor = '#393F4A';
|
||||||
|
} else if (closestColor === DEFAULT_COLOR) {
|
||||||
|
bgColor = '#F5F8FF';
|
||||||
|
} else {
|
||||||
|
bgColor = mapToken.colorPrimaryHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [bgColor, mapToken.colorPrimaryBgHover];
|
||||||
|
}, [themeType, closestColor, themeData.colorPrimary]);
|
||||||
|
|
||||||
|
const logoColor = React.useMemo(() => {
|
||||||
|
const hsl = new TinyColor(themeData.colorPrimary).toHsl();
|
||||||
|
hsl.l = Math.min(hsl.l, 0.7);
|
||||||
|
|
||||||
|
return new TinyColor(hsl).toHexString();
|
||||||
|
}, [themeData.colorPrimary]);
|
||||||
|
|
||||||
|
// ================================ Render ================================
|
||||||
|
const themeNode = (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
...themeToken,
|
||||||
|
},
|
||||||
|
hashed: true,
|
||||||
|
algorithm: algorithmFn,
|
||||||
|
components: {
|
||||||
|
Layout: isLight
|
||||||
|
? {
|
||||||
|
colorBgHeader: 'transparent',
|
||||||
|
colorBgBody: 'transparent',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
// colorBgBody: 'transparent',
|
||||||
|
},
|
||||||
|
Menu: isLight
|
||||||
|
? {
|
||||||
|
colorItemBg: 'transparent',
|
||||||
|
colorSubItemBg: 'transparent',
|
||||||
|
colorActiveBarWidth: 0,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TokenChecker />
|
||||||
|
<div
|
||||||
|
css={[
|
||||||
|
style.demo,
|
||||||
|
isLight && closestColor !== DEFAULT_COLOR && style.otherDemo,
|
||||||
|
!isLight && style.darkDemo,
|
||||||
|
]}
|
||||||
|
style={{ borderRadius: themeData.borderRadius }}
|
||||||
|
>
|
||||||
|
<Layout>
|
||||||
|
<Header css={style.header}>
|
||||||
|
{/* Logo */}
|
||||||
|
<div css={style.logo}>
|
||||||
|
<div css={[style.logoImg, closestColor !== DEFAULT_COLOR && style.logoImgPureColor]}>
|
||||||
|
<img
|
||||||
|
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
|
||||||
|
style={{
|
||||||
|
filter:
|
||||||
|
closestColor === DEFAULT_COLOR
|
||||||
|
? undefined
|
||||||
|
: `drop-shadow(30px 0 0 ${logoColor})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1>Ant Design 5.0</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space css={style.menu} size="middle">
|
||||||
|
<BellOutlined />
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
<div
|
||||||
|
css={[style.avatar, themeType === 'dark' && style.avatarDark]}
|
||||||
|
style={{
|
||||||
|
backgroundColor: avatarColor,
|
||||||
|
backgroundImage: `url(${getAvatarURL(closestColor)})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
boxShadow: `0 0 2px rgba(0, 0, 0, 0.2)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Header>
|
||||||
|
<Layout>
|
||||||
|
<Sider width={200} className="site-layout-background">
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
css={style.side}
|
||||||
|
selectedKeys={['Themes']}
|
||||||
|
openKeys={['Design']}
|
||||||
|
style={{ height: '100%', borderRight: 0 }}
|
||||||
|
items={sideMenuItems}
|
||||||
|
/>
|
||||||
|
</Sider>
|
||||||
|
<Layout style={{ padding: '0 24px 24px' }}>
|
||||||
|
<Breadcrumb style={{ margin: '16px 0' }}>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<HomeOutlined />
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item overlay={<Menu items={subMenuItems} />}>Design</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>Themes</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
<Content>
|
||||||
|
<Typography.Title level={2}>{locale.customizeTheme}</Typography.Title>
|
||||||
|
<Card title={locale.myTheme}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
initialValues={themeData}
|
||||||
|
onValuesChange={onThemeChange}
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
wrapperCol={{ span: 20 }}
|
||||||
|
css={style.form}
|
||||||
|
>
|
||||||
|
<Form.Item label={locale.titleTheme} name="themeType">
|
||||||
|
<ThemePicker />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={locale.titlePrimaryColor} name="colorPrimary">
|
||||||
|
<ColorPicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={locale.titleBorderRadius} name="borderRadius">
|
||||||
|
<RadiusPicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={locale.titleCompact} name="compact">
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="default">{locale.default}</Radio>
|
||||||
|
<Radio value="compact">{locale.compact}</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const posStyle: React.CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
title={locale.themeTitle}
|
||||||
|
titleColor={getTitleColor(themeData.colorPrimary, isLight)}
|
||||||
|
description={locale.themeDesc}
|
||||||
|
id="flexible"
|
||||||
|
background={backgroundColor}
|
||||||
|
decoration={
|
||||||
|
// =========================== Theme Background ===========================
|
||||||
|
<>
|
||||||
|
{/* >>>>>> Default <<<<<< */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
transition: `all ${token.motionDurationSlow}`,
|
||||||
|
opacity: isLight && closestColor === DEFAULT_COLOR ? 1 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Image Left Top */}
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
...posStyle,
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-900px)',
|
||||||
|
top: -100,
|
||||||
|
height: 500,
|
||||||
|
}}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/bd71b0c6-f93a-4e52-9c8a-f01a9b8fe22b.svg"
|
||||||
|
/>
|
||||||
|
{/* Image Right Bottom */}
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
...posStyle,
|
||||||
|
right: '50%',
|
||||||
|
transform: 'translateX(750px)',
|
||||||
|
bottom: -100,
|
||||||
|
height: 287,
|
||||||
|
}}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/84ad805a-74cb-4916-b7ba-9cdc2bdec23a.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* >>>>>> Dark <<<<<< */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
transition: `all ${token.motionDurationSlow}`,
|
||||||
|
opacity: !isLight || !closestColor ? 1 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Image Left Top */}
|
||||||
|
<img
|
||||||
|
style={{ ...posStyle, left: 0, top: -100, height: 500 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/a213184a-f212-4afb-beec-1e8b36bb4b8a.svg"
|
||||||
|
/>
|
||||||
|
{/* Image Right Bottom */}
|
||||||
|
<img
|
||||||
|
style={{ ...posStyle, right: 0, bottom: -100, height: 287 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/bb74a2fb-bff1-4d0d-8c2d-2ade0cd9bb0d.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* >>>>>> Background Image <<<<<< */}
|
||||||
|
<BackgroundImage isLight={isLight} colorPrimary={themeData.colorPrimary} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{themeNode}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
94
.dumi/pages/index/index.tsx
Normal file
94
.dumi/pages/index/index.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import React, { type FC } from 'react';
|
||||||
|
import { useLocale as useDumiLocale } from 'dumi';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useLocale from '../../hooks/useLocale';
|
||||||
|
import Banner from './components/Banner';
|
||||||
|
import Group from './components/Group';
|
||||||
|
import { useSiteData } from './components/util';
|
||||||
|
import useSiteToken from '../../hooks/useSiteToken';
|
||||||
|
import Theme from './components/Theme';
|
||||||
|
import BannerRecommends from './components/BannerRecommends';
|
||||||
|
import ComponentsList from './components/ComponentsList';
|
||||||
|
import DesignFramework from './components/DesignFramework';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
// padding: 0 116px;
|
||||||
|
|
||||||
|
// background: url(https://gw.alipayobjects.com/zos/bmw-prod/5741382d-cc22-4ede-b962-aea287a1d1a1/l4nq43o8_w2646_h1580.png);
|
||||||
|
// background-size: 20% 10%;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
assetsTitle: '组件丰富,选用自如',
|
||||||
|
assetsDesc: '大量实用组件满足你的需求,灵活定制与拓展',
|
||||||
|
|
||||||
|
designTitle: '设计语言与研发框架',
|
||||||
|
designDesc: '配套生态,让你快速搭建网站应用',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
assetsTitle: 'Rich components',
|
||||||
|
assetsDesc: 'Practical components to meet your needs, flexible customization and expansion',
|
||||||
|
|
||||||
|
designTitle: 'Design and framework',
|
||||||
|
designDesc: 'Supporting ecology, allowing you to quickly build website applications',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Homepage: FC = () => {
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
const { id: localeId } = useDumiLocale();
|
||||||
|
const localeStr = localeId === 'zh-CN' ? 'cn' : 'en';
|
||||||
|
|
||||||
|
const [siteData, loading] = useSiteData();
|
||||||
|
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={{ algorithm: undefined }}>
|
||||||
|
<section>
|
||||||
|
<Banner>
|
||||||
|
<BannerRecommends extras={siteData?.extras?.[localeStr]} icons={siteData?.icons} />
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<div css={style.container}>
|
||||||
|
<Theme />
|
||||||
|
<Group
|
||||||
|
background="#fff"
|
||||||
|
collapse
|
||||||
|
title={locale.assetsTitle}
|
||||||
|
description={locale.assetsDesc}
|
||||||
|
id="design"
|
||||||
|
>
|
||||||
|
<ComponentsList />
|
||||||
|
</Group>
|
||||||
|
<Group
|
||||||
|
title={locale.designTitle}
|
||||||
|
description={locale.designDesc}
|
||||||
|
background="#F5F8FF"
|
||||||
|
decoration={
|
||||||
|
<>
|
||||||
|
{/* Image Left Top */}
|
||||||
|
<img
|
||||||
|
style={{ position: 'absolute', left: 0, top: -50, height: 160 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/bmw-prod/ba37a413-28e6-4be4-b1c5-01be1a0ebb1c.svg"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DesignFramework />
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Homepage;
|
1
.dumi/pages/theme-editor-cn/index.tsx
Normal file
1
.dumi/pages/theme-editor-cn/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from '../theme-editor/index';
|
36
.dumi/pages/theme-editor/index.tsx
Normal file
36
.dumi/pages/theme-editor/index.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { ThemeEditor } from 'antd-token-previewer';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import ThemeContext from '../../theme/slots/ThemeContext';
|
||||||
|
import useLocale from '../../hooks/useLocale';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
title: '主题编辑器',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: 'Theme Editor',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomTheme = () => {
|
||||||
|
const [locale] = useLocale(locales);
|
||||||
|
const { setTheme, theme } = useContext(ThemeContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ConfigProvider theme={{ algorithm: undefined }}>
|
||||||
|
<ThemeEditor
|
||||||
|
theme={{ name: 'test', key: 'test', config: theme }}
|
||||||
|
simple
|
||||||
|
style={{ height: 'calc(100vh - 64px)' }}
|
||||||
|
onThemeChange={newTheme => {
|
||||||
|
setTheme(newTheme.config);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomTheme;
|
60
.dumi/rehypeAntd.ts
Normal file
60
.dumi/rehypeAntd.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import { type HastRoot, type UnifiedTransformer, unistUtilVisit } from 'dumi';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plugin for modify hast tree when docs compiling
|
||||||
|
*/
|
||||||
|
function rehypeAntd(): UnifiedTransformer<HastRoot> {
|
||||||
|
return (tree, vFile) => {
|
||||||
|
unistUtilVisit.visit(tree, 'element', node => {
|
||||||
|
if (node.tagName === 'DumiDemoGrid') {
|
||||||
|
// replace DumiDemoGrid to DemoWrapper, to implement demo toolbar
|
||||||
|
node.tagName = 'DemoWrapper';
|
||||||
|
} else if (node.tagName === 'ResourceCards') {
|
||||||
|
const propNames = ['title', 'cover', 'description', 'src', 'official'];
|
||||||
|
const contentNode = node.children[0];
|
||||||
|
|
||||||
|
assert(
|
||||||
|
contentNode.type === 'text',
|
||||||
|
`ResourceCards content must be plain text!\nat ${
|
||||||
|
(vFile.data.frontmatter as any).filename
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// clear children
|
||||||
|
node.children = [];
|
||||||
|
|
||||||
|
// generate JSX props
|
||||||
|
(node as any).JSXAttributes = [
|
||||||
|
{
|
||||||
|
type: 'JSXAttribute',
|
||||||
|
name: 'resources',
|
||||||
|
value: JSON.stringify(
|
||||||
|
contentNode.value
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.reduce<any>((acc, cur) => {
|
||||||
|
// match text from ` - 桌面组件 Sketch 模板包`
|
||||||
|
const [, isProp, val] = cur.match(/(\s+)?-\s(.+)/)!;
|
||||||
|
|
||||||
|
if (!isProp) {
|
||||||
|
// create items when match title
|
||||||
|
acc.push({ [propNames[0]]: val });
|
||||||
|
} else {
|
||||||
|
// add props when match others
|
||||||
|
const prev = acc[acc.length - 1];
|
||||||
|
|
||||||
|
prev[propNames[Object.keys(prev).length]] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default rehypeAntd;
|
4
.dumi/theme/antd.js
Normal file
4
.dumi/theme/antd.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Need import for the additional core style
|
||||||
|
// exports.styleCore = require('../components/style/reset.css');
|
||||||
|
|
||||||
|
module.exports = require('../../components');
|
8
.dumi/theme/builtins/APITable/index.tsx
Normal file
8
.dumi/theme/builtins/APITable/index.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React, { type FC } from 'react';
|
||||||
|
|
||||||
|
const APITable: FC = () => {
|
||||||
|
// TODO: implement api table, depend on the new markdown data structure passed
|
||||||
|
return <>API Table</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default APITable;
|
54
.dumi/theme/builtins/ComponentOverview/ProComponentsList.ts
Normal file
54
.dumi/theme/builtins/ComponentOverview/ProComponentsList.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
export type Component = {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
cover: string;
|
||||||
|
link: string;
|
||||||
|
tag?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const proComponentsList: Component[] = [
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/4n5H%24UX%24j/bianzu%2525204.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/layout',
|
||||||
|
subtitle: '高级布局',
|
||||||
|
title: 'ProLayout',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/mStei5BFC/bianzu%2525207.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/form',
|
||||||
|
subtitle: '高级表单',
|
||||||
|
title: 'ProForm',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/AwU0Cv%26Ju/bianzu%2525208.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/table',
|
||||||
|
subtitle: '高级表格',
|
||||||
|
title: 'ProTable',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/H0%26LSYYfh/bianzu%2525209.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/descriptions',
|
||||||
|
subtitle: '高级定义列表',
|
||||||
|
title: 'ProDescriptions',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/uZUmLtne5/bianzu%2525209.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/list',
|
||||||
|
subtitle: '高级列表',
|
||||||
|
title: 'ProList',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cover: 'https://gw.alipayobjects.com/zos/antfincdn/N3eU432oA/bianzu%2525209.svg',
|
||||||
|
link: 'https://procomponents.ant.design/components/editable-table',
|
||||||
|
subtitle: '可编辑表格',
|
||||||
|
title: 'EditableProTable',
|
||||||
|
tag: 'https://gw.alipayobjects.com/zos/antfincdn/OG4ajVYzh/bianzu%2525202.svg',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default proComponentsList;
|
196
.dumi/theme/builtins/ComponentOverview/index.tsx
Normal file
196
.dumi/theme/builtins/ComponentOverview/index.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import React, { useState, memo, useMemo } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet-async';
|
||||||
|
import { Link, useRouteMeta, useIntl, useSidebarData } from 'dumi';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { Input, Divider, Row, Col, Card, Typography, Tag, Space } from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import proComponentsList from './ProComponentsList';
|
||||||
|
import type { Component } from './ProComponentsList';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
componentsOverview: css`
|
||||||
|
padding: 0;
|
||||||
|
`,
|
||||||
|
componentsOverviewGroupTitle: css`
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 24px !important;
|
||||||
|
`,
|
||||||
|
componentsOverviewTitle: css`
|
||||||
|
overflow: hidden;
|
||||||
|
color: ${token.colorTextHeading};
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`,
|
||||||
|
componentsOverviewImg: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 152px;
|
||||||
|
background-color: ${token.colorBgElevated};
|
||||||
|
`,
|
||||||
|
componentsOverviewCard: css`
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.5s;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 6px 16px -8px #00000014, 0 9px 28px #0000000d, 0 12px 48px 16px #00000008;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
componentsOverviewSearch: css`
|
||||||
|
&${token.antCls}-input-affix-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickCard = (pathname: string) => {
|
||||||
|
if (window.gtag) {
|
||||||
|
window.gtag('event', '点击', {
|
||||||
|
event_category: '组件总览卡片',
|
||||||
|
event_label: pathname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reportSearch = debounce<(value: string) => void>(value => {
|
||||||
|
if (window.gtag) {
|
||||||
|
window.gtag('event', '搜索', {
|
||||||
|
event_category: '组件总览卡片',
|
||||||
|
event_label: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
const Overview: React.FC = () => {
|
||||||
|
const style = useStyle();
|
||||||
|
const meta = useRouteMeta();
|
||||||
|
const data = useSidebarData();
|
||||||
|
|
||||||
|
const { locale, formatMessage } = useIntl();
|
||||||
|
const documentTitle = `${meta.frontmatter.title} - Ant Design`;
|
||||||
|
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
|
||||||
|
const sectionRef = React.useRef<HTMLElement>(null);
|
||||||
|
|
||||||
|
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = event => {
|
||||||
|
if (event.keyCode === 13 && search.trim().length) {
|
||||||
|
sectionRef.current?.querySelector<HTMLElement>('.components-overview-card')?.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const groups = useMemo<{ title: string; children: Component[] }[]>(() => {
|
||||||
|
return data
|
||||||
|
.filter(item => item.title)
|
||||||
|
.map<{ title: string; children: Component[] }>(item => {
|
||||||
|
return {
|
||||||
|
title: item.title!,
|
||||||
|
children: item.children.map(child => ({
|
||||||
|
title: child.frontmatter.title,
|
||||||
|
subtitle: child.frontmatter.subtitle,
|
||||||
|
cover: child.frontmatter.cover,
|
||||||
|
link: child.link,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.concat([
|
||||||
|
{
|
||||||
|
title: locale === 'zh-CN' ? '重型组件' : 'Others',
|
||||||
|
children: proComponentsList,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [data, locale]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="markdown" ref={sectionRef}>
|
||||||
|
<Divider />
|
||||||
|
<Input
|
||||||
|
value={search}
|
||||||
|
placeholder={formatMessage({ id: 'app.components.overview.search' })}
|
||||||
|
css={style.componentsOverviewSearch}
|
||||||
|
onChange={e => {
|
||||||
|
setSearch(e.target.value);
|
||||||
|
reportSearch(e.target.value);
|
||||||
|
}}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
|
||||||
|
suffix={<SearchOutlined />}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
{groups
|
||||||
|
.filter(i => i.title)
|
||||||
|
.map(group => {
|
||||||
|
const components = group?.children?.filter(
|
||||||
|
component =>
|
||||||
|
!search.trim() ||
|
||||||
|
component.title.toLowerCase().includes(search.trim().toLowerCase()) ||
|
||||||
|
(component?.subtitle || '').toLowerCase().includes(search.trim().toLowerCase()),
|
||||||
|
);
|
||||||
|
return components?.length ? (
|
||||||
|
<div key={group.title} css={style.componentsOverview}>
|
||||||
|
<Title level={2} css={style.componentsOverviewGroupTitle}>
|
||||||
|
<Space align="center">
|
||||||
|
<span style={{ fontSize: 24 }}>{group.title}</span>
|
||||||
|
<Tag style={{ display: 'block' }}>{components.length}</Tag>
|
||||||
|
</Space>
|
||||||
|
</Title>
|
||||||
|
<Row gutter={[24, 24]}>
|
||||||
|
{components.map(component => {
|
||||||
|
const url = `${component.link}/`;
|
||||||
|
|
||||||
|
/** Link 不能跳转到外链 */
|
||||||
|
const ComponentLink = !url.startsWith('http') ? Link : 'a';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col xs={24} sm={12} lg={8} xl={6} key={component.title}>
|
||||||
|
<ComponentLink to={url} href={url} onClick={() => onClickCard(url)}>
|
||||||
|
<Card
|
||||||
|
bodyStyle={{
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'bottom right',
|
||||||
|
backgroundImage: `url(${component?.tag || ''})`,
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
css={style.componentsOverviewCard}
|
||||||
|
title={
|
||||||
|
<div css={style.componentsOverviewTitle}>
|
||||||
|
{component.title} {component.subtitle}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div css={style.componentsOverviewImg}>
|
||||||
|
<img src={component.cover} alt={component.title} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</ComponentLink>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Overview);
|
67
.dumi/theme/builtins/DemoWrapper/index.tsx
Normal file
67
.dumi/theme/builtins/DemoWrapper/index.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React, { useContext, useLayoutEffect, useState } from 'react';
|
||||||
|
import { DumiDemoGrid, FormattedMessage } from 'dumi';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
import { BugFilled, BugOutlined, CodeFilled, CodeOutlined } from '@ant-design/icons';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import DemoContext from '../../slots/DemoContext';
|
||||||
|
|
||||||
|
const DemoWrapper: typeof DumiDemoGrid = ({ items }) => {
|
||||||
|
const { showDebug, setShowDebug, setDebugDemos } = useContext(DemoContext);
|
||||||
|
|
||||||
|
const [expandAll, setExpandAll] = useState(false);
|
||||||
|
|
||||||
|
const expandTriggerClass = classNames('code-box-expand-trigger', {
|
||||||
|
'code-box-expand-trigger-active': expandAll,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleVisibleToggle = () => {
|
||||||
|
setShowDebug?.(!showDebug);
|
||||||
|
};
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setDebugDemos?.(items.filter(item => item.previewerProps.debug).map(item => item.demo.id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExpandToggle = () => {
|
||||||
|
setExpandAll(!expandAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
const visibleDemos = showDebug ? items : items.filter(item => !item.previewerProps.debug);
|
||||||
|
const filteredItems = visibleDemos.map(item => ({
|
||||||
|
...item,
|
||||||
|
previewerProps: { ...item.previewerProps, expand: expandAll },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="demo-wrapper">
|
||||||
|
<span className="all-code-box-controls">
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<FormattedMessage id={`app.component.examples.${expandAll ? 'collapse' : 'expand'}`} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{expandAll ? (
|
||||||
|
<CodeFilled className={expandTriggerClass} onClick={handleExpandToggle} />
|
||||||
|
) : (
|
||||||
|
<CodeOutlined className={expandTriggerClass} onClick={handleExpandToggle} />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<FormattedMessage id={`app.component.examples.${showDebug ? 'hide' : 'visible'}`} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showDebug ? (
|
||||||
|
<BugFilled className={expandTriggerClass} onClick={handleVisibleToggle} />
|
||||||
|
) : (
|
||||||
|
<BugOutlined className={expandTriggerClass} onClick={handleVisibleToggle} />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
{/* FIXME: find a new way instead of `key` to trigger re-render */}
|
||||||
|
<DumiDemoGrid items={filteredItems} key={expandAll + '' + showDebug} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DemoWrapper;
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { injectIntl } from 'react-intl';
|
import { useIntl } from 'dumi';
|
||||||
import CopyableIcon from './CopyableIcon';
|
import CopyableIcon from './CopyableIcon';
|
||||||
import type { ThemeType } from './index';
|
import type { ThemeType } from './index';
|
||||||
import type { CategoriesKeys } from './fields';
|
import type { CategoriesKeys } from './fields';
|
||||||
@ -10,11 +10,11 @@ interface CategoryProps {
|
|||||||
icons: string[];
|
icons: string[];
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
newIcons: string[];
|
newIcons: string[];
|
||||||
intl: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Category: React.FC<CategoryProps> = props => {
|
const Category: React.FC<CategoryProps> = props => {
|
||||||
const { icons, title, newIcons, theme, intl } = props;
|
const { icons, title, newIcons, theme } = props;
|
||||||
|
const intl = useIntl();
|
||||||
const [justCopied, setJustCopied] = React.useState<string | null>(null);
|
const [justCopied, setJustCopied] = React.useState<string | null>(null);
|
||||||
const copyId = React.useRef<NodeJS.Timeout | null>(null);
|
const copyId = React.useRef<NodeJS.Timeout | null>(null);
|
||||||
const onCopied = React.useCallback((type: string, text: string) => {
|
const onCopied = React.useCallback((type: string, text: string) => {
|
||||||
@ -38,7 +38,7 @@ const Category: React.FC<CategoryProps> = props => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>{intl.messages[`app.docs.components.icon.category.${title}`]}</h3>
|
<h3>{intl.formatMessage({ id: `app.docs.components.icon.category.${title}` })}</h3>
|
||||||
<ul className="anticons-list">
|
<ul className="anticons-list">
|
||||||
{icons.map(name => (
|
{icons.map(name => (
|
||||||
<CopyableIcon
|
<CopyableIcon
|
||||||
@ -55,4 +55,4 @@ const Category: React.FC<CategoryProps> = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(Category);
|
export default Category;
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Upload, Tooltip, Popover, Modal, Progress, message, Spin, Result } from 'antd';
|
import { Upload, Tooltip, Popover, Modal, Progress, message, Spin, Result } from 'antd';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import { injectIntl } from 'react-intl';
|
import { useIntl } from 'dumi';
|
||||||
import * as AntdIcons from '@ant-design/icons';
|
import * as AntdIcons from '@ant-design/icons';
|
||||||
|
|
||||||
const allIcons: { [key: string]: any } = AntdIcons;
|
const allIcons: { [key: string]: any } = AntdIcons;
|
||||||
@ -17,10 +17,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PicSearcherProps {
|
|
||||||
intl: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PicSearcherState {
|
interface PicSearcherState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
modalOpen: boolean;
|
modalOpen: boolean;
|
||||||
@ -36,8 +32,8 @@ interface iconObject {
|
|||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
const PicSearcher: React.FC = () => {
|
||||||
const { messages } = intl;
|
const intl = useIntl();
|
||||||
const [state, setState] = useState<PicSearcherState>({
|
const [state, setState] = useState<PicSearcherState>({
|
||||||
loading: false,
|
loading: false,
|
||||||
modalOpen: false,
|
modalOpen: false,
|
||||||
@ -63,7 +59,7 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
const toImage = (url: string) =>
|
const toImage = (url: string): Promise<HTMLImageElement> =>
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.setAttribute('crossOrigin', 'anonymous');
|
img.setAttribute('crossOrigin', 'anonymous');
|
||||||
@ -139,13 +135,13 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="icon-pic-searcher">
|
<div className="icon-pic-searcher">
|
||||||
<Popover
|
<Popover
|
||||||
content={messages[`app.docs.components.icon.pic-searcher.intro`]}
|
content={intl.formatMessage({ id: `app.docs.components.icon.pic-searcher.intro` })}
|
||||||
open={state.popoverVisible}
|
open={state.popoverVisible}
|
||||||
>
|
>
|
||||||
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
|
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
|
||||||
</Popover>
|
</Popover>
|
||||||
<Modal
|
<Modal
|
||||||
title={messages[`app.docs.components.icon.pic-searcher.title`]}
|
title={intl.formatMessage({ id: `app.docs.components.icon.pic-searcher.title` })}
|
||||||
open={state.modalOpen}
|
open={state.modalOpen}
|
||||||
onCancel={toggleModal}
|
onCancel={toggleModal}
|
||||||
footer={null}
|
footer={null}
|
||||||
@ -153,7 +149,7 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
{state.modelLoaded || (
|
{state.modelLoaded || (
|
||||||
<Spin
|
<Spin
|
||||||
spinning={!state.modelLoaded}
|
spinning={!state.modelLoaded}
|
||||||
tip={messages['app.docs.components.icon.pic-searcher.modelloading']}
|
tip={intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.modelloading' })}
|
||||||
>
|
>
|
||||||
<div style={{ height: 100 }} />
|
<div style={{ height: 100 }} />
|
||||||
</Spin>
|
</Spin>
|
||||||
@ -170,21 +166,21 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
<AntdIcons.InboxOutlined />
|
<AntdIcons.InboxOutlined />
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-text">
|
<p className="ant-upload-text">
|
||||||
{messages['app.docs.components.icon.pic-searcher.upload-text']}
|
{intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-text' })}
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-hint">
|
<p className="ant-upload-hint">
|
||||||
{messages['app.docs.components.icon.pic-searcher.upload-hint']}
|
{intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-hint' })}
|
||||||
</p>
|
</p>
|
||||||
</Dragger>
|
</Dragger>
|
||||||
)}
|
)}
|
||||||
<Spin
|
<Spin
|
||||||
spinning={state.loading}
|
spinning={state.loading}
|
||||||
tip={messages['app.docs.components.icon.pic-searcher.matching']}
|
tip={intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.matching' })}
|
||||||
>
|
>
|
||||||
<div className="icon-pic-search-result">
|
<div className="icon-pic-search-result">
|
||||||
{state.icons.length > 0 && (
|
{state.icons.length > 0 && (
|
||||||
<div className="result-tip">
|
<div className="result-tip">
|
||||||
{messages['app.docs.components.icon.pic-searcher.result-tip']}
|
{intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.result-tip' })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<table>
|
<table>
|
||||||
@ -192,9 +188,11 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="col-icon">
|
<th className="col-icon">
|
||||||
{messages['app.docs.components.icon.pic-searcher.th-icon']}
|
{intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-icon' })}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{intl.formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-score' })}
|
||||||
</th>
|
</th>
|
||||||
<th>{messages['app.docs.components.icon.pic-searcher.th-score']}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
)}
|
)}
|
||||||
@ -226,7 +224,9 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
<Result
|
<Result
|
||||||
status="500"
|
status="500"
|
||||||
title="503"
|
title="503"
|
||||||
subTitle={messages['app.docs.components.icon.pic-searcher.server-error']}
|
subTitle={intl.formatMessage({
|
||||||
|
id: 'app.docs.components.icon.pic-searcher.server-error',
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -236,4 +236,4 @@ const PicSearcher: React.FC<PicSearcherProps> = ({ intl }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(PicSearcher);
|
export default PicSearcher;
|
@ -200,8 +200,6 @@ const logo = [
|
|||||||
'Yahoo',
|
'Yahoo',
|
||||||
'Reddit',
|
'Reddit',
|
||||||
'Sketch',
|
'Sketch',
|
||||||
'WhatsApp',
|
|
||||||
'Dingtalk',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
|
const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import Icon, * as AntdIcons from '@ant-design/icons';
|
import Icon, * as AntdIcons from '@ant-design/icons';
|
||||||
import { Radio, Input, Empty } from 'antd';
|
import { Radio, Input, Empty } from 'antd';
|
||||||
import type { RadioChangeEvent } from 'antd/es/radio/interface';
|
import type { RadioChangeEvent } from 'antd/es/radio/interface';
|
||||||
import { injectIntl } from 'react-intl';
|
import { useIntl } from 'dumi';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import Category from './Category';
|
import Category from './Category';
|
||||||
import IconPicSearcher from './IconPicSearcher';
|
import IconPicSearcher from './IconPicSearcher';
|
||||||
@ -18,18 +18,14 @@ export enum ThemeType {
|
|||||||
|
|
||||||
const allIcons: { [key: string]: any } = AntdIcons;
|
const allIcons: { [key: string]: any } = AntdIcons;
|
||||||
|
|
||||||
interface IconDisplayProps {
|
interface IconSearchState {
|
||||||
intl: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IconDisplayState {
|
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
const IconSearch: React.FC = () => {
|
||||||
const { messages } = intl;
|
const intl = useIntl();
|
||||||
const [displayState, setDisplayState] = React.useState<IconDisplayState>({
|
const [displayState, setDisplayState] = React.useState<IconSearchState>({
|
||||||
theme: ThemeType.Outlined,
|
theme: ThemeType.Outlined,
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
});
|
});
|
||||||
@ -51,8 +47,8 @@ const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
|||||||
const { searchKey = '', theme } = displayState;
|
const { searchKey = '', theme } = displayState;
|
||||||
|
|
||||||
const categoriesResult = Object.keys(categories)
|
const categoriesResult = Object.keys(categories)
|
||||||
.map((key: CategoriesKeys) => {
|
.map(key => {
|
||||||
let iconList = categories[key];
|
let iconList = categories[key as CategoriesKeys];
|
||||||
if (searchKey) {
|
if (searchKey) {
|
||||||
const matchKey = searchKey
|
const matchKey = searchKey
|
||||||
// eslint-disable-next-line prefer-regex-literals
|
// eslint-disable-next-line prefer-regex-literals
|
||||||
@ -83,7 +79,7 @@ const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
|||||||
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
||||||
}, [displayState.searchKey, displayState.theme]);
|
}, [displayState.searchKey, displayState.theme]);
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="markdown">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
value={displayState.theme}
|
value={displayState.theme}
|
||||||
@ -92,17 +88,20 @@ const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
|||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
>
|
>
|
||||||
<Radio.Button value={ThemeType.Outlined}>
|
<Radio.Button value={ThemeType.Outlined}>
|
||||||
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
|
<Icon component={OutlinedIcon} />{' '}
|
||||||
|
{intl.formatMessage({ id: 'app.docs.components.icon.outlined' })}
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
<Radio.Button value={ThemeType.Filled}>
|
<Radio.Button value={ThemeType.Filled}>
|
||||||
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
|
<Icon component={FilledIcon} />{' '}
|
||||||
|
{intl.formatMessage({ id: 'app.docs.components.icon.filled' })}
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
<Radio.Button value={ThemeType.TwoTone}>
|
<Radio.Button value={ThemeType.TwoTone}>
|
||||||
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
|
<Icon component={TwoToneIcon} />{' '}
|
||||||
|
{intl.formatMessage({ id: 'app.docs.components.icon.two-tone' })}
|
||||||
</Radio.Button>
|
</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={messages['app.docs.components.icon.search.placeholder']}
|
placeholder={intl.formatMessage({ id: 'app.docs.components.icon.search.placeholder' })}
|
||||||
style={{ margin: '0 10px', flex: 1 }}
|
style={{ margin: '0 10px', flex: 1 }}
|
||||||
allowClear
|
allowClear
|
||||||
onChange={e => handleSearchIcon(e.currentTarget.value)}
|
onChange={e => handleSearchIcon(e.currentTarget.value)}
|
||||||
@ -112,8 +111,8 @@ const IconDisplay: React.FC<IconDisplayProps> = ({ intl }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{renderCategories}
|
{renderCategories}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(IconDisplay);
|
export default IconSearch;
|
@ -1,18 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { CustomIconComponentProps } from '@ant-design/icons/es/components/Icon';
|
||||||
|
|
||||||
interface CustomIconComponentProps {
|
type CustomIconComponent = React.ComponentType<
|
||||||
width: string | number;
|
CustomIconComponentProps | React.SVGProps<SVGSVGElement>
|
||||||
height: string | number;
|
>;
|
||||||
fill: string;
|
|
||||||
viewBox?: string;
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
spin?: boolean;
|
|
||||||
rotate?: number;
|
|
||||||
['aria-hidden']?: React.AriaAttributes['aria-hidden'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FilledIcon: React.FC<CustomIconComponentProps> = props => {
|
export const FilledIcon: CustomIconComponent = props => {
|
||||||
const path =
|
const path =
|
||||||
'M864 64H160C107 64 64 107 64 160v' +
|
'M864 64H160C107 64 64 107 64 160v' +
|
||||||
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
|
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
|
||||||
@ -24,7 +17,7 @@ export const FilledIcon: React.FC<CustomIconComponentProps> = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OutlinedIcon: React.FC<CustomIconComponentProps> = props => {
|
export const OutlinedIcon: CustomIconComponent = props => {
|
||||||
const path =
|
const path =
|
||||||
'M864 64H160C107 64 64 107 64 160v7' +
|
'M864 64H160C107 64 64 107 64 160v7' +
|
||||||
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
|
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
|
||||||
@ -38,7 +31,7 @@ export const OutlinedIcon: React.FC<CustomIconComponentProps> = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TwoToneIcon: React.FC<CustomIconComponentProps> = props => {
|
export const TwoToneIcon: CustomIconComponent = props => {
|
||||||
const path =
|
const path =
|
||||||
'M16 512c0 273.932 222.066 496 496 49' +
|
'M16 512c0 273.932 222.066 496 496 49' +
|
||||||
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
|
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
|
101
.dumi/theme/builtins/Previewer/fromDumiProps.tsx
Normal file
101
.dumi/theme/builtins/Previewer/fromDumiProps.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import React, { useEffect, useState, type FC } from 'react';
|
||||||
|
// @ts-ignore
|
||||||
|
import JsonML from 'jsonml.js/lib/utils';
|
||||||
|
// @ts-ignore
|
||||||
|
import toReactComponent from 'jsonml-to-react-element';
|
||||||
|
// @ts-ignore
|
||||||
|
import Prism from 'prismjs';
|
||||||
|
import { useLocation } from 'dumi';
|
||||||
|
import { useIntl, type IPreviewerProps } from 'dumi';
|
||||||
|
import { ping } from '../../utils';
|
||||||
|
import sylvanas from 'sylvanas';
|
||||||
|
|
||||||
|
let pingDeferrer: PromiseLike<boolean>;
|
||||||
|
|
||||||
|
function useShowRiddleButton() {
|
||||||
|
const [showRiddleButton, setShowRiddleButton] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
pingDeferrer ??= new Promise<boolean>(resolve => {
|
||||||
|
ping(status => {
|
||||||
|
if (status !== 'timeout' && status !== 'error') {
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
pingDeferrer.then(setShowRiddleButton);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return showRiddleButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HOC for convert dumi previewer props to bisheng previewer props
|
||||||
|
*/
|
||||||
|
export default function fromDumiProps<P extends object>(
|
||||||
|
WrappedComponent: React.ComponentType<P>,
|
||||||
|
): FC<IPreviewerProps> {
|
||||||
|
const hoc = function DumiPropsAntdPreviewer(props: IPreviewerProps) {
|
||||||
|
const showRiddleButton = useShowRiddleButton();
|
||||||
|
const location = useLocation();
|
||||||
|
const { asset, children, demoUrl, expand, description = '', ...meta } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
const entryCode = asset.dependencies['index.tsx'].value;
|
||||||
|
const transformedProps = {
|
||||||
|
meta: {
|
||||||
|
id: asset.id,
|
||||||
|
title: '',
|
||||||
|
filename: meta.filePath,
|
||||||
|
...meta,
|
||||||
|
},
|
||||||
|
content: description,
|
||||||
|
preview: () => children,
|
||||||
|
utils: {
|
||||||
|
toReactComponent(jsonML: any) {
|
||||||
|
return toReactComponent(jsonML, [
|
||||||
|
[
|
||||||
|
function (node: any) {
|
||||||
|
return JsonML.isElement(node) && JsonML.getTagName(node) === 'pre';
|
||||||
|
},
|
||||||
|
function (node: any, index: any) {
|
||||||
|
// @ts-ignore
|
||||||
|
// ref: https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/bisheng-plugin-highlight/lib/browser.js#L7
|
||||||
|
var attr = JsonML.getAttributes(node);
|
||||||
|
return React.createElement(
|
||||||
|
'pre',
|
||||||
|
{
|
||||||
|
key: index,
|
||||||
|
className: `language-${attr.lang}`,
|
||||||
|
},
|
||||||
|
React.createElement('code', {
|
||||||
|
dangerouslySetInnerHTML: { __html: attr.highlighted },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
intl: { locale: intl.locale },
|
||||||
|
showRiddleButton,
|
||||||
|
highlightedCodes: {
|
||||||
|
jsx: Prism.highlight(meta.jsx, Prism.languages.javascript, 'jsx'),
|
||||||
|
tsx: Prism.highlight(entryCode, Prism.languages.javascript, 'tsx'),
|
||||||
|
},
|
||||||
|
style: meta.style,
|
||||||
|
location,
|
||||||
|
src: demoUrl,
|
||||||
|
expand,
|
||||||
|
// FIXME: confirm is there has any case?
|
||||||
|
highlightedStyle: '',
|
||||||
|
// FIXME: dumi support usePrefersColor
|
||||||
|
theme: 'light',
|
||||||
|
} as P;
|
||||||
|
|
||||||
|
return <WrappedComponent {...transformedProps} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return hoc;
|
||||||
|
}
|
@ -7,13 +7,14 @@ import LZString from 'lz-string';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage } from 'dumi';
|
||||||
import BrowserFrame from '../../BrowserFrame';
|
import BrowserFrame from '../../common/BrowserFrame';
|
||||||
import EditButton from '../EditButton';
|
import EditButton from '../../common/EditButton';
|
||||||
import CodePenIcon from './CodePenIcon';
|
import CodePenIcon from '../../common/CodePenIcon';
|
||||||
import CodePreview from './CodePreview';
|
import CodePreview from '../../common/CodePreview';
|
||||||
import CodeSandboxIcon from './CodeSandboxIcon';
|
import CodeSandboxIcon from '../../common/CodeSandboxIcon';
|
||||||
import RiddleIcon from './RiddleIcon';
|
import RiddleIcon from '../../common/RiddleIcon';
|
||||||
|
import fromDumiProps from './fromDumiProps';
|
||||||
|
|
||||||
const { ErrorBoundary } = Alert;
|
const { ErrorBoundary } = Alert;
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ class Demo extends React.Component {
|
|||||||
handleIframeReady = () => {
|
handleIframeReady = () => {
|
||||||
const { theme, setIframeTheme } = this.props;
|
const { theme, setIframeTheme } = this.props;
|
||||||
if (this.iframeRef.current) {
|
if (this.iframeRef.current) {
|
||||||
setIframeTheme(this.iframeRef.current, theme);
|
// setIframeTheme(this.iframeRef.current, theme);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,7 +138,6 @@ class Demo extends React.Component {
|
|||||||
style,
|
style,
|
||||||
highlightedStyle,
|
highlightedStyle,
|
||||||
expand,
|
expand,
|
||||||
utils,
|
|
||||||
intl: { locale },
|
intl: { locale },
|
||||||
theme,
|
theme,
|
||||||
showRiddleButton,
|
showRiddleButton,
|
||||||
@ -166,7 +166,7 @@ class Demo extends React.Component {
|
|||||||
});
|
});
|
||||||
const localizedTitle = meta.title[locale] || meta.title;
|
const localizedTitle = meta.title[locale] || meta.title;
|
||||||
const localizeIntro = content[locale] || content;
|
const localizeIntro = content[locale] || content;
|
||||||
const introChildren = utils.toReactComponent(['div'].concat(localizeIntro));
|
const introChildren = <div dangerouslySetInnerHTML={{ __html: localizeIntro }}></div>;
|
||||||
|
|
||||||
const highlightClass = classNames('highlight-wrapper', {
|
const highlightClass = classNames('highlight-wrapper', {
|
||||||
'highlight-wrapper-expand': codeExpand,
|
'highlight-wrapper-expand': codeExpand,
|
||||||
@ -513,4 +513,4 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectIntl(Demo);
|
export default fromDumiProps(Demo);
|
@ -3,9 +3,62 @@ import * as React from 'react';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { Tabs, Skeleton, Avatar, Divider, Empty } from 'antd';
|
import { Tabs, Skeleton, Avatar, Divider, Empty } from 'antd';
|
||||||
import { useSiteData } from '../../Home/util';
|
import { useSiteData } from '../../../pages/index/components/util';
|
||||||
import type { Article, Authors } from '../../Home/util';
|
import type { Article, Authors } from '../../../pages/index/components/util';
|
||||||
import './index.less';
|
import { css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const { antCls } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
articles: css`
|
||||||
|
h4 {
|
||||||
|
margin: 40px 0 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-skeleton {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
display: block;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-tabs-nav::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
|
||||||
|
td {
|
||||||
|
width: 50%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
articleList: css`
|
||||||
|
li {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-avatar > img {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
interface ArticleListProps {
|
interface ArticleListProps {
|
||||||
name: React.ReactNode;
|
name: React.ReactNode;
|
||||||
@ -13,37 +66,42 @@ interface ArticleListProps {
|
|||||||
authors: Authors;
|
authors: Authors;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArticleList: React.FC<ArticleListProps> = ({ name, data = [], authors = [] }) => (
|
const ArticleList: React.FC<ArticleListProps> = ({ name, data = [], authors = [] }) => {
|
||||||
<td>
|
const { articleList } = useStyle();
|
||||||
<h4>{name}</h4>
|
return (
|
||||||
<ul className="article-list">
|
<td>
|
||||||
{data.length === 0 ? (
|
<h4>{name}</h4>
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
<ul css={articleList}>
|
||||||
) : (
|
{data.length === 0 ? (
|
||||||
data.map((article, index) => {
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
const author = authors.find(auth => auth.name === article.author);
|
) : (
|
||||||
return (
|
data.map((article, index) => {
|
||||||
<li key={index}>
|
const author = authors.find(auth => auth.name === article.author);
|
||||||
<a href={author?.href} target="_blank" rel="noreferrer">
|
return (
|
||||||
<Avatar size="small" src={author?.avatar} />
|
<li key={index}>
|
||||||
</a>
|
<a href={author?.href} target="_blank" rel="noreferrer">
|
||||||
<Divider type="vertical" />
|
<Avatar size="small" src={author?.avatar} />
|
||||||
<a href={article.href} target="_blank" rel="noreferrer">
|
</a>
|
||||||
{article.title}
|
<Divider type="vertical" />
|
||||||
</a>
|
<a href={article.href} target="_blank" rel="noreferrer">
|
||||||
</li>
|
{article.title}
|
||||||
);
|
</a>
|
||||||
})
|
</li>
|
||||||
)}
|
);
|
||||||
</ul>
|
})
|
||||||
</td>
|
)}
|
||||||
);
|
</ul>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { locale } = useIntl();
|
const { locale } = useIntl();
|
||||||
const isZhCN = locale === 'zh-CN';
|
const isZhCN = locale === 'zh-CN';
|
||||||
const [{ articles = { cn: [], en: [] }, authors = [] }, loading] = useSiteData();
|
const [{ articles = { cn: [], en: [] }, authors = [] }, loading] = useSiteData();
|
||||||
|
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
// ========================== Data ==========================
|
// ========================== Data ==========================
|
||||||
const mergedData = React.useMemo(() => {
|
const mergedData = React.useMemo(() => {
|
||||||
const yearData: Record<number | string, Record<string, Article[]>> = {};
|
const yearData: Record<number | string, Record<string, Article[]>> = {};
|
||||||
@ -89,7 +147,7 @@ export default () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="articles" className="antd-articles">
|
<div id="articles" css={styles.articles}>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
118
.dumi/theme/builtins/ResourceCards/index.tsx
Normal file
118
.dumi/theme/builtins/ResourceCards/index.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import React, { type FC } from 'react';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const { boxShadowSecondary } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
card: css`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
color: inherit;
|
||||||
|
list-style: none;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: ${boxShadowSecondary};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
image: css`
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
max-width: none;
|
||||||
|
height: 184px;
|
||||||
|
margin: -1px -1px 0;
|
||||||
|
object-fit: cover;
|
||||||
|
`,
|
||||||
|
badge: css`
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
background: rgba(0, 0, 0, 0.65);
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
|
||||||
|
`,
|
||||||
|
title: css`
|
||||||
|
margin: 16px 20px 8px;
|
||||||
|
color: #0d1a26;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 28px;
|
||||||
|
`,
|
||||||
|
description: css`
|
||||||
|
margin: 0 20px 20px;
|
||||||
|
color: #697b8c;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Resource = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
cover: string;
|
||||||
|
src: string;
|
||||||
|
official?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResourceCardProps = {
|
||||||
|
resource: Resource;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ResourceCard: FC<ResourceCardProps> = ({ resource }) => {
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
const { title: titleStr, description, cover, src, official } = resource;
|
||||||
|
|
||||||
|
let coverColor: string | null = null;
|
||||||
|
let title: string = titleStr;
|
||||||
|
const titleMatch = titleStr.match(/(.*)(#[\dA-Fa-f]{6})/);
|
||||||
|
if (titleMatch) {
|
||||||
|
title = titleMatch[1].trim();
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
coverColor = titleMatch[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6} style={{ padding: 12 }}>
|
||||||
|
<a css={styles.card} target="_blank" href={src}>
|
||||||
|
<img
|
||||||
|
css={styles.image}
|
||||||
|
src={cover}
|
||||||
|
alt={title}
|
||||||
|
style={coverColor ? { backgroundColor: coverColor } : {}}
|
||||||
|
/>
|
||||||
|
{official && <div css={styles.badge}>Official</div>}
|
||||||
|
<p css={styles.title}>{title}</p>
|
||||||
|
<p css={styles.description}>{description}</p>
|
||||||
|
</a>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResourceCardsProps = {
|
||||||
|
resources: Resource[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ResourceCards: FC<ResourceCardsProps> = ({ resources }) => {
|
||||||
|
return (
|
||||||
|
<Row style={{ margin: '-12px -12px 0 -12px' }}>
|
||||||
|
{resources.map(item => (
|
||||||
|
<ResourceCard resource={item} key={item.title} />
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceCards;
|
5
.dumi/theme/common/BrowserFrame.jsx
Normal file
5
.dumi/theme/common/BrowserFrame.jsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const BrowserFrame = ({ children }) => <div className="browser-mockup with-url">{children}</div>;
|
||||||
|
|
||||||
|
export default BrowserFrame;
|
23
.dumi/theme/common/CommonHelmet.tsx
Normal file
23
.dumi/theme/common/CommonHelmet.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useRouteMeta } from 'dumi';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet-async';
|
||||||
|
|
||||||
|
const CommonHelmet = () => {
|
||||||
|
const meta = useRouteMeta();
|
||||||
|
|
||||||
|
const [title, description] = useMemo(() => {
|
||||||
|
const helmetTitle = `${meta.frontmatter.subtitle || ''} ${meta.frontmatter.title} - Ant Design`;
|
||||||
|
let helmetDescription = meta.frontmatter.description;
|
||||||
|
return [helmetTitle, helmetDescription];
|
||||||
|
}, [meta]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<title>{title}</title>
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
{description && <meta name="description" content={description} />}
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommonHelmet;
|
56
.dumi/theme/common/EditButton.tsx
Normal file
56
.dumi/theme/common/EditButton.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const branchUrl = 'https://github.com/ant-design/ant-design/edit/master/';
|
||||||
|
|
||||||
|
export interface EditButtonProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
filename?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { colorIcon, colorText, iconCls } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
editButton: css`
|
||||||
|
a& {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
|
||||||
|
${iconCls} {
|
||||||
|
display: block;
|
||||||
|
color: ${colorIcon};
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${colorText};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditButton({ title, filename }: EditButtonProps) {
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<a
|
||||||
|
css={styles.editButton}
|
||||||
|
href={`${branchUrl}${filename}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<EditOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
1795
.dumi/theme/common/GlobalStyles.tsx
Normal file
1795
.dumi/theme/common/GlobalStyles.tsx
Normal file
File diff suppressed because it is too large
Load Diff
142
.dumi/theme/common/PrevAndNext.tsx
Normal file
142
.dumi/theme/common/PrevAndNext.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, { ReactElement, useMemo } from 'react';
|
||||||
|
import { ClassNames, css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../hooks/useSiteToken';
|
||||||
|
import { Menu, MenuProps, Typography } from 'antd';
|
||||||
|
import useMenu from '../../hooks/useMenu';
|
||||||
|
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
|
||||||
|
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { colorSplit, iconCls, fontSizeIcon } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
prevNextNav: css`
|
||||||
|
width: calc(100% - 234px);
|
||||||
|
margin-inline-end: 170px;
|
||||||
|
margin-inline-start: 64px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
border-top: 1px solid ${colorSplit};
|
||||||
|
display: flex;
|
||||||
|
`,
|
||||||
|
pageNav: `
|
||||||
|
flex: 1;
|
||||||
|
height: 72px;
|
||||||
|
line-height: 72px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
${iconCls} {
|
||||||
|
font-size: ${fontSizeIcon}px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chinese {
|
||||||
|
margin-inline-start: 4px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
prevNav: `
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
.footer-nav-icon-after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-nav-icon-before {
|
||||||
|
position: relative;
|
||||||
|
margin-inline-end: 1em;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 0;
|
||||||
|
right: 0;
|
||||||
|
transition: right 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .footer-nav-icon-before {
|
||||||
|
right: 0.2em;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
nextNav: `
|
||||||
|
text-align: end;
|
||||||
|
|
||||||
|
.footer-nav-icon-before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-nav-icon-after {
|
||||||
|
position: relative;
|
||||||
|
margin-inline-start: 1em;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: left 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .footer-nav-icon-after {
|
||||||
|
left: 0.2em;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const flattenMenu = (menuItems: MenuProps['items']): MenuProps['items'] | null => {
|
||||||
|
if (Array.isArray(menuItems)) {
|
||||||
|
return menuItems.reduce<Exclude<MenuProps['items'], undefined>>((acc, item) => {
|
||||||
|
if (!item) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
if ('children' in item && item.children) {
|
||||||
|
return acc.concat(flattenMenu(item.children) ?? []);
|
||||||
|
}
|
||||||
|
return acc.concat(item);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PrevAndNext = () => {
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
const [menuItems, selectedKey] = useMenu({
|
||||||
|
before: <LeftOutlined className="footer-nav-icon-before" />,
|
||||||
|
after: <RightOutlined className="footer-nav-icon-after" />,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [prev, next] = useMemo(() => {
|
||||||
|
const flatMenu = flattenMenu(menuItems);
|
||||||
|
if (!flatMenu) {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
let activeMenuItemIndex = -1;
|
||||||
|
flatMenu.forEach((menuItem, i) => {
|
||||||
|
if (menuItem && menuItem.key === selectedKey) {
|
||||||
|
activeMenuItemIndex = i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const prev = flatMenu[activeMenuItemIndex - 1];
|
||||||
|
const next = flatMenu[activeMenuItemIndex + 1];
|
||||||
|
return [prev as MenuItemType, next as MenuItemType];
|
||||||
|
}, [menuItems, selectedKey]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section css={styles.prevNextNav}>
|
||||||
|
<ClassNames>
|
||||||
|
{({ css: classCss, cx }) => (
|
||||||
|
<>
|
||||||
|
{prev &&
|
||||||
|
React.cloneElement(prev.label as ReactElement, {
|
||||||
|
className: cx(classCss(styles.pageNav), classCss(styles.prevNav)),
|
||||||
|
})}
|
||||||
|
{next &&
|
||||||
|
React.cloneElement(next.label as ReactElement, {
|
||||||
|
className: cx(classCss(styles.pageNav), classCss(styles.nextNav)),
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ClassNames>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrevAndNext;
|
151
.dumi/theme/layouts/DocLayout/index.tsx
Normal file
151
.dumi/theme/layouts/DocLayout/index.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import React, { type FC, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { useOutlet, useSearchParams } from 'dumi';
|
||||||
|
import Header from 'dumi/theme/slots/Header';
|
||||||
|
import Footer from 'dumi/theme/slots/Footer';
|
||||||
|
import '../../static/style';
|
||||||
|
import useLocation from '../../../hooks/useLocation';
|
||||||
|
import SiteContext from '../../slots/SiteContext';
|
||||||
|
import ConfigProvider, { DirectionType } from 'antd/es/config-provider';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||||
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
import zhCN from 'antd/es/locale/zh_CN';
|
||||||
|
import { createCache, StyleProvider } from '@ant-design/cssinjs';
|
||||||
|
import ResourceLayout from '../ResourceLayout';
|
||||||
|
import GlobalStyles from '../../common/GlobalStyles';
|
||||||
|
import SidebarLayout from '../SidebarLayout';
|
||||||
|
|
||||||
|
const styleCache = createCache();
|
||||||
|
if (typeof global !== 'undefined') {
|
||||||
|
(global as any).styleCache = styleCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
cn: {
|
||||||
|
title: 'Ant Design - 一套企业级 UI 设计语言和 React 组件库',
|
||||||
|
description: '基于 Ant Design 设计体系的 React UI 组件库,用于研发企业级中后台产品。',
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: "Ant Design - The world's second most popular React UI framework",
|
||||||
|
description:
|
||||||
|
'An enterprise-class UI design language and React UI library with a set of high-quality React components, one of best React UI library for enterprises',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const RESPONSIVE_MOBILE = 768;
|
||||||
|
|
||||||
|
const DocLayout: FC = () => {
|
||||||
|
const outlet = useOutlet();
|
||||||
|
const location = useLocation();
|
||||||
|
const { pathname, search } = location;
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const [locale, lang] = useLocale(locales);
|
||||||
|
|
||||||
|
// TODO: place doc layout here, apply for all docs route paths
|
||||||
|
// migrate from: https://github.com/ant-design/ant-design/blob/eb9179464b9c4a93c856e1e70ddbdbaaf3f3371f/site/theme/template/Layout/index.tsx
|
||||||
|
|
||||||
|
const [isMobile, setIsMobile] = React.useState<boolean>(false);
|
||||||
|
const [direction, setDirection] = React.useState<DirectionType>('ltr');
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const updateMobileMode = () => {
|
||||||
|
setIsMobile(window.innerWidth < RESPONSIVE_MOBILE);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const nprogressHiddenStyle = document.getElementById('nprogress-style');
|
||||||
|
if (nprogressHiddenStyle) {
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
nprogressHiddenStyle.parentNode?.removeChild(nprogressHiddenStyle);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle direction
|
||||||
|
const queryDirection = searchParams.get('direction');
|
||||||
|
setDirection(queryDirection === 'rtl' ? 'rtl' : 'ltr');
|
||||||
|
|
||||||
|
// Handle mobile mode
|
||||||
|
updateMobileMode();
|
||||||
|
window.addEventListener('resize', updateMobileMode);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', updateMobileMode);
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (typeof (window as any).ga !== 'undefined') {
|
||||||
|
(window as any).ga('send', 'pageview', pathname + search);
|
||||||
|
}
|
||||||
|
if (typeof (window as any)._hmt !== 'undefined') {
|
||||||
|
(window as any)._hmt.push(['_trackPageview', pathname + search]);
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const changeDirection = (direction: DirectionType): void => {
|
||||||
|
setDirection(direction);
|
||||||
|
if (direction === 'ltr') {
|
||||||
|
searchParams.delete('direction');
|
||||||
|
} else {
|
||||||
|
searchParams.set('direction', 'rtl');
|
||||||
|
}
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = useMemo(() => {
|
||||||
|
if (
|
||||||
|
['', '/'].some(path => path === pathname) ||
|
||||||
|
['/index'].some(path => pathname.startsWith(path))
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{outlet}
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else if (pathname.startsWith('/docs/resource')) {
|
||||||
|
return <ResourceLayout>{outlet}</ResourceLayout>;
|
||||||
|
} else if (pathname.startsWith('/theme-editor')) {
|
||||||
|
return <>{outlet}</>;
|
||||||
|
}
|
||||||
|
return <SidebarLayout>{outlet}</SidebarLayout>;
|
||||||
|
}, [pathname, outlet]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyleProvider cache={styleCache}>
|
||||||
|
<SiteContext.Provider value={{ isMobile, direction }}>
|
||||||
|
<HelmetProvider context={{}}>
|
||||||
|
<Helmet encodeSpecialCharacters={false}>
|
||||||
|
<html
|
||||||
|
lang={lang}
|
||||||
|
data-direction={direction}
|
||||||
|
className={classNames({ [`rtl`]: direction === 'rtl' })}
|
||||||
|
/>
|
||||||
|
<title>{locale.title}</title>
|
||||||
|
<link
|
||||||
|
sizes="144x144"
|
||||||
|
href="https://gw.alipayobjects.com/zos/antfincdn/UmVnt3t4T0/antd.png"
|
||||||
|
/>
|
||||||
|
<meta name="description" content={locale.description} />
|
||||||
|
<meta property="og:title" content={locale.title} />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png"
|
||||||
|
/>
|
||||||
|
</Helmet>
|
||||||
|
<ConfigProvider locale={lang === 'cn' ? zhCN : undefined} direction={direction}>
|
||||||
|
<GlobalStyles />
|
||||||
|
<Header changeDirection={changeDirection} />
|
||||||
|
{content}
|
||||||
|
</ConfigProvider>
|
||||||
|
</HelmetProvider>
|
||||||
|
</SiteContext.Provider>
|
||||||
|
</StyleProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocLayout;
|
63
.dumi/theme/layouts/GlobalLayout.tsx
Normal file
63
.dumi/theme/layouts/GlobalLayout.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, { type FC, useLayoutEffect } from 'react';
|
||||||
|
import { useOutlet } from 'dumi';
|
||||||
|
import { ConfigProvider, theme as antdTheme } from 'antd';
|
||||||
|
import { ThemeConfig } from 'antd/es/config-provider/context';
|
||||||
|
import ThemeContext, { ThemeContextProps } from '../slots/ThemeContext';
|
||||||
|
|
||||||
|
const ANT_DESIGN_SITE_THEME = 'antd-site-theme';
|
||||||
|
|
||||||
|
const GlobalLayout: FC = () => {
|
||||||
|
const outlet = useOutlet();
|
||||||
|
|
||||||
|
const [theme, setTheme] = React.useState<ThemeConfig>({});
|
||||||
|
|
||||||
|
const contextValue = React.useMemo<ThemeContextProps>(
|
||||||
|
() => ({
|
||||||
|
theme,
|
||||||
|
setTheme: newTheme => {
|
||||||
|
setTheme(newTheme);
|
||||||
|
localStorage.setItem(
|
||||||
|
ANT_DESIGN_SITE_THEME,
|
||||||
|
JSON.stringify(newTheme, (key, value) => {
|
||||||
|
if (key === 'algorithm') {
|
||||||
|
return value === antdTheme.darkAlgorithm ? 'dark' : value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[theme],
|
||||||
|
);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const localTheme = localStorage.getItem(ANT_DESIGN_SITE_THEME);
|
||||||
|
if (localTheme) {
|
||||||
|
try {
|
||||||
|
const themeConfig = JSON.parse(localTheme);
|
||||||
|
if (themeConfig.algorithm === 'dark') {
|
||||||
|
themeConfig.algorithm = antdTheme.darkAlgorithm;
|
||||||
|
}
|
||||||
|
setTheme(themeConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={contextValue}>
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
...theme,
|
||||||
|
// TODO: Site algorithm
|
||||||
|
// algorithm: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{outlet}
|
||||||
|
</ConfigProvider>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlobalLayout;
|
@ -3,11 +3,53 @@ import classNames from 'classnames';
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import scrollTo from '../../../../components/_util/scrollTo';
|
import scrollTo from '../../../../components/_util/scrollTo';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
import './AffixTabs.less';
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { boxShadowSecondary, antCls } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
affixTabs: css`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 11;
|
||||||
|
padding: 0 40px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: ${boxShadowSecondary};
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
|
||||||
|
${antCls}-tabs {
|
||||||
|
max-width: 1208px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
${antCls}-tabs-nav {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-tabs-tab {
|
||||||
|
padding: 21px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
affixTabsFixed: css`
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const VIEW_BALANCE = 32;
|
const VIEW_BALANCE = 32;
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
@ -15,6 +57,8 @@ export default () => {
|
|||||||
const [loaded, setLoaded] = React.useState(false);
|
const [loaded, setLoaded] = React.useState(false);
|
||||||
const [fixedId, setFixedId] = React.useState<string | null>(null);
|
const [fixedId, setFixedId] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
function scrollToId(id: string) {
|
function scrollToId(id: string) {
|
||||||
const targetNode = document.getElementById(id);
|
const targetNode = document.getElementById(id);
|
||||||
|
|
||||||
@ -70,22 +114,17 @@ export default () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div css={[styles.affixTabs, fixedId && styles.affixTabsFixed]} ref={containerRef}>
|
||||||
className={classNames('resource-affix-tabs', {
|
|
||||||
'resource-affix-tabs-fixed': fixedId,
|
|
||||||
})}
|
|
||||||
ref={containerRef}
|
|
||||||
>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={fixedId || undefined}
|
activeKey={fixedId || undefined}
|
||||||
onChange={key => {
|
onChange={key => {
|
||||||
scrollToId(key);
|
scrollToId(key);
|
||||||
}}
|
}}
|
||||||
>
|
items={idsRef.current.map(id => ({
|
||||||
{idsRef.current.map(id => (
|
key: id,
|
||||||
<TabPane key={id} tab={id.replace(/-/g, ' ')} />
|
label: <span style={{ textTransform: 'capitalize' }}>{id.replace(/-/g, ' ')}</span>,
|
||||||
))}
|
}))}
|
||||||
</Tabs>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
139
.dumi/theme/layouts/ResourceLayout/index.tsx
Normal file
139
.dumi/theme/layouts/ResourceLayout/index.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import React, { FC, PropsWithChildren } from 'react';
|
||||||
|
import Footer from 'dumi/theme/slots/Footer';
|
||||||
|
import AffixTabs from './AffixTabs';
|
||||||
|
import { useRouteMeta } from 'dumi';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import EditButton from '../../common/EditButton';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { Layout, Typography } from 'antd';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import CommonHelmet from '../../common/CommonHelmet';
|
||||||
|
|
||||||
|
export type ResourceLayoutProps = PropsWithChildren<{}>;
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const { antCls } = token;
|
||||||
|
|
||||||
|
const resourcePadding = 40;
|
||||||
|
const articleMaxWidth = 1208;
|
||||||
|
const resourcePaddingXS = 24;
|
||||||
|
|
||||||
|
return {
|
||||||
|
resourcePage: css`
|
||||||
|
footer {
|
||||||
|
margin-top: 176px;
|
||||||
|
|
||||||
|
.rc-footer-container {
|
||||||
|
max-width: ${articleMaxWidth}px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
resourceContent: css`
|
||||||
|
padding: 0 ${resourcePadding}px;
|
||||||
|
max-width: ${articleMaxWidth}px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: content-box;
|
||||||
|
|
||||||
|
> .markdown {
|
||||||
|
> p {
|
||||||
|
margin-bottom: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 124px;
|
||||||
|
color: #314659;
|
||||||
|
font-weight: lighter;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 38px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 88px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 56px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #697b8c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 767.99px) {
|
||||||
|
& {
|
||||||
|
article {
|
||||||
|
padding: 0 ${resourcePaddingXS}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-col {
|
||||||
|
padding-top: 16px !important;
|
||||||
|
padding-bottom: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
banner: css`
|
||||||
|
padding: 0 ${resourcePadding}px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: url('https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*y_r7RogIG1wAAAAAAAAAAABkARQnAQ');
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
box-sizing: content-box;
|
||||||
|
max-width: ${articleMaxWidth}px;
|
||||||
|
margin: 56px auto 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
max-width: ${articleMaxWidth}px;
|
||||||
|
margin: 0 auto 56px;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 767.99px) {
|
||||||
|
& {
|
||||||
|
margin: 0 -${resourcePaddingXS}px;
|
||||||
|
padding: 0 ${resourcePaddingXS}px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ResourceLayout: FC<ResourceLayoutProps> = ({ children }) => {
|
||||||
|
const styles = useStyle();
|
||||||
|
const meta = useRouteMeta();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<CommonHelmet />
|
||||||
|
<div id="resources-page" css={styles.resourcePage}>
|
||||||
|
<AffixTabs />
|
||||||
|
<div css={styles.banner}>
|
||||||
|
<Typography.Title>
|
||||||
|
{meta.frontmatter.title}
|
||||||
|
<EditButton
|
||||||
|
title={<FormattedMessage id="app.content.edit-page" />}
|
||||||
|
filename={meta.frontmatter.filename}
|
||||||
|
/>
|
||||||
|
</Typography.Title>
|
||||||
|
<section>{meta.frontmatter.description}</section>
|
||||||
|
</div>
|
||||||
|
<div css={styles.resourceContent}>{children}</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResourceLayout;
|
16
.dumi/theme/layouts/SidebarLayout/index.tsx
Normal file
16
.dumi/theme/layouts/SidebarLayout/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React, { FC, PropsWithChildren } from 'react';
|
||||||
|
import Sidebar from '../../slots/Sidebar';
|
||||||
|
import Content from '../../slots/Content';
|
||||||
|
import CommonHelmet from '../../common/CommonHelmet';
|
||||||
|
|
||||||
|
const SidebarLayout: FC<PropsWithChildren<{}>> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<main style={{ display: 'flex', marginTop: 40 }}>
|
||||||
|
<CommonHelmet />
|
||||||
|
<Sidebar />
|
||||||
|
<Content>{children}</Content>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarLayout;
|
138
.dumi/theme/locales/en-US.json
Normal file
138
.dumi/theme/locales/en-US.json
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"app.theme.switch.dynamic": "Dynamic Theme",
|
||||||
|
"app.theme.switch.default": "Default theme",
|
||||||
|
"app.theme.switch.dark": "Dark theme",
|
||||||
|
"app.theme.switch.compact": "Compact theme",
|
||||||
|
"app.header.search": "Search...",
|
||||||
|
"app.header.menu.documentation": "Docs",
|
||||||
|
"app.header.menu.components": "Components",
|
||||||
|
"app.header.menu.spec": "Design",
|
||||||
|
"app.header.menu.resource": "Resources",
|
||||||
|
"app.header.menu.more": "More",
|
||||||
|
"app.header.menu.mobile": "Mobile",
|
||||||
|
"app.header.menu.pro.v4": "Ant Design Pro",
|
||||||
|
"app.header.menu.pro.components": "Ant Design Pro Components",
|
||||||
|
"app.header.menu.charts": "Ant Design Charts",
|
||||||
|
"app.header.menu.ecosystem": "Ecosystem",
|
||||||
|
"app.header.lang": "中文",
|
||||||
|
"app.content.edit-page": "Edit this page on GitHub!",
|
||||||
|
"app.content.edit-demo": "Edit this demo on GitHub!",
|
||||||
|
"app.content.contributors": "contributors",
|
||||||
|
"app.component.examples": "Examples",
|
||||||
|
"app.component.examples.expand": "Expand all code",
|
||||||
|
"app.component.examples.collapse": "Collapse all code",
|
||||||
|
"app.component.examples.visible": "Expand debug examples",
|
||||||
|
"app.component.examples.hide": "Collapse debug examples",
|
||||||
|
"app.component.examples.openDemoNotReact18": "Open Demo with React < 18",
|
||||||
|
"app.component.examples.openDemoWithReact18": "Open Demo with React 18",
|
||||||
|
"app.demo.debug": "Debug only, won't display at online",
|
||||||
|
"app.demo.copy": "Copy code",
|
||||||
|
"app.demo.copied": "Copied!",
|
||||||
|
"app.demo.code.show": "Show code",
|
||||||
|
"app.demo.code.hide": "Hide code",
|
||||||
|
"app.demo.codepen": "Open in CodePen",
|
||||||
|
"app.demo.codesandbox": "Open in CodeSandbox",
|
||||||
|
"app.demo.stackblitz": "Open in Stackblitz",
|
||||||
|
"app.demo.riddle": "Open in Riddle",
|
||||||
|
"app.home.introduce": "A design system for enterprise-level products. Create an efficient and enjoyable work experience.",
|
||||||
|
"app.home.pr-welcome": "💡 It is an alpha version and still in progress. Contribution from community is welcome!",
|
||||||
|
"app.home.recommend": "Recommended",
|
||||||
|
"app.home.popularize": "Popular",
|
||||||
|
"app.home.design-and-framework": "Design language and framework",
|
||||||
|
"app.home.design-values": "Design values",
|
||||||
|
"app.home.design-values-description": "This is Ant Design's internal standard for evaluating design quality. Based on the assumption that \"everyone is pursuing happiness at work\", we have added the two values of \"Meaningfulness\" and \"Growth\" on the basis of \"Certainty\" and \"Naturalness\" to guide each designer towards better judgment and decision-making.",
|
||||||
|
"app.home.certainty": "Certainty",
|
||||||
|
"app.home.meaningful": "Meaningfulness",
|
||||||
|
"app.home.growth": "Growth",
|
||||||
|
"app.home.natural": "Naturalness",
|
||||||
|
"app.home.design-guide": "Guidelines",
|
||||||
|
"app.home.components": "Components",
|
||||||
|
"app.home.detail": "More details",
|
||||||
|
"app.home.global-style": "Global style",
|
||||||
|
"app.home.design-patterns": "Design patterns",
|
||||||
|
"app.home.more": "Learn More",
|
||||||
|
"app.home.getting-started": "Getting Started",
|
||||||
|
"app.home.design-language": "Design Language",
|
||||||
|
"app.home.product-antv-slogan": "A new way to do data visualization",
|
||||||
|
"app.home.product-pro-slogan": "Out-of-the-box UI solution for enterprise applications",
|
||||||
|
"app.home.product-mobile-slogan": "Mobile UI components with Ant Design",
|
||||||
|
"app.home.product-hitu": "HiTu",
|
||||||
|
"app.home.product-hitu-slogan": "A new generation of graphical solutions",
|
||||||
|
"app.home.product-kitchen-slogan": "A Sketch plugin to enhance designers",
|
||||||
|
"app.home.product-icons-slogan": "A set of premium icons",
|
||||||
|
"app.home.view-more": "More",
|
||||||
|
"app.footer.repo": "GitHub Repository",
|
||||||
|
"app.footer.awesome": "Awesome Ant Design",
|
||||||
|
"app.footer.course": "Ant Design Practical Tutorial",
|
||||||
|
"app.footer.chinamirror": "China Mirror 🇨🇳",
|
||||||
|
"app.footer.primary-color-changing": "Changing primary color...",
|
||||||
|
"app.footer.primary-color-changed": "Changed primary color successfully!",
|
||||||
|
"app.footer.scaffold": "Scaffold",
|
||||||
|
"app.footer.kitchen": "Sketch Toolkit",
|
||||||
|
"app.footer.landing": "Landing Templates",
|
||||||
|
"app.footer.scaffolds": "Scaffold Market",
|
||||||
|
"app.footer.dev-tools": "Developer Tools",
|
||||||
|
"app.footer.umi": "React Application Framework",
|
||||||
|
"app.footer.dumi": "Component doc generator",
|
||||||
|
"app.footer.qiankun": "Micro-Frontends Framework",
|
||||||
|
"app.footer.hooks": "React Hooks Library",
|
||||||
|
"app.footer.resources": "Resources",
|
||||||
|
"app.footer.data-vis": "Data Visualization",
|
||||||
|
"app.footer.eggjs": "Enterprise Node Framework",
|
||||||
|
"app.footer.motion": "Motion Solution",
|
||||||
|
"app.footer.antd-library": "Axure library",
|
||||||
|
"app.footer.antux": "Sitemap Template",
|
||||||
|
"app.footer.community": "Community",
|
||||||
|
"app.footer.help": "Help",
|
||||||
|
"app.footer.change-log": "Change Log",
|
||||||
|
"app.footer.theme": "Theme Editor",
|
||||||
|
"app.footer.faq": "FAQ",
|
||||||
|
"app.footer.feedback": "Feedback",
|
||||||
|
"app.footer.stackoverflow": "StackOverflow",
|
||||||
|
"app.footer.segmentfault": "SegmentFault",
|
||||||
|
"app.footer.discussions": "Discussions",
|
||||||
|
"app.footer.bug-report": "Bug Report",
|
||||||
|
"app.footer.issues": "Issues",
|
||||||
|
"app.footer.version": "Version: ",
|
||||||
|
"app.footer.author": "Created by XTech",
|
||||||
|
"app.footer.work_with_us": "Work with Us",
|
||||||
|
"app.footer.more-product": "More Products",
|
||||||
|
"app.footer.company": "XTech",
|
||||||
|
"app.footer.ant-design": "UI Design Language",
|
||||||
|
"app.footer.yuque": "YuQue",
|
||||||
|
"app.footer.yuque.slogan": "Write your document as a team",
|
||||||
|
"app.footer.antv.slogan": "Data Visualization",
|
||||||
|
"app.footer.egg.slogan": "Enterprise Node.js Framework",
|
||||||
|
"app.footer.zhihu": "Ant Design Blog",
|
||||||
|
"app.footer.zhihu.xtech": "Experience Cloud Blog",
|
||||||
|
"app.footer.seeconf": "Experience Tech Conference",
|
||||||
|
"app.footer.xtech": "Ant Financial Experience Tech",
|
||||||
|
"app.footer.xtech.slogan": "Experience The Beauty",
|
||||||
|
"app.docs.color.pick-primary": "Pick your primary color",
|
||||||
|
"app.docs.color.pick-background": "Pick your background color",
|
||||||
|
"app.docs.components.icon.search.placeholder": "Search icons here, click icon to copy code",
|
||||||
|
"app.docs.components.icon.outlined": "Outlined",
|
||||||
|
"app.docs.components.icon.filled": "Filled",
|
||||||
|
"app.docs.components.icon.two-tone": "Two Tone",
|
||||||
|
"app.docs.components.icon.category.direction": "Directional Icons",
|
||||||
|
"app.docs.components.icon.category.suggestion": "Suggested Icons",
|
||||||
|
"app.docs.components.icon.category.editor": "Editor Icons",
|
||||||
|
"app.docs.components.icon.category.data": "Data Icons",
|
||||||
|
"app.docs.components.icon.category.other": "Application Icons",
|
||||||
|
"app.docs.components.icon.category.logo": "Brand and Logos",
|
||||||
|
"app.docs.components.icon.pic-searcher.intro": "AI Search by image is online, you are welcome to use it! 🎉",
|
||||||
|
"app.docs.components.icon.pic-searcher.title": "Search by image",
|
||||||
|
"app.docs.components.icon.pic-searcher.upload-text": "Click, drag, or paste file to this area to upload",
|
||||||
|
"app.docs.components.icon.pic-searcher.upload-hint": "We will find the best matching icon based on the image provided",
|
||||||
|
"app.docs.components.icon.pic-searcher.server-error": "Predict service is temporarily unavailable",
|
||||||
|
"app.docs.components.icon.pic-searcher.matching": "Matching...",
|
||||||
|
"app.docs.components.icon.pic-searcher.modelloading": "Model is loading...",
|
||||||
|
"app.docs.components.icon.pic-searcher.result-tip": "Matched the following icons for you:",
|
||||||
|
"app.docs.components.icon.pic-searcher.th-icon": "Icon",
|
||||||
|
"app.docs.components.icon.pic-searcher.th-score": "Probability",
|
||||||
|
"app.docs.resource.design": "Design",
|
||||||
|
"app.docs.resource.develop": "Develop",
|
||||||
|
"app.components.overview.search": "Search in components",
|
||||||
|
"app.implementation.community": "community",
|
||||||
|
"app.implementation.official": "official"
|
||||||
|
}
|
137
.dumi/theme/locales/zh-CN.json
Normal file
137
.dumi/theme/locales/zh-CN.json
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"app.theme.switch.dynamic": "动态主题",
|
||||||
|
"app.theme.switch.default": "默认主题",
|
||||||
|
"app.theme.switch.dark": "暗黑主题",
|
||||||
|
"app.theme.switch.compact": "紧凑主题",
|
||||||
|
"app.header.search": "全文本搜索...",
|
||||||
|
"app.header.menu.documentation": "文档",
|
||||||
|
"app.header.menu.components": "组件",
|
||||||
|
"app.header.menu.spec": "设计",
|
||||||
|
"app.header.menu.resource": "资源",
|
||||||
|
"app.header.menu.more": "更多",
|
||||||
|
"app.header.menu.mobile": "移动版",
|
||||||
|
"app.header.menu.pro.v4": "Ant Design Pro",
|
||||||
|
"app.header.menu.pro.components": "Ant Design Pro Components",
|
||||||
|
"app.header.menu.charts": "Ant Design Charts",
|
||||||
|
"app.header.menu.ecosystem": "生态",
|
||||||
|
"app.header.lang": "English",
|
||||||
|
"app.content.edit-page": "在 GitHub 上编辑此页!",
|
||||||
|
"app.content.edit-demo": "在 GitHub 上编辑此示例!",
|
||||||
|
"app.content.contributors": "文档贡献者",
|
||||||
|
"app.component.examples": "代码演示",
|
||||||
|
"app.component.examples.expand": "展开全部代码",
|
||||||
|
"app.component.examples.collapse": "收起全部代码",
|
||||||
|
"app.component.examples.visible": "显示调试专用演示",
|
||||||
|
"app.component.examples.hide": "隐藏调试专用演示",
|
||||||
|
"app.component.examples.openDemoNotReact18": "使用 React 18 以下版本打开 Demo",
|
||||||
|
"app.component.examples.openDemoWithReact18": "使用 React 18 打开 Demo",
|
||||||
|
"app.demo.debug": "此演示仅供调试,线上不会展示",
|
||||||
|
"app.demo.copy": "复制代码",
|
||||||
|
"app.demo.copied": "复制成功",
|
||||||
|
"app.demo.code.show": "显示代码",
|
||||||
|
"app.demo.code.hide": "收起代码",
|
||||||
|
"app.demo.codepen": "在 CodePen 中打开",
|
||||||
|
"app.demo.codesandbox": "在 CodeSandbox 中打开",
|
||||||
|
"app.demo.stackblitz": "在 Stackblitz 中打开",
|
||||||
|
"app.demo.riddle": "在 Riddle 中打开",
|
||||||
|
"app.home.introduce": "企业级产品设计体系,创造高效愉悦的工作体验",
|
||||||
|
"app.home.pr-welcome": "💡 当前为 alpha 版本,仍在开发中。欢迎社区一起共建,让 Ant Design 变得更好!",
|
||||||
|
"app.home.recommend": "精彩推荐",
|
||||||
|
"app.home.popularize": "推广",
|
||||||
|
"app.home.design-and-framework": "设计语言与研发框架",
|
||||||
|
"app.home.design-values": "设计价值观",
|
||||||
|
"app.home.design-values-description": "这是 Ant Design 评价设计好坏的内在标准。基于「每个人都追求快乐工作」这一假定,我们在「确定性」和「自然」的基础上,新增「意义感」和「生长性」两个价值观,指引每个设计者做更好地判断和决策。",
|
||||||
|
"app.home.certainty": "确定性",
|
||||||
|
"app.home.meaningful": "意义感",
|
||||||
|
"app.home.growth": "生长性",
|
||||||
|
"app.home.natural": "自然",
|
||||||
|
"app.home.design-guide": "设计指引",
|
||||||
|
"app.home.components": "组件库",
|
||||||
|
"app.home.detail": "查看详情",
|
||||||
|
"app.home.global-style": "全局样式",
|
||||||
|
"app.home.design-patterns": "设计模式",
|
||||||
|
"app.home.more": "更多内容",
|
||||||
|
"app.home.getting-started": "开始使用",
|
||||||
|
"app.home.design-language": "设计语言",
|
||||||
|
"app.home.product-antv-slogan": "全新一代数据可视化解决方案",
|
||||||
|
"app.home.product-pro-slogan": "开箱即用的中台前端/设计解决方案",
|
||||||
|
"app.home.product-mobile-slogan": "基于 Preact / React / React Native 的 UI 组件库",
|
||||||
|
"app.home.product-hitu": "海兔",
|
||||||
|
"app.home.product-hitu-slogan": "全新一代图形化解决方案",
|
||||||
|
"app.home.product-kitchen-slogan": "一款为设计者提升工作效率的 Sketch 工具集",
|
||||||
|
"app.home.product-icons-slogan": "一整套优质的图标集",
|
||||||
|
"app.home.view-more": "查看全部",
|
||||||
|
"app.footer.repo": "GitHub 仓库",
|
||||||
|
"app.footer.awesome": "Awesome Ant Design",
|
||||||
|
"app.footer.chinamirror": "国内镜像站点 🇨🇳",
|
||||||
|
"app.footer.primary-color-changing": "正在修改主题色...",
|
||||||
|
"app.footer.primary-color-changed": "修改主题色成功!",
|
||||||
|
"app.footer.kitchen": "Sketch 工具集",
|
||||||
|
"app.footer.landing": "首页模板集",
|
||||||
|
"app.footer.scaffold": "脚手架",
|
||||||
|
"app.footer.scaffolds": "脚手架市场",
|
||||||
|
"app.footer.dev-tools": "开发工具",
|
||||||
|
"app.footer.umi": "React 应用开发框架",
|
||||||
|
"app.footer.dumi": "组件/文档研发工具",
|
||||||
|
"app.footer.qiankun": "微前端框架",
|
||||||
|
"app.footer.hooks": "React Hooks 库",
|
||||||
|
"app.footer.resources": "相关资源",
|
||||||
|
"app.footer.data-vis": "数据可视化",
|
||||||
|
"app.footer.eggjs": "企业级 Node 开发框架",
|
||||||
|
"app.footer.motion": "设计动效",
|
||||||
|
"app.footer.antd-library": "Axure 部件库",
|
||||||
|
"app.footer.antux": "页面逻辑素材",
|
||||||
|
"app.footer.community": "社区",
|
||||||
|
"app.footer.help": "帮助",
|
||||||
|
"app.footer.change-log": "更新日志",
|
||||||
|
"app.footer.theme": "主题编辑器",
|
||||||
|
"app.footer.faq": "常见问题",
|
||||||
|
"app.footer.feedback": "反馈和建议",
|
||||||
|
"app.footer.stackoverflow": "StackOverflow",
|
||||||
|
"app.footer.segmentfault": "SegmentFault",
|
||||||
|
"app.footer.discussions": "讨论区",
|
||||||
|
"app.footer.bug-report": "报告 Bug",
|
||||||
|
"app.footer.issues": "讨论列表",
|
||||||
|
"app.footer.version": "文档版本:",
|
||||||
|
"app.footer.author": "蚂蚁集团体验技术部出品 @ XTech",
|
||||||
|
"app.footer.work_with_us": "加入我们",
|
||||||
|
"app.footer.more-product": "更多产品",
|
||||||
|
"app.footer.company": "XTech",
|
||||||
|
"app.footer.ant-design": "蚂蚁 UI 体系",
|
||||||
|
"app.footer.yuque": "语雀",
|
||||||
|
"app.footer.yuque.slogan": "专业的云端知识库",
|
||||||
|
"app.footer.antv.slogan": "数据可视化解决方案",
|
||||||
|
"app.footer.egg.slogan": "企业级 Node.js 框架",
|
||||||
|
"app.footer.zhihu": "Ant Design 专栏",
|
||||||
|
"app.footer.zhihu.xtech": "体验科技专栏",
|
||||||
|
"app.footer.seeconf": "蚂蚁体验科技大会",
|
||||||
|
"app.footer.xtech": "蚂蚁体验科技",
|
||||||
|
"app.footer.xtech.slogan": "让用户体验美好",
|
||||||
|
"app.docs.color.pick-primary": "选择你的主色",
|
||||||
|
"app.docs.color.pick-background": "选择你的背景色",
|
||||||
|
"app.docs.components.icon.search.placeholder": "在此搜索图标,点击图标可复制代码",
|
||||||
|
"app.docs.components.icon.outlined": "线框风格",
|
||||||
|
"app.docs.components.icon.filled": "实底风格",
|
||||||
|
"app.docs.components.icon.two-tone": "双色风格",
|
||||||
|
"app.docs.components.icon.category.direction": "方向性图标",
|
||||||
|
"app.docs.components.icon.category.suggestion": "提示建议性图标",
|
||||||
|
"app.docs.components.icon.category.editor": "编辑类图标",
|
||||||
|
"app.docs.components.icon.category.data": "数据类图标",
|
||||||
|
"app.docs.components.icon.category.other": "网站通用图标",
|
||||||
|
"app.docs.components.icon.category.logo": "品牌和标识",
|
||||||
|
"app.docs.components.icon.pic-searcher.intro": "AI 截图搜索上线了,快来体验吧!🎉",
|
||||||
|
"app.docs.components.icon.pic-searcher.title": "上传图片搜索图标",
|
||||||
|
"app.docs.components.icon.pic-searcher.upload-text": "点击/拖拽/粘贴上传图片",
|
||||||
|
"app.docs.components.icon.pic-searcher.upload-hint": "我们会通过上传的图片进行匹配,得到最相似的图标",
|
||||||
|
"app.docs.components.icon.pic-searcher.server-error": "识别服务暂不可用",
|
||||||
|
"app.docs.components.icon.pic-searcher.matching": "匹配中...",
|
||||||
|
"app.docs.components.icon.pic-searcher.modelloading": "神经网络模型加载中...",
|
||||||
|
"app.docs.components.icon.pic-searcher.result-tip": "为您匹配到以下图标:",
|
||||||
|
"app.docs.components.icon.pic-searcher.th-icon": "图标",
|
||||||
|
"app.docs.components.icon.pic-searcher.th-score": "匹配度",
|
||||||
|
"app.docs.resource.design": "设计",
|
||||||
|
"app.docs.resource.develop": "开发",
|
||||||
|
"app.components.overview.search": "搜索组件",
|
||||||
|
"app.implementation.community": "社区实现",
|
||||||
|
"app.implementation.official": "官方"
|
||||||
|
}
|
116
.dumi/theme/plugin.ts
Normal file
116
.dumi/theme/plugin.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import type { IApi, IRoute } from 'dumi';
|
||||||
|
import { extractStyle } from '@ant-design/cssinjs';
|
||||||
|
import ReactTechStack from 'dumi/dist/techStacks/react';
|
||||||
|
import sylvanas from 'sylvanas';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extends dumi internal tech stack, for customize previewer props
|
||||||
|
*/
|
||||||
|
class AntdReactTechStack extends ReactTechStack {
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
generatePreviewerProps(...[props, opts]: any) {
|
||||||
|
if (opts.type === 'external') {
|
||||||
|
// try to find md file with the same name as the demo tsx file
|
||||||
|
const locale = opts.mdAbsPath.match(/index\.([a-z-]+)\.md$/i)?.[1];
|
||||||
|
const mdPath = opts.fileAbsPath!.replace(/\.\w+$/, '.md');
|
||||||
|
const md = fs.existsSync(mdPath) ? fs.readFileSync(mdPath, 'utf-8') : '';
|
||||||
|
|
||||||
|
const codePath = opts.fileAbsPath!.replace(/\.\w+$/, '.tsx');
|
||||||
|
const code = fs.existsSync(codePath) ? fs.readFileSync(codePath, 'utf-8') : '';
|
||||||
|
|
||||||
|
props.jsx = sylvanas.parseText(code);
|
||||||
|
|
||||||
|
if (md) {
|
||||||
|
// extract description & css style from md file
|
||||||
|
const description = md.match(
|
||||||
|
new RegExp(`(?:^|\\n)## ${locale}([^]+?)(\\n## [a-z]|\\n\`\`\`|\\n<style>|$)`),
|
||||||
|
)?.[1];
|
||||||
|
const style = md.match(/\n```css\n([^]+?)\n```|\n<style>\n([^]+?)\n<\/style>/)?.[1];
|
||||||
|
|
||||||
|
props.description ??= description?.trim();
|
||||||
|
props.style ??= style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolve = (path: string): string => require.resolve(path);
|
||||||
|
|
||||||
|
const RoutesPlugin = (api: IApi) => {
|
||||||
|
const ssrCssFileName = `ssr-${Date.now()}.css`;
|
||||||
|
|
||||||
|
api.registerTechStack(() => new AntdReactTechStack());
|
||||||
|
|
||||||
|
api.modifyRoutes(routes => {
|
||||||
|
// TODO: append extra routes, such as home, changelog, form-v3
|
||||||
|
|
||||||
|
const extraRoutesList: IRoute[] = [
|
||||||
|
{
|
||||||
|
id: 'changelog-cn',
|
||||||
|
path: 'changelog-cn',
|
||||||
|
absPath: '/changelog-cn',
|
||||||
|
parentId: 'DocLayout',
|
||||||
|
file: resolve('../../CHANGELOG.zh-CN.md'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'changelog',
|
||||||
|
path: 'changelog',
|
||||||
|
absPath: '/changelog',
|
||||||
|
parentId: 'DocLayout',
|
||||||
|
file: resolve('../../CHANGELOG.en-US.md'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
extraRoutesList.forEach(itemRoute => {
|
||||||
|
routes[itemRoute.path] = itemRoute;
|
||||||
|
});
|
||||||
|
|
||||||
|
return routes;
|
||||||
|
});
|
||||||
|
|
||||||
|
api.modifyExportHTMLFiles(files =>
|
||||||
|
files
|
||||||
|
// exclude dynamic route path, to avoid deploy failed by `:id` directory
|
||||||
|
.filter(f => !f.path.includes(':'))
|
||||||
|
// FIXME: workaround to make emotion support react 18 pipeableStream
|
||||||
|
// ref: https://github.com/emotion-js/emotion/issues/2800#issuecomment-1221296308
|
||||||
|
.map(file => {
|
||||||
|
let styles = '';
|
||||||
|
|
||||||
|
// extract all emotion style tags from body
|
||||||
|
file.content = file.content.replace(/<style data-emotion[\s\S\n]+?<\/style>/g, s => {
|
||||||
|
styles += s;
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// insert emotion style tags to head
|
||||||
|
file.content = file.content.replace('</head>', `${styles}</head>`);
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// add ssr css file to html
|
||||||
|
api.modifyConfig(memo => {
|
||||||
|
memo.styles ??= [];
|
||||||
|
memo.styles.push(`/${ssrCssFileName}`);
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate ssr css file
|
||||||
|
api.onBuildHtmlComplete(() => {
|
||||||
|
const styleText = extractStyle((global as any).styleCache);
|
||||||
|
const styleTextWithoutStyleTag = styleText
|
||||||
|
.replace(/<style\s[^>]*>/g, '')
|
||||||
|
.replace(/<\/style>/g, '');
|
||||||
|
|
||||||
|
fs.writeFileSync(`./_site/${ssrCssFileName}`, styleTextWithoutStyleTag, 'utf8');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoutesPlugin;
|
218
.dumi/theme/slots/Content/index.tsx
Normal file
218
.dumi/theme/slots/Content/index.tsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import React, { ReactNode, type FC, useMemo, useState, useLayoutEffect } from 'react';
|
||||||
|
import { useIntl, useRouteMeta } from 'dumi';
|
||||||
|
import Footer from 'dumi/theme/slots/Footer';
|
||||||
|
import { Col, Typography, Avatar, Tooltip, Affix, Anchor } from 'antd';
|
||||||
|
import EditButton from '../../common/EditButton';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import useLocation from '../../../hooks/useLocation';
|
||||||
|
import ContributorsList from '@qixian.cs/github-contributors-list';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import PrevAndNext from '../../common/PrevAndNext';
|
||||||
|
import DemoContext, { DemoContextProps } from '../DemoContext';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { antCls } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
contributorsList: css`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 120px !important;
|
||||||
|
|
||||||
|
a,
|
||||||
|
${antCls}-avatar + ${antCls}-avatar {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-inline-end: 8px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
toc: css`
|
||||||
|
${antCls}-anchor {
|
||||||
|
${antCls}-anchor-link-title {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-anchor-ink-ball {
|
||||||
|
left: 0;
|
||||||
|
width: ${token.lineWidthBold}px;
|
||||||
|
height: ${token.fontSizeSM * token.lineHeightSM}px;
|
||||||
|
margin-top: ${token.marginXXS}px;
|
||||||
|
background-color: ${token.colorPrimary};
|
||||||
|
border: none;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
tocWrapper: css`
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 0;
|
||||||
|
width: 160px;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 8px 8px 8px 4px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.toc-debug {
|
||||||
|
color: ${token['purple-6']};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${token['purple-5']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: auto;
|
||||||
|
padding-inline: 4px;
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* background of the scrollbar except button or resizer */
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scrollbar itself */
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: ${token.colorFill};
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set button(top and bottom of the scrollbar) */
|
||||||
|
::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnchorItem = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
children?: AnchorItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const Content: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
|
const meta = useRouteMeta();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
const [showDebug, setShowDebug] = useState(false);
|
||||||
|
const [debugDemos, setDebugDemos] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setShowDebug(process.env.NODE_ENV === 'development');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const contextValue = useMemo<DemoContextProps>(
|
||||||
|
() => ({ showDebug, setShowDebug, debugDemos, setDebugDemos }),
|
||||||
|
[showDebug, debugDemos],
|
||||||
|
);
|
||||||
|
|
||||||
|
const anchorItems = useMemo(() => {
|
||||||
|
return meta.toc.reduce<AnchorItem[]>((result, item) => {
|
||||||
|
if (item.depth === 2) {
|
||||||
|
result.push({ ...item });
|
||||||
|
} else if (item.depth === 3) {
|
||||||
|
const parent = result[result.length - 1];
|
||||||
|
if (parent) {
|
||||||
|
parent.children = parent.children || [];
|
||||||
|
parent.children.push({ ...item });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
}, [meta.toc]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DemoContext.Provider value={contextValue}>
|
||||||
|
<Col xxl={20} xl={19} lg={18} md={18} sm={24} xs={24}>
|
||||||
|
<Affix>
|
||||||
|
<div css={styles.tocWrapper}>
|
||||||
|
<div>
|
||||||
|
<Anchor css={styles.toc} affix={false} showInkInFixed>
|
||||||
|
{anchorItems.map(item => (
|
||||||
|
<Anchor.Link href={`#${item.id}`} title={item.title} key={item.id}>
|
||||||
|
{item.children
|
||||||
|
?.filter(child => showDebug || !debugDemos.includes(child.id))
|
||||||
|
.map(child => (
|
||||||
|
<Anchor.Link
|
||||||
|
href={`#${child.id}`}
|
||||||
|
title={
|
||||||
|
<span
|
||||||
|
className={classNames(debugDemos.includes(child.id) && 'toc-debug')}
|
||||||
|
>
|
||||||
|
{child.title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key={child.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Anchor.Link>
|
||||||
|
))}
|
||||||
|
</Anchor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
|
<div style={{ padding: '0 170px 32px 64px' }}>
|
||||||
|
<Typography.Title level={2}>
|
||||||
|
{meta.frontmatter.title}
|
||||||
|
{meta.frontmatter.subtitle && (
|
||||||
|
<span style={{ marginLeft: 12 }}>{meta.frontmatter.subtitle}</span>
|
||||||
|
)}
|
||||||
|
{!pathname.startsWith('/components/overview') && (
|
||||||
|
<EditButton
|
||||||
|
title={<FormattedMessage id="app.content.edit-page" />}
|
||||||
|
filename={meta.frontmatter.filename}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Typography.Title>
|
||||||
|
{children}
|
||||||
|
<ContributorsList
|
||||||
|
css={styles.contributorsList}
|
||||||
|
fileName={meta.frontmatter.filename ?? ''}
|
||||||
|
renderItem={(item, loading) =>
|
||||||
|
loading ? (
|
||||||
|
<Avatar style={{ opacity: 0.3 }} />
|
||||||
|
) : (
|
||||||
|
item && (
|
||||||
|
<Tooltip
|
||||||
|
title={`${formatMessage({ id: 'app.content.contributors' })}: ${item.username}`}
|
||||||
|
key={item.username}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`https://github.com/${item.username}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Avatar src={item.url}>{item.username}</Avatar>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
repo="ant-design"
|
||||||
|
owner="ant-design"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<PrevAndNext />
|
||||||
|
<Footer />
|
||||||
|
</Col>
|
||||||
|
</DemoContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Content;
|
14
.dumi/theme/slots/DemoContext.tsx
Normal file
14
.dumi/theme/slots/DemoContext.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export type DemoContextProps = {
|
||||||
|
showDebug?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DemoContext = createContext<{
|
||||||
|
showDebug?: boolean;
|
||||||
|
setShowDebug?: (showDebug: boolean) => void;
|
||||||
|
debugDemos?: string[];
|
||||||
|
setDebugDemos?: (visibleItems: string[]) => void;
|
||||||
|
}>({});
|
||||||
|
|
||||||
|
export default DemoContext;
|
@ -1,47 +1,89 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import RcFooter from 'rc-footer';
|
import RcFooter from 'rc-footer';
|
||||||
import { Link } from 'bisheng/router';
|
import { Link, FormattedMessage } from 'dumi';
|
||||||
import type { WrappedComponentProps } from 'react-intl';
|
import type { FooterColumn } from 'rc-footer/lib/column';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
||||||
import {
|
import {
|
||||||
AntDesignOutlined,
|
AntDesignOutlined,
|
||||||
MediumOutlined,
|
BgColorsOutlined,
|
||||||
TwitterOutlined,
|
BugOutlined,
|
||||||
ZhihuOutlined,
|
|
||||||
UsergroupAddOutlined,
|
|
||||||
GithubOutlined,
|
GithubOutlined,
|
||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
ProfileOutlined,
|
|
||||||
BugOutlined,
|
|
||||||
IssuesCloseOutlined,
|
IssuesCloseOutlined,
|
||||||
|
MediumOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
BgColorsOutlined,
|
TwitterOutlined,
|
||||||
|
UsergroupAddOutlined,
|
||||||
|
ZhihuOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import type { FooterColumn } from 'rc-footer/lib/column';
|
import useLocation from '../../../hooks/useLocation';
|
||||||
import { getLocalizedPathname } from '../utils';
|
import { css } from '@emotion/react';
|
||||||
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { TinyColor } from '@ctrl/tinycolor';
|
||||||
|
import getAlphaColor from 'antd/es/theme/util/getAlphaColor';
|
||||||
|
|
||||||
const Footer: React.FC<WrappedComponentProps & { location: any }> = props => {
|
const locales = {
|
||||||
const { intl, location } = props;
|
cn: {
|
||||||
const getColumns = useMemo<FooterColumn[]>(() => {
|
owner: '蚂蚁体验技术部 & 蚂蚁企业级应用设计',
|
||||||
const isZhCN = intl.locale === 'zh-CN';
|
},
|
||||||
const getLinkHash = (path: string, hash: { zhCN: string; enUS: string }) => {
|
en: {
|
||||||
const pathName = getLocalizedPathname(path, isZhCN, location.query, hash);
|
owner: 'Ant Financial Experience & Ant Enterprise-application Design',
|
||||||
const { pathname, query = {} } = pathName;
|
},
|
||||||
const pathnames = pathname.split('#');
|
};
|
||||||
if ('direction' in query) {
|
|
||||||
return `${pathnames[0]}?direction=rtl#${pathnames[1]}`;
|
|
||||||
}
|
|
||||||
return pathname;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLink = (path: string) => {
|
const useStyle = () => {
|
||||||
const pathName = getLocalizedPathname(path, isZhCN, location.query);
|
const { token } = useSiteToken();
|
||||||
const { pathname, query = {} } = pathName;
|
const background = new TinyColor(getAlphaColor('#f0f3fa', '#fff'))
|
||||||
if ('direction' in query) {
|
.onBackground(token.colorBgContainer)
|
||||||
return `${pathname}?direction=rtl}`;
|
.toHexString();
|
||||||
|
|
||||||
|
return {
|
||||||
|
holder: css`
|
||||||
|
background: ${background};
|
||||||
|
`,
|
||||||
|
|
||||||
|
footer: css`
|
||||||
|
background: ${background};
|
||||||
|
color: ${token.colorTextSecondary};
|
||||||
|
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
return pathname;
|
|
||||||
};
|
h2,
|
||||||
|
a {
|
||||||
|
color: ${token.colorText};
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-footer-column {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-footer-container {
|
||||||
|
max-width: 1208px;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: ${token.marginXXL}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rc-footer-bottom {
|
||||||
|
font-size: ${token.fontSize}px;
|
||||||
|
box-shadow: inset 0 106px 36px -116px rgba(0, 0, 0, 0.14);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const [locale, lang] = useLocale(locales);
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
const { getLink } = location;
|
||||||
|
|
||||||
|
const getColumns = React.useMemo<FooterColumn[]>(() => {
|
||||||
|
const isZhCN = lang === 'cn';
|
||||||
|
|
||||||
const col1 = {
|
const col1 = {
|
||||||
title: <FormattedMessage id="app.footer.resources" />,
|
title: <FormattedMessage id="app.footer.resources" />,
|
||||||
@ -167,9 +209,9 @@ const Footer: React.FC<WrappedComponentProps & { location: any }> = props => {
|
|||||||
col2.items.push({
|
col2.items.push({
|
||||||
icon: <UsergroupAddOutlined />,
|
icon: <UsergroupAddOutlined />,
|
||||||
title: <FormattedMessage id="app.footer.work_with_us" />,
|
title: <FormattedMessage id="app.footer.work_with_us" />,
|
||||||
url: getLinkHash('/docs/resources', {
|
url: getLink('/docs/resources', {
|
||||||
zhCN: '加入我们',
|
cn: '加入我们',
|
||||||
enUS: 'JoinUs',
|
en: 'JoinUs',
|
||||||
}),
|
}),
|
||||||
LinkComponent: Link,
|
LinkComponent: Link,
|
||||||
} as unknown as typeof col2['items'][number]);
|
} as unknown as typeof col2['items'][number]);
|
||||||
@ -300,31 +342,25 @@ const Footer: React.FC<WrappedComponentProps & { location: any }> = props => {
|
|||||||
{
|
{
|
||||||
icon: <BgColorsOutlined />,
|
icon: <BgColorsOutlined />,
|
||||||
title: <FormattedMessage id="app.footer.theme" />,
|
title: <FormattedMessage id="app.footer.theme" />,
|
||||||
url: getLinkHash('/components/config-provider/', {
|
url: getLink('/theme-editor'),
|
||||||
zhCN: 'components-config-provider-demo-theme',
|
|
||||||
enUS: 'components-config-provider-demo-theme',
|
|
||||||
}),
|
|
||||||
LinkComponent: Link,
|
LinkComponent: Link,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
return [col1, col2, col3, col4];
|
return [col1, col2, col3, col4];
|
||||||
}, [intl.locale, location.query]);
|
}, [lang, location.search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RcFooter
|
<RcFooter
|
||||||
columns={getColumns}
|
columns={getColumns}
|
||||||
|
css={style.footer}
|
||||||
bottom={
|
bottom={
|
||||||
<>
|
<>
|
||||||
Made with <span style={{ color: '#fff' }}>❤</span> by
|
Made with <span style={{ color: '#fff' }}>❤</span> by {locale.owner}
|
||||||
{/* eslint-disable-next-line react/jsx-curly-brace-presence */}{' '}
|
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://xtech.antfin.com">
|
|
||||||
<FormattedMessage id="app.footer.company" />
|
|
||||||
</a>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(Footer);
|
export default Footer;
|
77
.dumi/theme/slots/Header/Github.tsx
Normal file
77
.dumi/theme/slots/Header/Github.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
// @ts-ignore
|
||||||
|
import GitHubButton from 'react-github-button';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { antCls, colorPrimary } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
githubBtn: css`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: nowrap;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.gh-btn {
|
||||||
|
height: auto;
|
||||||
|
padding: 1px 4px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
.gh-ico {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-count {
|
||||||
|
height: auto;
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: normal;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${colorPrimary};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
.gh-count {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
responsiveMode: css`
|
||||||
|
.gh-count {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GithubProps {
|
||||||
|
responsive: null | 'narrow' | 'crowded';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({ responsive }: GithubProps) => {
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GitHubButton
|
||||||
|
css={[style.githubBtn, responsive && style.responsiveMode]}
|
||||||
|
type="stargazers"
|
||||||
|
namespace="ant-design"
|
||||||
|
repo="ant-design"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
74
.dumi/theme/slots/Header/Logo.tsx
Normal file
74
.dumi/theme/slots/Header/Logo.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Link, useLocation } from 'dumi';
|
||||||
|
import * as utils from '../../utils';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { antCls, headerHeight, colorTextHeading, fontFamily, mobileMaxWidth } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
logo: css`
|
||||||
|
height: ${headerHeight}px;
|
||||||
|
padding-left: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: ${colorTextHeading};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: PuHuiTi, ${fontFamily}, sans-serif;
|
||||||
|
line-height: ${headerHeight}px;
|
||||||
|
letter-spacing: -0.18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${colorTextHeading};
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
float: right;
|
||||||
|
padding-right: 40px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: ${mobileMaxWidth}px) {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface LogoProps {
|
||||||
|
isZhCN: boolean;
|
||||||
|
location: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Logo = ({ isZhCN }: LogoProps) => {
|
||||||
|
const { search } = useLocation();
|
||||||
|
const { logo } = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h1>
|
||||||
|
<Link to={utils.getLocalizedPathname('/', isZhCN, search)} css={logo}>
|
||||||
|
<img alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" />
|
||||||
|
Ant Design
|
||||||
|
</Link>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logo;
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { Dropdown, Button } from 'antd';
|
import { Dropdown, Menu, Button } from 'antd';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import type { SharedProps } from './interface';
|
import type { SharedProps } from './interface';
|
||||||
@ -11,12 +11,7 @@ export function getEcosystemGroup(): Exclude<MenuProps['items'], undefined> {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<a
|
<a href="https://charts.ant.design" target="_blank" rel="noopener noreferrer">
|
||||||
href="https://charts.ant.design"
|
|
||||||
className="header-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="app.header.menu.charts" />
|
<FormattedMessage id="app.header.menu.charts" />
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
@ -24,12 +19,7 @@ export function getEcosystemGroup(): Exclude<MenuProps['items'], undefined> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<a
|
<a href="http://pro.ant.design" target="_blank" rel="noopener noreferrer">
|
||||||
href="http://pro.ant.design"
|
|
||||||
className="header-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="app.header.menu.pro.v4" />
|
<FormattedMessage id="app.header.menu.pro.v4" />
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
@ -37,12 +27,7 @@ export function getEcosystemGroup(): Exclude<MenuProps['items'], undefined> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<a
|
<a href="http://procomponents.ant.design" target="_blank" rel="noopener noreferrer">
|
||||||
href="http://procomponents.ant.design"
|
|
||||||
className="header-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<FormattedMessage id="app.header.menu.pro.components" />
|
<FormattedMessage id="app.header.menu.pro.components" />
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
@ -50,12 +35,7 @@ export function getEcosystemGroup(): Exclude<MenuProps['items'], undefined> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<a
|
<a href="http://ng.ant.design" target="_blank" rel="noopener noreferrer">
|
||||||
href="http://ng.ant.design"
|
|
||||||
className="header-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Ant Design of Angular
|
Ant Design of Angular
|
||||||
<span style={smallStyle}>
|
<span style={smallStyle}>
|
||||||
(<FormattedMessage id="app.implementation.community" />)
|
(<FormattedMessage id="app.implementation.community" />)
|
||||||
@ -66,12 +46,7 @@ export function getEcosystemGroup(): Exclude<MenuProps['items'], undefined> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<a
|
<a href="http://antdv.com" target="_blank" rel="noopener noreferrer">
|
||||||
href="http://antdv.com"
|
|
||||||
className="header-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Ant Design of Vue
|
Ant Design of Vue
|
||||||
<span style={smallStyle}>
|
<span style={smallStyle}>
|
||||||
(<FormattedMessage id="app.implementation.community" />)
|
(<FormattedMessage id="app.implementation.community" />)
|
@ -1,21 +1,87 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { Link, useLocation, FormattedMessage } from 'dumi';
|
||||||
import { Link } from 'bisheng/router';
|
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { MenuOutlined } from '@ant-design/icons';
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
import { Menu } from 'antd';
|
import { Menu } from 'antd';
|
||||||
import { getEcosystemGroup } from './More';
|
import { getEcosystemGroup } from './More';
|
||||||
import * as utils from '../../utils';
|
import * as utils from '../../utils';
|
||||||
import type { SharedProps } from './interface';
|
import type { SharedProps } from './interface';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
import './Navigation.less';
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { antCls, iconCls, fontFamily, headerHeight, menuItemBorder, colorPrimary } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
nav: css`
|
||||||
|
height: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Avenir, ${fontFamily}, sans-serif;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&${antCls}-menu-horizontal {
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
& > ${antCls}-menu-item, & > ${antCls}-menu-submenu {
|
||||||
|
min-width: (40px + 12px * 2);
|
||||||
|
height: ${headerHeight}px;
|
||||||
|
padding-right: 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
line-height: ${headerHeight}px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 0;
|
||||||
|
right: 12px;
|
||||||
|
bottom: auto;
|
||||||
|
left: 12px;
|
||||||
|
border-width: ${menuItemBorder}px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ${antCls}-menu-submenu-title ${iconCls} {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ${antCls}-menu-item-selected {
|
||||||
|
a {
|
||||||
|
color: ${colorPrimary};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ${antCls}-menu-item, & > ${antCls}-menu-submenu {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
popoverMenuNav: css`
|
||||||
|
${antCls}-menu-item,
|
||||||
|
${antCls}-menu-submenu {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-menu-item-group-title {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-menu-item-group-list {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-menu-item,
|
||||||
|
a {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export interface NavigationProps extends SharedProps {
|
export interface NavigationProps extends SharedProps {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
pathname: string;
|
isClient: boolean;
|
||||||
responsive: null | 'narrow' | 'crowded';
|
responsive: null | 'narrow' | 'crowded';
|
||||||
location: { pathname: string; query: any };
|
|
||||||
directionText: string;
|
directionText: string;
|
||||||
showTechUIButton: boolean;
|
showTechUIButton: boolean;
|
||||||
onLangChange: () => void;
|
onLangChange: () => void;
|
||||||
@ -24,22 +90,29 @@ export interface NavigationProps extends SharedProps {
|
|||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
isZhCN,
|
isZhCN,
|
||||||
|
isClient,
|
||||||
isMobile,
|
isMobile,
|
||||||
pathname,
|
|
||||||
responsive,
|
responsive,
|
||||||
location,
|
|
||||||
directionText,
|
directionText,
|
||||||
showTechUIButton,
|
showTechUIButton,
|
||||||
onLangChange,
|
onLangChange,
|
||||||
onDirectionChange,
|
onDirectionChange,
|
||||||
}: NavigationProps) => {
|
}: NavigationProps) => {
|
||||||
|
const { pathname, search } = useLocation();
|
||||||
|
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
const menuMode = isMobile ? 'inline' : 'horizontal';
|
const menuMode = isMobile ? 'inline' : 'horizontal';
|
||||||
|
|
||||||
const module = pathname.split('/').slice(0, -1).join('/');
|
const module = pathname
|
||||||
|
.split('/')
|
||||||
|
.filter(path => path)
|
||||||
|
.slice(0, -1)
|
||||||
|
.join('/');
|
||||||
let activeMenuItem = module || 'home';
|
let activeMenuItem = module || 'home';
|
||||||
if (location.pathname === 'changelog' || location.pathname === 'changelog-cn') {
|
if (pathname.startsWith('/changelog')) {
|
||||||
activeMenuItem = 'docs/react';
|
activeMenuItem = 'docs/react';
|
||||||
} else if (location.pathname === 'docs/resources' || location.pathname === 'docs/resources-cn') {
|
} else if (pathname.startsWith('/docs/resources')) {
|
||||||
activeMenuItem = 'docs/resources';
|
activeMenuItem = 'docs/resources';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +159,7 @@ export default ({
|
|||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN, location.query)}>
|
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN, search)}>
|
||||||
<FormattedMessage id="app.header.menu.spec" />
|
<FormattedMessage id="app.header.menu.spec" />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
@ -94,7 +167,7 @@ export default ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN, location.query)}>
|
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN, search)}>
|
||||||
<FormattedMessage id="app.header.menu.documentation" />
|
<FormattedMessage id="app.header.menu.documentation" />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
@ -102,7 +175,7 @@ export default ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, location.query)}>
|
<Link to={utils.getLocalizedPathname('/components/overview/', isZhCN, search)}>
|
||||||
<FormattedMessage id="app.header.menu.components" />
|
<FormattedMessage id="app.header.menu.components" />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
@ -110,11 +183,11 @@ export default ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Link to={utils.getLocalizedPathname('/docs/resources', isZhCN, location.query)}>
|
<Link to={utils.getLocalizedPathname('/docs/resources', isZhCN, search)}>
|
||||||
<FormattedMessage id="app.header.menu.resource" />
|
<FormattedMessage id="app.header.menu.resource" />
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
key: 'docs/resources',
|
key: '/docs/resources',
|
||||||
},
|
},
|
||||||
showTechUIButton
|
showTechUIButton
|
||||||
? {
|
? {
|
||||||
@ -127,7 +200,7 @@ export default ({
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
isZhCN &&
|
isZhCN &&
|
||||||
typeof window !== 'undefined' &&
|
isClient &&
|
||||||
window.location.host !== 'ant-design.antgroup.com' &&
|
window.location.host !== 'ant-design.antgroup.com' &&
|
||||||
window.location.host !== 'ant-design.gitee.io'
|
window.location.host !== 'ant-design.gitee.io'
|
||||||
? {
|
? {
|
||||||
@ -169,7 +242,7 @@ export default ({
|
|||||||
className={classNames('menu-site')}
|
className={classNames('menu-site')}
|
||||||
mode={menuMode}
|
mode={menuMode}
|
||||||
selectedKeys={[activeMenuItem]}
|
selectedKeys={[activeMenuItem]}
|
||||||
id="nav"
|
css={style.nav}
|
||||||
disabledOverflow
|
disabledOverflow
|
||||||
items={items}
|
items={items}
|
||||||
/>
|
/>
|
@ -1,27 +1,155 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { Link } from 'bisheng/router';
|
import { Link, useNavigate } from 'dumi';
|
||||||
import classNames from 'classnames';
|
|
||||||
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||||
import { Input, Tooltip, Typography } from 'antd';
|
import { Input, Tooltip, Typography } from 'antd';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import type { DocSearchProps, DocSearchModalProps } from 'docsearch-react-fork';
|
import type { DocSearchModalProps, DocSearchProps } from 'docsearch-react-fork';
|
||||||
import { useDocSearchKeyboardEvents } from 'docsearch-react-fork';
|
import { useDocSearchKeyboardEvents } from 'docsearch-react-fork';
|
||||||
import '@docsearch/css';
|
import '@docsearch/css';
|
||||||
import type { SharedProps } from './interface';
|
import type { SharedProps } from './interface';
|
||||||
import type { IAlgoliaConfig } from './algolia-config';
|
import type { IAlgoliaConfig } from './algolia-config';
|
||||||
import { transformHitUrl } from './algolia-config';
|
import { transformHitUrl } from './algolia-config';
|
||||||
import WrapHelmet from '../../Components/Helmet';
|
import WrapHelmet from '../../common/Helmet';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
import './SearchBar.less';
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const searchIconColor = '#ced4d9';
|
||||||
|
|
||||||
|
const { antCls, iconCls } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchBox: css`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: auto !important;
|
||||||
|
align-items: center;
|
||||||
|
height: 22px;
|
||||||
|
margin: 0 auto 0 0 !important;
|
||||||
|
padding-left: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-left: 1px solid ${searchIconColor};
|
||||||
|
transition: width 0.5s;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
margin: 0 0 0 auto !important;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-left: 0;
|
||||||
|
border-right: 1px solid ${token.colorSplit};
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
${iconCls} {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
flex: none;
|
||||||
|
color: ${searchIconColor};
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-input-affix-wrapper {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-left: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #a3b1bf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
keybindings: css`
|
||||||
|
cursor: pointer;
|
||||||
|
`,
|
||||||
|
keybinding: css`
|
||||||
|
color: ${searchIconColor};
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
// better keybinding font display using \`Arial\`
|
||||||
|
font-family: Arial; /* stylelint-disable-line font-family-no-missing-generic-family-keyword */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
narrowMode: css`
|
||||||
|
flex: none !important;
|
||||||
|
width: 30px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
${iconCls} {
|
||||||
|
color: #a3b1bf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${iconCls} {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
max-width: none;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-left: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
padding-right: 11px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
focused: css`
|
||||||
|
width: 500px;
|
||||||
|
|
||||||
|
${iconCls} {
|
||||||
|
color: @search-icon-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export interface SearchBarProps extends SharedProps {
|
export interface SearchBarProps extends SharedProps {
|
||||||
onTriggerFocus?: (focus: boolean) => void;
|
onTriggerFocus?: (focus: boolean) => void;
|
||||||
responsive: null | 'narrow' | 'crowded';
|
responsive: null | 'narrow' | 'crowded';
|
||||||
algoliaConfig: IAlgoliaConfig;
|
algoliaConfig: IAlgoliaConfig;
|
||||||
router: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let SearchModal: React.FC<DocSearchModalProps> | null = null;
|
let SearchModal: React.FC<DocSearchModalProps> | null = null;
|
||||||
@ -46,10 +174,10 @@ function isAppleDevice() {
|
|||||||
*/
|
*/
|
||||||
const SearchBar = ({
|
const SearchBar = ({
|
||||||
isZhCN,
|
isZhCN,
|
||||||
|
isClient,
|
||||||
responsive,
|
responsive,
|
||||||
onTriggerFocus,
|
onTriggerFocus,
|
||||||
algoliaConfig,
|
algoliaConfig,
|
||||||
router,
|
|
||||||
}: SearchBarProps) => {
|
}: SearchBarProps) => {
|
||||||
const [isInputFocus, setInputFocus] = React.useState(false);
|
const [isInputFocus, setInputFocus] = React.useState(false);
|
||||||
const [inputSearch, setInputSearch] = React.useState('');
|
const [inputSearch, setInputSearch] = React.useState('');
|
||||||
@ -58,12 +186,16 @@ const SearchBar = ({
|
|||||||
const [searchModalQuery, setSearchModalQuery] = React.useState('');
|
const [searchModalQuery, setSearchModalQuery] = React.useState('');
|
||||||
const searchPlaceholder = isZhCN ? '在 ant.design 中搜索' : 'Search in ant.design';
|
const searchPlaceholder = isZhCN ? '在 ant.design 中搜索' : 'Search in ant.design';
|
||||||
const searchInputPlaceholder = isZhCN ? '搜索' : 'Search';
|
const searchInputPlaceholder = isZhCN ? '搜索' : 'Search';
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
const triggerSearchModalImport = React.useCallback(() => {
|
const triggerSearchModalImport = React.useCallback(() => {
|
||||||
if (SearchModal) {
|
if (SearchModal) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return import('docsearch-react-fork/modal').then(({ DocSearchModal }) => {
|
return import('docsearch-react-fork/modal').then(({ DocSearchModal }) => {
|
||||||
SearchModal = DocSearchModal;
|
SearchModal = DocSearchModal;
|
||||||
});
|
});
|
||||||
@ -117,17 +249,14 @@ const SearchBar = ({
|
|||||||
|
|
||||||
const navigator = React.useRef({
|
const navigator = React.useRef({
|
||||||
navigate({ itemUrl }: { itemUrl: string }) {
|
navigate({ itemUrl }: { itemUrl: string }) {
|
||||||
router.push(itemUrl);
|
navigate(itemUrl);
|
||||||
},
|
},
|
||||||
}).current;
|
}).current;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
css={[style.searchBox, responsive && style.narrowMode, isInputFocus && style.focused]}
|
||||||
id="search-box"
|
id="search-box"
|
||||||
className={classNames({
|
|
||||||
'narrow-mode': responsive,
|
|
||||||
focused: isInputFocus,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<WrapHelmet>
|
<WrapHelmet>
|
||||||
{/* pre-connect to algolia server */}
|
{/* pre-connect to algolia server */}
|
||||||
@ -153,10 +282,10 @@ const SearchBar = ({
|
|||||||
}}
|
}}
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
suffix={
|
suffix={
|
||||||
typeof window !== 'undefined' && (
|
isClient && (
|
||||||
<Tooltip placement="right" title={isZhCN ? '唤起搜索窗' : 'Search in doc modal'}>
|
<Tooltip placement="right" title={isZhCN ? '唤起搜索窗' : 'Search in doc modal'}>
|
||||||
<span
|
<span
|
||||||
className="keybindings"
|
css={style.keybindings}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// move userSearch to SearchModal
|
// move userSearch to SearchModal
|
||||||
setSearchModalQuery(inputSearch);
|
setSearchModalQuery(inputSearch);
|
||||||
@ -164,10 +293,10 @@ const SearchBar = ({
|
|||||||
handleModalOpen();
|
handleModalOpen();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text keyboard className="keybinding">
|
<Text keyboard css={style.keybinding}>
|
||||||
{isAppleDevice() ? CMD_KEY : CTRL_KEY}
|
{isAppleDevice() ? CMD_KEY : CTRL_KEY}
|
||||||
</Text>
|
</Text>
|
||||||
<Text keyboard className="keybinding">
|
<Text keyboard css={style.keybinding}>
|
||||||
K
|
K
|
||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
387
.dumi/theme/slots/Header/index.tsx
Normal file
387
.dumi/theme/slots/Header/index.tsx
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { FormattedMessage, useIntl } from 'dumi';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Button, Col, Modal, Popover, Row, Select } from 'antd';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import canUseDom from 'rc-util/lib/Dom/canUseDom';
|
||||||
|
import type { DirectionType } from 'antd/es/config-provider';
|
||||||
|
import * as utils from '../../utils';
|
||||||
|
import { getThemeConfig, ping } from '../../utils';
|
||||||
|
import packageJson from '../../../../package.json';
|
||||||
|
import Logo from './Logo';
|
||||||
|
import SearchBar from './SearchBar';
|
||||||
|
import More from './More';
|
||||||
|
import Navigation from './Navigation';
|
||||||
|
import Github from './Github';
|
||||||
|
import type { SiteContextProps } from '../SiteContext';
|
||||||
|
import SiteContext from '../SiteContext';
|
||||||
|
import { AlgoliaConfig } from './algolia-config';
|
||||||
|
import { useLocation, useNavigate } from 'dumi';
|
||||||
|
import { ClassNames, css } from '@emotion/react';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import useLocale from '../../../hooks/useLocale';
|
||||||
|
|
||||||
|
const RESPONSIVE_XS = 1120;
|
||||||
|
const RESPONSIVE_SM = 1200;
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const antdVersion: string = packageJson.version;
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: css`
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
max-width: 100%;
|
||||||
|
background: ${token.colorBgContainer};
|
||||||
|
box-shadow: ${token.boxShadow};
|
||||||
|
|
||||||
|
@media only screen and (max-width: ${token.mobileMaxWidth}px) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
menuRow: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: none;
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${token.antCls}-row-rtl & {
|
||||||
|
> * {
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
headerButton: css`
|
||||||
|
color: ${token.colorText};
|
||||||
|
border-color: ${token.colorBorder};
|
||||||
|
`,
|
||||||
|
popoverMenu: {
|
||||||
|
width: 300,
|
||||||
|
|
||||||
|
[`${token.antCls}-popover-inner-content`]: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface HeaderProps {
|
||||||
|
changeDirection: (direction: DirectionType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let docsearch: any;
|
||||||
|
const triggerDocSearchImport = () => {
|
||||||
|
if (docsearch) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return import('docsearch.js').then(ds => {
|
||||||
|
docsearch = ds.default;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function initDocSearch({ isZhCN, navigate }: { isZhCN: boolean; navigate: any }) {
|
||||||
|
if (!canUseDom()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerDocSearchImport().then(() => {
|
||||||
|
docsearch({
|
||||||
|
appId: AlgoliaConfig.appId,
|
||||||
|
apiKey: AlgoliaConfig.apiKey,
|
||||||
|
indexName: AlgoliaConfig.indexName,
|
||||||
|
inputSelector: '#search-box input',
|
||||||
|
algoliaOptions: AlgoliaConfig.getSearchParams(isZhCN),
|
||||||
|
transformData: AlgoliaConfig.transformData,
|
||||||
|
debug: AlgoliaConfig.debug,
|
||||||
|
// https://docsearch.algolia.com/docs/behavior#handleselected
|
||||||
|
handleSelected(input: any, _$1: unknown, suggestion: any) {
|
||||||
|
navigate(suggestion.url);
|
||||||
|
setTimeout(() => {
|
||||||
|
input.setVal('');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL';
|
||||||
|
|
||||||
|
function disableAntdMirrorModal() {
|
||||||
|
window.localStorage.setItem(SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL, 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldOpenAntdMirrorModal() {
|
||||||
|
return !window.localStorage.getItem(SHOULD_OPEN_ANT_DESIGN_MIRROR_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeaderState {
|
||||||
|
menuVisible: boolean;
|
||||||
|
windowWidth: number;
|
||||||
|
searching: boolean;
|
||||||
|
showTechUIButton: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header: React.FC<HeaderProps> = props => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { changeDirection } = props;
|
||||||
|
const [, lang] = useLocale();
|
||||||
|
const [isClient, setIsClient] = React.useState(false);
|
||||||
|
|
||||||
|
const themeConfig = getThemeConfig();
|
||||||
|
const [headerState, setHeaderState] = useState<HeaderState>({
|
||||||
|
menuVisible: false,
|
||||||
|
windowWidth: 1400,
|
||||||
|
searching: false,
|
||||||
|
showTechUIButton: false,
|
||||||
|
});
|
||||||
|
const { direction, isMobile } = useContext<SiteContextProps>(SiteContext);
|
||||||
|
const pingTimer = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const location = useLocation();
|
||||||
|
const { pathname, search } = location;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
const handleHideMenu = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: false }));
|
||||||
|
}, []);
|
||||||
|
const onWindowResize = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, windowWidth: window.innerWidth }));
|
||||||
|
}, []);
|
||||||
|
const onTriggerSearching = useCallback((searching: boolean) => {
|
||||||
|
setHeaderState(prev => ({ ...prev, searching }));
|
||||||
|
}, []);
|
||||||
|
const handleShowMenu = useCallback(() => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: true }));
|
||||||
|
}, []);
|
||||||
|
const onMenuVisibleChange = useCallback((visible: boolean) => {
|
||||||
|
setHeaderState(prev => ({ ...prev, menuVisible: visible }));
|
||||||
|
}, []);
|
||||||
|
const onDirectionChange = useCallback(() => {
|
||||||
|
changeDirection?.(direction !== 'rtl' ? 'rtl' : 'ltr');
|
||||||
|
}, [direction]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleHideMenu();
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsClient(typeof window !== 'undefined');
|
||||||
|
initDocSearch({ isZhCN: lang === 'cn', navigate });
|
||||||
|
onWindowResize();
|
||||||
|
window.addEventListener('resize', onWindowResize);
|
||||||
|
pingTimer.current = ping(status => {
|
||||||
|
if (status !== 'timeout' && status !== 'error') {
|
||||||
|
setHeaderState(prev => ({ ...prev, showTechUIButton: true }));
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV === 'production' &&
|
||||||
|
window.location.host !== 'ant-design.antgroup.com' &&
|
||||||
|
shouldOpenAntdMirrorModal()
|
||||||
|
) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '提示',
|
||||||
|
content: '内网用户推荐访问国内镜像以获得极速体验~',
|
||||||
|
okText: '🚀 立刻前往',
|
||||||
|
cancelText: '不再弹出',
|
||||||
|
closable: true,
|
||||||
|
onOk() {
|
||||||
|
window.open('https://ant-design.antgroup.com', '_self');
|
||||||
|
disableAntdMirrorModal();
|
||||||
|
},
|
||||||
|
onCancel() {
|
||||||
|
disableAntdMirrorModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', onWindowResize);
|
||||||
|
if (pingTimer.current) {
|
||||||
|
clearTimeout(pingTimer.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
const handleVersionChange = useCallback((url: string) => {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const currentPathname = window.location.pathname;
|
||||||
|
if (/overview/.test(currentPathname) && /0?[1-39][0-3]?x/.test(url)) {
|
||||||
|
window.location.href = currentUrl
|
||||||
|
.replace(window.location.origin, url)
|
||||||
|
.replace(/\/components\/overview/, `/docs${/0(9|10)x/.test(url) ? '' : '/react'}/introduce`)
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = currentUrl.replace(window.location.origin, url);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onLangChange = useCallback(() => {
|
||||||
|
const currentProtocol = `${window.location.protocol}//`;
|
||||||
|
const currentHref = window.location.href.slice(currentProtocol.length);
|
||||||
|
|
||||||
|
if (utils.isLocalStorageNameSupported()) {
|
||||||
|
localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
|
||||||
|
}
|
||||||
|
window.location.href =
|
||||||
|
currentProtocol +
|
||||||
|
currentHref.replace(
|
||||||
|
window.location.pathname,
|
||||||
|
utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), search).pathname,
|
||||||
|
);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const nextDirectionText = useMemo<string>(
|
||||||
|
() => (direction !== 'rtl' ? 'RTL' : 'LTR'),
|
||||||
|
[direction],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDropdownStyle = useMemo<React.CSSProperties>(
|
||||||
|
() => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'right' } : {}),
|
||||||
|
[direction],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { menuVisible, windowWidth, searching, showTechUIButton } = headerState;
|
||||||
|
const docVersions: Record<string, string> = {
|
||||||
|
[antdVersion]: antdVersion,
|
||||||
|
...themeConfig?.docVersions,
|
||||||
|
};
|
||||||
|
const versionOptions = Object.keys(docVersions).map(version => (
|
||||||
|
<Option value={docVersions[version]} key={version}>
|
||||||
|
{version}
|
||||||
|
</Option>
|
||||||
|
));
|
||||||
|
|
||||||
|
const isHome = ['', 'index', 'index-cn'].includes(pathname);
|
||||||
|
|
||||||
|
const isZhCN = lang === 'cn';
|
||||||
|
const isRTL = direction === 'rtl';
|
||||||
|
let responsive: null | 'narrow' | 'crowded' = null;
|
||||||
|
if (windowWidth < RESPONSIVE_XS) {
|
||||||
|
responsive = 'crowded';
|
||||||
|
} else if (windowWidth < RESPONSIVE_SM) {
|
||||||
|
responsive = 'narrow';
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerClassName = classNames({
|
||||||
|
clearfix: true,
|
||||||
|
'home-header': isHome,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sharedProps = {
|
||||||
|
isZhCN,
|
||||||
|
isRTL,
|
||||||
|
isClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigationNode = (
|
||||||
|
<Navigation
|
||||||
|
key="nav"
|
||||||
|
{...sharedProps}
|
||||||
|
responsive={responsive}
|
||||||
|
isMobile={isMobile}
|
||||||
|
showTechUIButton={showTechUIButton}
|
||||||
|
directionText={nextDirectionText}
|
||||||
|
onLangChange={onLangChange}
|
||||||
|
onDirectionChange={onDirectionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
let menu: (React.ReactElement | null)[] = [
|
||||||
|
navigationNode,
|
||||||
|
<Select
|
||||||
|
key="version"
|
||||||
|
className="version"
|
||||||
|
size="small"
|
||||||
|
defaultValue={antdVersion}
|
||||||
|
onChange={handleVersionChange}
|
||||||
|
dropdownStyle={getDropdownStyle}
|
||||||
|
getPopupContainer={trigger => trigger.parentNode}
|
||||||
|
>
|
||||||
|
{versionOptions}
|
||||||
|
</Select>,
|
||||||
|
<Button size="small" onClick={onLangChange} css={style.headerButton} key="lang-button">
|
||||||
|
<FormattedMessage id="app.header.lang" />
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={onDirectionChange}
|
||||||
|
css={style.headerButton}
|
||||||
|
key="direction-button"
|
||||||
|
>
|
||||||
|
{nextDirectionText}
|
||||||
|
</Button>,
|
||||||
|
<More key="more" {...sharedProps} />,
|
||||||
|
<Github key="github" responsive={responsive} />,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (windowWidth < RESPONSIVE_XS) {
|
||||||
|
menu = searching ? [] : [navigationNode];
|
||||||
|
} else if (windowWidth < RESPONSIVE_SM) {
|
||||||
|
menu = searching ? [] : menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colProps = isHome
|
||||||
|
? [{ flex: 'none' }, { flex: 'auto' }]
|
||||||
|
: [
|
||||||
|
{ xxl: 4, xl: 5, lg: 6, md: 6, sm: 24, xs: 24 },
|
||||||
|
{ xxl: 20, xl: 19, lg: 18, md: 18, sm: 0, xs: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header css={style.header} className={headerClassName}>
|
||||||
|
{isMobile && (
|
||||||
|
<ClassNames>
|
||||||
|
{({ css }) => (
|
||||||
|
<Popover
|
||||||
|
overlayClassName={css(style.popoverMenu)}
|
||||||
|
placement="bottomRight"
|
||||||
|
content={menu}
|
||||||
|
trigger="click"
|
||||||
|
open={menuVisible}
|
||||||
|
arrowPointAtCenter
|
||||||
|
onOpenChange={onMenuVisibleChange}
|
||||||
|
>
|
||||||
|
<MenuOutlined className="nav-phone-icon" onClick={handleShowMenu} />
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</ClassNames>
|
||||||
|
)}
|
||||||
|
<Row style={{ flexFlow: 'nowrap', height: 64 }}>
|
||||||
|
<Col {...colProps[0]}>
|
||||||
|
<Logo {...sharedProps} location={location} />
|
||||||
|
</Col>
|
||||||
|
<Col {...colProps[1]} css={style.menuRow}>
|
||||||
|
<SearchBar
|
||||||
|
key="search"
|
||||||
|
{...sharedProps}
|
||||||
|
algoliaConfig={AlgoliaConfig}
|
||||||
|
responsive={responsive}
|
||||||
|
onTriggerFocus={onTriggerSearching}
|
||||||
|
/>
|
||||||
|
{!isMobile && menu}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
@ -1,4 +1,5 @@
|
|||||||
export interface SharedProps {
|
export interface SharedProps {
|
||||||
isZhCN: boolean;
|
isZhCN: boolean;
|
||||||
isRTL: boolean;
|
isRTL: boolean;
|
||||||
|
isClient: boolean;
|
||||||
}
|
}
|
157
.dumi/theme/slots/Sidebar/index.tsx
Normal file
157
.dumi/theme/slots/Sidebar/index.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React, { type FC, useContext } from 'react';
|
||||||
|
import { useSidebarData } from 'dumi';
|
||||||
|
import { Affix, Col, Menu } from 'antd';
|
||||||
|
import MobileMenu from 'rc-drawer';
|
||||||
|
import SiteContext from '../SiteContext';
|
||||||
|
import useMenu from '../../../hooks/useMenu';
|
||||||
|
import useSiteToken from '../../../hooks/useSiteToken';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
|
const { antCls, fontFamily, colorSplit } = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
asideContainer: css`
|
||||||
|
min-height: 100%;
|
||||||
|
padding-bottom: 48px;
|
||||||
|
font-family: Avenir, ${fontFamily}, sans-serif;
|
||||||
|
|
||||||
|
&${antCls}-menu-inline {
|
||||||
|
${antCls}-menu-submenu-title h4,
|
||||||
|
> ${antCls}-menu-item,
|
||||||
|
${antCls}-menu-item a {
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
> ${antCls}-menu-item-group > ${antCls}-menu-item-group-title {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: relative;
|
||||||
|
top: 12px;
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
height: 1px;
|
||||||
|
background: ${colorSplit};
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> ${antCls}-menu-item,
|
||||||
|
> ${antCls}-menu-submenu
|
||||||
|
> ${antCls}-menu-submenu-title,
|
||||||
|
> ${antCls}-menu-item-group
|
||||||
|
> ${antCls}-menu-item-group-title,
|
||||||
|
> ${antCls}-menu-item-group
|
||||||
|
> ${antCls}-menu-item-group-list
|
||||||
|
> ${antCls}-menu-item,
|
||||||
|
&${antCls}-menu-inline
|
||||||
|
> ${antCls}-menu-item-group
|
||||||
|
> ${antCls}-menu-item-group-list
|
||||||
|
> ${antCls}-menu-item {
|
||||||
|
padding-left: 40px !important;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
padding-right: 40px !important;
|
||||||
|
padding-left: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nest Category > Type > Article
|
||||||
|
&${antCls}-menu-inline {
|
||||||
|
${antCls}-menu-item-group-title {
|
||||||
|
padding-left: 60px;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
padding-right: 60px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-menu-item-group-list > ${antCls}-menu-item {
|
||||||
|
padding-left: 80px !important;
|
||||||
|
|
||||||
|
${antCls}-row-rtl & {
|
||||||
|
padding-right: 80px !important;
|
||||||
|
padding-left: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${antCls}-menu-item-group:first-child {
|
||||||
|
${antCls}-menu-item-group-title {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a[disabled] {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chinese {
|
||||||
|
margin-left: 6px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.67;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
mainMenu: css`
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.main-menu-inner {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .main-menu-inner {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div,
|
||||||
|
> div > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Sidebar: FC = () => {
|
||||||
|
const sidebarData = useSidebarData();
|
||||||
|
const { isMobile } = useContext(SiteContext);
|
||||||
|
const styles = useStyle();
|
||||||
|
|
||||||
|
const [menuItems, selectedKey] = useMenu();
|
||||||
|
|
||||||
|
const menuChild = (
|
||||||
|
<Menu
|
||||||
|
items={menuItems}
|
||||||
|
inlineIndent={30}
|
||||||
|
css={styles.asideContainer}
|
||||||
|
mode="inline"
|
||||||
|
selectedKeys={[selectedKey]}
|
||||||
|
defaultOpenKeys={sidebarData?.map(({ title }) => title).filter(item => item) as string[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return isMobile ? (
|
||||||
|
<MobileMenu key="Mobile-menu">{menuChild}</MobileMenu>
|
||||||
|
) : (
|
||||||
|
<Col xxl={4} xl={5} lg={6} md={6} sm={24} xs={24} css={styles.mainMenu}>
|
||||||
|
<Affix>
|
||||||
|
<section style={{ width: '100%' }} className="main-menu-inner">
|
||||||
|
{menuChild}
|
||||||
|
</section>
|
||||||
|
</Affix>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
@ -4,9 +4,6 @@ import type { DirectionType } from 'antd/es/config-provider';
|
|||||||
export interface SiteContextProps {
|
export interface SiteContextProps {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
theme?: string;
|
|
||||||
setTheme?: (theme: string, persist?: boolean) => void;
|
|
||||||
setIframeTheme?: (iframeNode: HTMLIFrameElement, theme: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SiteContext = React.createContext<SiteContextProps>({
|
const SiteContext = React.createContext<SiteContextProps>({
|
11
.dumi/theme/slots/ThemeContext.tsx
Normal file
11
.dumi/theme/slots/ThemeContext.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { ThemeConfig } from 'antd/es/config-provider/context';
|
||||||
|
|
||||||
|
export type ThemeContextProps = {
|
||||||
|
theme: ThemeConfig;
|
||||||
|
setTheme: (theme: ThemeConfig) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextProps>({ theme: {}, setTheme: () => {} });
|
||||||
|
|
||||||
|
export default ThemeContext;
|
@ -1,3 +1,3 @@
|
|||||||
import 'react-github-button/assets/style.css';
|
import 'react-github-button/assets/style.css';
|
||||||
import 'docsearch.js/dist/cdn/docsearch.css';
|
import 'docsearch.js/dist/cdn/docsearch.css';
|
||||||
import './index.less';
|
import 'rc-footer/assets/index.css';
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
categoryOrder: {
|
categoryOrder: {
|
||||||
'Ant Design': 0,
|
'Ant Design': 0,
|
||||||
全局样式: 1,
|
全局样式: 1,
|
||||||
@ -43,6 +43,7 @@ module.exports = {
|
|||||||
'Template Document': 3,
|
'Template Document': 3,
|
||||||
},
|
},
|
||||||
docVersions: {
|
docVersions: {
|
||||||
|
'4.x': 'https://ant.design',
|
||||||
'3.x': 'http://3x.ant.design',
|
'3.x': 'http://3x.ant.design',
|
||||||
'2.x': 'http://2x.ant.design',
|
'2.x': 'http://2x.ant.design',
|
||||||
'1.x': 'http://1x.ant.design',
|
'1.x': 'http://1x.ant.design',
|
@ -1,12 +1,16 @@
|
|||||||
import flatten from 'lodash/flatten';
|
import flatten from 'lodash/flatten';
|
||||||
import flattenDeep from 'lodash/flattenDeep';
|
import flattenDeep from 'lodash/flattenDeep';
|
||||||
import themeConfig from '../../themeConfig';
|
import themeConfig from './themeConfig';
|
||||||
|
|
||||||
interface Meta {
|
interface Meta {
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
category?: any;
|
category?: any;
|
||||||
type?: any;
|
type?: any;
|
||||||
title?: any;
|
title?: any;
|
||||||
|
subtitle?: string;
|
||||||
|
tag?: string;
|
||||||
|
path?: string;
|
||||||
|
cover?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
children?: Meta[];
|
children?: Meta[];
|
||||||
}
|
}
|
||||||
@ -118,7 +122,7 @@ export function isZhCN(pathname: string) {
|
|||||||
export function getLocalizedPathname(
|
export function getLocalizedPathname(
|
||||||
path: string,
|
path: string,
|
||||||
zhCN?: boolean,
|
zhCN?: boolean,
|
||||||
query?: { [key: string]: any },
|
search?: string,
|
||||||
hash?: {
|
hash?: {
|
||||||
zhCN?: string;
|
zhCN?: string;
|
||||||
enUS?: string;
|
enUS?: string;
|
||||||
@ -142,7 +146,7 @@ export function getLocalizedPathname(
|
|||||||
fullPath += `#${localHash}`;
|
fullPath += `#${localHash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { pathname: fullPath, query };
|
return { pathname: fullPath, search };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ping(callback: (status: string) => void) {
|
export function ping(callback: (status: string) => void) {
|
15
.dumi/tsconfig.json
Normal file
15
.dumi/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "../.dumi/tmp/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "@emotion/react",
|
||||||
|
"baseUrl": "../",
|
||||||
|
"paths": {
|
||||||
|
"@@/*": [".dumi/tmp/*"],
|
||||||
|
"antd": ["components/index.tsx"],
|
||||||
|
"antd/es/*": ["components/*"],
|
||||||
|
"dumi/theme/*": [".dumi/theme/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./**/*", "../site/theme/template/Content"]
|
||||||
|
}
|
98
.dumirc.ts
Normal file
98
.dumirc.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { defineConfig } from 'dumi';
|
||||||
|
import path from 'path';
|
||||||
|
import rehypeAntd from './.dumi/rehypeAntd';
|
||||||
|
import { version } from './package.json';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
conventionRoutes: {
|
||||||
|
// to avoid generate routes for .dumi/pages/index/components/xx
|
||||||
|
exclude: [new RegExp('index/components/')],
|
||||||
|
},
|
||||||
|
ssr: process.env.NODE_ENV === 'production' ? {} : false,
|
||||||
|
hash: true,
|
||||||
|
outputPath: '_site',
|
||||||
|
favicons: ['https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png'],
|
||||||
|
resolve: {
|
||||||
|
docDirs: [{ type: 'doc', dir: 'docs' }],
|
||||||
|
atomDirs: [{ type: 'component', dir: 'components' }],
|
||||||
|
codeBlockMode: 'passive',
|
||||||
|
},
|
||||||
|
locales: [
|
||||||
|
{ id: 'en-US', name: 'English', suffix: '' },
|
||||||
|
{ id: 'zh-CN', name: '中文', suffix: '-cn' },
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
antdReproduceVersion: version,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
'@root-entry-name': 'default',
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
'antd/lib': path.join(__dirname, 'components'),
|
||||||
|
'antd/es': path.join(__dirname, 'components'),
|
||||||
|
'antd/locale': path.join(__dirname, 'components/locale'),
|
||||||
|
// Change antd from `index.js` to `.dumi/theme/antd.js` to remove deps of root style
|
||||||
|
antd: require.resolve('./.dumi/theme/antd.js'),
|
||||||
|
},
|
||||||
|
extraRehypePlugins: [rehypeAntd],
|
||||||
|
extraBabelPresets: ['@emotion/babel-preset-css-prop'],
|
||||||
|
mfsu: false,
|
||||||
|
headScripts: [
|
||||||
|
`
|
||||||
|
(function () {
|
||||||
|
function isLocalStorageNameSupported() {
|
||||||
|
var testKey = 'test';
|
||||||
|
var storage = window.localStorage;
|
||||||
|
try {
|
||||||
|
storage.setItem(testKey, '1');
|
||||||
|
storage.removeItem(testKey);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 优先级提高到所有静态资源的前面,语言不对,加载其他静态资源没意义
|
||||||
|
var pathname = location.pathname;
|
||||||
|
|
||||||
|
function isZhCN(pathname) {
|
||||||
|
return /-cn\\/?$/.test(pathname);
|
||||||
|
}
|
||||||
|
function getLocalizedPathname(path, zhCN) {
|
||||||
|
var pathname = path.indexOf('/') === 0 ? path : '/' + path;
|
||||||
|
if (!zhCN) {
|
||||||
|
// to enUS
|
||||||
|
return /\\/?index(-cn)?/.test(pathname) ? '/' : pathname.replace('-cn', '');
|
||||||
|
} else if (pathname === '/') {
|
||||||
|
return '/index-cn';
|
||||||
|
} else if (pathname.indexOf('/') === pathname.length - 1) {
|
||||||
|
return pathname.replace(/\\/$/, '-cn/');
|
||||||
|
}
|
||||||
|
return pathname + '-cn';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的 URL, \`?locale=...\`
|
||||||
|
var queryString = location.search;
|
||||||
|
if (queryString) {
|
||||||
|
var isZhCNConfig = queryString.indexOf('zh-CN') > -1;
|
||||||
|
if (isZhCNConfig && !isZhCN(pathname)) {
|
||||||
|
location.pathname = getLocalizedPathname(pathname, isZhCNConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首页无视链接里面的语言设置 https://github.com/ant-design/ant-design/issues/4552
|
||||||
|
if (isLocalStorageNameSupported() && (pathname === '/' || pathname === '/index-cn')) {
|
||||||
|
var lang =
|
||||||
|
(window.localStorage && localStorage.getItem('locale')) ||
|
||||||
|
((navigator.language || navigator.browserLanguage).toLowerCase() === 'zh-cn'
|
||||||
|
? 'zh-CN'
|
||||||
|
: 'en-US');
|
||||||
|
// safari is 'zh-cn', while other browser is 'zh-CN';
|
||||||
|
if ((lang === 'zh-CN') !== isZhCN(pathname)) {
|
||||||
|
location.pathname = getLocalizedPathname(pathname, lang === 'zh-CN');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.documentElement.className += isZhCN(pathname) ? 'zh-cn' : 'en-us';
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
});
|
@ -2,23 +2,9 @@ components/**/*.js
|
|||||||
components/**/*.jsx
|
components/**/*.jsx
|
||||||
components/version/token.tsx
|
components/version/token.tsx
|
||||||
!components/*/__tests__/**/*.js
|
!components/*/__tests__/**/*.js
|
||||||
!components/*/demo/*
|
!components/*/demo/*.md
|
||||||
!.*.js
|
!.*.js
|
||||||
~*
|
~*
|
||||||
# Docs templates
|
|
||||||
site/theme/template/Color/ColorPicker.jsx
|
|
||||||
site/theme/template/IconDisplay/*.js
|
|
||||||
site/theme/template/IconDisplay/*.jsx
|
|
||||||
site/theme/template/IconDisplay/fields.js
|
|
||||||
site/theme/template/Home/**/*.jsx
|
|
||||||
site/theme/template/utils.jsx
|
|
||||||
site/theme/template/Layout/**/*.jsx
|
|
||||||
site/theme/template/Layout/Footer.jsx
|
|
||||||
site/theme/template/Content/Article.jsx
|
|
||||||
site/theme/template/Content/EditButton.jsx
|
|
||||||
site/theme/template/Resources/*.jsx
|
|
||||||
site/theme/template/Resources/**/*.jsx
|
|
||||||
site/theme/template/NotFound.jsx
|
|
||||||
typings
|
typings
|
||||||
es/**/*
|
es/**/*
|
||||||
lib/**/*
|
lib/**/*
|
||||||
|
12
.eslintrc.js
12
.eslintrc.js
@ -86,6 +86,18 @@ module.exports = {
|
|||||||
'react/no-unstable-nested-components': 0,
|
'react/no-unstable-nested-components': 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['components/**/demo/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'import/no-extraneous-dependencies': 0,
|
||||||
|
'no-console': 0,
|
||||||
|
'compat/compat': 0,
|
||||||
|
'react/no-unstable-nested-components': 0,
|
||||||
|
'jsx-a11y/control-has-associated-label': 0,
|
||||||
|
'class-methods-use-this': 0,
|
||||||
|
'react/no-access-state-in-setstate': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'react/jsx-one-expression-per-line': 0,
|
'react/jsx-one-expression-per-line': 0,
|
||||||
|
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@ -67,28 +67,6 @@ jobs:
|
|||||||
run: npm run lint
|
run: npm run lint
|
||||||
needs: setup
|
needs: setup
|
||||||
|
|
||||||
tsx-demo:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: restore cache from package-lock.json
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: package-temp-dir
|
|
||||||
key: lock-${{ github.sha }}
|
|
||||||
|
|
||||||
- name: restore cache from node_modules
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: node_modules
|
|
||||||
key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
|
|
||||||
|
|
||||||
- name: tsx-demo
|
|
||||||
run: npm run check-ts-demo
|
|
||||||
needs: setup
|
|
||||||
|
|
||||||
check_metadata:
|
check_metadata:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -62,6 +62,9 @@ site/theme/template/NotFound.jsx
|
|||||||
scripts/previewEditor/index.html
|
scripts/previewEditor/index.html
|
||||||
components/version/version.tsx
|
components/version/version.tsx
|
||||||
components/version/token.tsx
|
components/version/token.tsx
|
||||||
|
.dumi/tmp
|
||||||
|
.dumi/tmp-test
|
||||||
|
.dumi/tmp-production
|
||||||
|
|
||||||
# Image snapshot diff
|
# Image snapshot diff
|
||||||
__diff_output__/
|
__diff_output__/
|
||||||
|
25
.jest.js
25
.jest.js
@ -1,7 +1,27 @@
|
|||||||
|
const compileModules = [
|
||||||
|
'array-move',
|
||||||
|
'react-dnd',
|
||||||
|
'react-dnd-html5-backend',
|
||||||
|
'@react-dnd',
|
||||||
|
'dnd-core',
|
||||||
|
'tween-one',
|
||||||
|
'@babel',
|
||||||
|
'@ant-design',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ignoreList = [];
|
||||||
|
|
||||||
|
// cnpm use `_` as prefix
|
||||||
|
['', '_'].forEach(prefix => {
|
||||||
|
compileModules.forEach(module => {
|
||||||
|
ignoreList.push(`${prefix}${module}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const transformIgnorePatterns = [
|
const transformIgnorePatterns = [
|
||||||
// Ignore modules without es dir.
|
// Ignore modules without es dir.
|
||||||
// Update: @babel/runtime should also be transformed
|
// Update: @babel/runtime should also be transformed
|
||||||
'/node_modules/(?!array-move|react-dnd|react-dnd-html5-backend|@react-dnd|dnd-core|tween-one|@babel|@ant-design)[^/]+?/(?!(es)/)',
|
`/node_modules/(?!${ignoreList.join('|')})[^/]+?/(?!(es)/)`,
|
||||||
];
|
];
|
||||||
|
|
||||||
function getTestRegex(libDir) {
|
function getTestRegex(libDir) {
|
||||||
@ -20,6 +40,8 @@ module.exports = {
|
|||||||
modulePathIgnorePatterns: ['/_site/'],
|
modulePathIgnorePatterns: ['/_site/'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'/\\.(css|less)$/': 'identity-obj-proxy',
|
'/\\.(css|less)$/': 'identity-obj-proxy',
|
||||||
|
'^antd$': '<rootDir>/components/index',
|
||||||
|
'^antd/es/(.*)$': '<rootDir>/components/$1',
|
||||||
},
|
},
|
||||||
testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node', 'image.test.js', 'image.test.ts'],
|
testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node', 'image.test.js', 'image.test.ts'],
|
||||||
transform: {
|
transform: {
|
||||||
@ -38,6 +60,7 @@ module.exports = {
|
|||||||
'!components/**/*/interface.{ts,tsx}',
|
'!components/**/*/interface.{ts,tsx}',
|
||||||
'!components/*/__tests__/image.test.{ts,tsx}',
|
'!components/*/__tests__/image.test.{ts,tsx}',
|
||||||
'!components/__tests__/node.test.tsx',
|
'!components/__tests__/node.test.tsx',
|
||||||
|
'!components/*/demo/*.tsx',
|
||||||
],
|
],
|
||||||
transformIgnorePatterns,
|
transformIgnorePatterns,
|
||||||
globals: {
|
globals: {
|
||||||
|
@ -4,8 +4,8 @@ CODEOWNERS
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
Dockerfile.ui-test
|
Dockerfile.ui-test
|
||||||
package.json
|
package.json
|
||||||
.umi
|
.dumi/tmp
|
||||||
.umi-production
|
.dumi/tmp-production
|
||||||
AUTHORS.txt
|
AUTHORS.txt
|
||||||
lib/
|
lib/
|
||||||
es/
|
es/
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"parser": "json"
|
"parser": "json"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "components/*/index.*.md",
|
||||||
|
"options": {
|
||||||
|
"proseWrap": "preserve"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
1
.surgeignore
Normal file
1
.surgeignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
!.dumi*
|
@ -26,7 +26,7 @@ describe('node', () => {
|
|||||||
|
|
||||||
// Test for ssr
|
// Test for ssr
|
||||||
describe(componentName, () => {
|
describe(componentName, () => {
|
||||||
const demoList = glob.sync(`./components/${componentName}/demo/*.md`);
|
const demoList = glob.sync(`./components/${componentName}/demo/*.tsx`);
|
||||||
|
|
||||||
// Use mock to get config
|
// Use mock to get config
|
||||||
require(`../../${componentTestFile}`); // eslint-disable-line global-require, import/no-dynamic-require
|
require(`../../${componentTestFile}`); // eslint-disable-line global-require, import/no-dynamic-require
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/basic.md extend context correctly 1`] = `
|
exports[`renders ./components/affix/demo/basic.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@ -34,7 +34,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/debug.md extend context correctly 1`] = `
|
exports[`renders ./components/affix/demo/debug.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
style="height:10000px"
|
style="height:10000px"
|
||||||
>
|
>
|
||||||
@ -65,7 +65,7 @@ exports[`renders ./components/affix/demo/debug.md extend context correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/on-change.md extend context correctly 1`] = `
|
exports[`renders ./components/affix/demo/on-change.tsx extend context correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class=""
|
class=""
|
||||||
@ -82,7 +82,7 @@ exports[`renders ./components/affix/demo/on-change.md extend context correctly 1
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/target.md extend context correctly 1`] = `
|
exports[`renders ./components/affix/demo/target.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="scrollable-container"
|
class="scrollable-container"
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/basic.md correctly 1`] = `
|
exports[`renders ./components/affix/demo/basic.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@ -34,7 +34,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/debug.md correctly 1`] = `
|
exports[`renders ./components/affix/demo/debug.tsx correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
style="height:10000px"
|
style="height:10000px"
|
||||||
>
|
>
|
||||||
@ -65,7 +65,7 @@ exports[`renders ./components/affix/demo/debug.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/on-change.md correctly 1`] = `
|
exports[`renders ./components/affix/demo/on-change.tsx correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class=""
|
class=""
|
||||||
@ -82,7 +82,7 @@ exports[`renders ./components/affix/demo/on-change.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/affix/demo/target.md correctly 1`] = `
|
exports[`renders ./components/affix/demo/target.tsx correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="scrollable-container"
|
class="scrollable-container"
|
||||||
>
|
>
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
---
|
|
||||||
order: 0
|
|
||||||
title:
|
|
||||||
zh-CN: 基本
|
|
||||||
en-US: Basic
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
最简单的用法。
|
最简单的用法。
|
||||||
@ -12,31 +5,3 @@ title:
|
|||||||
## en-US
|
## en-US
|
||||||
|
|
||||||
The simplest usage.
|
The simplest usage.
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Affix, Button } from 'antd';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
const [top, setTop] = useState(10);
|
|
||||||
const [bottom, setBottom] = useState(10);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Affix offsetTop={top}>
|
|
||||||
<Button type="primary" onClick={() => setTop(top + 10)}>
|
|
||||||
Affix top
|
|
||||||
</Button>
|
|
||||||
</Affix>
|
|
||||||
<br />
|
|
||||||
<Affix offsetBottom={bottom}>
|
|
||||||
<Button type="primary" onClick={() => setBottom(bottom + 10)}>
|
|
||||||
Affix bottom
|
|
||||||
</Button>
|
|
||||||
</Affix>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
```
|
|
||||||
|
25
components/affix/demo/basic.tsx
Normal file
25
components/affix/demo/basic.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [top, setTop] = useState(10);
|
||||||
|
const [bottom, setBottom] = useState(10);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Affix offsetTop={top}>
|
||||||
|
<Button type="primary" onClick={() => setTop(top + 10)}>
|
||||||
|
Affix top
|
||||||
|
</Button>
|
||||||
|
</Affix>
|
||||||
|
<br />
|
||||||
|
<Affix offsetBottom={bottom}>
|
||||||
|
<Button type="primary" onClick={() => setBottom(bottom + 10)}>
|
||||||
|
Affix bottom
|
||||||
|
</Button>
|
||||||
|
</Affix>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
order: 99
|
|
||||||
title:
|
|
||||||
zh-CN: 调整浏览器大小,观察 Affix 容器是否发生变化。跟随变化为正常。#17678
|
|
||||||
en-US: debug
|
|
||||||
debug: true
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
DEBUG
|
DEBUG
|
||||||
@ -13,28 +5,3 @@ DEBUG
|
|||||||
## en-US
|
## en-US
|
||||||
|
|
||||||
DEBUG
|
DEBUG
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Affix, Button } from 'antd';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
const [top, setTop] = useState(10);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ height: 10000 }}>
|
|
||||||
<div>Top</div>
|
|
||||||
<Affix offsetTop={top}>
|
|
||||||
<div style={{ background: 'red' }}>
|
|
||||||
<Button type="primary" onClick={() => setTop(top + 10)}>
|
|
||||||
Affix top
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Affix>
|
|
||||||
<div>Bottom</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
```
|
|
||||||
|
22
components/affix/demo/debug.tsx
Normal file
22
components/affix/demo/debug.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [top, setTop] = useState(10);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: 10000 }}>
|
||||||
|
<div>Top</div>
|
||||||
|
<Affix offsetTop={top}>
|
||||||
|
<div style={{ background: 'red' }}>
|
||||||
|
<Button type="primary" onClick={() => setTop(top + 10)}>
|
||||||
|
Affix top
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
|
<div>Bottom</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
@ -1,10 +1,3 @@
|
|||||||
---
|
|
||||||
order: 1
|
|
||||||
title:
|
|
||||||
zh-CN: 固定状态改变的回调
|
|
||||||
en-US: Callback
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
可以获得是否固定的状态。
|
可以获得是否固定的状态。
|
||||||
@ -12,16 +5,3 @@ title:
|
|||||||
## en-US
|
## en-US
|
||||||
|
|
||||||
Callback with affixed state.
|
Callback with affixed state.
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Affix, Button } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const App: React.FC = () => (
|
|
||||||
<Affix offsetTop={120} onChange={affixed => console.log(affixed)}>
|
|
||||||
<Button>120px to affix top</Button>
|
|
||||||
</Affix>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
```
|
|
||||||
|
10
components/affix/demo/on-change.tsx
Normal file
10
components/affix/demo/on-change.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<Affix offsetTop={120} onChange={affixed => console.log(affixed)}>
|
||||||
|
<Button>120px to affix top</Button>
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
@ -1,10 +1,3 @@
|
|||||||
---
|
|
||||||
order: 2
|
|
||||||
title:
|
|
||||||
zh-CN: 滚动容器
|
|
||||||
en-US: Container to scroll.
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
## zh-CN
|
||||||
|
|
||||||
用 `target` 设置 `Affix` 需要监听其滚动事件的元素,默认为 `window`。
|
用 `target` 设置 `Affix` 需要监听其滚动事件的元素,默认为 `window`。
|
||||||
@ -13,27 +6,6 @@ title:
|
|||||||
|
|
||||||
Set a `target` for 'Affix', which is listen to scroll event of target element (default is `window`).
|
Set a `target` for 'Affix', which is listen to scroll event of target element (default is `window`).
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Affix, Button } from 'antd';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
|
||||||
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="scrollable-container" ref={setContainer}>
|
|
||||||
<div className="background">
|
|
||||||
<Affix target={() => container}>
|
|
||||||
<Button type="primary">Fixed at the top of container</Button>
|
|
||||||
</Affix>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
```
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#components-affix-demo-target .scrollable-container {
|
#components-affix-demo-target .scrollable-container {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
18
components/affix/demo/target.tsx
Normal file
18
components/affix/demo/target.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Affix, Button } from 'antd';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="scrollable-container" ref={setContainer}>
|
||||||
|
<div className="background">
|
||||||
|
<Affix target={() => container}>
|
||||||
|
<Button type="primary">Fixed at the top of container</Button>
|
||||||
|
</Affix>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
@ -1,8 +1,12 @@
|
|||||||
---
|
---
|
||||||
category: Components
|
category: Components
|
||||||
type: Navigation
|
|
||||||
title: Affix
|
title: Affix
|
||||||
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
||||||
|
demo:
|
||||||
|
cols: 2
|
||||||
|
group:
|
||||||
|
title: Navigation
|
||||||
|
order: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
Wrap Affix around another component to make it stick the viewport.
|
Wrap Affix around another component to make it stick the viewport.
|
||||||
@ -13,6 +17,13 @@ On longer web pages, it's helpful to stick component into the viewport. This is
|
|||||||
|
|
||||||
Please note that Affix should not cover other content on the page, especially when the size of the viewport is small.
|
Please note that Affix should not cover other content on the page, especially when the size of the viewport is small.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<code src="./demo/basic.tsx">Basic</code>
|
||||||
|
<code src="./demo/on-change.tsx">Callback</code>
|
||||||
|
<code src="./demo/target.tsx">Container to scroll.</code>
|
||||||
|
<code src="./demo/debug.tsx" debug>debug</code>
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
| Property | Description | Type | Default |
|
| Property | Description | Type | Default |
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
---
|
---
|
||||||
category: Components
|
category: Components
|
||||||
subtitle: 固钉
|
subtitle: 固钉
|
||||||
type: 导航
|
|
||||||
title: Affix
|
title: Affix
|
||||||
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
||||||
|
demo:
|
||||||
|
cols: 2
|
||||||
|
group:
|
||||||
|
title: 导航
|
||||||
|
order: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
将页面元素钉在可视范围。
|
将页面元素钉在可视范围。
|
||||||
@ -14,6 +18,13 @@ cover: https://gw.alipayobjects.com/zos/alicdn/tX6-md4H6/Affix.svg
|
|||||||
|
|
||||||
页面可视范围过小时,慎用此功能以免遮挡页面内容。
|
页面可视范围过小时,慎用此功能以免遮挡页面内容。
|
||||||
|
|
||||||
|
## 代码演示
|
||||||
|
|
||||||
|
<code src="./demo/basic.tsx">基本</code>
|
||||||
|
<code src="./demo/on-change.tsx">固定状态改变的回调</code>
|
||||||
|
<code src="./demo/target.tsx">滚动容器</code>
|
||||||
|
<code src="./demo/debug.tsx" debug>调整浏览器大小,观察 Affix 容器是否发生变化。跟随变化为正常。#17678</code>
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
| 成员 | 说明 | 类型 | 默认值 |
|
| 成员 | 说明 | 类型 | 默认值 |
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/action.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/action.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success"
|
class="ant-alert ant-alert-success"
|
||||||
@ -265,7 +265,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/banner.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/banner.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-warning ant-alert-banner"
|
class="ant-alert ant-alert-warning ant-alert-banner"
|
||||||
@ -415,7 +415,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/basic.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/basic.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
data-show="true"
|
data-show="true"
|
||||||
@ -433,7 +433,7 @@ exports[`renders ./components/alert/demo/basic.md extend context correctly 1`] =
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/closable.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/closable.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-warning ant-alert-no-icon"
|
class="ant-alert ant-alert-warning ant-alert-no-icon"
|
||||||
@ -523,7 +523,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/close-text.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/close-text.tsx extend context correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-info ant-alert-no-icon"
|
class="ant-alert ant-alert-info ant-alert-no-icon"
|
||||||
data-show="true"
|
data-show="true"
|
||||||
@ -552,7 +552,7 @@ exports[`renders ./components/alert/demo/close-text.md extend context correctly
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/custom-icon.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/custom-icon.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
@ -864,7 +864,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/description.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/description.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon"
|
||||||
@ -949,7 +949,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/error-boundary.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/error-boundary.tsx extend context correctly 1`] = `
|
||||||
<button
|
<button
|
||||||
class="ant-btn ant-btn-default ant-btn-dangerous"
|
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||||
type="button"
|
type="button"
|
||||||
@ -960,7 +960,7 @@ exports[`renders ./components/alert/demo/error-boundary.md extend context correc
|
|||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/icon.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/icon.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success"
|
class="ant-alert ant-alert-success"
|
||||||
@ -1307,60 +1307,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/loop-banner.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/smooth-closed.tsx extend context correctly 1`] = `
|
||||||
<div
|
|
||||||
class="ant-alert ant-alert-warning ant-alert-banner"
|
|
||||||
data-show="true"
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-label="exclamation-circle"
|
|
||||||
class="anticon anticon-exclamation-circle ant-alert-icon"
|
|
||||||
role="img"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
data-icon="exclamation-circle"
|
|
||||||
fill="currentColor"
|
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="ant-alert-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-alert-message"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=" marquee-container"
|
|
||||||
style="--pause-on-hover: paused; --pause-on-click: running;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="marquee"
|
|
||||||
style="--play: running; --direction: normal; --duration: 0s; --delay: 0s; --iteration-count: infinite;"
|
|
||||||
>
|
|
||||||
I can be a React component, multiple React components, or just some text.
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="marquee"
|
|
||||||
style="--play: running; --direction: normal; --duration: 0s; --delay: 0s; --iteration-count: infinite;"
|
|
||||||
>
|
|
||||||
I can be a React component, multiple React components, or just some text.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/smooth-closed.md extend context correctly 1`] = `
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
@ -1408,7 +1355,7 @@ exports[`renders ./components/alert/demo/smooth-closed.md extend context correct
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/style.md extend context correctly 1`] = `
|
exports[`renders ./components/alert/demo/style.tsx extend context correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/action.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/action.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success"
|
class="ant-alert ant-alert-success"
|
||||||
@ -265,7 +265,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/banner.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-warning ant-alert-banner"
|
class="ant-alert ant-alert-warning ant-alert-banner"
|
||||||
@ -415,7 +415,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/basic.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/basic.tsx correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
data-show="true"
|
data-show="true"
|
||||||
@ -433,7 +433,7 @@ exports[`renders ./components/alert/demo/basic.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/closable.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-warning ant-alert-no-icon"
|
class="ant-alert ant-alert-warning ant-alert-no-icon"
|
||||||
@ -523,7 +523,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/close-text.tsx correctly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-info ant-alert-no-icon"
|
class="ant-alert ant-alert-info ant-alert-no-icon"
|
||||||
data-show="true"
|
data-show="true"
|
||||||
@ -552,7 +552,7 @@ exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/custom-icon.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
@ -864,7 +864,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/description.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon"
|
||||||
@ -949,7 +949,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/error-boundary.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/error-boundary.tsx correctly 1`] = `
|
||||||
<button
|
<button
|
||||||
class="ant-btn ant-btn-default ant-btn-dangerous"
|
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||||
type="button"
|
type="button"
|
||||||
@ -960,7 +960,7 @@ exports[`renders ./components/alert/demo/error-boundary.md correctly 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/icon.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success"
|
class="ant-alert ant-alert-success"
|
||||||
@ -1307,60 +1307,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/loop-banner.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/smooth-closed.tsx correctly 1`] = `
|
||||||
<div
|
|
||||||
class="ant-alert ant-alert-warning ant-alert-banner"
|
|
||||||
data-show="true"
|
|
||||||
role="alert"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-label="exclamation-circle"
|
|
||||||
class="anticon anticon-exclamation-circle ant-alert-icon"
|
|
||||||
role="img"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
data-icon="exclamation-circle"
|
|
||||||
fill="currentColor"
|
|
||||||
focusable="false"
|
|
||||||
height="1em"
|
|
||||||
viewBox="64 64 896 896"
|
|
||||||
width="1em"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="ant-alert-content"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ant-alert-message"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=" marquee-container"
|
|
||||||
style="--pause-on-hover: paused; --pause-on-click: running;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="marquee"
|
|
||||||
style="--play: running; --direction: normal; --duration: 0s; --delay: 0s; --iteration-count: infinite;"
|
|
||||||
>
|
|
||||||
I can be a React component, multiple React components, or just some text.
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="marquee"
|
|
||||||
style="--play: running; --direction: normal; --duration: 0s; --delay: 0s; --iteration-count: infinite;"
|
|
||||||
>
|
|
||||||
I can be a React component, multiple React components, or just some text.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
|
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
@ -1408,7 +1355,7 @@ exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
exports[`renders ./components/alert/demo/style.tsx correctly 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<div
|
<div
|
||||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import { extendTest } from '../../../tests/shared/demoTest';
|
import { extendTest } from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
extendTest('alert', { skip: ['loop-banner.md'] });
|
extendTest('alert', { skip: ['loop-banner.tsx'] });
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import demoTest from '../../../tests/shared/demoTest';
|
import demoTest from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
demoTest('alert', { skip: ['loop-banner.md'] });
|
demoTest('alert', { skip: ['loop-banner.tsx'] });
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user