mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 11:10:01 +08:00
site: improve overview page (#40126)
* site: imp * update fontsize * add scrollTo(0) * revert * add style return type * add resize Event when input onChange * docs: fix affix height and revert resize event * fix: justifycontent => justify-content * fix style Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
parent
267b40c93e
commit
1c8f6507ce
@ -1,8 +1,9 @@
|
|||||||
import React, { memo, useMemo, useState } from 'react';
|
import React, { memo, useMemo, useRef, useState } from 'react';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { Link, useIntl, useSidebarData, useLocation } from 'dumi';
|
import { Link, useIntl, useSidebarData, useLocation } from 'dumi';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { Card, Col, Divider, Input, Row, Space, Tag, Typography } from 'antd';
|
import { Card, Col, Divider, Input, Row, Space, Tag, Typography, Affix } from 'antd';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import type { Component } from './ProComponentsList';
|
import type { Component } from './ProComponentsList';
|
||||||
import proComponentsList from './ProComponentsList';
|
import proComponentsList from './ProComponentsList';
|
||||||
@ -10,7 +11,6 @@ import useSiteToken from '../../../hooks/useSiteToken';
|
|||||||
|
|
||||||
const useStyle = () => {
|
const useStyle = () => {
|
||||||
const { token } = useSiteToken();
|
const { token } = useSiteToken();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentsOverviewGroupTitle: css`
|
componentsOverviewGroupTitle: css`
|
||||||
margin-bottom: 24px !important;
|
margin-bottom: 24px !important;
|
||||||
@ -33,22 +33,25 @@ const useStyle = () => {
|
|||||||
box-shadow: 0 6px 16px -8px #00000014, 0 9px 28px #0000000d, 0 12px 48px 16px #00000008;
|
box-shadow: 0 6px 16px -8px #00000014, 0 9px 28px #0000000d, 0 12px 48px 16px #00000008;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
componentsOverviewAffix: css`
|
||||||
|
display: flex;
|
||||||
|
transition: all 0.3s;
|
||||||
|
justify-content: space-between;
|
||||||
|
`,
|
||||||
componentsOverviewSearch: css`
|
componentsOverviewSearch: css`
|
||||||
font-size: ${token.fontSizeXL}px;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.anticon-search {
|
.anticon-search {
|
||||||
color: ${token.colorTextDisabled};
|
color: ${token.colorTextDisabled};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
componentsOverviewContent: css`
|
componentsOverviewContent: css`
|
||||||
&:empty:after {
|
&:empty:after {
|
||||||
content: 'Not Found';
|
|
||||||
text-align: center;
|
|
||||||
padding: 16px 0 40px;
|
|
||||||
display: block;
|
display: block;
|
||||||
|
padding: 16px 0 40px;
|
||||||
color: ${token.colorTextDisabled};
|
color: ${token.colorTextDisabled};
|
||||||
|
text-align: center;
|
||||||
border-bottom: 1px solid ${token.colorSplit};
|
border-bottom: 1px solid ${token.colorSplit};
|
||||||
|
content: 'Not Found';
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
@ -76,13 +79,27 @@ const { Title } = Typography;
|
|||||||
|
|
||||||
const Overview: React.FC = () => {
|
const Overview: React.FC = () => {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
|
|
||||||
const data = useSidebarData();
|
const data = useSidebarData();
|
||||||
|
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { token } = useSiteToken();
|
||||||
|
const { borderRadius, colorBgContainer, fontSizeXL } = token;
|
||||||
|
|
||||||
|
const affixedStyle: CSSProperties = {
|
||||||
|
boxShadow: 'rgba(50, 50, 93, 0.25) 0 6px 12px -2px, rgba(0, 0, 0, 0.3) 0 3px 7px -3px',
|
||||||
|
padding: 8,
|
||||||
|
margin: -8,
|
||||||
|
borderRadius,
|
||||||
|
backgroundColor: colorBgContainer,
|
||||||
|
};
|
||||||
|
|
||||||
const { search: urlSearch } = useLocation();
|
const { search: urlSearch } = useLocation();
|
||||||
const { locale, formatMessage } = useIntl();
|
const { locale, formatMessage } = useIntl();
|
||||||
|
|
||||||
const [search, setSearch] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
|
|
||||||
const sectionRef = React.useRef<HTMLElement>(null);
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
|
||||||
if (event.keyCode === 13 && search.trim().length) {
|
if (event.keyCode === 13 && search.trim().length) {
|
||||||
@ -114,23 +131,27 @@ const Overview: React.FC = () => {
|
|||||||
]),
|
]),
|
||||||
[data, locale],
|
[data, locale],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="markdown" ref={sectionRef}>
|
<section className="markdown" ref={sectionRef}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Input
|
<Affix offsetTop={24} onChange={setSearchBarAffixed}>
|
||||||
value={search}
|
<div css={style.componentsOverviewAffix} style={searchBarAffixed ? affixedStyle : {}}>
|
||||||
placeholder={formatMessage({ id: 'app.components.overview.search' })}
|
<Input
|
||||||
css={style.componentsOverviewSearch}
|
autoFocus
|
||||||
onChange={(e) => {
|
value={search}
|
||||||
setSearch(e.target.value);
|
placeholder={formatMessage({ id: 'app.components.overview.search' })}
|
||||||
reportSearch(e.target.value);
|
css={style.componentsOverviewSearch}
|
||||||
}}
|
onChange={(e) => {
|
||||||
onKeyDown={onKeyDown}
|
setSearch(e.target.value);
|
||||||
bordered={false}
|
reportSearch(e.target.value);
|
||||||
autoFocus
|
}}
|
||||||
suffix={<SearchOutlined />}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
bordered={false}
|
||||||
|
suffix={<SearchOutlined />}
|
||||||
|
style={{ fontSize: searchBarAffixed ? fontSizeXL - 2 : fontSizeXL }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div css={style.componentsOverviewContent}>
|
<div css={style.componentsOverviewContent}>
|
||||||
{groups
|
{groups
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import * as React from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import Icon, * as AntdIcons from '@ant-design/icons';
|
import Icon, * as AntdIcons from '@ant-design/icons';
|
||||||
|
import type { SegmentedProps } from 'antd';
|
||||||
|
import type { IntlShape } from 'react-intl';
|
||||||
import { Segmented, Input, Empty, Affix } from 'antd';
|
import { Segmented, Input, Empty, Affix } from 'antd';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
import { useIntl } from 'dumi';
|
import { useIntl } from 'dumi';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import Category from './Category';
|
import Category from './Category';
|
||||||
@ -17,6 +21,32 @@ export enum ThemeType {
|
|||||||
|
|
||||||
const allIcons: { [key: string]: any } = AntdIcons;
|
const allIcons: { [key: string]: any } = AntdIcons;
|
||||||
|
|
||||||
|
const useStyle = () => ({
|
||||||
|
iconSearchAffix: css`
|
||||||
|
display: flex;
|
||||||
|
transition: all 0.3s;
|
||||||
|
justify-content: space-between;
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = (intl: IntlShape): SegmentedProps['options'] => [
|
||||||
|
{
|
||||||
|
value: ThemeType.Outlined,
|
||||||
|
icon: <Icon component={OutlinedIcon} />,
|
||||||
|
label: intl.formatMessage({ id: 'app.docs.components.icon.outlined' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ThemeType.Filled,
|
||||||
|
icon: <Icon component={FilledIcon} />,
|
||||||
|
label: intl.formatMessage({ id: 'app.docs.components.icon.filled' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ThemeType.TwoTone,
|
||||||
|
icon: <Icon component={TwoToneIcon} />,
|
||||||
|
label: intl.formatMessage({ id: 'app.docs.components.icon.two-tone' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
interface IconSearchState {
|
interface IconSearchState {
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
@ -24,25 +54,23 @@ interface IconSearchState {
|
|||||||
|
|
||||||
const IconSearch: React.FC = () => {
|
const IconSearch: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [displayState, setDisplayState] = React.useState<IconSearchState>({
|
const { iconSearchAffix } = useStyle();
|
||||||
theme: ThemeType.Outlined,
|
const [displayState, setDisplayState] = useState<IconSearchState>({
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
theme: ThemeType.Outlined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newIconNames: string[] = [];
|
const newIconNames: string[] = [];
|
||||||
|
|
||||||
const handleSearchIcon = React.useCallback(
|
const handleSearchIcon = debounce((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
debounce((searchKey: string) => {
|
setDisplayState((prevState) => ({ ...prevState, searchKey: e.target.value }));
|
||||||
setDisplayState((prevState) => ({ ...prevState, searchKey }));
|
}, 300);
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeTheme = React.useCallback((value) => {
|
const handleChangeTheme = useCallback((value) => {
|
||||||
setDisplayState((prevState) => ({ ...prevState, theme: value as ThemeType }));
|
setDisplayState((prevState) => ({ ...prevState, theme: value as ThemeType }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
|
const renderCategories = useMemo<React.ReactNode | React.ReactNode[]>(() => {
|
||||||
const { searchKey = '', theme } = displayState;
|
const { searchKey = '', theme } = displayState;
|
||||||
|
|
||||||
const categoriesResult = Object.keys(categories)
|
const categoriesResult = Object.keys(categories)
|
||||||
@ -77,62 +105,38 @@ const IconSearch: React.FC = () => {
|
|||||||
newIcons={newIconNames}
|
newIcons={newIconNames}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
|
return categoriesResult.length ? categoriesResult : <Empty style={{ margin: '2em 0' }} />;
|
||||||
}, [displayState.searchKey, displayState.theme]);
|
}, [displayState.searchKey, displayState.theme]);
|
||||||
|
|
||||||
const [searchBarAffixed, setSearchBarAffixed] = React.useState(false);
|
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
|
||||||
const { token } = useSiteToken();
|
const { token } = useSiteToken();
|
||||||
const { borderRadius, colorBgContainer } = token;
|
const { borderRadius, colorBgContainer } = token;
|
||||||
const affixedStyle = searchBarAffixed
|
|
||||||
? {
|
const affixedStyle: CSSProperties = {
|
||||||
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px',
|
boxShadow: 'rgba(50, 50, 93, 0.25) 0 6px 12px -2px, rgba(0, 0, 0, 0.3) 0 3px 7px -3px',
|
||||||
padding: 8,
|
padding: 8,
|
||||||
margin: -8,
|
margin: -8,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
background: colorBgContainer,
|
backgroundColor: colorBgContainer,
|
||||||
}
|
};
|
||||||
: {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="markdown">
|
<div className="markdown">
|
||||||
<Affix offsetTop={24} onChange={(affixed) => setSearchBarAffixed(affixed)}>
|
<Affix offsetTop={24} onChange={setSearchBarAffixed}>
|
||||||
<div
|
<div css={iconSearchAffix} style={searchBarAffixed ? affixedStyle : {}}>
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
transition: 'all .3s',
|
|
||||||
...affixedStyle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Segmented
|
<Segmented
|
||||||
value={displayState.theme}
|
|
||||||
onChange={handleChangeTheme}
|
|
||||||
size="large"
|
size="large"
|
||||||
options={[
|
value={displayState.theme}
|
||||||
{
|
options={options(intl)}
|
||||||
icon: <Icon component={OutlinedIcon} />,
|
onChange={handleChangeTheme}
|
||||||
label: intl.formatMessage({ id: 'app.docs.components.icon.outlined' }),
|
|
||||||
value: ThemeType.Outlined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Icon component={FilledIcon} />,
|
|
||||||
label: intl.formatMessage({ id: 'app.docs.components.icon.filled' }),
|
|
||||||
value: ThemeType.Filled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Icon component={TwoToneIcon} />,
|
|
||||||
label: intl.formatMessage({ id: 'app.docs.components.icon.two-tone' }),
|
|
||||||
value: ThemeType.TwoTone,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={intl.formatMessage({ id: 'app.docs.components.icon.search.placeholder' })}
|
placeholder={intl.formatMessage({ id: 'app.docs.components.icon.search.placeholder' })}
|
||||||
style={{ flex: 1, marginInlineStart: 16 }}
|
style={{ flex: 1, marginInlineStart: 16 }}
|
||||||
allowClear
|
allowClear
|
||||||
onChange={(e) => handleSearchIcon(e.currentTarget.value)}
|
|
||||||
size="large"
|
|
||||||
autoFocus
|
autoFocus
|
||||||
|
size="large"
|
||||||
|
onChange={handleSearchIcon}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Affix>
|
</Affix>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSidebarData } from 'dumi';
|
import { useSidebarData } from 'dumi';
|
||||||
import { Affix, Col, ConfigProvider, Menu } from 'antd';
|
import { Col, ConfigProvider, Menu } from 'antd';
|
||||||
import MobileMenu from 'rc-drawer';
|
import MobileMenu from 'rc-drawer';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import SiteContext from '../SiteContext';
|
import SiteContext from '../SiteContext';
|
||||||
@ -107,6 +107,9 @@ const useStyle = () => {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.main-menu-inner {
|
.main-menu-inner {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -115,11 +118,6 @@ const useStyle = () => {
|
|||||||
&:hover .main-menu-inner {
|
&:hover .main-menu-inner {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> div,
|
|
||||||
> div > div {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -153,11 +151,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<MobileMenu key="Mobile-menu">{menuChild}</MobileMenu>
|
<MobileMenu key="Mobile-menu">{menuChild}</MobileMenu>
|
||||||
) : (
|
) : (
|
||||||
<Col xxl={4} xl={5} lg={6} md={6} sm={24} xs={24} css={styles.mainMenu}>
|
<Col xxl={4} xl={5} lg={6} md={6} sm={24} xs={24} css={styles.mainMenu}>
|
||||||
<Affix>
|
<section className="main-menu-inner">{menuChild}</section>
|
||||||
<section style={{ width: '100%' }} className="main-menu-inner">
|
|
||||||
{menuChild}
|
|
||||||
</section>
|
|
||||||
</Affix>
|
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,8 +7,14 @@ export interface LoadingIconProps {
|
|||||||
existIcon: boolean;
|
existIcon: boolean;
|
||||||
loading?: boolean | object;
|
loading?: boolean | object;
|
||||||
}
|
}
|
||||||
const getCollapsedWidth = () => ({ width: 0, opacity: 0, transform: 'scale(0)' });
|
|
||||||
const getRealWidth = (node: HTMLElement) => ({
|
const getCollapsedWidth = (): React.CSSProperties => ({
|
||||||
|
width: 0,
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'scale(0)',
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRealWidth = (node: HTMLElement): React.CSSProperties => ({
|
||||||
width: node.scrollWidth,
|
width: node.scrollWidth,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transform: 'scale(1)',
|
transform: 'scale(1)',
|
||||||
|
Loading…
Reference in New Issue
Block a user