1
0
mirror of synced 2026-01-06 15:01:04 -05:00

Merge pull request #19621 from github/repo-sync

repo sync
This commit is contained in:
Octomerger Bot
2022-08-09 13:03:51 -04:00
committed by GitHub
9 changed files with 163 additions and 178 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,4 +1,3 @@
import type { ThemeProviderProps } from '@primer/react'
import { useEffect } from 'react'
import useSWR from 'swr'
@@ -14,16 +13,6 @@ export type Session = {
isSignedIn: boolean
csrfToken?: string
userLanguage: string // en, es, ja, cn
theme: {
colorMode: Pick<ThemeProviderProps, 'colorMode'>
nightTheme: string
dayTheme: string
}
themeCss: {
colorMode: Pick<ThemeProviderProps, 'colorMode'>
nightTheme: string
dayTheme: string
}
}
// React hook version

View File

@@ -0,0 +1,114 @@
import { useState, useEffect } from 'react'
import Cookies from 'js-cookie'
enum CssColorMode {
auto = 'auto',
light = 'light',
dark = 'dark',
}
enum ComponentColorMode {
auto = 'auto',
day = 'day',
night = 'night',
}
enum SupportedTheme {
light = 'light',
dark = 'dark',
dark_dimmed = 'dark_dimmed',
dark_high_contrast = 'dark_high_contrast',
}
type CssColorTheme = {
colorMode: CssColorMode
lightTheme: SupportedTheme
darkTheme: SupportedTheme
}
type ComponentColorTheme = {
colorMode: ComponentColorMode
dayScheme: SupportedTheme
nightScheme: SupportedTheme
}
type ColorModeThemes = {
css: CssColorTheme
component: ComponentColorTheme
}
export const defaultCSSTheme: CssColorTheme = {
colorMode: CssColorMode.auto,
lightTheme: SupportedTheme.light,
darkTheme: SupportedTheme.dark,
}
export const defaultComponentTheme: ComponentColorTheme = {
colorMode: ComponentColorMode.auto,
dayScheme: SupportedTheme.light,
nightScheme: SupportedTheme.dark,
}
const cssColorModeToComponentColorMode: Record<CssColorMode, ComponentColorMode> = {
[CssColorMode.auto]: ComponentColorMode.auto,
[CssColorMode.light]: ComponentColorMode.day,
[CssColorMode.dark]: ComponentColorMode.night,
}
function filterMode(mode = ''): CssColorMode | undefined {
if (Object.values<string>(CssColorMode).includes(mode)) {
return mode as CssColorMode
}
}
function filterTheme({ name = '', color_mode = '' } = {}): SupportedTheme | undefined {
if (Object.values<string>(SupportedTheme).includes(name)) {
return name as SupportedTheme
}
if (Object.values<string>(SupportedTheme).includes(color_mode)) {
return color_mode as SupportedTheme
}
}
export function getCssTheme(cookieValue = ''): CssColorTheme {
if (!cookieValue) return defaultCSSTheme
try {
const parsed = JSON.parse(cookieValue)
const { color_mode, light_theme, dark_theme } = parsed
return {
colorMode: filterMode(color_mode) || defaultCSSTheme.colorMode,
lightTheme: filterTheme(light_theme) || defaultCSSTheme.lightTheme,
darkTheme: filterTheme(dark_theme) || defaultCSSTheme.darkTheme,
}
} catch (err) {
console.warn("Unable to parse 'color_mode' cookie", err)
return defaultCSSTheme
}
}
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,
}
}
export function useTheme() {
const [theme, setTheme] = useState<ColorModeThemes>({
css: defaultCSSTheme,
component: defaultComponentTheme,
})
useEffect(() => {
const cookieValue = Cookies.get('color_mode')
const css = getCssTheme(cookieValue)
const component = getComponentTheme(cookieValue)
setTheme({ css, component })
}, [])
return { theme }
}

View File

