diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..fe5bd20a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnPaste": true +} \ No newline at end of file diff --git a/README.md b/README.md index 9a504c8e..8d6e192d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This project was originally inspired by next-themes. Next-themes is an awesome p - ✅ Documented with [Typedoc](https://react18-tools.github.io/nextjs-themes) ([Docs](https://react18-tools.github.io/nextjs-themes)) - ✅ Use combinations of [data-th=""] and [data-color-scheme=""] for dark/light varients of themes - ✅ Use [data-csp=""] to style based on colorSchemePreference. +- ✅ Want to avoid cookies (Not recommended), set storage prop to `localStorage` or `sessionStorage` (to avoid persistance) Check out the [live example](https://nextjs-themes.vercel.app/). @@ -37,9 +38,17 @@ Check out the [live example](https://nextjs-themes.vercel.app/). ```bash $ pnpm add nextjs-themes -# or +``` + +**OR** + +```bash $ npm install nextjs-themes -# or +``` + +**OR** + +```bash $ yarn add nextjs-themes ``` @@ -47,9 +56,17 @@ $ yarn add nextjs-themes ```bash $ pnpm add nextjs-themes-lite -# or +``` + +**or** + +```bash $ npm install nextjs-themes-lite -# or +``` + +**or** + +```bash $ yarn add nextjs-themes-lite ``` diff --git a/examples/app-router/CHANGELOG.md b/examples/app-router/CHANGELOG.md index 88fb2885..2dd91c4d 100644 --- a/examples/app-router/CHANGELOG.md +++ b/examples/app-router/CHANGELOG.md @@ -1,5 +1,13 @@ # app-router +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - nextjs-themes@2.1.0 + - shared-ui@1.0.2 + ## 0.0.15 ### Patch Changes diff --git a/examples/app-router/package.json b/examples/app-router/package.json index 0425f4ad..cca1cacc 100644 --- a/examples/app-router/package.json +++ b/examples/app-router/package.json @@ -1,6 +1,6 @@ { "name": "app-router", - "version": "0.0.15", + "version": "0.0.16", "private": true, "scripts": { "dev": "next dev --port 3002", @@ -18,9 +18,9 @@ }, "devDependencies": { "@next/eslint-plugin-next": "^14.0.4", - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", "typescript": "^5.3.3" diff --git a/examples/pages-router/CHANGELOG.md b/examples/pages-router/CHANGELOG.md index e7e2544f..cf1c9bfc 100644 --- a/examples/pages-router/CHANGELOG.md +++ b/examples/pages-router/CHANGELOG.md @@ -1,5 +1,13 @@ # nextjs-pages-router +## 1.0.11 + +### Patch Changes + +- Updated dependencies + - nextjs-themes@2.1.0 + - shared-ui@1.0.2 + ## 1.0.10 ### Patch Changes diff --git a/examples/pages-router/package.json b/examples/pages-router/package.json index fb126f0b..910547c6 100644 --- a/examples/pages-router/package.json +++ b/examples/pages-router/package.json @@ -1,6 +1,6 @@ { "name": "pages-router", - "version": "1.0.10", + "version": "1.0.11", "private": true, "scripts": { "dev": "next dev --port 3003", @@ -16,9 +16,9 @@ "shared-ui": "workspace:*" }, "devDependencies": { - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", "typescript": "^5.3.3" diff --git a/examples/simple-multi-theme/CHANGELOG.md b/examples/simple-multi-theme/CHANGELOG.md index d5748aca..a46a08d2 100644 --- a/examples/simple-multi-theme/CHANGELOG.md +++ b/examples/simple-multi-theme/CHANGELOG.md @@ -1,5 +1,13 @@ # simple-multi-theme +## 1.0.11 + +### Patch Changes + +- Updated dependencies + - nextjs-themes@2.1.0 + - shared-ui@1.0.2 + ## 1.0.10 ### Patch Changes diff --git a/examples/simple-multi-theme/package.json b/examples/simple-multi-theme/package.json index 6562d6bd..1a119359 100644 --- a/examples/simple-multi-theme/package.json +++ b/examples/simple-multi-theme/package.json @@ -1,6 +1,6 @@ { "name": "simple-multi-theme", - "version": "1.0.10", + "version": "1.0.11", "private": true, "scripts": { "dev": "next dev --port 3001", @@ -16,9 +16,9 @@ "shared-ui": "workspace:*" }, "devDependencies": { - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", "typescript": "^5.3.3" diff --git a/examples/tailwind/CHANGELOG.md b/examples/tailwind/CHANGELOG.md index 384f3790..cf4493fd 100644 --- a/examples/tailwind/CHANGELOG.md +++ b/examples/tailwind/CHANGELOG.md @@ -1,5 +1,12 @@ # tailwind +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - nextjs-themes@2.1.0 + ## 0.1.4 ### Patch Changes diff --git a/examples/tailwind/package.json b/examples/tailwind/package.json index 624c46cb..85bccf32 100644 --- a/examples/tailwind/package.json +++ b/examples/tailwind/package.json @@ -1,6 +1,6 @@ { "name": "tailwind", - "version": "0.1.4", + "version": "0.1.5", "private": true, "scripts": { "dev": "next dev", @@ -16,14 +16,14 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.16", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-config-custom": "workspace:*", "postcss": "^8.4.32", - "tailwindcss": "^3.3.6", + "tailwindcss": "^3.4.0", "tsconfig": "workspace:*", "typescript": "^5.3.3" } diff --git a/examples/vite/CHANGELOG.md b/examples/vite/CHANGELOG.md index 28a77691..3b98c873 100644 --- a/examples/vite/CHANGELOG.md +++ b/examples/vite/CHANGELOG.md @@ -1,5 +1,13 @@ # vite-example +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - nextjs-themes@2.1.0 + - shared-ui@1.0.2 + ## 0.0.15 ### Patch Changes diff --git a/examples/vite/package.json b/examples/vite/package.json index fa873d02..cbe0b621 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -1,7 +1,7 @@ { "name": "vite-example", "private": true, - "version": "0.0.15", + "version": "0.0.16", "type": "module", "scripts": { "dev": "vite --port 3001", @@ -14,19 +14,19 @@ "nextjs-themes": "workspace:*", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.20.1", + "react-router-dom": "^6.21.1", "shared-ui": "workspace:*" }, "devDependencies": { - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", - "@typescript-eslint/eslint-plugin": "^6.13.2", - "@typescript-eslint/parser": "^6.13.2", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", + "@typescript-eslint/eslint-plugin": "^6.16.0", + "@typescript-eslint/parser": "^6.16.0", "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "typescript": "^5.3.3", - "vite": "^5.0.7" + "vite": "^5.0.10" } } diff --git a/lib/nextjs-themes/CHANGELOG.md b/lib/nextjs-themes/CHANGELOG.md index ed009b01..44cd6f6d 100644 --- a/lib/nextjs-themes/CHANGELOG.md +++ b/lib/nextjs-themes/CHANGELOG.md @@ -1,5 +1,11 @@ # nextjs-themes +## 2.1.0 + +### Minor Changes + +- Add support for changing storage + ## 2.0.1 ### Patch Changes diff --git a/lib/nextjs-themes/package.json b/lib/nextjs-themes/package.json index 74fa8b84..ce8797e3 100644 --- a/lib/nextjs-themes/package.json +++ b/lib/nextjs-themes/package.json @@ -2,7 +2,7 @@ "name": "nextjs-themes", "author": "Mayank Kumar Chaudhari ", "private": false, - "version": "2.0.1", + "version": "2.1.0", "description": "Unleash the Power of React Server Components! Use multiple themes on your site with confidence, without losing any advantages of React Server Components.", "main": "./index.ts", "types": "./index.ts", @@ -25,12 +25,12 @@ }, "devDependencies": { "@testing-library/react": "^14.1.2", - "@turbo/gen": "^1.11.1", - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@turbo/gen": "^1.11.2", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.0.2", + "@vitest/coverage-v8": "^1.1.0", "esbuild-plugin-ignoretests": "^0.0.7", "esbuild-plugin-react18": "^0.0.6", "esbuild-plugin-removetestid": "^0.0.5", @@ -44,11 +44,11 @@ "tsup": "^8.0.1", "typedoc": "^0.25.4", "typescript": "5.3.3", - "vite-tsconfig-paths": "^4.2.2", - "vitest": "^1.0.2" + "vite-tsconfig-paths": "^4.2.3", + "vitest": "^1.1.0" }, "dependencies": { - "persist-and-sync": "^1.1.1", + "persist-and-sync": "^1.2.0", "zustand": "^4.4.7" }, "peerDependencies": { diff --git a/lib/nextjs-themes/src/client/theme-switcher/theme-switcher.tsx b/lib/nextjs-themes/src/client/theme-switcher/theme-switcher.tsx index 1e78d4f2..c65c527b 100644 --- a/lib/nextjs-themes/src/client/theme-switcher/theme-switcher.tsx +++ b/lib/nextjs-themes/src/client/theme-switcher/theme-switcher.tsx @@ -3,48 +3,18 @@ import { useEffect } from "react"; import type { ColorSchemeType } from "../../store"; import { useTheme } from "../../store"; import { resolveTheme } from "../../utils"; +import { StorageType } from "persist-and-sync"; export interface ThemeSwitcherProps { forcedTheme?: string; forcedColorScheme?: ColorSchemeType; targetSelector?: string; themeTransition?: string; -} - -export function ThemeSwitcher(props: ThemeSwitcherProps) { - useThemeSwitcher(props); - return null; -} - -export function useThemeSwitcher(props: ThemeSwitcherProps) { - const depArray = useTheme(state => [ - state.theme, - state.darkTheme, - state.lightTheme, - state.colorSchemePref, - state.forcedColorScheme, - state.forcedTheme, - ]); - - useEffect(() => { - const themeState = useTheme.getState(); - const media = matchMedia("(prefers-color-scheme: dark)"); - const updateTheme = () => { - const restoreTransitions = disableAnimation(props.themeTransition); - - const resolvedData = resolveTheme(media.matches, themeState, props); - themeState.setResolved(resolvedData); - updateDOM(resolvedData, media.matches, props.targetSelector); - - restoreTransitions(); - }; - - media.addEventListener("change", updateTheme); - updateTheme(); - return () => { - media.removeEventListener("change", updateTheme); - }; - }, [props.forcedColorScheme, props.forcedTheme, props.targetSelector, ...depArray]); + /** + * defaultValue `"cookies"` + * set storage to `localStorage` or `sessionsStorage` when using only client side or when you must avoid using cookies + */ + storage?: StorageType; } export interface DataProps { @@ -62,11 +32,11 @@ export interface UpdateProps { th: string; } -function updateDOM( +const updateDOM = ( { resolvedTheme, resolvedColorScheme, resolvedColorSchemePref, th }: UpdateProps, isSystemDark: boolean, targetSelector?: string, -) { +) => { [document.querySelector(targetSelector || "#nextjs-themes"), document.documentElement].forEach(target => { /** ensuring that class 'dark' is always present when dark color scheme is applied to support Tailwind */ if (target) @@ -79,9 +49,8 @@ function updateDOM( /** store system preference for computing data-theme on server side */ document.cookie = `data-color-scheme-system=${isSystemDark ? "dark" : "light"}`; -} +}; -// todo: customizable transition time const disableAnimation = (themeTransition = "none") => { const css = document.createElement("style"); const transition = `transition: ${themeTransition} !important;`; @@ -99,3 +68,52 @@ const disableAnimation = (themeTransition = "none") => { }, 1); }; }; + +/** + * You can use this hook in place of `` component. + * Please note that you need to add "use client" on top of the component in which you are using this hook. + */ +export function useThemeSwitcher(props: ThemeSwitcherProps) { + const [setStorage, ...depArray] = useTheme(state => [ + state.setStorage, + state.theme, + state.darkTheme, + state.lightTheme, + state.colorSchemePref, + state.forcedColorScheme, + state.forcedTheme, + ]); + + useEffect(() => { + setStorage(props.storage ?? "cookies"); + }, [props.storage]); + + useEffect(() => { + const themeState = useTheme.getState(); + const media = matchMedia("(prefers-color-scheme: dark)"); + const updateTheme = () => { + const restoreTransitions = disableAnimation(props.themeTransition); + + const resolvedData = resolveTheme(media.matches, themeState, props); + themeState.setResolved(resolvedData); + updateDOM(resolvedData, media.matches, props.targetSelector); + + restoreTransitions(); + }; + + media.addEventListener("change", updateTheme); + updateTheme(); + return () => { + media.removeEventListener("change", updateTheme); + }; + }, [props.forcedColorScheme, props.forcedTheme, props.targetSelector, ...depArray]); +} + +/** + * Use this component in your layout - `app/layout.tsx` or your custom layout or in `_app.tsx` file. + * @component + */ +export function ThemeSwitcher(props: ThemeSwitcherProps) { + useThemeSwitcher(props); + return null; +} diff --git a/lib/nextjs-themes/src/store.ts b/lib/nextjs-themes/src/store.ts index 8d8e193b..0390eecd 100644 --- a/lib/nextjs-themes/src/store.ts +++ b/lib/nextjs-themes/src/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { persistNSync } from "persist-and-sync"; +import { PersistNSyncOptionsType, StorageType, persistNSync } from "persist-and-sync"; export type ColorSchemeType = "" | "system" | "dark" | "light"; @@ -12,6 +12,7 @@ export type ThemeStoreType = { resolvedColorScheme: ColorSchemeType; forcedTheme?: string; forcedColorScheme?: ColorSchemeType; + __persistNSyncOptions: PersistNSyncOptionsType; }; export type ThemeStoreActionsType = { @@ -23,6 +24,13 @@ export type ThemeStoreActionsType = { setForcedTheme: (forcedTheme?: string) => void; setForcedColorScheme: (forcedColorScheme?: ColorSchemeType) => void; setResolved: (resolved: { resolvedTheme: string; resolvedColorScheme: ColorSchemeType }) => void; + setStorage: (storage: StorageType) => void; +}; + +const storeOptions: PersistNSyncOptionsType = { + name: "nextjs-themes", + exclude: [/forced/, /resolved/, /^__/], + storage: "cookies", }; export const initialState: ThemeStoreType = { @@ -32,6 +40,7 @@ export const initialState: ThemeStoreType = { lightTheme: "", colorSchemePref: "system", resolvedColorScheme: "system", + __persistNSyncOptions: storeOptions, }; export const useTheme = create()( @@ -46,7 +55,8 @@ export const useTheme = create()( setColorSchemePref: colorSchemePref => set({ ...get(), colorSchemePref }), setResolved: ({ resolvedColorScheme, resolvedTheme }) => set({ ...get(), resolvedColorScheme, resolvedTheme }), setThemeSet: ({ lightTheme, darkTheme }) => set({ ...get(), lightTheme, darkTheme }), + setStorage: storage => set({ ...get(), __persistNSyncOptions: { ...storeOptions, storage } }), }), - { name: "nextjs-themes", exclude: [/forced/, /resolved/], storage: "cookies" }, + storeOptions, ), ); diff --git a/lib/nextjs-themes/touchup.js b/lib/nextjs-themes/touchup.js index f666535c..a8c0c341 100644 --- a/lib/nextjs-themes/touchup.js +++ b/lib/nextjs-themes/touchup.js @@ -5,35 +5,35 @@ const path = require("node:path"); const packageJson = require(path.resolve(process.cwd(), "package.json")); if (process.env.TOKEN) { - const { Octokit } = require("octokit"); - // Octokit.js - // https://github.com/octokit/core.js#readme - const octokit = new Octokit({ - auth: process.env.TOKEN, - }); + const { Octokit } = require("octokit"); + // Octokit.js + // https://github.com/octokit/core.js#readme + const octokit = new Octokit({ + auth: process.env.TOKEN, + }); - const octoOptions = { - owner: process.env.OWNER, - repo: process.env.REPO, - headers: { - "X-GitHub-Api-Version": "2022-11-28", - }, - }; - const tagName = `v${packageJson.version}`; - const name = `Release ${tagName}`; - /** Create a release */ - octokit.request("POST /repos/{owner}/{repo}/releases", { - ...octoOptions, - tag_name: tagName, - target_commitish: "main", - name, - draft: false, - prerelease: false, - generate_release_notes: true, - headers: { - "X-GitHub-Api-Version": "2022-11-28", - }, - }); + const octoOptions = { + owner: process.env.OWNER, + repo: process.env.REPO, + headers: { + "X-GitHub-Api-Version": "2022-11-28", + }, + }; + const tagName = `v${packageJson.version}`; + const name = `Release ${tagName}`; + /** Create a release */ + octokit.request("POST /repos/{owner}/{repo}/releases", { + ...octoOptions, + tag_name: tagName, + target_commitish: "main", + name, + draft: false, + prerelease: false, + generate_release_notes: true, + headers: { + "X-GitHub-Api-Version": "2022-11-28", + }, + }); } delete packageJson.scripts; @@ -43,3 +43,5 @@ packageJson.types = "index.d.ts"; fs.writeFileSync(path.resolve(process.cwd(), "dist", "package.json"), JSON.stringify(packageJson, null, 2)); fs.copyFileSync(path.resolve(process.cwd(), "..", "..", "README.md"), path.resolve(process.cwd(), "dist", "README.md")); + +fs.copyFileSync(path.resolve(__dirname, "CHANGELOG.md"), path.resolve(__dirname, "dist", "CHANGELOG.md")); diff --git a/packages/eslint-config-custom/package.json b/packages/eslint-config-custom/package.json index 3548639e..69c45989 100644 --- a/packages/eslint-config-custom/package.json +++ b/packages/eslint-config-custom/package.json @@ -6,7 +6,7 @@ "dependencies": { "eslint-config-next": "^14.0.4", "eslint-config-prettier": "^9.1.0", - "eslint-config-turbo": "^1.11.1", + "eslint-config-turbo": "^1.11.2", "eslint-plugin-react": "7.33.2" }, "publishConfig": { diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json index e66b5d14..ffd0aa58 100644 --- a/packages/shared-ui/package.json +++ b/packages/shared-ui/package.json @@ -11,9 +11,9 @@ }, "devDependencies": { "@mayank1513/fork-me": "^2.0.0", - "@types/node": "^20.10.4", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", + "@types/node": "^20.10.6", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", "eslint-config-custom": "workspace:*", "next": "^14.0.4", "nextjs-themes": "workspace:*", @@ -22,6 +22,6 @@ "typescript": "5.3.3" }, "dependencies": { - "eslint": "^8.55.0" + "eslint": "^8.56.0" } }