Skip to content

Commit

Permalink
Merge pull request #43 from react18-tools/feat/39-create-switcher-wit…
Browse files Browse the repository at this point in the history
…hout-injection

Feat/39 create switcher without injection
  • Loading branch information
mayank1513 authored Jun 27, 2024
2 parents d27e3ef + 9b06e4d commit 0d88f7c
Show file tree
Hide file tree
Showing 24 changed files with 397 additions and 132 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-moons-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-themes": patch
---

Create Switcher without injecting scripts for containerized themes.
18 changes: 18 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"mode": "exit",
"tag": "feat",
"initialVersions": {
"@example/app": "0.0.0",
"@example/pages": "0.0.0",
"@example/tailwind": "0.0.0",
"@example/vite": "0.0.0",
"nextjs-themes": "4.0.2",
"@repo/eslint-config": "0.0.0",
"@repo/typescript-config": "0.0.0",
"@repo/shared": "0.0.0",
"@repo/scripts": "0.0.0"
},
"changesets": [
"giant-moons-watch"
]
}
2 changes: 1 addition & 1 deletion examples/pages-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@types/node": "^20.14.9",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"react-webgl-trails": "^0.0.2",
"react-webgl-trails": "^0.0.3",
"typescript": "^5.5.2"
}
}
3 changes: 1 addition & 2 deletions examples/pages-router/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import "../styles/global.css";
import { Inter } from "next/font/google";
import Link from "next/link";
import { Card, Cards, LandingPage, Layout, type CardProps } from "@repo/shared/dist/server";
import { ThemeController, PageNavigatorCard, Header } from "@repo/shared";
import { ThemeController, PageNavigatorCard, Header, ScopedThemes } from "@repo/shared";
import { ColorSwitch } from "nextjs-themes/color-switch";
import { ScopedThemes } from "@repo/shared/dist/client/scoped-themes";
import { MouseTrail } from "react-webgl-trails";

const inter = Inter({ subsets: ["latin"] });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ interface CardProps {
text: string;
}

/** Card */
export default function Card({ href, title, text }: CardProps) {
return (
<a
href={href}
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer">
<h2 className={`mb-3 text-2xl font-semibold`}>
{title + " "}
<h2 className={"mb-3 text-2xl font-semibold"}>
{`${title} `}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>{text}</p>
<p className={"m-0 max-w-[30ch] text-sm opacity-50"}>{text}</p>
</a>
);
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb)))
rgb(var(--background-start-rgb));
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const metadata: Metadata = {
description: "Generated by Mayank <https://github.com/mayank1513>",
};

/** Layout */
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Image from "next/image";
import { ColorSwitch } from "nextjs-themes";
import Card from "./_components/card";

