Push props down so we can manipulate at the top level

This commit is contained in:
Jackson Harper 2023-06-19 18:58:39 +08:00
parent 3c7dbc995c
commit a730a4cb85
4 changed files with 251 additions and 383 deletions

View file

@ -2,14 +2,16 @@ import { getLuminance, lighten, parseToRgba, toHsla } from 'color2k'
import { useRouter } from 'next/router'
import { Button } from './Button'
import { SpanBox, HStack } from './LayoutPrimitives'
import { Circle } from 'phosphor-react'
import { Circle, X } from 'phosphor-react'
import { isDarkTheme } from '../../lib/themeUpdater'
import { theme } from '../tokens/stitches.config'
type LabelChipProps = {
text: string
color: string // expected to be a RGB hex color string
isSelected?: boolean
useAppAppearance?: boolean
xAction?: () => void
}
export function LabelChip(props: LabelChipProps): JSX.Element {
@ -18,7 +20,7 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
const luminance = getLuminance(props.color)
const textColor = luminance > 0.5 ? '#000000' : '#ffffff'
const selectedBorder = isDark ? 'white' : 'black'
const selectedBorder = isDark ? '#FFEA9F' : 'black'
const unSelectedBorder = isDark ? '#6A6968' : '#D9D9D9'
if (props.useAppAppearance) {
@ -42,9 +44,30 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
backgroundColor: isDark ? '#2A2A2A' : '#F5F5F5',
}}
>
<HStack alignment="center" css={{ gap: '5px' }}>
<HStack alignment="center" css={{ gap: '10px' }}>
<Circle size={14} color={props.color} weight="fill" />
<SpanBox css={{ pt: '1px' }}>{props.text}</SpanBox>
{props.xAction && (
<Button
style="ghost"
css={{ display: 'flex', pt: '1px' }}
onClick={(event) => {
if (props.xAction) {
props.xAction()
event.preventDefault()
}
}}
>
<X
size={14}
color={
props.isSelected
? '#FFEA9F'
: theme.colors.thBorderSubtle.toString()
}
/>
</Button>
)}
</HStack>
</SpanBox>
)

View file

