From 51dc3f899a1f38d4db37a48ca764bee2618f39a7 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 8 Feb 2026 14:09:29 -0500 Subject: [PATCH 01/36] refactor: refactoring navbars --- app/root.res | 2 +- app/routes.res | 14 ++++- app/routes/Guide.res | 90 ++++++++++++++++++++++++++ app/routes/Guides.res | 3 + app/routes/MdxRoute.res | 42 +------------ markdown-pages/guide/parsing-json.mdx | 91 +++++++++++++++++++++++++++ react-router.config.mjs | 3 +- src/Mdx.res | 42 +++++++++++++ src/components/BreadCrumbs.res | 21 +++++++ src/components/Guide_Utils.res | 2 + src/components/NavbarPrimary.res | 16 +++++ src/components/NavbarSecondary.res | 16 +++++ src/components/NavbarTertiary.res | 16 +++++ src/components/Sidebar.res | 21 +++++++ styles/main.css | 6 +- 15 files changed, 338 insertions(+), 47 deletions(-) create mode 100644 app/routes/Guide.res create mode 100644 app/routes/Guides.res create mode 100644 markdown-pages/guide/parsing-json.mdx create mode 100644 src/components/BreadCrumbs.res create mode 100644 src/components/Guide_Utils.res create mode 100644 src/components/NavbarPrimary.res create mode 100644 src/components/NavbarSecondary.res create mode 100644 src/components/NavbarTertiary.res create mode 100644 src/components/Sidebar.res diff --git a/app/root.res b/app/root.res index 255797d01..0da06aa15 100644 --- a/app/root.res +++ b/app/root.res @@ -68,7 +68,7 @@ let default = () => { - + // diff --git a/app/routes.res b/app/routes.res index fd039b386..090493245 100644 --- a/app/routes.res +++ b/app/routes.res @@ -28,6 +28,16 @@ let stdlibRoutes = let beltRoutes = beltPaths->Array.map(path => route(path, "./routes/ApiRoute.jsx", ~options={id: path})) +let guideRoutes = + mdxRoutes("./routes/Guide.jsx")->Array.filter(route => + route.path->Option.map(path => String.includes(path, "guide/"))->Option.getOr(false) + ) + +let mdxRoutes = + mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(route => + route.path->Option.map(path => !String.includes(path, "guide/"))->Option.getOr(true) + ) + let default = [ index("./routes/LandingPageRoute.jsx"), route("packages", "./routes/PackagesRoute.jsx"), @@ -42,6 +52,8 @@ let default = [ route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}), ...stdlibRoutes, ...beltRoutes, - ...mdxRoutes("./routes/MdxRoute.jsx"), + ...mdxRoutes, + route("guides", "./routes/Guides.jsx"), + ...guideRoutes, route("*", "./routes/NotFoundRoute.jsx"), ] diff --git a/app/routes/Guide.res b/app/routes/Guide.res new file mode 100644 index 000000000..ecfdb4d7c --- /dev/null +++ b/app/routes/Guide.res @@ -0,0 +1,90 @@ +open Guide_Utils + +// For some reason the MDX components have to be defined in the route file +%%private( + let components = { + // Replacing HTML defaults + "a": Markdown.A.make, + "blockquote": Markdown.Blockquote.make, + "code": Markdown.Code.make, + "h1": Markdown.H1.make, + "h2": Markdown.H2.make, + "h3": Markdown.H3.make, + "h4": Markdown.H4.make, + "h5": Markdown.H5.make, + "hr": Markdown.Hr.make, + "intro": Markdown.Intro.make, + "li": Markdown.Li.make, + "ol": Markdown.Ol.make, + "p": Markdown.P.make, + "pre": Markdown.Pre.make, + "strong": Markdown.Strong.make, + "table": Markdown.Table.make, + "th": Markdown.Th.make, + "thead": Markdown.Thead.make, + "td": Markdown.Td.make, + "ul": Markdown.Ul.make, + // These are custom components we provide + "Cite": Markdown.Cite.make, + "CodeTab": Markdown.CodeTab.make, + "Image": Markdown.Image.make, + "Info": Markdown.Info.make, + "Intro": Markdown.Intro.make, + "UrlBox": Markdown.UrlBox.make, + "Video": Markdown.Video.make, + "Warn": Markdown.Warn.make, + "CommunityContent": CommunityContent.make, + "WarningTable": WarningTable.make, + "Docson": DocsonLazy.make, + "Suspense": React.Suspense.make, + } +) + +type loaderData = {...Mdx.t, sidebarItems: array} + +let loader: ReactRouter.Loader.t = async ({request}) => { + let mdx = await Mdx.loadMdx(request, ~options={remarkPlugins: Mdx.plugins}) + let guidePages = await getGuidePages() + { + __raw: mdx.__raw, + attributes: mdx.attributes, + sidebarItems: guidePages->Array.map((page): Sidebar.item => { + { + slug: page.slug->Option.getOrThrow, + title: page.title, + } + }), + } +} + +let default = () => { + let loaderData: loaderData = ReactRouter.useLoaderData() + // let attributes = Mdx.useMdxAttributes() + let component = Mdx.useMdxComponent(~components) + + let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) + + let navbarClasses = switch scrollDirection { + | Up(_) => "translate-y-0" + | Down(_) => "-translate-y-full md:translate-y-0" + } + + let secondaryNavbarClasses = switch scrollDirection { + | Up(_) => "translate-y-[32]" + // TODO: this has to be full plus the 16 for the banner above + | Down(_) => "-translate-y-[128px] md:translate-y-[32]" + } + + <> + + + +
+ + +
+ +} diff --git a/app/routes/Guides.res b/app/routes/Guides.res new file mode 100644 index 000000000..a4b7b77e2 --- /dev/null +++ b/app/routes/Guides.res @@ -0,0 +1,3 @@ +let default = () => { +
{React.string("guides")}
+} diff --git a/app/routes/MdxRoute.res b/app/routes/MdxRoute.res index c3b495b7c..8dd0c2f70 100644 --- a/app/routes/MdxRoute.res +++ b/app/routes/MdxRoute.res @@ -12,46 +12,6 @@ type loaderData = { filePath: option, } -/** - This configures the MDX component to use our custom markdown components - */ -let components = { - // Replacing HTML defaults - "a": Markdown.A.make, - "blockquote": Markdown.Blockquote.make, - "code": Markdown.Code.make, - "h1": Markdown.H1.make, - "h2": Markdown.H2.make, - "h3": Markdown.H3.make, - "h4": Markdown.H4.make, - "h5": Markdown.H5.make, - "hr": Markdown.Hr.make, - "intro": Markdown.Intro.make, - "li": Markdown.Li.make, - "ol": Markdown.Ol.make, - "p": Markdown.P.make, - "pre": Markdown.Pre.make, - "strong": Markdown.Strong.make, - "table": Markdown.Table.make, - "th": Markdown.Th.make, - "thead": Markdown.Thead.make, - "td": Markdown.Td.make, - "ul": Markdown.Ul.make, - // These are custom components we provide - "Cite": Markdown.Cite.make, - "CodeTab": Markdown.CodeTab.make, - "Image": Markdown.Image.make, - "Info": Markdown.Info.make, - "Intro": Markdown.Intro.make, - "UrlBox": Markdown.UrlBox.make, - "Video": Markdown.Video.make, - "Warn": Markdown.Warn.make, - "CommunityContent": CommunityContent.make, - "WarningTable": WarningTable.make, - "Docson": DocsonLazy.make, - "Suspense": React.Suspense.make, -} - let convertToNavItems = (items, rootPath) => Array.map(items, (item): SidebarLayout.Sidebar.NavItem.t => { let href = switch item.slug { @@ -282,7 +242,7 @@ let loader: ReactRouter.Loader.t = async ({request}) => { let default = () => { let {pathname} = ReactRouter.useLocation() - let component = useMdxComponent(~components) + let component = useMdxComponent(~components=Mdx.components) let attributes = useMdxAttributes() let loaderData: loaderData = ReactRouter.useLoaderData() diff --git a/markdown-pages/guide/parsing-json.mdx b/markdown-pages/guide/parsing-json.mdx new file mode 100644 index 000000000..3b322237d --- /dev/null +++ b/markdown-pages/guide/parsing-json.mdx @@ -0,0 +1,91 @@ +--- +title: Parsing JSON +description: How to easily parse JSON using ReScript +--- + +# Parsing JSON 🚀✨ + +Welcome, brave developer! 🎉 You've stumbled upon the ULTIMATE guide to parsing JSON like an absolute legend! 💪 Get ready to transform those curly braces into pure, type-safe bliss! 🌈 + +## Why JSON? Why Now? Why Not XML? 🤔💭 + +Great question, friend! 🙌 JSON is literally everywhere - it's like the oxygen of the internet! Every API speaks it, every config loves it, and YOUR code deserves to handle it with grace and elegance! ✨ + +``` +// The JSON you'll encounter in the wild 🦁 +{ + "vibes": "immaculate", + "coffee_level": 9000, + "bugs": null // always null, obviously 😎 +} +``` + +## Step 1: Befriend Your JSON 🤝 + +First, you need to UNDERSTAND your JSON on a spiritual level! 🧘‍♀️ Look at it. Appreciate its structure. Thank it for its service! + +``` +// Define what your data WANTS to be 🦋 +type amazingData = { + vibes: string, + coffeeLevel: int, + bugs: option // we don't do those here 🚫🐛 +} +``` + +## Decoding: The Art of Data Transformation 🎨🔮 + +Now we get to the MAGIC! ✨ Parsing JSON is like being a translator between two worlds - the chaotic realm of untyped data and the peaceful kingdom of type safety! 👑 + +``` +// Pseudo-decode your data like a PRO 💯 +let parseTheThings = json => { + json + |> checkIfActuallyJSON // very important step 📋 + |> extractTheGoodies // get the good stuff 🍬 + |> makeItTypeSafe // safety first! 🦺 + |> celebrateSuccess // 🎊🎊🎊 +} +``` + +## Handling Errors Like a Gentle Giant 🦣💝 + +Sometimes JSON is... broken. 😢 That's okay! We handle errors with COMPASSION and GRACE! + +``` +// When things go wrong (they won't, but just in case) 🤞 +switch maybeParsed { +| Ok(data) => doHappyDance(data) 💃 +| Error(e) => breatheDeeply() |> tryAgain 🧘 +} +``` + +## Pro Tips From the JSON Whisperer 🐴👂 + +1. **Always validate first!** 🔍 Don't trust that external API - it's probably having a bad day +2. **Use descriptive types!** 📝 Future you will send flowers to present you +3. **Handle missing fields!** 🕳️ Not everything is guaranteed in this world (except bugs in production) + +## The Grand Finale: Putting It All Together 🎭🎪 + +``` +// Your final masterpiece 🖼️ +let parseUserFromAPI = rawJSON => { + rawJSON + |> validate // ✅ check + |> decode // 🔓 unlock + |> transform // 🦋 beautify + |> cache // 💾 remember + |> return // 🎁 deliver +} + +// Usage: literally this easy +let user = parseUserFromAPI(sketchyAPIResponse) +// Boom! Type-safe user! 🎆 +``` + +## Conclusion: You're Basically a JSON Ninja Now 🥷 + +Congratulations! 🎊 You've completed the journey from JSON newbie to JSON CHAMPION! 🏆 Go forth and parse with confidence! Remember: every curly brace is just a hug from your data! 🤗 + +Happy coding! 💻✨🚀🎉💪🌈 diff --git a/react-router.config.mjs b/react-router.config.mjs index ddb22da04..8e6eaff70 100644 --- a/react-router.config.mjs +++ b/react-router.config.mjs @@ -7,8 +7,9 @@ const mdx = init({ "markdown-pages/docs", "markdown-pages/community", "markdown-pages/syntax-lookup", + "markdown-pages/guide", ], - aliases: ["blog", "docs", "community", "syntax-lookup"], + aliases: ["blog", "docs", "community", "syntax-lookup", "guide"], }); const { stdlibPaths } = await import("./app/routes.jsx"); diff --git a/src/Mdx.res b/src/Mdx.res index cd6b388db..e10687c62 100644 --- a/src/Mdx.res +++ b/src/Mdx.res @@ -222,3 +222,45 @@ let anchorLinkPlugin = (tree, _vfile) => { let anchorLinkPlugin = makePlugin(_options => (tree, vfile) => anchorLinkPlugin(tree, vfile)) let plugins = [remarkLinkPlugin, gfm, remarkReScriptPreludePlugin, anchorLinkPlugin] + +/** + This configures the MDX component to use our custom markdown components + */ +let components = { + // Replacing HTML defaults + "a": Markdown.A.make, + "blockquote": Markdown.Blockquote.make, + "code": Markdown.Code.make, + "h1": Markdown.H1.make, + "h2": Markdown.H2.make, + "h3": Markdown.H3.make, + "h4": Markdown.H4.make, + "h5": Markdown.H5.make, + "hr": Markdown.Hr.make, + "intro": Markdown.Intro.make, + "li": Markdown.Li.make, + "ol": Markdown.Ol.make, + "p": Markdown.P.make, + "pre": Markdown.Pre.make, + "strong": Markdown.Strong.make, + "table": Markdown.Table.make, + "th": Markdown.Th.make, + "thead": Markdown.Thead.make, + "td": Markdown.Td.make, + "ul": Markdown.Ul.make, + // These are custom components we provide + "Cite": Markdown.Cite.make, + "CodeTab": Markdown.CodeTab.make, + "Image": Markdown.Image.make, + "Info": Markdown.Info.make, + "Intro": Markdown.Intro.make, + "UrlBox": Markdown.UrlBox.make, + "Video": Markdown.Video.make, + "Warn": Markdown.Warn.make, + "CommunityContent": CommunityContent.make, + "WarningTable": WarningTable.make, + "Docson": DocsonLazy.make, + "Suspense": React.Suspense.make, +} + +let useMdx = () => useMdxComponent(~components) diff --git a/src/components/BreadCrumbs.res b/src/components/BreadCrumbs.res new file mode 100644 index 000000000..4d1001488 --- /dev/null +++ b/src/components/BreadCrumbs.res @@ -0,0 +1,21 @@ +@react.component +let make = () => { + let {pathname} = ReactRouter.useLocation() + + let paths = (pathname :> string)->String.split("/")->Array.filter(path => path != "") + + let lastIndex = paths->Array.length - 1 + +
+ {paths + ->Array.mapWithIndex((path, i) => + <> + + {React.string(path->String.capitalize)} + + {i == lastIndex ? React.null : React.string(" / ")} + + ) + ->React.array} +
+} diff --git a/src/components/Guide_Utils.res b/src/components/Guide_Utils.res new file mode 100644 index 000000000..43e05bbe4 --- /dev/null +++ b/src/components/Guide_Utils.res @@ -0,0 +1,2 @@ +let getGuidePages = async () => + (await Mdx.allMdx(~filterByPaths=["markdown-pages/guide"]))->Mdx.filterMdxPages("guide") diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res new file mode 100644 index 000000000..f563d435a --- /dev/null +++ b/src/components/NavbarPrimary.res @@ -0,0 +1,16 @@ +@react.component +let make = () => { + let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) + + let navbarClasses = switch scrollDirection { + | Up(_) => "translate-y-0" + | Down(_) => "-translate-y-full lg:translate-y-0" + } + + +} diff --git a/src/components/NavbarSecondary.res b/src/components/NavbarSecondary.res new file mode 100644 index 000000000..1604fd89a --- /dev/null +++ b/src/components/NavbarSecondary.res @@ -0,0 +1,16 @@ +@react.component +let make = () => { + let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) + + let navbarClasses = switch scrollDirection { + | Up(_) => "translate-y-0" + | Down(_) => "-translate-y-[128px] lg:translate-y-0" + } + + +} diff --git a/src/components/NavbarTertiary.res b/src/components/NavbarTertiary.res new file mode 100644 index 000000000..da6869dba --- /dev/null +++ b/src/components/NavbarTertiary.res @@ -0,0 +1,16 @@ +@react.component +let make = () => { + let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) + + let navbarClasses = switch scrollDirection { + | Up(_) => "translate-y-0" + | Down(_) => "-translate-y-[192px] lg:translate-y-0" + } + + +} diff --git a/src/components/Sidebar.res b/src/components/Sidebar.res new file mode 100644 index 000000000..1334f6f35 --- /dev/null +++ b/src/components/Sidebar.res @@ -0,0 +1,21 @@ +type item = { + slug: string, + title: string, +} + +@react.component +let make = (~items) => { +
    + {items + ->Array.map(item => +
  • + + {React.string(item.title)} + +
  • + ) + ->React.array} +
+} diff --git a/styles/main.css b/styles/main.css index 814556b49..4a49e28f9 100644 --- a/styles/main.css +++ b/styles/main.css @@ -4,9 +4,9 @@ @import "tailwindcss"; -@source '../src/**/*.{mjs,js,res}'; -@source '../pages/**/*.{mjs,js,mdx}'; -@source '../app/**/*.{mjs,js,mdx}'; +@source '../src/**/*.{jsx,res}'; +@source '../pages/**/*.{jsx,mdx}'; +@source '../app/**/*.{jsx,mdx}'; @theme { --radius-*: initial; From be70e99d8910983f850928017c74477bfe6229e8 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 8 Feb 2026 15:56:25 -0500 Subject: [PATCH 02/36] making progress on the nav --- app/root.res | 3 +- app/routes/Guide.res | 25 +- src/components/NavbarPrimary.res | 2 +- src/components/NavbarSecondary.res | 35 +- src/components/NavbarTertiary.res | 5 +- src/components/Sidebar.res | 534 ++++++++++++++++++++++++++++- styles/main.css | 18 +- 7 files changed, 593 insertions(+), 29 deletions(-) diff --git a/app/root.res b/app/root.res index 0da06aa15..36f98671a 100644 --- a/app/root.res +++ b/app/root.res @@ -65,7 +65,8 @@ let default = () => { /> - + + // className={isScrollLockEnabled ? "overflow-hidden" : ""}> // diff --git a/app/routes/Guide.res b/app/routes/Guide.res index ecfdb4d7c..d47a4ae45 100644 --- a/app/routes/Guide.res +++ b/app/routes/Guide.res @@ -62,29 +62,16 @@ let default = () => { // let attributes = Mdx.useMdxAttributes() let component = Mdx.useMdxComponent(~components) - let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) - - let navbarClasses = switch scrollDirection { - | Up(_) => "translate-y-0" - | Down(_) => "-translate-y-full md:translate-y-0" - } - - let secondaryNavbarClasses = switch scrollDirection { - | Up(_) => "translate-y-[32]" - // TODO: this has to be full plus the 16 for the banner above - | Down(_) => "-translate-y-[128px] md:translate-y-[32]" - } - - <> +
- - -
+ {React.null} + // + } diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res index f563d435a..a82f063b2 100644 --- a/src/components/NavbarPrimary.res +++ b/src/components/NavbarPrimary.res @@ -9,7 +9,7 @@ let make = () => { diff --git a/src/components/NavbarSecondary.res b/src/components/NavbarSecondary.res index 1604fd89a..ede6e5234 100644 --- a/src/components/NavbarSecondary.res +++ b/src/components/NavbarSecondary.res @@ -1,5 +1,32 @@ +open ReactRouter + +let link = "no-underline block hover:cursor-pointer hover:text-fire-30 mb-px" +let activeLink = "font-medium text-fire-30 border-b border-fire" + +let linkOrActiveLink = (~target: Path.t, ~route: Path.t) => target === route ? activeLink : link + +let isActiveLink = (~includes: string, ~excludes: option=?, ~route: Path.t) => { + let route = (route :> string) + // includes means we want the lnk to be active if it contains the expected text + let includes = route->String.includes(includes) + // excludes allows us to not have links be active even if they do have the includes text + let excludes = switch excludes { + | Some(excludes) => route->String.includes(excludes) + | None => false + } + includes && !excludes ? activeLink : link +} + +module MobileDrawerButton = { + @react.component + let make = (~hidden: bool) => + +} + @react.component -let make = () => { +let make = (~children) => { let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) let navbarClasses = switch scrollDirection { @@ -9,8 +36,12 @@ let make = () => { } diff --git a/src/components/NavbarTertiary.res b/src/components/NavbarTertiary.res index da6869dba..4e3a897ff 100644 --- a/src/components/NavbarTertiary.res +++ b/src/components/NavbarTertiary.res @@ -4,12 +4,13 @@ let make = () => { let navbarClasses = switch scrollDirection { | Up(_) => "translate-y-0" - | Down(_) => "-translate-y-[192px] lg:translate-y-0" + // + | Down(_) => "-translate-y-[176px] lg:translate-y-0" } diff --git a/src/components/Sidebar.res b/src/components/Sidebar.res index 1334f6f35..320b1a845 100644 --- a/src/components/Sidebar.res +++ b/src/components/Sidebar.res @@ -5,7 +5,539 @@ type item = { @react.component let make = (~items) => { -
    + } From 949389fe7c9ed70edd33ad5896c562fe75004d7f Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Tue, 10 Feb 2026 18:19:00 -0500 Subject: [PATCH 04/36] refactoring --- app/root.res | 2 +- app/routes/Guide.res | 8 ++-- src/components/NavbarPrimary.res | 72 ++++++++++++++++++++++++++------ src/components/Search.res | 4 +- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/app/root.res b/app/root.res index 36f98671a..0bb490a48 100644 --- a/app/root.res +++ b/app/root.res @@ -65,7 +65,7 @@ let default = () => { /> - + // className={isScrollLockEnabled ? "overflow-hidden" : ""}> diff --git a/app/routes/Guide.res b/app/routes/Guide.res index d47a4ae45..24d1df372 100644 --- a/app/routes/Guide.res +++ b/app/routes/Guide.res @@ -62,16 +62,16 @@ let default = () => { // let attributes = Mdx.useMdxAttributes() let component = Mdx.useMdxComponent(~components) -
    + <> {React.null} // -
    + + } diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res index 8aa9d2c67..3079ab671 100644 --- a/src/components/NavbarPrimary.res +++ b/src/components/NavbarPrimary.res @@ -1,26 +1,74 @@ open ReactRouter +module LeftContent = { + @react.component + let make = () => { +
    + + ReScript Home + ReScript Home + + + {React.string("Docs")} + + {React.string("Playground")} + {React.string("Blog")} + + {React.string("Community")} + +
    + } +} + +module RightContent = { + @react.component + let make = () => { + + } +} + @react.component let make = () => { let scrollDirection = Hooks.useScrollDirection(~topMargin=64, ~threshold=32) let navbarClasses = switch scrollDirection { | Up(_) => "translate-y-0" - | Down(_) => "-translate-y-full lg:translate-y-0" + | Down(_) => "-translate-y-full md:translate-y-0" } } diff --git a/src/components/Search.res b/src/components/Search.res index 581c765c3..fee16f930 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -157,9 +157,7 @@ let make = () => { }, [setState]) <> - {switch state { From bd7b348c3c3a4605eef931859dab4cbcfe837055 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 15 Feb 2026 09:06:54 -0500 Subject: [PATCH 05/36] mobile overlay works --- __tests__/Example.test.res | 28 -------- __tests__/NavbarPrimary_.test.res | 62 +++++++++++++++++ app/root.res | 16 ++--- app/routes/Guide.res | 1 - functions/ogimage/[[path]]/index.png.res | 2 - rescript.json | 3 +- src/bindings/ReactRouter.res | 7 ++ src/bindings/Vitest.res | 12 ++++ src/common/Util.res | 1 - src/components/Icon.res | 3 +- src/components/Icon.resi | 2 +- src/components/NavbarMobileOverlay.res | 88 ++++++++++++++++++++++++ src/components/NavbarPrimary.res | 83 ++++++++++++++++------ src/components/NavbarPrimary.resi | 2 + src/components/NavbarUtils.res | 29 ++++++++ src/components/Navigation.res | 1 + src/components/Search.res | 2 +- styles/main.css | 9 +++ vitest.config.js | 16 ----- vitest.config.mjs | 24 +++++++ vitest.setup.mjs | 2 + 21 files changed, 309 insertions(+), 84 deletions(-) delete mode 100644 __tests__/Example.test.res create mode 100644 __tests__/NavbarPrimary_.test.res create mode 100644 src/components/NavbarMobileOverlay.res create mode 100644 src/components/NavbarPrimary.resi create mode 100644 src/components/NavbarUtils.res delete mode 100644 vitest.config.js create mode 100644 vitest.config.mjs create mode 100644 vitest.setup.mjs diff --git a/__tests__/Example.test.res b/__tests__/Example.test.res deleted file mode 100644 index df140a2db..000000000 --- a/__tests__/Example.test.res +++ /dev/null @@ -1,28 +0,0 @@ -open Vitest - -module Example = { - @react.component - let make = (~handleClick) => -
    - -
    -} - -test("basic assertions", async () => { - expect("foo")->toBe("foo") - - expect(true)->toBe(true) -}) - -test("component rendering", async () => { - let callback = fn() - let screen = await render() - - await element(screen->getByText("testing"))->toBeVisible - - let button = await screen->getByRole(#button) - - await button->click - - expect(callback)->toHaveBeenCalled -}) diff --git a/__tests__/NavbarPrimary_.test.res b/__tests__/NavbarPrimary_.test.res new file mode 100644 index 000000000..d8ed362d8 --- /dev/null +++ b/__tests__/NavbarPrimary_.test.res @@ -0,0 +1,62 @@ +open ReactRouter +open Vitest + +test("desktop has everything visible", async () => { + await viewport(1440, 500) + + let screen = await render( + + + , + ) + + await element(screen->getByText("Docs"))->toBeVisible + await element(screen->getByText("Playground"))->toBeVisible + await element(screen->getByText("Blog"))->toBeVisible + await element(screen->getByText("Community"))->toBeVisible + + await element(screen->getByLabelText("Github"))->toBeVisible + await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(screen->getByLabelText("Bluesky"))->toBeVisible + await element(screen->getByLabelText("Forum"))->toBeVisible +}) + +test("tablet has everything visible", async () => { + await viewport(900, 500) + + let screen = await render( + + + , + ) + + await element(screen->getByText("Docs"))->toBeVisible + await element(screen->getByText("Playground"))->toBeVisible + await element(screen->getByText("Blog"))->toBeVisible + await element(screen->getByText("Community"))->toBeVisible + + await element(screen->getByLabelText("Github"))->toBeVisible + await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(screen->getByLabelText("Bluesky"))->toBeVisible + await element(screen->getByLabelText("Forum"))->toBeVisible +}) + +test("phone has some things hidden and a mobile nav", async () => { + await viewport(600, 500) + + let screen = await render( + + + , + ) + + await element(screen->getByText("Docs"))->toBeVisible + await element(screen->getByText("Playground"))->notToBeVisible + await element(screen->getByText("Blog"))->notToBeVisible + await element(screen->getByText("Community"))->notToBeVisible + + await element(screen->getByLabelText("Github"))->notToBeVisible + await element(screen->getByLabelText("X (formerly Twitter)"))->notToBeVisible + await element(screen->getByLabelText("Bluesky"))->notToBeVisible + await element(screen->getByLabelText("Forum"))->notToBeVisible +}) diff --git a/app/root.res b/app/root.res index 0bb490a48..9aeb19c95 100644 --- a/app/root.res +++ b/app/root.res @@ -66,15 +66,13 @@ let default = () => { - // className={isScrollLockEnabled ? "overflow-hidden" : ""}> - - - // - - - - - + + // + // + + + + // } diff --git a/app/routes/Guide.res b/app/routes/Guide.res index 24d1df372..7303a56fd 100644 --- a/app/routes/Guide.res +++ b/app/routes/Guide.res @@ -63,7 +63,6 @@ let default = () => { let component = Mdx.useMdxComponent(~components) <> - {React.null} //
    diff --git a/functions/ogimage/[[path]]/index.png.res b/functions/ogimage/[[path]]/index.png.res index bdc524e7b..0b215994f 100644 --- a/functions/ogimage/[[path]]/index.png.res +++ b/functions/ogimage/[[path]]/index.png.res @@ -16,8 +16,6 @@ let loadGoogleFont = async (family: string) => { type context = {request: FetchAPI.request, params: {path: array}} let onRequest = async ({params}: context) => { - Console.log(params.path) - let title = params.path[0]->Option.getOr("ReScript")->decodeURIComponent // let url = WebAPI.URL.make(~url=request.url) // let title = url.searchParams->URLSearchParams.get("title") diff --git a/rescript.json b/rescript.json index 9214ceafd..2903912dc 100644 --- a/rescript.json +++ b/rescript.json @@ -10,8 +10,7 @@ "sources": [ { "dir": "__tests__", - "subdirs": true, - "type": "dev" + "subdirs": true }, { "dir": "app", diff --git a/src/bindings/ReactRouter.res b/src/bindings/ReactRouter.res index 6df145ca1..796fb32eb 100644 --- a/src/bindings/ReactRouter.res +++ b/src/bindings/ReactRouter.res @@ -51,12 +51,14 @@ module Link = { @module("react-router") @react.component external make: ( + ~onClick: ReactEvent.Mouse.t => unit=?, ~children: React.element=?, ~className: string=?, ~target: string=?, ~to: Path.t, ~preventScrollReset: bool=?, ~prefetch: prefetch=?, + @as("aria-label") ~ariaLabel: string=?, ) => React.element = "Link" module Path = { @@ -116,3 +118,8 @@ module Routes = { @module("react-router-mdx/server") external mdxRoutes: string => array = "routes" } + +module BrowserRouter = { + @react.component @module("react-router") + external make: (~children: React.element) => React.element = "BrowserRouter" +} diff --git a/src/bindings/Vitest.res b/src/bindings/Vitest.res index 0c151148e..4270cfd23 100644 --- a/src/bindings/Vitest.res +++ b/src/bindings/Vitest.res @@ -16,6 +16,12 @@ external fn: unit => 'a => 'b = "fn" @module("vitest") external expect: 'a => expect = "expect" +/** + * Vitest browser + */ +@module("vitest/browser") @scope("page") +external viewport: (int, int) => promise = "viewport" + /** * vitest-browser-react */ @@ -31,6 +37,9 @@ external element: 'a => element = "element" @send external getByText: (screen, string) => element = "getByText" +@send +external getByLabelText: (screen, string) => element = "getByLabelText" + @send external getByRole: (screen, [#button]) => promise = "getByRole" @@ -54,3 +63,6 @@ external toHaveBeenCalled: expect => unit = "toHaveBeenCalled" */ @send external toBeVisible: element => promise = "toBeVisible" + +@send @scope("not") +external notToBeVisible: element => promise = "toBeVisible" diff --git a/src/common/Util.res b/src/common/Util.res index c47e5f686..024474f17 100644 --- a/src/common/Util.res +++ b/src/common/Util.res @@ -62,7 +62,6 @@ module Url = { let makeOpenGraphImageUrl = (title, description) => { let baseUrl = Env.deployment_url->Option.getOr(Env.root_url) - Console.log(baseUrl) `${baseUrl}${baseUrl->Stdlib.String.endsWith("/") ? "" : "/"}ogimage/${encodeURIComponent( title, )}/${encodeURIComponent(description)}/index.png` diff --git a/src/components/Icon.res b/src/components/Icon.res index daac2bdf4..4d341d008 100644 --- a/src/components/Icon.res +++ b/src/components/Icon.res @@ -102,8 +102,9 @@ module Caret = { module DrawerDots = { @react.component - let make = (~className: string="") => + let make = (~className: string="", ~onClick) => React.element + let make: (~className: string=?, ~onClick: JsxEvent.Mouse.t => unit) => React.element } module CornerLeftUp: { diff --git a/src/components/NavbarMobileOverlay.res b/src/components/NavbarMobileOverlay.res new file mode 100644 index 000000000..a3008db37 --- /dev/null +++ b/src/components/NavbarMobileOverlay.res @@ -0,0 +1,88 @@ +open ReactRouter +open NavbarUtils + +module MobileNav = { + @react.component + let make = (~route: Path.t) => { + let base = "font-normal mx-4 py-5 text-gray-40 border-b border-gray-80" + let extLink = "block hover:cursor-pointer hover:text-white text-gray-60" + + } +} + +@react.component +let make = () => { + let location = ReactRouter.useLocation() + let route = location.pathname + + // TODO: close dialog when you click outside + // React.useEffect(() => { + // document->WebAPI.Document.addEventListener(Click, closeMobileOverlay) + // Some(() => document->WebAPI.Document.removeEventListener(Click, closeMobileOverlay)) + // // None + // }, []) + + + + +} diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res index 3079ab671..6fad9a7d8 100644 --- a/src/components/NavbarPrimary.res +++ b/src/components/NavbarPrimary.res @@ -1,12 +1,20 @@ open ReactRouter +open NavbarUtils + +let isActive = (~url, ~pathname: Path.t) => { + (pathname :> string)->String.includes(url) + ? "hover:text-fire-30 font-medium text-fire-30 border-b border-fire" + : "hover:text-fire-30" +} module LeftContent = { @react.component let make = () => { + let {pathname} = useLocation()
    - + ReScript Home @@ -14,14 +22,19 @@ module LeftContent = { className="hidden lg:block" alt="ReScript Home" src="/brand/rescript-logo.svg" width="116" /> - + {React.string("Docs")} - {React.string("Playground")} - {React.string("Blog")} - + + {React.string("Playground")} + + + {React.string("Blog")} + + {React.string("Community")}
    @@ -31,21 +44,44 @@ module LeftContent = { module RightContent = { @react.component let make = () => { + let iconClasses = "w-6 h-6 opacity-50 hover:opacity-100" + let linkClasses = "hidden md:block" } @@ -60,15 +96,18 @@ let make = () => { | Down(_) => "-translate-y-full md:translate-y-0" } - + + } diff --git a/src/components/NavbarPrimary.resi b/src/components/NavbarPrimary.resi new file mode 100644 index 000000000..1ca44ce26 --- /dev/null +++ b/src/components/NavbarPrimary.resi @@ -0,0 +1,2 @@ +@react.component +let make: unit => React.element diff --git a/src/components/NavbarUtils.res b/src/components/NavbarUtils.res new file mode 100644 index 000000000..91621eadf --- /dev/null +++ b/src/components/NavbarUtils.res @@ -0,0 +1,29 @@ +let link = "no-underline block hover:cursor-pointer hover:text-fire-30 mb-px" +let activeLink = "font-medium text-fire-30 border-b border-fire" + +let linkOrActiveLink = (~target: Path.t, ~route: Path.t) => target === route ? activeLink : link + +let linkOrActiveLinkSubroute = (~target: Path.t, ~route: Path.t) => + String.startsWith((route :> string), (target :> string)) ? activeLink : link + +external elementToDialog: WebAPI.DOMAPI.element => WebAPI.DOMAPI.htmlDialogElement = "%identity" + +let getMobileOverlayDialog = () => { + document->WebAPI.Document.getElementById("mobile-overlay")->elementToDialog +} + +@get external _open: WebAPI.DOMAPI.htmlDialogElement => bool = "open" + +let openMobileOverlay = _ => getMobileOverlayDialog()->WebAPI.HTMLDialogElement.showModal + +let closeMobileOverlay = _ => getMobileOverlayDialog()->WebAPI.HTMLDialogElement.close + +let toggleMobileOverlay = _ => { + let isOpen = getMobileOverlayDialog()->_open + + if isOpen { + closeMobileOverlay() + } else { + openMobileOverlay() + } +} diff --git a/src/components/Navigation.res b/src/components/Navigation.res index 4502e767d..1ab48aece 100644 --- a/src/components/Navigation.res +++ b/src/components/Navigation.res @@ -224,6 +224,7 @@ let make = (~fixed=true, ~isOverlayOpen: bool, ~setOverlayOpen: (bool => bool) = }} > diff --git a/src/components/Search.res b/src/components/Search.res index fee16f930..35e3df4cc 100644 --- a/src/components/Search.res +++ b/src/components/Search.res @@ -158,7 +158,7 @@ let make = () => { <> {switch state { | Active => diff --git a/styles/main.css b/styles/main.css index 8ca519493..0cbca7b1c 100644 --- a/styles/main.css +++ b/styles/main.css @@ -390,6 +390,15 @@ @apply min-w-64 bg-white border-gray-20 border-r-2 overflow-y-scroll h-full md:block; } +/* When any dialog is open as a modal, lock the body scroll */ +body:has(dialog[open]) { + overflow: hidden; +} + +body { + scrollbar-gutter: stable; +} + /* This has to stay at the end! */ /* This is to prevent FOUC (flash of unstyled content) */ html { diff --git a/vitest.config.js b/vitest.config.js deleted file mode 100644 index 18eb0da85..000000000 --- a/vitest.config.js +++ /dev/null @@ -1,16 +0,0 @@ -import { defineConfig } from "vitest/config"; -import { playwright } from "@vitest/browser-playwright"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], - test: { - include: ["__tests__/*.jsx"], - browser: { - enabled: true, - provider: playwright(), - // https://vitest.dev/config/browser/playwright - instances: [{ browser: "chromium" }], - }, - }, -}); diff --git a/vitest.config.mjs b/vitest.config.mjs new file mode 100644 index 000000000..e9f5e49b9 --- /dev/null +++ b/vitest.config.mjs @@ -0,0 +1,24 @@ +import { defineConfig } from "vitest/config"; +import { playwright, defineBrowserCommand } from "@vitest/browser-playwright"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], + test: { + include: ["__tests__/*.jsx"], + setupFiles: ["./vitest.setup.mjs"], + browser: { + enabled: true, + provider: playwright(), + // https://vitest.dev/config/browser/playwright + instances: [ + { + browser: "chromium", + name: "desktop", + viewport: { width: 1440, height: 900 }, + }, + ], + }, + }, +}); diff --git a/vitest.setup.mjs b/vitest.setup.mjs new file mode 100644 index 000000000..6d48aafc1 --- /dev/null +++ b/vitest.setup.mjs @@ -0,0 +1,2 @@ +import "./styles/main.css"; +import "./styles/utils.css"; From 482b405304f3965104102782f0102a9699df766b Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 15 Feb 2026 16:47:14 -0500 Subject: [PATCH 06/36] tests are passing --- __tests__/NavbarPrimary_.test.res | 75 ++++++++++++++++---------- src/bindings/Vitest.res | 15 ++++-- src/components/Icon.res | 3 +- src/components/Icon.resi | 2 +- src/components/NavbarMobileOverlay.res | 28 +++++++--- src/components/NavbarPrimary.res | 10 +++- src/components/NavbarUtils.res | 22 ++++++-- src/components/Navigation.res | 1 - 8 files changed, 106 insertions(+), 50 deletions(-) diff --git a/__tests__/NavbarPrimary_.test.res b/__tests__/NavbarPrimary_.test.res index d8ed362d8..5a6c3cbae 100644 --- a/__tests__/NavbarPrimary_.test.res +++ b/__tests__/NavbarPrimary_.test.res @@ -10,15 +10,19 @@ test("desktop has everything visible", async () => { , ) - await element(screen->getByText("Docs"))->toBeVisible - await element(screen->getByText("Playground"))->toBeVisible - await element(screen->getByText("Blog"))->toBeVisible - await element(screen->getByText("Community"))->toBeVisible - - await element(screen->getByLabelText("Github"))->toBeVisible - await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible - await element(screen->getByLabelText("Bluesky"))->toBeVisible - await element(screen->getByLabelText("Forum"))->toBeVisible + let leftContent = await screen->getByTestId("navbar-primary-left-content") + + await element(leftContent->getByText("Docs"))->toBeVisible + await element(leftContent->getByText("Playground"))->toBeVisible + await element(leftContent->getByText("Blog"))->toBeVisible + await element(leftContent->getByText("Community"))->toBeVisible + + let rightContent = await screen->getByTestId("navbar-primary-right-content") + + await element(rightContent->getByLabelText("Github"))->toBeVisible + await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(rightContent->getByLabelText("Bluesky"))->toBeVisible + await element(rightContent->getByLabelText("Forum"))->toBeVisible }) test("tablet has everything visible", async () => { @@ -30,19 +34,23 @@ test("tablet has everything visible", async () => { , ) - await element(screen->getByText("Docs"))->toBeVisible - await element(screen->getByText("Playground"))->toBeVisible - await element(screen->getByText("Blog"))->toBeVisible - await element(screen->getByText("Community"))->toBeVisible + let leftContent = await screen->getByTestId("navbar-primary-left-content") + + await element(leftContent->getByText("Docs"))->toBeVisible + await element(leftContent->getByText("Playground"))->toBeVisible + await element(leftContent->getByText("Blog"))->toBeVisible + await element(leftContent->getByText("Community"))->toBeVisible + + let rightContent = await screen->getByTestId("navbar-primary-right-content") - await element(screen->getByLabelText("Github"))->toBeVisible - await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible - await element(screen->getByLabelText("Bluesky"))->toBeVisible - await element(screen->getByLabelText("Forum"))->toBeVisible + await element(rightContent->getByLabelText("Github"))->toBeVisible + await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(rightContent->getByLabelText("Bluesky"))->toBeVisible + await element(rightContent->getByLabelText("Forum"))->toBeVisible }) -test("phone has some things hidden and a mobile nav", async () => { - await viewport(600, 500) +test("phone has some things hidden and a mobile nav that can be toggled", async () => { + await viewport(600, 1200) let screen = await render( @@ -50,13 +58,26 @@ test("phone has some things hidden and a mobile nav", async () => { , ) - await element(screen->getByText("Docs"))->toBeVisible - await element(screen->getByText("Playground"))->notToBeVisible - await element(screen->getByText("Blog"))->notToBeVisible - await element(screen->getByText("Community"))->notToBeVisible + // await element(screen->getByText("Docs"))->toBeVisible + // await element(screen->getByText("Playground"))->notToBeVisible + // await element(screen->getByText("Blog"))->notToBeVisible + // await element(screen->getByText("Community"))->notToBeVisible + + await element(screen->getByTestId("mobile-nav"))->notToBeVisible + + // await element(screen->getByLabelText("Github"))->notToBeVisible + // await element(screen->getByLabelText("X (formerly Twitter)"))->notToBeVisible + // await element(screen->getByLabelText("Bluesky"))->notToBeVisible + // await element(screen->getByLabelText("Forum"))->notToBeVisible + + let button = await screen->getByTestId("toggle-mobile-overlay") + + await element(button)->toBeVisible + + await button->click - await element(screen->getByLabelText("Github"))->notToBeVisible - await element(screen->getByLabelText("X (formerly Twitter)"))->notToBeVisible - await element(screen->getByLabelText("Bluesky"))->notToBeVisible - await element(screen->getByLabelText("Forum"))->notToBeVisible + // await element(screen->getByLabelText("Github"))->toBeVisible + // await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible + // await element(screen->getByLabelText("Bluesky"))->toBeVisible + // await element(screen->getByLabelText("Forum"))->toBeVisible }) diff --git a/src/bindings/Vitest.res b/src/bindings/Vitest.res index 4270cfd23..0355ab191 100644 --- a/src/bindings/Vitest.res +++ b/src/bindings/Vitest.res @@ -1,6 +1,5 @@ type page type expect -type screen type element type mock @@ -26,7 +25,7 @@ external viewport: (int, int) => promise = "viewport" * vitest-browser-react */ @module("vitest-browser-react") -external render: Jsx.element => promise = "render" +external render: Jsx.element => promise = "render" @module("vitest") @scope("expect") external element: 'a => element = "element" @@ -35,13 +34,19 @@ external element: 'a => element = "element" * Locators */ @send -external getByText: (screen, string) => element = "getByText" +external getByTestId: (element, string) => promise = "getByTestId" @send -external getByLabelText: (screen, string) => element = "getByLabelText" +external getByText: (element, string) => promise = "getByText" @send -external getByRole: (screen, [#button]) => promise = "getByRole" +external getByLabelText: (element, string) => promise = "getByLabelText" + +@send +external getAllByLabelText: (element, string) => promise> = "getAllByLabelText" + +@send +external getByRole: (element, [#button]) => promise = "getByRole" /** * Actions diff --git a/src/components/Icon.res b/src/components/Icon.res index 4d341d008..daac2bdf4 100644 --- a/src/components/Icon.res +++ b/src/components/Icon.res @@ -102,9 +102,8 @@ module Caret = { module DrawerDots = { @react.component - let make = (~className: string="", ~onClick) => + let make = (~className: string="") => unit) => React.element + let make: (~className: string=?) => React.element } module CornerLeftUp: { diff --git a/src/components/NavbarMobileOverlay.res b/src/components/NavbarMobileOverlay.res index a3008db37..731554ecb 100644 --- a/src/components/NavbarMobileOverlay.res +++ b/src/components/NavbarMobileOverlay.res @@ -7,6 +7,7 @@ module MobileNav = { let base = "font-normal mx-4 py-5 text-gray-40 border-b border-gray-80" let extLink = "block hover:cursor-pointer hover:text-white text-gray-60"
    • @@ -47,7 +48,12 @@ module MobileNav = {
    • - + {React.string("X")}
    • @@ -75,14 +81,20 @@ let make = () => { let location = ReactRouter.useLocation() let route = location.pathname - // TODO: close dialog when you click outside - // React.useEffect(() => { - // document->WebAPI.Document.addEventListener(Click, closeMobileOverlay) - // Some(() => document->WebAPI.Document.removeEventListener(Click, closeMobileOverlay)) - // // None - // }, []) + let handleBackdropClick = (e: JsxEvent.Mouse.t) => { + let target = e->JsxEvent.Mouse.target + let currentTarget = e->JsxEvent.Mouse.currentTarget + if target == currentTarget { + closeMobileOverlay() + } + } + + React.useEffect(() => { + // Make sure the dialog element closes if the component unmounts + Some(closeMobileOverlay) + }, []) - + } diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res index 6fad9a7d8..9002f6220 100644 --- a/src/components/NavbarPrimary.res +++ b/src/components/NavbarPrimary.res @@ -12,6 +12,7 @@ module LeftContent = { let make = () => { let {pathname} = useLocation()
      @@ -47,13 +48,18 @@ module RightContent = { let iconClasses = "w-6 h-6 opacity-50 hover:opacity-100" let linkClasses = "hidden md:block"
      - + ariaLabel="Toggle additional menu" + dataTestId="toggle-mobile-overlay" + > + + diff --git a/src/components/NavbarUtils.res b/src/components/NavbarUtils.res index 91621eadf..37865348e 100644 --- a/src/components/NavbarUtils.res +++ b/src/components/NavbarUtils.res @@ -9,17 +9,31 @@ let linkOrActiveLinkSubroute = (~target: Path.t, ~route: Path.t) => external elementToDialog: WebAPI.DOMAPI.element => WebAPI.DOMAPI.htmlDialogElement = "%identity" let getMobileOverlayDialog = () => { - document->WebAPI.Document.getElementById("mobile-overlay")->elementToDialog + Nullable.make(document->WebAPI.Document.getElementById("mobile-overlay")->elementToDialog) } @get external _open: WebAPI.DOMAPI.htmlDialogElement => bool = "open" -let openMobileOverlay = _ => getMobileOverlayDialog()->WebAPI.HTMLDialogElement.showModal +let openMobileOverlay = _ => + switch getMobileOverlayDialog() { + | Nullable.Value(dialog) => dialog->WebAPI.HTMLDialogElement.showModal + | Null => () + | Undefined => () + } -let closeMobileOverlay = _ => getMobileOverlayDialog()->WebAPI.HTMLDialogElement.close +let closeMobileOverlay = _ => + switch getMobileOverlayDialog() { + | Nullable.Value(dialog) => dialog->WebAPI.HTMLDialogElement.close + | Null => () + | Undefined => () + } let toggleMobileOverlay = _ => { - let isOpen = getMobileOverlayDialog()->_open + let isOpen = switch getMobileOverlayDialog() { + | Nullable.Value(dialog) => dialog->_open + | Null => false + | Undefined => false + } if isOpen { closeMobileOverlay() diff --git a/src/components/Navigation.res b/src/components/Navigation.res index 1ab48aece..4502e767d 100644 --- a/src/components/Navigation.res +++ b/src/components/Navigation.res @@ -224,7 +224,6 @@ let make = (~fixed=true, ~isOverlayOpen: bool, ~setOverlayOpen: (bool => bool) = }} > From 80b836dca50d14fdb3b5edc87f6305bd8b9de0f9 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 15 Feb 2026 19:16:28 -0500 Subject: [PATCH 07/36] done with mobile overlay --- __tests__/NavbarPrimary_.test.res | 26 +++++++++++++++++--------- src/components/NavbarMobileOverlay.res | 11 ++++++----- styles/main.css | 5 +++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/__tests__/NavbarPrimary_.test.res b/__tests__/NavbarPrimary_.test.res index 5a6c3cbae..857db8d9b 100644 --- a/__tests__/NavbarPrimary_.test.res +++ b/__tests__/NavbarPrimary_.test.res @@ -58,17 +58,21 @@ test("phone has some things hidden and a mobile nav that can be toggled", async , ) - // await element(screen->getByText("Docs"))->toBeVisible - // await element(screen->getByText("Playground"))->notToBeVisible - // await element(screen->getByText("Blog"))->notToBeVisible - // await element(screen->getByText("Community"))->notToBeVisible + let leftContent = await screen->getByTestId("navbar-primary-left-content") - await element(screen->getByTestId("mobile-nav"))->notToBeVisible + await element(leftContent->getByText("Docs"))->toBeVisible + await element(leftContent->getByText("Playground"))->notToBeVisible + await element(leftContent->getByText("Blog"))->notToBeVisible + await element(leftContent->getByText("Community"))->notToBeVisible + + let rightContent = await screen->getByTestId("navbar-primary-right-content") - // await element(screen->getByLabelText("Github"))->notToBeVisible - // await element(screen->getByLabelText("X (formerly Twitter)"))->notToBeVisible - // await element(screen->getByLabelText("Bluesky"))->notToBeVisible - // await element(screen->getByLabelText("Forum"))->notToBeVisible + await element(rightContent->getByLabelText("Github"))->notToBeVisible + await element(rightContent->getByLabelText("X (formerly Twitter)"))->notToBeVisible + await element(rightContent->getByLabelText("Bluesky"))->notToBeVisible + await element(rightContent->getByLabelText("Forum"))->notToBeVisible + + await element(screen->getByTestId("mobile-nav"))->notToBeVisible let button = await screen->getByTestId("toggle-mobile-overlay") @@ -76,6 +80,10 @@ test("phone has some things hidden and a mobile nav that can be toggled", async await button->click + let mobileNav = await screen->getByTestId("mobile-nav") + + await element(mobileNav)->toBeVisible + // await element(screen->getByLabelText("Github"))->toBeVisible // await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible // await element(screen->getByLabelText("Bluesky"))->toBeVisible diff --git a/src/components/NavbarMobileOverlay.res b/src/components/NavbarMobileOverlay.res index 731554ecb..b0dde37a6 100644 --- a/src/components/NavbarMobileOverlay.res +++ b/src/components/NavbarMobileOverlay.res @@ -6,10 +6,7 @@ module MobileNav = { let make = (~route: Path.t) => { let base = "font-normal mx-4 py-5 text-gray-40 border-b border-gray-80" let extLink = "block hover:cursor-pointer hover:text-white text-gray-60" -
        +
        • { Some(closeMobileOverlay) }, []) - + } diff --git a/styles/main.css b/styles/main.css index 0cbca7b1c..e8fa350ba 100644 --- a/styles/main.css +++ b/styles/main.css @@ -395,6 +395,11 @@ body:has(dialog[open]) { overflow: hidden; } +#mobile-overlay { + max-width: 100%; + margin: 0; +} + body { scrollbar-gutter: stable; } From 3eb6f20dfd2e15bc9186d1c3685e48c307cfeddd Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 15 Feb 2026 19:40:06 -0500 Subject: [PATCH 08/36] screenshots --- .gitignore | 5 ++++- __tests__/NavbarPrimary_.test.res | 17 +++++++++++++---- .../desktop-navbar-primary-chromium-linux.png | Bin 0 -> 1934 bytes .../mobile-navbar-primary-chromium-linux.png | Bin 0 -> 2030 bytes ...e-overlay-navbar-primary-chromium-linux.png | Bin 0 -> 6849 bytes .../tablet-navbar-primary-chromium-linux.png | Bin 0 -> 3312 bytes src/bindings/Vitest.res | 3 +++ src/components/NavbarPrimary.res | 2 +- 8 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 __tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png create mode 100644 __tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png create mode 100644 __tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png create mode 100644 __tests__/__screenshots__/NavbarPrimary_.test.jsx/tablet-navbar-primary-chromium-linux.png diff --git a/.gitignore b/.gitignore index 59dc8d7ad..c977f3bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,7 @@ __tests__/**/*.jsx _scripts # Local env files -.env.local \ No newline at end of file +.env.local + +# Vitest screenshots +!__tests__/__screenshots__/**/* \ No newline at end of file diff --git a/__tests__/NavbarPrimary_.test.res b/__tests__/NavbarPrimary_.test.res index 857db8d9b..02c7f7469 100644 --- a/__tests__/NavbarPrimary_.test.res +++ b/__tests__/NavbarPrimary_.test.res @@ -23,6 +23,10 @@ test("desktop has everything visible", async () => { await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible await element(rightContent->getByLabelText("Bluesky"))->toBeVisible await element(rightContent->getByLabelText("Forum"))->toBeVisible + + let navbar = await screen->getByTestId("navbar-primary") + + await element(navbar)->toMatchScreenshot("desktop-navbar-primary") }) test("tablet has everything visible", async () => { @@ -47,6 +51,10 @@ test("tablet has everything visible", async () => { await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible await element(rightContent->getByLabelText("Bluesky"))->toBeVisible await element(rightContent->getByLabelText("Forum"))->toBeVisible + + let navbar = await screen->getByTestId("navbar-primary") + + await element(navbar)->toMatchScreenshot("tablet-navbar-primary") }) test("phone has some things hidden and a mobile nav that can be toggled", async () => { @@ -84,8 +92,9 @@ test("phone has some things hidden and a mobile nav that can be toggled", async await element(mobileNav)->toBeVisible - // await element(screen->getByLabelText("Github"))->toBeVisible - // await element(screen->getByLabelText("X (formerly Twitter)"))->toBeVisible - // await element(screen->getByLabelText("Bluesky"))->toBeVisible - // await element(screen->getByLabelText("Forum"))->toBeVisible + let navbar = await screen->getByTestId("navbar-primary") + + await element(navbar)->toMatchScreenshot("mobile-navbar-primary") + + await element(mobileNav)->toMatchScreenshot("mobile-overlay-navbar-primary") }) diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d5fbe103ad55ae45dc66ec66d1a258b5a19924fa GIT binary patch literal 1934 zcmV;92XXj`P)#?3s&l zWu<_kD2k%IsFB<-^Vct+8dHT^z^Z zv2Y|z!)dTzG)*TOjbIoCZli(QY|?8cJu(#wVQAnBfs_&yf}@K(W4y5WLQ}E3d^?X#xzZ4UOPp+FV16+ibZaHd5cbb-zOG} zkxHj1tQBZBo8p^ z4462ML%c7}gXLu&EIpu?OnO8EJl`ky&N~de{yP77_gzkZ@Bz2pdym-7o9KYxYj0-~ zyfM(npGp=#&LlV-H2KwohrF5UV=!p)NoALesq=$WoI zd_2rpB*gDG%Dmnmr|z=d^q5P=_{lu|09!5lRc0HG!*5#!q9cfr3$|9Q>p9_iA3op zl}b^o*%XUKip3(;Y8BhINhT6>W7{^-Xq1QB4*|*U?k?SUtrm|>O-*uQYLdr%-=~vg zGReyFGC~O2EX$%)Die)H=_QjM5klbkK2l2B4mF$D@4rtL@P(k0ri693$)C$L`gD!2 zoF<->{Ni8|pRc7&gHGb6jtL0bBz?L@O-TNia=FaCd-s@`IYq;Daq14yXaw7~89p-1 zojZ3aJ}S~lI-SC**?7Ls(9jU4PtUTty2?(qN<5JODbbJ|&E?ow-$2(jN~H?bogEGz z8RqEGBW!O!#C2T`9z4L-<`$hqqfsJ}2$h{3lKuT0$sNV1J0z0{`Um>C@|VBTkrd3& z-yoGr@#?8paq4xl*(`xz0Nb)LOcO)bu`HY2T8(%zL0dMPrRlmqryC&!Qu1FUl4o%ohrz)?%9S#~V320BNhiMV z^X0AEj2}NnEEc1!(P*%+uz(PPZVbcZNxH7lYdSq5q{J5j-*xdC4Lsk+7Xr`s@rA&b zeB*)J!tw-HtvU@M`B^r>ow`fR(0MZz=i`#irL78=jt`Lz7_7T39yUGNZq;4d-b(dx zE)nJ9vc;9kE*A&-cpj-#iqWxAHa0hrQW6SKd-=5()vc9a zHi#$U7>16Nl29l_C=|l?e7e$fCZ{IJ9>^l4Boqo^+ZN?giBh?Yl#=!Jb=C?6;>iU4 z{rx19Ngh3VMAforE0@a@%O%d8Kg;6cB8u{|t5&P5uWurS4 z@E~oYqazFr4N=+Ip_@n~LjOQNg+c*c*BKl<$g?C9al+v+uItijwFrm9BoaxUMIwnt zqnM`2`1m*j0|VH$O)qJ)XD-T>l>+Lu_tsF?etg$EkDw{t~?~Ha1FbB*))A{X4xB3SSU5CZRMLIDIgOOa0`%6o-<#IV%ttOhLvA(`R zC*g3IiHQj|Ha7^ECYfx8g?}#6Y&Pj+Zten4^1K#z?=Da*7U_kN+%WUkFQd(#xhPjw z3hcLpq4Cpn1OtiWKO{&gky3KE;q%$9hoUIT%Rtj~q!ehHj+BCKG)+TF$&*aeL`sS8 z`|JnPGLPJp$MNwYd@R1?f%#S|2grX>lqU^=L0g#tG U%!hLpl>h($07*qoM6N<$f`YrYDgXcg literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..702beaca53ee9ee052d18db56132bc3e60c4dade GIT binary patch literal 2030 zcmZuyc{r478y|9x;S^&?IF#WfITAD3#*)!=%-9|1~a2U>9Wj` zN=`DAtugA*l*pE)Ix?2VQi&+ON7r?(@B8DsuIGBMce&sD`Q7*PyMMoI7bn|Y(kjw0 z7;G1jfG5FVVsvn?2mcXVudV(T1A|H4BjRyxablv5$?%seYTe61Vb83Ys*`tOVJfL* zmZ>xl$}IZf`!k_D*(G z`o#;^(ULM>(S3+R5qt91ZG0=!`7E0xUIgn0z#*mklmt) z*0f8H03TFnuh~?=NljFRgpf_Wg_8pB#*CyT{^yaLh6EwpAGJ(D#4fwwqc|rgOA&yQ%b5sYsSGXk#l2YdnVcXw9MTcQVAr)1tVYe;3t7$BcI! zl@(1rZEHKm@eJS)YSJJGg@ID=y5*M;%>C`XYrHEhWl3elj#mjrCJmc3+olyxHNuYq z&rhcc68eMCj?wR0W<&|)4Z)Xm6z!SH41LF?!8fr?R_UpE%W*xe19R%x7Yf!u;p8VD z1Wv{8qV)d4mJ9n6HnJQl^ zi%84$8QGbcVQVhw&GQZuv)BbyS*s@c=g;T8yu3~0UL(@dj+zR`JUH%Gwdf&lLr5Hj zK8JgDnrPPHFAHIIT^ikaYQa8KDkgS<%bIg2+Ptx&w};XIj>a*j%5JMqY>rkTqm6?< z-U$kRo5aGyyd)*LYn?x-%Z=}ZZxd#|(nTktTkSqQG>r1G$~^ANp4+g1peEq0bv0;Ie6TW?HZydIi52z7R8IWC6>v7kDUPb2I9Ui@R z4)5yfdN|Q{G{9o?7+P974h%MCMmJ}J1rZG}tv3~7 z5DYL56ms@=Iu~R?hR(>h>mg^(P?|l1&b?CkRXLMo&`l}6#&|T zke5CzXyCF*Hs6Uf?fEM=OZ zEiyO$>vA_3pGuN`|IpC0*CV(JdbLyi@2eOl?}>c%0R?G*&NyuBali8xGapj5h{zfl zIT61w>))NX!Evuujr&JkV73}l;~s1}I`pjMWvypYKQ@Z1nz1aEw>1yF49VD7Xkn{6!x&_!;%3T zd(2J)R-i5cbaES5G7ueZ?Yu~&$>{kGTMQjo*3^5Y3KLvYYUL=eKF!2K7>!64FNpvg zU?ZTvzkh6oG;rM$bC4)4ga}Q$`6U!#uk7A2S zez#5gc5rdUUeuk~o4kp7mM3)?jntcKFwTUe$zzpB-Z0rO+4kI3O K!r!v-OZ^YJ1Hv%? literal 0 HcmV?d00001 diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..30b879b6be629de830531d5a61bf40946f2ebf9c GIT binary patch literal 6849 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 xZ4SFGUr3__c&Df8OKK9a_gC0is} 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 Xs$Y7q`xpV-WH`*QR+w_*8xQ^iXAw+v literal 0 HcmV?d00001 diff --git a/src/bindings/Vitest.res b/src/bindings/Vitest.res index 0355ab191..53ae92b80 100644 --- a/src/bindings/Vitest.res +++ b/src/bindings/Vitest.res @@ -71,3 +71,6 @@ external toBeVisible: element => promise = "toBeVisible" @send @scope("not") external notToBeVisible: element => promise = "toBeVisible" + +@send +external toMatchScreenshot: (element, string) => promise = "toMatchScreenshot" diff --git a/src/components/NavbarPrimary.res b/src/components/NavbarPrimary.res index 9002f6220..a2df0e63f 100644 --- a/src/components/NavbarPrimary.res +++ b/src/components/NavbarPrimary.res @@ -104,7 +104,7 @@ let make = () => { <> {switch sidebar { | Some(sidebar) => sidebar diff --git a/src/components/NavbarUtils.res b/src/components/NavbarUtils.res index 5c457c8f9..b20dd5b8f 100644 --- a/src/components/NavbarUtils.res +++ b/src/components/NavbarUtils.res @@ -60,7 +60,7 @@ let getMobileTertiaryDialog = () => { let openMobileTertiaryDrawer = _ => switch getMobileTertiaryDialog() { - | Nullable.Value(dialog) => dialog->WebAPI.HTMLDialogElement.show + | Nullable.Value(dialog) => dialog->WebAPI.HTMLDialogElement.showModal | Null => () | Undefined => () } diff --git a/src/layouts/ApiOverviewLayout.resi b/src/layouts/ApiOverviewLayout.resi index 66c04fcec..840a42826 100644 --- a/src/layouts/ApiOverviewLayout.resi +++ b/src/layouts/ApiOverviewLayout.resi @@ -1,3 +1,5 @@ +let categories: array + module Docs: { @react.component let make: (~children: React.element) => React.element diff --git a/src/layouts/SidebarLayout.res b/src/layouts/SidebarLayout.res index c3ae75e7f..af716dfb8 100644 --- a/src/layouts/SidebarLayout.res +++ b/src/layouts/SidebarLayout.res @@ -269,7 +269,7 @@ module BreadCrumbs = { let make = ( ~theme: ColorTheme.t, ~editHref: option=?, - ~sidebarState: (bool, (bool => bool) => unit), + ~sidebarState: (bool, (bool => bool) => unit)=(false, _ => ()), // (Sidebar, toggleSidebar) ... for toggling sidebar in mobile view ~sidebar: React.element, ~rightSidebar: option=?, diff --git a/src/layouts/SidebarLayout.resi b/src/layouts/SidebarLayout.resi index 6383b46a4..1422b7b3d 100644 --- a/src/layouts/SidebarLayout.resi +++ b/src/layouts/SidebarLayout.resi @@ -63,7 +63,7 @@ let make: ( ~theme: ColorTheme.t, ~editHref: string=?, // (Sidebar, toggleSidebar) ... for toggling sidebar in mobile view - ~sidebarState: (bool, (bool => bool) => unit), + ~sidebarState: (bool, (bool => bool) => unit)=?, ~sidebar: React.element, ~rightSidebar: React.element=?, ~categories: array=?, diff --git a/styles/main.css b/styles/main.css index 8522b4243..db3ed30eb 100644 --- a/styles/main.css +++ b/styles/main.css @@ -400,6 +400,11 @@ body:has(dialog[open]) { margin: 0; } +#mobile-tertiary-drawer { + max-width: 100%; + margin: 0; +} + body { scrollbar-gutter: stable; height: 100%; diff --git a/vitest.config.mjs b/vitest.config.mjs index e9f5e49b9..dce076124 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -15,7 +15,6 @@ export default defineConfig({ instances: [ { browser: "chromium", - name: "desktop", viewport: { width: 1440, height: 900 }, }, ], From c671d16fbb3152d8729859a2ab03200548c40292 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 16 Feb 2026 17:26:34 -0500 Subject: [PATCH 17/36] remove build errors and warnings --- app/root.res | 4 ++-- app/routes/MdxRoute.res | 2 +- src/ApiDocs.res | 13 +---------- src/layouts/ApiLayout.res | 3 +-- src/layouts/ApiLayout.resi | 1 - src/layouts/ApiOverviewLayout.res | 9 +------- src/layouts/CommunityLayout.res | 3 --- src/layouts/DocsLayout.res | 6 +---- src/layouts/SidebarLayout.res | 37 ++----------------------------- src/layouts/SidebarLayout.resi | 2 -- 10 files changed, 9 insertions(+), 71 deletions(-) diff --git a/app/root.res b/app/root.res index 320dacb1c..970804c8a 100644 --- a/app/root.res +++ b/app/root.res @@ -39,8 +39,8 @@ open ReactRouter @react.component let default = () => { let {pathname} = ReactRouter.useLocation() - let (isOverlayOpen, setOverlayOpen) = React.useState(_ => false) - let (isScrollLockEnabled, setIsScrollLockEnabled) = React.useState(_ => false) + let (_isOverlayOpen, setOverlayOpen) = React.useState(_ => false) + let (_isScrollLockEnabled, setIsScrollLockEnabled) = React.useState(_ => false) React.useEffect(() => { // When the path changes close the sidebar and disable scroll lock diff --git a/app/routes/MdxRoute.res b/app/routes/MdxRoute.res index c8f680712..55f1bf4fb 100644 --- a/app/routes/MdxRoute.res +++ b/app/routes/MdxRoute.res @@ -408,7 +408,7 @@ let default = () => { {React.string("Edit")} - +
          {component()}
          diff --git a/src/ApiDocs.res b/src/ApiDocs.res index 5ce5f01b9..20817bd4f 100644 --- a/src/ApiDocs.res +++ b/src/ApiDocs.res @@ -332,18 +332,7 @@ let make = (props: props) => { } } - let prefix = {Url.name: "API", href: "/docs/manual/api"} - let {pathname: route} = ReactRouter.useLocation() - let breadcrumbs = ApiLayout.makeBreadcrumbs(~prefix, route) - - - children - + children } module Data = { diff --git a/src/layouts/ApiLayout.res b/src/layouts/ApiLayout.res index 7928ac407..5b391632b 100644 --- a/src/layouts/ApiLayout.res +++ b/src/layouts/ApiLayout.res @@ -64,7 +64,6 @@ let makeBreadcrumbs = (~prefix: Url.breadcrumb, route: Path.t): list, ~title="", ~activeToc: option=?, @@ -86,7 +85,7 @@ let make = ( let sidebar = - + children } diff --git a/src/layouts/ApiLayout.resi b/src/layouts/ApiLayout.resi index 59169a747..648d7975f 100644 --- a/src/layouts/ApiLayout.resi +++ b/src/layouts/ApiLayout.resi @@ -9,7 +9,6 @@ let makeBreadcrumbs: (~prefix: Url.breadcrumb, Path.t) => list @react.component let make: ( - ~breadcrumbs: list=?, ~categories: array, ~title: string=?, ~activeToc: TableOfContents.t=?, diff --git a/src/layouts/ApiOverviewLayout.res b/src/layouts/ApiOverviewLayout.res index 65bb5adc5..6389d6fb3 100644 --- a/src/layouts/ApiOverviewLayout.res +++ b/src/layouts/ApiOverviewLayout.res @@ -23,11 +23,6 @@ module Docs = { let make = (~children) => { let {pathname: route} = ReactRouter.useLocation() - let breadcrumbs = list{ - {Url.name: "Docs", href: `/docs/manual/api`}, - {name: "API", href: `/docs/manual/api`}, - } - let (isSidebarOpen, setSidebarOpen) = React.useState(_ => false) let preludeSection = @@ -44,9 +39,7 @@ module Docs = { route /> - + children } diff --git a/src/layouts/CommunityLayout.res b/src/layouts/CommunityLayout.res index d56f35767..d95cc201d 100644 --- a/src/layouts/CommunityLayout.res +++ b/src/layouts/CommunityLayout.res @@ -11,8 +11,6 @@ let make = (~children, ~categories, ~entries) => { ->String.replaceAll("-", " ") ->Util.String.capitalizeSentence - let breadcrumbs = list{{Url.name: "Community", href: "/community"}} - let (isSidebarOpen, setSidebarOpen) = React.useState(_ => false) <> @@ -26,7 +24,6 @@ let make = (~children, ~categories, ~entries) => { />} sidebarState=(isSidebarOpen, setSidebarOpen) theme=#Reason - breadcrumbs > children diff --git a/src/layouts/DocsLayout.res b/src/layouts/DocsLayout.res index 5ff29d614..be6e62e91 100644 --- a/src/layouts/DocsLayout.res +++ b/src/layouts/DocsLayout.res @@ -28,9 +28,7 @@ let makeBreadcrumbs = (~basePath: string, route: string): list = @react.component let make = ( - ~editHref: option=?, ~activeToc: option=?, - ~breadcrumbs: option>=?, ~categories: array, ~components=MarkdownComponents.default, ~theme=#Reason, @@ -49,9 +47,7 @@ let make = ( let sidebar = - + children } diff --git a/src/layouts/SidebarLayout.res b/src/layouts/SidebarLayout.res index af716dfb8..9ec63cd40 100644 --- a/src/layouts/SidebarLayout.res +++ b/src/layouts/SidebarLayout.res @@ -6,11 +6,6 @@ */ module Link = ReactRouter.Link -let isDocRoute = (~route: Path.t) => { - let route = (route :> string) - route->String.includes("/docs/") || route->String.includes("/syntax-lookup") -} - module Toc = { type raw = Dict.t<{ "title": string, @@ -145,7 +140,7 @@ module Sidebar = { // the height of the navbars above is fluid across pages, and it's easy to get it wrong // so we calculate it dynamically here - let sidebarTopOffset = isOpen + let _sidebarTopOffset = isOpen ? { let mobileNavbarHeight = Nullable.make(document->WebAPI.Document.getElementById("mobile-navbar")) @@ -268,36 +263,21 @@ module BreadCrumbs = { @react.component let make = ( ~theme: ColorTheme.t, - ~editHref: option=?, ~sidebarState: (bool, (bool => bool) => unit)=(false, _ => ()), // (Sidebar, toggleSidebar) ... for toggling sidebar in mobile view ~sidebar: React.element, ~rightSidebar: option=?, ~categories: option>=?, - ~breadcrumbs: option>=?, ~children, ) => { - let (isNavOpen, setNavOpen) = React.useState(() => false) + let (_isNavOpen, setNavOpen) = React.useState(() => false) let location = ReactRouter.useLocation() let theme = ColorTheme.toCN(theme) - let hasBreadcrumbs = switch breadcrumbs { - | None => false - | Some(l) => List.length(l) > 0 - } - - let breadcrumbs = breadcrumbs->Option.mapOr(React.null, crumbs => ) - // TODO: post rr7 - this can most likely be removed let (_isSidebarOpen, setSidebarOpen) = sidebarState - let (_isLocked, toggleScrollLock) = ScrollLockContext.useScrollLock() - - let toggleSidebar = () => { - setSidebarOpen(prev => !prev) - toggleScrollLock(prev => !prev) - } let {pathname} = ReactRouter.useLocation() @@ -307,19 +287,6 @@ let make = ( None }, [pathname]) - let handleDrawerButtonClick = React.useCallback(evt => { - ReactEvent.Mouse.preventDefault(evt) - toggleSidebar() - }, []) - - let editLinkEl = switch editHref { - | Some(href) => - - {React.string("Edit")} - - | None => React.null - } - let pagination = switch categories { | Some(categories) => let items = categories->Array.flatMap(c => c.items) diff --git a/src/layouts/SidebarLayout.resi b/src/layouts/SidebarLayout.resi index 1422b7b3d..6498b5a16 100644 --- a/src/layouts/SidebarLayout.resi +++ b/src/layouts/SidebarLayout.resi @@ -61,12 +61,10 @@ module BreadCrumbs: { @react.component let make: ( ~theme: ColorTheme.t, - ~editHref: string=?, // (Sidebar, toggleSidebar) ... for toggling sidebar in mobile view ~sidebarState: (bool, (bool => bool) => unit)=?, ~sidebar: React.element, ~rightSidebar: React.element=?, ~categories: array=?, - ~breadcrumbs: list=?, ~children: React.element, ) => React.element From e0bf40d76aaaa80ef1e707a6463de61d71fc7e31 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 16 Feb 2026 17:36:25 -0500 Subject: [PATCH 18/36] commit screenshot changes --- .github/workflows/pull-request.yml | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e6a8c4a08..30934211c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -47,3 +47,8 @@ jobs: run: yarn playwright install --with-deps - name: Vitest run: yarn ci:test + - name: Commit and Push changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update vitest screenshots [skip ci]" + file_pattern: "**/__screenshots__/**/*.png" diff --git a/package.json b/package.json index 39407f269..34f375558 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build:vite": "react-router build", "build": "yarn build:res && yarn build:scripts && yarn build:update-index && yarn build:vite", "ci:format": "prettier . --check --experimental-cli", - "ci:test": "yarn vitest --run --browser.headless", + "ci:test": "yarn vitest --run --browser.headless --update", "clean:res": "rescript clean", "convert-images": "auto-convert-images", "dev:res": "rescript watch", From 21a333cea26a574fd92206b917747309551f6baf Mon Sep 17 00:00:00 2001 From: jderochervlk <60623931+jderochervlk@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:38:16 +0000 Subject: [PATCH 19/36] chore: update vitest screenshots [skip ci] --- .../desktop-navbar-primary-chromium-linux.png | Bin 1934 -> 10159 bytes .../mobile-navbar-primary-chromium-linux.png | Bin 2030 -> 1726 bytes ...-overlay-navbar-primary-chromium-linux.png | Bin 6849 -> 5576 bytes .../tablet-navbar-primary-chromium-linux.png | Bin 3312 -> 10288 bytes ...desktop-navbar-tertiary-chromium-linux.png | Bin 3768 -> 4539 bytes .../mobile-navbar-tertiary-chromium-linux.png | Bin 3448 -> 2362 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/desktop-navbar-primary-chromium-linux.png index d5fbe103ad55ae45dc66ec66d1a258b5a19924fa..9f2555de18795c36053ee148175f01135d718762 100644 GIT binary patch literal 10159 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 U%!hLpl>h($07*qoM6N<$f`YrYDgXcg diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-navbar-primary-chromium-linux.png index 702beaca53ee9ee052d18db56132bc3e60c4dade..e6b54e021e6618ffdcedbf291fb786d7ef3d8e7a 100644 GIT binary patch literal 1726 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 K!r!v-OZ^YJ1Hv%? diff --git a/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png b/__tests__/__screenshots__/NavbarPrimary_.test.jsx/mobile-overlay-navbar-primary-chromium-linux.png index 30b879b6be629de830531d5a61bf40946f2ebf9c..37f28fd3dfd042cbc66fb18b58633ae78764241f 100644 GIT binary patch literal 5576 zcmd5=XIN9&x>hNI;Glxidnl0(0i;GmgiS9B1`LQa5s;DIgi(qGX`9{xNF+)XkY0rW zq=w#$Nbd-QUha-9-hAHHc_>u9i!E?-nZ`4PFWzaWui^NZOPBN#4Dj>gB<*$>@e6~& z6;A{DQD;CbKN$?rJEsP|r3;6tz&U`|`qx3acfY{k>VNoy`xL9y2Nk2fO! z3>X|J4->O!{OHthHAWa+_gVSu!t(Niw)rj?aKkS@h5cln?^8*ML4nJ{r5GU-?Ph2* zv*X^Y7T>?EeLSgEx|J*i#+v%ZyN~ZtQO)AhEl-}21Y8Km(rUH5aB0LTsx(d3Q}bUr zKvmnz4B5wIWbmp>Irv8B`4Y&XE&Yn65}EEZD$#XXDH{0b)y|Z#gXp=I8?Gij#FKv6r&KrR!(dcG+ok_05prtU`O#vctHBQEo7ZS!LVUljqjXv2ffpRW~*s&nPaA zsd-mXk%+;tsh`_BgX`_>93EB&HZj)aPH466SG-mfPGu|tM%mwv+N!*yTB}Qv5L&1L z7fKS-kqucbt=OKvJ{92sEx^Wx&w{zByPzbn@kz*SBSUBG&QC>Mot@XA$2f1n^su#9 zf+LDdJf519H0V5YCd&}KjdTp*tm3=1hWRIEdAQ&qDW~}>!3IsvGwSAYXNiP9FeR_CtXUvoEfBKwnjc)K;DsY237^;727>KbaG`y|qdMH2DG{IgMYT@YbOMj!1UG+^e=s!*k{ zwL_08^g0h>Pt5eu64ftZaM?d>i~ra+Ru{x=gIY4r!GI>=fj;Hx@VSTj9`wu8Do(kelaUPz&zwM-1*wZoPdFah0N@2HimtawT$@?U}lx{cg5hdd-~ zPq*}{k3;hG`03PoqRz5=yabDM@@5*DAE)AQiS9dVIE9f=*up%xnDAXCG`$CZEjhHF z)v^#3WJEJp>^RX_uqL7mZk4=av2iUby5~3My0x|4Ibmkmz9>oD-ORO-d<#A~x8<84 z0`dP0CsCZGTiqIaTeuV2JAHRt+*~#`&E)GQ?PF?i`5_jzEOIgQW!%e;d0>R;)YhQGd)FR^ea1Lp3 z2i<_ySe=fJHB$n$>7FTN^5er>U7fL2?cS)5_fPBOnzqyUetgcC;sNe$&WWWKWP*IY zN3b2iideWo#(Jkejq}=5dq*&90c##P&2QTEIwbW#a?&-NIS;Pi)t| zv|P@bUlRCeyP0**r3l#vtK64rahMWuSpLQ<45hk%W^~uu&Nn2W5BVeo*@SH1K8gs{mVY-x7Gfao)KDz* zd-<=yd<*<^f?e+H@d5jFaH~sWFC5ZvYY(mSb6QDX2m@wKP|N0w6z7{Ck*nW&!|=IX zS_B4M8Xg|mZNU2}85xD1p2NsJ%MeKLSZIhvtw)owbXX6SJ1{f%JEs0SK7V9qhbmp? zZRr*t9vSgmEpx3L+mK4B+AZ{%?AG|eVE8%cyvX`+j1W|B;TdJwDT9=^Q|soh8+>C# zOthsE-+lP->h$I;x^=NHYpCi#s6)|Pp1`fz-M1m7@;b?Tl{B|>Lw)sRZAW4?`-z}g zaT0`L0|Q!~0aNeuSp(T=o;ngCQ%q%hvw=}W#o)vEfH74 zfMGU_l9CoL#|{yc8sqbp7WKz978iLO9314g2lE&ty$}XCEpDyWI8pO6OZ_?N*vG$8 zxUKmHgJTsF53b*_x-32F!L4(+PwD0D4S==1wmoV+;=G!1wdJl$@AKG+W=I_}ZSMpI zRGQy+rI+q6mQi1ui8A2#eAI5xmLM5ffS5u0?y#$M^C`7ln0Ase%)Q$XQSI3(eRsQR z25Z{_zSCgn93O8iEo|(9q|UiR=EZ~jFRNn}3(Y$PhZyftLX3%r`-$(YsY0UFgo$=S zh9Igu0PN_{Zj=grXXQu*ox+FQ?2PHLn#xv-Fa>RIZ#hX^Qr(~g9*gyzRx`{qRZhql zEwdDxIEl6TQ49tLFDqL2(I;{$sd+WEzcjI*TrL^+qN-9*aB*?j=t((x16JR7mfIoB z<}+{pxQvi!KT@77w_2La)j436YPr&5SN1)Kd46GGcI@cD7GG?zF=!FyTpuzJ2}mRv zo8h^``1&NIwnN@2vP5va6?Z-wu{!F_@`d4iWHc8shV8M`Jebx)-krGFXp`BRB z#YRR^^!4>S9k~v4^^7R{`9(-(4R8Q_iBS|QgZYuHa&BAzU*Ug@7r(X5zY1uSI7K`{ z+3F4%h_#4aT7=A#GXm}(E;8M0v#ACu{*6FGT(OozE6Ib6-fRO!PK2#+m5Vrto#GZ% zlXGAF_G#a1@sS-|1iwx*r(@X~GXRkWJKR4CMk!KL@JgdbG=6(qaK!IG4x+pt|00F* z!@b-@32n#JGEWg`h)Ky%c$RVq5(UFA8Mr|UR9;`xdOg9mbvlg(T@bdQZv|cHU3b+vG+jn_+q=bfO zPS-j6`ui`bUF#l|DP$1!-qyUDl^G{vAzNsJw_N(aB=7)=j%q586p+LY3SJpMv>v$921J`J|R^&^HS17^MSZ&M+ zTemKm$as9^NY|DN309PQWpi>Q!>gV2rN=IXGa#Tx2k8%Tbwcx|>^0%nN6QJfql`pn z3PjeMkjJkIJ04Bz_>Dr*MpHj1wFDPy)32xjKmj6FvpTN7-+h;Z=oEdvt5CmZG6b6Y z9Y#tYc9X%ZtUe)1boKT0I_O0b>+DQS)AKr2jnaYx0x~lnF*QjR_#SPlbvMIilv~&o zH8TY3Bfx^|C=??A>SCHZ`^P(Gjm)?Q`cxo?OCS*Ff>jOLgS_qwA4f<)gD*4j82>{&>n)Dx`q z_~7U0p2YcUqky9#9$Lhrg&r~?0_o)$K_Sf25im+uS69KiqG7A|q~&~T{K4aDUx-qN z)(@!=Xt9tv-$KmnY?UU)hmu*U@+B2L<>YY|3bepUFZn| z*i-)Ug#UCWa-X6D>Bps|We_jsENg&6-}3a7;URD@#b5g_KaPj+xoZcZa@xNwS%M0< zL`rUZIAkN{;~}}!uYi}ZOR>V6wEL_&(;GOg*DATK?33^~&$Zov+U|@$!n{t#e&Se; z{1bF7q9IaM_e+_x^^-0YdVOd0^H6E{fHVGL?j3^UZ5V|}usKr1yg!=}QY8P2xX|mM z_AQ8og2bW3&lAauu7#v)eqq(RK8T+2qwA8Jw&TOE@kMy?)XKL>j1o9!?CaO(6u*3I zF|RDb#kVJx=o%R{VS$q``vj361yH)WwG#oyhp)94I*d5NuLQSYB>mGDRu*QUS)8y# zg*F>X#A=_c`l=5P4>L$*Ujk$q$;cPlrB8mv0`%fvmkaUVR*)wm$55TARrIg$3kYy) z8MH_+Lbn3ks{IDyZ*yZ-S6M(8adk$lg5yfs+sY)kL3J%$64%)^^m!9sqXxoQJ`j@K zA-o6>?Z_5^;EZsr%=Z_-Nq0RdjU4yEJkX(`<-@2tI0yk0*=8jWk{SP=6(sW3JL(&} z?YgKt&tMCKGv!_oa9FfjI3P~k7?KCEVLE@rbARsI%!*;&%Pi zKe@X=HEcD3Oiu6np7*(#)^i=2ZH|EIaJ;Ub+A6L>q5k|+01PA@L$%xp%A zSRd`v^EX?HR~~i?Kxa&Z*s-ySrwp^{h&mJ>AWK(26^2x5ULKP0u+xzMT?#Z9pO0fq zF{lGU63E58AUUAXbz1+*nRzvfAsjykg^($KC~-hnNjzg?3J@i!zgB?(C#qi=ps*jx2rW3?G91Q=ZV?;&uBoI1Cdnfb8x88ks-mJUk-aoR|x03I)efIv9omaOI8q5q_42KRKVuowp zxO3>x;aA{u|HN_d4I=8r=AlDezrb%?yNf+c`N)XvUAH7JFC7p#PKo4u>6E)FOtIV2H>+FTM{p-9tZshkm%+y~^h485EllUtt; zIp|W49G;F1bq+Tzc|P12cYq@;!nIMpu{xNiqwT_+a^r!RzW0@ycuaOGv*`&qUI5<3 ze<~Cra$W#_=Y}di?U$zz_{qa?{P`nz$jzT1@W2zA_|qr<>V?J*=Ja%ZV|4c^79M%k zYu66*4v4Wr*N$#1fdy5z=@Y$e&lDA1jei^%6jVABTc=E|aSw&;83s5G5*?IU_{`MX zrkcV_*VT&i*V@uG@og|p()0QGM=C3wtS)6Sgx0j&!iY~kFA9_FXNNXnH46iT?s9ht zpYL02Q^U@+>oPp3u~LIGUlQa9bKRMp*pyczSKLQk zc*5oa!1XAnMl=Cy~N$uPsm*(4#f`fM{ElmW9+UoZgOB|(Rxxaih z2s^e*FXc5K6p!fX;V!ivzE}UcG@6-PHnhOS4(js0gh!J+k}21nsW-`r zo9;1+mVa_SMkgTNn9%;9Hh_7GM+yB}(y9TkZ>Sh4mycV`(TSINRZ+pDFzwP32HC4( zvg?1mW0Cs4DzV*rv0VXE;@3+I$28j(eC_KA$KzXVo3gO){lf$`sOb{EVHqYS2FYeZJ~K$)wk(-d|5xS`QqDu?g3 zLOz9JKSX@vj--l1;MJm>uqOx;i|UufElIu`?bxX>jjX&J;Y{}=5)-##1ZBuQ)aBbt zN1L4u%3T$USCF~tw$6ad*4Q|v()%a)_o**`&B{uydY_2!_S_uz?yC&>^yzxA43Fhf zRplS=n+Fs(`R&n_teR>ojO-u%{UHpJ;QcRSz@LHyXW7N)s-snvV5ah5e%Q6x42(g5 zIEWVy%;yNat28Gy^%(?C&SpYJK4HCC%bFa})s-byilV1u z;SqqrlI&$$QbUxrkVvRe+vQBVb}vIQ3qQ!*ZfJ5G(P5}9pNt9moWdbw+VX+ zehDAfJ%RHJ4rW30ri& z<|4f2ZGpOvYG-H!fV3*ueL#@_0ZWj=bZbjgexYrRKuESX5h)(shwm@-RUtCZsU7cj zTY;Ds+Y4o8=Lf!EWDjk!j{d*pdl=p|AWBV&XeAcBf5fZcy|XsGo|XoIUpF`BLp3`U z+7*D!n)}IFQpY}?K7G1lxa)c*fR(NSJ3aBUQqSW*Sk*gIYpx*@m7CfU*1wcapaqga z#_1%fFw^r(Q zbywNwV`G@$Ru`dKQ{RQH+>N4+$80orFiTWdBoZvl+xb_9vl$dAqt!`yzLanA;?-}3 zb3_dbymwg8j(Z*Nc$}1jA#)26;|^bo-T6uGBRdzx#HKWBPi`y@X-}_4f|DRPN$NYS zJY#K6mv7=Rj(?dSf0b5S5&Gem@k z5a;0@vwwe?AEnjUp{o|aWc%^cvDe&k!5bTHZVSJTTliAd@(0}7)P)4KOExO|(4&=` zVb5!ZMO$zi0|uIS(!ICbD=SjF%SpTRL&cP_9N$fY{ms#RenrJQAUd83ragT8{>H+9Ccf-~6EzfHt>U&IkGySYs6~Xf1fCIn=;S1vX}iO6oRv?e>M{Q4^lVCZai4ypEQmi_=>1`37Z zHO`nc>+g=N@z2T2C@hRn@&4|-x85iHZN+$jR1jy?5d37wA!)2JER{zA{X9Nzx0PVL zzHsS%ylGqf3c9(arPSl2!P?((WI+_D%oRq4tqBfTyGy%)0NlU*Ag}#razI$B`W;i#+AM@j!u{#y$SE`5+Vw&r zk=p7cKE0fz^07f>U(#b^0p2!t?b+7H>itlVZ~W1c-ikmZ+$&oX->4)e0TmGzZgET1 zU|KOsyfTPQSJwJnmW&0#DYPN9m{@iN00%t}OjVk;@5M1~zAaCSbtS0@AXY`t^4FrEbC6 z-R;#^aQt&=5nTbuuF0t*naysP4iQ~_JY;+w(3rGyhrn?b9@T1}z5b%d9REE&NTRAS zLL|HtGowSJ=B?Ba-`7xPWC_C`tIE|JjSiLLwAViU+l?R+J?3X+_?)KlTg0t; zrwblBIM~#FU#G1Bfg?GvZ1s9)+p8*f8btun6o?u<=|j4&QbYJMD!f<}^fM93h4z;= z_a=+C8$1ufXc7)oZ|os>b5WnUn!diYp7Jt8;pWjmtlRXZ#ji0R-nI(dYNK%l_T(nXMPjsSX=rMV z+<6p!r_Y9*0azX=ZWIb-Ia#l(ywJ~%}Px6KMi@!?M`p8H$NI)kmJKqR8%yja~9@D&vt>@ zQ+=SM=#Wd&Y$NrVPu2^`{pRq^&1XGF3$O)<6fgkh9&+X8pso{Tm5g$$<_(hC9bZW`4_d~_QUU@Fcnt!omO&f9HK$9Jb1LO$SDZ39^O5t+#Hc@YT- z;mnef=-a94VFh!MM$yVP49||UUYH#Oq*R|SeevSOv1j}hFL;&QsGr&Ny09r>6B7?; zhaYFctu$qpOiH`cDj_y5j=VMzBtb)?s;xQ+AiOmV49+x!@C(EJWGvekp}T9#!dp`g zzMqR1D}scRyaH*)1SrP0rKPVBA-g-NUh^SNb~8583QtnP%E}bcG|{}hGVQfd;H&P9 zglYuy*tEtL6&4nTf;1QP-8;j@#noTzBysiXRY17nKV*Bs2(VrN!N#e{wUBdE*g=EE zN{xF}?YP=Yp!9GW+{Dmv9?6Ge0#Rb6i8E2`CZ;CFMe_!s)~k-WERE-z&o<(boLy`Lj?-^f5G`bw~hw@0|N=Z09e+}-ZsVQ|V_ zHgZ5O(a_unb(CPC5`@Q}T$@MMWa#DrQvf?whHB}3x7KJK4fSMy+_&owfrI@g9dS|z z-3vwEV`X#r4^|2Tq_)ALYU_;!<1|ztnDI0@U^j6)B1(6hqk2oo)pcD)uIS2>2`qr- zOjS9vjYVzTbYyG1eCLhd$E`#(0rkO1gF-dAyLWTP@9OFbW4AO8fbiTs{a5{ue~R*- zg(@sL2oFg%$~!`1hM5_5KmfGB$7U2JC6qHORj~P{aB&xk+k+%FKFBQXQ-+!w$2EE#J63l zSkugijLe^yqiJiHpWHcmTpFr*&anzdpAf+8%>jIrurU0TcqQgIBL}jzhmlnK>utd% zkw%I8`xT{P=7mpJL}xp4+?^1Sgr>>ZI>yr9u=2n{O@YU`yJluzDsV>NZdZZLA$O*p zg?gM;Xb9%D+77fMXU7nbTHbr&+1b6F#ZDIQjVnS&$~T>tql4tJ1F@jkl|0(VX||EXicO>k;eAf*vIT)E+8r;_ZyEb^jAHb zXO25-t{fjlF2R@`mb#;mGC}Pgi4)X&AU0PprcK^YY zw9wJokMYmg#miz-_V=*Ukw#&VN>{1)XFmI186a?{%0H{3zwAT)PaUdpOixdP)ZwSq znpFcgTuJfwCPS;Og2&ofe42ak-=F)3E>x9UU+N1{aItl1p&M;)m6I6nijgMEJfsS+ zi&X^1L*Ssfq@19orA`CGOfDJh&x{=At=~jMI6qDo*qwuW>>MjQ2S38dZeB<7-odVg zB~c49qq+sdEe8vw&@8;lH>UHl8|lwk4wq=t!1~C@74*~zx5Zqm9U?ES-v-jHHT%AESg)#>bEF# ze*FUMUMQq;T?(Uyhk#B(rm16TjK9AZy6Rw$e??tAgs%=1vwTmLciyr(aGly#R)Pxa z>+4?sS10D*qlg9PivhN7D23RB}qo$cPFIyo#a4 zOFBbu5|#!Lsg8osXExg^$r}K)_u&4iESX4*3QN^0Ax{Kpc1Q3*J+A;JaqI1QQWhK> zp%cP~G0@j%RC~!RWM|!U0^w@8eC0d)hLGKYkX2UR=o`SOgk)|L(;@7$_|eBsG)8EMvS=qn66N3#jXVg*5i8}#UIAhaKg zzGRgPh0qHJ%XNYO7Ql|GnMUsF>uZC9lQge?%1ftDz{|XVr0Z**g23%)+ch$6sIwB! z=#G};C@82(?7n(cPWry;$?a`uQ_Ga3J^2>xJtjLs744+^73bT+5qd_&k88-Jw6Cru?Kk6zbEtlH@sPsQz|Nyzi&{$ xZ4SFGUaon zG{P6^FiV*<@%C|aOCkRVf2UD=fv~@v`k=7JrtSVnw^@NLVUB*Yo-36;o&Q-IVW?>s zPFN?3Os(|m$6UANXvFKj;#j{+`laA?<~zhr1||A3DG^~PoE;mU$WHo3L*YR__%r8r zg9-T`d}w%hOYP6a2d+=C-~RQBjauTz*S~y_KWg_N;IG??XEad$x=o`lALXyx^6eE- z{~0*xe|KP#I=Q$zixrg;8||J01go!ms=`*$IJ;mX&_GUfNGqcO6iQ}72tgww%U<~< zmGsOa{xHt*Sq(R?*{#1PN}Y*ZdBOU~{aa0HLdj|zUj}bOeA>LFcCnJfFg3$5$%4Wb zO+_w`%1|$Td&K`x&J9GEPyi%6P4zPN% zAjUvcDYU3oqE>fVe$qb+7M0)M-)DLdu0e=T#K!c6k4vym&OF#faPat;zp6MlH}LdS zux$0$`bg8k5T|HVXC!GAZ0UwdmT>1Dyjy*O6c_yg3ws_bN#5JH0ZmQ9QJhh#QQW*6 zMl-w@7Z>$q{Yn)n1tsSc*9Hv76jcq5yk3PcgzYxcD#F8XD6k4<*y0`)#0M$?*p9@dP?%sXDOP?U$*Jsk9U;^Gybhq+Z3{k8N zXAcMX#vCAq9J!g9nf2;ns5ff|!7Nj;T8&=s?>jj;JM;T8gbNAux~#0Ma5y}>MMOmO z>nOYPK-m0=y*(Qa37gSnlP6k`c$v=Y=a0qxQ1kPhi#~jyO;q?cH`fjfQkT}ra|iuq z81MlPD&Pl3QR2b`QQ}fxfU&QyhAk37qQFLoLQs+WKm$eEUlknho9KVcT~b2A#rI6b zQ5~`bLropPlq3z|Pf+;gT@owCXoCCuw`&L7QB>{=*;>-Dnf-}@Mltc$7G)SF_lFBh zjQ6?=vVP*I;KH#=wrKmt*il7kbIeVQ`7f@9_V3=k3vOt*k88n2VI|b2^yy#-T|q%% z_n|@kYt2G00pC-egoFeiG64aB0o4JE) zKwTS5mldWe-0D&P`4ZJW-$=avd{e1imjwZf!)&9*@;7ZWc1mXJWM=PvlPIoFZcl#$ zG3=bW3-+(=KD5oOtq~lR9j*}nZEBf6(IX33{?pavnO;M6((*D?B1ibfOOzb7uw_fm z@D3mBNbGghOynj!1)C7fqO11gNnhxla4DwfL1UvYUEqs&glAMtOeJtU1HS&RUkRsd zlfG4Ou4!wjsi}RLs+xx=(?k%(oo$r7r5Cl@Frg|nGh3g_tp(mUr`BS;iI%2$h96-=d);{}b6^w<8hv)cS zZ}tY*LMe`}#c(4O2rXHp`f2QyoV}-jCLXoe(9}ntR~bfxs%SS2>JL;(uGSow^y**t z9v}o}Mx~Av+11H0uMg%f7{EyCPvopC%*ddin%31r z`=88Mi$55Pn*~}{r%XJXQ63-Uddk|r4CWl4ojok8t))p z3H!5-PtFK3N=mcEpD4vz5gx)S!c>fj3XU~Rvj#dmWnbKjRtbT(4=}Z7Gcom8tj@7` zo_(kzr=~{8NaPT*>^zfaVviN09Z0FHBvVXgTYVx7K_4dIT8mn0V9RL|#%{Yse22ub z=Dwn}V^>%Ca3)*_lO(vmWr)Ye%8z)-5PBS}D24XCRZ|tBUlZ{@I$Lb`M+_yI$!_$l zJ2l?IF}i03*~<)y(p+dnGCQMzcBh9X(RIWUMHPNYeQ5EQVY1J3KIT@t;D7clJh|98 z`o7?g+-IraZ{O6>(QQ>Se&p2Ghv`{8`1I)$eZm9tAkL7+?S_Q5HVL0%Eip#btTtV> z*Rh--HhaPZGqnPq`}9x=ehLOY#?ahc35{Yu^XbJ!nmczK6-+pXGUS(q{QX{g_^_Fo znZ47v0LEm)K@Tcz^1cr|bG$v2{O(=zg8R3zN{wE>U)3Y2(0c%$k%e%4m}FM5zb&&b zd14UBEvoYTId$C=CgR)*$6LU6ilu_*n?otuB&ggP0g#jXtjE6LV$jFhx9csgc4$?+g`SB-2eNttOO$7@JXWH;7_u(7fVb$kRQmFW#E=@K6na3Y>kqV(r(v12QJ>Vau3Hxd!L+B@jxtl3nD^4 z`cKr;vok9j8+bI|{7R;K%--S5!=_6wB4uT7&$BI(7|P9+Xg>V!)6+7{W3hHK2D~7W zlCT+kQR+9Zs-_f>pub(vP=fuOH$DHu7N$5$4`u(3QAtS1YH+)845d)l$wANny1P^S z<^v|C#j?ij4zl*KD9%~eiA>@ZQkX~raJk^Lv`!FH3;N8fntqSZxVXA;a&orT;Ns)& zkEc7tDg5S6yn|b5ss5-gu#*R*h*(8o$h>T@&2417r3>AA-O4|~un|AbDL{@= zRkQ_C+tR}>$UUo5N@i1gju-t|41Okb2K&@m%5Rf>#YLbC>s&En-@-WBZ@i#s=O?21gdr?#6M3t+3P0j z0ZcmOf6`)&ua@@1j#|5rP^2#|kM=$T`|{Y#?EdG^pGPMq zIKf7Fd3%G%j$38+!YQ6(Lf61R$<7Y1JI0kudn^7)L}cWrxbSc+`I+4uSKlOh7}7Re{WL! zY{Iy^qK&Io+p&|?bSJUz*E6`$8X=1N^@|FOPZkdyPWA2&pgwJVj|A|(c8 zgdPAL2rwmh`9Rxh!Z2@yVVVgD{MwmnuTjFs<2)RHio^^^s5d~-y`A^IDzfa5H!Zz( z?|a>)&_@gFj!mU`mo$Y|nHhG7itadfh&c0@!8vr!GFf~@^eM#fM8BkjAcjkTU3 zVDO=^F_gf@=!?0nZB(&>B7(z^WQ0E8A3Oz;ttS;96dDGgl<&d`{!$5Z#KuNFtB{}q z!vAtuiV_2f=24Jvqtx-#a(+S*PRCLdzBEmW+t*i<;$(FXP{}0_kGSOV7_u}^su?@$o+6hX2WBDDtg7`kg(*rN+7S_>1R8aPmXsd z9E_AD@7$pcHFd{0b8nVW>|4tHcgQ*}Jw|V?&yPr=K7W0of~P9l7?*K~UwV3HG)W(q zQE2kwdLwGesgLB``_pmt&QtXaG?`T3r+B4SMY+(G*FU$7u7)No zWA`~G zeFL_x#$~rGcq>a9(A?i<&Rrd79~Ke`pZt_@nr_5=RQk374ZV?|{9@s>=wd)dM>}~D zzq;zozq#+yC6FiF#-WWO;*w+25AqkW} zFDG=Zww?bD(^nFT?F*G=X0&R(y3Nfo^UwX0lMf5Sc|-?ECxmRT=2=`!>w6*`79*c- z{J89mJTDjqHPq-ppDEXyH&PFnnOE9g(Td6$(+4_u@V6`Vv-en(XjNu|BpsSKXfB_x z!Kzmt(7%6hkou4=71E&UQsuD+1(*cH?Yz9aj33(t=6f$RdWiuto#N#c>>BJD9Lz6i zhzP)XZK6Mm+f9zp!C4JW={pb}0@z5Flb>JC&rdXY6@lP3L@kh)+6_n0{weXWCK#b% zn9a+3j!#<7&0>63Su&2h_q$|C|<989EydN2OeFBoN z)@Pckt73aD?s*o8_Ss@3N`)`JcJpje3&hCw46HHZZS?o{x?CYCb5{2IhnpCX2@M$vB3hb z)k9{y6;g0=IxO)3P++XPNwjnt6h4hOzdR7hIBlQNI$|E)Y><=GcgncbP zvEm(TthV=$BW*rn{$P)np5^%_gvpxi-D{*72*HWuc%qIB$R!FhbX~Z`TV;smzb$eBUR%tIXn3cpSJY6`_1*kc6N@3 zCR$pLj%MoXO&ez9^szQr=nP&!-hZHtU(X$L1EBfQ0dTeRlGS%(whKEnF?EdkyazZ=R3vExKeQ=NwgC{Jz*>4eaH$>E%)tk zv;)L2+}u68HQx|ja7etag`xbk?%PkT ziW1(CTY43OI@*#GF}hO=b_pu3yNw?{?l#uhvEr0NiJy=+98u@enT<^BGqd|{Hl9~L z8XUlZyf++Nlik3~7y6;n*c3a`z2mp5eA%*NmzO`&wJqAQ9mK1n9~gGaz9BT}Sy5>$ z(XydV)U+9dE7R^BMS1R{(h?U-q#SA6UsSeRj#2*A} z8nsHOZJfN={QOHrg{x}6*ke(k`7qjl@#?%wY`5lWOMYXH*R6RzsQpa8Lhvq&VOpJi zUa^R@;F$QxxE+s>c4!}7`;-gT0uP&*os1($QgpfTq$<4F%7IJ#imUJZ?@DJhFOIA}MN?%<*w_eiDmq2Z30aVlqF zl7@pa3D8A+?LSQHR8zRxPas^5R3z^1MxNCZfA;!!QR7jaDG7zC7$rl zfmo3ef0B8C%Yj1uKpxol}#Pj%dB69{WnaQEG~@YzR`Bkp^()ruoDjF2g zm2bJkpE1P-p8G^$!RVk=d3h#L0w%wHXE9`|z&)v|IdjWI+J%3Gkr$lR&Ny`Cr_(2UQjGo3~4s%(L^nr(BEpM{yO)TFI~Kp~QI& zZP1bQF4Wmp(eFA@B@vjqQYCsdhg2odrhe${=*JDA33=|H5=^F?i9)loT@jjwB+C(MQ5)EiN)C51V4<3(rDQc`v}Iu( zMNv-%$QY$<>Liyh$F!t9vgLjYQt7v3)URHT`G2q782uA|j7=PMMLnw)8Lg?KjoGQ? z)~k&W!Mn-QN> zMb^F_vTES=%Tfp+-_`z|FJ$euIHB#Ml7R&$nR_ULP>~gpW07TU zR}@4kB-DdqUlMKjfh{^+tUGO*?3*5*4VC{J8Ntc1w6#qxEPmtuy4I;xvB!v#HAjNP3-=$?G}#--p3+DN%&Dk{q& zn(Sh(Up0ruV-7~zJjynO(f#+!=EN1b+CFDr-AKPnBxRyq>ghrG=#{{1Sh}@|?t4Y} z+HgeyuMCe)ds;VjejL7AO##d^*<>Ukxa0HDYfOE;idErb80CEuzE0(uiuaUvsjet& ztl=Hjx(B1&_lgHClBA0ot~5uVSb3pS6gX+x{j_$qnmrd7AMFb3ver8|&g1#sS~gD1 zwm3~$O;0y_cEVm>%qh;J)0q}Jp{|+IKG6HMX0xsuMlkHRs_1hWH^wu{ZZSspd~ru~ zL9WP|eeeM(-&j|fPIA{^wu!P`lx1niqgXKoI?A8c%1c~947OhJuj`MfZqwqh)Mj8% zm*sUV)4?1mFmkm=$8{l*dgtmP&Euk_ps0)}u-dA2M8^IZ1_lO8>*?;N4mGfnx>y|2 z5SU@I%ju0ml|!e+X!FAz?;w%J=N6WhDRT4!L7U^Z`-UwFGDN&d#8XPjnzN*!;((10 z0&$&$wdh@${R2&`Ho%DwBvn>&kvuFhxAaJ<9j5~t01QN&2yC3Up>D(uX8c;Pz@=g6oc!IkJz3@wwT@LNVQ!`7W0p$ z!hqpdxXNY>i(P6rA0{{1M6E1MOkR3X#(wO+5g0eA))DWOOPpj*C23?gFAkto@s+mn{lJQ`N9Z3 zTIgu@EA<403cPzC9J-39R zFQsDAXr<*IbZ82wl@=9t>BWF7WMXG8tge1z#L+Vr-%AXt`+<<$Txilu}YHwN4Mg=VEGLUbP zDhWdU^vGXz%0je#?E6krdojB&RzFZ2P5#iZNNE@4ny)2$T;K6qSk##{`ji`6yJG!> zk!k8k(r?-p?sQWpqh{Fe<#fB)itL%4!RSK^z?A(_0gMnquun#xRm-aO)m!)L9*VH$hCLq~`S1AHD5@LN9?g66edODM>o`+{!n;+~o<42Ql^#1kYuSN+%@HGvCIR3HpZ7T^m6wYv(07PI=PvvS0|TP~(5HZ2wXwk*_>yLVi-Y3}5F)BND_M~P=PpA_ z-*%b-9njdvhr+@qLyljGgKWiXdOC&!NH-q{SuvCSGp!~ys9J&Qi}XK6=-0WyU_>0; z)1y}T@TdOJ=S#txVwaq_1VPtr32czRWO2}hfK<9TLL60(sS(}k{^)V{ZN0Y1$H&n4 zD@<5gcE~$Tu!mbkGilxTL`uC)iSCKb$@%gO-uq~qzdp)DWj!s7apn>-+j&&J9j9!2 z?n3wb8;?U%WnzJ5o<|Wmk$zhNBForNIe zkfvY(PxZ(mj7+>86D)r}VG=RKDJ&cnw#$gCrK{^$1MiM?-q2#NGM0uLB6ZJ|9`&J2 zvEkpQT;w3N?0IDTdWFMpf)9to$xQUr^m}(u`4JHT{{oiA}@AviTl-oH;}CdQ7-&Q=9k&pOEr($|kXPa@qhlzA;J*-tw= z`jT-*%JW2Ja_&e;r6@?%E1`6|?xCPcyhK4|gUr(fs@7j$|3XIC%J}8&=AJHN$R7=L z176b50LXIdg6SF?KL0kdak2WC;uvbN*4_lhR6N(;t^@Cj7CXZ2y}Z1RX@HjL_1pJ& z-UChp+5?Pmk7k|uPI3q_@S9p!R_M7O5@T1`PI2Xad%-3&!( zoHFuyohLgDG-L~g56`b?>LkYdo!)IRfuqTyucXPqA{B^4ZCF4xZ)JSUL?(6JCIMPv zt~+u5l9~Q|$inCfz$9xgPX|W?Y*=w@)%G^kcTzUBr<#)Vx0lRu2`qkWY`g$s>QuQ= z>kT0MzgEisvA(`!USFo@?JeX!=Sqr)*H2DC(ZR>XB>~PA=%kxKDRxxx>HT#6GRx1nz&9?O2G)6&Dr^e z0R!|Hjd1?{4Iux+;t(%6J)p-O2grU8un<6o{fQeOtO&<)Cj|a!U7>alLSP7Zl&1m} zUS9r<&28FLB`!etGLH@0K6v=h%yAHkK4K*}Ep*H=lqocv$}PV(_@3r&G?zALqsfJ; zaHYQB1@q+K;%bWqyd5VGPe->tZ@QU}PXMU3DLY}|jmWY^X+<%+N$5^IC~U3RPK1mFk~z3?Oh} z0;ydo!pBmN(NvK<;DX$^N%vR!zq`6b(@;iSXj#rB7dVKrK>rfT^&Nkcf!E{EfGJ($ zcu;IaJan-qilgWW+d z6e_aS;3g($#!8+llRYTvzlyt?=iDZFy$%p!Wi1>dGctc2cctR~GdJ@8-8QrTZ~bdm aVqd0DQOL7DN literal 3312 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 Xs$Y7q`xpV-WH`*QR+w_*8xQ^iXAw+v diff --git a/__tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png b/__tests__/__screenshots__/NavbarTertiary_.test.jsx/desktop-navbar-tertiary-chromium-linux.png index 3d1c542d80e2889434f6fe5fdcdab3a3beee214b..a6be3b920e75e7fdfd27f9bcfd1f76cd80eef419 100644 GIT binary patch literal 4539 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 diff --git a/__tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png b/__tests__/__screenshots__/NavbarTertiary_.test.jsx/mobile-navbar-tertiary-chromium-linux.png index d90751662c8763c51864ce82aada5ef807f56f88..249d188630a4f1631fd94f97559ed18b3f4a52f8 100644 GIT binary patch literal 2362 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 From f4ec369fb5b05ec13b03ede530a9d60142c04728 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 16 Feb 2026 17:54:18 -0500 Subject: [PATCH 20/36] remove unused values --- app/root.res | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/root.res b/app/root.res index 970804c8a..cbddcf72d 100644 --- a/app/root.res +++ b/app/root.res @@ -38,17 +38,6 @@ open ReactRouter @react.component let default = () => { - let {pathname} = ReactRouter.useLocation() - let (_isOverlayOpen, setOverlayOpen) = React.useState(_ => false) - let (_isScrollLockEnabled, setIsScrollLockEnabled) = React.useState(_ => false) - - React.useEffect(() => { - // When the path changes close the sidebar and disable scroll lock - setOverlayOpen(_ => false) - setIsScrollLockEnabled(_ => false) - None - }, [pathname]) - From f66a9f11fa00a0f02b4c2fae9425f11e6b6c0b9c Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 16 Feb 2026 17:57:38 -0500 Subject: [PATCH 21/36] remove unused file --- src/components/Guide_Utils.res | 2 -- src/components/Sidebar.res | 25 ------------------------- 2 files changed, 27 deletions(-) delete mode 100644 src/components/Guide_Utils.res delete mode 100644 src/components/Sidebar.res diff --git a/src/components/Guide_Utils.res b/src/components/Guide_Utils.res deleted file mode 100644 index 43e05bbe4..000000000 --- a/src/components/Guide_Utils.res +++ /dev/null @@ -1,2 +0,0 @@ -let getGuidePages = async () => - (await Mdx.allMdx(~filterByPaths=["markdown-pages/guide"]))->Mdx.filterMdxPages("guide") diff --git a/src/components/Sidebar.res b/src/components/Sidebar.res deleted file mode 100644 index 635aefecf..000000000 --- a/src/components/Sidebar.res +++ /dev/null @@ -1,25 +0,0 @@ -type item = { - slug: string, - title: string, -} - -@react.component -let make = (~items) => { - -} From 5e5c0e184c3ee74cc9b9803a3a3a080bbe2ec29e Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 16 Feb 2026 18:03:17 -0500 Subject: [PATCH 22/36] tidying up --- react-router.config.mjs | 3 +-- rescript.json | 3 ++- src/Mdx.res | 42 ----------------------------------- src/layouts/SidebarLayout.res | 33 --------------------------- 4 files changed, 3 insertions(+), 78 deletions(-) diff --git a/react-router.config.mjs b/react-router.config.mjs index 8e6eaff70..ddb22da04 100644 --- a/react-router.config.mjs +++ b/react-router.config.mjs @@ -7,9 +7,8 @@ const mdx = init({ "markdown-pages/docs", "markdown-pages/community", "markdown-pages/syntax-lookup", - "markdown-pages/guide", ], - aliases: ["blog", "docs", "community", "syntax-lookup", "guide"], + aliases: ["blog", "docs", "community", "syntax-lookup"], }); const { stdlibPaths } = await import("./app/routes.jsx"); diff --git a/rescript.json b/rescript.json index 2903912dc..9214ceafd 100644 --- a/rescript.json +++ b/rescript.json @@ -10,7 +10,8 @@ "sources": [ { "dir": "__tests__", - "subdirs": true + "subdirs": true, + "type": "dev" }, { "dir": "app", diff --git a/src/Mdx.res b/src/Mdx.res index e10687c62..cd6b388db 100644 --- a/src/Mdx.res +++ b/src/Mdx.res @@ -222,45 +222,3 @@ let anchorLinkPlugin = (tree, _vfile) => { let anchorLinkPlugin = makePlugin(_options => (tree, vfile) => anchorLinkPlugin(tree, vfile)) let plugins = [remarkLinkPlugin, gfm, remarkReScriptPreludePlugin, anchorLinkPlugin] - -/** - This configures the MDX component to use our custom markdown components - */ -let components = { - // Replacing HTML defaults - "a": Markdown.A.make, - "blockquote": Markdown.Blockquote.make, - "code": Markdown.Code.make, - "h1": Markdown.H1.make, - "h2": Markdown.H2.make, - "h3": Markdown.H3.make, - "h4": Markdown.H4.make, - "h5": Markdown.H5.make, - "hr": Markdown.Hr.make, - "intro": Markdown.Intro.make, - "li": Markdown.Li.make, - "ol": Markdown.Ol.make, - "p": Markdown.P.make, - "pre": Markdown.Pre.make, - "strong": Markdown.Strong.make, - "table": Markdown.Table.make, - "th": Markdown.Th.make, - "thead": Markdown.Thead.make, - "td": Markdown.Td.make, - "ul": Markdown.Ul.make, - // These are custom components we provide - "Cite": Markdown.Cite.make, - "CodeTab": Markdown.CodeTab.make, - "Image": Markdown.Image.make, - "Info": Markdown.Info.make, - "Intro": Markdown.Intro.make, - "UrlBox": Markdown.UrlBox.make, - "Video": Markdown.Video.make, - "Warn": Markdown.Warn.make, - "CommunityContent": CommunityContent.make, - "WarningTable": WarningTable.make, - "Docson": DocsonLazy.make, - "Suspense": React.Suspense.make, -} - -let useMdx = () => useMdxComponent(~components) diff --git a/src/layouts/SidebarLayout.res b/src/layouts/SidebarLayout.res index 9ec63cd40..19a24f140 100644 --- a/src/layouts/SidebarLayout.res +++ b/src/layouts/SidebarLayout.res @@ -138,23 +138,6 @@ module Sidebar = { ) => { let isItemActive = (navItem: NavItem.t) => navItem.href === (route :> string) - // the height of the navbars above is fluid across pages, and it's easy to get it wrong - // so we calculate it dynamically here - let _sidebarTopOffset = isOpen - ? { - let mobileNavbarHeight = - Nullable.make(document->WebAPI.Document.getElementById("mobile-navbar")) - ->Nullable.map(el => el.clientHeight) - ->Nullable.getOr(0) - let docNavbarHeight = - Nullable.make(document->WebAPI.Document.getElementById("doc-navbar")) - ->Nullable.map(el => el.clientHeight) - ->Nullable.getOr(0) - - mobileNavbarHeight + docNavbarHeight + 8 - } - : 0 - let getActiveToc = (navItem: NavItem.t) => { if navItem.href === (route :> string) { activeToc @@ -165,9 +148,6 @@ module Sidebar = { <>
          Int.toString}px`, - // }} id="sidebar" className={( isOpen ? "fixed w-full left-0 h-full z-20 min-w-320" : "hidden " @@ -270,23 +250,10 @@ let make = ( ~categories: option>=?, ~children, ) => { - let (_isNavOpen, setNavOpen) = React.useState(() => false) - let location = ReactRouter.useLocation() let theme = ColorTheme.toCN(theme) - // TODO: post rr7 - this can most likely be removed - let (_isSidebarOpen, setSidebarOpen) = sidebarState - - let {pathname} = ReactRouter.useLocation() - - React.useEffect(() => { - setSidebarOpen(_ => false) - setNavOpen(_ => false) - None - }, [pathname]) - let pagination = switch categories { | Some(categories) => let items = categories->Array.flatMap(c => c.items) From 76414e8d22699c9fe5902ae1f8a0247ca4f02cb5 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Tue, 17 Feb 2026 09:26:57 -0500 Subject: [PATCH 23/36] fix version dropdown --- __tests__/VersionSelect_.test.res | 80 +++++++++++++++++++++++++++++++ src/components/VersionSelect.res | 7 ++- styles/main.css | 6 +-- 3 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 __tests__/VersionSelect_.test.res diff --git a/__tests__/VersionSelect_.test.res b/__tests__/VersionSelect_.test.res new file mode 100644 index 000000000..54943bdae --- /dev/null +++ b/__tests__/VersionSelect_.test.res @@ -0,0 +1,80 @@ +open Vitest + +test("renders current version label", async () => { + let screen = await render() + + let el = await screen->getByTestId("version-select") + await element(el)->toBeVisible + + let label = await screen->getByText("v12 (latest)") + await element(label)->toBeVisible +}) + +test("clicking button shows older versions", async () => { + let screen = await render() + + // Menu should be hidden initially + let v11 = await screen->getByText("v11") + await element(v11)->notToBeVisible + + // Click the trigger button + let button = await screen->getByRole(#button) + await button->click + + // Older versions should now be visible + let v11After = await screen->getByText("v11") + await element(v11After)->toBeVisible + + let v9 = await screen->getByText("v9.1 - v10.1") + await element(v9)->toBeVisible + + let v8 = await screen->getByText("v8.2 - v9.0") + await element(v8)->toBeVisible + + let v6 = await screen->getByText("v6.0 - v8.1") + await element(v6)->toBeVisible +}) + +test("clicking button again closes older versions", async () => { + let screen = await render() + + let button = await screen->getByRole(#button) + + // Open + await button->click + let v11 = await screen->getByText("v11") + await element(v11)->toBeVisible + + // Close + await button->click + let v11After = await screen->getByText("v11") + await element(v11After)->notToBeVisible +}) + +test("multiple instances have unique popover IDs", async () => { + let screen = await render( +
          +
          + +
          +
          + +
          +
          , + ) + + let first = await screen->getByTestId("first") + let second = await screen->getByTestId("second") + + // Click the button in the first instance + let firstButton = await first->getByRole(#button) + await firstButton->click + + // First instance menu should be visible + let firstV11 = await first->getByText("v11") + await element(firstV11)->toBeVisible + + // Second instance menu should remain hidden + let secondV11 = await second->getByText("v11") + await element(secondV11)->notToBeVisible +}) diff --git a/src/components/VersionSelect.res b/src/components/VersionSelect.res index eb68d9d7f..cf58b7d89 100644 --- a/src/components/VersionSelect.res +++ b/src/components/VersionSelect.res @@ -23,18 +23,21 @@ let version = "v12" @react.component let make = () => { + let id = React.useId() + let popoverId = "older-versions-" ++ id + let children = Array.map(olderVersions, ver => { {React.string(ver.label)} })
          -
          +
          {switch state { | Active => From a15986e211738f6604a1b4b17c5f054df2ac214f Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Wed, 18 Feb 2026 13:50:58 -0500 Subject: [PATCH 25/36] Update src/components/BreadCrumbs.res Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/components/BreadCrumbs.res | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/BreadCrumbs.res b/src/components/BreadCrumbs.res index 4d1001488..1ef78c297 100644 --- a/src/components/BreadCrumbs.res +++ b/src/components/BreadCrumbs.res @@ -8,14 +8,17 @@ let make = () => {
          {paths - ->Array.mapWithIndex((path, i) => - <> - + ->Array.mapWithIndex((path, i) => { + let cumulativePath = + "/" ++ (paths->Array.slice(0, i + 1)->Array.joinWith("/")) + + + {React.string(path->String.capitalize)} {i == lastIndex ? React.null : React.string(" / ")} - - ) + + }) ->React.array}
          } From 588fc927c256c8d955958a2e872d560485a877c5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 09:09:25 -0500 Subject: [PATCH 26/36] Fix mobile overlay not closing on navigation link clicks (#1198) * Initial plan * Add onClick handlers to close mobile overlay for all navigation links Co-authored-by: jderochervlk <60623931+jderochervlk@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jderochervlk <60623931+jderochervlk@users.noreply.github.com> --- src/components/NavbarMobileOverlay.res | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/NavbarMobileOverlay.res b/src/components/NavbarMobileOverlay.res index b0dde37a6..362a22473 100644 --- a/src/components/NavbarMobileOverlay.res +++ b/src/components/NavbarMobileOverlay.res @@ -22,6 +22,7 @@ module MobileNav = { prefetch={#intent} to=#"/blog" className={linkOrActiveLinkSubroute(~target=#"/blog", ~route)} + onClick=toggleMobileOverlay > {React.string("Blog")} @@ -31,6 +32,7 @@ module MobileNav = { prefetch={#intent} to=#"/community/overview" className={linkOrActiveLink(~target=#"/community/overview", ~route)} + onClick=toggleMobileOverlay > {React.string("Community")} @@ -40,6 +42,7 @@ module MobileNav = { prefetch={#intent} to=#"/packages" className={linkOrActiveLink(~target=#"/packages", ~route)} + onClick=toggleMobileOverlay > {React.string("Packages")} @@ -50,22 +53,23 @@ module MobileNav = { rel="noopener noreferrer" className=extLink ariaLabel="X (formerly Twitter)" + onClick=closeMobileOverlay > {React.string("X")}
        • - + {React.string("Bluesky")}
        • - + {React.string("GitHub")}
        • - + {React.string("Forum")}
        • From 0f26f30e622617f4735ff9149fdeade5460a8844 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sat, 21 Feb 2026 09:14:24 -0500 Subject: [PATCH 27/36] PR feedback. --- __tests__/NavbarPrimary_.test.res | 57 ++++++++++++++++--------------- src/components/BreadCrumbs.res | 3 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/__tests__/NavbarPrimary_.test.res b/__tests__/NavbarPrimary_.test.res index 02c7f7469..23c7b50e3 100644 --- a/__tests__/NavbarPrimary_.test.res +++ b/__tests__/NavbarPrimary_.test.res @@ -12,17 +12,17 @@ test("desktop has everything visible", async () => { let leftContent = await screen->getByTestId("navbar-primary-left-content") - await element(leftContent->getByText("Docs"))->toBeVisible - await element(leftContent->getByText("Playground"))->toBeVisible - await element(leftContent->getByText("Blog"))->toBeVisible - await element(leftContent->getByText("Community"))->toBeVisible + await element(await leftContent->getByText("Docs"))->toBeVisible + await element(await leftContent->getByText("Playground"))->toBeVisible + await element(await leftContent->getByText("Blog"))->toBeVisible + await element(await leftContent->getByText("Community"))->toBeVisible let rightContent = await screen->getByTestId("navbar-primary-right-content") - await element(rightContent->getByLabelText("Github"))->toBeVisible - await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible - await element(rightContent->getByLabelText("Bluesky"))->toBeVisible - await element(rightContent->getByLabelText("Forum"))->toBeVisible + await element(await rightContent->getByLabelText("GitHub"))->toBeVisible + await element(await rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(await rightContent->getByLabelText("Bluesky"))->toBeVisible + await element(await rightContent->getByLabelText("Forum"))->toBeVisible let navbar = await screen->getByTestId("navbar-primary") @@ -40,17 +40,17 @@ test("tablet has everything visible", async () => { let leftContent = await screen->getByTestId("navbar-primary-left-content") - await element(leftContent->getByText("Docs"))->toBeVisible - await element(leftContent->getByText("Playground"))->toBeVisible - await element(leftContent->getByText("Blog"))->toBeVisible - await element(leftContent->getByText("Community"))->toBeVisible + await element(await leftContent->getByText("Docs"))->toBeVisible + await element(await leftContent->getByText("Playground"))->toBeVisible + await element(await leftContent->getByText("Blog"))->toBeVisible + await element(await leftContent->getByText("Community"))->toBeVisible let rightContent = await screen->getByTestId("navbar-primary-right-content") - await element(rightContent->getByLabelText("Github"))->toBeVisible - await element(rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible - await element(rightContent->getByLabelText("Bluesky"))->toBeVisible - await element(rightContent->getByLabelText("Forum"))->toBeVisible + await element(await rightContent->getByLabelText("GitHub"))->toBeVisible + await element(await rightContent->getByLabelText("X (formerly Twitter)"))->toBeVisible + await element(await rightContent->getByLabelText("Bluesky"))->toBeVisible + await element(await rightContent->getByLabelText("Forum"))->toBeVisible let navbar = await screen->getByTestId("navbar-primary") @@ -68,19 +68,20 @@ test("phone has some things hidden and a mobile nav that can be toggled", async let leftContent = await screen->getByTestId("navbar-primary-left-content") - await element(leftContent->getByText("Docs"))->toBeVisible - await element(leftContent->getByText("Playground"))->notToBeVisible - await element(leftContent->getByText("Blog"))->notToBeVisible - await element(leftContent->getByText("Community"))->notToBeVisible + await element(await leftContent->getByText("Docs"))->toBeVisible + await element(await leftContent->getByText("Playground"))->notToBeVisible + await element(await leftContent->getByText("Blog"))->notToBeVisible + await element(await leftContent->getByText("Community"))->notToBeVisible let rightContent = await screen->getByTestId("navbar-primary-right-content") - await element(rightContent->getByLabelText("Github"))->notToBeVisible - await element(rightContent->getByLabelText("X (formerly Twitter)"))->notToBeVisible - await element(rightContent->getByLabelText("Bluesky"))->notToBeVisible - await element(rightContent->getByLabelText("Forum"))->notToBeVisible + await element(await rightContent->getByLabelText("GitHub"))->notToBeVisible + await element(await rightContent->getByLabelText("X (formerly Twitter)"))->notToBeVisible + await element(await rightContent->getByLabelText("Bluesky"))->notToBeVisible + await element(await rightContent->getByLabelText("Forum"))->notToBeVisible - await element(screen->getByTestId("mobile-nav"))->notToBeVisible + let mobileNav = await screen->getByTestId("mobile-nav") + await element(mobileNav)->notToBeVisible let button = await screen->getByTestId("toggle-mobile-overlay") @@ -88,13 +89,13 @@ test("phone has some things hidden and a mobile nav that can be toggled", async await button->click - let mobileNav = await screen->getByTestId("mobile-nav") + let mobileNavAfterOpen = await screen->getByTestId("mobile-nav") - await element(mobileNav)->toBeVisible + await element(mobileNavAfterOpen)->toBeVisible let navbar = await screen->getByTestId("navbar-primary") await element(navbar)->toMatchScreenshot("mobile-navbar-primary") - await element(mobileNav)->toMatchScreenshot("mobile-overlay-navbar-primary") + await element(mobileNavAfterOpen)->toMatchScreenshot("mobile-overlay-navbar-primary") }) diff --git a/src/components/BreadCrumbs.res b/src/components/BreadCrumbs.res index 1ef78c297..93b33c008 100644 --- a/src/components/BreadCrumbs.res +++ b/src/components/BreadCrumbs.res @@ -9,8 +9,7 @@ let make = () => {
          {paths ->Array.mapWithIndex((path, i) => { - let cumulativePath = - "/" ++ (paths->Array.slice(0, i + 1)->Array.joinWith("/")) + let cumulativePath = "/" ++ paths->Array.slice(~start=0, ~end=i + 1)->Array.join("/") From 17f7d42ca895ce7b5b27ce6db579fc2e55331517 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sat, 21 Feb 2026 09:15:10 -0500 Subject: [PATCH 28/36] PR feedback --- src/components/NavbarMobileOverlay.res | 21 ++++++++++++++++++--- src/components/NavbarTertiary.res | 7 ++++++- src/components/Search.res | 7 ++++++- vitest.config.mjs | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/components/NavbarMobileOverlay.res b/src/components/NavbarMobileOverlay.res index 362a22473..d0fb83d89 100644 --- a/src/components/NavbarMobileOverlay.res +++ b/src/components/NavbarMobileOverlay.res @@ -59,17 +59,32 @@ module MobileNav = {
        • - + {React.string("Bluesky")}
        • - + {React.string("GitHub")}
        • - + {React.string("Forum")}
        • diff --git a/src/components/NavbarTertiary.res b/src/components/NavbarTertiary.res index e584e4851..3a5da47e6 100644 --- a/src/components/NavbarTertiary.res +++ b/src/components/NavbarTertiary.res @@ -32,7 +32,12 @@ let make = (~sidebar: option=?, ~children) => { ${isDocRoute(~route=pathname) ? "top-28" : "top-16"}`} >
          -
          { }, [setState]) <> - {switch state { diff --git a/vitest.config.mjs b/vitest.config.mjs index dce076124..49417630f 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -1,5 +1,5 @@ import { defineConfig } from "vitest/config"; -import { playwright, defineBrowserCommand } from "@vitest/browser-playwright"; +import { playwright } from "@vitest/browser-playwright"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; From 2605d2a58c0abc7ac85b126215db5465d01f02ad Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sat, 21 Feb 2026 09:21:20 -0500 Subject: [PATCH 29/36] cleanup --- src/components/NavbarSecondary.res | 8 -------- src/components/NavbarUtils.res | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/NavbarSecondary.res b/src/components/NavbarSecondary.res index b9ec959aa..8e25b3434 100644 --- a/src/components/NavbarSecondary.res +++ b/src/components/NavbarSecondary.res @@ -1,14 +1,6 @@ open ReactRouter open NavbarUtils -module MobileDrawerButton = { - @react.component - let make = (~hidden: bool) => - -} - @react.component let make = () => { let location = ReactRouter.useLocation() diff --git a/src/components/NavbarUtils.res b/src/components/NavbarUtils.res index b20dd5b8f..0f639ffdb 100644 --- a/src/components/NavbarUtils.res +++ b/src/components/NavbarUtils.res @@ -3,7 +3,7 @@ let activeLink = "font-medium text-fire-30 border-b border-fire" let isActiveLink = (~includes: string, ~excludes: option=?, ~route: Path.t) => { let route = (route :> string) - // includes means we want the lnk to be active if it contains the expected text + // includes means we want the link to be active if it contains the expected text let includes = route->String.includes(includes) // excludes allows us to not have links be active even if they do have the includes text let excludes = switch excludes { From 04ba370b3cc64b07aaae99c540e4530c396dcdc1 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Mon, 23 Feb 2026 09:55:02 -0500 Subject: [PATCH 30/36] Fix sidebar scrolling when it shouldn't --- src/layouts/SidebarLayout.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/SidebarLayout.res b/src/layouts/SidebarLayout.res index 19a24f140..bc63fde45 100644 --- a/src/layouts/SidebarLayout.res +++ b/src/layouts/SidebarLayout.res @@ -155,7 +155,7 @@ module Sidebar = { >