115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
![]() |
import { Combobox } from '@headlessui/react'
|
||
|
import Fuse from 'fuse.js'
|
||
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||
|
import { CommandArgumentOption } from 'lib/commandTypes'
|
||
|
import { useEffect, useRef, useState } from 'react'
|
||
|
|
||
|
function CommandArgOptionInput({
|
||
|
options,
|
||
|
argName,
|
||
|
stepBack,
|
||
|
onSubmit,
|
||
|
placeholder,
|
||
|
}: {
|
||
|
options: CommandArgumentOption<unknown>[]
|
||
|
argName: string
|
||
|
stepBack: () => void
|
||
|
onSubmit: (data: unknown) => void
|
||
|
placeholder?: string
|
||
|
}) {
|
||
|
const { commandBarSend, commandBarState } = useCommandsContext()
|
||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||
|
const formRef = useRef<HTMLFormElement>(null)
|
||
|
const [argValue, setArgValue] = useState<(typeof options)[number]['value']>(
|
||
|
options.find((o) => 'isCurrent' in o && o.isCurrent)?.value ||
|
||
|
commandBarState.context.argumentsToSubmit[argName] ||
|
||
|
options[0].value
|
||
|
)
|
||
|
const [query, setQuery] = useState('')
|
||
|
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
||
|
|
||
|
const fuse = new Fuse(options, {
|
||
|
keys: ['name', 'description'],
|
||
|
threshold: 0.3,
|
||
|
})
|
||
|
|
||
|
useEffect(() => {
|
||
|
inputRef.current?.focus()
|
||
|
inputRef.current?.select()
|
||
|
}, [inputRef])
|
||
|
|
||
|
useEffect(() => {
|
||
|
const results = fuse.search(query).map((result) => result.item)
|
||
|
setFilteredOptions(query.length > 0 ? results : options)
|
||
|
}, [query])
|
||
|
|
||
|
function handleSelectOption(option: CommandArgumentOption<unknown>) {
|
||
|
setArgValue(option)
|
||
|
onSubmit(option.value)
|
||
|
}
|
||
|
|
||
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||
|
e.preventDefault()
|
||
|
onSubmit(argValue)
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
||
|
<Combobox value={argValue} onChange={handleSelectOption} name="options">
|
||
|
<div className="flex items-center mx-4 mt-4 mb-2">
|
||
|
<label
|
||
|
htmlFor="option-input"
|
||
|
className="capitalize px-2 py-1 rounded-l bg-chalkboard-100 dark:bg-chalkboard-80 text-chalkboard-10 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80"
|
||
|
>
|
||
|
{argName}
|
||
|
</label>
|
||
|
<Combobox.Input
|
||
|
id="option-input"
|
||
|
ref={inputRef}
|
||
|
onChange={(event) => setQuery(event.target.value)}
|
||
|
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||
|
onKeyDown={(event) => {
|
||
|
if (event.metaKey && event.key === 'k')
|
||
|
commandBarSend({ type: 'Close' })
|
||
|
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||
|
stepBack()
|
||
|
}
|
||
|
}}
|
||
|
placeholder={
|
||
|
(argValue as CommandArgumentOption<unknown>)?.name ||
|
||
|
placeholder ||
|
||
|
'Select an option for ' + argName
|
||
|
}
|
||
|
autoCapitalize="off"
|
||
|
autoComplete="off"
|
||
|
autoCorrect="off"
|
||
|
spellCheck="false"
|
||
|
autoFocus
|
||
|
/>
|
||
|
</div>
|
||
|
<Combobox.Options
|
||
|
static
|
||
|
className="overflow-y-auto max-h-96 cursor-pointer"
|
||
|
>
|
||
|
{filteredOptions?.map((option) => (
|
||
|
<Combobox.Option
|
||
|
key={option.name}
|
||
|
value={option}
|
||
|
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
|
||
|
>
|
||
|
<p className="flex-grow">{option.name} </p>
|
||
|
{'isCurrent' in option && option.isCurrent && (
|
||
|
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||
|
current
|
||
|
</small>
|
||
|
)}
|
||
|
</Combobox.Option>
|
||
|
))}
|
||
|
</Combobox.Options>
|
||
|
</Combobox>
|
||
|
</form>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export default CommandArgOptionInput
|