diff --git a/src/Blog.res b/src/Blog.res index f2c0c57d2..b98841a1b 100644 --- a/src/Blog.res +++ b/src/Blog.res @@ -289,7 +289,7 @@ let default = (props: props): React.element => { } - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) let title = "Blog | ReScript Documentation" <> @@ -298,7 +298,7 @@ let default = (props: props): React.element => { />
- +
diff --git a/src/Packages.res b/src/Packages.res index 3b2aa851c..73ed2352d 100644 --- a/src/Packages.res +++ b/src/Packages.res @@ -328,8 +328,8 @@ type state = let scrollToTop: unit => unit = %raw(`function() { window.scroll({ - top: 0, - left: 0, + top: 0, + left: 0, behavior: 'smooth' }); } @@ -462,7 +462,7 @@ let default = (props: props) => { None }, [state]) - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) <> { />
- +
diff --git a/src/SyntaxLookup.res b/src/SyntaxLookup.res index 3d729b8e0..6b5af7ea2 100644 --- a/src/SyntaxLookup.res +++ b/src/SyntaxLookup.res @@ -336,7 +336,7 @@ let default = (props: props) => { onSearchValueChange("") } - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) let title = "Syntax Lookup | ReScript Documentation" let content = @@ -372,7 +372,7 @@ let default = (props: props) => { />
- +
diff --git a/src/Try.res b/src/Try.res index 2562b92ac..b40e82442 100644 --- a/src/Try.res +++ b/src/Try.res @@ -1,7 +1,7 @@ type props = {versions: array} let default = props => { - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) let lazyPlayground = Next.Dynamic.dynamic( async () => await Js.import(Playground.make), @@ -20,7 +20,7 @@ let default = props => {
- + playground
diff --git a/src/bindings/Next.res b/src/bindings/Next.res index 0179bf8c1..c783c5197 100644 --- a/src/bindings/Next.res +++ b/src/bindings/Next.res @@ -63,7 +63,6 @@ module Link = { ~children: React.element, ~className: string=?, ~target: string=?, - ~hrefRel: string=?, ) => React.element = "default" } diff --git a/src/bindings/Next.resi b/src/bindings/Next.resi index e225919df..00583343d 100644 --- a/src/bindings/Next.resi +++ b/src/bindings/Next.resi @@ -63,7 +63,6 @@ module Link: { ~children: React.element, ~className: string=?, ~target: string=?, - ~hrefRel: string=?, ) => React.element } diff --git a/src/bindings/Webapi.res b/src/bindings/Webapi.res index 27c3ff07a..5520fad21 100644 --- a/src/bindings/Webapi.res +++ b/src/bindings/Webapi.res @@ -37,6 +37,7 @@ module Window = { external removeEventListener: (string, 'a => unit) => unit = "removeEventListener" @scope("window") @val external innerWidth: int = "innerWidth" @scope("window") @val external innerHeight: int = "innerHeight" + @scope("window") @val external scrollY: int = "scrollY" } module Fetch = { diff --git a/src/common/App.res b/src/common/App.res index 2dabf82f7..4962cd772 100644 --- a/src/common/App.res +++ b/src/common/App.res @@ -56,93 +56,106 @@ let make = (props: props): React.element => { // docs routes | {base: ["docs", "manual"], pagepath, version} => // check if it's an api route - switch Belt.Array.get(pagepath, 0) { - | Some("api") => - switch version { - | Latest => - switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { - | (1, _) => content + + {switch Belt.Array.get(pagepath, 0) { + | Some("api") => + switch version { + | Latest => + switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { + | (1, _) => content + | _ => content + } + | Version("v8.0.0") => + switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { + | (1, _) => content + | (2, Some("js")) => content + | (2, Some("belt")) => content + | (_, Some("js")) => content + | (_, Some("belt")) => content + | (_, Some("dom")) => content + | _ => React.null + } + | Version("v9.0.0") => + switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { + | (1, _) => content + | (2, Some("js")) => content + | (2, Some("belt")) => content + | (_, Some("js")) => content + | (_, Some("belt")) => content + | (_, Some("dom")) => content + | _ => React.null + } + | Version("v10.0.0") => + switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { + | (1, _) => content + | (2, Some("js")) => content + | (2, Some("belt")) => content + | (_, Some("js")) => content + | (_, Some("belt")) => content + | (_, Some("dom")) => content + | _ => React.null + } | _ => content } - | Version("v8.0.0") => - switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { - | (1, _) => content - | (2, Some("js")) => content - | (2, Some("belt")) => content - | (_, Some("js")) => content - | (_, Some("belt")) => content - | (_, Some("dom")) => content - | _ => React.null - } - | Version("v9.0.0") => - switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { - | (1, _) => content - | (2, Some("js")) => content - | (2, Some("belt")) => content - | (_, Some("js")) => content - | (_, Some("belt")) => content - | (_, Some("dom")) => content + | _ => + switch version { + | Latest => + frontmatter}> + content + + | Version("v8.0.0") => + frontmatter}> + content + + | Version("v9.0.0") => + frontmatter}> + content + + | Version("v10.0.0") => + frontmatter}> + content + | _ => React.null } - | Version("v10.0.0") => - switch (Belt.Array.length(pagepath), Belt.Array.get(pagepath, 1)) { - | (1, _) => content - | (2, Some("js")) => content - | (2, Some("belt")) => content - | (_, Some("js")) => content - | (_, Some("belt")) => content - | (_, Some("dom")) => content - | _ => React.null - } - | _ => content - } - | _ => - switch version { + }} + + | {base: ["docs", "react"], version} => + + {switch version { | Latest => - frontmatter}> - content - - | Version("v8.0.0") => - frontmatter}> + frontmatter}> content - - | Version("v9.0.0") => - frontmatter}> + + | Version("v0.10.0") => + frontmatter}> content - - | Version("v10.0.0") => - frontmatter}> + + | Version("v0.11.0") => + frontmatter}> content - + | _ => React.null - } - } - | {base: ["docs", "react"], version} => - switch version { - | Latest => - frontmatter}> - content - - | Version("v0.10.0") => - frontmatter}> content - | Version("v0.11.0") => - frontmatter}> content - | _ => React.null - } + }} + | {base: ["docs", "reason-compiler"], version: Latest} => - content + + content + // common routes | {base} => switch Belt.List.fromArray(base) { | list{"community", ..._rest} => - frontmatter}> content + + frontmatter}> content + | list{"try"} => content | list{"blog"} => content // Blog implements its own layout as well - | list{"syntax-lookup"} => content - | list{"packages"} => content - | list{"blog", ..._rest} => // Here, the layout will be handled by the Blog_Article component + | list{"syntax-lookup"} => content + | list{"packages"} => content + | list{"blog", ..._rest} => + // Here, the layout will be handled by the Blog_Article component // to keep the frontmatter parsing etc in one place - content + content | _ => let fm = component->frontmatter->DocFrontmatter.decode let title = switch url { diff --git a/src/common/EnableCollapsibleNavbar.res b/src/common/EnableCollapsibleNavbar.res new file mode 100644 index 000000000..f8473822b --- /dev/null +++ b/src/common/EnableCollapsibleNavbar.res @@ -0,0 +1,12 @@ +@react.component +let make = (~children) => { + let scrollDir = Hooks.useScrollDirection() + +
"group nav-appear" + | Down(_) => "group nav-disappear" + }}> + children +
+} diff --git a/src/common/Hooks.res b/src/common/Hooks.res index 7df6c7872..5617bf2aa 100644 --- a/src/common/Hooks.res +++ b/src/common/Hooks.res @@ -1,6 +1,4 @@ /* Contains some generic hooks */ -%%raw("import React from 'react'") - let useOutsideClick: (ReactDOM.Ref.t, unit => unit) => unit = %raw(`(outerRef, trigger) => { function handleClickOutside(event) { if (outerRef.current && !outerRef.current.contains(event.target)) { @@ -16,33 +14,38 @@ let useOutsideClick: (ReactDOM.Ref.t, unit => unit) => unit = %raw(`(outerRef, t }); }`) -let useWindowWidth: unit => option = %raw(` () => { - const isClient = typeof window === 'object'; +/** scrollDir is not memo-friendly. + It must be used with pattern matching. + Do not pass it directly to child components. */ +type scrollDir = + | Up({scrollY: int}) + | Down({scrollY: int}) - function getSize() { - return { - width: isClient ? window.innerWidth : undefined, - height: isClient ? window.innerHeight : undefined - }; - } - - const [windowSize, setWindowSize] = React.useState(getSize); +/** + This will cause highly frequent events, so use it only once in a root as possible. + And split the children components to prevent heavy ones from being re-rendered unnecessarily. */ +let useScrollDirection = () => { + let (_, startScrollEventTransition) = React.useTransition() + let (scrollDir, setScrollDir) = React.useState(() => Up({scrollY: %raw(`Infinity`)})) React.useEffect(() => { - if (!isClient) { - return false; + let onScroll = _e => { + startScrollEventTransition(() => { + setScrollDir( + prev => { + let Up({scrollY}) | Down({scrollY}) = prev + if scrollY === 0 || scrollY > Webapi.Window.scrollY { + Up({scrollY: Webapi.Window.scrollY}) + } else { + Down({scrollY: Webapi.Window.scrollY}) + } + }, + ) + }) } + Webapi.Window.addEventListener("scroll", onScroll) + Some(() => Webapi.Window.removeEventListener("scroll", onScroll)) + }, []) - function handleResize() { - setWindowSize(getSize()); - } - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); // Empty array ensures that effect is only run on mount and unmount - - if(windowSize) { - return windowSize.width; - } - return null; -}`) + scrollDir +} diff --git a/src/components/Footer.res b/src/components/Footer.res index e1d4799a2..5947747f1 100644 --- a/src/components/Footer.res +++ b/src/components/Footer.res @@ -74,3 +74,5 @@ let make = () => {
} + +let make = React.memo(make) diff --git a/src/components/Markdown.res b/src/components/Markdown.res index e5792f9e5..a7ca402b6 100644 --- a/src/components/Markdown.res +++ b/src/components/Markdown.res @@ -380,11 +380,7 @@ module A = { | [pathname] => Js.String2.replaceByRe(pathname, regex, "") | _ => href } - + children } diff --git a/src/components/Navigation.res b/src/components/Navigation.res index 02bbb42c2..7ff8dbb48 100644 --- a/src/components/Navigation.res +++ b/src/components/Navigation.res @@ -385,9 +385,9 @@ module MobileNav = { /*
  • - + {React.string("Community")} - +
  • */ @@ -413,7 +413,7 @@ module MobileNav = { /* isOverlayOpen: if the mobile overlay is toggled open */ @react.component -let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => { +let make = (~fixed=true, ~isOverlayOpen: bool, ~setOverlayOpen: (bool => bool) => unit) => { let minWidth = "20rem" let router = Next.Router.useRouter() let route = router.route @@ -443,8 +443,6 @@ let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => { let isSubnavOpen = Js.Array2.find(collapsibles, c => c.state !== Closed) !== None - let (isOverlayOpen, setOverlayOpen) = overlayState - let toggleOverlay = () => setOverlayOpen(prev => !prev) let resetCollapsibles = () => @@ -518,7 +516,7 @@ let make = (~fixed=true, ~overlayState: (bool, (bool => bool) => unit)) => { ref={ReactDOM.Ref.domRef(navRef)} id="header" style={ReactDOMStyle.make(~minWidth, ())} - 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"}> + 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"}>
    bool) => unit)) => { /> } + +let make = React.memo(make) diff --git a/src/components/Navigation.resi b/src/components/Navigation.resi index 411584c8b..4c1f8c476 100644 --- a/src/components/Navigation.resi +++ b/src/components/Navigation.resi @@ -1,2 +1,6 @@ @react.component -let make: (~fixed: bool=?, ~overlayState: (bool, (bool => bool) => unit)) => React.element +let make: ( + ~fixed: bool=?, + ~isOverlayOpen: bool, + ~setOverlayOpen: (bool => bool) => unit, +) => React.element diff --git a/src/layouts/LandingPageLayout.res b/src/layouts/LandingPageLayout.res index c2548b9f4..6292a9976 100644 --- a/src/layouts/LandingPageLayout.res +++ b/src/layouts/LandingPageLayout.res @@ -191,7 +191,7 @@ module QuickInstall = { // and in the next tick, add the opacity-100 class, so the transition animation actually takes place. // If we don't do that, the banner will essentially pop up without any animation let bannerEl = Document.createElement("div") - bannerEl->Element.setClassName("foobar opacity-0 absolute top-0 mt-4 -mr-1 px-2 rounded right-0 + bannerEl->Element.setClassName("foobar opacity-0 absolute top-0 mt-4 -mr-1 px-2 rounded right-0 bg-turtle text-gray-80-tr body-sm transition-all duration-500 ease-in-out ") let textNode = Document.createTextNode("Copied!") @@ -694,7 +694,7 @@ module Sponsors = { @react.component let make = (~components=MarkdownComponents.default, ~children) => { - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) <> { />
    - +
    // Delete this again, when ReScript 11.1 is out for some time. diff --git a/src/layouts/MainLayout.res b/src/layouts/MainLayout.res index dd3994a9a..83ad573f1 100644 --- a/src/layouts/MainLayout.res +++ b/src/layouts/MainLayout.res @@ -1,11 +1,11 @@ @react.component let make = (~components=MarkdownComponents.default, ~children) => { - let overlayState = React.useState(() => false) + let (isOverlayOpen, setOverlayOpen) = React.useState(() => false) <> -
    +
    - +
    children diff --git a/src/layouts/SidebarLayout.res b/src/layouts/SidebarLayout.res index f6e5665e2..8b38fc35d 100644 --- a/src/layouts/SidebarLayout.res +++ b/src/layouts/SidebarLayout.res @@ -254,6 +254,11 @@ let make = ( ) }, []) + let handleDrawerButtonClick = React.useCallback(evt => { + ReactEvent.Mouse.preventDefault(evt) + toggleSidebar() + }, []) + let editLinkEl = switch editHref { | Some(href) => @@ -301,21 +306,15 @@ let make = (
    - +
    sidebar
    //width of the right content part
    -