Skip to content

Commit

Permalink
Merge pull request #9 from react18-tools/feat/support-wrapping-html
Browse files Browse the repository at this point in the history
Feat/support wrapping html
  • Loading branch information
mayank1513 committed Jun 13, 2024
2 parents 1f3bf89 + b1fe1a4 commit 5558307
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 38 deletions.
12 changes: 12 additions & 0 deletions lib/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# nextjs-darkmode

## 0.2.0

### Minor Changes

- 05568cb: Support children in Switch element. Use case: Theme Switch with mode label.
- 59dbc4f: Support passing tag to server-target. Use case: replace html element in case SSR is preferred over SSG.

### Patch Changes

- 05568cb: Override pseudo element transitions as well to avoid patched theme transition
- 05568cb: Refactor: refactor code hoping better minzip size

## 0.1.1

### Patch Changes
Expand Down
2 changes: 1 addition & 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.1.1",
"version": "0.2.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
22 changes: 11 additions & 11 deletions lib/src/client/core/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface CoreProps {
const modifyTransition = (themeTransition = "none", nonce = "") => {
const css = document.createElement("style");
/** split by ';' to prevent CSS injection */
css.textContent = `*{transition:${themeTransition.split(";")[0]} !important;}`;
css.textContent = `*,*:after,*:before{transition:${themeTransition.split(";")[0]} !important;}`;
nonce && css.setAttribute("nonce", nonce);
document.head.appendChild(css);

Expand Down Expand Up @@ -60,16 +60,16 @@ export const Core = ({ t, nonce }: CoreProps) => {
// We need to always update documentElement to support Tailwind configuration
// skipcq: JS-D008, JS-0042 -> map keyword is shorter
[document.documentElement, serverTargetEl].map(el => {
// skipcq: JS-0042
if (!el) return;
const clsList = el.classList;
modes.forEach(mode => clsList.remove(mode));
clsList.add(resolvedMode);
[
["sm", systemMode],
["rm", resolvedMode],
["m", mode],
].forEach(([dataLabel, value]) => el.setAttribute(`data-${dataLabel}`, value));
if (el) {
const clsList = el.classList;
modes.forEach(mode => clsList.remove(mode));
clsList.add(resolvedMode);
[
["sm", systemMode],
["rm", resolvedMode],
["m", mode],
].forEach(([dataLabel, value]) => el.setAttribute(`data-${dataLabel}`, value));
}
});
restoreTransitions();
// System mode is decided by current system state and need not be stored in localStorage
Expand Down
1 change: 1 addition & 0 deletions lib/src/client/switch/switch.module.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.switch {
all: unset;
position: relative;
display: inline-block;
color: currentColor;
border-radius: 50%;
border: 1px dashed currentColor;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/client/switch/switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("switch", () => {
act(() => {
hook.result.current.setMode(SYSTEM);
});
render(<Switch skipSystem />);
render(<Switch skipSystem>Switch with children</Switch>);
const element = screen.getByTestId("switch");
await act(() => fireEvent.click(element));
expect(hook.result.current.mode).toBe(DARK);
Expand Down
30 changes: 26 additions & 4 deletions lib/src/client/switch/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styles from "./switch.module.scss";
import { useStore } from "../../utils";
import { modes } from "../../constants";

export interface SwitchProps extends HTMLProps<HTMLElement> {
export interface SwitchProps extends HTMLProps<HTMLButtonElement> {
/** html tag @defaultValue 'button' */
tag?: "button" | "div";
/** Diameter of the color switch */
Expand All @@ -22,7 +22,13 @@ export interface SwitchProps extends HTMLProps<HTMLElement> {
*
* @source - Source code
*/
export const Switch = ({ tag: Tag = "button", size = 24, skipSystem, ...props }: SwitchProps) => {
export const Switch = ({
tag: Tag = "button",
size = 24,
skipSystem,
children,
...props
}: SwitchProps) => {
const [state, setState] = useStore();
/** toggle mode */
const handleModeSwitch = () => {
Expand All @@ -34,11 +40,27 @@ export const Switch = ({ tag: Tag = "button", size = 24, skipSystem, ...props }:
m: modes[(index + 1) % n],
});
};
const className = [props.className, styles["switch"]].filter(Boolean).join(" ");
if (children) {
return (
// @ts-expect-error -- too complex types
<Tag
suppressHydrationWarning
{...props}
data-testid="switch"
// skipcq: JS-0417
onClick={handleModeSwitch}>
{/* @ts-expect-error -> we are setting the CSS variable */}
<div className={styles.switch} style={{ "--size": `${size}px` }} />
{children}
</Tag>
);
}
return (
<Tag
// Hydration warning is caused when the data from localStorage differs from the default data provided while rendering on server
suppressHydrationWarning
{...props}
className={className}
className={[props.className, styles.switch].join(" ")}
// @ts-expect-error -> we are setting the CSS variable
style={{ "--size": `${size}px` }}
data-testid="switch"
Expand Down
22 changes: 20 additions & 2 deletions lib/src/server/server-target/server-target.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { cookies } from "next/headers";
import { COOKIE_KEY, LIGHT } from "../../constants";
import { HTMLProps } from "react";

export interface ServerTargetProps extends HTMLProps<HTMLHtmlElement | HTMLDivElement> {
tag?: "html" | "div";
}

/**
* Server Side target for avoiding flash of un-themed content.
Expand All @@ -15,9 +20,22 @@ import { COOKIE_KEY, LIGHT } from "../../constants";
* </html>
* ```
*
* If you prefer SSR over SSG, you can replace `html` element with `ServerTarget`. This way you can avoid having to write sybling selectors.
*
* @example
* ```tsx
* <ServerTarget tag="html">
* ...
* </ServerTarget>
* ```
*
* @param tag - Tag to use for the target. Defaults to `div`.
* ```
*
* @source - Source code
*/
export const ServerTarget = () => {
export const ServerTarget = ({ tag: Tag = "div", ...props }: ServerTargetProps) => {
const rm = cookies().get(COOKIE_KEY)?.value ?? LIGHT;
return <div className={rm} data-rm={rm} data-ndm="ndm" data-testid="server-target" />;
// @ts-expect-error -- too complex types
return <Tag className={rm} data-rm={rm} data-ndm="ndm" data-testid="server-target" {...props} />;
};
7 changes: 1 addition & 6 deletions lib/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,12 @@ export interface Store {
s: ResolvedScheme;
}

const DEFAULT_STORE_VAL: Store = {
m: SYSTEM,
s: DARK as ResolvedScheme,
};

/** local abstaction of RGS to avoid multiple imports */
export const useStore = () =>
useRGS<Store>("ndm", () =>
typeof localStorage === "undefined"
? /* v8 ignore next */
DEFAULT_STORE_VAL
{ m: SYSTEM, s: DARK as ResolvedScheme }
: {
m: (localStorage.getItem(COOKIE_KEY) ?? SYSTEM) as ColorSchemePreference,
s: (matchMedia(MEDIA).matches ? DARK : LIGHT) as ResolvedScheme,
Expand Down
10 changes: 10 additions & 0 deletions packages/shared/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @repo/shared

## 0.0.6

### Patch Changes

- Updated dependencies [05568cb]
- Updated dependencies [05568cb]
- Updated dependencies [05568cb]
- Updated dependencies [59dbc4f]
- [email protected]

## 0.0.5

### 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.5",
"version": "0.0.6",
"private": true,
"sideEffects": false,
"main": "./dist/index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/client/header/header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
padding: 15px 20px;
gap: 20px;
display: flex;
place-items: center;
text-transform: capitalize;
cursor: pointer;
margin: 0;
Expand Down
16 changes: 4 additions & 12 deletions packages/shared/src/client/header/theme-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@

import { useMode } from "nextjs-darkmode/hooks";
import styles from "./header.module.scss";
import { useCallback } from "react";
import { ColorSchemePreference } from "nextjs-darkmode";
import { Switch } from "nextjs-darkmode/switch";

const modes: ColorSchemePreference[] = ["dark", "light", "system"];
import { Switch } from "nextjs-darkmode/switch";

/** This is a wrapper around `nextjs-darkmode's ColorSwitch component to improve mobile view. */
export default function ThemeSwitch() {
const { mode, setMode } = useMode();
const toggle = useCallback(() => {
const index = modes.indexOf(mode);
setMode(modes[(index + 1) % modes.length]);
}, [mode]);
const { mode } = useMode();

return (
<button className={styles.themeswitch} onClick={toggle}>
<Switch tag="div" />
<Switch className={styles.themeswitch}>
<span className="mb" suppressHydrationWarning>
{mode}
</span>
</button>
</Switch>
);
}
1 change: 1 addition & 0 deletions scripts/manual-publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const isNotPatch = newMajor !== oldMajor || newMinor !== oldMinor;
const pushCmd = `git add . && git commit -m "Apply changesets and update CHANGELOG" && git push origin ${BRANCH}`;

if (isNotPatch && BRANCH === DEFAULT_BRANCH) {
require("./update-security-md")(`${newMajor}.${newMinor}`, `${oldMajor}.${oldMinor}`);
execSync(pushCmd);
/** Create new release branch for every Major or Minor release */
const releaseBranch = `release-${newMajor}.${newMinor}`;
Expand Down
1 change: 1 addition & 0 deletions scripts/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const [oldMajor, oldMinor] = LATEST_VERSION.split(".");
const isPatch = newMajor === oldMajor && newMinor === oldMinor;

if (!isPatch) {
require("./update-security-md")(`${newMajor}.${newMinor}`, `${oldMajor}.${oldMinor}`);
/** Create new release branch for every Major or Minor release */
const releaseBranch = `release-${newMajor}.${newMinor}`;
execSync(`git checkout -b ${releaseBranch} && git push origin ${releaseBranch}`);
Expand Down
15 changes: 15 additions & 0 deletions scripts/update-security-md.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { execSync } = require("child_process");

module.exports = (newMajor_minor, oldMajor_minor) => {
/** Update SECURITY.md */
execSync(
`sed -i -e "s/.*| :white_check_mark:.*/| ${newMajor_minor}.x | :white_check_mark: |/" SECURITY.md`,
);
execSync(
`sed -i -e "s/.*| :warning:.*/| ${oldMajor_minor}.x | :warning: |/" SECURITY.md`,
);
execSync(`sed -i -e "s/.*| :x:.*/| < ${oldMajor_minor} | :x: |/" SECURITY.md`);
execSync(
`git add SECURITY.md && git commit -m 'Update SECURITY.md [skip ci]' && git push origin ${process.env.BRANCH}`,
);
};

0 comments on commit 5558307

Please sign in to comment.