forked from nodejs/nodejs.dev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.tsx
More file actions
105 lines (95 loc) · 3.02 KB
/
layout.tsx
File metadata and controls
105 lines (95 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
import 'prismjs/themes/prism-okaidia.css';
import React, { useEffect, useRef } from 'react';
import Header from './header';
import './layout.css';
import './mobile.css';
import SEO from './seo';
import { isMobileScreen } from '../util/isScreenWithinWidth';
import { notifyWhenStickyHeadersChange } from '../util/notifyWhenStickyHeadersChange';
import { StickyChange, SentinelObserverSetupOptions } from '../types';
import {
addFocusOutlineListeners,
removeFocusOutlineListeners,
} from '../util/outlineOnKeyboardNav';
type Props = {
children: React.ReactNode;
title?: string;
description?: string;
img?: string;
};
const Layout = ({ children, title, description, img }: Props) => {
const prevOffset = useRef<number>(-1);
useEffect(() => {
if (window.document && 'documentElement' in window.document) {
addFocusOutlineListeners();
}
if ('IntersectionObserver' in window) {
setupObserver();
} else {
// Use polyfill for browsers without IntersectionObserver support
import('intersection-observer')
.then(setupObserver)
// Fallback for browsers without IntersectionObserver support
.catch(magicHeroNumber);
}
return cleanUp;
});
const setupObserver = (): void => {
const container: HTMLElement = document.querySelector(
'.side-nav'
) as HTMLElement;
const stickyElementsClassName: string = 'side-nav__title';
const root: HTMLElement | null = isMobileScreen() ? null : container;
const headerRootMargin: string = '-93px 0px 0px 0px';
const setupOptions: SentinelObserverSetupOptions = {
container,
stickyElementsClassName,
root,
headerRootMargin,
};
if (container) {
notifyWhenStickyHeadersChange(setupOptions);
}
document.addEventListener('stickychange', (({
detail,
}: CustomEvent<StickyChange>) => {
const { target, stuck }: StickyChange = detail;
// Update sticking header color
target.style.color = stuck ? '#fff' : '#000';
}) as EventListener);
};
const magicHeroNumber = (): void => {
if (typeof window === 'undefined') {
// Guard for SSR
return;
}
const doc: Document = window.document;
if (!doc.body.dataset.browser) {
doc.body.dataset.browser = 'legacy';
}
const scrollingElement: HTMLElement | null = doc.scrollingElement as HTMLElement;
if (scrollingElement) {
const offset: number = Math.min(scrollingElement.scrollTop - 62, 210);
if (Math.abs(prevOffset.current - offset) > 5) {
prevOffset.current = offset;
doc.body.setAttribute(
'style',
`--magic-hero-number: ${356 - offset}px`
);
}
}
window.requestAnimationFrame(magicHeroNumber);
};
const cleanUp = (): void => {
return removeFocusOutlineListeners();
};
return (
<>
<SEO title={title} description={description} img={img} />
<Header />
<main>{children}</main>
</>
);
};
export default Layout;