diff --git a/DASHBOARD_MIGRATION_GUIDE.md b/DASHBOARD_MIGRATION_GUIDE.md new file mode 100644 index 000000000..76977c60e --- /dev/null +++ b/DASHBOARD_MIGRATION_GUIDE.md @@ -0,0 +1,451 @@ +# Custom Themed Dashboard Migration Guide + +This guide will help you migrate the custom themed dashboard example to a new Next.js application. + +## Quick Instructions for Cursor Agent + +1. Copy and paste the entire contents of this file into the Cursor agent in your new Next.js repo +2. Also provide the `FILES_TO_COPY.md` file which lists all the files you need to copy +3. The agent will help you set up the dashboard with your custom theme + +## Prerequisites + +- A fresh Next.js 15+ application with TypeScript +- Tailwind CSS configured +- The app router enabled (using `/app` directory) + +## Dependencies to Install + +Run the following command in your new Next.js app: + +```bash +npm install @radix-ui/react-accordion @radix-ui/react-alert-dialog @radix-ui/react-aspect-ratio @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-collapsible @radix-ui/react-context-menu @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card @radix-ui/react-label @radix-ui/react-menubar @radix-ui/react-navigation-menu @radix-ui/react-popover @radix-ui/react-progress @radix-ui/react-radio-group @radix-ui/react-scroll-area @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slider @radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-toast @radix-ui/react-toggle @radix-ui/react-toggle-group @radix-ui/react-tooltip class-variance-authority clsx cmdk date-fns embla-carousel-react input-otp lucide-react next-themes react-day-picker react-hook-form react-resizable-panels recharts sonner tailwind-merge tailwindcss-animate vaul zod @tanstack/react-table @tanstack/react-virtual +``` + +Or if using pnpm: +```bash +pnpm add @radix-ui/react-accordion @radix-ui/react-alert-dialog @radix-ui/react-aspect-ratio @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-collapsible @radix-ui/react-context-menu @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card @radix-ui/react-label @radix-ui/react-menubar @radix-ui/react-navigation-menu @radix-ui/react-popover @radix-ui/react-progress @radix-ui/react-radio-group @radix-ui/react-scroll-area @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slider @radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-toast @radix-ui/react-toggle @radix-ui/react-toggle-group @radix-ui/react-tooltip class-variance-authority clsx cmdk date-fns embla-carousel-react input-otp lucide-react next-themes react-day-picker react-hook-form react-resizable-panels recharts sonner tailwind-merge tailwindcss-animate vaul zod @tanstack/react-table @tanstack/react-virtual +``` + +## Files to Copy from TweakCN Repository + +### 1. Core UI Components +Copy the entire `components/ui/` directory to your new app's `components/ui/` directory. These are all the shadcn/ui components needed. + +### 2. Dashboard Example Components +Copy these directories and files: +- `components/examples/dashboard/` → `components/examples/dashboard/` +- `components/examples/dashboard/data.json` → `components/examples/dashboard/data.json` + +### 3. Theme Provider +Create `components/theme-provider.tsx`: +```tsx +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { type ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} + +export const useTheme = () => { + const [mounted, setMounted] = React.useState(false) + const [theme, setTheme] = React.useState<"light" | "dark">("light") + + React.useEffect(() => { + setMounted(true) + const currentTheme = document.documentElement.classList.contains("dark") ? "dark" : "light" + setTheme(currentTheme) + }, []) + + const toggleTheme = (coords?: { x: number; y: number }) => { + const newTheme = theme === "light" ? "dark" : "light" + + if (!coords) { + document.documentElement.classList.toggle("dark") + setTheme(newTheme) + return + } + + const x = coords.x + const y = coords.y + const endRadius = Math.hypot( + Math.max(x, window.innerWidth - x), + Math.max(y, window.innerHeight - y) + ) + + // @ts-ignore + if (!document.startViewTransition) { + document.documentElement.classList.toggle("dark") + setTheme(newTheme) + return + } + + // @ts-ignore + const transition = document.startViewTransition(() => { + document.documentElement.classList.toggle("dark") + setTheme(newTheme) + }) + + transition.ready.then(() => { + const clipPath = [ + `circle(0px at ${x}px ${y}px)`, + `circle(${endRadius}px at ${x}px ${y}px)`, + ] + + document.documentElement.animate( + { + clipPath: document.documentElement.classList.contains("dark") + ? clipPath.reverse() + : clipPath, + }, + { + duration: 400, + easing: "ease-out", + pseudoElement: "::view-transition-new(root)", + } + ) + }) + } + + if (!mounted) return { theme: "light", toggleTheme } + + return { theme, toggleTheme } +} +``` + +### 4. Mobile Hook +Create `hooks/use-mobile.tsx`: +```tsx +import * as React from "react" + +const MOBILE_BREAKPOINT = 768 + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState(undefined) + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + } + mql.addEventListener("change", onChange) + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + return () => mql.removeEventListener("change", onChange) + }, []) + + return !!isMobile +} +``` + +### 5. Utility Functions +Create `lib/utils.ts`: +```ts +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +### 6. Dashboard Page Files +Copy these files exactly: +- `app/examples/dashboard/page.tsx` +- `app/examples/dashboard/layout.tsx` +- `app/examples/dashboard/custom-theme-applier.tsx` + +## Setup Instructions + +### 1. Update your `tailwind.config.ts`: +```ts +import type { Config } from "tailwindcss" + +const config: Config = { + darkMode: ["class"], + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "var(--color-border)", + input: "var(--color-input)", + ring: "var(--color-ring)", + background: "var(--color-background)", + foreground: "var(--color-foreground)", + primary: { + DEFAULT: "var(--color-primary)", + foreground: "var(--color-primary-foreground)", + }, + secondary: { + DEFAULT: "var(--color-secondary)", + foreground: "var(--color-secondary-foreground)", + }, + destructive: { + DEFAULT: "var(--color-destructive)", + foreground: "var(--color-destructive-foreground)", + }, + muted: { + DEFAULT: "var(--color-muted)", + foreground: "var(--color-muted-foreground)", + }, + accent: { + DEFAULT: "var(--color-accent)", + foreground: "var(--color-accent-foreground)", + }, + popover: { + DEFAULT: "var(--color-popover)", + foreground: "var(--color-popover-foreground)", + }, + card: { + DEFAULT: "var(--color-card)", + foreground: "var(--color-card-foreground)", + }, + sidebar: { + DEFAULT: "var(--color-sidebar)", + foreground: "var(--color-sidebar-foreground)", + primary: "var(--color-sidebar-primary)", + "primary-foreground": "var(--color-sidebar-primary-foreground)", + accent: "var(--color-sidebar-accent)", + "accent-foreground": "var(--color-sidebar-accent-foreground)", + border: "var(--color-sidebar-border)", + ring: "var(--color-sidebar-ring)", + }, + chart: { + "1": "var(--color-chart-1)", + "2": "var(--color-chart-2)", + "3": "var(--color-chart-3)", + "4": "var(--color-chart-4)", + "5": "var(--color-chart-5)", + }, + }, + borderRadius: { + lg: "var(--radius-lg)", + md: "var(--radius-md)", + sm: "var(--radius-sm)", + xl: "var(--radius-xl)", + }, + fontFamily: { + sans: ["var(--font-sans)", "sans-serif"], + serif: ["var(--font-serif)", "serif"], + mono: ["var(--font-mono)", "monospace"], + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} +export default config +``` + +### 2. Update your `app/globals.css`: +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); + + --tracking-tighter: calc(var(--letter-spacing) - 0.05em); + --tracking-tight: calc(var(--letter-spacing) - 0.025em); + --tracking-normal: var(--letter-spacing); + --tracking-wide: calc(var(--letter-spacing) + 0.025em); + --tracking-wider: calc(var(--letter-spacing) + 0.05em); + --tracking-widest: calc(var(--letter-spacing) + 0.1em); + } +} + +* { + border-color: var(--color-border); +} + +body { + background-color: var(--color-background); + color: var(--color-foreground); + -webkit-font-smoothing: antialiased; + letter-spacing: var(--letter-spacing); +} + +/* View Transition Wave Effect */ +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} + +::view-transition-old(root) { + z-index: 0; +} + +::view-transition-new(root) { + z-index: 1; +} + +@keyframes reveal { + from { + clip-path: circle(0% at var(--x, 50%) var(--y, 50%)); + opacity: 0.7; + } + to { + clip-path: circle(150% at var(--x, 50%) var(--y, 50%)); + opacity: 1; + } +} + +::view-transition-new(root) { + animation: reveal 0.4s ease-in-out forwards; +} +``` + +### 3. Update your `app/layout.tsx`: +```tsx +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" +import { ThemeProvider } from "@/components/theme-provider" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Custom Dashboard", + description: "Custom themed dashboard example", +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + ) +} +``` + +## Running the Dashboard + +After completing all the steps above: + +1. Start your development server: + ```bash + npm run dev + # or + pnpm dev + ``` + +2. Navigate to `http://localhost:3000/examples/dashboard` + +3. You should see the dashboard with your custom theme applied! + +## Troubleshooting + +### If the theme isn't applying: +1. Make sure the `CustomThemeApplier` component is being rendered +2. Check the browser console for any errors +3. Verify all CSS variables are being set on the document root element + +### If components look broken: +1. Ensure all UI components were copied correctly +2. Check that all dependencies are installed +3. Verify your Tailwind config includes all the necessary theme extensions + +### If the sidebar isn't working: +1. Make sure the `"use client"` directive is at the top of `components/ui/sidebar.tsx` +2. Verify the `use-mobile` hook is properly imported + +## Custom Theme Colors Reference + +Your custom theme uses these beautiful OKLCH colors: + +**Light Mode Primary**: `oklch(0.8148 0.0819 225.7537)` - Blue-purple +**Dark Mode Primary**: `oklch(0.6531 0.1347 242.6867)` - Vibrant blue-purple +**Accent Colors**: Purple shades that complement the primary colors + +The theme includes proper contrast ratios and a cohesive color palette across all components. \ No newline at end of file diff --git a/FILES_TO_COPY.md b/FILES_TO_COPY.md new file mode 100644 index 000000000..0260a9043 --- /dev/null +++ b/FILES_TO_COPY.md @@ -0,0 +1,198 @@ +# Files to Copy from TweakCN Repository + +## Directory Structure to Create in Your New App + +``` +your-nextjs-app/ +├── app/ +│ ├── examples/ +│ │ └── dashboard/ +│ │ ├── custom-theme-applier.tsx +│ │ ├── layout.tsx +│ │ └── page.tsx +│ ├── globals.css (update existing) +│ └── layout.tsx (update existing) +├── components/ +│ ├── examples/ +│ │ └── dashboard/ +│ │ ├── components/ +│ │ │ ├── app-sidebar.tsx +│ │ │ ├── chart-area-interactive.tsx +│ │ │ ├── chart-bar-mixed.tsx +│ │ │ ├── chart-pie-donut.tsx +│ │ │ ├── data-table.tsx +│ │ │ ├── nav-documents.tsx +│ │ │ ├── nav-main.tsx +│ │ │ ├── nav-secondary.tsx +│ │ │ ├── nav-user.tsx +│ │ │ ├── section-cards.tsx +│ │ │ └── site-header.tsx +│ │ ├── data.json +│ │ └── index.tsx +│ ├── ui/ +│ │ ├── accordion.tsx +│ │ ├── alert-dialog.tsx +│ │ ├── alert.tsx +│ │ ├── aspect-ratio.tsx +│ │ ├── avatar.tsx +│ │ ├── badge.tsx +│ │ ├── breadcrumb.tsx +│ │ ├── button.tsx +│ │ ├── calendar.tsx +│ │ ├── card.tsx +│ │ ├── carousel.tsx +│ │ ├── chart.tsx +│ │ ├── checkbox.tsx +│ │ ├── collapsible.tsx +│ │ ├── command.tsx +│ │ ├── context-menu.tsx +│ │ ├── dialog.tsx +│ │ ├── drawer.tsx +│ │ ├── dropdown-menu.tsx +│ │ ├── form.tsx +│ │ ├── hover-card.tsx +│ │ ├── input-otp.tsx +│ │ ├── input.tsx +│ │ ├── label.tsx +│ │ ├── menubar.tsx +│ │ ├── navigation-menu.tsx +│ │ ├── pagination.tsx +│ │ ├── popover.tsx +│ │ ├── progress.tsx +│ │ ├── radio-group.tsx +│ │ ├── resizable.tsx +│ │ ├── scroll-area.tsx +│ │ ├── select.tsx +│ │ ├── separator.tsx +│ │ ├── sheet.tsx +│ │ ├── sidebar.tsx +│ │ ├── skeleton.tsx +│ │ ├── slider.tsx +│ │ ├── sonner.tsx +│ │ ├── switch.tsx +│ │ ├── table.tsx +│ │ ├── tabs.tsx +│ │ ├── textarea.tsx +│ │ ├── toast.tsx +│ │ ├── toaster.tsx +│ │ ├── toggle-group.tsx +│ │ ├── toggle.tsx +│ │ ├── tooltip.tsx +│ │ └── use-toast.ts +│ └── theme-provider.tsx (create new - see guide) +├── hooks/ +│ └── use-mobile.tsx (create new - see guide) +├── lib/ +│ └── utils.ts (create new - see guide) +└── tailwind.config.ts (update existing) +``` + +## Exact Files to Copy from TweakCN + +### 1. Dashboard Page Files +``` +app/examples/dashboard/custom-theme-applier.tsx +app/examples/dashboard/layout.tsx +app/examples/dashboard/page.tsx +``` + +### 2. Dashboard Example Components +``` +components/examples/dashboard/index.tsx +components/examples/dashboard/data.json +components/examples/dashboard/components/app-sidebar.tsx +components/examples/dashboard/components/chart-area-interactive.tsx +components/examples/dashboard/components/chart-bar-mixed.tsx +components/examples/dashboard/components/chart-pie-donut.tsx +components/examples/dashboard/components/data-table.tsx +components/examples/dashboard/components/nav-documents.tsx +components/examples/dashboard/components/nav-main.tsx +components/examples/dashboard/components/nav-secondary.tsx +components/examples/dashboard/components/nav-user.tsx +components/examples/dashboard/components/section-cards.tsx +components/examples/dashboard/components/site-header.tsx +``` + +### 3. UI Components (All files in components/ui/) +``` +components/ui/accordion.tsx +components/ui/alert-dialog.tsx +components/ui/alert.tsx +components/ui/aspect-ratio.tsx +components/ui/avatar.tsx +components/ui/badge.tsx +components/ui/breadcrumb.tsx +components/ui/button.tsx +components/ui/calendar.tsx +components/ui/card.tsx +components/ui/carousel.tsx +components/ui/chart.tsx +components/ui/checkbox.tsx +components/ui/collapsible.tsx +components/ui/command.tsx +components/ui/context-menu.tsx +components/ui/dialog.tsx +components/ui/drawer.tsx +components/ui/dropdown-menu.tsx +components/ui/form.tsx +components/ui/hover-card.tsx +components/ui/input-otp.tsx +components/ui/input.tsx +components/ui/label.tsx +components/ui/menubar.tsx +components/ui/navigation-menu.tsx +components/ui/pagination.tsx +components/ui/popover.tsx +components/ui/progress.tsx +components/ui/radio-group.tsx +components/ui/resizable.tsx +components/ui/scroll-area.tsx +components/ui/select.tsx +components/ui/separator.tsx +components/ui/sheet.tsx +components/ui/sidebar.tsx +components/ui/skeleton.tsx +components/ui/slider.tsx +components/ui/sonner.tsx +components/ui/switch.tsx +components/ui/table.tsx +components/ui/tabs.tsx +components/ui/textarea.tsx +components/ui/toast.tsx +components/ui/toaster.tsx +components/ui/toggle-group.tsx +components/ui/toggle.tsx +components/ui/tooltip.tsx +components/ui/use-toast.ts +``` + +## Copy Commands (from TweakCN root) + +If you're using the terminal to copy files, here are the commands: + +```bash +# Create directories first +mkdir -p ../your-new-app/app/examples/dashboard +mkdir -p ../your-new-app/components/examples/dashboard/components +mkdir -p ../your-new-app/components/ui +mkdir -p ../your-new-app/hooks +mkdir -p ../your-new-app/lib + +# Copy dashboard page files +cp app/examples/dashboard/custom-theme-applier.tsx ../your-new-app/app/examples/dashboard/ +cp app/examples/dashboard/layout.tsx ../your-new-app/app/examples/dashboard/ +cp app/examples/dashboard/page.tsx ../your-new-app/app/examples/dashboard/ + +# Copy dashboard example components +cp -r components/examples/dashboard/* ../your-new-app/components/examples/dashboard/ + +# Copy all UI components +cp -r components/ui/* ../your-new-app/components/ui/ +``` + +## Important Notes + +1. **Don't forget to update `components/ui/sidebar.tsx`** - Make sure it has `"use client"` at the top +2. **Create the files mentioned in the guide** - `theme-provider.tsx`, `use-mobile.tsx`, and `utils.ts` need to be created manually as shown in the guide +3. **Update configuration files** - `tailwind.config.ts`, `app/globals.css`, and `app/layout.tsx` need to be updated as shown in the guide +4. **Install all dependencies** - Use the npm/pnpm install command from the guide \ No newline at end of file diff --git a/app/examples/dashboard/custom-theme-applier.tsx b/app/examples/dashboard/custom-theme-applier.tsx new file mode 100644 index 000000000..992f62cf0 --- /dev/null +++ b/app/examples/dashboard/custom-theme-applier.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { useEffect } from "react"; + +const customThemeVariables = { + light: { + background: "oklch(1.0000 0 0)", + foreground: "oklch(0 0 0)", + card: "oklch(0.9761 0 0)", + "card-foreground": "oklch(0 0 0)", + popover: "oklch(1.0000 0 0)", + "popover-foreground": "oklch(0 0 0)", + primary: "oklch(0.8148 0.0819 225.7537)", + "primary-foreground": "oklch(1.0000 0 0)", + secondary: "oklch(0.8562 0.0489 219.6543)", + "secondary-foreground": "oklch(0 0 0)", + muted: "oklch(0.7572 0 0)", + "muted-foreground": "oklch(0 0 0)", + accent: "oklch(0.7403 0.1357 244.2552)", + "accent-foreground": "oklch(1.0000 0 0)", + destructive: "oklch(0.6542 0.2321 28.6592)", + "destructive-foreground": "oklch(1.0000 0 0)", + border: "oklch(0.8975 0 0)", + input: "oklch(0.9761 0 0)", + ring: "oklch(0.8148 0.0819 225.7537)", + "chart-1": "oklch(0.8148 0.0819 225.7537)", + "chart-2": "oklch(0.8562 0.0489 219.6543)", + "chart-3": "oklch(0.7403 0.1357 244.2552)", + "chart-4": "oklch(0.8442 0.1722 84.9338)", + "chart-5": "oklch(0.6542 0.2321 28.6592)", + sidebar: "oklch(0.9761 0 0)", + "sidebar-foreground": "oklch(0 0 0)", + "sidebar-primary": "oklch(0.8148 0.0819 225.7537)", + "sidebar-primary-foreground": "oklch(1.0000 0 0)", + "sidebar-accent": "oklch(0.7403 0.1357 244.2552)", + "sidebar-accent-foreground": "oklch(1.0000 0 0)", + "sidebar-border": "oklch(0.8975 0 0)", + "sidebar-ring": "oklch(0.8148 0.0819 225.7537)", + "font-sans": "Inter", + "font-serif": "Georgia", + "font-mono": "Monaco", + radius: "8px", + "shadow-2xs": "0px 2px 4px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 2px 4px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 2px 4px 0px hsl(0 0% 0% / 0.25)", + "tracking-normal": "0px", + "letter-spacing": "0px", + }, + dark: { + background: "oklch(0.2178 0 0)", + foreground: "oklch(1.0000 0 0)", + card: "oklch(0.3052 0 0)", + "card-foreground": "oklch(1.0000 0 0)", + popover: "oklch(0.2178 0 0)", + "popover-foreground": "oklch(1.0000 0 0)", + primary: "oklch(0.6531 0.1347 242.6867)", + "primary-foreground": "oklch(1.0000 0 0)", + secondary: "oklch(0.3645 0.0403 250.3174)", + "secondary-foreground": "oklch(1.0000 0 0)", + muted: "oklch(0.4855 0 0)", + "muted-foreground": "oklch(1.0000 0 0)", + accent: "oklch(0.4271 0.0887 258.2946)", + "accent-foreground": "oklch(1.0000 0 0)", + destructive: "oklch(0.6542 0.2321 28.6592)", + "destructive-foreground": "oklch(1.0000 0 0)", + border: "oklch(0.3867 0 0)", + input: "oklch(0.3052 0 0)", + ring: "oklch(0.6531 0.1347 242.6867)", + "chart-1": "oklch(0.6531 0.1347 242.6867)", + "chart-2": "oklch(0.3645 0.0403 250.3174)", + "chart-3": "oklch(0.4271 0.0887 258.2946)", + "chart-4": "oklch(0.8442 0.1722 84.9338)", + "chart-5": "oklch(0.6542 0.2321 28.6592)", + sidebar: "oklch(0.3052 0 0)", + "sidebar-foreground": "oklch(1.0000 0 0)", + "sidebar-primary": "oklch(0.6531 0.1347 242.6867)", + "sidebar-primary-foreground": "oklch(1.0000 0 0)", + "sidebar-accent": "oklch(0.4271 0.0887 258.2946)", + "sidebar-accent-foreground": "oklch(1.0000 0 0)", + "sidebar-border": "oklch(0.3867 0 0)", + "sidebar-ring": "oklch(0.6531 0.1347 242.6867)", + "font-sans": "Inter", + "font-serif": "Georgia", + "font-mono": "Monaco", + radius: "8px", + "shadow-2xs": "0px 2px 4px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 2px 4px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 2px 4px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 2px 4px 0px hsl(0 0% 0% / 0.25)", + "letter-spacing": "0px", + }, +}; + +export function CustomThemeApplier() { + useEffect(() => { + const applyTheme = () => { + const root = document.documentElement; + const isDark = root.classList.contains("dark"); + const themeVars = isDark ? customThemeVariables.dark : customThemeVariables.light; + + // Apply all theme variables + Object.entries(themeVars).forEach(([key, value]) => { + root.style.setProperty(`--${key}`, value); + }); + }; + + // Apply theme immediately + applyTheme(); + + // Watch for theme changes + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === "attributes" && mutation.attributeName === "class") { + applyTheme(); + } + }); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], + }); + + return () => observer.disconnect(); + }, []); + + return null; +} diff --git a/app/examples/dashboard/layout.tsx b/app/examples/dashboard/layout.tsx new file mode 100644 index 000000000..7e8432c71 --- /dev/null +++ b/app/examples/dashboard/layout.tsx @@ -0,0 +1,3 @@ +export default function DashboardExampleLayout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/app/examples/dashboard/page.tsx b/app/examples/dashboard/page.tsx new file mode 100644 index 000000000..57ddf62f5 --- /dev/null +++ b/app/examples/dashboard/page.tsx @@ -0,0 +1,36 @@ +"use client"; + +import DashboardExample from "@/components/examples/dashboard"; +import { useTheme } from "@/components/theme-provider"; +import { Button } from "@/components/ui/button"; +import { Moon, Sun } from "lucide-react"; +import { CustomThemeApplier } from "./custom-theme-applier"; + +export default function DashboardExamplePage() { + const { theme, toggleTheme } = useTheme(); + + const handleThemeToggle = (event: React.MouseEvent) => { + const { clientX: x, clientY: y } = event; + toggleTheme({ x, y }); + }; + + return ( + + {/* Apply custom theme */} + + + {/* Theme toggle button */} + + {theme === "dark" ? : } + Toggle theme + + + + + ); +}