From ebf0b8c70534ff67baefeb7483cf6a2ff8eeab77 Mon Sep 17 00:00:00 2001 From: Un7corn Date: Thu, 12 Mar 2026 11:49:50 +0800 Subject: [PATCH 1/5] Update App.tsx --- .../template/src/App.tsx | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/cra-template-typescript/template/src/App.tsx b/packages/cra-template-typescript/template/src/App.tsx index a53698aab3..4a190acba3 100644 --- a/packages/cra-template-typescript/template/src/App.tsx +++ b/packages/cra-template-typescript/template/src/App.tsx @@ -1,26 +1,17 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import { Header } from './components/Header' +import './App.css' -function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); +function App( +) { + return + ( +
+
+
+ ) } -export default App; +export default App From f5d2b3dce18a1250c0b8985c8910999ed4b0cf42 Mon Sep 17 00:00:00 2001 From: Un7corn Date: Thu, 12 Mar 2026 11:50:54 +0800 Subject: [PATCH 2/5] Update App.css --- .../template/src/App.css | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/cra-template-typescript/template/src/App.css b/packages/cra-template-typescript/template/src/App.css index 74b5e05345..c35fa58c06 100644 --- a/packages/cra-template-typescript/template/src/App.css +++ b/packages/cra-template-typescript/template/src/App.css @@ -14,24 +14,54 @@ } .App-header { - background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); - color: white; + transition: background-color 0.2s ease, color 0.2s ease; } .App-link { color: #61dafb; } +.App-theme-toggle { + margin-top: 20px; + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + border-radius: 4px; + border: none; + transition: background-color 0.2s ease, color 0.2s ease; +} + +[data-theme='light'] .App-header { + background-color: #282c34; + color: white; +} + +[data-theme='light'] .App-theme-toggle { + background-color: #61dafb; + color: #282c34; +} + +[data-theme='dark'] .App-header { + background-color: #121212; + color: #f5f5f5; +} + +[data-theme='dark'] .App-theme-toggle { + background-color: #f5f5f5; + color: #121212; +} + @keyframes App-logo-spin { from { transform: rotate(0deg); } + to { transform: rotate(360deg); } From 5ce7ff89664a944d3953c0fb423374550f436c9e Mon Sep 17 00:00:00 2001 From: Un7corn Date: Thu, 12 Mar 2026 11:55:40 +0800 Subject: [PATCH 3/5] Create useDarkMode.ts --- .../template/src/hooks/useDarkMode.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 packages/cra-template-typescript/template/src/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts diff --git a/packages/cra-template-typescript/template/src/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts b/packages/cra-template-typescript/template/src/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts new file mode 100644 index 0000000000..2c672859e1 --- /dev/null +++ b/packages/cra-template-typescript/template/src/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react' + +type Theme = 'light' | 'dark' + +const STORAGE_KEY = 'theme' +const MEDIA_QUERY = '(prefers-color-scheme: dark)' + +function getStoredTheme(): Theme | null { + try { + const value = window.localStorage.getItem(STORAGE_KEY) + return value === 'light' || value === 'dark' ? value : null + } catch { + return null + } +} + +function getSystemTheme(): Theme { + return window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light' +} + +export function useDarkMode() { + const [theme, setThemeState] = useState(() => { + if (typeof window === 'undefined') { + return 'light' + } + + return getStoredTheme() ?? getSystemTheme() + }) + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme) + + try { + window.localStorage.setItem(STORAGE_KEY, theme) + } catch { + // ignore storage errors + } + }, [theme]) + + useEffect(() => { + if (typeof window === 'undefined') { + return + } + + const mediaQuery = window.matchMedia(MEDIA_QUERY) + + const handleChange = (event: MediaQueryListEvent) => { + const storedTheme = getStoredTheme() + + if (!storedTheme) { + setThemeState(event.matches ? 'dark' : 'light') + } + } + + if (typeof mediaQuery.addEventListener === 'function') { + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + } + + mediaQuery.addListener(handleChange) + return () => mediaQuery.removeListener(handleChange) + }, []) + + const setTheme = (nextTheme: Theme) => { + setThemeState(nextTheme) + } + + const toggleTheme = () => { + setThemeState(prev => (prev === 'light' ? 'dark' : 'light')) + } + + return { + theme, + isDark: theme === 'dark', + setTheme, + toggleTheme + } +} + From 84a051e099a0ea65921c5e73403e8cac354b31a3 Mon Sep 17 00:00:00 2001 From: Un7corn Date: Thu, 12 Mar 2026 11:57:55 +0800 Subject: [PATCH 4/5] Create Header.tsx --- .../template/src/components/Header.tsx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/cra-template-typescript/template/src/components/Header.tsx diff --git a/packages/cra-template-typescript/template/src/components/Header.tsx b/packages/cra-template-typescript/template/src/components/Header.tsx new file mode 100644 index 0000000000..a3b1361424 --- /dev/null +++ b/packages/cra-template-typescript/template/src/components/Header.tsx @@ -0,0 +1,33 @@ +import logo from '../logo.svg' +import { useDarkMode } from '../hooks/useDarkMode' + +export function Header() { + const { theme, toggleTheme } = useDarkMode() + + return ( +
+ logo + +

+ Edit src/App.tsx and save to reload. +

+ + + Learn React + + + +
+ ) +} From df67fe0453eea9ef643142738aac393e99ce3ae2 Mon Sep 17 00:00:00 2001 From: Un7corn Date: Thu, 12 Mar 2026 12:00:03 +0800 Subject: [PATCH 5/5] Create useDarkMode.ts --- .../template/src/hooks/useDarkMode.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/cra-template-typescript/template/src/hooks/useDarkMode.ts diff --git a/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts b/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts new file mode 100644 index 0000000000..872b1e4ad5 --- /dev/null +++ b/packages/cra-template-typescript/template/src/hooks/useDarkMode.ts @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react' + +type Theme = 'light' | 'dark' + +const STORAGE_KEY = 'theme' +const MEDIA_QUERY = '(prefers-color-scheme: dark)' + +function getStoredTheme(): Theme | null { + try { + const value = window.localStorage.getItem(STORAGE_KEY) + return value === 'light' || value === 'dark' ? value : null + } catch { + return null + } +} + +function getSystemTheme(): Theme { + return window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light' +} + +export function useDarkMode() { + const [theme, setThemeState] = useState(() => { + if (typeof window === 'undefined') { + return 'light' + } + + return getStoredTheme() ?? getSystemTheme() + }) + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme) + + try { + window.localStorage.setItem(STORAGE_KEY, theme) + } catch { + // ignore storage errors + } + }, [theme]) + + useEffect(() => { + if (typeof window === 'undefined') { + return + } + + const mediaQuery = window.matchMedia(MEDIA_QUERY) + + const handleChange = (event: MediaQueryListEvent) => { + const storedTheme = getStoredTheme() + + if (!storedTheme) { + setThemeState(event.matches ? 'dark' : 'light') + } + } + + if (typeof mediaQuery.addEventListener === 'function') { + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + } + + mediaQuery.addListener(handleChange) + return () => mediaQuery.removeListener(handleChange) + }, []) + + const setTheme = (nextTheme: Theme) => { + setThemeState(nextTheme) + } + + const toggleTheme = () => { + setThemeState(prev => (prev === 'light' ? 'dark' : 'light')) + } + + return { + theme, + isDark: theme === 'dark', + setTheme, + toggleTheme + } +}