Files
omnivore/packages/web/components/templates/homeFeed/MultiSelectControls.tsx

415 lines
11 KiB
TypeScript

import { useState } from 'react'
import { theme } from '../../tokens/stitches.config'
import { Box, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { BulkAction } from '../../../lib/networking/library_items/useLibraryItems'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
import { LabelIcon } from '../../elements/icons/LabelIcon'
import { TrashIcon } from '../../elements/icons/TrashIcon'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal'
import { X } from '@phosphor-icons/react'
import { MultiSelectMode } from './LibraryHeader'
import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { MarkAsReadIcon } from '../../elements/icons/MarkAsReadIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { UnarchiveIcon } from '../../elements/icons/UnarchiveIcon'
import { MoveToInboxIcon } from '../../elements/icons/MoveToInboxIcon'
export type MultiSelectProps = {
viewer: UserBasicData | undefined
folder: string | undefined
searchTerm: string | undefined
applySearchQuery: (searchQuery: string) => void
numItemsSelected: number
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
}
export const MultiSelectControls = (props: MultiSelectProps): JSX.Element => {
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showLabelsModal, setShowLabelsModal] = useState(false)
// Don't change on immediate hover, the button has to be blurred at least once
const [hoveredOut, setHoveredOut] = useState(false)
const [hoverColor, setHoverColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Box
css={{
height: '38px',
width: '100%',
maxWidth: '521px',
bg: '$thLibrarySearchbox',
borderRadius: '6px',
boxShadow:
'0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
}}
onMouseLeave={(event) => {
setHoveredOut(true)
event.preventDefault()
}}
>
<HStack
alignment="center"
distribution="end"
css={{
width: '100%',
height: '100%',
pr: '10px',
}}
onClick={(e) => {
e.preventDefault()
}}
>
<HStack
alignment="center"
distribution="center"
css={{
width: '53px',
height: '100%',
display: 'flex',
bg: props.multiSelectMode !== 'off' ? '$ctaBlue' : 'transparent',
borderTopLeftRadius: '6px',
borderBottomLeftRadius: '6px',
'--checkbox-color': 'white',
'&:hover': {
bg: hoveredOut ? '$thLibraryMultiselectHover' : '$ctaBlue',
'--checkbox-color': hoveredOut
? 'var(--colors-thLibraryMultiselectCheckboxHover)'
: 'white',
},
}}
>
<CheckBoxButton {...props} />
</HStack>
<HStack
alignment="center"
distribution="start"
css={{
gap: '15px',
pl: '15px',
border: '2px solid transparent',
width: '100%',
height: '100%',
'@mdDown': {
pl: '5px',
},
}}
>
<SpanBox
css={{
display: 'flex',
fontSize: '14px',
fontFamily: '$display',
marginRight: 'auto',
'@mdDown': {
display: 'none',
},
}}
>
{props.numItemsSelected} items selected
</SpanBox>
<SpanBox
css={{
display: 'none',
fontSize: '11px',
fontFamily: '$display',
marginRight: 'auto',
'@mdDown': {
display: 'flex',
},
}}
>
{props.numItemsSelected} items
</SpanBox>
{props.folder !== 'archive' && <ArchiveButton {...props} />}
<AddLabelsButton setShowLabelsModal={setShowLabelsModal} />
{props.folder == 'subscriptions' && (
<MoveToLibraryButton {...props} />
)}
{props.folder !== 'trash' && (
<RemoveItemsButton setShowConfirmDelete={setShowConfirmDelete} />
)}
<MarkAsReadButton {...props} />
{showConfirmDelete && (
<ConfirmationModal
message={`You are about to delete ${props.numItemsSelected} items. All associated notes and highlights will be deleted.`}
acceptButtonLabel={'Delete'}
onAccept={() => {
props.performMultiSelectAction(BulkAction.DELETE)
}}
onOpenChange={(open: boolean) => {
setShowConfirmDelete(false)
}}
/>
)}
{showLabelsModal && (
<AddBulkLabelsModal
bulkSetLabels={(labels: Label[]) => {
const labelIds = labels.map((l) => l.id)
props.performMultiSelectAction(BulkAction.ADD_LABELS, labelIds)
}}
onOpenChange={(open: boolean) => {
setShowLabelsModal(false)
}}
/>
)}
<CancelButton {...props} />
</HStack>
</HStack>
</Box>
)
}
export const CheckBoxButton = (props: MultiSelectProps): JSX.Element => {
return (
<Button
title="Select multiple"
style="plainIcon"
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
onClick={(e) => {
switch (props.multiSelectMode) {
case 'off':
case 'none':
case 'some':
props.setMultiSelectMode('visible')
break
default:
props.setMultiSelectMode('off')
break
}
e.preventDefault()
}}
>
<HeaderCheckboxIcon multiSelectMode={props.multiSelectMode} />
</Button>
)
}
export const ArchiveButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Archive"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.performMultiSelectAction(BulkAction.ARCHIVE)
e.preventDefault()
}}
>
<ArchiveIcon size={20} color={color} />
</Button>
)
}
export const MarkAsReadButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Mark As Read"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.performMultiSelectAction(BulkAction.MARK_AS_READ)
e.preventDefault()
}}
>
<MarkAsReadIcon size={20} color={color} />
</Button>
)
}
export const MoveToLibraryButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Move to library"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.performMultiSelectAction(BulkAction.MOVE_TO_FOLDER)
e.preventDefault()
}}
>
<MoveToInboxIcon size={20} color={color} />
</Button>
)
}
type AddLabelsButtonProps = {
setShowLabelsModal: (set: boolean) => void
}
export const AddLabelsButton = (props: AddLabelsButtonProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Add labels"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.setShowLabelsModal(true)
e.preventDefault()
}}
>
<LabelIcon size={20} color={color} />
</Button>
)
}
type RemoveItemsButtonProps = {
setShowConfirmDelete: (set: boolean) => void
}
export const RemoveItemsButton = (
props: RemoveItemsButtonProps
): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Remove"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.setShowConfirmDelete(true)
e.preventDefault()
}}
>
<TrashIcon size={20} color={color} />
</Button>
)
}
export const CancelButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Cancel"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
style="plainIcon"
onClick={(e) => {
props.setMultiSelectMode('off')
e.preventDefault()
}}
>
<X width={19} height={19} color={color} />
</Button>
)
}