diff --git a/packages/web/components/templates/article/EditLabelsControl.tsx b/packages/web/components/templates/article/EditLabelsControl.tsx new file mode 100644 index 000000000..33d064c3e --- /dev/null +++ b/packages/web/components/templates/article/EditLabelsControl.tsx @@ -0,0 +1,321 @@ +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, useEffect, MouseEventHandler } 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 EditLabelsControlProps = { + // labels: Label[] + // article: ArticleAttributes + // onOpenChange: (open: boolean) => void + // setLabels: (labels: Label[]) => void +} + +type HeaderProps = { + filterText: string + focused: boolean + parentRef: React.RefObject + 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': { + bg: '$grayBgActive', + }, +}) + +const useToggleLabels = () => { + const [selectedLabels, setSelectedLabels] = useState([]) + + const isSelected = useCallback((label: Label): boolean => { + return selectedLabels.some((other) => { + return other.id === label.id + }) + }, [selectedLabels]) + + const toggleLabel = useCallback((label: Label) => { + if (isSelected(label)) { + setSelectedLabels(selectedLabels.filter((other) => { + return other.id !== label.id + })) + } else { + setSelectedLabels([...selectedLabels, label]) + } + }, [isSelected, selectedLabels]) + + return [isSelected, toggleLabel] +} + +function Header(props: HeaderProps): JSX.Element { + const inputRef = useRef(null) + + useEffect(() => { + if (props.focused && inputRef.current) { + inputRef.current.focus() + } + }, [props.focused]) + + return ( + + + { + event.target.select() + //setFocused(true) + console.log('input::focused()') + }} + onBlur={() => { + // setFocused(false) + console.log('input::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)', + }, + }} + /> + + ) +} + +type LabelListItemProps = { + label: Label + focused: boolean + selected: boolean + toggleLabel: (label: Label) => void +} + +function LabelListItem(props: LabelListItemProps): JSX.Element { + const ref = useRef(null) + const { label, focused, selected } = props + + useEffect(() => { + if (props.focused && ref.current) { + ref.current.focus() + } + }, [props.focused]) + + return ( + { + event.preventDefault() + props.toggleLabel(label) + ref.current?.blur() + }} + > + + + {selected && } + + + + + + {label.name} + + + {selected && } + + + ) +} + +export function EditLabelsControl(props: EditLabelsControlProps): JSX.Element { + const parentRef = useRef(null) + const [filterText, setFilterText] = useState('') + const { labels } = useGetLabelsQuery() + + const [selectedLabels, setSelectedLabels] = useState([]) + + const isSelected = useCallback((label: Label): boolean => { + return selectedLabels.some((other) => { + return other.id === label.id + }) + }, [selectedLabels]) + + const toggleLabel = useCallback((label: Label) => { + if (isSelected(label)) { + setSelectedLabels(selectedLabels.filter((other) => { + return other.id !== label.id + })) + } else { + setSelectedLabels([...selectedLabels, label]) + } + }, [isSelected, selectedLabels]) + + const filteredLabels = useMemo(() => { + if (!labels) { + return [] + } + return labels.filter((label) => { + return label.name.toLowerCase().includes(filterText.toLowerCase()) + }) + }, [labels, filterText]) + + useEffect(() => { + setFocusedIndex(undefined) + }, [filterText]) + + // Move focus through the labels list on tab or arrow up/down keys + const [focusedIndex, setFocusedIndex] = useState(undefined) + const handleKeyDown = useCallback((event: React.KeyboardEvent) => { + if (event.key === 'ArrowUp') { + event.preventDefault() + if (focusedIndex) { + setFocusedIndex(Math.max(0, focusedIndex - 1)) + } else { + setFocusedIndex(undefined) + } + } + if (event.key === 'ArrowDown' || event.key === 'Tab') { + event.preventDefault() + if (focusedIndex === undefined) { + setFocusedIndex(0) + } else { + setFocusedIndex(Math.min(filteredLabels.length - 1, focusedIndex + 1)) + } + } + if (event.key === 'Enter') { + event.preventDefault() + if (focusedIndex !== undefined) { + const label = filteredLabels[focusedIndex] + if (label) { + toggleLabel(label) + } + } + } + }, [filteredLabels, focusedIndex]) + + return ( + +
+ + {filteredLabels && + filteredLabels.map((label, idx) => ( + + ))} + + {filterText && ( + )} + {/* Footer */} + + + Edit labels + + + ) +} \ No newline at end of file