Files
omnivore/packages/web/components/templates/article/EditLabelsModal.tsx
Jackson Harper eeea5b7c28 Consistent dropdown layout for article actions
We also offset a little to display the popover in the middle of
the article action button on desktop

Disable autofocus on touch devices so the keyboard doesn't take
up too much space
2022-04-12 12:23:55 -07:00

284 lines
9.3 KiB
TypeScript

import {
ModalContent,
ModalOverlay,
ModalRoot,
} from '../../elements/ModalPrimitives'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { CrossIcon } from '../../elements/images/CrossIcon'
import { styled, theme } from '../../tokens/stitches.config'
import { Label, useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { ChangeEvent, useCallback, useRef, useState, useMemo } from 'react'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { LabelChip } from '../../elements/LabelChip'
import { Check, Circle, Pen, PencilSimple, PencilSimpleLine, Plus, TagSimple } from 'phosphor-react'
import Link from 'next/link'
import { isTouchScreenDevice } from '../../../lib/deviceType'
type EditLabelsModalProps = {
// labels: Label[]
// article: ArticleAttributes
// onOpenChange: (open: boolean) => void
// setLabels: (labels: Label[]) => void
}
type HeaderProps = {
filterText: string
setFilterText: (text: string) => void
}
const FormInput = styled('input', {
width: '100%',
fontSize: '16px',
fontFamily: 'inter',
fontWeight: 'normal',
lineHeight: '1.8',
color: '$grayTextContrast',
'&:focus': {
outline: 'none',
},
})
const StyledLabel = styled('label', {
display: 'flex',
justifyContent: 'flex-start',
'&:focus-visible': {
backgroundColor: 'red',
},
})
function Header(props: HeaderProps): JSX.Element {
const inputRef = useRef<HTMLInputElement>(null)
return (
<VStack css={{ width: '100%', my: '0px', borderBottom: '1px solid $grayBorder',
}}>
{/* <HStack
distribution="between"
alignment="center"
css={{
width: '100%',
flex: '0 0 20px',
mx: '0px',
p: '0px',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
}}
> */}
{/* <StyledText style="modalHeadline" css={{ p: '16px', fontSize: '12px', }}>
Apply labels to this page
</StyledText> */}
{/* <Button
css={{ pt: '16px', pr: '16px' }}
style="ghost"
onClick={() => {
}}
>
<CrossIcon
size={20}
strokeColor={theme.colors.grayText.toString()}
/>
</Button> */}
{/* </HStack> */}
<Box css={{
width: '100%',
my: '14px',
px: '14px',
}}>
<form
onSubmit={(event) => {
// event.preventDefault()
// props.applySearchQuery(searchTerm || '')
inputRef.current?.blur()
}}
>
<FormInput
ref={inputRef}
type="text"
autoFocus={!isTouchScreenDevice()}
value={props.filterText}
placeholder="Filter for label"
onFocus={(event) => {
event.target.select()
//setFocused(true)
}}
onBlur={() => {
//setFocused(false)
console.log('blurred')
}}
onKeyDown={(event) => {
console.log('keydown', event.key)
}}
onChange={(event) => {
console.log('event', event)
props.setFilterText(event.target.value)
}}
css={{
border: '1px solid $grayBorder',
borderRadius: '8px',
width: '100%',
bg: 'transparent',
fontSize: '16px',
fontFamily: 'inter',
fontWeight: 'normal',
marginBottom: '2px',
textIndent: '8px',
paddingLeft: '8px',
color: '$grayTextContrast',
'&:focus': {
outline: 'none',
boxShadow: '0px 0px 2px 2px rgba(255, 234, 159, 0.56)',
},
}}
/>
</form>
</Box>
</VStack>)
}
type LabelsListProps = {
// pageId: string
// selectedLabels: Label[]
availableLabels: Label[]
// setSelectedLabels: (labels: Label[]) => void
}
function LabelsList(props: LabelsListProps): JSX.Element {
const isSelected = useCallback((label: Label) => {
// return props.selectedLabels.some((other) => {
// return other.id === label.id
// })
return false
}, [])
return (
<VStack css={{ flexGrow: '1', overflow: 'scroll', width: '100%', height: '100%', maxHeight: '400px', }}>
{props.availableLabels &&
props.availableLabels.map((label, idx) => (
<StyledLabel
key={label.id}
tabIndex={idx + 1}
css={{
width: '100%',
height: '42px',
borderBottom: '1px solid $grayBorder',
}}
onClick={async () => {
// console.log('selected label', label)
// if (props.selectedLabels.includes(label)) {
// props.setSelectedLabels(
// props.selectedLabels.filter((id) => id !== label)
// )
// } else {
// props.setSelectedLabels([...props.selectedLabels, label])
// }
// const result = await setLabelsMutation(props.pageId, props.selectedLabels.map((l) => l.id))
// console.log('result', result)
}}
>
<Box css={{ pl: '5px', width: '25px', height: '45px', display: 'flex', alignItems: 'center' }}>
{isSelected(label) && <Check color={theme.colors.grayText.toString()} weight='bold' />}
</Box>
<Box css={{ width: '30px', height: '100%', display: 'flex', alignItems: 'center' }}>
<Circle width={22} height={22} color={label.color} weight='fill' />
</Box>
<Box css={{ overflow: 'clip', height: '100%', display: 'flex', alignItems: 'center' }}>
<StyledText style="caption">{label.name}</StyledText>
</Box>
<Box css={{ pl: '10px', width: '40px', marginLeft: 'auto', height: '100%', display: 'flex', alignItems: 'center' }}>
{isSelected(label) && <CrossIcon
size={14}
strokeColor={theme.colors.grayText.toString()}
/>}
</Box>
{/*
<CrossIcon
size={14}
strokeColor={theme.colors.grayText.toString()}
/> */}
{/* <input
type="checkbox"
value={label.id}
onChange={handleChange}
checked={selectedLabels.includes(label)}
/> */}
</StyledLabel>
))}
</VStack>)
}
export function EditLabelsModal(props: EditLabelsModalProps): JSX.Element {
const [filterText, setFilterText] = useState('')
const [selectedLabels, setSelectedLabels] = useState([])
const { labels } = useGetLabelsQuery()
const saveAndExit = useCallback(async () => {
// const result = await setLabelsMutation(props.article.id, selectedLabels.map((l) => l.id))
// console.log('result of setting labels', result)
// // props.onOpenChange(false)
// // props.setLabels(selectedLabels)
}, [props, selectedLabels])
const filteredLabels = useMemo(() => {
if (!labels) {
return []
}
return labels.filter((label) => {
return label.name.toLowerCase().includes(filterText.toLowerCase())
})
}, [labels, filterText])
return (
// <ModalRoot defaultOpen onOpenChange={saveAndExit}>
// <ModalOverlay />
// <ModalContent
// onPointerDownOutside={(event) => {
// event.preventDefault()
// }}
// css={{ p: '0', width: '100%', maxWidth: '400px' }}
// >
<VStack distribution="start" css={{
p: '0', maxHeight: '400px',
width: '265px',
}}>
<Header setFilterText={setFilterText} filterText={filterText} />
<LabelsList
availableLabels={filteredLabels}
// selectedLabels={selectedLabels || []}
/>
{filterText && (
<Button style='modalOption' css={{
pl: '26px', color: theme.colors.grayText.toString(), height: '42px', borderBottom: '1px solid $grayBorder'
}}
onClick={() => {
// props.createLabel(filterText)
// setFilterText('')
}}
>
<HStack alignment='center' distribution='start' css={{ gap: '8px' }}>
<Plus size={18} color={theme.colors.grayText.toString()} />
<SpanBox css={{ fontSize: '12px' }}>{`Create new label "${filterText}"`}</SpanBox>
</HStack>
</Button>)}
{/* Footer */}
<HStack distribution="start" alignment="center" css={{
ml: '20px', gap: '8px', width: '100%', fontSize: '12px', p: '8px', height: '42px',
'a:link': {
textDecoration: 'none',
},
'a:visited': {
color: theme.colors.grayText.toString(),
},
}}
>
<PencilSimple size={18} color={theme.colors.grayText.toString()} />
<Link href="/settings/labels">Edit labels</Link>
</HStack>
</VStack>
// </ModalContent>
// </ModalRoot>
)
}