mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 11:18:24 +02:00
fix(v2): enable scrolling for sidebar menu only (#2645)
* fix(v2): enable scrolling for sidebar menu only * Add support for announcement bar * fix: remove redundant styles
This commit is contained in:
parent
34e664ac27
commit
d391a2bcdb
9 changed files with 186 additions and 65 deletions
|
@ -5,44 +5,23 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useState, useEffect} from 'react';
|
import React from 'react';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import useAnnouncementBarContext from '@theme/hooks/useAnnouncementBarContext';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
const STORAGE_DISMISS_KEY = 'docusaurus.announcement.dismiss';
|
|
||||||
const STORAGE_ID_KEY = 'docusaurus.announcement.id';
|
|
||||||
|
|
||||||
function AnnouncementBar() {
|
function AnnouncementBar() {
|
||||||
const {
|
const {
|
||||||
siteConfig: {themeConfig: {announcementBar = {}}} = {},
|
siteConfig: {themeConfig: {announcementBar = {}}} = {},
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {id, content, backgroundColor, textColor} = announcementBar;
|
const {content, backgroundColor, textColor} = announcementBar;
|
||||||
const [isClosed, setClosed] = useState(true);
|
const {
|
||||||
const handleClose = () => {
|
isAnnouncementBarClosed,
|
||||||
localStorage.setItem(STORAGE_DISMISS_KEY, true);
|
closeAnnouncementBar,
|
||||||
setClosed(true);
|
} = useAnnouncementBarContext();
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (!content || isAnnouncementBarClosed) {
|
||||||
const viewedId = localStorage.getItem(STORAGE_ID_KEY);
|
|
||||||
const isNewAnnouncement = id !== viewedId;
|
|
||||||
|
|
||||||
localStorage.setItem(STORAGE_ID_KEY, id);
|
|
||||||
|
|
||||||
if (isNewAnnouncement) {
|
|
||||||
localStorage.setItem(STORAGE_DISMISS_KEY, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isNewAnnouncement ||
|
|
||||||
localStorage.getItem(STORAGE_DISMISS_KEY) === 'false'
|
|
||||||
) {
|
|
||||||
setClosed(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!content || isClosed) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +38,7 @@ function AnnouncementBar() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.announcementBarClose}
|
className={styles.announcementBarClose}
|
||||||
onClick={handleClose}
|
onClick={closeAnnouncementBar}
|
||||||
aria-label="Close">
|
aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -5,13 +5,24 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--docusaurus-announcement-bar-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.announcementBar {
|
.announcementBar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: var(--docusaurus-announcement-bar-height);
|
||||||
background-color: var(--ifm-color-primary);
|
background-color: var(--ifm-color-primary);
|
||||||
color: var(--ifm-color-black);
|
color: var(--ifm-color-black);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
:root {
|
||||||
|
--docusaurus-announcement-bar-height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.announcementBarClose {
|
.announcementBarClose {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {createContext} from 'react';
|
||||||
|
|
||||||
|
const AnnouncementBarContext = createContext({
|
||||||
|
isAnnouncementBarClosed: false,
|
||||||
|
closeAnnouncementBar: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AnnouncementBarContext;
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AnnouncementBarContext from '@theme/AnnouncementBarContext';
|
||||||
|
import useAnnouncementBar from '@theme/hooks/useAnnouncementBar';
|
||||||
|
|
||||||
|
function AnnouncementBarProvider(props) {
|
||||||
|
const {isAnnouncementBarClosed, closeAnnouncementBar} = useAnnouncementBar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnnouncementBarContext.Provider
|
||||||
|
value={{isAnnouncementBarClosed, closeAnnouncementBar}}>
|
||||||
|
{props.children}
|
||||||
|
</AnnouncementBarContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnnouncementBarProvider;
|
|
@ -8,8 +8,10 @@
|
||||||
import React, {useState, useCallback} from 'react';
|
import React, {useState, useCallback} from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import useAnnouncementBarContext from '@theme/hooks/useAnnouncementBarContext';
|
||||||
import useLockBodyScroll from '@theme/hooks/useLockBodyScroll';
|
import useLockBodyScroll from '@theme/hooks/useLockBodyScroll';
|
||||||
import useLogo from '@theme/hooks/useLogo';
|
import useLogo from '@theme/hooks/useLogo';
|
||||||
|
import useScrollPosition from '@theme/hooks/useScrollPosition';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||||
|
|
||||||
|
@ -134,6 +136,8 @@ function DocSidebar(props) {
|
||||||
isClient,
|
isClient,
|
||||||
} = useDocusaurusContext();
|
} = useDocusaurusContext();
|
||||||
const {logoLink, logoLinkProps, logoImageUrl, logoAlt} = useLogo();
|
const {logoLink, logoLinkProps, logoImageUrl, logoAlt} = useLogo();
|
||||||
|
const {isAnnouncementBarClosed} = useAnnouncementBarContext();
|
||||||
|
const {scrollY} = useScrollPosition();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
docsSidebars,
|
docsSidebars,
|
||||||
|
@ -163,7 +167,10 @@ function DocSidebar(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.sidebar}>
|
<div
|
||||||
|
className={classnames(styles.sidebar, {
|
||||||
|
[styles.sidebarWithHideableNavbar]: hideOnScroll,
|
||||||
|
})}>
|
||||||
{hideOnScroll && (
|
{hideOnScroll && (
|
||||||
<Link
|
<Link
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
|
@ -179,6 +186,8 @@ function DocSidebar(props) {
|
||||||
<div
|
<div
|
||||||
className={classnames('menu', 'menu--responsive', styles.menu, {
|
className={classnames('menu', 'menu--responsive', styles.menu, {
|
||||||
'menu--show': showResponsiveSidebar,
|
'menu--show': showResponsiveSidebar,
|
||||||
|
[styles.menuWithAnnouncementBar]:
|
||||||
|
!isAnnouncementBarClosed && scrollY === 0,
|
||||||
})}>
|
})}>
|
||||||
<button
|
<button
|
||||||
aria-label={showResponsiveSidebar ? 'Close Menu' : 'Open Menu'}
|
aria-label={showResponsiveSidebar ? 'Close Menu' : 'Open Menu'}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
@media (min-width: 997px) {
|
@media (min-width: 997px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
@ -14,6 +16,10 @@
|
||||||
padding-top: var(--ifm-navbar-height);
|
padding-top: var(--ifm-navbar-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarWithHideableNavbar {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar::-webkit-scrollbar {
|
.sidebar::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
}
|
}
|
||||||
|
@ -35,10 +41,9 @@
|
||||||
.sidebarLogo {
|
.sidebarLogo {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
margin: 0 var(--ifm-navbar-padding-horizontal);
|
margin: 0 var(--ifm-navbar-padding-horizontal);
|
||||||
height: var(--ifm-navbar-height);
|
min-height: var(--ifm-navbar-height);
|
||||||
|
max-height: var(--ifm-navbar-height);
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
@ -49,8 +54,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
flex-grow: 1;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menuWithAnnouncementBar {
|
||||||
|
margin-bottom: var(--docusaurus-announcement-bar-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarLogo {
|
.sidebarLogo {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
|
|
||||||
import ThemeProvider from '@theme/ThemeProvider';
|
import ThemeProvider from '@theme/ThemeProvider';
|
||||||
import TabGroupChoiceProvider from '@theme/TabGroupChoiceProvider';
|
import TabGroupChoiceProvider from '@theme/TabGroupChoiceProvider';
|
||||||
|
import AnnouncementBarProvider from '@theme/AnnouncementBarProvider';
|
||||||
import AnnouncementBar from '@theme/AnnouncementBar';
|
import AnnouncementBar from '@theme/AnnouncementBar';
|
||||||
import Navbar from '@theme/Navbar';
|
import Navbar from '@theme/Navbar';
|
||||||
import Footer from '@theme/Footer';
|
import Footer from '@theme/Footer';
|
||||||
|
@ -50,6 +51,7 @@ function Layout(props) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<TabGroupChoiceProvider>
|
<TabGroupChoiceProvider>
|
||||||
|
<AnnouncementBarProvider>
|
||||||
<Head>
|
<Head>
|
||||||
{/* TODO: Do not assume that it is in english language */}
|
{/* TODO: Do not assume that it is in english language */}
|
||||||
<html lang="en" />
|
<html lang="en" />
|
||||||
|
@ -70,7 +72,10 @@ function Layout(props) {
|
||||||
<meta property="twitter:image" content={metaImageUrl} />
|
<meta property="twitter:image" content={metaImageUrl} />
|
||||||
)}
|
)}
|
||||||
{metaImage && (
|
{metaImage && (
|
||||||
<meta name="twitter:image:alt" content={`Image for ${metaTitle}`} />
|
<meta
|
||||||
|
name="twitter:image:alt"
|
||||||
|
content={`Image for ${metaTitle}`}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{permalink && (
|
{permalink && (
|
||||||
<meta property="og:url" content={siteUrl + permalink} />
|
<meta property="og:url" content={siteUrl + permalink} />
|
||||||
|
@ -82,6 +87,7 @@ function Layout(props) {
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="main-wrapper">{children}</div>
|
<div className="main-wrapper">{children}</div>
|
||||||
{!noFooter && <Footer />}
|
{!noFooter && <Footer />}
|
||||||
|
</AnnouncementBarProvider>
|
||||||
</TabGroupChoiceProvider>
|
</TabGroupChoiceProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {useState, useEffect} from 'react';
|
||||||
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
|
||||||
|
const STORAGE_DISMISS_KEY = 'docusaurus.announcement.dismiss';
|
||||||
|
const STORAGE_ID_KEY = 'docusaurus.announcement.id';
|
||||||
|
|
||||||
|
const useAnnouncementBar = () => {
|
||||||
|
const {
|
||||||
|
siteConfig: {
|
||||||
|
themeConfig: {
|
||||||
|
announcementBar: {id},
|
||||||
|
},
|
||||||
|
} = {},
|
||||||
|
} = useDocusaurusContext();
|
||||||
|
const [isClosed, setClosed] = useState(true);
|
||||||
|
const handleClose = () => {
|
||||||
|
localStorage.setItem(STORAGE_DISMISS_KEY, true);
|
||||||
|
setClosed(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const viewedId = localStorage.getItem(STORAGE_ID_KEY);
|
||||||
|
const isNewAnnouncement = id !== viewedId;
|
||||||
|
|
||||||
|
localStorage.setItem(STORAGE_ID_KEY, id);
|
||||||
|
|
||||||
|
if (isNewAnnouncement) {
|
||||||
|
localStorage.setItem(STORAGE_DISMISS_KEY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isNewAnnouncement ||
|
||||||
|
localStorage.getItem(STORAGE_DISMISS_KEY) === 'false'
|
||||||
|
) {
|
||||||
|
setClosed(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAnnouncementBarClosed: isClosed,
|
||||||
|
closeAnnouncementBar: handleClose,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAnnouncementBar;
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {useContext} from 'react';
|
||||||
|
import AnnouncementBarContext from '@theme/AnnouncementBarContext';
|
||||||
|
|
||||||
|
function useAnnouncementBarContext() {
|
||||||
|
return useContext(AnnouncementBarContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useAnnouncementBarContext;
|
Loading…
Add table
Reference in a new issue