From c0e9354a6efef366df6dfaa3c0dfa01ba1291b05 Mon Sep 17 00:00:00 2001 From: CanisMinor Date: Wed, 6 Sep 2023 03:21:31 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Auto=20scroll=20to=20top?= =?UTF-8?q?=20when=20change=20route?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/DocLayout/DocumentLayout.tsx | 109 +++++++++++++++++++++ src/layouts/DocLayout/index.tsx | 119 ++--------------------- src/pages/Changelog/index.tsx | 7 +- src/pages/Docs/index.tsx | 7 +- src/pages/Home/index.tsx | 7 +- src/slots/Content/index.tsx | 12 ++- src/slots/Header/ThemeSwitch.tsx | 5 +- src/store/initialState.ts | 88 +++++++++++++++++ src/store/useSiteStore.ts | 97 +----------------- 9 files changed, 239 insertions(+), 212 deletions(-) create mode 100644 src/layouts/DocLayout/DocumentLayout.tsx create mode 100644 src/store/initialState.ts diff --git a/src/layouts/DocLayout/DocumentLayout.tsx b/src/layouts/DocLayout/DocumentLayout.tsx new file mode 100644 index 0000000..391a0e2 --- /dev/null +++ b/src/layouts/DocLayout/DocumentLayout.tsx @@ -0,0 +1,109 @@ +import { Layout } from '@lobehub/ui'; +import { useResponsive, useTheme } from 'antd-style'; +import { Helmet, useIntl, useLocation } from 'dumi'; +import isEqual from 'fast-deep-equal'; +import { memo, useCallback, useEffect } from 'react'; +import { shallow } from 'zustand/shallow'; + +import Changelog from '@/pages/Changelog'; +import Docs from '@/pages/Docs'; +import Home from '@/pages/Home'; +import Footer from '@/slots/Footer'; +import Header from '@/slots/Header'; +import Sidebar from '@/slots/Sidebar'; +import Toc from '@/slots/Toc'; +import { isHeroPageSel, siteTitleSel, tocAnchorItemSel, useSiteStore } from '@/store'; + +const DocumentLayout = memo(() => { + const intl = useIntl(); + const { hash } = useLocation(); + const theme = useTheme(); + const { mobile, laptop } = useResponsive(); + + const { loading, page, siteTitle, noToc } = useSiteStore((s) => { + const isChanlogPage = s.location.pathname === '/changelog'; + const isHomePage = isHeroPageSel(s); + let page; + + if (isHomePage) { + page = 'home'; + } else if (isChanlogPage) { + page = 'changelog'; + } else { + page = 'docs'; + } + + return { + loading: s.siteData.loading, + noToc: tocAnchorItemSel(s).length === 0, + page: page, + siteTitle: siteTitleSel(s), + }; + }, shallow); + + const fm = useSiteStore((s) => s.routeMeta.frontmatter, isEqual); + + const hideSidebar = page !== 'docs' || mobile || fm.sidebar === false; + const shouldHideToc = fm.toc === false || noToc; + const hideToc = mobile ? shouldHideToc : !laptop || shouldHideToc; + + const HelmetBlock = useCallback( + () => ( + + + {fm.title && } + {fm.description && } + {fm.description && } + {fm.keywords && } + {fm.keywords && } + {!fm.title || page === 'home' ? ( + {siteTitle} + ) : ( + + {fm.title} - {siteTitle} + + )} + + ), + [intl, fm, siteTitle, page], + ); + + // handle hash change or visit page hash after async chunk loaded + useEffect(() => { + const id = hash.replace('#', ''); + + if (!id) return; + setTimeout(() => { + const elm = document.querySelector(`#${decodeURIComponent(id)}`); + if (elm) { + elm.scrollIntoView(); + window.scrollBy({ top: -80 }); + } + }, 1); + }, [loading, hash]); + + useEffect(() => { + document.body.scrollTo(0, 0); + }, [siteTitle]); + + return ( + <> + + } + header={
} + headerHeight={mobile && page !== 'home' ? theme.headerHeight + 36 : theme.headerHeight} + sidebar={hideSidebar ? undefined : } + toc={hideToc ? undefined : } + tocWidth={hideToc ? 0 : theme.tocWidth} + > + {page === 'home' && } + {page === 'changelog' && } + {page === 'docs' && } + + + ); +}); + +export default DocumentLayout; diff --git a/src/layouts/DocLayout/index.tsx b/src/layouts/DocLayout/index.tsx index 9481720..4b0c4b7 100644 --- a/src/layouts/DocLayout/index.tsx +++ b/src/layouts/DocLayout/index.tsx @@ -1,125 +1,20 @@ -import { Layout, ThemeProvider } from '@lobehub/ui'; -import { extractStaticStyle, useResponsive, useTheme } from 'antd-style'; -import { Helmet, useIntl, useLocation } from 'dumi'; -import isEqual from 'fast-deep-equal'; -import { memo, useCallback, useEffect } from 'react'; +import { ThemeProvider } from '@lobehub/ui'; +import { extractStaticStyle } from 'antd-style'; +import { memo } from 'react'; import { shallow } from 'zustand/shallow'; import Favicons from '@/components/Favicons'; import { StoreUpdater } from '@/components/StoreUpdater'; import GlobalStyle from '@/layouts/DocLayout/GlobalStyle'; -import Changelog from '@/pages/Changelog'; -import Docs from '@/pages/Docs'; -import Home from '@/pages/Home'; -import Footer from '@/slots/Footer'; -import Header from '@/slots/Header'; -import Sidebar from '@/slots/Sidebar'; -import Toc from '@/slots/Toc'; -import { - isHeroPageSel, - siteTitleSel, - tocAnchorItemSel, - useSiteStore, - useThemeStore, -} from '@/store'; +import { useThemeStore } from '@/store'; import customToken from '@/styles/customToken'; -const DocumentLayout = memo(() => { - const intl = useIntl(); - const { hash } = useLocation(); - const theme = useTheme(); - const { mobile, laptop } = useResponsive(); - - const { loading, page, siteTitle, noToc } = useSiteStore((s) => { - const isChanlogPage = s.location.pathname === '/changelog'; - const isHomePage = isHeroPageSel(s); - let page; - - if (isHomePage) { - page = 'home'; - } else if (isChanlogPage) { - page = 'changelog'; - } else { - page = 'docs'; - } - - return { - loading: s.siteData.loading, - noToc: tocAnchorItemSel(s).length === 0, - page: page, - siteTitle: siteTitleSel(s), - }; - }, shallow); - - const fm = useSiteStore((s) => s.routeMeta.frontmatter, isEqual); - - const hideSidebar = page !== 'docs' || mobile || fm.sidebar === false; - const shouldHideToc = fm.toc === false || noToc; - const hideToc = mobile ? shouldHideToc : !laptop || shouldHideToc; - - const HelmetBlock = useCallback( - () => ( - - - {fm.title && } - {fm.description && } - {fm.description && } - {fm.keywords && } - {fm.keywords && } - {!fm.title || page === 'home' ? ( - {siteTitle} - ) : ( - - {fm.title} - {siteTitle} - - )} - - ), - [intl, fm, siteTitle, page], - ); - - // handle hash change or visit page hash after async chunk loaded - useEffect(() => { - const id = hash.replace('#', ''); - - if (!id) return; - setTimeout(() => { - const elm = document.querySelector(`#${decodeURIComponent(id)}`); - if (elm) { - elm.scrollIntoView(); - window.scrollBy({ top: -80 }); - } - }, 1); - }, [loading, hash]); - - useEffect(() => { - document.body.scrollTo(0, 0); - }, [siteTitle]); - - return ( - <> - - } - header={
} - headerHeight={mobile && page !== 'home' ? theme.headerHeight + 36 : theme.headerHeight} - sidebar={hideSidebar ? undefined : } - toc={hideToc ? undefined : } - tocWidth={hideToc ? 0 : theme.tocWidth} - > - {page === 'home' && } - {page === 'changelog' && } - {page === 'docs' && } - - - ); -}); +import DocumentLayout from './DocumentLayout'; // @ts-ignore global.__ANTD_CACHE__ = extractStaticStyle.cache; -export default memo(() => { +const App = memo(() => { const themeMode = useThemeStore((st) => st.themeMode, shallow); return ( @@ -137,3 +32,5 @@ export default memo(() => { ); }); + +export default App; diff --git a/src/pages/Changelog/index.tsx b/src/pages/Changelog/index.tsx index 0aab35e..2b6cbdf 100644 --- a/src/pages/Changelog/index.tsx +++ b/src/pages/Changelog/index.tsx @@ -1,7 +1,7 @@ import { useResponsive } from 'antd-style'; import { useOutlet } from 'dumi'; import isEqual from 'fast-deep-equal'; -import { memo } from 'react'; +import { memo, useEffect } from 'react'; import { Center } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; @@ -29,6 +29,11 @@ const Changelog = memo(() => { const { styles } = useStyles(); + useEffect(() => { + window.scrollTo(0, 0); + document.body.scrollTo(0, 0); + }, []); + return ( <>
diff --git a/src/pages/Docs/index.tsx b/src/pages/Docs/index.tsx index 511e888..907337c 100644 --- a/src/pages/Docs/index.tsx +++ b/src/pages/Docs/index.tsx @@ -1,7 +1,7 @@ import { Giscus } from '@lobehub/ui'; import { useResponsive } from 'antd-style'; import { useOutlet } from 'dumi'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useEffect } from 'react'; import { Center } from 'react-layout-kit'; import { shallow } from 'zustand/shallow'; @@ -20,6 +20,11 @@ const Documents = memo(() => { ); const { styles } = useStyles(); + useEffect(() => { + window.scrollTo(0, 0); + document.body.scrollTo(0, 0); + }, [location.pathname]); + const Comment = useCallback( () => giscus && ( diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 8931b5a..cbff6d5 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,5 +1,5 @@ import { useOutlet } from 'dumi'; -import { memo } from 'react'; +import { memo, useEffect } from 'react'; import { Flexbox } from 'react-layout-kit'; import Features from '@/slots/Features'; @@ -8,6 +8,11 @@ import Hero from '@/slots/Hero'; const Home = memo(() => { const outlet = useOutlet(); + useEffect(() => { + window.scrollTo(0, 0); + document.body.scrollTo(0, 0); + }, []); + return ( diff --git a/src/slots/Content/index.tsx b/src/slots/Content/index.tsx index e185edb..d60c2cb 100644 --- a/src/slots/Content/index.tsx +++ b/src/slots/Content/index.tsx @@ -1,6 +1,6 @@ -import { Skeleton, Typography } from 'antd'; +import { Skeleton } from 'antd'; import { useResponsive } from 'antd-style'; -import { memo } from 'react'; +import { memo, useEffect } from 'react'; import { Flexbox } from 'react-layout-kit'; import ContentFooter from '@/slots/ContentFooter'; @@ -15,11 +15,15 @@ const Content = memo(({ children, ...props }) => { const { styles, cx } = useStyles(); const { mobile } = useResponsive(); + useEffect(() => { + document.body.scrollTo(0, 0); + }, [loading]); + return ( - +
- +
{ const themeMode = useThemeStore((s) => s.themeMode); + const setColorMode = usePrefersColor()[2]; + useEffect(() => setColorMode(themeMode), [themeMode]); return ( ; + demos: Record< + string, + { + asset: IPreviewerProps['asset']; + component: ComponentType; + routeId: string; + } + >; + entryExports: Record; + + loading: boolean; + locales: ILocalesConfig; + pkg: Partial>; + setLoading: (status: boolean) => void; + themeConfig: SiteThemeConfig; +} + +export interface SiteStore { + locale: ILocale; + location: Location; + navData: NavData; + routeMeta: IRouteMeta; + sidebar?: ISidebarGroup[]; + siteData: ISiteData; + tabMeta?: NonNullable[0]['meta']; +} + +export const initialState: SiteStore = { + locale: { id: 'zh-CN', name: '中文', suffix: '' }, + location: { + hash: '', + key: '', + pathname: '', + search: '', + state: '', + }, + navData: [], + + routeMeta: { + // @ts-ignore + frontmatter: {}, + + tabs: undefined, + + texts: [], + + toc: [], + }, + + sidebar: [], + + siteData: { + components: {}, + + demos: {}, + + entryExports: {}, + + loading: true, + + locales: [], + + pkg: {}, + // @ts-ignore + setLoading: undefined, + // @ts-ignore + themeConfig: {}, + }, +}; diff --git a/src/store/useSiteStore.ts b/src/store/useSiteStore.ts index 9fdff11..027fda9 100644 --- a/src/store/useSiteStore.ts +++ b/src/store/useSiteStore.ts @@ -1,99 +1,10 @@ -import { AtomAsset } from 'dumi-assets-types'; -import { - ILocale, - ILocalesConfig, - INavItem, - IPreviewerProps, - IRouteMeta, - ISidebarGroup, -} from 'dumi/dist/client/theme-api/types'; -import { PICKED_PKG_FIELDS } from 'dumi/dist/constants'; -import type { Location } from 'history'; -import { ComponentType } from 'react'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; -import { SiteThemeConfig } from '@/types'; - -export type NavData = (INavItem & { children?: INavItem[] | undefined })[]; - -export interface ISiteData { - components: Record; - demos: Record< - string, - { - asset: IPreviewerProps['asset']; - component: ComponentType; - routeId: string; - } - >; - entryExports: Record; - - loading: boolean; - locales: ILocalesConfig; - pkg: Partial>; - setLoading: (status: boolean) => void; - themeConfig: SiteThemeConfig; -} - -export interface SiteStore { - locale: ILocale; - location: Location; - navData: NavData; - routeMeta: IRouteMeta; - sidebar?: ISidebarGroup[]; - siteData: ISiteData; - tabMeta?: NonNullable[0]['meta']; -} - -const initialState: SiteStore = { - locale: { id: 'zh-CN', name: '中文', suffix: '' }, - location: { - hash: '', - key: '', - pathname: '', - search: '', - state: '', - }, - navData: [], - - routeMeta: { - // @ts-ignore - frontmatter: {}, - - tabs: undefined, - - texts: [], - - toc: [], - }, - - sidebar: [], - - siteData: { - components: {}, - - demos: {}, - - entryExports: {}, - - loading: true, - - locales: [], - - pkg: {}, - // @ts-ignore - setLoading: undefined, - // @ts-ignore - themeConfig: {}, - }, -}; +import { SiteStore, initialState } from './initialState'; export const useSiteStore = create()( - devtools( - () => ({ - ...initialState, - }), - { name: '@' }, - ), + devtools(() => initialState, { name: 'dumi-theme-lobehub' }), ); + +export * from './initialState';