Выбор тегов
Сделай компонент выбора тегов для статьи: пользователь набирает теги в одном поле, каждый введённый тег превращается в удаляемую метку. Выбранные теги показывай под полем и отдавай наружу через колбэк onChange.
tag-picker__mcp__claude-code__claude-opus-5__1 · 24.09.2026, 13:28:37 мск
Первый вызов модели получил 12 466 токенов входа, включая системный промпт Claude Code; вызовов модели 3, каждый отправлял разговор заново, и самый большой получил 17 211 токенов. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.
Компилятор
Ошибок нет: все компоненты и пропсы существуют.
Линтер и покрытие
- inline-styleСтроки 60, 80, 83, 93Инлайновые стили вместо классов и токенов библиотеки
JxChip, JxInputField
Ожидались JxTagInput · покрытие 0%
- get_rules({"task":"ui"}) → ~1247 токенов
- get_component_api({"name":"JxTagInput","detail":"full"}) → ~323 токенов
- get_component_api({"name":"JxChip","detail":"full"}) → ~360 токенов
- get_component_api({"name":"JxInputField","detail":"full"}) → ~484 токенов
Инструменты (search_components,get_component_api,get_component_examples,list_design_tokens,get_docs,get_rules, ~1213)
Вход 6 · выход 2 915 · из кэша 40 448 · в кэш 5 194 · 40 с
Рендер
Код компонента
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 90 91 92 93 94 95 96 97 98 99 100 101 102
import { useState, type KeyboardEvent } from 'react';
import { JxChip, JxInputField } from '@jinx-ui/react';
type ArticleTagsFieldProps = {
label?: string;
placeholder?: string;
value?: string[];
defaultValue?: string[];
onChange?: (tags: string[]) => void;
};
export default function ArticleTagsField({
label = 'Теги статьи',
placeholder = 'Введите тег и нажмите Enter',
value,
defaultValue,
onChange,
}: ArticleTagsFieldProps) {
const [draft, setDraft] = useState('');
const [error, setError] = useState<string | undefined>(undefined);
const [internalTags, setInternalTags] = useState<string[]>(defaultValue ?? []);
const tags = value ?? internalTags;
const commit = (next: string[]) => {
if (value === undefined) setInternalTags(next);
onChange?.(next);
};
const addTag = () => {
const tag = draft.trim();
if (tag.length === 0) return;
if (tags.some((existing) => existing.toLowerCase() === tag.toLowerCase())) {
setError(`Тег «${tag}» уже добавлен`);
return;
}
commit([...tags, tag]);
setDraft('');
setError(undefined);
};
const removeTag = (tag: string) => {
commit(tags.filter((existing) => existing !== tag));
setError(undefined);
};
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' || event.key === ',') {
event.preventDefault();
addTag();
return;
}
if (event.key === 'Backspace' && draft.length === 0 && tags.length > 0) {
event.preventDefault();
removeTag(tags[tags.length - 1]);
}
};
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', fontFamily: 'var(--jx-font-sans)' }}>
<JxInputField
label={label}
placeholder={placeholder}
value={draft}
onChange={(event) => {
setDraft(event.target.value);
setError(undefined);
}}
onKeyDown={handleKeyDown}
onBlur={addTag}
errorText={error}
helperText="Enter или запятая добавляет тег, Backspace удаляет последний"
aria-describedby="article-tags-list"
/>
<div
id="article-tags-list"
role="group"
aria-label="Выбранные теги"
style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '8px', minHeight: '28px' }}
>
{tags.length === 0 ? (
<span style={{ color: 'var(--jx-text-2)', fontSize: '13px' }}>
Пока ни одного тега — введите первый и нажмите Enter
</span>
) : (
tags.map((tag) => (
<JxChip
key={tag}
removable
onRemove={() => removeTag(tag)}
title={tag}
style={{ maxWidth: '220px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
{tag}
</JxChip>
))
)}
</div>
</div>
);
}