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 { css } from '@emotion/react';
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 type { Component } from './ProComponentsList';
import proComponentsList from './ProComponentsList';
@ -10,7 +11,6 @@ import useSiteToken from '../../../hooks/useSiteToken';
const useStyle = () => {
const { token } = useSiteToken();
return {
componentsOverviewGroupTitle: css`
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;
}
`,
componentsOverviewAffix: css`
display: flex;
transition: all 0.3s;
justify-content: space-between;
`,
componentsOverviewSearch: css`
font-size: ${token.fontSizeXL}px;
padding: 0;
.anticon-search {
color: ${token.colorTextDisabled};
}
`,
componentsOverviewContent: css`
&:empty:after {
content: 'Not Found';
text-align: center;
padding: 16px 0 40px;
display: block;
padding: 16px 0 40px;
color: ${token.colorTextDisabled};
text-align: center;
border-bottom: 1px solid ${token.colorSplit};
content: 'Not Found';
}
`,
};
@ -76,13 +79,27 @@ const { Title } = Typography;
const Overview: React.FC = () => {
const style = useStyle();
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 { locale, formatMessage } = useIntl();
const [search, setSearch] = useState<string>('');
const sectionRef = React.useRef<HTMLElement>(null);
const sectionRef = useRef<HTMLElement>(null);
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
if (event.keyCode === 13 && search.trim().length) {
@ -114,11 +131,13 @@ const Overview: React.FC = () => {
]),
[data, locale],
);
return (
<section className="markdown" ref={sectionRef}>
<Divider />
<Affix offsetTop={24} onChange={setSearchBarAffixed}>
<div css={style.componentsOverviewAffix} style={searchBarAffixed ? affixedStyle : {}}>
<Input
autoFocus
value={search}
placeholder={formatMessage({ id: 'app.components.overview.search' })}
css={style.componentsOverviewSearch}
@ -128,9 +147,11 @@ const Overview: React.FC = () => {
}}
onKeyDown={onKeyDown}
bordered={false}
autoFocus
suffix={<SearchOutlined />}
style={{ fontSize: searchBarAffixed ? fontSizeXL - 2 : fontSizeXL }}
/>
</div>
</Affix>
<Divider />
<div css={style.componentsOverviewContent}>
{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 type { SegmentedProps } from 'antd';
import type { IntlShape } from 'react-intl';
import { Segmented, Input, Empty, Affix } from 'antd';
import { css } from '@emotion/react';
import { useIntl } from 'dumi';
import debounce from 'lodash/debounce';
import Category from './Category';
@ -17,6 +21,32 @@ export enum ThemeType {
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 {
theme: ThemeType;
searchKey: string;
@ -24,25 +54,23 @@ interface IconSearchState {
const IconSearch: React.FC = () => {
const intl = useIntl();
const [displayState, setDisplayState] = React.useState<IconSearchState>({
theme: ThemeType.Outlined,
const { iconSearchAffix } = useStyle();
const [displayState, setDisplayState] = useState<IconSearchState>({
searchKey: '',
theme: ThemeType.Outlined,
});
const newIconNames: string[] = [];
const handleSearchIcon = React.useCallback(
debounce((searchKey: string) => {
setDisplayState((prevState) => ({ ...prevState, searchKey }));
}),
[],
);
const handleSearchIcon = debounce((e: React.ChangeEvent<HTMLInputElement>) => {
setDisplayState((prevState) => ({ ...prevState, searchKey: e.target.value }));
}, 300);
const handleChangeTheme = React.useCallback((value) => {
const handleChangeTheme = useCallback((value) => {
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 categoriesResult = Object.keys(categories)
@ -77,62 +105,38 @@ const IconSearch: React.FC = () => {
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]);
const [searchBarAffixed, setSearchBarAffixed] = React.useState(false);
const [searchBarAffixed, setSearchBarAffixed] = useState<boolean>(false);
const { token } = useSiteToken();
const { borderRadius, colorBgContainer } = token;
const affixedStyle = searchBarAffixed
? {
boxShadow: 'rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px',
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,
background: colorBgContainer,
}
: {};
backgroundColor: colorBgContainer,
};
return (
<div className="markdown">
<Affix offsetTop={24} onChange={(affixed) => setSearchBarAffixed(affixed)}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
transition: 'all .3s',
...affixedStyle,
}}
>
<Affix offsetTop={24} onChange={setSearchBarAffixed}>
<div css={iconSearchAffix} style={searchBarAffixed ? affixedStyle : {}}>
<Segmented
value={displayState.theme}
onChange={handleChangeTheme}
size="large"
options={[
{
icon: <Icon component={OutlinedIcon} />,
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,
},
]}
value={displayState.theme}
options={options(intl)}
onChange={handleChangeTheme}
/>
<Input.Search
placeholder={intl.formatMessage({ id: 'app.docs.components.icon.search.placeholder' })}
style={{ flex: 1, marginInlineStart: 16 }}
allowClear
onChange={(e) => handleSearchIcon(e.currentTarget.value)}
size="large"
autoFocus
size="large"
onChange={handleSearchIcon}
/>
</div>
</Affix>

View File

@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import { useSidebarData } from 'dumi';
import { Affix, Col, ConfigProvider, Menu } from 'antd';
import { Col, ConfigProvider, Menu } from 'antd';
import MobileMenu from 'rc-drawer';
import { css } from '@emotion/react';
import SiteContext from '../SiteContext';
@ -107,6 +107,9 @@ const useStyle = () => {
z-index: 1;
.main-menu-inner {
position: sticky;
top: 0;
width: 100%;
height: 100%;
max-height: 100vh;
overflow: hidden;
@ -115,11 +118,6 @@ const useStyle = () => {
&:hover .main-menu-inner {
overflow-y: auto;
}
> div,
> div > div {
height: 100%;
}
`,
};
};
@ -153,11 +151,7 @@ const Sidebar: React.FC = () => {
<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>
<section className="main-menu-inner">{menuChild}</section>
</Col>
);
};

View File

@ -7,8 +7,14 @@ export interface LoadingIconProps {
existIcon: boolean;
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,
opacity: 1,
transform: 'scale(1)',