feat: add theme provider and toggle components with shadcn

This commit is contained in:
ngc2207 2024-11-21 00:44:58 +08:00
parent 1caca0f582
commit ff57a87de5
5 changed files with 123 additions and 2 deletions

View File

@ -9,10 +9,12 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "15.0.3", "next": "15.0.3",
"next-themes": "^0.4.3",
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",

View File

@ -2,6 +2,7 @@ import "@/app/globals.css";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { ThemeProvider } from "@/components/theme-provider";
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
@ -16,9 +17,16 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en" suppressHydrationWarning>
<body className={cn("font-sans antialiased", inter.variable)}> <body className={cn("font-sans antialiased", inter.variable)}>
{children} <ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body> </body>
</html> </html>
); );

View File

@ -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 <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

View File

@ -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<Theme, Theme> = {
light: "dark",
dark: "system",
system: "light",
};
const icons: Record<Theme, JSX.Element> = {
light: <Sun className="h-5 w-5" />,
dark: <Moon className="h-5 w-5" />,
system: <MonitorCog className="h-5 w-5" />,
};
export function ThemeToggle() {
const { theme = "system", setTheme } = useTheme();
const toggleTheme = () => setTheme(nextTheme[theme as Theme]);
return (
<Button
variant="ghost"
size="icon"
className="flex h-6 w-6 items-center justify-center rounded-md transition"
onClick={toggleTheme}
>
{icons[theme as Theme]}
<span className="sr-only">Toggle theme</span>
</Button>
);
}

View File

@ -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<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }