diff --git a/package.json b/package.json index 426087f5e..3ec128128 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@lbry/components": "^2.7.0", "@reach/rect": "^0.2.1", "@reach/tabs": "^0.1.5", + "@reach/tooltip": "^0.2.1", "@types/three": "^0.93.1", "async-exit-hook": "^2.0.1", "babel-eslint": "^10.0.1", @@ -148,7 +149,6 @@ "react-hot-loader": "^4.7.2", "react-modal": "^3.1.7", "react-paginate": "^5.2.1", - "react-portal-tooltip": "^2.4.7", "react-pose": "^4.0.5", "react-redux": "^6.0.1", "react-router": "^5.0.0", @@ -175,7 +175,6 @@ "three": "^0.93.0", "three-full": "^17.1.0", "tree-kill": "^1.1.0", - "uniqid": "^5.0.3", "unist-util-visit": "^1.4.1", "video.js": "^7.2.2", "villain": "btzr-io/Villain", diff --git a/src/ui/component/common/channelThumbnail/gerbil.png b/src/ui/component/channelThumbnail/gerbil.png similarity index 100% rename from src/ui/component/common/channelThumbnail/gerbil.png rename to src/ui/component/channelThumbnail/gerbil.png diff --git a/src/ui/component/channelThumbnail/index.js b/src/ui/component/channelThumbnail/index.js new file mode 100644 index 000000000..01b1d3c73 --- /dev/null +++ b/src/ui/component/channelThumbnail/index.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { makeSelectThumbnailForUri } from 'lbry-redux'; +import ChannelThumbnail from './view'; + +const select = (state, props) => ({ + thumbnail: makeSelectThumbnailForUri(props.uri)(state), +}); + +export default connect( + select, + null +)(ChannelThumbnail); diff --git a/src/ui/component/common/channelThumbnail/index.jsx b/src/ui/component/channelThumbnail/view.jsx similarity index 75% rename from src/ui/component/common/channelThumbnail/index.jsx rename to src/ui/component/channelThumbnail/view.jsx index 8aa2db55f..df3c24142 100644 --- a/src/ui/component/common/channelThumbnail/index.jsx +++ b/src/ui/component/channelThumbnail/view.jsx @@ -5,22 +5,23 @@ import classnames from 'classnames'; import Gerbil from './gerbil.png'; type Props = { - uri: string, thumbnail: ?string, + uri: string, + className?: string, }; function ChannelThumbnail(props: Props) { - const { thumbnail, uri } = props; + const { thumbnail, uri, className } = props; // Generate a random color class based on the first letter of the channel name const { channelName } = parseURI(uri); const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57 - const thumbnailClass = `channel-thumbnail__default--${initializer % 4}`; + const colorClassName = `channel-thumbnail__default--${initializer % 4}`; return (
{!thumbnail && } diff --git a/src/ui/component/channelTooltip/index.js b/src/ui/component/channelTooltip/index.js new file mode 100644 index 000000000..69dd6079d --- /dev/null +++ b/src/ui/component/channelTooltip/index.js @@ -0,0 +1,2 @@ +import ChannelTooltip from './view'; +export default ChannelTooltip; diff --git a/src/ui/component/channelTooltip/position.js b/src/ui/component/channelTooltip/position.js new file mode 100644 index 000000000..f3903d528 --- /dev/null +++ b/src/ui/component/channelTooltip/position.js @@ -0,0 +1,41 @@ +// Defualt function used by reach-ui tooltip componment, +// slightly modify to register current direction: +// https://github.com/reach/reach-ui/blob/f0c8c5c467be46c202148ee69b1ba789b57d5e60/packages/tooltip/src/index.js#L478 + +// feels awkward when it's perfectly aligned w/ the trigger +const OFFSET = 8; + +const positionDefault = (triggerRect, tooltipRect, currentDirection, setDirection) => { + const styles = { + left: `${triggerRect.left + window.pageXOffset}px`, + top: `${triggerRect.top + triggerRect.height + window.pageYOffset}px`, + }; + + const collisions = { + top: triggerRect.top - tooltipRect.height < 0, + right: window.innerWidth < triggerRect.left + tooltipRect.width, + bottom: window.innerHeight < triggerRect.bottom + tooltipRect.height + OFFSET, + left: triggerRect.left - tooltipRect.width < 0, + }; + + const directionRight = collisions.right && !collisions.left; + const directionUp = collisions.bottom && !collisions.top; + + const direction = directionUp ? 'top' : 'bottom'; + + if (direction !== currentDirection) { + setDirection(direction); + } + + return { + ...styles, + left: directionRight + ? `${triggerRect.right - tooltipRect.width + window.pageXOffset}px` + : `${triggerRect.left + window.pageXOffset}px`, + top: directionUp + ? `${triggerRect.top - OFFSET - tooltipRect.height + window.pageYOffset}px` + : `${triggerRect.top + OFFSET + triggerRect.height + window.pageYOffset}px`, + }; +}; + +export default positionDefault; diff --git a/src/ui/component/channelTooltip/view.jsx b/src/ui/component/channelTooltip/view.jsx new file mode 100644 index 000000000..90b967052 --- /dev/null +++ b/src/ui/component/channelTooltip/view.jsx @@ -0,0 +1,130 @@ +// @flow +import * as React from 'react'; +import classnames from 'classnames'; +import ChannelThumbnail from 'component/channelThumbnail'; +import TruncatedText from 'component/common/truncated-text'; + +// Tooltip components +import Portal from '@reach/portal'; +import getPostion from './position.js'; +import { useTooltip, TooltipPopup } from '@reach/tooltip'; + +// Tooltip base styles +import '@reach/tooltip/styles.css'; + +const ARROW_SIZE = 10; + +type ChannelTooltipProps = { + uri: string, + title: ?string, + claimId: string, + children: any, + ariaLabel: ?string, + thumbnail: ?string, + channelName: string, + description: ?string, +}; + +type TriangleTooltipProps = { + label: any, + children: any, + + ariaLabel: ?string, +}; + +function TriangleTooltip(props: TriangleTooltipProps) { + const { label, children, ariaLabel } = props; + + // get the props from useTooltip + const [trigger, tooltip] = useTooltip(); + + // Tooltip direction + const [direction, setDirection] = React.useState('bottom'); + + // destructure off what we need to position the triangle + const { isVisible, triggerRect } = tooltip; + + let arrowTop; + + // Top + if (direction === 'top') { + arrowTop = triggerRect && triggerRect.bottom - (triggerRect.height + ARROW_SIZE) + window.scrollY; + } + + // Bottom + if (direction === 'bottom') { + arrowTop = triggerRect && triggerRect.bottom + window.scrollY; + } + + return ( + + {React.cloneElement(children, trigger)} + {isVisible && ( + // The Triangle. We position it relative to the trigger, not the popup + // so that collisions don't have a triangle pointing off to nowhere. + // Using a Portal may seem a little extreme, but we can keep the + // positioning logic simpler here instead of needing to consider + // the popup's position relative to the trigger and collisions + + + + )} + getPostion(triggerRect, tooltipRect, direction, setDirection)} + /> + + ); +} + +const ChannelTooltipLabel = (props: ChannelTooltipProps) => { + const { uri, title, claimId, thumbnail, description, channelName } = props; + const channelUrl = `${channelName}#${claimId}`; + const formatedName = channelName.replace('@', ''); + + return ( +
+
+ +
+

