From 45ceae5c6e3a81c39eb50a87606e7af4a01ea4fb Mon Sep 17 00:00:00 2001 From: gunshiz Date: Mon, 29 Jun 2026 18:54:38 +0700 Subject: [PATCH] feat: add theme toggle component with dropdown menu support to the layout --- app/form/layout.tsx | 27 ++-- app/layout.tsx | 11 +- components/theme-provider.tsx | 7 + components/theme-toggle.tsx | 55 +++++++ components/ui/dropdown-menu.tsx | 269 ++++++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+), 11 deletions(-) create mode 100644 components/theme-provider.tsx create mode 100644 components/theme-toggle.tsx create mode 100644 components/ui/dropdown-menu.tsx diff --git a/app/form/layout.tsx b/app/form/layout.tsx index 30a7191..78f73c0 100644 --- a/app/form/layout.tsx +++ b/app/form/layout.tsx @@ -3,6 +3,8 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { FormLayoutClient } from "./client"; import LoginDialog from "@/components/login-dialog"; import { Metadata } from "next"; +import { ThemeToggle } from "@/components/theme-toggle"; +import Link from 'next/link'; const siteUrl = process.env.BASE_URL; @@ -38,17 +40,22 @@ export default async function FormLayout({ const session = await getServerSession(authOptions); return ( -
+
- - ฟอร์ม - - {session?.user && ( - - )} + + + Form + + +
+ {session?.user && ( + + )} + +
{children} diff --git a/app/layout.tsx b/app/layout.tsx index 9d1d1e5..90539ed 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -71,6 +71,7 @@ export const metadata: Metadata = { import { TooltipProvider } from "@/components/ui/tooltip"; import { Toaster } from "sonner"; +import { ThemeProvider } from "@/components/theme-provider"; export default function RootLayout({ children, @@ -80,13 +81,21 @@ export default function RootLayout({ return ( + {children} + ); diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..34a9890 --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,7 @@ +"use client" + +import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx new file mode 100644 index 0000000..93560c7 --- /dev/null +++ b/components/theme-toggle.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import { Moon, Sun, Monitor } 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 ThemeToggle() { + const { setTheme, theme, resolvedTheme } = useTheme() + const [mounted, setMounted] = React.useState(false) + + React.useEffect(() => { + setMounted(true) + }, []) + + return ( + + + + + + setTheme("light")}> + + Light theme + + setTheme("dark")}> + + Dark theme + + setTheme("system")}> + + System + + + + ) +} diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..172cc15 --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,269 @@ +"use client" + +import * as React from "react" +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { CheckIcon, ChevronRightIcon } from "lucide-react" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + align = "start", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}