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:
lijianan 2023-01-11 21:37:12 +08:00 committed by GitHub
parent 267b40c93e
commit 1c8f6507ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 113 additions and 88 deletions

View File

@ -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

View File

@ -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>

View File

@ -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>
); );
}; };

View File

@ -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)',