Item popover (#1162)
* WIP Item Popover * Hide user on ItemSumarry to avoid infinite popovers * Introduce HoverablePopover * Delete itempopover & userpopover css * Fix excess bottom padding on the ItemPopover * Fix ItemSummary: Use text for itens that doesn't have a title * Handling #itemid/something links + Tweaks for rendering comment summary * refine hoverable popover --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
parent
691818e779
commit
471888563e
|
@ -0,0 +1,49 @@
|
||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { Popover } from 'react-bootstrap'
|
||||||
|
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
|
||||||
|
import styles from './hoverable-popover.module.css'
|
||||||
|
|
||||||
|
export default function HoverablePopover ({ id, trigger, body, onShow }) {
|
||||||
|
const [showOverlay, setShowOverlay] = useState(false)
|
||||||
|
|
||||||
|
const timeoutId = useRef(null)
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
clearTimeout(timeoutId.current)
|
||||||
|
onShow && onShow()
|
||||||
|
timeoutId.current = setTimeout(() => {
|
||||||
|
setShowOverlay(true)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
clearTimeout(timeoutId.current)
|
||||||
|
timeoutId.current = setTimeout(() => setShowOverlay(false), 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverlayTrigger
|
||||||
|
show={showOverlay}
|
||||||
|
placement='bottom'
|
||||||
|
onHide={handleMouseLeave}
|
||||||
|
overlay={
|
||||||
|
<Popover
|
||||||
|
onPointerEnter={handleMouseEnter}
|
||||||
|
onPointerLeave={handleMouseLeave}
|
||||||
|
className={styles.HoverablePopover}
|
||||||
|
>
|
||||||
|
<Popover.Body className={styles.HoverablePopover}>
|
||||||
|
{body}
|
||||||
|
</Popover.Body>
|
||||||
|
</Popover>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
onPointerEnter={handleMouseEnter}
|
||||||
|
onPointerLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</span>
|
||||||
|
</OverlayTrigger>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
.userPopover {
|
.hoverablePopover {
|
||||||
border: 1px solid var(--theme-toolbarActive)
|
border: 1px solid var(--theme-toolbarActive)
|
||||||
}
|
}
|
||||||
|
|
||||||
.userPopBody {
|
.hoverablePopBody {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
|
@ -26,7 +26,7 @@ import UserPopover from './user-popover'
|
||||||
export default function ItemInfo ({
|
export default function ItemInfo ({
|
||||||
item, full, commentsText = 'comments',
|
item, full, commentsText = 'comments',
|
||||||
commentTextSingular = 'comment', className, embellishUser, extraInfo, onEdit, editText,
|
commentTextSingular = 'comment', className, embellishUser, extraInfo, onEdit, editText,
|
||||||
onQuoteReply, extraBadges, nested, pinnable
|
onQuoteReply, extraBadges, nested, pinnable, showActionDropdown = true, showUser = true
|
||||||
}) {
|
}) {
|
||||||
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
|
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
@ -61,10 +61,10 @@ export default function ItemInfo ({
|
||||||
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === AD_USER_ID) &&
|
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === AD_USER_ID) &&
|
||||||
<>
|
<>
|
||||||
<span title={`from ${numWithUnits(item.upvotes, {
|
<span title={`from ${numWithUnits(item.upvotes, {
|
||||||
abbreviate: false,
|
abbreviate: false,
|
||||||
unitSingular: 'stacker',
|
unitSingular: 'stacker',
|
||||||
unitPlural: 'stackers'
|
unitPlural: 'stackers'
|
||||||
})} ${item.mine
|
})} ${item.mine
|
||||||
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
||||||
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${item.meDontLikeSats
|
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${item.meDontLikeSats
|
||||||
? ` & ${numWithUnits(item.meDontLikeSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
|
? ` & ${numWithUnits(item.meDontLikeSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
|
||||||
|
@ -102,12 +102,13 @@ export default function ItemInfo ({
|
||||||
</Link>
|
</Link>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<span>
|
<span>
|
||||||
<UserPopover name={item.user.name}>
|
{showUser &&
|
||||||
<Link href={`/${item.user.name}`}>
|
<UserPopover name={item.user.name}>
|
||||||
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
|
<Link href={`/${item.user.name}`}>
|
||||||
{embellishUser}
|
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
|
||||||
</Link>
|
{embellishUser}
|
||||||
</UserPopover>
|
</Link>
|
||||||
|
</UserPopover>}
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
|
<Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
|
||||||
{timeSince(new Date(item.createdAt))}
|
{timeSince(new Date(item.createdAt))}
|
||||||
|
@ -152,54 +153,57 @@ export default function ItemInfo ({
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</>}
|
</>}
|
||||||
<ActionDropdown>
|
{
|
||||||
<CopyLinkDropdownItem item={item} />
|
showActionDropdown &&
|
||||||
{(item.parentId || item.text) && onQuoteReply &&
|
<ActionDropdown>
|
||||||
<Dropdown.Item onClick={onQuoteReply}>quote reply</Dropdown.Item>}
|
<CopyLinkDropdownItem item={item} />
|
||||||
{me && <BookmarkDropdownItem item={item} />}
|
{(item.parentId || item.text) && onQuoteReply &&
|
||||||
{me && <SubscribeDropdownItem item={item} />}
|
<Dropdown.Item onClick={onQuoteReply}>quote reply</Dropdown.Item>}
|
||||||
{item.otsHash &&
|
{me && <BookmarkDropdownItem item={item} />}
|
||||||
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
|
{me && <SubscribeDropdownItem item={item} />}
|
||||||
opentimestamp
|
{item.otsHash &&
|
||||||
</Link>}
|
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
|
||||||
{item?.noteId && (
|
opentimestamp
|
||||||
<Dropdown.Item onClick={() => window.open(`https://njump.me/${item.noteId}`, '_blank', 'noopener,noreferrer,nofollow')}>
|
</Link>}
|
||||||
nostr note
|
{item?.noteId && (
|
||||||
</Dropdown.Item>
|
<Dropdown.Item onClick={() => window.open(`https://njump.me/${item.noteId}`, '_blank', 'noopener,noreferrer,nofollow')}>
|
||||||
)}
|
nostr note
|
||||||
{item && item.mine && !item.noteId && !item.isJob && !item.parentId &&
|
</Dropdown.Item>
|
||||||
<CrosspostDropdownItem item={item} />}
|
)}
|
||||||
{me && !item.position &&
|
{item && item.mine && !item.noteId && !item.isJob && !item.parentId &&
|
||||||
!item.mine && !item.deletedAt &&
|
<CrosspostDropdownItem item={item} />}
|
||||||
(item.meDontLikeSats > meTotalSats
|
{me && !item.position &&
|
||||||
? <DropdownItemUpVote item={item} />
|
!item.mine && !item.deletedAt &&
|
||||||
: <DontLikeThisDropdownItem id={item.id} />)}
|
(item.meDontLikeSats > meTotalSats
|
||||||
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
|
? <DropdownItemUpVote item={item} />
|
||||||
<>
|
: <DontLikeThisDropdownItem id={item.id} />)}
|
||||||
<hr className='dropdown-divider' />
|
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
|
||||||
<OutlawDropdownItem item={item} />
|
<>
|
||||||
</>}
|
<hr className='dropdown-divider' />
|
||||||
{me && !nested && !item.mine && sub && Number(me.id) !== Number(sub.userId) &&
|
<OutlawDropdownItem item={item} />
|
||||||
<>
|
</>}
|
||||||
<hr className='dropdown-divider' />
|
{me && !nested && !item.mine && sub && Number(me.id) !== Number(sub.userId) &&
|
||||||
<MuteSubDropdownItem item={item} sub={sub} />
|
<>
|
||||||
</>}
|
<hr className='dropdown-divider' />
|
||||||
{canPin &&
|
<MuteSubDropdownItem item={item} sub={sub} />
|
||||||
<>
|
</>}
|
||||||
<hr className='dropdown-divider' />
|
{canPin &&
|
||||||
<PinSubDropdownItem item={item} />
|
<>
|
||||||
</>}
|
<hr className='dropdown-divider' />
|
||||||
{item.mine && !item.position && !item.deletedAt && !item.bio &&
|
<PinSubDropdownItem item={item} />
|
||||||
<>
|
</>}
|
||||||
<hr className='dropdown-divider' />
|
{item.mine && !item.position && !item.deletedAt && !item.bio &&
|
||||||
<DeleteDropdownItem itemId={item.id} type={item.title ? 'post' : 'comment'} />
|
<>
|
||||||
</>}
|
<hr className='dropdown-divider' />
|
||||||
{me && !item.mine &&
|
<DeleteDropdownItem itemId={item.id} type={item.title ? 'post' : 'comment'} />
|
||||||
<>
|
</>}
|
||||||
<hr className='dropdown-divider' />
|
{me && !item.mine &&
|
||||||
<MuteDropdownItem user={item.user} />
|
<>
|
||||||
</>}
|
<hr className='dropdown-divider' />
|
||||||
</ActionDropdown>
|
<MuteDropdownItem user={item.user} />
|
||||||
|
</>}
|
||||||
|
</ActionDropdown>
|
||||||
|
}
|
||||||
{extraInfo}
|
{extraInfo}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ITEM } from '@/fragments/items'
|
||||||
|
import errorStyles from '@/styles/error.module.css'
|
||||||
|
import { useLazyQuery } from '@apollo/client'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import HoverablePopover from './hoverable-popover'
|
||||||
|
import { ItemSkeleton, ItemSummary } from './item'
|
||||||
|
|
||||||
|
export default function ItemPopover ({ id, children }) {
|
||||||
|
const [getItem, { loading, data }] = useLazyQuery(
|
||||||
|
ITEM,
|
||||||
|
{
|
||||||
|
variables: { id },
|
||||||
|
fetchPolicy: 'cache-first'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverablePopover
|
||||||
|
onShow={getItem}
|
||||||
|
trigger={children}
|
||||||
|
body={!data || loading
|
||||||
|
? <ItemSkeleton showUpvote={false} />
|
||||||
|
: !data.item
|
||||||
|
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>ITEM NOT FOUND</h1>
|
||||||
|
: <ItemSummary item={data.item} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -18,6 +18,26 @@ import { Badge } from 'react-bootstrap'
|
||||||
import AdIcon from '@/svgs/advertisement-fill.svg'
|
import AdIcon from '@/svgs/advertisement-fill.svg'
|
||||||
import { DownZap } from './dont-link-this'
|
import { DownZap } from './dont-link-this'
|
||||||
import { timeLeft } from '@/lib/time'
|
import { timeLeft } from '@/lib/time'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import removeMd from 'remove-markdown'
|
||||||
|
|
||||||
|
function onItemClick (e, router, item) {
|
||||||
|
const viewedAt = commentsViewedAt(item)
|
||||||
|
if (viewedAt) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
window.open(
|
||||||
|
`/items/${item.id}`,
|
||||||
|
'_blank',
|
||||||
|
'noopener,noreferrer'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
router.push(
|
||||||
|
`/items/${item.id}?commentsViewedAt=${viewedAt}`,
|
||||||
|
`/items/${item.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function SearchTitle ({ title }) {
|
export function SearchTitle ({ title }) {
|
||||||
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {
|
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {
|
||||||
|
@ -51,23 +71,9 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
||||||
<div className={`${styles.main} flex-wrap`}>
|
<div className={`${styles.main} flex-wrap`}>
|
||||||
<Link
|
<Link
|
||||||
href={`/items/${item.id}`}
|
href={`/items/${item.id}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => onItemClick(e, router, item)}
|
||||||
const viewedAt = commentsViewedAt(item)
|
ref={titleRef}
|
||||||
if (viewedAt) {
|
className={`${styles.title} text-reset me-2`}
|
||||||
e.preventDefault()
|
|
||||||
if (e.ctrlKey || e.metaKey) {
|
|
||||||
window.open(
|
|
||||||
`/items/${item.id}`,
|
|
||||||
'_blank',
|
|
||||||
'noopener,noreferrer'
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
router.push(
|
|
||||||
`/items/${item.id}?commentsViewedAt=${viewedAt}`,
|
|
||||||
`/items/${item.id}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}} ref={titleRef} className={`${styles.title} text-reset me-2`}
|
|
||||||
>
|
>
|
||||||
{item.searchTitle ? <SearchTitle title={item.searchTitle} /> : item.title}
|
{item.searchTitle ? <SearchTitle title={item.searchTitle} /> : item.title}
|
||||||
{item.pollCost && <PollIndicator item={item} />}
|
{item.pollCost && <PollIndicator item={item} />}
|
||||||
|
@ -108,7 +114,48 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ItemSkeleton ({ rank, children }) {
|
export function ItemSummary ({ item }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const link = (
|
||||||
|
<Link
|
||||||
|
href={`/items/${item.id}`}
|
||||||
|
onClick={(e) => onItemClick(e, router, item)}
|
||||||
|
className={`${item.title && styles.title} ${styles.summaryText} text-reset me-2`}
|
||||||
|
>
|
||||||
|
{item.title ?? removeMd(item.text)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
const info = (
|
||||||
|
<ItemInfo
|
||||||
|
item={item}
|
||||||
|
showUser={false}
|
||||||
|
showActionDropdown={false}
|
||||||
|
extraBadges={item.title && Number(item?.user?.id) === AD_USER_ID && <Badge className={styles.newComment} bg={null}>AD</Badge>}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles.item, 'mb-0 pb-0')}>
|
||||||
|
<div className={styles.hunk}>
|
||||||
|
{item.title
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
{link}
|
||||||
|
{info}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{info}
|
||||||
|
{link}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemSkeleton ({ rank, children, showUpvote = true }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{rank
|
{rank
|
||||||
|
@ -118,7 +165,7 @@ export function ItemSkeleton ({ rank, children }) {
|
||||||
</div>)
|
</div>)
|
||||||
: <div />}
|
: <div />}
|
||||||
<div className={`${styles.item} ${styles.skeleton}`}>
|
<div className={`${styles.item} ${styles.skeleton}`}>
|
||||||
<UpVote className={styles.upvote} />
|
{showUpvote && <UpVote className={styles.upvote} />}
|
||||||
<div className={styles.hunk}>
|
<div className={styles.hunk}>
|
||||||
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
|
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
|
||||||
<span className={`${styles.title} clouds text-reset flex-md-fill flex-md-shrink-0 me-2`} />
|
<span className={`${styles.title} clouds text-reset flex-md-fill flex-md-shrink-0 me-2`} />
|
||||||
|
@ -149,11 +196,10 @@ function PollIndicator ({ item }) {
|
||||||
return (
|
return (
|
||||||
<span className={styles.icon} title={isActive ? 'active' : 'results in'}>
|
<span className={styles.icon} title={isActive ? 'active' : 'results in'}>
|
||||||
<PollIcon
|
<PollIcon
|
||||||
className={`${
|
className={`${isActive
|
||||||
isActive
|
? 'fill-success'
|
||||||
? 'fill-success'
|
|
||||||
: 'fill-grey'
|
: 'fill-grey'
|
||||||
} ms-1`} height={14} width={14}
|
} ms-1`} height={14} width={14}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,6 +6,14 @@
|
||||||
margin-bottom: .15rem;
|
margin-bottom: .15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summaryText {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import Link from 'next/link'
|
||||||
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
import { UNKNOWN_LINK_REL } from '@/lib/constants'
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual'
|
||||||
import UserPopover from './user-popover'
|
import UserPopover from './user-popover'
|
||||||
|
import ItemPopover from './item-popover'
|
||||||
|
|
||||||
export function SearchText ({ text }) {
|
export function SearchText ({ text }) {
|
||||||
return (
|
return (
|
||||||
|
@ -227,7 +228,11 @@ export default memo(function Text ({ rel, imgproxyUrls, children, tab, itemId, o
|
||||||
try {
|
try {
|
||||||
const linkText = parseInternalLinks(href)
|
const linkText = parseInternalLinks(href)
|
||||||
if (linkText) {
|
if (linkText) {
|
||||||
return <Link href={href}>{linkText}</Link>
|
return (
|
||||||
|
<ItemPopover id={linkText.replace('#', '').split('/')[0]}>
|
||||||
|
<Link href={href}>{linkText}</Link>
|
||||||
|
</ItemPopover>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore errors like invalid URLs
|
// ignore errors like invalid URLs
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import { USER } from '@/fragments/users'
|
import { USER } from '@/fragments/users'
|
||||||
import errorStyles from '@/styles/error.module.css'
|
import errorStyles from '@/styles/error.module.css'
|
||||||
import { useLazyQuery } from '@apollo/client'
|
import { useLazyQuery } from '@apollo/client'
|
||||||
import Link from 'next/link'
|
|
||||||
import { useRef, useState } from 'react'
|
|
||||||
import { Popover } from 'react-bootstrap'
|
|
||||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
|
|
||||||
import { UserBase, UserSkeleton } from './user-list'
|
|
||||||
import styles from './user-popover.module.css'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import HoverablePopover from './hoverable-popover'
|
||||||
|
import ItemPopover from './item-popover'
|
||||||
|
import { UserBase, UserSkeleton } from './user-list'
|
||||||
|
|
||||||
function StackingSince ({ since }) {
|
function StackingSince ({ since }) {
|
||||||
return (
|
return (
|
||||||
<small className='text-muted d-flex-inline'>
|
<small className='text-muted d-flex-inline'>
|
||||||
stacking since:{' '}
|
stacking since:{' '}
|
||||||
{since
|
{since
|
||||||
? <Link href={`/items/${since}`}>#{since}</Link>
|
? (
|
||||||
|
<ItemPopover id={since}>
|
||||||
|
<Link href={`/items/${since}`}>#{since}</Link>
|
||||||
|
</ItemPopover>
|
||||||
|
)
|
||||||
: <span>never</span>}
|
: <span>never</span>}
|
||||||
</small>
|
</small>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserPopover ({ name, children }) {
|
export default function UserPopover ({ name, children }) {
|
||||||
const [showOverlay, setShowOverlay] = useState(false)
|
|
||||||
|
|
||||||
const [getUser, { loading, data }] = useLazyQuery(
|
const [getUser, { loading, data }] = useLazyQuery(
|
||||||
USER,
|
USER,
|
||||||
{
|
{
|
||||||
|
@ -31,52 +31,19 @@ export default function UserPopover ({ name, children }) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeoutId = useRef(null)
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
clearTimeout(timeoutId.current)
|
|
||||||
getUser()
|
|
||||||
timeoutId.current = setTimeout(() => {
|
|
||||||
setShowOverlay(true)
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
clearTimeout(timeoutId.current)
|
|
||||||
timeoutId.current = setTimeout(() => setShowOverlay(false), 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger
|
<HoverablePopover
|
||||||
show={showOverlay}
|
onShow={getUser}
|
||||||
placement='bottom'
|
trigger={children}
|
||||||
onHide={handleMouseLeave}
|
body={!data || loading
|
||||||
overlay={
|
? <UserSkeleton />
|
||||||
<Popover
|
: !data.user
|
||||||
onMouseEnter={handleMouseEnter}
|
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>USER NOT FOUND</h1>
|
||||||
onMouseLeave={handleMouseLeave}
|
: (
|
||||||
className={styles.userPopover}
|
<UserBase user={data.user} className='mb-0 pb-0'>
|
||||||
>
|
<StackingSince since={data.user.since} />
|
||||||
<Popover.Body className={styles.userPopBody}>
|
</UserBase>
|
||||||
{!data || loading
|
)}
|
||||||
? <UserSkeleton />
|
/>
|
||||||
: !data.user
|
|
||||||
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>USER NOT FOUND</h1>
|
|
||||||
: (
|
|
||||||
<UserBase user={data.user} className='mb-0 pb-0'>
|
|
||||||
<StackingSince since={data.user.since} />
|
|
||||||
</UserBase>
|
|
||||||
)}
|
|
||||||
</Popover.Body>
|
|
||||||
</Popover>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
</OverlayTrigger>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue