Skip to content

Commit 6cb18fb

Browse files
committed
Hide top nav on reading docs on mobile
The top navigation disappears when you scroll down and reappears when you scroll up. Like browsers, and many content services like Medium, This is a necessary feature for mobile readers with a small viewport. Also made some code changes here to add memoization, because the scroll event is highly frequant. `React.useState` preserves the reference of the set dispatcher, but doesn't for the surrounding tuple. Adding a few memo, most children, except for components that are cheap to render, are not re-rendered. Also no deep re-render, the appearance state is propagated via old good CSS cascading rather than React state.
1 parent 3987e81 commit 6cb18fb

14 files changed

+69
-39
lines changed

src/Blog.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ let default = (props: props): React.element => {
289289
</>
290290
}
291291

292-
let overlayState = React.useState(() => false)
292+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
293293
let title = "Blog | ReScript Documentation"
294294

295295
<>
@@ -298,7 +298,7 @@ let default = (props: props): React.element => {
298298
/>
299299
<div className="mt-16 pt-2">
300300
<div className="text-gray-80 text-18">
301-
<Navigation overlayState />
301+
<Navigation isOverlayOpen setOverlayOpen />
302302
<div className="flex justify-center overflow-hidden">
303303
<main className="min-w-320 lg:align-center w-full lg:px-0 max-w-1280 pb-48">
304304
<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/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
}

