[site/components][f]: floatingui tooltip + arrow

This commit is contained in:
olayway 2022-05-18 09:36:34 +02:00
parent 0b65f7d404
commit 5290e24d95
4 changed files with 124 additions and 63 deletions

View File

@ -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 <Fragment>
<a {...props} {...getReferenceProps({ref: reference})} />
{open && loaded && (
// TODO temp span
<span
{...getFloatingProps({
ref: floating,
className: "tooltip",
style: {
position: strategy,
left: x ?? '',
top: y ?? '',
},
})}
>
{ preview }
</span>
{( // TODO temp client only
typeof window !== 'undefined' && window.document &&
ReactDOM.createPortal(
(<Tooltip
{...getFloatingProps({
ref: floating,
theme: 'light',
arrowRef,
arrowX,
arrowY,
placement,
style: {
position: strategy,
visibility: showTooltip && previewLoaded ? 'visible' : 'hidden',
left: x ?? '',
top: y ?? '',
},
})}
>
{ preview }
</Tooltip>
), document.body
)
)}
</Fragment>;
}

View File

@ -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 (
<div>
<div style={ tooltipTextStyles(theme) }>
{ content }
<div {...tooltipProps} ref={ref}>
<div style={ tooltipStyles(theme) }>
{ children }
</div>
{/* <div style={footer(theme)}>{wikiLogo(theme)}</div> */}
{/* {arrow(theme)} */}
<div ref={arrowRef} style={ tooltipArrowStyles({
theme,
x: arrowX,
y: arrowY,
side: arrowPlacement
}) }></div>
</div>
)
}
})

View File

@ -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("/");
};

View File

@ -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;