Better hover action positioning

This commit is contained in:
Jackson Harper 2023-06-26 15:38:28 +08:00
parent d4ccde4cd2
commit 92b4f0e523
4 changed files with 178 additions and 149 deletions

View file

@ -20,7 +20,6 @@ import {
CardCheckbox,
DescriptionStyle,
LibraryItemMetadata,
MenuStyle,
MetaStyle,
siteName,
TitleStyle,
@ -29,6 +28,14 @@ import { sortedLabels } from '../../../lib/labelsSort'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import { LibraryHoverActions } from './LibraryHoverActions'
import {
useHover,
useFloating,
useInteractions,
size,
offset,
autoUpdate,
} from '@floating-ui/react'
dayjs.extend(relativeTime)
@ -64,11 +71,29 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
const [anchor, setAnchor] = useState<HTMLDivElement | null>(null)
const [isOpen, setIsOpen] = useState(false)
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [
offset({
mainAxis: -25,
}),
size(),
],
placement: 'top-end',
whileElementsMounted: autoUpdate,
})
const hover = useHover(context)
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
return (
<VStack
ref={setAnchor}
ref={refs.setReference}
{...getReferenceProps()}
css={{
pl: '20px',
padding: '15px',
@ -99,13 +124,18 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
<LibraryGridCardContent {...props} isHovered={isHovered} />
) : (
<>
<LibraryHoverActions
anchor={anchor}
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
<Box
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref

View file

@ -17,7 +17,6 @@ import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery
import { useFloating, shift, offset } from '@floating-ui/react'
type LibraryHoverActionsProps = {
anchor: HTMLDivElement | null
viewer: UserBasicData
isHovered: boolean
@ -28,125 +27,100 @@ type LibraryHoverActionsProps = {
export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
const [menuOpen, setMenuOpen] = useState(false)
const { refs, floatingStyles } = useFloating({
elements: {
reference: props.anchor,
},
middleware: [
offset({
mainAxis: -44,
crossAxis: -10,
}),
],
placement: 'top-end',
})
return (
<Box ref={refs.setFloating} style={floatingStyles}>
<Box
css={{
// position: 'fixed',
// transform: 'translate(621px, 73px)',
<Box
css={{
overflow: 'clip',
// top: '5px',
// right: '155px',
// marginTop: '-33px',
// marginRight: '-162px',
overflow: 'clip',
height: '33px',
width: '162px',
bg: '$thBackground',
display: 'flex',
height: '33px',
width: '162px',
bg: '$thBackground',
display: 'flex',
marginLeft: 'auto',
pt: '0px',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid $thBackground5',
borderRadius: '5px',
pt: '0px',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid $thBackground5',
borderRadius: '5px',
gap: '5px',
px: '5px',
visibility: props.isHovered || menuOpen ? 'unset' : 'hidden',
'&:hover': {
boxShadow:
'0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
},
gap: '5px',
px: '5px',
visibility: props.isHovered || menuOpen ? 'unset' : 'hidden',
'&:hover': {
boxShadow:
'0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
},
}}
>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('open-notebook')
event.preventDefault()
}}
>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('open-notebook')
event.preventDefault()
}}
>
<Notebook
size={19}
<Notebook size={19} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
const action = props.item.isArchived ? 'unarchive' : 'archive'
props.handleAction(action)
event.preventDefault()
}}
>
{props.item.isArchived ? (
<Tray size={18} color={theme.colors.thNotebookSubtle.toString()} />
) : (
<ArchiveBox
size={18}
color={theme.colors.thNotebookSubtle.toString()}
/>
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
const action = props.item.isArchived ? 'unarchive' : 'archive'
props.handleAction(action)
event.preventDefault()
}}
>
{props.item.isArchived ? (
<Tray size={18} color={theme.colors.thNotebookSubtle.toString()} />
) : (
<ArchiveBox
size={18}
)}
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('delete')
event.preventDefault()
}}
>
<Trash size={18} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('set-labels')
event.preventDefault()
}}
>
<Tag size={18} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<SpanBox
css={{
display: 'flex',
pt: '2.5px',
height: '33px',
'&:hover': {
bg: '$grayBgHover',
},
}}
>
<DotsThree
size={25}
weight="bold"
color={theme.colors.thNotebookSubtle.toString()}
/>
)}
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('delete')
event.preventDefault()
}}
>
<Trash size={18} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('set-labels')
event.preventDefault()
}}
>
<Tag size={18} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<SpanBox
css={{
display: 'flex',
pt: '2.5px',
height: '33px',
'&:hover': {
bg: '$grayBgHover',
},
}}
>
<DotsThree
size={25}
weight="bold"
color={theme.colors.thNotebookSubtle.toString()}
/>
</SpanBox>
}
/>
</Box>
</SpanBox>
}
/>
</Box>
)
}

View file

@ -1,41 +1,55 @@
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { LabelChip } from '../../elements/LabelChip'
import type { LinkedItemCardProps } from './CardTypes'
import { useCallback, useState } from 'react'
import {
Archive,
ArchiveBox,
DotsThree,
Note,
Notebook,
Tag,
Trash,
Tray,
} from 'phosphor-react'
import { useCallback, useEffect, useState } from 'react'
import Link from 'next/link'
import { CardMenu } from '../CardMenu'
import {
AuthorInfoStyle,
CardCheckbox,
LibraryItemMetadata,
MenuStyle,
MetaStyle,
siteName,
TitleStyle,
} from './LibraryCardStyles'
import { sortedLabels } from '../../../lib/labelsSort'
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import { LibraryHoverActions } from './LibraryHoverActions'
import {
useHover,
useFloating,
useInteractions,
size,
offset,
autoUpdate,
} from '@floating-ui/react'
export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
const [anchor, setAnchor] = useState<HTMLDivElement | null>(null)
const [isOpen, setIsOpen] = useState(false)
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [
offset({
mainAxis: -25,
// crossAxis: -10,
}),
size(),
],
placement: 'top-end',
whileElementsMounted: autoUpdate,
})
const hover = useHover(context)
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
return (
<VStack
ref={setAnchor}
ref={refs.setReference}
{...getReferenceProps()}
css={{
px: '20px',
pt: '20px',
@ -59,6 +73,12 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
'@media (min-width: 1600px)': {
width: '1340px',
},
boxShadow:
'0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
'@media (max-width: 930px)': {
boxShadow: 'unset',
borderRadius: 'unset',
},
}}
alignment="start"
distribution="start"
@ -73,13 +93,18 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
<LibraryListCardContent {...props} isHovered={isHovered} />
) : (
<>
<LibraryHoverActions
anchor={anchor}
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
<Box
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
@ -126,7 +151,9 @@ export function LibraryListCardContent(
distribution="start"
css={{ height: '100%', width: '100%' }}
>
<Box css={{ ...TitleStyle, width: '80%' }}>{props.item.title}</Box>
<Box css={{ ...TitleStyle, fontSize: '18px', width: '80%' }}>
{props.item.title}
</Box>
<SpanBox
css={{
mt: '5px',

View file

@ -1106,12 +1106,10 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
paddingTop: '0',
paddingBottom: '0px',
overflow: 'hidden',
boxShadow:
props.layout == 'LIST_LAYOUT'
? '0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);'
: 'unset',
'@media (max-width: 930px)': {
gridGap: props.layout == 'LIST_LAYOUT' ? '0px' : '20px',
},
'@xlgDown': {
border: 'unset',
borderRadius: props.layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@smDown': {