src/components/Navigation.res

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,9 @@ module MobileNav = {
385385
/*
386386
<li className=base>
387387
<Link href="/community" className={linkOrActiveLink(~target="/community", ~route)}>
388-
388+
389389
{React.string("Community")}
390-
390+
391391
</Link>
392392
</li>
393393
*/
@@ -413,7 +413,7 @@ module MobileNav = {
413413

414414
/* isOverlayOpen: if the mobile overlay is toggled open */
415415
@react.component
416-
let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => {
416+
let make = (~fixed=true, ~isOverlayOpen: bool, ~setOverlayOpen: (bool => bool) => unit) => {
417417
let minWidth = "20rem"
418418
let router = Next.Router.useRouter()
419419
let route = router.route
@@ -443,8 +443,6 @@ let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => {
443443

444444
let isSubnavOpen = Js.Array2.find(collapsibles, c => c.state !== Closed) !== None
445445

446-
let (isOverlayOpen, setOverlayOpen) = overlayState
447-
448446
let toggleOverlay = () => setOverlayOpen(prev => !prev)
449447

450448
let resetCollapsibles = () =>
@@ -518,7 +516,7 @@ let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => {
518516
ref={ReactDOM.Ref.domRef(navRef)}
519517
id="header"
520518
style={ReactDOMStyle.make(~minWidth, ())}
521-
className={fixedNav ++ " items-center z-50 px-4 flex xs:justify-center w-full h-16 bg-gray-90 shadow text-white-80 text-14"}>
519+
className={fixedNav ++ " items-center z-50 px-4 flex xs:justify-center w-full h-16 bg-gray-90 shadow text-white-80 text-14 transition duration-300 ease-out group-[.nav-disappear]:-translate-y-16 md:group-[.nav-disappear]:transform-none"}>
522520
<div className="flex justify-between items-center h-full w-full max-w-1280">
523521
<div className="h-8 w-8 lg:h-10 lg:w-32">
524522
<a
@@ -609,3 +607,5 @@ let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => {
609607
/>
610608
</>
611609
}
610+
611+
let make = React.memo(make)

src/components/Navigation.resi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
@react.component
2-
let make: (~fixed: bool=?, ~overlayState: (bool, (bool => bool) => unit)) => React.element
2+
let make: (
3+
~fixed: bool=?,
4+
~isOverlayOpen: bool,
5+
~setOverlayOpen: (bool => bool) => unit,
6+
) => React.element

src/layouts/LandingPageLayout.res

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ module QuickInstall = {
191191
// and in the next tick, add the opacity-100 class, so the transition animation actually takes place.
192192
// If we don't do that, the banner will essentially pop up without any animation
193193
let bannerEl = Document.createElement("div")
194-
bannerEl->Element.setClassName("foobar opacity-0 absolute top-0 mt-4 -mr-1 px-2 rounded right-0
194+
bannerEl->Element.setClassName("foobar opacity-0 absolute top-0 mt-4 -mr-1 px-2 rounded right-0
195195
bg-turtle text-gray-80-tr body-sm
196196
transition-all duration-500 ease-in-out ")
197197
let textNode = Document.createTextNode("Copied!")
@@ -694,7 +694,7 @@ module Sponsors = {
694694

695695
@react.component
696696
let make = (~components=MarkdownComponents.default, ~children) => {
697-
let overlayState = React.useState(() => false)
697+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
698698

699699
<>
700700
<Meta
@@ -705,7 +705,7 @@ let make = (~components=MarkdownComponents.default, ~children) => {
705705
/>
706706
<div className="mt-4 xs:mt-16">
707707
<div className="text-gray-80 text-18 z">
708-
<Navigation overlayState />
708+
<Navigation isOverlayOpen setOverlayOpen />
709709
<div className="absolute w-full top-16">
710710
// Delete this again, when ReScript 11.1 is out for some time.
711711
<Banner>

src/layouts/MainLayout.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
@react.component
22
let make = (~components=MarkdownComponents.default, ~children) => {
3-
let overlayState = React.useState(() => false)
3+
let (isOverlayOpen, setOverlayOpen) = React.useState(() => false)
44

55
<>
66
<div className="mt-4 xs:mt-16">
77
<div className="text-gray-80">
8-
<Navigation overlayState />
8+
<Navigation isOverlayOpen setOverlayOpen />
99
<div className="flex xs:justify-center overflow-hidden pb-48">
1010
<main className="mt-16 min-w-320 lg:align-center w-full px-4 md:px-8 max-w-1280 ">
1111
<MdxProvider components> children </MdxProvider>

src/layouts/SidebarLayout.res

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ module MobileDrawerButton = {
206206
</button>
207207
}
208208

209+
type scrollDir =
210+
| Up({scrollY: int})
211+
| Down({scrollY: int})
212+
209213
@react.component
210214
let make = (
211215
~metaTitle: string,
@@ -221,6 +225,8 @@ let make = (
221225
~children,
222226
) => {
223227
let (isNavOpen, setNavOpen) = React.useState(() => false)
228+
let (_, startScrollEventTransition) = React.useTransition()
229+
let (scrollDir, setScrollDir) = React.useState(() => Up({scrollY: %raw(`Infinity`)}))
224230
let router = Next.Router.useRouter()
225231
let version = Url.parse(router.route).version
226232

@@ -254,6 +260,30 @@ let make = (
254260
)
255261
}, [])
256262

263+
React.useEffect(() => {
264+
let onScroll = _e => {
265+
startScrollEventTransition(() => {
266+
setScrollDir(
267+
prev => {
268+
let Up({scrollY}) | Down({scrollY}) = prev
269+
if scrollY === 0 || scrollY > Webapi.Window.scrollY {
270+
Up({scrollY: Webapi.Window.scrollY})
271+
} else {
272+
Down({scrollY: Webapi.Window.scrollY})
273+
}
274+
},
275+
)
276+
})
277+
}
278+
Webapi.Window.addEventListener("scroll", onScroll)
279+
Some(() => Webapi.Window.removeEventListener("scroll", onScroll))
280+
}, [])
281+
282+
let handleDrawerButtonClick = React.useCallback(evt => {
283+
ReactEvent.Mouse.preventDefault(evt)
284+
toggleSidebar()
285+
}, [])
286+
257287
let editLinkEl = switch editHref {
258288
| Some(href) =>
259289
<a href className="inline text-14 hover:underline text-fire" rel="noopener noreferrer">
@@ -297,25 +327,24 @@ let make = (
297327
| None => React.null
298328
}
299329

330+
let navAppearanceCascading = switch scrollDir {
331+
| Up(_) => "nav-appear"
332+
| Down(_) => "nav-disappear"
333+
}
334+
300335
<>
301336
<Meta title=metaTitle version />
302-
<div className={"mt-16 min-w-320 " ++ theme}>
337+
<div className={"mt-16 min-w-320 " ++ theme ++ " group " ++ navAppearanceCascading}>
303338
<div className="w-full">
304-
<Navigation overlayState=(isNavOpen, setNavOpen) />
339+
<Navigation isOverlayOpen=isNavOpen setOverlayOpen=setNavOpen />
305340
<div className="flex lg:justify-center">
306341
<div className="flex w-full max-w-1280 md:mx-8">
307342
sidebar
308343
<main className="px-4 w-full pt-16 md:ml-12 lg:mr-8 mb-32 md:max-w-576 lg:max-w-740">
309344
//width of the right content part
310345
<div
311-
className="z-10 fixed border-b shadow top-16 left-0 pl-4 bg-white w-full py-4 md:relative md:border-none md:shadow-none md:p-0 md:top-auto flex items-center">
312-
<MobileDrawerButton
313-
hidden=isNavOpen
314-
onClick={evt => {
315-
ReactEvent.Mouse.preventDefault(evt)
316-
toggleSidebar()
317-
}}
318-
/>
346+
className={"z-10 fixed border-b shadow top-16 left-0 pl-4 bg-white w-full py-4 md:relative md:border-none md:shadow-none md:p-0 md:top-auto flex items-center transition duration-300 ease-out group-[.nav-disappear]:-translate-y-32 md:group-[.nav-disappear]:transform-none"}>
347+
<MobileDrawerButton hidden=isNavOpen onClick={handleDrawerButtonClick} />
319348
<div
320349
className="truncate overflow-x-auto touch-scroll flex items-center space-x-4 md:justify-between mr-4 w-full">
321350
breadcrumbs

0 commit comments

Comments
 (0)