Выбор тегов
Сделай компонент выбора тегов для статьи: пользователь набирает теги в одном поле, каждый введённый тег превращается в удаляемую метку. Выбранные теги показывай под полем и отдавай наружу через колбэк onChange.
tag-picker__none__claude-code__claude-opus-5__1 · 23.09.2026, 19:52:01 мск
Есть проблемы
Без контекста · claude-opus-5 · Claude CodeКонтекст до задачи~154
Токенов на прогон10 403
Цена$0.052
Ошибок компилятора3
Первый вызов модели получил 8 579 токенов входа, включая системный промпт Claude Code. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.
Компилятор
- Строка 2 · TS2305
Module '"@jinx-ui/react"' has no exported member 'Stack'. - Строка 2 · TS2305
Module '"@jinx-ui/react"' has no exported member 'Tag'. - Строка 2 · TS2305
Module '"@jinx-ui/react"' has no exported member 'TextField'.
Выдуманные компоненты
StackTagTextField
Линтер и покрытие
- inline-styleСтроки 64, 77Инлайновые стили вместо классов и токенов библиотеки
Компоненты библиотеки
Ни одного: код написан без библиотеки, поэтому прогон не засчитан
Ожидались JxTagInput · покрытие 0%
Только формулировка задачи
Вход 2 · выход 1 824 · из кэша 8 233 · в кэш 344 · 22 с
Рендер
Код компонента
tsx89 строк
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
import { useState, useCallback, type KeyboardEvent, type ChangeEvent } from 'react';
import { Stack, Tag, TextField } from '@jinx-ui/react';
// API names (TextField / Tag.onRemove / Stack) assumed from Jinx UI conventions — verify against the installed version.
type ArticleTagPickerProps = {
label?: string;
placeholder?: string;
initialTags?: string[];
maxTags?: number;
onChange?: (tags: string[]) => void;
};
function ArticleTagPicker({
label = 'Теги статьи',
placeholder = 'Введите тег и нажмите Enter',
initialTags = [],
maxTags = 10,
onChange,
}: ArticleTagPickerProps) {
const [tags, setTags] = useState<string[]>(initialTags);
const [draft, setDraft] = useState('');
const commit = useCallback(
(raw: string) => {
const next = raw.trim().replace(/\s+/g, ' ');
if (!next) return;
if (tags.length >= maxTags) return;
if (tags.some((tag) => tag.toLowerCase() === next.toLowerCase())) {
setDraft('');
return;
}
const updated = [...tags, next];
setTags(updated);
setDraft('');
onChange?.(updated);
},
[tags, maxTags, onChange],
);
const remove = useCallback(
(target: string) => {
const updated = tags.filter((tag) => tag !== target);
setTags(updated);
onChange?.(updated);
},
[tags, onChange],
);
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' || event.key === ',') {
event.preventDefault();
commit(draft);
return;
}
if (event.key === 'Backspace' && draft === '' && tags.length > 0) {
remove(tags[tags.length - 1]);
}
};
const isFull = tags.length >= maxTags;
return (
<Stack direction="column" style={{ gap: 'var(--jx-space-2)' }}>
<TextField
label={label}
value={draft}
placeholder={isFull ? `Достигнут предел: ${maxTags}` : placeholder}
disabled={isFull}
hint={`${tags.length} из ${maxTags}`}
onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => commit(draft)}
/>
{tags.length > 0 && (
<Stack direction="row" wrap style={{ gap: 'var(--jx-space-1)' }}>
{tags.map((tag) => (
<Tag key={tag} onRemove={() => remove(tag)} removeLabel={`Удалить тег ${tag}`}>
{tag}
</Tag>
))}
</Stack>
)}
</Stack>
);
}
export default ArticleTagPicker;