From b70cb757cc91417aa45b9ddbfe03e30dee54a8c8 Mon Sep 17 00:00:00 2001 From: ian Date: Thu, 1 Aug 2024 17:51:26 +0800 Subject: [PATCH 01/15] feat: useTheme --- .../hooks/src/useTheme/__test__/index.test.ts | 35 +++++++++++++ packages/hooks/src/useTheme/demo/demo1.tsx | 45 ++++++++++++++++ packages/hooks/src/useTheme/index.en-US.md | 28 ++++++++++ packages/hooks/src/useTheme/index.ts | 51 +++++++++++++++++++ packages/hooks/src/useTheme/index.zh-CN.md | 28 ++++++++++ 5 files changed, 187 insertions(+) create mode 100644 packages/hooks/src/useTheme/__test__/index.test.ts create mode 100644 packages/hooks/src/useTheme/demo/demo1.tsx create mode 100644 packages/hooks/src/useTheme/index.en-US.md create mode 100644 packages/hooks/src/useTheme/index.ts create mode 100644 packages/hooks/src/useTheme/index.zh-CN.md diff --git a/packages/hooks/src/useTheme/__test__/index.test.ts b/packages/hooks/src/useTheme/__test__/index.test.ts new file mode 100644 index 0000000000..504c7c2e21 --- /dev/null +++ b/packages/hooks/src/useTheme/__test__/index.test.ts @@ -0,0 +1,35 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { useTheme } from '../index'; + +describe('useTheme', () => { + it('init themeMode', () => { + const { + result: { current }, + } = renderHook(() => useTheme()); + expect(current.themeMode).toBe('system'); + }); + + it('setThemeMode', () => { + const { + result: { current }, + } = renderHook(() => useTheme()); + + act(() => { + current.setThemeMode('light'); + }); + expect(current.theme).toBe('light'); + expect(current.themeMode).toBe('light'); + + act(() => { + current.setThemeMode('dark'); + }); + expect(current.theme).toBe('dark'); + expect(current.themeMode).toBe('dark'); + + act(() => { + current.setThemeMode('system'); + }); + expect(current.themeMode).toBe('system'); + }); +}); diff --git a/packages/hooks/src/useTheme/demo/demo1.tsx b/packages/hooks/src/useTheme/demo/demo1.tsx new file mode 100644 index 0000000000..d525c0ed74 --- /dev/null +++ b/packages/hooks/src/useTheme/demo/demo1.tsx @@ -0,0 +1,45 @@ +/** + * title: Basic usage + * desc: The 'theme' is the system display theme ("light" or "dark"), the 'themeMode' can set 'theme' to "light" or "dark" or follow the system setting. + * + * title.zh-CN: 基础用法 + * desc.zh-CN: 'theme' 为系统当前显示主题("light" 或 "dark"),'themeMode' 为当前主题设置("light" 或 "dark" 或 "system")。 + */ + +import { useTheme } from 'ahooks'; +import React from 'react'; + +export default () => { + const { theme, themeMode, setThemeMode } = useTheme(); + + return ( + <> +
theme: {theme}
+
themeMode: {themeMode}
+ + + + + ); +}; diff --git a/packages/hooks/src/useTheme/index.en-US.md b/packages/hooks/src/useTheme/index.en-US.md new file mode 100644 index 0000000000..09e4d15fed --- /dev/null +++ b/packages/hooks/src/useTheme/index.en-US.md @@ -0,0 +1,28 @@ +--- +nav: + path: /hooks +--- + +# useTheme + +This hook is used to get and set the theme, and store the themeMode into localStorage. + +## Examples + +### Default usage + + + +## API + +```typescript +const { theme, themeMode, setThemeMode } = useTheme(); +``` + +### Result + +| Property | Description | Type | Default | +| ------------ | --------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- | +| theme | current display theme | `"light" \| "dark"` | if themeMode is "system" then equals to system setting,otherwise equals to themeMode | +| themeMode | selected theme mode | `"light" \| "dark" \| "system"` | equals to localStorage "themeMode", otherwise equals to "system" | +| setThemeMode | select theme mode | `(mode: "light" \| "dark" \| "system") => void` | | diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts new file mode 100644 index 0000000000..6c9afab1c6 --- /dev/null +++ b/packages/hooks/src/useTheme/index.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; + +const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); + +function getTheme() { + const [theme, setTheme] = useState<'light' | 'dark'>(() => { + return matchMedia.matches ? 'dark' : 'light'; + }); + + useEffect(() => { + // 监听系统颜色切换 + const listener = (event: { matches: boolean }) => { + if (event.matches) { + setTheme('dark'); + } else { + setTheme('light'); + } + }; + + matchMedia.addEventListener('change', listener); + + return () => { + matchMedia.removeEventListener('change', listener); + }; + }, []); + return theme; +} + +export type ThemeModeType = 'light' | 'dark' | 'system'; + +export function useTheme() { + const [themeMode, setThemeMode] = useState(() => { + const preferredThemeMode = localStorage.getItem('themeMode') as ThemeModeType | null; + return preferredThemeMode ? preferredThemeMode : 'system'; + }); + + const setThemeModeWithLocalStorage = (themeMode: ThemeModeType) => { + localStorage.setItem('themeMode', themeMode); + setThemeMode(themeMode); + }; + + const currentTheme = getTheme(); + + const theme = themeMode === 'system' ? currentTheme : themeMode; + + return { + theme, + themeMode, + setThemeMode: setThemeModeWithLocalStorage, + }; +} diff --git a/packages/hooks/src/useTheme/index.zh-CN.md b/packages/hooks/src/useTheme/index.zh-CN.md new file mode 100644 index 0000000000..6093f67819 --- /dev/null +++ b/packages/hooks/src/useTheme/index.zh-CN.md @@ -0,0 +1,28 @@ +--- +nav: + path: /hooks +--- + +# useTheme + +获取并设置主题的 Hook,并将 "themeMode" 存储在 localStorage 中。 + +## 代码演示 + +### 基础用法 + + + +## API + +```typescript +const { theme, themeMode, setThemeMode } = useTheme(); +``` + +### 返回值 + +| 值 | 说明 | 类型 | 默认值 | +| ------------ | -------------- | ----------------------------------------------- | ---------------------------------------------------------------------- | +| theme | 当前显示的主题 | `"light" \| "dark"` | 若 themeMode 为 "system" 则为系统当前使用主题,否则与 themeMode 值相同 | +| themeMode | 选择的主题模式 | `"light" \| "dark" \| "system"` | 等于 localStorage "themeMode" 字段的值,否则为 "system" | +| setThemeMode | 选择主题模式 | `(mode: "light" \| "dark" \| "system") => void` | | From e17317c3eb2e5a6cea55a6cd72ba3ef332838556 Mon Sep 17 00:00:00 2001 From: ian Date: Fri, 2 Aug 2024 03:16:33 +0800 Subject: [PATCH 02/15] test: useTheme --- .../hooks/src/useTheme/__test__/index.test.ts | 60 +++++++++++-------- packages/hooks/src/useTheme/index.ts | 10 ++-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/hooks/src/useTheme/__test__/index.test.ts b/packages/hooks/src/useTheme/__test__/index.test.ts index 504c7c2e21..a06e76baab 100644 --- a/packages/hooks/src/useTheme/__test__/index.test.ts +++ b/packages/hooks/src/useTheme/__test__/index.test.ts @@ -1,35 +1,43 @@ -import { renderHook } from '@testing-library/react'; -import { act } from 'react-dom/test-utils'; +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +import { act, renderHook } from '@testing-library/react'; import { useTheme } from '../index'; describe('useTheme', () => { - it('init themeMode', () => { - const { - result: { current }, - } = renderHook(() => useTheme()); - expect(current.themeMode).toBe('system'); + test('themeMode init', () => { + const { result } = renderHook(useTheme); + expect(result.current.themeMode).toBe('system'); }); - it('setThemeMode', () => { - const { - result: { current }, - } = renderHook(() => useTheme()); - - act(() => { - current.setThemeMode('light'); - }); - expect(current.theme).toBe('light'); - expect(current.themeMode).toBe('light'); + test('setThemeMode light', () => { + const { result } = renderHook(useTheme); + act(() => result.current.setThemeMode('light')); + expect(result.current.theme).toBe('light'); + expect(result.current.themeMode).toBe('light'); + }); - act(() => { - current.setThemeMode('dark'); - }); - expect(current.theme).toBe('dark'); - expect(current.themeMode).toBe('dark'); + test('setThemeMode dark', () => { + const { result } = renderHook(useTheme); + act(() => result.current.setThemeMode('dark')); + expect(result.current.theme).toBe('dark'); + expect(result.current.themeMode).toBe('dark'); + }); - act(() => { - current.setThemeMode('system'); - }); - expect(current.themeMode).toBe('system'); + test('setThemeMode system', () => { + const { result } = renderHook(useTheme); + act(() => result.current.setThemeMode('system')); + expect(result.current.themeMode).toBe('system'); }); }); diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index 6c9afab1c6..dfb33ddfa9 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); -function getTheme() { +function useCurrentTheme() { const [theme, setTheme] = useState<'light' | 'dark'>(() => { return matchMedia.matches ? 'dark' : 'light'; }); @@ -34,12 +34,12 @@ export function useTheme() { return preferredThemeMode ? preferredThemeMode : 'system'; }); - const setThemeModeWithLocalStorage = (themeMode: ThemeModeType) => { - localStorage.setItem('themeMode', themeMode); - setThemeMode(themeMode); + const setThemeModeWithLocalStorage = (mode: ThemeModeType) => { + setThemeMode(mode); + localStorage.setItem('themeMode', mode); }; - const currentTheme = getTheme(); + const currentTheme = useCurrentTheme(); const theme = themeMode === 'system' ? currentTheme : themeMode; From 3c5a026edcc3038ee8d6c4e40694e13fc8dbbb88 Mon Sep 17 00:00:00 2001 From: ian Date: Mon, 5 Aug 2024 11:10:27 +0800 Subject: [PATCH 03/15] feat: useTheme add localStorageKey --- packages/hooks/src/useTheme/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index dfb33ddfa9..af022ad4a7 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import useMemoizedFn from '../useMemoizedFn'; const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); @@ -28,7 +29,11 @@ function useCurrentTheme() { export type ThemeModeType = 'light' | 'dark' | 'system'; -export function useTheme() { +type PropsType = { + localStorageKey?: string; +}; + +export function useTheme(props: PropsType) { const [themeMode, setThemeMode] = useState(() => { const preferredThemeMode = localStorage.getItem('themeMode') as ThemeModeType | null; return preferredThemeMode ? preferredThemeMode : 'system'; @@ -36,7 +41,7 @@ export function useTheme() { const setThemeModeWithLocalStorage = (mode: ThemeModeType) => { setThemeMode(mode); - localStorage.setItem('themeMode', mode); + props.localStorageKey?.length && localStorage.setItem(props.localStorageKey, mode); }; const currentTheme = useCurrentTheme(); @@ -46,6 +51,6 @@ export function useTheme() { return { theme, themeMode, - setThemeMode: setThemeModeWithLocalStorage, + setThemeMode: useMemoizedFn(setThemeModeWithLocalStorage), }; } From 1322db350b41f739ef91ecc6cdb1325e1ca0ca73 Mon Sep 17 00:00:00 2001 From: ian Date: Mon, 5 Aug 2024 11:14:10 +0800 Subject: [PATCH 04/15] docs: useTheme add localStorageKey --- packages/hooks/src/useTheme/demo/demo1.tsx | 4 +++- packages/hooks/src/useTheme/index.en-US.md | 4 +++- packages/hooks/src/useTheme/index.zh-CN.md | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/hooks/src/useTheme/demo/demo1.tsx b/packages/hooks/src/useTheme/demo/demo1.tsx index d525c0ed74..8dd7708cc1 100644 --- a/packages/hooks/src/useTheme/demo/demo1.tsx +++ b/packages/hooks/src/useTheme/demo/demo1.tsx @@ -10,7 +10,9 @@ import { useTheme } from 'ahooks'; import React from 'react'; export default () => { - const { theme, themeMode, setThemeMode } = useTheme(); + const { theme, themeMode, setThemeMode } = useTheme({ + localStorageKey: 'themeMode', + }); return ( <> diff --git a/packages/hooks/src/useTheme/index.en-US.md b/packages/hooks/src/useTheme/index.en-US.md index 09e4d15fed..4cd1694534 100644 --- a/packages/hooks/src/useTheme/index.en-US.md +++ b/packages/hooks/src/useTheme/index.en-US.md @@ -16,7 +16,9 @@ This hook is used to get and set the theme, and store the themeMode into localSt ## API ```typescript -const { theme, themeMode, setThemeMode } = useTheme(); +const { theme, themeMode, setThemeMode } = useTheme({ + localStorageKey?: string; +}); ``` ### Result diff --git a/packages/hooks/src/useTheme/index.zh-CN.md b/packages/hooks/src/useTheme/index.zh-CN.md index 6093f67819..4461a00631 100644 --- a/packages/hooks/src/useTheme/index.zh-CN.md +++ b/packages/hooks/src/useTheme/index.zh-CN.md @@ -16,7 +16,9 @@ nav: ## API ```typescript -const { theme, themeMode, setThemeMode } = useTheme(); +const { theme, themeMode, setThemeMode } = useTheme({ + localStorageKey?: string; +}); ``` ### 返回值 From 15b2681fc1c60ecb6d7e007579873e49384fd1f7 Mon Sep 17 00:00:00 2001 From: Yiheng Date: Mon, 5 Aug 2024 11:30:47 +0800 Subject: [PATCH 05/15] Update packages/hooks/src/useTheme/index.en-US.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 云泥 <1656081615@qq.com> --- packages/hooks/src/useTheme/index.en-US.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useTheme/index.en-US.md b/packages/hooks/src/useTheme/index.en-US.md index 4cd1694534..8907208a57 100644 --- a/packages/hooks/src/useTheme/index.en-US.md +++ b/packages/hooks/src/useTheme/index.en-US.md @@ -5,7 +5,7 @@ nav: # useTheme -This hook is used to get and set the theme, and store the themeMode into localStorage. +This hook is used to get and set the theme, and store the `themeMode` into `localStorage`. ## Examples From 61a8165bf69e66ff8bce5632965105a1ff5e462c Mon Sep 17 00:00:00 2001 From: Yiheng Date: Mon, 5 Aug 2024 11:34:24 +0800 Subject: [PATCH 06/15] Update packages/hooks/src/useTheme/index.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add listener type Co-authored-by: 云泥 <1656081615@qq.com> --- packages/hooks/src/useTheme/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index af022ad4a7..5695818b60 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -11,6 +11,7 @@ function useCurrentTheme() { useEffect(() => { // 监听系统颜色切换 const listener = (event: { matches: boolean }) => { + const listener: MediaQueryList['onchange'] = (event) => { if (event.matches) { setTheme('dark'); } else { From d3c8c2c1554adf1eb3d7f1e4b186eff48e0eb729 Mon Sep 17 00:00:00 2001 From: ian Date: Mon, 5 Aug 2024 11:22:55 +0800 Subject: [PATCH 07/15] fix: missing localStorage.getItem(localStorageKey) --- packages/hooks/src/useTheme/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index 5695818b60..201f7ca35e 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -35,14 +35,16 @@ type PropsType = { }; export function useTheme(props: PropsType) { + const { localStorageKey } = props; const [themeMode, setThemeMode] = useState(() => { - const preferredThemeMode = localStorage.getItem('themeMode') as ThemeModeType | null; + const preferredThemeMode = + localStorageKey?.length && (localStorage.getItem(localStorageKey) as ThemeModeType | null); return preferredThemeMode ? preferredThemeMode : 'system'; }); const setThemeModeWithLocalStorage = (mode: ThemeModeType) => { setThemeMode(mode); - props.localStorageKey?.length && localStorage.setItem(props.localStorageKey, mode); + localStorageKey?.length && localStorage.setItem(localStorageKey, mode); }; const currentTheme = useCurrentTheme(); From fc98b1288a50f73770637423fc0f635b9bff6598 Mon Sep 17 00:00:00 2001 From: ian Date: Mon, 5 Aug 2024 11:51:13 +0800 Subject: [PATCH 08/15] docs: useTheme add Params --- packages/hooks/src/useTheme/index.en-US.md | 6 ++++++ packages/hooks/src/useTheme/index.ts | 1 - packages/hooks/src/useTheme/index.zh-CN.md | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/hooks/src/useTheme/index.en-US.md b/packages/hooks/src/useTheme/index.en-US.md index 8907208a57..bb75d31bdf 100644 --- a/packages/hooks/src/useTheme/index.en-US.md +++ b/packages/hooks/src/useTheme/index.en-US.md @@ -21,6 +21,12 @@ const { theme, themeMode, setThemeMode } = useTheme({ }); ``` +### Params + +| Property | Description | Type | Default | +| --------------- | ----------------------------------------------------- | -------- | --------- | +| localStorageKey | The key in localStorage to store selected theme mode. | `string` | undefined | + ### Result | Property | Description | Type | Default | diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index 201f7ca35e..068e6c9833 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -10,7 +10,6 @@ function useCurrentTheme() { useEffect(() => { // 监听系统颜色切换 - const listener = (event: { matches: boolean }) => { const listener: MediaQueryList['onchange'] = (event) => { if (event.matches) { setTheme('dark'); diff --git a/packages/hooks/src/useTheme/index.zh-CN.md b/packages/hooks/src/useTheme/index.zh-CN.md index 4461a00631..deecb2a6f5 100644 --- a/packages/hooks/src/useTheme/index.zh-CN.md +++ b/packages/hooks/src/useTheme/index.zh-CN.md @@ -5,7 +5,7 @@ nav: # useTheme -获取并设置主题的 Hook,并将 "themeMode" 存储在 localStorage 中。 +获取并设置当前主题,并将 `themeMode` 存储在 `localStorage` 中。 ## 代码演示 @@ -21,6 +21,12 @@ const { theme, themeMode, setThemeMode } = useTheme({ }); ``` +### 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| --------------- | ------------------------------------ | -------- | --------- | +| localStorageKey | localStorage 中用于存放主题模式的键. | `string` | undefined | + ### 返回值 | 值 | 说明 | 类型 | 默认值 | From a78281938fa815b6c5104ee32ded6f2face5be5a Mon Sep 17 00:00:00 2001 From: ian Date: Mon, 5 Aug 2024 11:59:09 +0800 Subject: [PATCH 09/15] feat: useTheme add default props --- packages/hooks/src/useTheme/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index 068e6c9833..eafe19efbd 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -33,7 +33,7 @@ type PropsType = { localStorageKey?: string; }; -export function useTheme(props: PropsType) { +export function useTheme(props: PropsType = {}) { const { localStorageKey } = props; const [themeMode, setThemeMode] = useState(() => { const preferredThemeMode = From 737957f14f6a89e924a9502b0a03509232c05b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E6=B3=A5?= <1656081615@qq.com> Date: Wed, 7 Aug 2024 14:00:48 +0800 Subject: [PATCH 10/15] Update packages/hooks/src/useTheme/index.zh-CN.md --- packages/hooks/src/useTheme/index.zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useTheme/index.zh-CN.md b/packages/hooks/src/useTheme/index.zh-CN.md index deecb2a6f5..3f5687911f 100644 --- a/packages/hooks/src/useTheme/index.zh-CN.md +++ b/packages/hooks/src/useTheme/index.zh-CN.md @@ -25,7 +25,7 @@ const { theme, themeMode, setThemeMode } = useTheme({ | 参数 | 说明 | 类型 | 默认值 | | --------------- | ------------------------------------ | -------- | --------- | -| localStorageKey | localStorage 中用于存放主题模式的键. | `string` | undefined | +| localStorageKey | localStorage 中用于存放主题模式的键. | `string` | `undefined` | ### 返回值 From cca8d27870c8daba81b8993adfa3f0f2f3ed7beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E6=B3=A5?= <1656081615@qq.com> Date: Wed, 7 Aug 2024 14:01:25 +0800 Subject: [PATCH 11/15] Update packages/hooks/src/useTheme/index.zh-CN.md --- packages/hooks/src/useTheme/index.zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useTheme/index.zh-CN.md b/packages/hooks/src/useTheme/index.zh-CN.md index 3f5687911f..37c90800d9 100644 --- a/packages/hooks/src/useTheme/index.zh-CN.md +++ b/packages/hooks/src/useTheme/index.zh-CN.md @@ -25,7 +25,7 @@ const { theme, themeMode, setThemeMode } = useTheme({ | 参数 | 说明 | 类型 | 默认值 | | --------------- | ------------------------------------ | -------- | --------- | -| localStorageKey | localStorage 中用于存放主题模式的键. | `string` | `undefined` | +| localStorageKey | localStorage 中用于存放主题模式的键 | `string` | `undefined` | ### 返回值 From 8b97c54752ad0a23198926a161b15d9e18a0f220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E6=B3=A5?= <1656081615@qq.com> Date: Wed, 7 Aug 2024 14:02:26 +0800 Subject: [PATCH 12/15] Update packages/hooks/src/useTheme/index.en-US.md --- packages/hooks/src/useTheme/index.en-US.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useTheme/index.en-US.md b/packages/hooks/src/useTheme/index.en-US.md index bb75d31bdf..f5bdb1c2fe 100644 --- a/packages/hooks/src/useTheme/index.en-US.md +++ b/packages/hooks/src/useTheme/index.en-US.md @@ -25,7 +25,7 @@ const { theme, themeMode, setThemeMode } = useTheme({ | Property | Description | Type | Default | | --------------- | ----------------------------------------------------- | -------- | --------- | -| localStorageKey | The key in localStorage to store selected theme mode. | `string` | undefined | +| localStorageKey | The key in localStorage to store selected theme mode | `string` | `undefined` | ### Result From 87f8076d338d4870127cd50aa621cf7868f15f74 Mon Sep 17 00:00:00 2001 From: liuyib <1656081615@qq.com> Date: Thu, 8 Aug 2024 14:48:25 +0800 Subject: [PATCH 13/15] style: optimize --- config/hooks.ts | 1 + packages/hooks/src/index.ts | 2 ++ packages/hooks/src/useTheme/index.ts | 42 +++++++++++++++++----------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/config/hooks.ts b/config/hooks.ts index fa74274cf2..26ffff428a 100644 --- a/config/hooks.ts +++ b/config/hooks.ts @@ -31,6 +31,7 @@ export const menus = [ 'useCounter', 'useTextSelection', 'useWebSocket', + 'useTheme', ], }, { diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index f977e5ff86..55c7232b0d 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -75,6 +75,7 @@ import useVirtualList from './useVirtualList'; import useWebSocket from './useWebSocket'; import useWhyDidYouUpdate from './useWhyDidYouUpdate'; import useMutationObserver from './useMutationObserver'; +import useTheme from './useTheme'; export { useRequest, @@ -156,4 +157,5 @@ export { useRafTimeout, useResetState, useMutationObserver, + useTheme, }; diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index eafe19efbd..3fecec96a2 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -1,54 +1,64 @@ import { useEffect, useState } from 'react'; import useMemoizedFn from '../useMemoizedFn'; +export enum ThemeMode { + LIGHT = 'light', + DARK = 'dark', + SYSTEM = 'system', +} + +export type ThemeModeType = `${ThemeMode}`; + const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); function useCurrentTheme() { const [theme, setTheme] = useState<'light' | 'dark'>(() => { - return matchMedia.matches ? 'dark' : 'light'; + return matchMedia.matches ? ThemeMode.DARK : ThemeMode.LIGHT; }); useEffect(() => { - // 监听系统颜色切换 - const listener: MediaQueryList['onchange'] = (event) => { + const onThemeChange: MediaQueryList['onchange'] = (event) => { if (event.matches) { - setTheme('dark'); + setTheme(ThemeMode.DARK); } else { - setTheme('light'); + setTheme(ThemeMode.LIGHT); } }; - matchMedia.addEventListener('change', listener); + matchMedia.addEventListener('change', onThemeChange); return () => { - matchMedia.removeEventListener('change', listener); + matchMedia.removeEventListener('change', onThemeChange); }; }, []); + return theme; } -export type ThemeModeType = 'light' | 'dark' | 'system'; - -type PropsType = { +type Options = { localStorageKey?: string; }; -export function useTheme(props: PropsType = {}) { - const { localStorageKey } = props; +export default function useTheme(options: Options = {}) { + const { localStorageKey } = options; + const [themeMode, setThemeMode] = useState(() => { const preferredThemeMode = localStorageKey?.length && (localStorage.getItem(localStorageKey) as ThemeModeType | null); - return preferredThemeMode ? preferredThemeMode : 'system'; + + return preferredThemeMode ? preferredThemeMode : ThemeMode.SYSTEM; }); const setThemeModeWithLocalStorage = (mode: ThemeModeType) => { setThemeMode(mode); - localStorageKey?.length && localStorage.setItem(localStorageKey, mode); + + if (localStorageKey?.length) { + localStorage.setItem(localStorageKey, mode); + } }; const currentTheme = useCurrentTheme(); - - const theme = themeMode === 'system' ? currentTheme : themeMode; + const theme = themeMode === ThemeMode.SYSTEM ? currentTheme : themeMode; return { theme, From 2aabd377daf94b4f3143dbb4e4c52cff8847ce43 Mon Sep 17 00:00:00 2001 From: ian Date: Thu, 8 Aug 2024 16:23:16 +0800 Subject: [PATCH 14/15] feat: useTheme add onChange callback --- jest.config.js | 2 +- match-media-mock.js | 13 ++++++++++++ .../hooks/src/useTheme/__test__/index.test.ts | 16 +------------- packages/hooks/src/useTheme/index.ts | 21 +++++++++++++------ 4 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 match-media-mock.js diff --git a/jest.config.js b/jest.config.js index 23c1d38339..824400839e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,7 +14,7 @@ module.exports = { testPathIgnorePatterns: ['/.history/'], modulePathIgnorePatterns: ['/package.json'], resetMocks: false, - setupFiles: ['./jest.setup.js', 'jest-localstorage-mock'], + setupFiles: ['./jest.setup.js', 'jest-localstorage-mock', './match-media-mock.js'], setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], transform: { '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }], diff --git a/match-media-mock.js b/match-media-mock.js new file mode 100644 index 0000000000..099fa51778 --- /dev/null +++ b/match-media-mock.js @@ -0,0 +1,13 @@ +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); diff --git a/packages/hooks/src/useTheme/__test__/index.test.ts b/packages/hooks/src/useTheme/__test__/index.test.ts index a06e76baab..3343269439 100644 --- a/packages/hooks/src/useTheme/__test__/index.test.ts +++ b/packages/hooks/src/useTheme/__test__/index.test.ts @@ -1,19 +1,5 @@ -Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), -}); - import { act, renderHook } from '@testing-library/react'; -import { useTheme } from '../index'; +import useTheme from '../index'; describe('useTheme', () => { test('themeMode init', () => { diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index 3fecec96a2..a11e4f64d0 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -9,19 +9,27 @@ export enum ThemeMode { export type ThemeModeType = `${ThemeMode}`; +export type ThemeType = 'light' | 'dark'; + const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); -function useCurrentTheme() { - const [theme, setTheme] = useState<'light' | 'dark'>(() => { - return matchMedia.matches ? ThemeMode.DARK : ThemeMode.LIGHT; +type Callback = (theme: ThemeType) => void; + +function useCurrentTheme(callback: Callback = () => {}) { + const [theme, setTheme] = useState(() => { + const init = matchMedia.matches ? ThemeMode.DARK : ThemeMode.LIGHT; + callback(init); + return init; }); useEffect(() => { const onThemeChange: MediaQueryList['onchange'] = (event) => { if (event.matches) { setTheme(ThemeMode.DARK); + callback(ThemeMode.DARK); } else { setTheme(ThemeMode.LIGHT); + callback(ThemeMode.LIGHT); } }; @@ -30,17 +38,18 @@ function useCurrentTheme() { return () => { matchMedia.removeEventListener('change', onThemeChange); }; - }, []); + }, [callback]); return theme; } type Options = { localStorageKey?: string; + onChange?: Callback; }; export default function useTheme(options: Options = {}) { - const { localStorageKey } = options; + const { localStorageKey, onChange } = options; const [themeMode, setThemeMode] = useState(() => { const preferredThemeMode = @@ -57,7 +66,7 @@ export default function useTheme(options: Options = {}) { } }; - const currentTheme = useCurrentTheme(); + const currentTheme = useCurrentTheme(onChange); const theme = themeMode === ThemeMode.SYSTEM ? currentTheme : themeMode; return { From 2d2f1d6fad3196c3c5f57de044590419f6603007 Mon Sep 17 00:00:00 2001 From: ian Date: Thu, 22 Aug 2024 09:31:33 +0800 Subject: [PATCH 15/15] feat: remove onChange --- packages/hooks/src/useTheme/index.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/hooks/src/useTheme/index.ts b/packages/hooks/src/useTheme/index.ts index a11e4f64d0..9eb3899dcc 100644 --- a/packages/hooks/src/useTheme/index.ts +++ b/packages/hooks/src/useTheme/index.ts @@ -13,12 +13,9 @@ export type ThemeType = 'light' | 'dark'; const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); -type Callback = (theme: ThemeType) => void; - -function useCurrentTheme(callback: Callback = () => {}) { +function useCurrentTheme() { const [theme, setTheme] = useState(() => { const init = matchMedia.matches ? ThemeMode.DARK : ThemeMode.LIGHT; - callback(init); return init; }); @@ -26,10 +23,8 @@ function useCurrentTheme(callback: Callback = () => {}) { const onThemeChange: MediaQueryList['onchange'] = (event) => { if (event.matches) { setTheme(ThemeMode.DARK); - callback(ThemeMode.DARK); } else { setTheme(ThemeMode.LIGHT); - callback(ThemeMode.LIGHT); } }; @@ -38,18 +33,17 @@ function useCurrentTheme(callback: Callback = () => {}) { return () => { matchMedia.removeEventListener('change', onThemeChange); }; - }, [callback]); + }, []); return theme; } type Options = { localStorageKey?: string; - onChange?: Callback; }; export default function useTheme(options: Options = {}) { - const { localStorageKey, onChange } = options; + const { localStorageKey } = options; const [themeMode, setThemeMode] = useState(() => { const preferredThemeMode = @@ -66,7 +60,7 @@ export default function useTheme(options: Options = {}) { } }; - const currentTheme = useCurrentTheme(onChange); + const currentTheme = useCurrentTheme(); const theme = themeMode === ThemeMode.SYSTEM ? currentTheme : themeMode; return {