diff --git a/README.md b/README.md index db73d73..909d3cd 100644 --- a/README.md +++ b/README.md @@ -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 - useFetch: 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. @@ -40,15 +46,5 @@ Anzol is built alongside automated tests to ensure quality. - useClickOutside: 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. - usePreferredScheme: 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. -- useDarkMode: 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. \ No newline at end of file +- useDarkMode: 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. \ No newline at end of file diff --git a/examples/App.tsx b/examples/App.tsx index 2722889..61b9029 100644 --- a/examples/App.tsx +++ b/examples/App.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import DemoUsePreferredScheme from "./components/demoUsePreferredScheme"; +import DemoUseDarkMode from "./components/demoUseDarkMode"; const App: React.FC = () => { return ( - + ); }; diff --git a/examples/components/demoUseDarkMode.tsx b/examples/components/demoUseDarkMode.tsx new file mode 100644 index 0000000..f03e481 --- /dev/null +++ b/examples/components/demoUseDarkMode.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import useDarkMode from "../../src/hooks/useDarkMode"; + +const DemoUseDarkMode = () => { + const { theme, setTheme, toggleTheme } = useDarkMode(); + return ( +
+ I change my color. I'm a chameleon. + + + +
+ ); +}; + +export default DemoUseDarkMode; diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts index b5a4ce5..df77d86 100644 --- a/src/hooks/useClickOutside.ts +++ b/src/hooks/useClickOutside.ts @@ -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, } @@ -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 = () => { diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts new file mode 100644 index 0000000..d62d0a1 --- /dev/null +++ b/src/hooks/useDarkMode.ts @@ -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 ( + *
+ * I change my color. I'm a chameleon. + * + * + * + *
+ * ); + * }; + * ``` + */ +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; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 4d154ba..fd8832d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,4 +51,9 @@ export { export { default as usePreferredScheme, -} from "./hooks/usePreferredScheme"; \ No newline at end of file +} from "./hooks/usePreferredScheme"; + +export { + default as useDarkMode, + type DarkModeState, +} from "./hooks/useDarkMode"; \ No newline at end of file