85 行
3.9 KiB
TypeScript
85 行
3.9 KiB
TypeScript
import { useState } from 'react'
|
||
|
||
import CountsCandidatePicker from '@/components/counts/CountsCandidatePicker'
|
||
import DateTimeField from '@/components/common/DateTimeField'
|
||
import { Button } from '@/components/ui/button'
|
||
import useDialogue from '@/lib/dialogues/useDialogue'
|
||
import { inputClass } from '@/lib/utils'
|
||
import { useUITranslation } from '@/lib/useUITranslation'
|
||
|
||
import type { CountsCondition, CountsDefinition, CountsFieldMetadata }
|
||
from '@/lib/countsDefinitions'
|
||
|
||
|
||
const CountsConditionValues = (
|
||
{ condition, field, definition, params, maximum, change }: {
|
||
condition: CountsCondition; field: CountsFieldMetadata; definition: CountsDefinition
|
||
params: URLSearchParams; maximum: number
|
||
change: (values: (string | number | boolean)[]) => void },
|
||
) => {
|
||
const { label, message } = useUITranslation ('counts')
|
||
const dialogue = useDialogue ()
|
||
const [draft, setDraft] = useState ('')
|
||
const values = condition.values ?? []
|
||
const pick = () => {
|
||
void dialogue.form ({ title: label (field.candidateSource === 'tags'
|
||
? 'counts:pattern.link' : 'counts:pattern.pattern'), size: 'large', footerLayout: 'inline',
|
||
body: controls => <CountsCandidatePicker field={field} definition={definition}
|
||
params={params} initial={values} maximum={maximum} controls={controls} save={change}/> })
|
||
}
|
||
if (condition.input === 'list' || field.candidateSource === 'tags')
|
||
return <Button type="button" variant="outline" disabled={condition.blank} onClick={pick}>
|
||
{label ('counts:pattern.list')}{' ('}{values.length}{')'}</Button>
|
||
const append = () => {
|
||
if (draft === '' || values.length >= maximum
|
||
|| values.some (value => String (value) === draft))
|
||
return
|
||
change ([...values, draft])
|
||
setDraft ('')
|
||
}
|
||
const input = () => {
|
||
if (field.type === 'datetime')
|
||
return <DateTimeField precision="second" value={draft} disabled={condition.blank}
|
||
className="h-10 w-48 min-w-0 max-w-full" aria-label={message ('value').text}
|
||
onChange={value => setDraft (value ?? '')}/>
|
||
if (field.type === 'boolean' || field.choices != null)
|
||
{
|
||
const choices = field.choices ?? ['true', 'false'].map (value => ({
|
||
value, label: `counts:truthValues.${value}` }))
|
||
return <select value={draft} disabled={condition.blank}
|
||
className={inputClass (false, 'h-10 w-36 min-w-0 max-w-full')}
|
||
aria-label={message ('value').text} onChange={event => setDraft (event.target.value)}>
|
||
<option value=""/>
|
||
{choices.map (choice => <option key={choice.value} value={choice.value}>
|
||
{message (choice.label).text}</option>)}
|
||
</select>
|
||
}
|
||
return <input value={draft} disabled={condition.blank}
|
||
type={field.type === 'integer' ? 'number' : 'text'} aria-label={message ('value').text}
|
||
className={inputClass (false, 'h-10 w-36 min-w-0 max-w-full')}
|
||
onChange={event => setDraft (event.target.value)}/>
|
||
}
|
||
const display = (value: string | number | boolean) => {
|
||
const choice = field.choices?.find (choice => choice.value === String (value))
|
||
if (choice != null) return message (choice.label).text
|
||
if (field.type === 'boolean') return message (`truthValues.${value}`).text
|
||
return String (value)
|
||
}
|
||
return <div className="flex min-w-0 max-w-full flex-wrap items-center gap-x-2 gap-y-2 py-1">
|
||
{values.map ((value, index) => <span key={`${index}:${value}`}
|
||
className="inline-flex items-center gap-1">
|
||
{display (value)}<Button type="button" size="sm" variant="ghost"
|
||
aria-label={message ('remove').text} disabled={condition.blank}
|
||
onClick={() => change (values.filter ((_, position) => position !== index))}>×</Button>
|
||
</span>)}
|
||
{input ()}
|
||
<Button type="button" variant="outline" disabled={condition.blank || draft === ''
|
||
|| values.length >= maximum} onClick={append}>{label ('add')}</Button>
|
||
<Button type="button" variant="ghost" disabled={condition.blank}
|
||
onClick={() => change ([])}>{label ('shared:actions.reset')}</Button>
|
||
</div>
|
||
}
|
||
|
||
|
||
export default CountsConditionValues
|