Skip to content

Commit

Permalink
implement useDarkMode
Browse files Browse the repository at this point in the history
  • Loading branch information
konstantin-lukas committed Sep 8, 2024
1 parent 0b0e99c commit 86bfac7
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 18 deletions.
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ directory.

Anzol is built alongside automated tests to ensure quality.

## Installation
Anzol is available on the NPM registry. To install it, just run:
```bash
npm install anzol
```

## Currently Available Hooks
- <b>useFetch:</b> Fetches the provided URL and optionally parses the response. Aborts requests when a new request is
started before the previous has finished to prevent flickering of stale responses by default.
Expand All @@ -40,15 +46,5 @@ Anzol is built alongside automated tests to ensure quality.
- <b>useClickOutside:</b> Provides a ref to attach to an HTML element and takes a callback function, and calls that
function when the user clicks anywhere outside the given element.
- <b>usePreferredScheme:</b> Listens for changes in the user's preferred scheme and returns it.

## Installation
Anzol is available on the NPM registry. To install it, just run:
```bash
npm install anzol
```

## Planned features
These features are not yet implement but we plan to do so in the foreseeable future. Feel free to make your own
suggestions.
- <b>useDarkMode:</b> Similar to usePreferredScheme but allows setting the user scheme manually and automatically
updates it when the preferred scheme changes. Uses local storage to save the chosen scheme across reloads.
- <b>useDarkMode:</b> Similar to usePreferredScheme but allows setting the user scheme manually and automatically
updates it when the preferred scheme changes. Uses local storage to save the chosen scheme across reloads.
4 changes: 2 additions & 2 deletions examples/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import DemoUsePreferredScheme from "./components/demoUsePreferredScheme";
import DemoUseDarkMode from "./components/demoUseDarkMode";

const App: React.FC = () => {
return (
<DemoUsePreferredScheme/>
<DemoUseDarkMode/>
);
};

Expand Down
19 changes: 19 additions & 0 deletions examples/components/demoUseDarkMode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import useDarkMode from "../../src/hooks/useDarkMode";

const DemoUseDarkMode = () => {
const { theme, setTheme, toggleTheme } = useDarkMode();
return (
<div style={{
backgroundColor: theme === "dark" ? "black" : "white",
color: theme === "dark" ? "white" : "black",
}}>
I change my color. I'm a chameleon.
<button onClick={() => setTheme("light")}>Set to light</button>
<button onClick={() => setTheme("dark")}>Set to dark</button>
<button onClick={() => toggleTheme()}>Toggle theme</button>
</div>
);
};

export default DemoUseDarkMode;
7 changes: 4 additions & 3 deletions src/hooks/useClickOutside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { useEffect, useRef } from "react";
export type ClickOutsideEvent = "click" | "dblclick" | "mousedown" | "mouseup" | "pointerdown" | "pointerup";

export interface ClickOutsideOptions {
/** The type of event to use for detecting clicks. Default is "click". */
eventType?: ClickOutsideEvent,
/** If set to true, a click on a child of the target element will count as a click on the
target element and not trigger the callback. */
includeChildren?: boolean,
}

Expand All @@ -12,9 +15,7 @@ export interface ClickOutsideOptions {
* @param callback - A function to execute when the user click's outside the specified element. As a parameter it takes
* an event which is either a MouseEvent or PointerEvent depending on the {@link eventType} specified. The e.target
* property will contain the element that was clicked.
* @param eventType - The type of event to use for detecting clicks. Default is "click".
* @param includeChildren - If set to true, a click on a child of the target element will count as a click on the
* target element and not trigger the callback.
* @param options - Allows configuring how the hook works.
* @return A reference you need to attach to the element to target. Can be used as a normal ref as well.
* ```tsx
* const DemoUseClickOutside = () => {
Expand Down
78 changes: 78 additions & 0 deletions src/hooks/useDarkMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect, useState } from "react";
import { usePreferredScheme } from "../index";

export interface DarkModeState {
/** Sets the current theme to the value passed in as an argument. */
setTheme: (selectedTheme: "light" | "dark") => void,
/** Changes theme to dark if it is currently light and vice versa. */
toggleTheme: () => void,
/** The currently selected theme. */
theme: "light" | "dark",
}

/**
*
* @param defaultTheme - Set a default theme. This overrides the user's preferred scheme but is overridden by the value
* saved in local storage if the hook is set to use local storage.
* @param updateOnPreferredSchemeChange - If set to true, updates the theme when the user's preferred scheme changes
* @param persistStateInLocalStorage - If set to true, uses the browsers local storage to persist the selected theme
* between page reloads.
* @return DarkModeState
*
* @example
* ```tsx
* const DemoUseDarkMode = () => {
* const { theme, setTheme, toggleTheme } = useDarkMode();
* return (
* <div style={{
* backgroundColor: theme === "dark" ? "black" : "white",
* color: theme === "dark" ? "white" : "black",
* }}>
* I change my color. I'm a chameleon.
* <button onClick={() => setTheme("light")}>Set to light</button>
* <button onClick={() => setTheme("dark")}>Set to dark</button>
* <button onClick={() => toggleTheme()}>Toggle theme</button>
* </div>
* );
* };
* ```
*/
function useDarkMode({
defaultTheme,
updateOnPreferredSchemeChange = true,
persistStateInLocalStorage = true,
}: {
defaultTheme?: "light" | "dark",
updateOnPreferredSchemeChange?: boolean,
persistStateInLocalStorage?: boolean,
} = {}): DarkModeState {
const preferredScheme = usePreferredScheme();
const [theme, setTheme] = useState((persistStateInLocalStorage
? localStorage.getItem("anzol-dark-mode-hook") ?? defaultTheme ?? preferredScheme
: defaultTheme ?? preferredScheme) as "light" | "dark",
);

useEffect(() => {
if (updateOnPreferredSchemeChange) {
const matchPreferredScheme = (e: MediaQueryListEvent) => setTheme(e.matches ? "dark" : "light");
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", matchPreferredScheme);
return () => window
.matchMedia("(prefers-color-scheme: dark)")
.removeEventListener("change", matchPreferredScheme);
}
}, [updateOnPreferredSchemeChange, preferredScheme]);

useEffect(() => {
if (persistStateInLocalStorage) localStorage.setItem("anzol-dark-mode-hook", theme);
}, [theme]);

return {
setTheme: (selectedTheme: "light" | "dark") => setTheme(selectedTheme),
toggleTheme: () => { setTheme(prevState => prevState === "light" ? "dark" : "light"); },
theme,
};
}

export default useDarkMode;
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ export {

export {
default as usePreferredScheme,
} from "./hooks/usePreferredScheme";
} from "./hooks/usePreferredScheme";

export {
default as useDarkMode,
type DarkModeState,
} from "./hooks/useDarkMode";

0 comments on commit 86bfac7

Please sign in to comment.