@@ -1,66 +0,0 @@
export const defaultCSSTheme = {
colorMode: 'auto', // light, dark, auto
nightTheme: 'dark',
dayTheme: 'light',
}
export const defaultComponentTheme = {
colorMode: 'auto', // day, night, auto
nightTheme: 'dark',
dayTheme: 'light',
}
const cssColorModeToComponentColorMode = {
auto: 'auto',
light: 'day',
dark: 'night',
}
const supportedThemes = ['light', 'dark', 'dark_dimmed']
/*
* Our version of primer/css is out of date, so we can only support known themes.
* For the least jarring experience possible, we fallback to the color_mode (light / dark) if provided by the theme, otherwise our own defaults
*/
function getSupportedTheme(theme, fallbackTheme) {
if (!theme) {
return fallbackTheme
}
return supportedThemes.includes(theme.name) ? theme.name : theme.color_mode
}
/*
* Returns theme for consumption by either primer/css or primer/components
* based on the cookie and/or fallback values
*/
export function getTheme(req, cssMode = false) {
const cookieValue = {}
const defaultTheme = cssMode ? defaultCSSTheme : defaultComponentTheme
if (req.cookies?.color_mode) {
try {
const parsed = JSON.parse(decodeURIComponent(req.cookies.color_mode))
cookieValue.color_mode = parsed.color_mode
cookieValue.dark_theme = parsed.dark_theme
cookieValue.light_theme = parsed.light_theme
} catch (err) {
if (process.env.NODE_ENV !== 'test') {
console.warn("Unable to parse 'color_mode' cookie", err)
}
}
}
// The cookie value is a primer/css color_mode. sometimes we need to convert that to a primer/components compatible version
const colorMode =
(cssMode
? cookieValue.color_mode
: cssColorModeToComponentColorMode[cookieValue.color_mode || '']) || defaultTheme.colorMode
return {
colorMode,
nightTheme: getSupportedTheme(cookieValue.dark_theme, defaultTheme.nightTheme),
dayTheme: getSupportedTheme(cookieValue.light_theme, defaultTheme.dayTheme),
}
}

View File

@@ -1,5 +1,4 @@
import express from 'express'
import { getTheme } from '../../lib/get-theme.js'
import { cacheControlFactory } from '../cache-control.js'
const router = express.Router()
@@ -11,8 +10,6 @@ router.get('/', (req, res) => {
isSignedIn: Boolean(req.cookies?.dotcom_user),
csrfToken: req.csrfToken?.() || '',
userLanguage: req.userLanguage,
theme: getTheme(req),
themeCss: getTheme(req, true),
})
})

View File