+ {title || formatedName} +

+

+ {channelUrl} +

+
+
+ {description && ( +
+
+ {description} +
+
+ )} +
+
+ ); +}; + +const ChannelTooltip = (props: ChannelTooltipProps) => { + const { children, ariaLabel } = props; + const label = ; + return ( + + {children} + + ); +}; + +export default React.memo(ChannelTooltip); diff --git a/src/ui/component/claimLink/view.jsx b/src/ui/component/claimLink/view.jsx index 4d61e576b..f69361cc2 100644 --- a/src/ui/component/claimLink/view.jsx +++ b/src/ui/component/claimLink/view.jsx @@ -1,10 +1,9 @@ // @flow -import uniqid from 'uniqid'; import * as React from 'react'; -import { parseURI } from 'lbry-redux'; import Button from 'component/button'; import PreviewLink from 'component/previewLink'; -import ChannelTooltip from 'component/common/channel-tooltip'; +import ChannelTooltip from 'component/channelTooltip'; +import { parseURI } from 'lbry-redux'; type Props = { uri: string, @@ -23,13 +22,7 @@ type Props = { }>, }; -type State = { - isTooltipActive: boolean, -}; - -class ClaimLink extends React.Component { - parentId: string; - +class ClaimLink extends React.Component { static defaultProps = { href: null, title: null, @@ -39,24 +32,6 @@ class ClaimLink extends React.Component { isResolvingUri: false, }; - constructor(props: Props) { - super(props); - this.state = { isTooltipActive: false }; - - // The tooltip component don't work very well with refs, - // so we need to use an unique id for each link: - // (this: any).buttonRef = React.createRef(); - (this: any).parentId = uniqid.time('claim-link-'); - } - - showTooltip = () => { - this.setState({ isTooltipActive: true }); - }; - - hideTooltip = () => { - this.setState({ isTooltipActive: false }); - }; - isClaimBlackListed() { const { claim, blackListedOutpoints } = this.props; @@ -97,38 +72,40 @@ class ClaimLink extends React.Component { return {children}; } - const { isChannel, path } = parseURI(uri); const { claim_id: claimId, name: claimName } = claim; + const { isChannel, path } = parseURI(uri); + const isChannelClaim = isChannel && !path; const showPreview = autoEmbed === true && !isUnresolved; - const renderChannelTooltip = isChannel && !path; + + const link = ( +