mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Better hover action positioning
This commit is contained in:
parent
d4ccde4cd2
commit
92b4f0e523
4 changed files with 178 additions and 149 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
Loading…
Reference in a new issue