@@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
import App from 'next/app'
import type { AppProps, AppContext } from 'next/app'
import Head from 'next/head'
import { ThemeProvider, SSRProvider, ThemeProviderProps } from '@primer/react'
import { ThemeProvider, SSRProvider } from '@primer/react'
import '../stylesheets/index.scss'
@@ -10,16 +10,17 @@ import { initializeEvents } from 'components/lib/events'
import experiment from 'components/lib/experiment'
import { LanguagesContext, LanguagesContextT } from 'components/context/LanguagesContext'
import { useSession } from 'components/hooks/useSession'
import { useTheme } from 'components/hooks/useTheme'
type MyAppProps = AppProps & {
isDotComAuthenticated: boolean
languagesContext: LanguagesContextT
}
type colorModeAuto = Pick<ThemeProviderProps, 'colorMode'>
const MyApp = ({ Component, pageProps, languagesContext }: MyAppProps) => {
const { session } = useSession()
const { theme } = useTheme()
useEffect(() => {
if (session?.csrfToken) {
initializeEvents(session.csrfToken)
@@ -54,18 +55,15 @@ const MyApp = ({ Component, pageProps, languagesContext }: MyAppProps) => {
</Head>
<SSRProvider>
<ThemeProvider
colorMode={
(session?.theme?.colorMode as colorModeAuto['colorMode']) ||
('auto' as colorModeAuto['colorMode'])
}
dayScheme={session?.theme?.dayTheme || 'light'}
nightScheme={session?.theme?.nightTheme || 'dark'}
colorMode={theme.component.colorMode}
dayScheme={theme.component.dayScheme}
nightScheme={theme.component.nightScheme}
>
{/* Appears Next.js can't modify <body> after server rendering: https://stackoverflow.com/a/54774431 */}
<div
data-color-mode={session?.themeCss?.colorMode || 'auto'}
data-dark-theme={session?.themeCss?.nightTheme || 'dark'}
data-light-theme={session?.themeCss?.dayTheme || 'light'}
data-color-mode={theme.css.colorMode}
data-light-theme={theme.css.lightTheme}
data-dark-theme={theme.css.darkTheme}
>
<LanguagesContext.Provider value={languagesContext}>
<Component {...pageProps} />

View File

@@ -13,9 +13,3 @@
@import "shadows.scss";
@import "syntax-highlighting.scss";
@import "utilities.scss";
// render a mostly gray background until we know the color mode via XHR
html,
body {
background: #6e7781;
}

View File

@@ -1,80 +0,0 @@
import { describe, expect, test } from '@jest/globals'
import { getTheme, defaultCSSTheme, defaultComponentTheme } from '../../lib/get-theme.js'
function serializeCookieValue(obj) {
return encodeURIComponent(JSON.stringify(obj))
}
describe('getTheme basics', () => {
test('always return an object with certain keys', () => {
const req = {} // doesn't even have a `.cookies`.
const theme = getTheme(req)
expect(theme.colorMode).toBe(defaultComponentTheme.colorMode)
expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme)
expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme)
const cssTheme = getTheme(req, true)
expect(cssTheme.colorMode).toBe(defaultCSSTheme.colorMode)
expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme)
expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme)
})
test('respect the color_mode cookie value', () => {
const req = {
cookies: {
color_mode: serializeCookieValue({
color_mode: 'dark',
light_theme: { name: 'light_colorblind', color_mode: 'light' },
dark_theme: { name: 'dark_tritanopia', color_mode: 'dark' },
}),
},
}
const theme = getTheme(req)
expect(theme.colorMode).toBe('night')
expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme)
expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme)
const cssTheme = getTheme(req, true)
expect(cssTheme.colorMode).toBe('dark')
expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme)
expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme)
})
test('respect the color_mode cookie value', () => {
const req = {
cookies: {
color_mode: serializeCookieValue({
color_mode: 'dark',
light_theme: { name: 'light_colorblind', color_mode: 'light' },
dark_theme: { name: 'dark_tritanopia', color_mode: 'dark' },
}),
},
}
const theme = getTheme(req)
expect(theme.colorMode).toBe('night')
expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme)
expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme)
const cssTheme = getTheme(req, true)
expect(cssTheme.colorMode).toBe('dark')
expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme)
expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme)
})
test('ignore "junk" cookie values', () => {
const req = {
cookies: {
color_mode: '[This is not valid JSON}',
},
}
const theme = getTheme(req)
expect(theme.colorMode).toBe('auto')
expect(theme.nightTheme).toBe(defaultComponentTheme.nightTheme)
expect(theme.dayTheme).toBe(defaultComponentTheme.dayTheme)
const cssTheme = getTheme(req, true)
expect(cssTheme.colorMode).toBe('auto')
expect(cssTheme.nightTheme).toBe(defaultCSSTheme.nightTheme)
expect(cssTheme.dayTheme).toBe(defaultCSSTheme.dayTheme)
})
})

39
tests/unit/use-theme.js Normal file
View File

@@ -0,0 +1,39 @@
import { describe, expect, test } from '@jest/globals'
import {
getComponentTheme,
getCssTheme,
defaultCSSTheme,
defaultComponentTheme,
} from '../../components/hooks/useTheme.ts'
describe('getTheme basics', () => {
test('always return an object with certain keys', () => {
const cookieValue = JSON.stringify({})
expect(getCssTheme(cookieValue)).toEqual(defaultCSSTheme)
expect(getComponentTheme(cookieValue)).toEqual(defaultComponentTheme)
})
test('ignore "junk" cookie values', () => {
const cookieValue = '[This is not valid JSON}'
expect(getCssTheme(cookieValue)).toEqual(defaultCSSTheme)
expect(getComponentTheme(cookieValue)).toEqual(defaultComponentTheme)
})
test('respect the color_mode cookie value', () => {
const cookieValue = JSON.stringify({
color_mode: 'dark',
light_theme: { name: 'light_colorblind', color_mode: 'light' },
dark_theme: { name: 'dark_tritanopia', color_mode: 'dark' },
})
const cssTheme = getCssTheme(cookieValue)
expect(cssTheme.colorMode).toBe('dark')
expect(cssTheme.darkTheme).toBe(defaultCSSTheme.darkTheme)
expect(cssTheme.lightTheme).toBe(defaultCSSTheme.lightTheme)
const componentTheme = getComponentTheme(cookieValue)
expect(componentTheme.colorMode).toBe('night')
expect(componentTheme.nightScheme).toBe(defaultComponentTheme.nightScheme)
expect(componentTheme.dayScheme).toBe(defaultComponentTheme.dayScheme)
})
})