import { CodeRounded, ViewModuleRounded } from '@mui/icons-material' import { Box, Button, Chip, FormHelperText, IconButton, TextField, Tooltip, Typography, } from '@mui/material' import type { ReactNode } from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' export type BaseSplitChipEditorMode = 'visual' | 'advanced' interface BaseSplitChipEditorProps { value?: string onChange: (value: string) => void disabled?: boolean error?: boolean helperText?: ReactNode placeholder?: string rows?: number separator?: string splitPattern?: RegExp defaultMode?: BaseSplitChipEditorMode showModeToggle?: boolean ariaLabel?: string addLabel?: ReactNode emptyLabel?: ReactNode modeLabels?: Partial> renderHeader?: (modeToggle: ReactNode) => ReactNode } const DEFAULT_SPLIT_PATTERN = /[,\n;\r]+/ const splitValue = (value: string, splitPattern: RegExp) => value .split(splitPattern) .map((item) => item.trim()) .filter(Boolean) export const BaseSplitChipEditor = ({ value = '', onChange, disabled = false, error = false, helperText, placeholder, rows = 4, separator = ',', splitPattern = DEFAULT_SPLIT_PATTERN, defaultMode = 'visual', showModeToggle = true, ariaLabel, addLabel, emptyLabel, modeLabels, renderHeader, }: BaseSplitChipEditorProps) => { const { t } = useTranslation() const [mode, setMode] = useState(defaultMode) const [draft, setDraft] = useState('') const resolvedLabels = useMemo( () => ({ visual: modeLabels?.visual ?? t('shared.editorModes.visualization'), advanced: modeLabels?.advanced ?? t('shared.editorModes.advanced'), add: addLabel ?? t('shared.actions.new'), empty: emptyLabel ?? t('shared.statuses.empty'), }), [t, modeLabels, addLabel, emptyLabel], ) const values = useMemo( () => splitValue(value, splitPattern), [value, splitPattern], ) const items = useMemo(() => { const counts = new Map() return values.map((item) => { const nextCount = (counts.get(item) ?? 0) + 1 counts.set(item, nextCount) return { key: `${item}-${nextCount}`, value: item, } }) }, [values]) const handleAddDraft = () => { const nextValues = splitValue(draft, splitPattern) if (!nextValues.length) { return } const nextValue = [...values, ...nextValues].join(separator) onChange(nextValue) setDraft('') } const handleRemoveItem = (index: number) => { const nextValue = values.filter((_, itemIndex) => itemIndex !== index) onChange(nextValue.join(separator)) } const nextMode = mode === 'visual' ? 'advanced' : 'visual' const toggleLabel = nextMode === 'visual' ? resolvedLabels.visual : resolvedLabels.advanced const ToggleIcon = nextMode === 'visual' ? ViewModuleRounded : CodeRounded const resolvedAriaLabel = ariaLabel ?? (typeof toggleLabel === 'string' ? toggleLabel : undefined) const modeToggle = showModeToggle ? ( { setMode(nextMode) if (nextMode === 'visual') { setDraft('') } }} > ) : null return ( <> {renderHeader ? renderHeader(modeToggle) : modeToggle} {mode === 'visual' ? ( {items.length ? ( items.map((item, index) => ( handleRemoveItem(index) } /> )) ) : ( {resolvedLabels.empty} )} setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault() handleAddDraft() } }} /> {helperText && ( {helperText} )} ) : ( { onChange(event.target.value) }} /> )} ) }