refactor tooltip

This commit is contained in:
btzr-io 2019-06-20 14:59:01 -06:00
parent 19c8e55d08
commit f10530bd5d
14 changed files with 378 additions and 205 deletions

View file

@ -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",

View file

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

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

View file

@ -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 (
<div
className={classnames('channel-thumbnail', {
[thumbnailClass]: !thumbnail,
className={classnames('channel-thumbnail', className, {
[colorClassName]: !thumbnail,
})}
>
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />}

View file

@ -0,0 +1,2 @@
import ChannelTooltip from './view';
export default ChannelTooltip;

View file

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

View file

@ -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.Fragment>
{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
<Portal>
<span
className={classnames('channel-tooltip__arrow', `channel-tooltip__arrow--${direction}`)}
style={{
position: 'absolute',
top: arrowTop,
left: triggerRect && triggerRect.left - ARROW_SIZE + triggerRect.width / 2,
}}
/>
</Portal>
)}
<TooltipPopup
{...tooltip}
label={label}
ariaLabel={ariaLabel}
className={'channel-tooltip'}
position={(triggerRect, tooltipRect) => getPostion(triggerRect, tooltipRect, direction, setDirection)}
/>
</React.Fragment>
);
}
const ChannelTooltipLabel = (props: ChannelTooltipProps) => {
const { uri, title, claimId, thumbnail, description, channelName } = props;
const channelUrl = `${channelName}#${claimId}`;
const formatedName = channelName.replace('@', '');
return (
<div className={'channel-tooltip__content'}>
<div className={'channel-tooltip__profile'}>
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
<div className={'channel-tooltip__info'}>
<h2 className={'channel-tooltip__title'}>
<TruncatedText lines={1}>{title || formatedName}</TruncatedText>
</h2>
<h3 className={'channel-tooltip__url'}>
<TruncatedText lines={1}>{channelUrl}</TruncatedText>
</h3>
</div>
</div>
{description && (
<div className={'channel-tooltip__description'}>
<div>
<TruncatedText lines={2}>{description}</TruncatedText>
</div>
</div>
)}
<div className={'channel-tooltip__stats'} />
</div>
);
};
const ChannelTooltip = (props: ChannelTooltipProps) => {
const { children, ariaLabel } = props;
const label = <ChannelTooltipLabel {...props} />;
return (
<TriangleTooltip label={label} ariaLabel={ariaLabel}>
{children}
</TriangleTooltip>
);
};
export default React.memo<ChannelTooltipProps>(ChannelTooltip);

View file

