Прокручиваемая панель фильтр-чипов в React
Ряд чипов под любой строкой поиска — темы YouTube, фильтры магазина, выбор тегов — это однострочный контейнер прокрутки, заполненный кнопками-переключателями. Сложные 10% — это то, что происходит по краям: новые чипы, появляющиеся вне экрана, перетаскивания, которые не должны ничего переключать, и стрелки, знающие, когда они бесполезны.
Добавьте фильтр — ряд сам прокрутит новый чип в область видимости.
Редактировать этот пример вживую в Storybook
Крайние случаи и есть та самая функциональность
Прокручивается любой flex-ряд с overflow-x: auto. Панель чипов оправдывает себя именно деталями:
- Чип, добавленный вне экрана, должен заявить о себе. Демо прокручивает к каждому новому чипу с помощью
apiRef.current.scrollToItem(el, 'smooth', 'end')после рендера — пример add-item-and-scroll-to-it реализует именно это. - Перетаскивание для прокрутки, клик для переключения — никогда одновременно. Пользователи десктопа перетаскивают ряд как тач-поверхность; отпускание над чипом не должно его переключать. Рецепт перетаскивания отслеживает жест и подавляет именно этот клик.
- Стрелки только тогда, когда они полезны.
useLeftArrowVisible/useRightArrowVisibleподключены к тому же IntersectionObserver, что и всё остальное, поэтому стрелки отключаются на настоящих краях — в том числе после добавления или удаления чипов.
Состояние остаётся в ваших руках
Библиотека прокручивает; она не владеет выбором. Чипы — это ваши кнопки: aria-pressed для переключателей с множественным выбором, обычное состояние для одиночного выбора — меню нужно лишь, чтобы у каждого был itemId. Это значит, что состояние чипов сочетается с тем, что у вас уже есть: параметрами URL, библиотекой форм, серверной моделью фильтров. Удаление чипа — это удаление элемента; анимированное исчезновение — пример items-animation.
Мобильные устройства: одно предупреждение о прокрутке страницы
На сенсорных экранах горизонтальный свайп внутри панели в некоторых браузерах может утянуть за собой страницу вбок. Если вы столкнулись с этим, пример prevent-body-scroll показывает touch-action и сдерживание overscroll, чтобы это заблокировать — только CSS, без библиотеки жестов.
Паттерн в минимальном виде
Чипы — это кнопки-переключатели с itemId; ref на API меню прокручивает вновь добавленный чип в область видимости.
function ChipBar({ options }: { options: string[] }) {
const apiRef = React.useRef<publicApiType>(null);
const [selected, setSelected] = React.useState<string[]>([]);
const toggle = (id: string) =>
setSelected((cur) =>
cur.includes(id) ? cur.filter((c) => c !== id) : [...cur, id],
);
// A chip appended off-screen scrolls itself into view.
const addChip = (id: string) => {
toggle(id);
requestAnimationFrame(() => {
const el = apiRef.current?.getItemElementById(id);
if (el) apiRef.current?.scrollToItem(el, 'smooth', 'end');
});
};
return (
<ScrollMenu apiRef={apiRef}>
{options.map((id) => (
<Chip itemId={id} key={id} pressed={selected.includes(id)}
onToggle={() => toggle(id)} />
))}
</ScrollMenu>
);
}Или установите как компонент shadcn
Элемент реестра chip-bar поставляет это в виде управляемого компонента — options, selected, onSelectedChange — стилизованного через Tailwind в вашей components/ui/:
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/chip-bar.json