[site/components][f]: floating-ui tooltip on hover

This commit is contained in:
olayway 2022-05-17 14:38:24 +02:00
parent 1ba2193870
commit 0b65f7d404
7 changed files with 236 additions and 136 deletions

View File

@ -1,25 +1,93 @@
import { useState, Fragment } from 'react';
import { Tooltip } from './Tooltip';
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 { Tooltip } from './Tooltip'
import getAbsolutePath from '../utils/absolutePath'
// TODO cancel request on mouseleave when it hasn't been fulfilled yet
export const Anchor = (props) => {
const href = props.href;
const { href } = props;
const router = useRouter();
const [ open, setOpen ] = useState(false);
const [ loaded, setLoaded ] = useState(false);
const [ preview, setPreview ] = useState("");
const { x, y, reference, floating, strategy, context } = useFloating({
open,
onOpenChange: setOpen
});
const { getReferenceProps, getFloatingProps } = useInteractions([
useHover(context, props)
]);
useEffect(() => {
if (open) {
fetchPreview();
}
}, [open])
const fetchPreview = async () => {
setLoaded(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) {
// TODO
console.log(`Looks like there was a problem. Status Code: ${response.status}`)
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"
})
setPreview(toString(p));
setLoaded(true);
}
if (
href &&
href.indexOf("http://") !== 0 &&
href.indexOf("http://") !== 0 &&
href.indexOf("http:") !== 0 &&
href.indexOf("https:") !== 0 &&
href.includes(".md")
) {
return (
<Fragment key={href}>
<Tooltip
theme="light"
mouseEnterDelay="0.5"
value={href}
>
<a {...props} />
</Tooltip>
</Fragment>
);
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>
)}
</Fragment>;
}
return <a {...props} />;
};

View File

@ -1,82 +0,0 @@
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import {unified} from 'unified'
import rehypeParse from 'rehype-parse'
import find from 'unist-util-find'
import {toString} from 'hast-util-to-string'
const textStyles = (theme) => ({
padding: '16px 22px',
fontSize: '11px',
background: theme === 'light' ? '#fff' : '#000',
})
const display = (data, theme) => {
return (
<div
style={{
color: theme === 'light' ? 'rgb(99, 98, 98)' : '#A8A8A8',
}}
>
<div style={textStyles(theme)}>{data}</div>
{/* <div style={footer(theme)}>{wikiLogo(theme)}</div> */}
{/* {arrow(theme)} */}
</div>
)
}
const getAbsolutePath = ({ currentPath, basePath, relativePath }) => {
console.log({ currentPath, basePath, relativePath });
const absolutePath = currentPath.slice(1).split("/")
absolutePath.pop(); // remove current page name
absolutePath.unshift(basePath);
absolutePath.push(relativePath);
console.log(absolutePath);
return absolutePath.join("/");
}
export const Content = (props) => {
const [state, setState] = useState({
data: "",
isLoaded: false,
})
const router = useRouter();
useEffect(async () => {
const basePath = "http://localhost:3000";
const currentPath = router.asPath;
const relativePath = props.value.split(".")[0]; // temp remove .md
const absolutePath = getAbsolutePath({ currentPath, basePath, relativePath });
console.log(absolutePath);
fetch(absolutePath).then((response) => {
if (response.status !== 200) {
console.log(`Looks like there was a problem. Status Code: ${response.status}`)
return
}
response.text().then((data) => {
const hast = unified().use(rehypeParse).parse(data);
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"
})
setState({
data: toString(p),
isLoaded: true,
})
})
})
}, [])
const { theme } = props
const { data, isLoaded } = state
return isLoaded ? display(data, theme) : <div />
}

View File

@ -1,32 +1,28 @@
import Trigger from 'rc-trigger'
import { Content } from './Content'
const tooltipTextStyles = (theme) => ({
padding: '16px 22px',
fontSize: '11px',
background: theme === 'light' ? '#fff' : '#000',
// color: theme === 'light' ? 'black' : 'white',
pointerEvents: 'none',
borderRadius: '4px',
position: 'absolute'
})
const position = {
bottom: {
points: ['tc', 'bc'],
offset: [0, 10]
},
right: {
points: ['tl', 'tc'],
offset: [20, 0]
},
top: {
points: ['bc', 'tc'],
offset: [0, -10]
}
}
// const tooltipBoxStyles = (theme) => ({
// color: theme === 'light' ? 'rgb(99, 98, 98)' : '#A8A8A8',
// transition: "0.1s",
// width: "50vw"
// })
export const Tooltip = (props) => {
const { children, value, theme, mouseEnterDelay = 0.5 } = props;
const { theme, content } = props;
return (
<Trigger
action={['hover']}
popup={<Content theme={theme} value={value} />}
mouseEnterDelay={mouseEnterDelay}
prefixCls='trigger'
popupAlign={position.bottom}
>
{children}
</Trigger>
<div>
<div style={ tooltipTextStyles(theme) }>
{ content }
</div>
{/* <div style={footer(theme)}>{wikiLogo(theme)}</div> */}
{/* {arrow(theme)} */}
</div>
)
}