@ -4,29 +4,34 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Label } from '../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { LabelChip } from './LabelChip'
import { randomLabelColorHex } from '../../utils/settings-page/labels/labelColorObjects'
import { v4 as uuidv4 } from 'uuid'
import { createLabelMutation } from '../../lib/networking/mutations/createLabelMutation'
import { showSuccessToast } from '../../lib/toastHelpers'
import { isTouchScreenDevice } from '../../lib/deviceType'
type LabelsPickerProps = {
selectedLabels: Label[]
focused: boolean
onFocus?: () => void
onFilterTextChange?: (filterText: string) => void
inputValue: string
setInputValue: (value: string) => void
clearInputState: () => void
onFocus?: () => void
setSelectedLabels: (labels: Label[]) => void
deleteLastLabel: () => void
selectOrCreateLabel: (value: string) => void
tabCount: number
setTabCount: (count: number) => void
tabStartValue: string
setTabStartValue: (value: string) => void
highlightLastLabel: boolean
setHighlightLastLabel: (set: boolean) => void
}
export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
const inputRef = useRef<HTMLInputElement | null>()
const availableLabels = useGetLabelsQuery()
const [inputValue, setInputValue] = useState('')
const [tabCount, setTabCount] = useState(-1)
const [tabStartValue, setTabStartValue] = useState('')
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
useEffect(() => {
if (!isTouchScreenDevice() && props.focused && inputRef.current) {
@ -34,115 +39,25 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
}
}, [props.focused])
useEffect(() => {
if (props.onFilterTextChange) {
props.onFilterTextChange(inputValue)
}
}, [inputValue])
const showMessage = useCallback((msg: string) => {
console.log('showMessage: ', msg)
}, [])
const clearInputState = useCallback(() => {
setTabCount(-1)
setInputValue('')
setTabStartValue('')
setHighlightLastLabel(false)
}, [tabCount, inputValue, tabStartValue])
const createLabelAsync = useCallback(
(tempLabel: Label) => {
;(async () => {
const currentLabels = props.selectedLabels
const newLabel = await createLabelMutation(
tempLabel.name,
tempLabel.color
)
if (newLabel) {
const idx = currentLabels.findIndex((l) => l.id === tempLabel.id)
showSuccessToast(`Created label ${newLabel.name}`, {
position: 'bottom-right',
})
if (idx !== -1) {
currentLabels[idx] = newLabel
props.setSelectedLabels([...currentLabels])
} else {
props.setSelectedLabels([...currentLabels, newLabel])
}
} else {
showMessage(`Error creating label ${tempLabel.name}`)
}
})()
},
[props.selectedLabels]
)
const selectOrCreateLabel = useCallback(
(value: string) => {
const current = props.selectedLabels ?? []
const lowerCasedValue = value.toLowerCase()
const existing = availableLabels.labels.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)
if (lowerCasedValue.length < 1) {
return
}
if (existing) {
const isAdded = props.selectedLabels.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)
if (!isAdded) {
props.setSelectedLabels([...current, existing])
clearInputState()
} else {
showMessage(`label ${value} already added.`)
}
} else {
const tempLabel = {
id: uuidv4(),
name: value,
color: randomLabelColorHex(),
description: '',
createdAt: new Date(),
_temporary: true,
}
props.setSelectedLabels([...current, tempLabel])
clearInputState()
createLabelAsync(tempLabel)
}
},
[
availableLabels,
props.selectedLabels,
clearInputState,
createLabelAsync,
showMessage,
]
)
const autoComplete = useCallback(() => {
const lowerCasedValue = inputValue.toLowerCase()
const lowerCasedValue = props.inputValue.toLowerCase()
if (lowerCasedValue.length < 1) {
return
}
let _tabCount = tabCount
let _tabStartValue = tabStartValue.toLowerCase()
let _tabCount = props.tabCount
let _tabStartValue = props.tabStartValue.toLowerCase()
if (_tabCount === -1) {
_tabCount = 0
_tabStartValue = lowerCasedValue
setTabCount(0)
setTabStartValue(lowerCasedValue)
props.setTabCount(0)
props.setTabStartValue(lowerCasedValue)
} else {
_tabCount = tabCount + 1
setTabCount(_tabCount)
_tabCount = props.tabCount + 1
props.setTabCount(_tabCount)
}
const matches = availableLabels.labels.filter((l) =>
@ -150,32 +65,21 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
)
if (_tabCount < matches.length) {
setInputValue(matches[_tabCount].name)
props.setInputValue(matches[_tabCount].name)
} else if (matches.length > 0) {
setTabCount(0)
setInputValue(matches[0].name)
props.setTabCount(0)
props.setInputValue(matches[0].name)
}
}, [inputValue, availableLabels, tabCount, tabStartValue])
const deleteLastLabel = useCallback(() => {
if (highlightLastLabel) {
const current = props.selectedLabels
current.pop()
props.setSelectedLabels([...current])
setHighlightLastLabel(false)
} else {
setHighlightLastLabel(true)
}
}, [highlightLastLabel, props.selectedLabels])
}, [props.inputValue, availableLabels, props.tabCount, props.tabStartValue])
const clearTabState = useCallback(() => {
setTabCount(-1)
setTabStartValue('')
props.setTabCount(-1)
props.setTabStartValue('')
}, [])
const isEmpty = useMemo(() => {
return props.selectedLabels.length === 0 && inputValue.length === 0
}, [inputValue, props.selectedLabels])
return props.selectedLabels.length === 0 && props.inputValue.length === 0
}, [props.inputValue, props.selectedLabels])
return (
<Box
@ -206,7 +110,6 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
'>span': {
marginTop: '0px',
marginBottom: '0px',
borderColor: 'transparent',
},
}}
onMouseDown={(event) => {
@ -228,8 +131,16 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
text={label.name}
color={label.color}
isSelected={
highlightLastLabel && idx == props.selectedLabels.length - 1
props.highlightLastLabel && idx == props.selectedLabels.length - 1
}
xAction={() => {
const idx = props.selectedLabels.findIndex((l) => l.id == label.id)
if (idx !== -1) {
const _selectedLabels = props.selectedLabels
_selectedLabels.splice(idx, 1)
props.setSelectedLabels([..._selectedLabels])
}
}}
useAppAppearance={true}
/>
))}
@ -253,17 +164,17 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
}}
minWidth="2px"
maxLength={48}
value={inputValue}
value={props.inputValue}
onClick={(event) => {
event.stopPropagation()
}}
onKeyUp={(event) => {
switch (event.key) {
case 'Escape':
clearInputState()
props.clearInputState()
break
case 'Enter':
selectOrCreateLabel(inputValue)
props.selectOrCreateLabel(props.inputValue)
event.preventDefault()
break
}
@ -277,243 +188,18 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
case 'Delete':
case 'Backspace':
clearTabState()
if (inputValue.length === 0) {
deleteLastLabel()
if (props.inputValue.length === 0) {
props.deleteLastLabel()
event.preventDefault()
}
break
}
}}
onChange={function (event) {
setInputValue(event.target.value)
props.setInputValue(event.target.value)
}}
/>
</SpanBox>
</Box>
)
}
// import { styled } from '@stitches/react'
// import { Label } from '../../lib/networking/fragments/labelFragment'
// import { LabelChip } from './LabelChip'
// import { Box } from './LayoutPrimitives'
// import { useCallback, useMemo, useState } from 'react'
// import { useCombobox, useMultipleSelection } from 'downshift'
// type LabelsPickerProps = {
// // selectedLabels: Label[]
// }
// const InputLabel = styled('input', {
// outline: 'none',
// boxSizing: 'content-box',
// maxWidth: '237px',
// background: 'red',
// borderStyle: 'none',
// width: '100%',
// })
// const StyledInput = styled('input', {
// outline: 'none',
// boxSizing: 'content-box',
// maxWidth: '237px',
// background: 'red',
// borderStyle: 'none',
// width: '100%',
// })
// type Suggestion = {
// name: string
// year: number
// }
// const languages = [
// {
// name: 'C',
// year: 1972,
// },
// {
// name: 'Elm',
// year: 2012,
// },
// ]
// export function LabelsPicker(props: LabelsPickerProps): JSX.Element {
// const labels: Label[] = [
// { id: '123', name: 'Label 01', color: '#000000', createdAt: new Date(0) },
// { id: '124', name: 'Label 02', color: '#000000', createdAt: new Date(0) },
// { id: '125', name: 'Label 03', color: '#000000', createdAt: new Date(0) },
// ]
// const initialSelectedItems: Label[] = []
// function getFilteredBooks(selectedItems: Label[], inputValue: string) {
// const lowerCasedInputValue = inputValue.toLowerCase()
// return labels.filter(function filterBook(book) {
// return (
// !selectedItems.includes(book) &&
// book.name.toLowerCase().includes(lowerCasedInputValue)
// )
// })
// }
// function MultipleComboBox() {
// const [inputValue, setInputValue] = useState('')
// const [selectedItems, setSelectedItems] = useState(initialSelectedItems)
// const items = useMemo(
// () => getFilteredBooks(selectedItems, inputValue),
// [selectedItems, inputValue]
// )
// const { getSelectedItemProps, getDropdownProps, removeSelectedItem } =
// useMultipleSelection({
// selectedItems,
// onStateChange({ selectedItems: newSelectedItems, type }) {
// switch (type) {
// case useMultipleSelection.stateChangeTypes
// .SelectedItemKeyDownBackspace:
// case useMultipleSelection.stateChangeTypes
// .SelectedItemKeyDownDelete:
// case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
// case useMultipleSelection.stateChangeTypes
// .FunctionRemoveSelectedItem:
// setSelectedItems(newSelectedItems as Label[])
// break
// default:
// break
// }
// },
// })
// const {
// isOpen,
// getToggleButtonProps,
// getLabelProps,
// getMenuProps,
// getInputProps,
// highlightedIndex,
// getItemProps,
// selectedItem,
// } = useCombobox({
// items,
// itemToString(item: Label | null) {
// return item ? item.name : ''
// },
// defaultHighlightedIndex: 0, // after selection, highlight the first item.
// selectedItem: null,
// stateReducer(state, actionAndChanges) {
// const { changes, type } = actionAndChanges
// switch (type) {
// case useCombobox.stateChangeTypes.InputKeyDownEnter:
// case useCombobox.stateChangeTypes.ItemClick:
// return {
// ...changes,
// isOpen: true, // keep the menu open after selection.
// highlightedIndex: 0, // with the first option highlighted.
// }
// default:
// return changes
// }
// },
// onStateChange({
// inputValue: newInputValue,
// type,
// selectedItem: newSelectedItem,
// }) {
// switch (type) {
// case useCombobox.stateChangeTypes.InputKeyDownEnter:
// case useCombobox.stateChangeTypes.ItemClick:
// case useCombobox.stateChangeTypes.InputBlur:
// if (newSelectedItem) {
// setSelectedItems([...selectedItems, newSelectedItem])
// }
// break
// case useCombobox.stateChangeTypes.InputChange:
// setInputValue(newInputValue || '')
// break
// default:
// break
// }
// },
// })
// return (
// <div className="w-[592px]">
// <div className="flex flex-col gap-1">
// <label className="w-fit" {...getLabelProps()}>
// Pick some books:
// </label>
// <div className="shadow-sm bg-white inline-flex gap-2 items-center flex-wrap p-1.5">
// {selectedItems.map(function renderSelectedItem(
// selectedItemForRender,
// index
// ) {
// return (
// <span
// className="bg-gray-100 rounded-md px-1 focus:bg-red-400"
// key={`selected-item-${index}`}
// {...getSelectedItemProps({
// selectedItem: selectedItemForRender,
// index,
// })}
// >
// {selectedItemForRender.name}
// <span
// className="px-1 cursor-pointer"
// onClick={(e) => {
// e.stopPropagation()
// removeSelectedItem(selectedItemForRender)
// }}
// >
// &#10005;
// </span>
// </span>
// )
// })}
// <div className="flex gap-0.5 grow">
// <input
// placeholder="Best book ever"
// className="w-full"
// {...getInputProps(
// getDropdownProps({ preventKeyAction: isOpen })
// )}
// />
// <button
// aria-label="toggle menu"
// className="px-2"
// type="button"
// {...getToggleButtonProps()}
// >
// &#8595;
// </button>
// </div>
// </div>
// </div>
// <ul
// className={`absolute w-inherit bg-white mt-1 shadow-md max-h-80 overflow-scroll p-0 ${
// !(isOpen && items.length) && 'hidden'
// }`}
// {...getMenuProps()}
// >
// {isOpen &&
// items.map((item, index) => (
// <li
// // className={
// // (highlightedIndex === index && 'bg-blue-300',
// // selectedItem === item && 'font-bold',
// // 'py-2 px-3 shadow-sm flex flex-col')
// // }
// key={`${item.id}${index}`}
// {...getItemProps({ item, index })}
// >
// <span>{item.name}</span>
// <span className="text-sm text-gray-700">{item.name}</span>
// </li>
// ))}
// </ul>
// </div>
// )
// }
// return <MultipleComboBox />
// }

