Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Content Security Policy #6

Merged
merged 5 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,33 @@ The `nextjs-themes` library was initially created to achieve a similar functiona
## Features

- ✅ Simple API to toggle between dark and light modes

- ✅ Perfect dark mode with just 2 lines of code
- ✅ Compatible with Tailwind CSS

- ✅ Compatible with Tailwind CSS, StyledComponents, emotion, Material UI, ...

- ✅ Secure by design - we support `nonce` when you want to apply Content Security Policy

- ✅ Fully treeshakable (e.g., `import from nextjs-darkmode/hooks`)

- ✅ Full TypeScript support

- ✅ Utilizes React 18 Server components

- ✅ Compatible with all React 18 build systems/tools/frameworks

- ✅ System setting with `prefers-color-scheme`

- ✅ Supports Next.js 13 & 14 `appDir`

- ✅ No flash on load (supports SSG, SSR, ISG, and Server Components)

- ✅ Sync theme across tabs and windows

- ✅ Apply custom transitions when changing themes

- ✅ Manipulate theme via the `useMode` hook

- ✅ No cookies when not using the corresponding `ServerTarget`

- ✅ Comprehensive documentation with [Typedoc](https://react18-tools.github.io/nextjs-darkmode)
Expand Down Expand Up @@ -190,6 +205,14 @@ When using `ServerTarget`, use the CSS general sibling combinator (~):

`data-sm` -> System preference

#### Content Security Policy

If you are using CSP rules for CSS files, you can pass `nonce` argument to the `Core` component. If `nonce` is not supplied transition styles will not be applied. This may allow patched transitions throught the page in some cases.

```tsx
<Core nonce={yourNonce} t="transition: all .5s" />
```

### Images

Show different images based on the current theme:
Expand Down
6 changes: 6 additions & 0 deletions lib/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# nextjs-darkmode

## 0.1.0

### Minor Changes

- Add nonce to support Content Security Policy

## 0.0.3

### Patch Changes
Expand Down
6 changes: 5 additions & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nextjs-darkmode",
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
"private": false,
"version": "0.0.3",
"version": "0.1.0",
"description": "Unleash the Power of React Server Components! Use dark/light mode on your site with confidence, without losing any advantages of React Server Components",
"license": "MPL-2.0",
"main": "./dist/index.js",
Expand Down Expand Up @@ -97,6 +97,10 @@
"Server Components",
"Dark Mode",
"React18 Tools",
"Content Security Policy",
"Security",
"CSP",
"nonce",
"Themes",
"Customizable",
"UI Components",
Expand Down
11 changes: 7 additions & 4 deletions lib/src/client/core/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { useEffect } from "react";
export interface CoreProps {
/** force apply CSS transition property to all the elements during theme switching. E.g., `all .3s` */
t?: string;
/** The nonce value for your Content Security Policy. */
nonce?: string;
}

/** Modify transition globally to avoid patched transitions */
const modifyTransition = (themeTransition = "none") => {
const modifyTransition = (themeTransition = "none", nonce = "") => {
const css = document.createElement("style");
/** split by ';' to prevent CSS injection */
css.textContent = `*{transition:${themeTransition.split(";")[0]} !important;}`;
nonce && css.setAttribute("nonce", nonce);
document.head.appendChild(css);

return () => {
Expand All @@ -33,7 +36,7 @@ const modifyTransition = (themeTransition = "none") => {
*
* @source - Source code
*/
export const Core = ({ t }: CoreProps) => {
export const Core = ({ t, nonce }: CoreProps) => {
const [{ m: mode, s: systemMode }, setThemeState] = useStore();
const resolvedMode = mode === SYSTEM ? systemMode : mode; // resolvedMode is the actual mode that will be used

Expand All @@ -58,7 +61,7 @@ export const Core = ({ t }: CoreProps) => {
}, []);

useEffect(() => {
const restoreTransitions = modifyTransition(t);
const restoreTransitions = modifyTransition(t, nonce);
const serverTargetEl = document.querySelector("[data-ndm]");
// We need to always update documentElement to support Tailwind configuration
// skipcq: JS-D008, JS-0042 -> map keyword is shorter
Expand All @@ -79,7 +82,7 @@ export const Core = ({ t }: CoreProps) => {
localStorage.setItem(COOKIE_KEY, mode);
if (serverTargetEl)
document.cookie = `${COOKIE_KEY}=${resolvedMode};max-age=31536000;SameSite=Strict;`;
}, [resolvedMode, systemMode, mode, t]);
}, [resolvedMode, systemMode, mode, t, nonce]);

return null;
};
7 changes: 7 additions & 0 deletions packages/shared/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @repo/shared

## 0.0.4

### Patch Changes

- Updated dependencies
- nextjs-darkmode@0.1.0

## 0.0.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@repo/shared",
"version": "0.0.3",
"version": "0.0.4",
"private": true,
"sideEffects": false,
"main": "./dist/index.js",
Expand Down
Loading