119
site/package-lock.json generated
View File

@ -5,6 +5,7 @@
"packages": {
"": {
"dependencies": {
"@floating-ui/react-dom-interactions": "^0.6.0",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.4",
"@mdx-js/loader": "^2.0.0",
@ -337,6 +338,42 @@
"resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz",
"integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ=="
},
"node_modules/@floating-ui/core": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.0.tgz",
"integrity": "sha512-W7+i5Suhhvv97WDTW//KqUA43f/2a4abprM1rWqtLM9lIlJ29tbFI8h232SvqunXon0WmKNEKVjbOsgBhTnbLw=="
},
"node_modules/@floating-ui/dom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.0.tgz",
"integrity": "sha512-PS75dnMg4GdWjDFOiOs15cDzYJpukRNHqQn0ugrBlsrpk2n+y8bwZ24XrsdLSL7kxshmxxr2nTNycLnmRIvV7g==",
"dependencies": {
"@floating-ui/core": "^0.7.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.0.tgz",
"integrity": "sha512-mpYGykTqwtBYT+ZTQQ2OfZ6wXJNuUgmqqD9ooCgbMRgvul6InFOTtWYvtujps439hmOFiVPm4PoBkEEn5imidg==",
"dependencies": {
"@floating-ui/dom": "^0.5.0",
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/react-dom-interactions": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.0.tgz",
"integrity": "sha512-8XzQuQStUNztHvg+Rj6MdUjBsOKIb6Oe0eIs1w9LfssOT0ZEPPHVsCZgLiWoyDaotF6pirLGAXkOfQxPM7VBRQ==",
"dependencies": {
"@floating-ui/react-dom": "^0.7.0",
"aria-hidden": "^1.1.3",
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.9.tgz",
@ -1277,6 +1314,22 @@
"sprintf-js": "~1.0.2"
}
},
"node_modules/aria-hidden": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz",
"integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==",
"dependencies": {
"tslib": "^1.0.0"
},
"engines": {
"node": ">=8.5.0"
}
},
"node_modules/aria-hidden/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/array-iterate": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-1.1.4.tgz",
@ -5850,6 +5903,19 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-subscription": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
@ -6291,6 +6357,38 @@
"resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz",
"integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ=="
},
"@floating-ui/core": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.0.tgz",
"integrity": "sha512-W7+i5Suhhvv97WDTW//KqUA43f/2a4abprM1rWqtLM9lIlJ29tbFI8h232SvqunXon0WmKNEKVjbOsgBhTnbLw=="
},
"@floating-ui/dom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.0.tgz",
"integrity": "sha512-PS75dnMg4GdWjDFOiOs15cDzYJpukRNHqQn0ugrBlsrpk2n+y8bwZ24XrsdLSL7kxshmxxr2nTNycLnmRIvV7g==",
"requires": {
"@floating-ui/core": "^0.7.0"
}
},
"@floating-ui/react-dom": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.0.tgz",
"integrity": "sha512-mpYGykTqwtBYT+ZTQQ2OfZ6wXJNuUgmqqD9ooCgbMRgvul6InFOTtWYvtujps439hmOFiVPm4PoBkEEn5imidg==",
"requires": {
"@floating-ui/dom": "^0.5.0",
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"@floating-ui/react-dom-interactions": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.0.tgz",
"integrity": "sha512-8XzQuQStUNztHvg+Rj6MdUjBsOKIb6Oe0eIs1w9LfssOT0ZEPPHVsCZgLiWoyDaotF6pirLGAXkOfQxPM7VBRQ==",
"requires": {
"@floating-ui/react-dom": "^0.7.0",
"aria-hidden": "^1.1.3",
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"@grpc/grpc-js": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.5.9.tgz",
@ -6947,6 +7045,21 @@
"sprintf-js": "~1.0.2"
}
},
"aria-hidden": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz",
"integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==",
"requires": {
"tslib": "^1.0.0"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"array-iterate": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-1.1.4.tgz",
@ -9961,6 +10074,12 @@
"unist-util-is": "^5.0.0"
}
},
"use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"requires": {}
},
"use-subscription": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",

View File

@ -7,6 +7,7 @@
"start": "next start"
},
"dependencies": {
"@floating-ui/react-dom-interactions": "^0.6.0",
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.4",
"@mdx-js/loader": "^2.0.0",

View File

@ -27,15 +27,3 @@ body {
.extra-small {
font-size: 0.50rem;
}
/* TOOLTIP */
.trigger {
position: absolute;
opacity: 1;
transition: 0.1s;
}
.trigger-hidden {
opacity: 0;
visibility: hidden;
}

View File

@ -0,0 +1,10 @@
const absolutePath = ({ currentPath, basePath, relativePath }) => {
const absolutePath = currentPath.slice(1).split("/")
absolutePath.pop(); // remove current page name
absolutePath.unshift(basePath);
absolutePath.push(relativePath);
console.log(absolutePath);
return absolutePath.join("/");
};
export default absolutePath;