import { useCallback, useRef, useState, useMemo, useEffect } from 'react'
import Link from 'next/link'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { styled, theme } from '../../tokens/stitches.config'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import {
Check,
Circle,
PencilSimple,
Plus,
WarningCircle,
} from 'phosphor-react'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
import { useRouter } from 'next/router'
import { LabelsPicker } from '../../elements/LabelsPicker'
export interface LabelsProvider {
labels?: Label[]
}
type SetLabelsControlProps = {
provider: LabelsProvider
inputValue: string
setInputValue: (value: string) => void
clearInputState: () => void
selectedLabels: Label[]
setSelectedLabels: (labels: Label[]) => void
onLabelsUpdated?: (labels: Label[]) => void
tabCount: number
setTabCount: (count: number) => void
tabStartValue: string
setTabStartValue: (value: string) => void
highlightLastLabel: boolean
setHighlightLastLabel: (set: boolean) => void
deleteLastLabel: () => void
selectOrCreateLabel: (value: string) => void
errorMessage?: string
}
type HeaderProps = {
focused: boolean
resetFocusedIndex: () => void
inputValue: string
setInputValue: (value: string) => void
clearInputState: () => void
selectedLabels: Label[]
setSelectedLabels: (labels: Label[]) => void
tabCount: number
setTabCount: (count: number) => void
tabStartValue: string
setTabStartValue: (value: string) => void
highlightLastLabel: boolean
setHighlightLastLabel: (set: boolean) => void
deleteLastLabel: () => void
selectOrCreateLabel: (value: string) => void
}
const StyledLabel = styled('label', {
display: 'flex',
justifyContent: 'flex-start',
})
function Header(props: HeaderProps): JSX.Element {
return (
{
props.resetFocusedIndex()
}}
clearInputState={props.clearInputState}
deleteLastLabel={props.deleteLastLabel}
selectOrCreateLabel={props.selectOrCreateLabel}
/>
)
}
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()
}}
>
{label.name}
{selected && (
)}
)
}
type FooterProps = {
focused: boolean
filterText: string
}
function Footer(props: FooterProps): JSX.Element {
const ref = useRef(null)
useEffect(() => {
if (props.focused && ref.current) {
ref.current.focus()
}
}, [props.focused])
return (
{props.filterText.length > 0 ? (
) : (
)}
)
}
export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const router = useRouter()
const { labels, revalidate } = useGetLabelsQuery()
// Move focus through the labels list on tab or arrow up/down keys
const [focusedIndex, setFocusedIndex] = useState(
undefined
)
useEffect(() => {
setFocusedIndex(undefined)
}, [props.inputValue])
const isSelected = useCallback(
(label: Label): boolean => {
return props.selectedLabels.some((other) => {
return other.id === label.id
})
},
[props.selectedLabels]
)
useEffect(() => {
if (focusedIndex === 0) {
props.setHighlightLastLabel(false)
}
}, [focusedIndex])
const toggleLabel = useCallback(
async (label: Label) => {
let newSelectedLabels = [...props.selectedLabels]
if (isSelected(label)) {
newSelectedLabels = props.selectedLabels.filter((other) => {
return other.id !== label.id
})
} else {
newSelectedLabels = [...props.selectedLabels, label]
}
props.setSelectedLabels(newSelectedLabels)
props.provider.labels = newSelectedLabels
if (props.onLabelsUpdated) {
props.onLabelsUpdated(newSelectedLabels)
}
props.clearInputState()
revalidate()
},
[isSelected, props, revalidate]
)
const filteredLabels = useMemo(() => {
if (!labels) {
return []
}
return labels
.filter((label) => {
return label.name.toLowerCase().includes(props.inputValue.toLowerCase())
})
.sort((left: Label, right: Label) => {
return left.name.localeCompare(right.name)
})
}, [labels, props.inputValue])
const createLabelFromFilterText = useCallback(
async (text: string) => {
const label = await createLabelMutation(text, randomLabelColorHex(), '')
if (label) {
showSuccessToast(`Created label ${label.name}`, {
position: 'bottom-right',
})
toggleLabel(label)
} else {
showErrorToast('Failed to create label', { position: 'bottom-right' })
}
},
[props.inputValue, toggleLabel]
)
const handleKeyDown = useCallback(
async (event: React.KeyboardEvent) => {
const maxIndex = filteredLabels.length + 1
if (event.key === 'ArrowUp') {
event.preventDefault()
let newIndex = focusedIndex
if (focusedIndex) {
newIndex = Math.max(0, focusedIndex - 1)
} else {
newIndex = undefined
}
// If the `Create New label` button isn't visible we skip it
// when navigating with the arrow keys
if (focusedIndex === maxIndex && !props.inputValue) {
newIndex = maxIndex - 2
}
setFocusedIndex(newIndex)
}
if (event.key === 'ArrowDown') {
event.preventDefault()
let newIndex = focusedIndex
if (focusedIndex === undefined) {
newIndex = 0
} else {
newIndex = Math.min(maxIndex, focusedIndex + 1)
}
// If the `Create New label` button isn't visible we skip it
// when navigating with the arrow keys
if (focusedIndex === maxIndex - 2 && !props.inputValue) {
newIndex = maxIndex
}
setFocusedIndex(newIndex)
}
if (event.key === 'Enter') {
event.preventDefault()
if (focusedIndex === maxIndex) {
router.push('/settings/labels')
return
}
if (focusedIndex === maxIndex - 1) {
const _filterText = props.inputValue
props.setInputValue('')
await createLabelFromFilterText(_filterText)
return
}
if (focusedIndex !== undefined) {
const label = filteredLabels[focusedIndex]
if (label) {
toggleLabel(label)
}
}
}
},
[
props.inputValue,
filteredLabels,
focusedIndex,
createLabelFromFilterText,
router,
toggleLabel,
]
)
return (
setFocusedIndex(undefined)}
inputValue={props.inputValue}
setInputValue={props.setInputValue}
selectedLabels={props.selectedLabels}
setSelectedLabels={props.setSelectedLabels}
tabCount={props.tabCount}
setTabCount={props.setTabCount}
tabStartValue={props.tabStartValue}
setTabStartValue={props.setTabStartValue}
highlightLastLabel={props.highlightLastLabel}
setHighlightLastLabel={props.setHighlightLastLabel}
deleteLastLabel={props.deleteLastLabel}
selectOrCreateLabel={props.selectOrCreateLabel}
clearInputState={props.clearInputState}
/>
{props.errorMessage && (
<>
{props.errorMessage}
>
)}
{filteredLabels.map((label, idx) => (
))}
)
}