Выбор тегов
Сделай компонент выбора тегов для статьи: пользователь набирает теги в одном поле, каждый введённый тег превращается в удаляемую метку. Выбранные теги показывай под полем и отдавай наружу через колбэк onChange.
tag-picker__readme__claude-code__claude-opus-5__1 · 23.09.2026, 20:07:05 мск
Есть проблемы
README · claude-opus-5 · Claude CodeКонтекст до задачи~798
Токенов на прогон11 327
Цена$0.057
Ошибок компилятора1
Первый вызов модели получил 9 515 токенов входа, включая системный промпт Claude Code. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.
Компилятор
- Строка 46 · TS2322
Type '{ label: string; placeholder: string; value: string[]; onValueChange: (next: string[]) => void; }' is not assignable to type 'IntrinsicAttributes & JxTagInputProps'.
Выдуманные пропсы
JxTagInput.label
Линтер и покрытие
- inline-styleСтроки 39, 55Инлайновые стили вместо классов и токенов библиотеки
Компоненты библиотеки
JxButton, JxTagInput
Ожидались JxTagInput · покрытие 100%
README (README.md, ~644)
Вход 2 · выход 1 812 · из кэша 8 233 · в кэш 1 280 · 23 с
Рендер
Код компонента
tsx80 строк
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
import { useState } from 'react';
import { JxButton, JxTagInput } from '@jinx-ui/react';
export interface ArticleTagsFieldProps {
value?: string[];
defaultValue?: string[];
onChange?: (tags: string[]) => void;
label?: string;
placeholder?: string;
maxTags?: number;
}
export default function ArticleTagsField({
value,
defaultValue = [],
onChange,
label = 'Теги статьи',
placeholder = 'Введите тег и нажмите Enter',
maxTags,
}: ArticleTagsFieldProps) {
const [internal, setInternal] = useState<string[]>(defaultValue);
const tags = value ?? internal;
const commit = (next: string[]) => {
const normalized = next
.map((tag) => tag.trim())
.filter((tag, index, all) => tag.length > 0 && all.indexOf(tag) === index);
const limited =
typeof maxTags === 'number' ? normalized.slice(0, maxTags) : normalized;
if (value === undefined) setInternal(limited);
onChange?.(limited);
};
const remove = (tag: string) => commit(tags.filter((item) => item !== tag));
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 'var(--jx-space-2)',
}}
>
<JxTagInput
label={label}
placeholder={placeholder}
value={tags}
onValueChange={commit}
/>
{tags.length > 0 && (
<ul
aria-label="Выбранные теги"
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 'var(--jx-space-2)',
margin: 0,
padding: 0,
listStyle: 'none',
}}
>
{tags.map((tag) => (
<li key={tag}>
<JxButton
variant="ghost"
size="sm"
onClick={() => remove(tag)}
aria-label={`Удалить тег ${tag}`}
>
{tag} ✕
</JxButton>
</li>
))}
</ul>
)}
</div>
);
}