Skip to content

Commit 9dcf138

Browse files
Merge pull request #856 from cometkim/scroll-nav
Hide top nav on reading docs on mobile
2 parents 961ec72 + 4e6f60c commit 9dcf138

17 files changed

+164
-136
lines changed

src/Blog.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ let default = (props: props): React.element => {
267267
</>
268268
}
269269

270-
let overlayState = React.useState(() => false)
270+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
271271
let title = "Blog | ReScript Documentation"
272272

273273
<>
@@ -276,7 +276,7 @@ let default = (props: props): React.element => {
276276
/>
277277
<div className="mt-16 pt-2">
278278
<div className="text-gray-80 text-18">
279-
<Navigation overlayState />
279+
<Navigation isOverlayOpen setOverlayOpen />
280280
<div className="flex justify-center overflow-hidden">
281281
<main className="min-w-320 lg:align-center w-full lg:px-0 max-w-1280 pb-48">
282282
<MdxProvider components=MarkdownComponents.default>

src/Packages.res

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,8 @@ type state =
328328

329329
let scrollToTop: unit => unit = %raw(`function() {
330330
window.scroll({
331-
top: 0,
332-
left: 0,
331+
top: 0,
332+
left: 0,
333333
behavior: 'smooth'
334334
});
335335
}
@@ -462,7 +462,7 @@ let default = (props: props) => {
462462
None
463463
}, [state])
464464

465-
let overlayState = React.useState(() => false)
465+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
466466
<>
467467
<Meta
468468
siteName="ReScript Packages"
@@ -471,7 +471,7 @@ let default = (props: props) => {
471471
/>
472472
<div className="mt-16 pt-2">
473473
<div className="text-gray-80 text-18">
474-
<Navigation overlayState />
474+
<Navigation isOverlayOpen setOverlayOpen />
475475
<div className="flex overflow-hidden">
476476
<div
477477
className="flex justify-between min-w-320 px-4 pt-16 lg:align-center w-full lg:px-8 pb-48">

src/SyntaxLookup.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ let default = (props: props) => {
336336
onSearchValueChange("")
337337
}
338338

339-
let overlayState = React.useState(() => false)
339+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
340340
let title = "Syntax Lookup | ReScript Documentation"
341341

342342
let content =
@@ -372,7 +372,7 @@ let default = (props: props) => {
372372
/>
373373
<div className="mt-4 xs:mt-16">
374374
<div className="text-gray-80">
375-
<Navigation overlayState />
375+
<Navigation isOverlayOpen setOverlayOpen />
376376
<div className="flex xs:justify-center overflow-hidden pb-48">
377377
<main className="mt-16 min-w-320 lg:align-center w-full px-4 md:px-8 max-w-1280">
378378
<MdxProvider components=MarkdownComponents.default>

src/Try.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
type props = {versions: array<string>}
22

33
let default = props => {
4-
let overlayState = React.useState(() => false)
4+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
55

66
let lazyPlayground = Next.Dynamic.dynamic(
77
async () => await Js.import(Playground.make),
@@ -20,7 +20,7 @@ let default = props => {
2020
</Next.Head>
2121
<div className="text-16">
2222
<div className="text-gray-40 text-14">
23-
<Navigation fixed=false overlayState />
23+
<Navigation fixed=false isOverlayOpen setOverlayOpen />
2424
playground
2525
</div>
2626
</div>

src/bindings/Next.res

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ module Link = {
6363
~children: React.element,
6464
~className: string=?,
6565
~target: string=?,
66-
~hrefRel: string=?,
6766
) => React.element = "default"
6867
}
6968

src/bindings/Next.resi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ module Link: {
6363
~children: React.element,
6464
~className: string=?,
6565
~target: string=?,
66-
~hrefRel: string=?,
6766
) => React.element
6867
}
6968

src/bindings/Webapi.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ module Window = {
3737
external removeEventListener: (string, 'a => unit) => unit = "removeEventListener"
3838
@scope("window") @val external innerWidth: int = "innerWidth"
3939
@scope("window") @val external innerHeight: int = "innerHeight"
40+
@scope("window") @val external scrollY: int = "scrollY"
4041
}
4142

4243
module Fetch = {

src/common/App.res

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -56,93 +56,106 @@ let make = (props: props): React.element => {
5656
// docs routes
5757
| {base: ["docs", "manual"], pagepath, version} =>
5858
// check if it's an api route
59-
switch Belt.Array.get(pagepath, 0) {
60-
| Some("api") =>
61-
switch version {
62-
| Latest =>
63-
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
64-
| (1, _) => <ApiOverviewLayout.Docs> content </ApiOverviewLayout.Docs>
59+
<EnableCollapsibleNavbar>
60+
{switch Belt.Array.get(pagepath, 0) {
61+
| Some("api") =>
62+
switch version {
63+
| Latest =>
64+
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
65+
| (1, _) => <ApiOverviewLayout.Docs> content </ApiOverviewLayout.Docs>
66+
| _ => content
67+
}
68+
| Version("v8.0.0") =>
69+
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
70+
| (1, _) => <ApiOverviewLayout8_0_0.Docs> content </ApiOverviewLayout8_0_0.Docs>
71+
| (2, Some("js")) => <JsDocsLayout8_0_0.Prose> content </JsDocsLayout8_0_0.Prose>
72+
| (2, Some("belt")) => <BeltDocsLayout8_0_0.Prose> content </BeltDocsLayout8_0_0.Prose>
73+
| (_, Some("js")) => <JsDocsLayout8_0_0.Docs> content </JsDocsLayout8_0_0.Docs>
74+
| (_, Some("belt")) => <BeltDocsLayout8_0_0.Docs> content </BeltDocsLayout8_0_0.Docs>
75+
| (_, Some("dom")) => <DomDocsLayout8_0_0.Docs> content </DomDocsLayout8_0_0.Docs>
76+
| _ => React.null
77+
}
78+
| Version("v9.0.0") =>
79+
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
80+
| (1, _) => <ApiOverviewLayout9_0_0.Docs> content </ApiOverviewLayout9_0_0.Docs>
81+
| (2, Some("js")) => <JsDocsLayout9_0_0.Prose> content </JsDocsLayout9_0_0.Prose>
82+
| (2, Some("belt")) => <BeltDocsLayout9_0_0.Prose> content </BeltDocsLayout9_0_0.Prose>
83+
| (_, Some("js")) => <JsDocsLayout9_0_0.Docs> content </JsDocsLayout9_0_0.Docs>
84+
| (_, Some("belt")) => <BeltDocsLayout9_0_0.Docs> content </BeltDocsLayout9_0_0.Docs>
85+
| (_, Some("dom")) => <DomDocsLayout9_0_0.Docs> content </DomDocsLayout9_0_0.Docs>
86+
| _ => React.null
87+
}
88+
| Version("v10.0.0") =>
89+
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
90+
| (1, _) => <ApiOverviewLayout10_0_0.Docs> content </ApiOverviewLayout10_0_0.Docs>
91+
| (2, Some("js")) => <JsDocsLayout10_0_0.Prose> content </JsDocsLayout10_0_0.Prose>
92+
| (2, Some("belt")) => <BeltDocsLayout10_0_0.Prose> content </BeltDocsLayout10_0_0.Prose>
93+
| (_, Some("js")) => <JsDocsLayout10_0_0.Docs> content </JsDocsLayout10_0_0.Docs>
94+
| (_, Some("belt")) => <BeltDocsLayout10_0_0.Docs> content </BeltDocsLayout10_0_0.Docs>
95+
| (_, Some("dom")) => <DomDocsLayout10_0_0.Docs> content </DomDocsLayout10_0_0.Docs>
96+
| _ => React.null
97+
}
6598
| _ => content
6699
}
67-
| Version("v8.0.0") =>
68-
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
69-
| (1, _) => <ApiOverviewLayout8_0_0.Docs> content </ApiOverviewLayout8_0_0.Docs>
70-
| (2, Some("js")) => <JsDocsLayout8_0_0.Prose> content </JsDocsLayout8_0_0.Prose>
71-
| (2, Some("belt")) => <BeltDocsLayout8_0_0.Prose> content </BeltDocsLayout8_0_0.Prose>
72-
| (_, Some("js")) => <JsDocsLayout8_0_0.Docs> content </JsDocsLayout8_0_0.Docs>
73-
| (_, Some("belt")) => <BeltDocsLayout8_0_0.Docs> content </BeltDocsLayout8_0_0.Docs>
74-
| (_, Some("dom")) => <DomDocsLayout8_0_0.Docs> content </DomDocsLayout8_0_0.Docs>
75-
| _ => React.null
76-
}
77-
| Version("v9.0.0") =>
78-
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
79-
| (1, _) => <ApiOverviewLayout9_0_0.Docs> content </ApiOverviewLayout9_0_0.Docs>
80-
| (2, Some("js")) => <JsDocsLayout9_0_0.Prose> content </JsDocsLayout9_0_0.Prose>
81-
| (2, Some("belt")) => <BeltDocsLayout9_0_0.Prose> content </BeltDocsLayout9_0_0.Prose>
82-
| (_, Some("js")) => <JsDocsLayout9_0_0.Docs> content </JsDocsLayout9_0_0.Docs>
83-
| (_, Some("belt")) => <BeltDocsLayout9_0_0.Docs> content </BeltDocsLayout9_0_0.Docs>
84-
| (_, Some("dom")) => <DomDocsLayout9_0_0.Docs> content </DomDocsLayout9_0_0.Docs>
100+
| _ =>
101+
switch version {
102+
| Latest =>
103+
<ManualDocsLayout.Latest frontmatter={component->frontmatter}>
104+
content
105+
</ManualDocsLayout.Latest>
106+
| Version("v8.0.0") =>
107+
<ManualDocsLayout.V800 frontmatter={component->frontmatter}>
108+
content
109+
</ManualDocsLayout.V800>
110+
| Version("v9.0.0") =>
111+
<ManualDocsLayout.V900 frontmatter={component->frontmatter}>
112+
content
113+
</ManualDocsLayout.V900>
114+
| Version("v10.0.0") =>
115+
<ManualDocsLayout.V1000 frontmatter={component->frontmatter}>
116+
content
117+
</ManualDocsLayout.V1000>
85118
| _ => React.null
86119
}
87-
| Version("v10.0.0") =>
88-
switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) {
89-
| (1, _) => <ApiOverviewLayout10_0_0.Docs> content </ApiOverviewLayout10_0_0.Docs>
90-
| (2, Some("js")) => <JsDocsLayout10_0_0.Prose> content </JsDocsLayout10_0_0.Prose>
91-
| (2, Some("belt")) => <BeltDocsLayout10_0_0.Prose> content </BeltDocsLayout10_0_0.Prose>
92-
| (_, Some("js")) => <JsDocsLayout10_0_0.Docs> content </JsDocsLayout10_0_0.Docs>
93-
| (_, Some("belt")) => <BeltDocsLayout10_0_0.Docs> content </BeltDocsLayout10_0_0.Docs>
94-
| (_, Some("dom")) => <DomDocsLayout10_0_0.Docs> content </DomDocsLayout10_0_0.Docs>
95-
| _ => React.null
96-
}
97-
| _ => content
98-
}
99-
| _ =>
100-
switch version {
120+
}}
121+
</EnableCollapsibleNavbar>
122+
| {base: ["docs", "react"], version} =>
123+
<EnableCollapsibleNavbar>
124+
{switch version {
101125
| Latest =>
102-
<ManualDocsLayout.Latest frontmatter={component->frontmatter}>
103-
content
104-
</ManualDocsLayout.Latest>
105-
| Version("v8.0.0") =>
106-
<ManualDocsLayout.V800 frontmatter={component->frontmatter}>
126+
<ReactDocsLayout.Latest frontmatter={component->frontmatter}>
107127
content
108-
</ManualDocsLayout.V800>
109-
| Version("v9.0.0") =>
110-
<ManualDocsLayout.V900 frontmatter={component->frontmatter}>
128+
</ReactDocsLayout.Latest>
129+
| Version("v0.10.0") =>
130+
<ReactDocsLayout.V0100 frontmatter={component->frontmatter}>
111131
content
112-
</ManualDocsLayout.V900>
113-
| Version("v10.0.0") =>
114-
<ManualDocsLayout.V1000 frontmatter={component->frontmatter}>
132+
</ReactDocsLayout.V0100>
133+
| Version("v0.11.0") =>
134+
<ReactDocsLayout.V0110 frontmatter={component->frontmatter}>
115135
content
116-
</ManualDocsLayout.V1000>
136+
</ReactDocsLayout.V0110>
117137
| _ => React.null
118-
}
119-
}
120-
| {base: ["docs", "react"], version} =>
121-
switch version {
122-
| Latest =>
123-
<ReactDocsLayout.Latest frontmatter={component->frontmatter}>
124-
content
125-
</ReactDocsLayout.Latest>
126-
| Version("v0.10.0") =>
127-
<ReactDocsLayout.V0100 frontmatter={component->frontmatter}> content </ReactDocsLayout.V0100>
128-
| Version("v0.11.0") =>
129-
<ReactDocsLayout.V0110 frontmatter={component->frontmatter}> content </ReactDocsLayout.V0110>
130-
| _ => React.null
131-
}
138+
}}
139+
</EnableCollapsibleNavbar>
132140
| {base: ["docs", "reason-compiler"], version: Latest} =>
133-
<ReasonCompilerDocsLayout> content </ReasonCompilerDocsLayout>
141+
<EnableCollapsibleNavbar>
142+
<ReasonCompilerDocsLayout> content </ReasonCompilerDocsLayout>
143+
</EnableCollapsibleNavbar>
134144
// common routes
135145
| {base} =>
136146
switch Belt.List.fromArray(base) {
137147
| list{"community", ..._rest} =>
138-
<CommunityLayout frontmatter={component->frontmatter}> content </CommunityLayout>
148+
<EnableCollapsibleNavbar>
149+
<CommunityLayout frontmatter={component->frontmatter}> content </CommunityLayout>
150+
</EnableCollapsibleNavbar>
139151
| list{"try"} => content
140152
| list{"blog"} => content // Blog implements its own layout as well
141-
| list{"syntax-lookup"} => content
142-
| list{"packages"} => content
143-
| list{"blog", ..._rest} => // Here, the layout will be handled by the Blog_Article component
153+
| list{"syntax-lookup"} => <EnableCollapsibleNavbar> content </EnableCollapsibleNavbar>
154+
| list{"packages"} => <EnableCollapsibleNavbar> content </EnableCollapsibleNavbar>
155+
| list{"blog", ..._rest} =>
156+
// Here, the layout will be handled by the Blog_Article component
144157
// to keep the frontmatter parsing etc in one place
145-
content
158+
<EnableCollapsibleNavbar> content </EnableCollapsibleNavbar>
146159
| _ =>
147160
let fm = component->frontmatter->DocFrontmatter.decode
148161
let title = switch url {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@react.component
2+
let make = (~children) => {
3+
let scrollDir = Hooks.useScrollDirection()
4+
5+
<div
6+
className={switch scrollDir {
7+
| Up(_) => "group nav-appear"
8+
| Down(_) => "group nav-disappear"
9+
}}>
10+
children
11+
</div>
12+
}

src/common/Hooks.res

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
/* Contains some generic hooks */
2-
%%raw("import React from 'react'")
3-
42
let useOutsideClick: (ReactDOM.Ref.t, unit => unit) => unit = %raw(`(outerRef, trigger) => {
53
function handleClickOutside(event) {
64
if (outerRef.current && !outerRef.current.contains(event.target)) {
@@ -16,33 +14,38 @@ let useOutsideClick: (ReactDOM.Ref.t, unit => unit) => unit = %raw(`(outerRef, t
1614
});
1715
}`)
1816

19-
let useWindowWidth: unit => option<int> = %raw(` () => {
20-
const isClient = typeof window === 'object';
17+
/** scrollDir is not memo-friendly.
18+
It must be used with pattern matching.
19+
Do not pass it directly to child components. */
20+
type scrollDir =
21+
| Up({scrollY: int})
22+
| Down({scrollY: int})
2123

22-
function getSize() {
23-
return {
24-
width: isClient ? window.innerWidth : undefined,
25-
height: isClient ? window.innerHeight : undefined
26-
};
27-
}
28-
29-
const [windowSize, setWindowSize] = React.useState(getSize);
24+
/**
25+
This will cause highly frequent events, so use it only once in a root as possible.
26+
And split the children components to prevent heavy ones from being re-rendered unnecessarily. */
27+
let useScrollDirection = () => {
28+
let (_, startScrollEventTransition) = React.useTransition()
29+
let (scrollDir, setScrollDir) = React.useState(() => Up({scrollY: %raw(`Infinity`)}))
3030

3131
React.useEffect(() => {
32-
if (!isClient) {
33-
return false;
32+
let onScroll = _e => {
33+
startScrollEventTransition(() => {
34+
setScrollDir(
35+
prev => {
36+
let Up({scrollY}) | Down({scrollY}) = prev
37+
if scrollY === 0 || scrollY > Webapi.Window.scrollY {
38+
Up({scrollY: Webapi.Window.scrollY})
39+
} else {
40+
Down({scrollY: Webapi.Window.scrollY})
41+
}
42+
},
43+
)
44+
})
3445
}
46+
Webapi.Window.addEventListener("scroll", onScroll)
47+
Some(() => Webapi.Window.removeEventListener("scroll", onScroll))
48+
}, [])
3549

36-
function handleResize() {
37-
setWindowSize(getSize());
38-
}
39-
40-
window.addEventListener('resize', handleResize);
41-
return () => window.removeEventListener('resize', handleResize);
42-
}, []); // Empty array ensures that effect is only run on mount and unmount
43-
44-
if(windowSize) {
45-
return windowSize.width;
46-
}
47-
return null;
48-
}`)
50+
scrollDir
51+
}

src/components/Footer.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@ let make = () => {
7474
</div>
7575
</footer>
7676
}
77+
78+
let make = React.memo(make)

src/components/Markdown.res

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,7 @@ module A = {
380380
| [pathname] => Js.String2.replaceByRe(pathname, regex, "")
381381
| _ => href
382382
}
383-
<Next.Link
384-
href
385-
hrefRel="noopener noreferrer"
386-
className="no-underline text-fire hover:underline"
387-
?target>
383+
<Next.Link href className="no-underline text-fire hover:underline" ?target>
388384
children
389385
</Next.Link>
390386
}

0 commit comments

Comments
 (0)