From 46e3730c399cb4c98dc9556705904cddb86299d5 Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 19:51:13 +0700 Subject: [PATCH 1/6] add use color mode --- src/hooks/useColorMode/useColorMode.demo.tsx | 19 ++++ src/hooks/useColorMode/useColorMode.ts | 104 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/hooks/useColorMode/useColorMode.demo.tsx create mode 100644 src/hooks/useColorMode/useColorMode.ts diff --git a/src/hooks/useColorMode/useColorMode.demo.tsx b/src/hooks/useColorMode/useColorMode.demo.tsx new file mode 100644 index 00000000..416ff9e4 --- /dev/null +++ b/src/hooks/useColorMode/useColorMode.demo.tsx @@ -0,0 +1,19 @@ +import { useColorMode } from './useColorMode'; + +const Demo = () => { + const colorMode = useColorMode(); + + const toggleColorMode = () => + colorMode.set( + colorMode.value === 'dark' ? 'light' : colorMode.value === 'light' ? 'auto' : 'dark' + ); + + return ( + <> + +

Click to change the color mode

+ + ); +}; + +export default Demo; diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts new file mode 100644 index 00000000..6fedcc6c --- /dev/null +++ b/src/hooks/useColorMode/useColorMode.ts @@ -0,0 +1,104 @@ +import { useEffect, useState } from 'react'; + +const CSS_DISABLE_TRANS = ` + *, *::before, *::after { + -webkit-transition: none !important; + -moz-transition: none !important; + -o-transition: none !important; + -ms-transition: none !important; + transition: none !important; + } +`; + +export type BasicColorMode = 'auto' | 'dark' | 'light'; + +/** The use color mode options */ +export interface UseColorModeOptions { + attribute?: string; + disableTransition?: boolean; + emitAuto?: boolean; + initialValue?: BasicColorMode | MODE; + modes?: Record; + selector?: string; + storageKey?: string | null; + onChanged?: ( + mode: BasicColorMode | MODE, + defaultHandler: (mode: BasicColorMode | MODE) => void + ) => void; +} + +/** The use color mode return type */ +export type UseColorModeReturn = BasicColorMode | T; + +/** + * @name useColorMode + * @description - Hook for recording the timestamp of the last change + * @category Browser + * + * @param {UseColorModeOptions} options The options for configuring color mode behavior. + * @returns {UseColorModeReturn} The current color mode value. + */ +export const useColorMode = ( + options?: UseColorModeOptions +) => { + const { + selector = 'html', + attribute = 'class', + disableTransition = true, + initialValue = 'auto', + storageKey = 'reactuse-color-scheme', + modes = {}, + onChanged + } = options ?? {}; + + const [value, setValue] = useState( + storageKey + ? (localStorage.getItem(storageKey) as BasicColorMode | MODE | null) || initialValue + : initialValue + ); + + const updateHTMLAttrs = (mode: string) => { + const element = document.querySelector(selector); + if (!element) return; + + const modeClasses = [...Object.values(modes), 'auto', 'dark', 'light'] as ( + | BasicColorMode + | MODE + )[]; + + if (attribute === 'class') { + element.classList.remove(...modeClasses); + element.classList.add(mode); + } else { + element.setAttribute(attribute, mode); + } + + if (disableTransition) { + const style = document.createElement('style'); + style.textContent = CSS_DISABLE_TRANS; + + document.head.appendChild(style); + + (() => getComputedStyle(style).opacity)(); + + document.head.removeChild(style); + } + }; + + useEffect(() => { + const mode = + value !== 'auto' + ? value + : window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; + + if (storageKey) localStorage.setItem(storageKey, value); + + const defaultOnChanged = (mode: BasicColorMode | MODE) => updateHTMLAttrs(mode); + + onChanged ? onChanged(mode, defaultOnChanged) : defaultOnChanged(mode); + }, [value]); + + return { value, set: setValue }; +}; From c151f38e066c4b0123add68d0e1f25c128cc9411 Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 20:03:24 +0700 Subject: [PATCH 2/6] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useColorMode/useColorMode.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts index 6fedcc6c..75c724b5 100644 --- a/src/hooks/useColorMode/useColorMode.ts +++ b/src/hooks/useColorMode/useColorMode.ts @@ -21,6 +21,7 @@ export interface UseColorModeOptions { modes?: Record; selector?: string; storageKey?: string | null; + storageType?: 'localStorage' | 'sessionStorage'; // New option for storage type onChanged?: ( mode: BasicColorMode | MODE, defaultHandler: (mode: BasicColorMode | MODE) => void @@ -48,12 +49,15 @@ export const useColorMode = ( initialValue = 'auto', storageKey = 'reactuse-color-scheme', modes = {}, + storageType = 'localStorage', onChanged } = options ?? {}; + const storage = storageType === 'sessionStorage' ? sessionStorage : localStorage; + const [value, setValue] = useState( storageKey - ? (localStorage.getItem(storageKey) as BasicColorMode | MODE | null) || initialValue + ? (storage.getItem(storageKey) as BasicColorMode | MODE | null) || initialValue : initialValue ); @@ -93,12 +97,12 @@ export const useColorMode = ( ? 'dark' : 'light'; - if (storageKey) localStorage.setItem(storageKey, value); + if (storageKey) storage.setItem(storageKey, value); const defaultOnChanged = (mode: BasicColorMode | MODE) => updateHTMLAttrs(mode); onChanged ? onChanged(mode, defaultOnChanged) : defaultOnChanged(mode); - }, [value]); + }, [value, storage, storageKey, onChanged]); return { value, set: setValue }; }; From 38b6e9c7c922e9c8380f77e4daf82492ecf7525b Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 20:07:07 +0700 Subject: [PATCH 3/6] =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB?= =?UTF-8?q?=20storageType=20=D0=BD=D0=B0=20storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useColorMode/useColorMode.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts index 75c724b5..44a4cd4d 100644 --- a/src/hooks/useColorMode/useColorMode.ts +++ b/src/hooks/useColorMode/useColorMode.ts @@ -20,8 +20,8 @@ export interface UseColorModeOptions { initialValue?: BasicColorMode | MODE; modes?: Record; selector?: string; + storage?: 'localStorage' | 'sessionStorage'; storageKey?: string | null; - storageType?: 'localStorage' | 'sessionStorage'; // New option for storage type onChanged?: ( mode: BasicColorMode | MODE, defaultHandler: (mode: BasicColorMode | MODE) => void @@ -49,11 +49,11 @@ export const useColorMode = ( initialValue = 'auto', storageKey = 'reactuse-color-scheme', modes = {}, - storageType = 'localStorage', + storage: _storage = 'localStorage', onChanged } = options ?? {}; - const storage = storageType === 'sessionStorage' ? sessionStorage : localStorage; + const storage = _storage === 'sessionStorage' ? sessionStorage : localStorage; const [value, setValue] = useState( storageKey From 73604e05951fd28b7ee0b9efa7803819ce6e8b87 Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 20:12:16 +0700 Subject: [PATCH 4/6] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20auto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useColorMode/useColorMode.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts index 44a4cd4d..7b89bd48 100644 --- a/src/hooks/useColorMode/useColorMode.ts +++ b/src/hooks/useColorMode/useColorMode.ts @@ -89,13 +89,10 @@ export const useColorMode = ( } }; + const auto = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + useEffect(() => { - const mode = - value !== 'auto' - ? value - : window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light'; + const mode = value !== 'auto' ? value : auto; if (storageKey) storage.setItem(storageKey, value); @@ -104,5 +101,5 @@ export const useColorMode = ( onChanged ? onChanged(mode, defaultOnChanged) : defaultOnChanged(mode); }, [value, storage, storageKey, onChanged]); - return { value, set: setValue }; + return { value, auto, set: setValue }; }; From 1a9e6c0c9fdf2117d81f50e30033018913d8aa3a Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 20:15:40 +0700 Subject: [PATCH 5/6] add hook to index ts --- src/hooks/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a98f8586..2b8c6c5e 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,6 +6,7 @@ export * from './useBreakpoints/useBreakpoints'; export * from './useBrowserLanguage/useBrowserLanguage'; export * from './useClickOutside/useClickOutside'; export * from './useClipboard/useClipboard'; +export * from './useColorMode/useColorMode'; export * from './useCounter/useCounter'; export * from './useCssVar/useCssVar'; export * from './useDebounceCallback/useDebounceCallback'; From d3faf2e746ae5bbffd7e024bec13a2123f2a7209 Mon Sep 17 00:00:00 2001 From: VLADISLAW9 Date: Sat, 25 Jan 2025 20:39:23 +0700 Subject: [PATCH 6/6] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20js=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useColorMode/useColorMode.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/hooks/useColorMode/useColorMode.ts b/src/hooks/useColorMode/useColorMode.ts index 7b89bd48..6710b2ce 100644 --- a/src/hooks/useColorMode/useColorMode.ts +++ b/src/hooks/useColorMode/useColorMode.ts @@ -14,14 +14,21 @@ export type BasicColorMode = 'auto' | 'dark' | 'light'; /** The use color mode options */ export interface UseColorModeOptions { + /** HTML attribute applying the target element */ attribute?: string; + /** Disable transition on switch */ disableTransition?: boolean; - emitAuto?: boolean; + /** The initial color mode */ initialValue?: BasicColorMode | MODE; + /** Prefix when adding value to the attribute */ modes?: Record; + /** CSS Selector for the target element applying to */ selector?: string; + /** Storage object, can be localStorage or sessionStorage */ storage?: 'localStorage' | 'sessionStorage'; + /** Key to persist the data into localStorage/sessionStorage. Pass `null` to disable persistence */ storageKey?: string | null; + /**A custom handler for handle the updates. When specified, the default behavior will be overridden */ onChanged?: ( mode: BasicColorMode | MODE, defaultHandler: (mode: BasicColorMode | MODE) => void @@ -29,15 +36,22 @@ export interface UseColorModeOptions { } /** The use color mode return type */ -export type UseColorModeReturn = BasicColorMode | T; +export interface UseColorModeReturn { + /** The value of the auto mode */ + auto: BasicColorMode; + /** The current color mode value */ + value: BasicColorMode | MODE; + /** Function to set the color mode */ + set: (mode: BasicColorMode | MODE) => void; +} /** * @name useColorMode - * @description - Hook for recording the timestamp of the last change + * @description - Hook for get and set color mode (dark / light / customs) with auto data persistence. * @category Browser * - * @param {UseColorModeOptions} options The options for configuring color mode behavior. - * @returns {UseColorModeReturn} The current color mode value. + * @param {UseColorModeOptions} options The options for configuring color mode hook. + * @returns {UseColorModeReturn} The object containing the current color mode and a function to set the color mode. */ export const useColorMode = ( options?: UseColorModeOptions