返回 marp
Desktop.tsx
根目录 / website / components / docs / layouts / Desktop.tsx
1 import {
2 CSSProperties,
3 useLayoutEffect,
4 useRef,
5 useState,
6 useMemo,
7 } from 'react'
8 import { Breadcrumb } from 'components/docs/Breadcrumb'
9 import { LayoutProps } from 'components/docs/Layout'
10 import { Navigation } from 'components/docs/Navigation'
11
12 export const Desktop: React.FC<LayoutProps> = ({
13 breadcrumbs,
14 children,
15 manifest,
16 slug,
17 }) => {
18 const sidebarRef = useRef<HTMLDivElement>(null)
19 const sidebarContentRef = useRef<HTMLDivElement>(null)
20 const headerHeightDetecterRef = useRef<HTMLDivElement>(null)
21
22 const [stickyGap, setStickyGap] = useState(0)
23 const [isScrollDown, setIsScrollDown] = useState(true)
24 const [sidebarScrollable, setSidebarScrollable] = useState(false)
25
26 useLayoutEffect(() => {
27 let previousScrollY = window.scrollY
28
29 const handleScroll = () => {
30 // Get header height
31 const headerHeight = headerHeightDetecterRef.current?.clientHeight || 80
32
33 // Detect whether sidebar is scrollable
34 const sidebarElm = sidebarRef.current
35 const sidebarRect = sidebarElm?.getBoundingClientRect()
36 const parentElm = sidebarElm?.parentElement
37 const parentRect = parentElm?.getBoundingClientRect()
38
39 if (sidebarContentRef.current && sidebarRect && parentRect) {
40 const viewHeight = Math.max(
41 0,
42 Math.min(window.innerHeight, parentRect.bottom) - headerHeight
43 )
44
45 setSidebarScrollable(
46 viewHeight < sidebarContentRef.current.clientHeight
47 )
48 }
49
50 // Handle scroll
51 const resetStickyGap = (base: 'top' | 'bottom') => {
52 if (sidebarRect && parentRect) {
53 setStickyGap(sidebarRect[base] - parentRect[base])
54 }
55 }
56
57 const { scrollY } = window
58 const scrollDelta = scrollY - previousScrollY
59
60 if (scrollDelta < 0) {
61 // Scroll up
62 setIsScrollDown((prev) => {
63 if (prev) resetStickyGap('bottom')
64 return false
65 })
66 } else {
67 // Scroll down
68 setIsScrollDown((prev) => {
69 if (!prev) resetStickyGap('top')
70 return true
71 })
72 }
73 previousScrollY = scrollY
74 }
75
76 window.addEventListener('scroll', handleScroll)
77 window.addEventListener('resize', handleScroll)
78
79 return () => {
80 window.removeEventListener('scroll', handleScroll)
81 window.removeEventListener('resize', handleScroll)
82 }
83 }, [])
84
85 const sidebarStyle = useMemo<CSSProperties>(() => {
86 if (!sidebarScrollable)
87 return { alignSelf: 'start', top: 'var(--header-height)' }
88
89 return {
90 alignSelf: isScrollDown ? 'end' : 'start',
91 top: !isScrollDown ? `var(--header-height)` : '',
92 bottom: isScrollDown ? `0px` : '',
93 marginBottom: stickyGap < 0 ? `${-stickyGap}px` : 0,
94 marginTop: stickyGap >= 0 ? `${stickyGap}px` : 0,
95 }
96 }, [isScrollDown, sidebarScrollable, stickyGap])
97
98 return (
99 <>
100 <div
101 ref={headerHeightDetecterRef}
102 className="fixed top-0 left-0 -z-10 h-[var(--header-height)] w-px opacity-0"
103 />
104 <div id="docs-container" className="text-sm xl:text-base">
105 <div ref={sidebarRef} id="docs-sidebar" style={sidebarStyle}>
106 <div ref={sidebarContentRef} className="sidebar-nav-content">
107 <Navigation manifest={manifest} slug={slug} />
108 </div>
109 </div>
110 <div className="my-6 w-px bg-gray-400" style={{ gridArea: 'border' }} />
111 <div className="[grid-area:contents]">
112 <div className="px-8 py-10">
113 {breadcrumbs?.length && (
114 <div className="mb-6 rounded bg-gray-300 p-2">
115 <Breadcrumb breadcrumbs={breadcrumbs} />
116 </div>
117 )}
118 <article id="docs-article" className="container">
119 {children}
120 </article>
121 </div>
122 </div>
123 </div>
124 {/* <div ref={docsClearfixRef} /> */}
125 <style jsx>{`
126 #docs-container {
127 @apply grid;
128
129 min-height: inherit;
130 grid-template:
131 'sidebar border contents' auto
132 / minmax(16rem, 20%) 1px minmax(0, 1fr);
133 }
134
135 #docs-sidebar {
136 @apply sticky [grid-area:sidebar];
137 }
138
139 #docs-article {
140 @apply mx-auto px-6;
141
142 --root-font-size: 0.9rem;
143 }
144
145 .sidebar-nav-content {
146 @apply mx-auto w-64 px-8 py-10;
147
148 min-width: 16rem;
149 }
150
151 @screen xl {
152 #docs-container {
153 grid-template:
154 'sidebar border contents nav' auto
155 / minmax(16rem, 20%) 1px minmax(0, 1fr) minmax(8rem, 15%);
156 }
157
158 #docs-article {
159 --root-font-size: 1rem;
160 }
161
162 .sidebar-nav-content {
163 @apply w-5/6;
164 }
165 }
166 `}</style>
167 </>
168 )
169 }
170
170 lines Plain Text