@ -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<Props, State> {
parentId: string;
class ClaimLink extends React.Component<Props> {
static defaultProps = {
href: null,
title: null,
@ -39,24 +32,6 @@ class ClaimLink extends React.Component<Props, State> {
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<Props, State> {
return <span>{children}</span>;
}
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 = (
<Button
label={children}
title={!isChannelClaim ? title || claimName : undefined}
button={!isChannel ? 'link' : undefined}
navigate={uri}
className="button--uri-indicator"
/>
);
const wrappedLink = (
<ChannelTooltip
uri={uri}
title={title}
claimId={claimId}
channelName={claimName}
ariaLabel={title || claimName}
currentTheme={currentTheme}
thumbnail={thumbnail}
description={description}
>
<span>{link}</span>
</ChannelTooltip>
);
return (
<React.Fragment>
<Button
id={this.parentId}
label={children}
title={title || claimName}
button={'link'}
navigate={uri}
className="button--uri-indicator"
onMouseEnter={this.showTooltip}
onMouseLeave={this.hideTooltip}
/>
{isChannelClaim ? wrappedLink : link}
{showPreview && <PreviewLink uri={uri} />}
{renderChannelTooltip && (
<ChannelTooltip
uri={uri}
title={title}
claimId={claimId}
channelName={claimName}
currentTheme={currentTheme}
thumbnail={thumbnail}
description={description}
active={this.state.isTooltipActive}
parent={`#${this.parentId}`}
group={'channel-tooltip'}
/>
)}
</React.Fragment>
);
}

View file

@ -1,75 +0,0 @@
// @flow
import * as React from 'react';
import ToolTip from 'react-portal-tooltip';
import TruncatedText from 'component/common/truncated-text';
import ChannelThumbnail from 'component/common/channelThumbnail';
type TooltipProps = {
uri: string,
title: ?string,
group: ?string,
active: ?boolean,
parent: ?HTMLElement | ?string,
claimId: string,
thumbnail: ?string,
channelName: string,
description: ?string,
currentTheme: ?string,
};
const ChannelTooltip = (props: TooltipProps) => {
const { uri, group, title, active, parent, claimId, thumbnail, description, channelName, currentTheme } = props;
let bgColor = 'var(--lbry-white)';
if (currentTheme === 'dark') {
// Background color of the tooltip component,
// taken from the header component:
// mix($lbry-black, $lbry-gray-3, 90%);
bgColor = '#32373b';
}
const style = {
style: { background: bgColor, padding: 0 },
arrowStyle: { color: bgColor, borderColor: false },
};
const channelUrl = `${channelName}#${claimId}`;
const formatedName = channelName.replace('@', '');
return (
<ToolTip
align="left"
arrow="left"
active={parent && active}
group={group}
style={style}
parent={parent}
position="bottom"
>
<div className={'channel-tooltip'}>
<div className={'channel-tooltip__profile'}>
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
<div className={'channel-tooltip__info'}>
<h2 className={'channel-tooltip__title'}>
<TruncatedText lines={1}>{title || formatedName}</TruncatedText>
</h2>
<h3 className={'channel-tooltip__url'}>
<TruncatedText lines={1}>{channelUrl}</TruncatedText>
</h3>
</div>
</div>
{description && (
<div className={'channel-tooltip__description'}>
<p>
<TruncatedText lines={2}>{description}</TruncatedText>
</p>
</div>
)}
<div className={'channel-tooltip__stats'} />
</div>
</ToolTip>
);
};
export default React.memo<TooltipProps>(ChannelTooltip);

View file

@ -17,18 +17,20 @@ type Props = {
class UriIndicator extends React.PureComponent<Props> {
componentDidMount() {
const { isResolvingUri, resolveUri, uri, claim } = this.props;
if (!isResolvingUri && !claim && uri) {
resolveUri(uri);
}
this.resolve(this.props);
}
componentDidUpdate(prevProps: Props) {
const { isResolvingUri, resolveUri, claim, uri } = this.props;
if (!isResolvingUri && uri && !claim) {
componentDidUpdate() {
this.resolve(this.props);
}
resolve = (props: Props) => {
const { isResolvingUri, resolveUri, claim, uri } = props;
if (!isResolvingUri && claim === undefined && uri) {
resolveUri(uri);
}
}
};
render() {
const { link, isResolvingUri, claim } = this.props;
@ -37,23 +39,31 @@ class UriIndicator extends React.PureComponent<Props> {
return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>;
}
if (!claim.signing_channel) {
const isChannelClaim = claim.value_type === 'channel';
if (!claim.signing_channel && !isChannelClaim) {
return <span className="channel-name">Anonymous</span>;
}
const { name, claim_id: claimId } = claim.signing_channel;
let claimLink;
if (claim.is_channel_signature_valid) {
claimLink = link ? buildURI({ channelName: name, claimId }) : false;
const channelClaim = isChannelClaim ? claim : claim.signing_channel;
if (channelClaim) {
const { name, claim_id: claimId } = channelClaim;
let channelLink;
if (claim.is_channel_signature_valid) {
channelLink = link ? buildURI({ channelName: name, claimId }) : false;
}
const inner = <span className="channel-name">{name}</span>;
if (!channelLink) {
return inner;
}
return <ClaimLink uri={channelLink}>{inner}</ClaimLink>;
} else {
return null;
}
const inner = <span className="channel-name">{name}</span>;
if (!claimLink) {
return inner;
}
return <ClaimLink uri={claimLink}>{inner}</ClaimLink>;
}
}

View file

@ -9,11 +9,11 @@ import {
import ChannelPage from './view';
const select = (state, props) => ({
page: selectCurrentChannelPage(state),
cover: makeSelectCoverForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
cover: makeSelectCoverForUri(props.uri)(state),
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
page: selectCurrentChannelPage(state),
});
export default connect(

View file

@ -9,7 +9,7 @@ import { withRouter } from 'react-router';
import { formatLbryUriForWeb } from 'util/uri';
import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout';
import ChannelThumbnail from 'component/common/channelThumbnail';
import ChannelThumbnail from 'component/channelThumbnail';
const PAGE_VIEW_QUERY = `view`;
const ABOUT_PAGE = `about`;
@ -26,7 +26,7 @@ type Props = {
};
function ChannelPage(props: Props) {
const { uri, title, cover, thumbnail, history, location, page } = props;
const { uri, title, cover, history, location, page } = props;
const { channelName, claimName, claimId } = parseURI(uri);
const { search } = location;
const urlParams = new URLSearchParams(search);
@ -49,42 +49,44 @@ function ChannelPage(props: Props) {
};
return (
<Page notContained className="main--no-padding-top">
<header className="channel-cover main__item--extend-outside">
{cover && <img className="channel-cover__custom" src={cover} />}
<Page>
<div className="card">
<header className="channel-cover">
{cover && <img className="channel-cover__custom" src={cover} />}
<div className="channel__primary-info">
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
<div className="channel__primary-info">
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} />
<div>
<h1 className="channel__title">{title || channelName}</h1>
<h2 className="channel__url">
{claimName}
{claimId && `#${claimId}`}
</h2>
<div>
<h1 className="channel__title">{title || channelName}</h1>
<h2 className="channel__url">
{claimName}
{claimId && `#${claimId}`}
</h2>
</div>
</div>
</div>
</header>
</header>
<Tabs onChange={onTabChange} index={tabIndex}>
<TabList className="main__item--extend-outside tabs__list--channel-page">
<Tab>{__('Content')}</Tab>
<Tab>{__('About')}</Tab>
<div className="card__actions">
<ShareButton uri={uri} />
<SubscribeButton uri={uri} />
</div>
</TabList>
<Tabs onChange={onTabChange} index={tabIndex}>
<TabList className="tabs__list--channel-page">
<Tab>{__('Content')}</Tab>
<Tab>{__('About')}</Tab>
<div className="card__actions">
<ShareButton uri={uri} />
<SubscribeButton uri={uri} />
</div>
</TabList>
<TabPanels>
<TabPanel>
<ChannelContent uri={uri} />
</TabPanel>
<TabPanel>
<ChannelAbout uri={uri} />
</TabPanel>
</TabPanels>
</Tabs>
<TabPanels className="channel__data">
<TabPanel>
<ChannelContent uri={uri} />
</TabPanel>
<TabPanel>
<ChannelAbout uri={uri} />
</TabPanel>
</TabPanels>
</Tabs>
</div>
</Page>
);
}

View file

@ -154,7 +154,28 @@
// Channel-tooltip
.channel-tooltip {
color: $lbry-black;
background-color: $lbry-white;
width: 20rem;
border: none;
border-radius: var(--card-radius);
z-index: 2;
html[data-mode='dark'] & {
color: mix($lbry-white, $lbry-gray-3, 50%);
background-color: mix($lbry-black, $lbry-gray-3, 90%);
}
box-shadow: 0px 2px 10px -2px black;
}
.channel-tooltip__content {
width: 100%;
margin: 0;
display: block;
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
.channel-thumbnail {
left: 0;
@ -169,6 +190,39 @@
}
}
.channel-tooltip__arrow {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
html[data-mode='dark'] & {
z-index: 3;
}
}
.channel-tooltip__arrow--top {
border-top: 10px solid $lbry-gray-5;
border-bottom: 0;
html[data-mode='dark'] & {
z-index: 3;
border-top: 10px solid mix($lbry-black, $lbry-gray-3, 90%);
border-bottom: 0;
}
}
.channel-tooltip__arrow--bottom {
border-top: 0;
border-bottom: 10px solid $lbry-gray-5;
html[data-mode='dark'] & {
z-index: 3;
border-top: 0;
border-bottom: 10px solid mix($lbry-black, $lbry-gray-3, 90%);
}
}
.channel-tooltip__profile {
display: flex;
align-items: center;
@ -179,6 +233,7 @@
padding: 1rem;
visibility: visible;
border-top: 1px solid rgba($lbry-gray-4, 0.4);
width: 100%;
html[data-mode='dark'] & {
border-top: 1px solid rgba($lbry-black, 0.8);
@ -186,16 +241,16 @@
}
.channel-tooltip__info {
align-items: center;
visibility: visible;
}
.channel-tooltip__title {
font-weight: bold;
font-size: 1.4rem;
line-height: 1.5em;
visibility: visible;
}
.channel-tooltip__url {
font-size: 1rem;
visibility: visible;
}

View file

@ -893,7 +893,7 @@
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
"@reach/auto-id@^0.2.0":
"@reach/auto-id@0.2.0", "@reach/auto-id@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
@ -908,6 +908,13 @@
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
"@reach/portal@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa"
integrity sha512-pUQ0EtCcYm4ormEjJmdk4uzZCxOpaRHB8FDKJXy6q6GqRqQwZ4lAT1f2Tvw0DAmULmyZTpe1/heXY27Tdnct+Q==
dependencies:
"@reach/component-component" "^0.1.3"
"@reach/rect@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
@ -925,11 +932,33 @@
"@reach/utils" "^0.2.2"
warning "^4.0.2"
"@reach/tooltip@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.1.tgz#70a80d6defedee53cedf5480cd3d37dfb20020d0"
integrity sha512-3O7oXoymNkEAolHN9WbuspY7mA3zIOrTaibmYkKFtGT6FgyBrAQyOQn1ZhBuSza6RjSIkEtFRpbDEKK1UJEI6A==
dependencies:
"@reach/auto-id" "0.2.0"
"@reach/portal" "^0.2.1"
"@reach/rect" "^0.2.1"
"@reach/utils" "^0.2.3"
"@reach/visually-hidden" "^0.1.4"
prop-types "^15.7.2"
"@reach/utils@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
"@reach/utils@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f"
integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ==
"@reach/visually-hidden@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ==
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -9447,11 +9476,6 @@ react-paginate@^5.2.1:
dependencies:
prop-types "^15.6.1"
react-portal-tooltip@^2.4.7:
version "2.4.7"
resolved "https://registry.yarnpkg.com/react-portal-tooltip/-/react-portal-tooltip-2.4.7.tgz#77fd4a75760afecd116736c480485aa22cbbb740"
integrity sha512-ZOuCKpvu8rr6aS6nNjckd78qEgWyHZnKidZ9Yqrd0HR5K9NYJ7csb4duc0kEXeG8DiCWsLPU70o1EVP+MxlINg==
react-pose@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"
@ -11453,11 +11477,6 @@ uniq@^1.0.1:
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
uniqid@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-5.0.3.tgz#917968310edc868d50df6c44f783f32c7d80fac1"
integrity sha512-R2qx3X/LYWSdGRaluio4dYrPXAJACTqyUjuyXHoJLBUOIfmMcnYOyY2d6Y4clZcIz5lK6ZaI0Zzmm0cPfsIqzQ==
uniqs@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"