From ff57a87de53061b099b26e260ae121c4a30532bd Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Thu, 21 Nov 2024 00:44:58 +0800 Subject: [PATCH] feat: add theme provider and toggle components with shadcn --- package.json | 2 ++ src/app/layout.tsx | 12 +++++-- src/components/theme-provider.tsx | 16 +++++++++ src/components/theme-toggle.tsx | 38 +++++++++++++++++++++ src/components/ui/button.tsx | 57 +++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/components/theme-provider.tsx create mode 100644 src/components/theme-toggle.tsx create mode 100644 src/components/ui/button.tsx diff --git a/package.json b/package.json index a06a98f..e6d6f7e 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.460.0", "next": "15.0.3", + "next-themes": "^0.4.3", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106", "tailwind-merge": "^2.5.4", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 931e856..b75788d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import "@/app/globals.css"; import { cn } from "@/lib/utils"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; +import { ThemeProvider } from "@/components/theme-provider"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); @@ -16,9 +17,16 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + + {children} + ); diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..890223e --- /dev/null +++ b/src/components/theme-provider.tsx @@ -0,0 +1,16 @@ +"use client"; + +import * as React from "react"; +import dynamic from "next/dynamic"; +import { ThemeProviderProps } from "next-themes"; + +const NextThemesProvider = dynamic( + () => import("next-themes").then((e) => e.ThemeProvider), + { + ssr: false, + } +); + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children}; +} diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx new file mode 100644 index 0000000..f23264e --- /dev/null +++ b/src/components/theme-toggle.tsx @@ -0,0 +1,38 @@ +"use client"; + +import React from "react"; +import { useTheme } from "next-themes"; +import { Button } from "@/components/ui/button"; +import { MonitorCog, Moon, Sun } from "lucide-react"; + +type Theme = "light" | "dark" | "system"; + +const nextTheme: Record = { + light: "dark", + dark: "system", + system: "light", +}; + +const icons: Record = { + light: , + dark: , + system: , +}; + +export function ThemeToggle() { + const { theme = "system", setTheme } = useTheme(); + + const toggleTheme = () => setTheme(nextTheme[theme as Theme]); + + return ( + + ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..65d4fcd --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants }