mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add hover actions to the library
This commit is contained in:
parent
b916b4a065
commit
ae05789e5b
7 changed files with 258 additions and 174 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
69
packages/web/components/templates/article/NotebookHeader.tsx
Normal file
69
packages/web/components/templates/article/NotebookHeader.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue