fix(v2): Fix announcementBar layout shifts (#5040)

* Fix announcementBar layout shift

* useAnnouncementBar should return correct state after hydration

* refactor announcementBar => move utils to theme-common

* restore previous announcementBar

* typo
This commit is contained in:
Sébastien Lorber 2021-06-24 11:35:35 +02:00 committed by GitHub
parent 814455f88e
commit 9916a0b4a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 179 additions and 110 deletions

View file

@ -0,0 +1,115 @@
/**
* 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, {
useState,
useEffect,
useCallback,
useMemo,
ReactNode,
useContext,
createContext,
} from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {createStorageSlot} from './storageUtils';
import {useThemeConfig} from './useThemeConfig';
export const AnnouncementBarDismissStorageKey =
'docusaurus.announcement.dismiss';
const AnnouncementBarIdStorageKey = 'docusaurus.announcement.id';
const AnnouncementBarDismissStorage = createStorageSlot(
AnnouncementBarDismissStorageKey,
);
const IdStorage = createStorageSlot(AnnouncementBarIdStorageKey);
const isDismissedInStorage = () =>
AnnouncementBarDismissStorage.get() === 'true';
const setDismissedInStorage = (bool: boolean) =>
AnnouncementBarDismissStorage.set(String(bool));
type AnnouncementBarAPI = {
readonly isClosed: boolean;
readonly close: () => void;
};
const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
const {announcementBar} = useThemeConfig();
const {isClient} = useDocusaurusContext();
const [isClosed, setClosed] = useState(() => {
return isClient
? // On client navigation: init with localstorage value
isDismissedInStorage()
: // On server/hydration: always visible to prevent layout shifts (will be hidden with css if needed)
false;
});
// Update state after hydration
useEffect(() => {
setClosed(isDismissedInStorage());
}, []);
const handleClose = useCallback(() => {
setDismissedInStorage(true);
setClosed(true);
}, []);
useEffect(() => {
if (!announcementBar) {
return;
}
const {id} = announcementBar;
let viewedId = IdStorage.get();
// retrocompatibility due to spelling mistake of default id
// see https://github.com/facebook/docusaurus/issues/3338
if (viewedId === 'annoucement-bar') {
viewedId = 'announcement-bar';
}
const isNewAnnouncement = id !== viewedId;
IdStorage.set(id);
if (isNewAnnouncement) {
setDismissedInStorage(false);
}
if (isNewAnnouncement || !isDismissedInStorage()) {
setClosed(false);
}
}, []);
return useMemo(() => {
return {
isClosed,
close: handleClose,
};
}, [isClosed]);
};
const AnnouncementBarContext = createContext<AnnouncementBarAPI | null>(null);
export const AnnouncementBarProvider = ({children}: {children: ReactNode}) => {
const value = useAnnouncementBarContextValue();
return (
<AnnouncementBarContext.Provider value={value}>
{children}
</AnnouncementBarContext.Provider>
);
};
export const useAnnouncementBar = (): AnnouncementBarAPI => {
const api = useContext(AnnouncementBarContext);
if (!api) {
throw new Error(
'useAnnouncementBar(): AnnouncementBar not found in React context: make sure to use the AnnouncementBarProvider on top of the tree',
);
}
return api;
};