diff options
Diffstat (limited to 'src/components/ActiveSectionProvider.tsx')
-rw-r--r-- | src/components/ActiveSectionProvider.tsx | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/src/components/ActiveSectionProvider.tsx b/src/components/ActiveSectionProvider.tsx new file mode 100644 index 0000000..022dbad --- /dev/null +++ b/src/components/ActiveSectionProvider.tsx | |||
@@ -0,0 +1,67 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 Kristóf Marussy | ||
3 | * | ||
4 | * SPDX-License-Identifier: MIT | ||
5 | */ | ||
6 | |||
7 | import { useLocation } from '@docusaurus/router'; | ||
8 | import { | ||
9 | type ReactNode, | ||
10 | createContext, | ||
11 | useCallback, | ||
12 | useContext, | ||
13 | useRef, | ||
14 | useState, | ||
15 | } from 'react'; | ||
16 | |||
17 | export interface ActiveSection { | ||
18 | hash: string | undefined; | ||
19 | setHash: (hash: string | undefined) => void; | ||
20 | } | ||
21 | |||
22 | const ActiveSectionContext = createContext<ActiveSection | undefined>( | ||
23 | undefined, | ||
24 | ); | ||
25 | |||
26 | export function useActiveSection(): ActiveSection { | ||
27 | const value = useContext(ActiveSectionContext); | ||
28 | if (value === undefined) { | ||
29 | throw new Error( | ||
30 | 'useActiveSection is only valid inside ActiveSectionProvider', | ||
31 | ); | ||
32 | } | ||
33 | return value; | ||
34 | } | ||
35 | |||
36 | export default function ActiveSectionProvider({ | ||
37 | children, | ||
38 | }: { | ||
39 | children: ReactNode; | ||
40 | }) { | ||
41 | const location = useLocation(); | ||
42 | const [hash, setHashState] = useState<string | undefined>(); | ||
43 | const lastHashRef = useRef<string | undefined>(); | ||
44 | const setHash = useCallback( | ||
45 | (hash: string | undefined) => { | ||
46 | if (hash === lastHashRef.current) { | ||
47 | return; | ||
48 | } | ||
49 | // Passing `undefined` to `hash` will give contraol pack to react-router. | ||
50 | const newURL = `${location.pathname}${location.search}${ | ||
51 | hash ?? location.hash | ||
52 | }`; | ||
53 | // Update the history entry without informing react-router so Docusaurus | ||
54 | // doesn't scroll to the top of the element. See | ||
55 | // https://github.com/facebook/docusaurus/blob/ca3dba5e5eab0f8b8a9b3388e2b2e637fe3a19a7/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx#L30-L39 | ||
56 | window.history.replaceState(null, null, newURL); | ||
57 | setHashState(hash); | ||
58 | lastHashRef.current = hash; | ||
59 | }, | ||
60 | [location, setHashState], | ||
61 | ); | ||
62 | return ( | ||
63 | <ActiveSectionContext.Provider value={{ hash, setHash }}> | ||
64 | {children} | ||
65 | </ActiveSectionContext.Provider> | ||
66 | ); | ||
67 | } | ||