From 51dc3f899a1f38d4db37a48ca764bee2618f39a7 Mon Sep 17 00:00:00 2001 From: jderochervlk Date: Sun, 8 Feb 2026 14:09:29 -0500 Subject: [PATCH 01/30] 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/30] 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) => { -