Open3

Next.jsでshadcnのdropdown-menuのTriggerにasChildをつけるとメニューが表示されない

miyabitti@ニートmiyabitti@ニート

Next.jsにダークモードを実装するとき、切り替えボタンを公式ドキュメントからコピペしたところ、動作しなかった。

"use client"

import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function ModeToggle() {
  const { setTheme } = useTheme()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild> {/* こいつ */}
        <Button variant="outline" size="icon">
          <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>
          Light
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>
          Dark
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>
          System
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

TriggerからasChildを外すと動作するが、その場合Triggerがbuttonとしてレンダリングされるため、buttonの中にbuttonという構造になり、ハイドレーションエラーが発生する。

ちなみに、Next v14.2を使用している

miyabitti@ニートmiyabitti@ニート

DropdownMenuを使用している親コンポーネントがCCの場合に動作しないことがわかった。
試しにCCでしか使えない部分を削除すると正常に表示された。

import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function ModeToggle() {
  // const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="icon" className="rounded-full">
          <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
          <span className="sr-only">テーマ切り替え</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem>ライト</DropdownMenuItem>
        <DropdownMenuItem>ダーク</DropdownMenuItem>
        <DropdownMenuItem>システム</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

miyabitti@ニートmiyabitti@ニート

簡単な解決策として、DropdownMenuTrigger自体にshadcnのButtonのスタイリングを割り当てれば解決する。

"use client";

import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { buttonVariants } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function ModeToggle() {
  const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger className={buttonVariants({ variant: "outline", size: "icon" })} style={{ borderRadius: "100%" }}>
        <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
        <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
        <span className="sr-only">テーマ切り替え</span>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>ライト</DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>ダーク</DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>システム</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}