View file

@ -3,12 +3,10 @@ import Link from 'next/link'
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 } from '../../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { Check, Circle, PencilSimple, Plus } from 'phosphor-react'
import { isTouchScreenDevice } from '../../../lib/deviceType'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
@ -22,20 +20,48 @@ export interface LabelsProvider {
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
}
type HeaderProps = {
filterText: string
focused: boolean
resetFocusedIndex: () => void
setFilterText: (text: string) => 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', {
@ -55,14 +81,22 @@ function Header(props: HeaderProps): JSX.Element {
>
<LabelsPicker
focused={props.focused}
inputValue={props.inputValue}
setInputValue={props.setInputValue}
selectedLabels={props.selectedLabels}
setSelectedLabels={props.setSelectedLabels}
onFilterTextChange={(filterText) => {
props.setFilterText(filterText)
}}
tabCount={props.tabCount}
setTabCount={props.setTabCount}
tabStartValue={props.tabStartValue}
setTabStartValue={props.setTabStartValue}
highlightLastLabel={props.highlightLastLabel}
setHighlightLastLabel={props.setHighlightLastLabel}
onFocus={() => {
props.resetFocusedIndex()
}}
clearInputState={props.clearInputState}
deleteLastLabel={props.deleteLastLabel}
selectOrCreateLabel={props.selectOrCreateLabel}
/>
</Box>
</VStack>
@ -223,12 +257,11 @@ function Footer(props: FooterProps): JSX.Element {
export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const router = useRouter()
const [filterText, setFilterText] = useState('')
const { labels, revalidate } = useGetLabelsQuery()
useEffect(() => {
setFocusedIndex(undefined)
}, [filterText])
}, [props.inputValue])
const isSelected = useCallback(
(label: Label): boolean => {
@ -256,6 +289,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
props.onLabelsUpdated(newSelectedLabels)
}
props.clearInputState()
revalidate()
},
[isSelected, props, revalidate]
@ -267,12 +301,12 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}
return labels
.filter((label) => {
return label.name.toLowerCase().includes(filterText.toLowerCase())
return label.name.toLowerCase().includes(props.inputValue.toLowerCase())
})
.sort((left: Label, right: Label) => {
return left.name.localeCompare(right.name)
})
}, [labels, filterText])
}, [labels, props.inputValue])
// Move focus through the labels list on tab or arrow up/down keys
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(
@ -291,7 +325,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
showErrorToast('Failed to create label', { position: 'bottom-right' })
}
},
[filterText, toggleLabel]
[props.inputValue, toggleLabel]
)
const handleKeyDown = useCallback(
@ -307,7 +341,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}
// If the `Create New label` button isn't visible we skip it
// when navigating with the arrow keys
if (focusedIndex === maxIndex && !filterText) {
if (focusedIndex === maxIndex && !props.inputValue) {
newIndex = maxIndex - 2
}
setFocusedIndex(newIndex)
@ -322,7 +356,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}
// If the `Create New label` button isn't visible we skip it
// when navigating with the arrow keys
if (focusedIndex === maxIndex - 2 && !filterText) {
if (focusedIndex === maxIndex - 2 && !props.inputValue) {
newIndex = maxIndex
}
setFocusedIndex(newIndex)
@ -334,8 +368,8 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
return
}
if (focusedIndex === maxIndex - 1) {
const _filterText = filterText
setFilterText('')
const _filterText = props.inputValue
props.setInputValue('')
await createLabelFromFilterText(_filterText)
return
}
@ -348,7 +382,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}
},
[
filterText,
props.inputValue,
filteredLabels,
focusedIndex,
createLabelFromFilterText,
@ -369,10 +403,19 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
<Header
focused={focusedIndex === undefined}
resetFocusedIndex={() => setFocusedIndex(undefined)}
setFilterText={setFilterText}
filterText={filterText}
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}
/>
<VStack
distribution="start"
@ -396,7 +439,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
))}
</VStack>
<Footer
filterText={filterText}
filterText={props.inputValue}
focused={focusedIndex === filteredLabels.length + 1}
/>
</VStack>

