このコミットが含まれているのは:
2026-10-28 00:28:29 +09:00
コミット d6cf992622
27個のファイルの変更、630行の追加、60行の削除
+84
ファイルの表示
@@ -0,0 +1,84 @@
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'
? 'pattern.link' : '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 ('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-9 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-9 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-9 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-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