ファイル
btrc-hub/frontend/src/components/counts/CountsConditionValues.tsx
T
2026-10-28 19:28:11 +09:00

85 行
3.9 KiB
TypeScript
Raw Blame 履歴

このファイルには曖昧(ambiguous)なUnicode文字が含まれています
このファイルには、他の文字と見間違える可能性があるUnicode文字が含まれています。 それが意図的なものと考えられる場合は、この警告を無視して構いません。 それらの文字を表示するにはエスケープボタンを使用します。
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