use simple script for popover menu
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
import React from "react"
|
||||
import { guideLinks } from "../data/guide-links"
|
||||
import { mainLinks } from "../data/main-links"
|
||||
import { createHydrater } from "../helpers/hydration"
|
||||
import { linkClass } from "../styles/components"
|
||||
import { AppLink } from "./app-link"
|
||||
import type { MainNavigationMobileMenuData } from "./main-navigation-mobile-menu"
|
||||
|
||||
const MenuHydrater = await createHydrater<MainNavigationMobileMenuData>(
|
||||
new URL("./main-navigation-mobile-menu.tsx", import.meta.url).pathname,
|
||||
)
|
||||
import { PopoverMenu } from "./popover-menu"
|
||||
|
||||
export function MainNavigation() {
|
||||
return (
|
||||
@@ -22,7 +17,23 @@ export function MainNavigation() {
|
||||
))}
|
||||
</div>
|
||||
<div className="md:hidden" id="main-navigation-popover">
|
||||
<MenuHydrater data={{ guideLinks }} />
|
||||
<PopoverMenu>
|
||||
{mainLinks.map((link) => (
|
||||
<AppLink
|
||||
{...link}
|
||||
key={link.to}
|
||||
className={PopoverMenu.itemClass}
|
||||
/>
|
||||
))}
|
||||
<hr className="border-0 h-[2px] bg-black/50" />
|
||||
{guideLinks.map((link) => (
|
||||
<AppLink
|
||||
{...link}
|
||||
key={link.to}
|
||||
className={PopoverMenu.itemClass}
|
||||
/>
|
||||
))}
|
||||
</PopoverMenu>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
||||
38
packages/docs-new/src/components/popover-menu.client.tsx
Normal file
38
packages/docs-new/src/components/popover-menu.client.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
const menus = document.querySelectorAll("[data-popover]")
|
||||
|
||||
for (const menu of menus) {
|
||||
const button = menu.querySelector<HTMLButtonElement>("[data-popover-button]")!
|
||||
|
||||
const panel = menu.querySelector<HTMLDivElement>("[data-popover-panel]")!
|
||||
const panelClasses = clsx`${panel.className} transition-all`
|
||||
|
||||
const visibleClass = clsx`${panelClasses} visible opacity-100 translate-y-0`
|
||||
const hiddenClass = clsx`${panelClasses} invisible opacity-0 translate-y-2`
|
||||
|
||||
let visible = false
|
||||
|
||||
const setVisible = (newVisible: boolean) => {
|
||||
visible = newVisible
|
||||
panel.className = visible ? visibleClass : hiddenClass
|
||||
|
||||
if (!visible) return
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const handleClose = (event: MouseEvent) => {
|
||||
if (panel.contains(event.target as Node)) return
|
||||
setVisible(false)
|
||||
window.removeEventListener("click", handleClose)
|
||||
}
|
||||
window.addEventListener("click", handleClose)
|
||||
})
|
||||
}
|
||||
|
||||
const toggleVisible = () => setVisible(!visible)
|
||||
|
||||
button.addEventListener("click", toggleVisible)
|
||||
|
||||
setVisible(false)
|
||||
panel.hidden = false
|
||||
}
|
||||
@@ -1,63 +1,22 @@
|
||||
import { MenuAlt4Icon } from "@heroicons/react/outline"
|
||||
import { useRect } from "@reach/rect"
|
||||
import clsx from "clsx"
|
||||
import React, { useRef, useState } from "react"
|
||||
import { FocusOn } from "react-focus-on"
|
||||
import React from "react"
|
||||
import { linkClass } from "../styles/components"
|
||||
|
||||
// todo: remove useRect usage and rely on css absolute positioning instead
|
||||
export function PopoverMenu({ children }: { children: React.ReactNode }) {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const buttonRect = useRect(buttonRef)
|
||||
|
||||
const panelRef = useRef<HTMLDivElement>(null)
|
||||
const panelRect = useRect(panelRef)
|
||||
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
title="Menu"
|
||||
className={linkClass}
|
||||
onClick={() => setVisible(!visible)}
|
||||
ref={buttonRef}
|
||||
>
|
||||
<div data-popover className="relative">
|
||||
<button data-popover-button title="Menu" className={linkClass}>
|
||||
<MenuAlt4Icon className="w-6" />
|
||||
</button>
|
||||
|
||||
<FocusOn
|
||||
enabled={visible}
|
||||
onClickOutside={() => setVisible(false)}
|
||||
onEscapeKey={() => setVisible(false)}
|
||||
<div
|
||||
data-popover-panel
|
||||
hidden
|
||||
className="absolute w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto right-0 top-[calc(100%+8px)]"
|
||||
>
|
||||
<div
|
||||
className="fixed"
|
||||
style={{
|
||||
left: (buttonRect?.right ?? 0) - (panelRect?.width ?? 0),
|
||||
top: (buttonRect?.bottom ?? 0) + 8,
|
||||
}}
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"transition-all",
|
||||
visible
|
||||
? "opacity-100 visible"
|
||||
: "translate-y-2 opacity-0 invisible",
|
||||
)}
|
||||
>
|
||||
<div ref={panelRef}>
|
||||
<div className="w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusOn>
|
||||
</>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
26
packages/docs-new/src/helpers/serve-compiled-script.ts
Normal file
26
packages/docs-new/src/helpers/serve-compiled-script.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { build } from "esbuild"
|
||||
import type { RequestHandler } from "express"
|
||||
import { readFile } from "node:fs/promises"
|
||||
import { dirname } from "node:path"
|
||||
|
||||
export async function serveCompiledScript(
|
||||
scriptFilePath: string,
|
||||
): Promise<RequestHandler> {
|
||||
const scriptBuild = await build({
|
||||
bundle: true,
|
||||
stdin: {
|
||||
contents: await readFile(scriptFilePath, "utf-8"),
|
||||
sourcefile: scriptFilePath,
|
||||
loader: "tsx",
|
||||
resolveDir: dirname(scriptFilePath),
|
||||
},
|
||||
target: ["chrome89", "firefox89"],
|
||||
format: "esm",
|
||||
write: false,
|
||||
})
|
||||
|
||||
return (req, res) => {
|
||||
res.setHeader("Content-Type", "application/javascript")
|
||||
res.end(scriptBuild.outputFiles[0]!.contents)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ export function Html({
|
||||
<link href="/tailwind.css" rel="stylesheet" />
|
||||
<link href="/prism-theme.css" rel="stylesheet" />
|
||||
|
||||
<script type="module" src="/popover-menu.client.js" />
|
||||
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
|
||||
@@ -7,6 +7,7 @@ import pinoHttp from "pino-http"
|
||||
import * as React from "react"
|
||||
import { renderMarkdownFile } from "./helpers/markdown"
|
||||
import { sendJsx } from "./helpers/send-jsx"
|
||||
import { serveCompiledScript } from "./helpers/serve-compiled-script"
|
||||
import { serveFile } from "./helpers/serve-file"
|
||||
import { serveTailwindCss } from "./helpers/tailwind"
|
||||
import DocsPage from "./pages/docs"
|
||||
@@ -25,6 +26,13 @@ const app = express()
|
||||
serveFile(new URL("./styles/prism-theme.css", import.meta.url).pathname),
|
||||
)
|
||||
|
||||
.get(
|
||||
"/popover-menu.client.js",
|
||||
await serveCompiledScript(
|
||||
new URL("./components/popover-menu.client.tsx", import.meta.url).pathname,
|
||||
),
|
||||
)
|
||||
|
||||
.get("/docs/*", async (req: Request<{ 0: string }>, res) => {
|
||||
const { html, data } = await renderMarkdownFile(
|
||||
`src/docs/${req.params[0]}.md`,
|
||||
|
||||
Reference in New Issue
Block a user