/** Home */
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
Expand Down Expand Up @@ -50,13 +51,13 @@ export default function Home() {
<StarMe
gitHubUrl="https://github.com/react18-tools/nextjs-themes"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
<h2 className={`mb-3 text-2xl font-semibold`}>
<h2 className={"mb-3 text-2xl font-semibold"}>
Star Me{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
<p className={"m-0 max-w-[30ch] text-sm opacity-50"}>
Explore and star official <code>nextjs-themes</code> repo.
</p>
</StarMe>
Expand Down
24 changes: 24 additions & 0 deletions lib/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# nextjs-themes

## 4.0.3-feat.0

### Patch Changes

- d89d157: Create Switcher without injecting scripts for containerized themes.

## 4.0.3

### Patch Changes

- 6e02178: Fix color-switch styles in containerized styles.

## 4.0.3-feat_39.0

### Patch Changes

- 6e02178: Fix color-switch styles in containerized styles.

## 4.0.2

### Patch Changes

- 3d3e015: Enhance: Avoid unnecessary script injection when adding contenarized themes for a target other than html element.

## 4.0.1

### Patch Changes
Expand Down
12 changes: 11 additions & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nextjs-themes",
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
"private": false,
"version": "4.0.1",
"version": "4.0.3-feat.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.",
"license": "MPL-2.0",
"main": "./dist/index.js",
Expand Down Expand Up @@ -46,6 +46,16 @@
"import": "./dist/client/theme-switcher/index.mjs",
"types": "./dist/client/theme-switcher/index.d.ts"
},
"./client/switcher": {
"require": "./dist/client/switcher/index.js",
"import": "./dist/client/switcher/index.mjs",
"types": "./dist/client/switcher/index.d.ts"
},
"./switcher": {
"require": "./dist/client/switcher/index.js",
"import": "./dist/client/switcher/index.mjs",
"types": "./dist/client/switcher/index.d.ts"
},
"./client/force-color-scheme": {
"require": "./dist/client/force-color-scheme/index.js",
"import": "./dist/client/force-color-scheme/index.mjs",
Expand Down
17 changes: 6 additions & 11 deletions lib/src/client/color-switch/color-switch.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
all: unset;
position: relative;
box-shadow: none;
color: #aaa;
color: currentColor;
border-radius: 50%;
border: 1px dashed gray;
border: 1px dashed currentColor;
cursor: pointer;
--s: 25px;
height: var(--s);
width: var(--s);
transition: all 0.3s ease-in-out 0s !important;
}

[data-csp="system"] .s::after,
[data-csp="system"] ~ .s::after,
[data-csp="system"] ~ * .s::after {
.system.s::after {
position: absolute;
height: 100%;
width: 100%;
Expand All @@ -25,21 +23,18 @@
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
content: "A";
}

[data-csp="dark"] .s,
[data-csp="dark"] ~ .s,
[data-csp="dark"] ~ * .s {
.dark.s {
box-shadow: calc(var(--s) / 4) calc(var(--s) / -4) calc(var(--s) / 8) inset #fff;
border: none;
background: transparent;
animation: a linear 0.5s;
}

[data-csp="light"] .s,
[data-csp="light"] ~ .s,
[data-csp="light"] ~ * .s {
.light.s {
box-shadow: 0 0 50px 10px yellow;
background-color: yellow;
border: 1px solid orangered;
Expand Down
5 changes: 2 additions & 3 deletions lib/src/client/color-switch/color-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ export const ColorSwitch = ({
className,
...props
}: ColorSwitchProps) => {
const { toggleColorScheme } = useTheme(targetSelector);
const { toggleColorScheme, colorSchemePref } = useTheme(targetSelector);

const cls = [styles.s, className].join(" ");
return (
<button
className={cls}
className={[styles.s, styles[colorSchemePref], className].join(" ")}
data-testid="color-switch"
// skipcq: JS-0417
onClick={() => toggleColorScheme(skipSystem)}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/client/switcher/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use client";

// component exports
export * from "./switcher";
78 changes: 78 additions & 0 deletions lib/src/client/switcher/switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect } from "react";
import { ThemeSwitcherProps } from "../theme-switcher";
import { DARK, DEFAULT_ID, LIGHT } from "../../constants";
import { useForcedStore, useThemeStore } from "../../store";
import type { ResolveFunc, UpdateDOMFunc, UpdateForcedPropsFunc } from "../theme-switcher/no-fouc";

let media: MediaQueryList;
let updateDOM: UpdateDOMFunc;
let resolveTheme: ResolveFunc;
let updateForcedProps: UpdateForcedPropsFunc;
let updateForcedState: UpdateForcedPropsFunc;

/** disable transition while switching theme */
const modifyTransition = (themeTransition = "none") => {
const css = document.createElement("style");
/** split by ';' to prevent CSS injection */
css.textContent = `transition:${themeTransition.split(";")[0]}!important;`;
document.head.appendChild(css);

return () => {
// Force restyle
getComputedStyle(document.body);
// Wait for next tick before removing
setTimeout(() => document.head.removeChild(css), 1);
};
};

/**
* The Core component applies classes and transitions without injecting any scripts. Use ThemeSwitcher only once per page. For scoped styles, use this component instead.
*
* Please note that you need to use suitable techniques to increase specificity of CSS selecors when using data- attributes for targetting a container which is within another themed container (including the html if you have used ThemeSwitcher without targetSelector)
*
* @example
* ```tsx
* <Switcher targetSelector="#container1" />
* ```
*/
export const Switcher = ({
forcedTheme,
forcedColorScheme,
targetSelector,
themeTransition,
}: ThemeSwitcherProps) => {
const k = targetSelector || `#${DEFAULT_ID}`;

const [state, setState] = useThemeStore(targetSelector);
const [forced] = useForcedStore(targetSelector);

useEffect(() => {
if (typeof m !== "undefined")
[media, updateDOM, resolveTheme, updateForcedProps, updateForcedState] = [m, u, r, f, g];

media.addEventListener("change", () =>
setState(state => ({ ...state, s: media.matches ? DARK : LIGHT })),
);
addEventListener("storage", e => {
if (e.key === k) setState(state => ({ ...state, ...JSON.parse(e.newValue || "{}") }));
});
}, []);

useEffect(() => {
const restoreThansitions = modifyTransition(themeTransition);
updateDOM(resolveTheme(state), k);
restoreThansitions();
localStorage.setItem(k, JSON.stringify(state));
}, [state]);

useEffect(() => {
updateForcedProps(forcedTheme, forcedColorScheme);
updateDOM(resolveTheme(state), k);
}, [forcedColorScheme, forcedTheme]);

useEffect(() => {
updateForcedState(forced.f, forced.fc);
updateDOM(resolveTheme(state), k);
}, [forced]);
return null;
};
8 changes: 8 additions & 0 deletions lib/src/client/theme-switcher/no-fouc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ declare global {
var g: UpdateForcedPropsFunc;
}

export type ScriptArgs = [
string,
ThemeStoreType,
Record<string, string> | undefined,
string | undefined,
ColorSchemeType | undefined,
];

/** @internal Script to be injected for avoiding FOUC */
export const noFOUCScript = (
key: string,
Expand Down
Loading

0 comments on commit 0d88f7c

Please sign in to comment.