View file

@ -1,6 +1,5 @@
import { useCallback, useEffect, useState } from 'react'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { showErrorToast } from '../../../lib/toastHelpers'
import { SpanBox, VStack } from '../../elements/LayoutPrimitives'
import {
ModalRoot,
@ -9,6 +8,11 @@ import {
ModalTitleBar,
} from '../../elements/ModalPrimitives'
import { LabelsProvider, SetLabelsControl } from './SetLabelsControl'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showSuccessToast } from '../../../lib/toastHelpers'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { v4 as uuidv4 } from 'uuid'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
type SetLabelsModalProps = {
provider: LabelsProvider
@ -19,6 +23,12 @@ type SetLabelsModalProps = {
}
export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
const [inputValue, setInputValue] = useState('')
const availableLabels = useGetLabelsQuery()
const [tabCount, setTabCount] = useState(-1)
const [tabStartValue, setTabStartValue] = useState('')
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
const [selectedLabels, setSelectedLabels] = useState(
props.provider.labels ?? []
)
@ -37,6 +47,101 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
[props, selectedLabels]
)
const showMessage = useCallback((msg: string) => {
console.log('showMessage: ', msg)
}, [])
const clearInputState = useCallback(() => {
setTabCount(-1)
setInputValue('')
setTabStartValue('')
setHighlightLastLabel(false)
}, [tabCount, tabStartValue, highlightLastLabel])
const createLabelAsync = useCallback(
(tempLabel: Label) => {
;(async () => {
const currentLabels = selectedLabels
const newLabel = await createLabelMutation(
tempLabel.name,
tempLabel.color
)
if (newLabel) {
const idx = currentLabels.findIndex((l) => l.id === tempLabel.id)
showSuccessToast(`Created label ${newLabel.name}`, {
position: 'bottom-right',
})
if (idx !== -1) {
currentLabels[idx] = newLabel
setSelectedLabels([...currentLabels])
} else {
setSelectedLabels([...currentLabels, newLabel])
}
} else {
showMessage(`Error creating label ${tempLabel.name}`)
}
})()
},
[selectedLabels]
)
const selectOrCreateLabel = useCallback(
(value: string) => {
const current = selectedLabels ?? []
const lowerCasedValue = value.toLowerCase()
const existing = availableLabels.labels.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)
if (lowerCasedValue.length < 1) {
return
}
if (existing) {
const isAdded = selectedLabels.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)
if (!isAdded) {
setSelectedLabels([...current, existing])
clearInputState()
} else {
showMessage(`label ${value} already added.`)
}
} else {
const tempLabel = {
id: uuidv4(),
name: value,
color: randomLabelColorHex(),
description: '',
createdAt: new Date(),
_temporary: true,
}
setSelectedLabels([...current, tempLabel])
clearInputState()
createLabelAsync(tempLabel)
}
},
[
availableLabels,
selectedLabels,
clearInputState,
createLabelAsync,
showMessage,
]
)
const deleteLastLabel = useCallback(() => {
if (highlightLastLabel) {
const current = selectedLabels
current.pop()
setSelectedLabels([...current])
setHighlightLastLabel(false)
} else {
setHighlightLastLabel(true)
}
}, [highlightLastLabel, selectedLabels])
useEffect(() => {
if (!containsTemporaryLabel(selectedLabels)) {
;(async () => {
@ -64,9 +169,20 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
</SpanBox>
<SetLabelsControl
provider={props.provider}
inputValue={inputValue}
setInputValue={setInputValue}
clearInputState={clearInputState}
selectedLabels={selectedLabels}
setSelectedLabels={setSelectedLabels}
onLabelsUpdated={props.onLabelsUpdated}
tabCount={tabCount}
setTabCount={setTabCount}
tabStartValue={tabStartValue}
setTabStartValue={setTabStartValue}
highlightLastLabel={highlightLastLabel}
setHighlightLastLabel={setHighlightLastLabel}
deleteLastLabel={deleteLastLabel}
selectOrCreateLabel={selectOrCreateLabel}
/>
</VStack>
</ModalContent>