Shadcn/uiでDialog内でComboboxを使うとスクロールできない問題を解決する

Shadcn/uiは便利なのですが、Github Issuesを見ていただくとわかるように大量のIssueがOpenのまま存在します。
そのため、多くのバグや仕様は大抵の場合Issueがあり、誰かが地雷を踏んでいるか解決するかなどしてくれています。

今回はShadcn/uiで生成したDialogを使ったコンポーネント内で、公式ドキュメントを参考に作った[Combobox](https://ui.shadcn.com/docs/components/combobox)を使ったとき、かつスクロールが多く発生する程度にコンボボックス内の選択項目が多い場合に、スクロールが効かなくなる問題についてです。

関連するIssueは以下です。

https://github.com/shadcn-ui/ui/issues/4759

## 解決策

上記Issueで私がコメントしているとおり、 `Popover` コンポーネントに `modal={true}` を追加するだけで解決します。

```tsx
<Dialog>
  ...
  <Popover modal={true} onOpenChange={setOpen} open={open}>
    xxxx
  </Popover>
  ...
</Dialog>
```

で、この `modal` についてどこに書かれているかというと以下です。

https://www.radix-ui.com/primitives/docs/components/popover

こちらはShadcn/uiではなく、shadcn/uiが内部で使っている `@radix-ui/react-popover'` のドキュメントになります。分かりづらいですねぇ。

## まとめ

Shadcn/uiは様々なライブラリの組み合わせでUIを表現している都合上、今回のようなコンポーネントの組み合わせによる問題や仕様について、ドキュメントやIssueがいろんなライブラリのGithubや公式ドキュメントに散らばってしまうというデメリットがあります。

個人的にShadcn/uiを「流行ってるから選ぶ」というのは早計で、__「自力で調べ上げる検索力と解決力がチームにあるか」__ が重要かなと感じます。

## （余談）combobox改良版

ついでに私が使ってる `modal` に対応したcomboboxコンポーネントの実装コードを以下に置いておきます。
こちらのコードはChatGPTとともに生成したもので、modalとは別にユースケースとしてPlaceholderや幅を指定しないとUIが崩れてしまう問題があったため、これらもPropsに追加しています。

```tsx:combobox.tsx
import * as React from 'react';

import { Check, ChevronsUpDown } from 'lucide-react';

import { Button } from './button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from './command';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from './popover';

import { cn } from "@/lib/utils"

type Option = {
  label: string;
  value: string;
};

type ComboboxProps = {
  className?: string;  // Buttonの幅（クラス名指定用）
  modal?: boolean;  // モーダル表示の有無（デフォルトは false）
  onSelect: (value: string) => void;  // 選択時のコールバック関数
  options: Option[];  // 親コンポーネントから渡される key-value のデータ
  placeholder?: string;  // プレースホルダーのテキスト 
  width?: string; // 追加のカスタムクラス(幅の調整）
};

export function Combobox({
  options,
  placeholder = 'Please Select',
  onSelect,
  width = 'w-[200px]',
  className = '',
  modal = false,
}: ComboboxProps) {
  const [open, setOpen] = React.useState(false);
  const [value, setValue] = React.useState('');

  return (
    <Popover modal={modal} onOpenChange={setOpen} open={open}>
      <PopoverTrigger asChild>
        <Button
          aria-expanded={open}
          className={cn(width, 'justify-between', className)}
          role="combobox"
          variant="outline"
        >
          {value ? options.find((option) => option.value === value)?.label : placeholder}
          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className={cn(width, 'p-100')}>
        <Command>
          <CommandInput placeholder={`Search ${placeholder.toLowerCase()}`} />
          <CommandList>
            <CommandEmpty>No options found.</CommandEmpty>
            <CommandGroup>
              {options.map((option) => (
                <CommandItem
                  key={option.value}
                  onSelect={(currentValue) => {
                    setValue(currentValue === value ? '' : currentValue);
                    onSelect(currentValue);
                    setOpen(false);
                  }}
                  value={option.value}
                >
                  <Check
                    className={cn(
                      'mr-2 h-4 w-4',
                      value === option.value ? 'opacity-100' : 'opacity-0'
                    )}
                  />
                  {option.label}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
```

なお、コンポーネントのPropsの使い方についてはコード内にコメントで記述したりドキュメント書いたりという方法もありますが、Storybookを書いて開発チームで共有することを個人的にはおすすめしたいです。グラフィカルかつインタラクティブにPropsの値の違いがUIにどう影響するのかわかるためです。