Add hover actions to the library

This commit is contained in:
Jackson Harper 2023-06-26 12:30:15 +08:00
parent b916b4a065
commit ae05789e5b
7 changed files with 258 additions and 174 deletions

View file

@ -261,10 +261,11 @@ export const Button = styled('button', {
border: 'none',
cursor: 'pointer',
padding: '4px',
borderRadius: '5px',
height: '100%',
pt: '6px',
'&:hover': {
opacity: 0.7,
bg: '$grayBgHover',
},
},
ghost: {

View file

@ -172,7 +172,10 @@ export function Dropdown(
} = props
return (
<Root modal={modal} onOpenChange={props.onOpenChange}>
<DropdownTrigger disabled={disabled} css={{ cursor: 'pointer' }}>
<DropdownTrigger
disabled={disabled}
css={{ height: '100%', cursor: 'pointer' }}
>
{triggerElement}
</DropdownTrigger>
<DropdownContent

View file

@ -14,18 +14,6 @@ export const MetaStyle = {
fontFamily: '$display',
}
export const MenuStyle = {
display: 'flex',
marginLeft: 'auto',
height: '30px',
width: '180px',
// mt: '-5px',
// mr: '-5px',
pt: '2px',
alignItems: 'center',
justifyContent: 'center',
}
export const TitleStyle = {
color: '$thTextContrast2',
fontSize: '16px',

View file

@ -28,6 +28,7 @@ import {
import { sortedLabels } from '../../../lib/labelsSort'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import { LibraryHoverActions } from './LibraryHoverActions'
dayjs.extend(relativeTime)
@ -95,18 +96,26 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
{props.inMultiSelect ? (
<LibraryGridCardContent {...props} isHovered={isHovered} />
) : (
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
<>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
passHref
>
<LibraryGridCardContent {...props} isHovered={isHovered} />
</a>
</Link>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryGridCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</>
)}
</VStack>
)
@ -114,7 +123,6 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
const { isChecked, setIsChecked, item } = props
const [menuOpen, setMenuOpen] = useState(false)
const originText = siteName(props.item.originalArticleUrl, props.item.url)
const handleCheckChanged = useCallback(() => {
@ -131,79 +139,16 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
distribution="start"
>
<LibraryItemMetadata item={props.item} />
{props.inMultiSelect ? (
{props.inMultiSelect && (
<SpanBox css={{ marginLeft: 'auto' }}>
<CardCheckbox
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
) : (
<Box
css={{
...MenuStyle,
width: '30px',
mt: '-5px',
mr: '-5px',
gap: '10px',
px: '20px',
borderRadius: '1000px',
visibility: props.isHovered || menuOpen ? 'unset' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
{/* <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.thHighContrast.toString()}
/>
) : (
<ArchiveBox
size={18}
color={theme.colors.thHighContrast.toString()}
/>
)}
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('delete')
event.preventDefault()
}}
>
<Trash size={18} color={theme.colors.thHighContrast.toString()} />
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('set-labels')
event.preventDefault()
}}
>
<Tag size={18} color={theme.colors.thHighContrast.toString()} />
</Button> */}
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
)}
</HStack>
<VStack
alignment="start"
distribution="start"

View file

@ -0,0 +1,141 @@
import { useState } from 'react'
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { LinkedItemCardAction } from './CardTypes'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import {
ArchiveBox,
DotsThree,
Notebook,
Tag,
Trash,
Tray,
} from 'phosphor-react'
import { CardMenu } from '../CardMenu'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
type LibraryHoverActionsProps = {
viewer: UserBasicData
isHovered: boolean
item: LibraryItemNode
handleAction: (action: LinkedItemCardAction) => void
}
export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
const [menuOpen, setMenuOpen] = useState(false)
return (
<Box
css={{
position: 'relative',
width: '100%',
height: '0px',
display: 'flex',
}}
>
<Box
css={{
position: 'absolute',
top: '-6px',
right: '-6px',
overflow: 'clip',
height: '33px',
width: '162px',
bg: '$thBackground',
display: 'flex',
marginLeft: 'auto',
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);',
},
}}
>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('open-notebook')
event.preventDefault()
}}
>
<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) => {
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>
</Box>
)
}

View file

@ -27,6 +27,7 @@ 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'
export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
@ -67,18 +68,26 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
{props.inMultiSelect ? (
<LibraryListCardContent {...props} isHovered={isHovered} />
) : (
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
<>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
passHref
>
<LibraryListCardContent {...props} isHovered={isHovered} />
</a>
</Link>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryListCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</>
)}
</VStack>
)
@ -99,85 +108,13 @@ export function LibraryListCardContent(
<>
<HStack css={MetaStyle} distribution="start">
<LibraryItemMetadata item={props.item} showProgress={true} />
{props.inMultiSelect ? (
{props.inMultiSelect && (
<SpanBox css={{ marginLeft: 'auto' }}>
<CardCheckbox
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
) : (
<Box
css={{
...MenuStyle,
gap: '10px',
px: '20px',
visibility: props.isHovered || menuOpen ? 'unset' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('open-notebook')
event.preventDefault()
}}
>
<Notebook
size={19}
color={theme.colors.thHighContrast.toString()}
/>
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('set-labels')
event.preventDefault()
}}
>
<Tag size={18} color={theme.colors.thHighContrast.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.thHighContrast.toString()}
/>
) : (
<ArchiveBox
size={18}
color={theme.colors.thHighContrast.toString()}
/>
)}
</Button>
<Button
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('delete')
event.preventDefault()
}}
>
<Trash size={18} color={theme.colors.thHighContrast.toString()} />
</Button>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
)}
</HStack>
<VStack

View file

@ -0,0 +1,69 @@
import { useCallback } from 'react'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import {
UserBasicData,
useGetViewerQuery,
} from '../../../lib/networking/queries/useGetViewerQuery'
import { CloseButton } from '../../elements/CloseButton'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { HStack } from '../../elements/LayoutPrimitives'
import { MenuTrigger } from '../../elements/MenuTrigger'
import { StyledText } from '../../elements/StyledText'
import { NotebookModal } from './NotebookModal'
type NotebookHeaderProps = {
setShowNotebook: (set: boolean) => void
}
export const NotebookHeader = (props: NotebookHeaderProps) => {
const handleClose = useCallback(() => {
props.setShowNotebook(false)
}, [props])
return (
<HStack
distribution="center"
alignment="center"
css={{
width: '100%',
position: 'sticky',
top: '0px',
height: '50px',
p: '20px',
background: '#F8FAFB',
zIndex: 10,
}}
>
<StyledText style="modalHeadline" css={{ color: '$thTextSubtle2' }}>
Notebook
</StyledText>
<HStack
css={{
ml: 'auto',
cursor: 'pointer',
gap: '15px',
mr: '-5px',
}}
distribution="center"
alignment="center"
>
<Dropdown triggerElement={<MenuTrigger />}>
{/* <DropdownOption
onSelect={() => {
exportHighlights()
}}
title="Export Notebook"
/>
<DropdownOption
onSelect={() => {
setShowConfirmDeleteNote(true)
}}
title="Delete Article Note"
/> */}
</Dropdown>
<CloseButton close={handleClose} />
</HStack>
</HStack>
)
}