Прокручиваемая панель фильтр-чипов в React

Ряд чипов под любой строкой поиска — темы YouTube, фильтры магазина, выбор тегов — это однострочный контейнер прокрутки, заполненный кнопками-переключателями. Сложные 10% — это то, что происходит по краям: новые чипы, появляющиеся вне экрана, перетаскивания, которые не должны ничего переключать, и стрелки, знающие, когда они бесполезны.

Добавьте фильтр — ряд сам прокрутит новый чип в область видимости.

Крайние случаи и есть та самая функциональность

Прокручивается любой flex-ряд с overflow-x: auto. Панель чипов оправдывает себя именно деталями:

Состояние остаётся в ваших руках

Библиотека прокручивает; она не владеет выбором. Чипы — это ваши кнопки: aria-pressed для переключателей с множественным выбором, обычное состояние для одиночного выбора — меню нужно лишь, чтобы у каждого был itemId. Это значит, что состояние чипов сочетается с тем, что у вас уже есть: параметрами URL, библиотекой форм, серверной моделью фильтров. Удаление чипа — это удаление элемента; анимированное исчезновение — пример items-animation.

Мобильные устройства: одно предупреждение о прокрутке страницы

На сенсорных экранах горизонтальный свайп внутри панели в некоторых браузерах может утянуть за собой страницу вбок. Если вы столкнулись с этим, пример prevent-body-scroll показывает touch-action и сдерживание overscroll, чтобы это заблокировать — только CSS, без библиотеки жестов.

Паттерн в минимальном виде

Чипы — это кнопки-переключатели с itemId; ref на API меню прокручивает вновь добавленный чип в область видимости.

ChipBar.tsx
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/:

shadcn
npx shadcn@latest add https://react-horizontal-scrolling-menu.dev/r/chip-bar.json

Похожие примеры

Все примеры (21)