diff --git a/package.json b/package.json
index 51a6e0a3..e0b0745e 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"lucide-react": "^0.483.0",
"motion": "^12.5.0",
"next-themes": "^0.3.0",
+ "nuqs": "^2.4.1",
"posthog-js": "^1.232.4",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 413f93e2..4c9d121d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -149,6 +149,9 @@ importers:
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ nuqs:
+ specifier: ^2.4.1
+ version: 2.4.1(react-router@7.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
posthog-js:
specifier: ^1.232.4
version: 1.234.9
@@ -2731,6 +2734,9 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
+ mitt@3.0.1:
+ resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+
motion-dom@12.6.3:
resolution: {integrity: sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==}
@@ -2801,6 +2807,24 @@ packages:
resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ nuqs@2.4.1:
+ resolution: {integrity: sha512-u6sngTspqDe3jWHtcmqHQg3dl35niizCZAsm5gy7PBlgG2rwl71Dp2QUv5hwBaWKI9qz0wqILZY86TsRxq66SQ==}
+ peerDependencies:
+ '@remix-run/react': '>=2'
+ next: '>=14.2.0'
+ react: '>=18.2.0 || ^19.0.0-0'
+ react-router: ^6 || ^7
+ react-router-dom: ^6 || ^7
+ peerDependenciesMeta:
+ '@remix-run/react':
+ optional: true
+ next:
+ optional: true
+ react-router:
+ optional: true
+ react-router-dom:
+ optional: true
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -5840,6 +5864,8 @@ snapshots:
minipass@7.1.2: {}
+ mitt@3.0.1: {}
+
motion-dom@12.6.3:
dependencies:
motion-utils: 12.6.3
@@ -5907,6 +5933,13 @@ snapshots:
npm-package-arg: 10.1.0
semver: 7.7.1
+ nuqs@2.4.1(react-router@7.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
+ dependencies:
+ mitt: 3.0.1
+ react: 18.3.1
+ optionalDependencies:
+ react-router: 7.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+
object-assign@4.1.1: {}
object-hash@3.0.0: {}
diff --git a/src/app.tsx b/src/app.tsx
index 49297e06..1850f2ed 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -9,6 +9,7 @@ import Home from "./pages/home";
import { lazy, Suspense } from "react";
import { Loading } from "./components/loading";
import { HelmetProvider } from "react-helmet-async";
+import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
const Index = lazy(() => import("@/pages/index"));
@@ -18,27 +19,29 @@ const options = {
};
const App = () => (
-
-
-
-
-
-
-
- }>
-
- } />
- } />
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ }>
+
+ } />
+ } />
+
+
+
+
+
+
+
+
);
export default App;
diff --git a/src/components/editor/theme-preview-panel.tsx b/src/components/editor/theme-preview-panel.tsx
index 4329e49d..401884a1 100644
--- a/src/components/editor/theme-preview-panel.tsx
+++ b/src/components/editor/theme-preview-panel.tsx
@@ -2,7 +2,6 @@ import { ThemeEditorPreviewProps } from "@/types/theme";
import { Tabs, TabsContent, TabsList } from "@/components/ui/tabs";
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
import ColorPreview from "./theme-preview/color-preview";
-import ComponentsShowcase from "./theme-preview/components-showcase";
import TabsTriggerPill from "./theme-preview/tabs-trigger-pill";
import ExamplesPreviewContainer from "./theme-preview/examples-preview-container";
import { lazy } from "react";
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
index e3a8c7d5..18dca20b 100644
--- a/src/components/theme-provider.tsx
+++ b/src/components/theme-provider.tsx
@@ -4,6 +4,7 @@ import { colorFormatter } from "../utils/color-converter";
import { setShadowVariables } from "@/utils/shadows";
import { applyStyleToElement } from "@/utils/apply-style-to-element";
import { ThemeStyleProps, ThemeStyles } from "@/types/theme";
+import { useThemePresetFromUrl } from "@/hooks/use-theme-preset-from-url";
type Theme = "dark" | "light";
@@ -80,6 +81,9 @@ const updateThemeClass = (root: HTMLElement, mode: Theme) => {
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const { themeState, setThemeState } = useEditorStore();
+ // Handle theme preset from URL
+ useThemePresetFromUrl();
+
useLayoutEffect(() => {
const root = window.document.documentElement;
const { currentMode: mode, styles: themeStyles } = themeState;
diff --git a/src/hooks/use-theme-preset-from-url.ts b/src/hooks/use-theme-preset-from-url.ts
new file mode 100644
index 00000000..20e20d85
--- /dev/null
+++ b/src/hooks/use-theme-preset-from-url.ts
@@ -0,0 +1,16 @@
+import { useQueryState } from "nuqs";
+import React from "react";
+import { useEditorStore } from "@/store/editor-store";
+
+export const useThemePresetFromUrl = () => {
+ const [preset, setPreset] = useQueryState("theme");
+ const applyThemePreset = useEditorStore((state) => state.applyThemePreset);
+
+ // Apply theme preset if it exists in URL and remove it
+ React.useEffect(() => {
+ if (preset) {
+ applyThemePreset(preset);
+ setPreset(null); // Remove the preset from URL
+ }
+ }, [preset, setPreset, applyThemePreset]);
+};