From baa568c7fe837c0588fb2890bde2a971be9a495f Mon Sep 17 00:00:00 2001 From: Sajjad Hossain Date: Tue, 1 Oct 2024 11:23:32 +0600 Subject: [PATCH] optimizing theme --- package-lock.json | 104 +++++++++++++++++++++++ package.json | 1 + src/color-schemes/components/useTheme.ts | 73 ++++++++-------- 3 files changed, 144 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8eba666fef5c..af0fe8908734 100644 --- a/package-lock.json +++ b/package-lock.json @@ -150,6 +150,7 @@ "rimraf": "^5.0.0", "robots-parser": "^3.0.0", "sass": "^1.75.0", + "shx": "^0.3.4", "start-server-and-test": "^2.0.3", "typescript": "^5.4.4", "unist-util-remove": "^4.0.0", @@ -8227,6 +8228,16 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ip-regex": { "version": "4.3.0", "license": "MIT", @@ -12216,6 +12227,18 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -13061,6 +13084,87 @@ "dev": true, "license": "MIT" }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shelljs/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/side-channel": { "version": "1.0.4", "license": "MIT", diff --git a/package.json b/package.json index 1d9caf424c69..f53ada0537e2 100644 --- a/package.json +++ b/package.json @@ -336,6 +336,7 @@ "rimraf": "^5.0.0", "robots-parser": "^3.0.0", "sass": "^1.75.0", + "shx": "^0.3.4", "start-server-and-test": "^2.0.3", "typescript": "^5.4.4", "unist-util-remove": "^4.0.0", diff --git a/src/color-schemes/components/useTheme.ts b/src/color-schemes/components/useTheme.ts index e240440b9b9a..bce6c1568623 100644 --- a/src/color-schemes/components/useTheme.ts +++ b/src/color-schemes/components/useTheme.ts @@ -1,66 +1,77 @@ import { useState, useEffect } from 'react' import Cookies from '../../frame/components/lib/cookies' +// Enum representing CSS color modes enum CssColorMode { - auto = 'auto', - light = 'light', - dark = 'dark', + auto = 'auto', // Detects user's OS theme preference + light = 'light', // Forces light theme + dark = 'dark', // Forces dark theme } +// Enum representing component color modes (maps to CssColorMode) enum ComponentColorMode { - auto = 'auto', - day = 'day', - night = 'night', + auto = 'auto', // Automatically adjusts component theme + day = 'day', // Light theme for components + night = 'night', // Dark theme for components } +// Enum for supported theme variants (used in both CSS and component themes) enum SupportedTheme { - light = 'light', - dark = 'dark', - dark_dimmed = 'dark_dimmed', - dark_high_contrast = 'dark_high_contrast', + light = 'light', // Light theme + dark = 'dark', // Dark theme + dark_dimmed = 'dark_dimmed', // Dimmed dark theme + dark_high_contrast = 'dark_high_contrast', // High contrast dark theme } +// Type for managing CSS color theme settings type CssColorTheme = { colorMode: CssColorMode lightTheme: SupportedTheme darkTheme: SupportedTheme } +// Type for managing component-specific color theme settings type ComponentColorTheme = { colorMode: ComponentColorMode dayScheme: SupportedTheme nightScheme: SupportedTheme } +// Consolidated theme settings for both CSS and component themes type ColorModeThemes = { css: CssColorTheme component: ComponentColorTheme } +// Default theme settings for CSS export const defaultCSSTheme: CssColorTheme = { colorMode: CssColorMode.auto, lightTheme: SupportedTheme.light, darkTheme: SupportedTheme.dark, } +// Default theme settings for components export const defaultComponentTheme: ComponentColorTheme = { colorMode: ComponentColorMode.auto, dayScheme: SupportedTheme.light, nightScheme: SupportedTheme.dark, } +// Mapping CSS color modes to component color modes for consistency const cssColorModeToComponentColorMode: Record = { [CssColorMode.auto]: ComponentColorMode.auto, [CssColorMode.light]: ComponentColorMode.day, [CssColorMode.dark]: ComponentColorMode.night, } +// Filters the CSS color mode, ensuring it's a valid value function filterMode(mode = ''): CssColorMode | undefined { if (Object.values(CssColorMode).includes(mode)) { return mode as CssColorMode } } +// Filters the supported theme, ensuring it's a valid theme name or color mode function filterTheme({ name = '', color_mode = '' } = {}): SupportedTheme | undefined { if (Object.values(SupportedTheme).includes(name)) { return name as SupportedTheme @@ -70,6 +81,7 @@ function filterTheme({ name = '', color_mode = '' } = {}): SupportedTheme | unde } } +// Parses the CSS theme from a cookie value, applying default settings if parsing fails export function getCssTheme(cookieValue = ''): CssColorTheme { if (!cookieValue) return defaultCSSTheme try { @@ -81,23 +93,25 @@ export function getCssTheme(cookieValue = ''): CssColorTheme { darkTheme: filterTheme(dark_theme) || defaultCSSTheme.darkTheme, } } catch (err) { - if (process.env.NODE_ENV === 'development') + // Only log the error in development mode + if (process.env.NODE_ENV === 'development') { console.warn("Unable to parse 'color_mode' cookie", err) + } return defaultCSSTheme } } +// Converts the CSS theme to a component-compatible theme export function getComponentTheme(cookieValue = ''): ComponentColorTheme { const { colorMode, lightTheme, darkTheme } = getCssTheme(cookieValue) return { - // The cookie value is a primer/css color_mode. - // We need to convert that to a primer/react compatible version. - colorMode: cssColorModeToComponentColorMode[colorMode], - dayScheme: lightTheme, - nightScheme: darkTheme, + colorMode: cssColorModeToComponentColorMode[colorMode], // Convert CSS color mode to component mode + dayScheme: lightTheme, // Day scheme uses the light theme + nightScheme: darkTheme, // Night scheme uses the dark theme } } +// Custom hook to manage the current theme for both CSS and components export function useTheme() { const [theme, setTheme] = useState({ css: defaultCSSTheme, @@ -105,25 +119,16 @@ export function useTheme() { }) useEffect(() => { - // Using setTimeout with a default delay value of 0 interjects one - // additional event cycle, which works around a bug that is the - // result of a timing issue. Without the setTimeout function - // the page loads, then the docs site switches the color mode to - // match the user's GitHub color mode. Primer React has a useEffect - // call that overrides this change, causing the site to ignore the - // user's GitHub color mode and revert to auto. - // As a temporary workaround, this code that fetches the user's GitHub - // color mode will be called after Primer React's useEffect call. - // The long term solution to this theming issue is to migrate to CSS variables - // under the hood, which Primer is planning to do in the next couple quarters. - // Reference: https://github.com/primer/react/issues/2229 + // The setTimeout ensures that the user's theme preference is applied + // after any Primer React theme-related changes. + // This is a workaround for a known timing issue in Primer React. setTimeout(() => { - const cookieValue = Cookies.get('color_mode') - const css = getCssTheme(cookieValue) - const component = getComponentTheme(cookieValue) - setTheme({ css, component }) + const cookieValue = Cookies.get('color_mode') // Fetch the user's theme from cookies + const css = getCssTheme(cookieValue) // Parse the CSS theme from the cookie + const component = getComponentTheme(cookieValue) // Convert it to a component-compatible theme + setTheme({ css, component }) // Update the theme state with the fetched theme }) - }, []) + }, []) // Run only once when the component is mounted - return { theme } + return { theme } // Return the current theme object }