From 5290e24d9560049318c19906646ffa9d230ddec1 Mon Sep 17 00:00:00 2001 From: olayway Date: Wed, 18 May 2022 09:36:34 +0200 Subject: [PATCH] [site/components][f]: floatingui tooltip + arrow --- site/components/Anchor.js | 109 ++++++++++++++++++++-------------- site/components/Tooltip.js | 61 +++++++++++++------ site/utils/absolutePath.js | 1 - site/utils/documentExtract.js | 16 +++++ 4 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 site/utils/documentExtract.js diff --git a/site/components/Anchor.js b/site/components/Anchor.js index 7a719b3..03dc895 100644 --- a/site/components/Anchor.js +++ b/site/components/Anchor.js @@ -1,13 +1,19 @@ +import ReactDOM from 'react-dom' import { useRouter } from 'next/router' -import { useState, useEffect, Fragment } from 'react' -import { useFloating, useHover, useInteractions } from '@floating-ui/react-dom-interactions' -import { unified } from 'unified' -import rehypeParse from 'rehype-parse' -import find from 'unist-util-find' -import { toString } from 'hast-util-to-string' +import { useState, useEffect, useRef, Fragment } from 'react' +import { + useFloating, + useHover, + useInteractions, + arrow, + autoPlacement, + autoUpdate, + offset +} from '@floating-ui/react-dom-interactions' -import { Tooltip } from './Tooltip' import getAbsolutePath from '../utils/absolutePath' +import documentExtract from '../utils/documentExtract' +import { Tooltip } from './Tooltip' // TODO cancel request on mouseleave when it hasn't been fulfilled yet @@ -15,30 +21,47 @@ import getAbsolutePath from '../utils/absolutePath' export const Anchor = (props) => { const { href } = props; const router = useRouter(); - const [ open, setOpen ] = useState(false); - const [ loaded, setLoaded ] = useState(false); + const arrowRef = useRef(null); + + const [ showTooltip, setShowTooltip ] = useState(false); const [ preview, setPreview ] = useState(""); - const { x, y, reference, floating, strategy, context } = useFloating({ - open, - onOpenChange: setOpen + const [ previewLoaded, setPreviewLoaded ] = useState(false); + + const { + x, + y, + reference, + floating, + placement, + strategy, + context, + middlewareData: { arrow: { x: arrowX, y: arrowY } = {}} + } = useFloating({ + open: showTooltip, + onOpenChange: setShowTooltip, + whileElementsMounted: autoUpdate, + middleware: [ + offset(5), + autoPlacement(), + arrow({ element: arrowRef, padding: 4 }) + ] }); const { getReferenceProps, getFloatingProps } = useInteractions([ useHover(context, props) ]); useEffect(() => { - if (open) { + if (showTooltip) { fetchPreview(); } - }, [open]) + }, [showTooltip]) const fetchPreview = async () => { - setLoaded(false); + setPreviewLoaded(false); const basePath = "http://localhost:3000"; // TODO const currentPath = router.asPath; const relativePath = props.href.split(".")[0]; // TBD temp remove .md const absolutePath = getAbsolutePath({ currentPath, basePath, relativePath }); - console.log(`Fetching: ${absolutePath}`); const response = await fetch(absolutePath); if (response.status !== 200) { @@ -47,20 +70,10 @@ export const Anchor = (props) => { return } const html = await response.text(); - const hast = unified().use(rehypeParse).parse(html); - console.log(hast) - const article = find(hast, (node) => { - return node.tagName === "article" - }) - const main = find(article, (node) => { - return node.tagName === "main" - }) - const p = find(main, (node) => { - return node.tagName === "p" - }) + const extract = documentExtract(html); - setPreview(toString(p)); - setLoaded(true); + setPreview(extract); + setPreviewLoaded(true); } if ( @@ -71,21 +84,29 @@ export const Anchor = (props) => { ) { return - {open && loaded && ( - // TODO temp span - - { preview } - + {( // TODO temp client only + typeof window !== 'undefined' && window.document && + ReactDOM.createPortal( + ( + { preview } + + ), document.body + ) )} ; } diff --git a/site/components/Tooltip.js b/site/components/Tooltip.js index 7a17402..4b90e2d 100644 --- a/site/components/Tooltip.js +++ b/site/components/Tooltip.js @@ -1,28 +1,53 @@ -const tooltipTextStyles = (theme) => ({ +import React from 'react'; + + +const tooltipStyles = (theme) => ({ + height: 'auto', + // maxWidth: '30rem', padding: '16px 22px', - fontSize: '11px', background: theme === 'light' ? '#fff' : '#000', - // color: theme === 'light' ? 'black' : 'white', - pointerEvents: 'none', + color: theme === 'light' ? 'rgb(99, 98, 98)' : '#A8A8A8', borderRadius: '4px', - position: 'absolute' + boxShadow: 'rgba(0, 0, 0, 0.55) 0px 0px 16px -3px', + fontSize: '90%' }) -// const tooltipBoxStyles = (theme) => ({ -// color: theme === 'light' ? 'rgb(99, 98, 98)' : '#A8A8A8', -// transition: "0.1s", -// width: "50vw" -// }) +const tooltipArrowStyles = ({ theme, x, y, side }) => ({ + position: "absolute", + left: x != null ? `${x}px` : '', + top: y != null ? `${y}px` : '', + right: '', + bottom: '', + [side]: '-4px', + height: "8px", + width: "8px", + background: theme === 'light' ? '#fff' : '#000', + transform: "rotate(45deg)" +}) + +export const Tooltip = React.forwardRef((props, ref) => { + const { theme, children, arrowRef, arrowX, arrowY, placement, ...tooltipProps } = props; + + console.log({ arrowRef, arrowX, arrowY, placement }); + + const arrowPlacement = { + top: 'bottom', + right: 'left', + bottom: 'top', + left: 'right', + }[placement.split('-')[0]]; -export const Tooltip = (props) => { - const { theme, content } = props; return ( -
-
- { content } +
+
+ { children }
- {/*
{wikiLogo(theme)}
*/} - {/* {arrow(theme)} */} +
) -} +}) diff --git a/site/utils/absolutePath.js b/site/utils/absolutePath.js index d12126a..441bdcb 100644 --- a/site/utils/absolutePath.js +++ b/site/utils/absolutePath.js @@ -3,7 +3,6 @@ const absolutePath = ({ currentPath, basePath, relativePath }) => { absolutePath.pop(); // remove current page name absolutePath.unshift(basePath); absolutePath.push(relativePath); - console.log(absolutePath); return absolutePath.join("/"); }; diff --git a/site/utils/documentExtract.js b/site/utils/documentExtract.js new file mode 100644 index 0000000..8f6ab55 --- /dev/null +++ b/site/utils/documentExtract.js @@ -0,0 +1,16 @@ +import { unified } from 'unified' +import rehypeParse from 'rehype-parse' +import find from 'unist-util-find' +import { toString } from 'hast-util-to-string' + + +// get first paragraph inside article's main tag +const documentExtract = (htmlString) => { + const hast = unified().use(rehypeParse).parse(htmlString); + const article = find(hast, (node) => node.tagName === "article"); + const main = find(article, (node) => node.tagName === "main"); + const paragraph = find(main, (node) => node.tagName === "p"); + return toString(paragraph); +} + +export default documentExtract;