popovers
This commit is contained in:
		
							parent
							
								
									cb89cce183
								
							
						
					
					
						commit
						8bfee04c8c
					
				
					 10 changed files with 143 additions and 16 deletions
				
			
		| 
						 | 
				
			
			@ -2,10 +2,30 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
 | 
			
		|||
import { Fragment, jsx, jsxs } from 'preact/jsx-runtime'
 | 
			
		||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
 | 
			
		||||
 | 
			
		||||
function Content({ tree }: QuartzComponentProps) {
 | 
			
		||||
  // @ts-ignore (preact makes it angry)
 | 
			
		||||
  const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
			
		||||
  return <article>{content}</article>
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import popoverScript from './scripts/popover.inline'
 | 
			
		||||
import popoverStyle from './styles/popover.scss'
 | 
			
		||||
 | 
			
		||||
interface Options {
 | 
			
		||||
  enablePopover: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default (() => Content) satisfies QuartzComponentConstructor
 | 
			
		||||
const defaultOptions: Options = {
 | 
			
		||||
  enablePopover: true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ((opts?: Partial<Options>) => {
 | 
			
		||||
  function Content({ tree }: QuartzComponentProps) {
 | 
			
		||||
    // @ts-ignore (preact makes it angry)
 | 
			
		||||
    const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
 | 
			
		||||
    return <article>{content}</article>
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const enablePopover = opts?.enablePopover ?? defaultOptions.enablePopover
 | 
			
		||||
  if (enablePopover) {
 | 
			
		||||
    Content.afterDOMLoaded = popoverScript
 | 
			
		||||
    Content.css = popoverStyle
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Content
 | 
			
		||||
}) satisfies QuartzComponentConstructor
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										41
									
								
								quartz/components/scripts/popover.inline.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								quartz/components/scripts/popover.inline.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import { computePosition, inline, shift, autoPlacement } from "@floating-ui/dom"
 | 
			
		||||
 | 
			
		||||
document.addEventListener("nav", () => {
 | 
			
		||||
  const links = [...document.getElementsByClassName("internal")] as HTMLLinkElement[]
 | 
			
		||||
  const p = new DOMParser()
 | 
			
		||||
  for (const link of links) {
 | 
			
		||||
    link.addEventListener("mouseenter", async ({ clientX, clientY }) => {
 | 
			
		||||
      if (link.dataset.fetchedPopover === "true") return
 | 
			
		||||
      const url = link.href
 | 
			
		||||
      const contents = await fetch(`${url}`)
 | 
			
		||||
        .then((res) => res.text())
 | 
			
		||||
        .catch((err) => {
 | 
			
		||||
          console.error(err)
 | 
			
		||||
        })
 | 
			
		||||
      if (!contents) return
 | 
			
		||||
      const html = p.parseFromString(contents, "text/html")
 | 
			
		||||
      const elts = [...html.getElementsByClassName("popover-hint")]
 | 
			
		||||
      if (elts.length === 0) return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      const popoverElement = document.createElement("div")
 | 
			
		||||
      popoverElement.classList.add("popover")
 | 
			
		||||
      elts.forEach(elt => popoverElement.appendChild(elt))
 | 
			
		||||
 | 
			
		||||
      const { x, y } = await computePosition(link, popoverElement, {
 | 
			
		||||
        middleware: [inline({
 | 
			
		||||
          x: clientX,
 | 
			
		||||
          y: clientY
 | 
			
		||||
        }), shift(), autoPlacement()]
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      Object.assign(popoverElement.style, {
 | 
			
		||||
        left: `${x}px`,
 | 
			
		||||
        top: `${y}px`,
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      link.appendChild(popoverElement)
 | 
			
		||||
      link.dataset.fetchedPopover = "true"
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -22,11 +22,13 @@ function toggleToc(this: HTMLElement) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function setupToc() {
 | 
			
		||||
  const toc = document.getElementById("toc")!
 | 
			
		||||
  const content = toc.nextElementSibling as HTMLElement
 | 
			
		||||
  content.style.maxHeight = content.scrollHeight + "px"
 | 
			
		||||
  toc.removeEventListener("click", toggleToc)
 | 
			
		||||
  toc.addEventListener("click", toggleToc)
 | 
			
		||||
  const toc = document.getElementById("toc")
 | 
			
		||||
  if (toc) {
 | 
			
		||||
    const content = toc.nextElementSibling as HTMLElement
 | 
			
		||||
    content.style.maxHeight = content.scrollHeight + "px"
 | 
			
		||||
    toc.removeEventListener("click", toggleToc)
 | 
			
		||||
    toc.addEventListener("click", toggleToc)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.addEventListener("resize", setupToc)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										43
									
								
								quartz/components/styles/popover.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								quartz/components/styles/popover.scss
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
@keyframes dropin {
 | 
			
		||||
  0% {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
  }
 | 
			
		||||
  50% {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.popover {
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  overflow: scroll;
 | 
			
		||||
  width: 30rem;
 | 
			
		||||
  height: 20rem;
 | 
			
		||||
  padding: 0 1rem;
 | 
			
		||||
  margin-top: -1rem;
 | 
			
		||||
  border: 1px solid var(--lightgray);
 | 
			
		||||
  background-color: var(--light);
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  box-shadow: 6px 6px 36px 0 rgba(0,0,0,0.25);
 | 
			
		||||
 | 
			
		||||
  font-weight: initial;
 | 
			
		||||
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: opacity 0.2s ease, visibility 0.2s ease;
 | 
			
		||||
 | 
			
		||||
  @media all and (max-width: 600px) {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover .popover, .popover:hover {
 | 
			
		||||
  animation: dropin 0.5s ease;
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  visibility: visible;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue