React で作る、スクロールするフィルターチップバー

検索バーの下にあるチップ行 — YouTube のトピック、ストアのフィルター、タグピッカー — は、トグルボタンで埋め尽くされた 1 行のスクロールコンテナです。難しいのは残り 10% にあたる端の挙動です:画面外に現れる新しいチップ、何もトグルしてはいけないドラッグ、そして無意味なときを知っている矢印。

フィルターを追加してみてください — 行が新しいチップを自動で見える位置までスクロールします。

エッジケースこそが機能

overflow-x: auto を持つフレックス行なら何でもスクロールします。チップバーが真価を発揮するのは細部です:

状態はあなたの手の中に

ライブラリはスクロールを担当し、選択状態は所有しません。チップはあなたのボタンです — 複数選択トグルには aria-pressed、単一選択には普通の state — メニュー側はそれぞれが itemId を持つことしか必要としません。つまりチップの状態は、URL の検索パラメータ、フォームライブラリ、サーバー駆動のフィルターモデルなど、すでにあるものと自然に組み合わさります。チップの削除は アイテムの削除、アニメーションで消すのは items-animation の例 です。

モバイル:ボディスクロールに関する 1 つの注意

タッチスクリーンでは、バー内での横方向のスワイプが、一部のブラウザではページごと横に引きずってしまうことがあります。それを見かけたら、prevent-body-scroll の例touch-action とオーバースクロールの封じ込めでそれを止める方法を示しています — CSS のみで、ジェスチャーライブラリ不要です。

パターン、最小構成

チップは itemId を持つトグルボタンで、メニュー API への ref が新しく追加されたチップを見える位置までスクロールします。

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 レジストリアイテムは、これを制御コンポーネントとして提供します — optionsselectedonSelectedChange — あなたの components/ui/ に置く Tailwind スタイルとして:

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

関連する例

すべての例(21 件)