diff --git a/src/components/color-preference-picker.js b/src/components/color-preference-picker.js new file mode 100644 index 00000000000..8820bb547e1 --- /dev/null +++ b/src/components/color-preference-picker.js @@ -0,0 +1,32 @@ +import React, {useCallback} from 'react' +import {SegmentedControl} from '@primer/react' +import {DeviceDesktopIcon, MoonIcon, SunIcon} from '@primer/octicons-react' + +const MODE_ICONS = [ + {id: 'auto', name: 'System', icon: DeviceDesktopIcon}, + {id: 'day', name: 'Day', icon: SunIcon}, + {id: 'night', name: 'Night', icon: MoonIcon}, +] + +export const ColorPreferencePicker = ({preferredColorMode, setColorPreference}) => { + const handleColorModeChange = useCallback( + modeIndex => { + const mode = MODE_ICONS[modeIndex].id + setColorPreference(mode) + }, + [setColorPreference], + ) + + return ( + + {MODE_ICONS.map((mode, index) => ( + + ))} + + ) +} diff --git a/src/components/header.js b/src/components/header.js index 404072cbbef..c24a5dbc970 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -9,6 +9,8 @@ import {HEADER_HEIGHT, HEADER_BAR} from '../constants' import headerNavItems from '../../content/header-nav.yml' import {DarkTheme} from '../theme' import SiteTitle from './site-title' +import {ColorPreferencePicker} from './color-preference-picker' +import {useColorPreference} from '../hooks/use-color-preference' const NpmHeaderBar = styled(Box)` height: ${HEADER_BAR}px; @@ -16,6 +18,8 @@ const NpmHeaderBar = styled(Box)` ` function Header() { + const {preferredColorMode, setColorPreference} = useColorPreference() + const search = useSearch() return ( @@ -43,6 +47,7 @@ function Header() { + {headerNavItems.map((item, index) => ( @@ -50,7 +55,7 @@ function Header() { ))} - + diff --git a/src/hooks/use-color-preference.js b/src/hooks/use-color-preference.js new file mode 100644 index 00000000000..a9343b7086e --- /dev/null +++ b/src/hooks/use-color-preference.js @@ -0,0 +1,20 @@ +import {useCallback, useMemo} from 'react' +import {useTheme} from '@primer/react' + +export const useColorPreference = (themeContextId = 'root') => { + const {colorMode, setColorMode} = useTheme() + const setColorPreference = useCallback( + mode => { + localStorage.setItem(`${themeContextId}-color-mode`, mode) + setColorMode(mode) + }, + [setColorMode, themeContextId], + ) + + const preferredColorMode = useMemo( + () => colorMode ?? localStorage.getItem(`${themeContextId}-color-mode`) ?? 'auto', + [colorMode, themeContextId], + ) + + return {preferredColorMode, setColorPreference} +} diff --git a/src/hooks/use-prism-theme.js b/src/hooks/use-prism-theme.js new file mode 100644 index 00000000000..020b4dcb481 --- /dev/null +++ b/src/hooks/use-prism-theme.js @@ -0,0 +1,17 @@ +import {useTheme} from '@primer/react' +import {themes} from 'prism-react-renderer' +import {useMemo} from 'react' + +const colorModeToThemeMap = { + light: themes.github, + dark: themes.vsDark, + day: themes.github, + night: themes.vsDark, +} + +export const usePrismTheme = () => { + const {resolvedColorMode} = useTheme() + + const theme = useMemo(() => colorModeToThemeMap[resolvedColorMode ?? 'day'], [resolvedColorMode]) + return {theme} +} diff --git a/src/mdx/code.js b/src/mdx/code.js index fcac8056c32..1dca439e7ce 100644 --- a/src/mdx/code.js +++ b/src/mdx/code.js @@ -1,11 +1,12 @@ import React from 'react' import {Box, Text, Button, themeGet} from '@primer/react' import {Octicon} from '@primer/react/deprecated' -import {Highlight, themes, Prism} from 'prism-react-renderer' +import {Highlight, Prism} from 'prism-react-renderer' import styled from 'styled-components' import {CheckIcon, CopyIcon} from '@primer/octicons-react' import copyToClipboard from 'copy-to-clipboard' import {announce} from '../util/aria-live' +import {usePrismTheme} from '../hooks/use-prism-theme' ;(typeof global !== 'undefined' ? global : window).Prism = Prism require('prismjs/components/prism-bash') @@ -99,9 +100,10 @@ const CodeBlock = ({children, code, className, style}) => ( ) function Code({className = '', prompt, children}) { + const {theme: codeTheme} = usePrismTheme() if (prompt) { return ( - + {children} ) @@ -115,7 +117,7 @@ function Code({className = '', prompt, children}) { } return ( - + {({className: highlightClassName, style, tokens, getLineProps, getTokenProps}) => ( {tokens.map((line, i) => ( diff --git a/src/theme.js b/src/theme.js index 0c9df6fbfe3..d73e8492ffa 100644 --- a/src/theme.js +++ b/src/theme.js @@ -4,6 +4,8 @@ import deepmerge from 'deepmerge' export const NPM_RED = '#cb3837' +const colorModePreference = (typeof window !== `undefined` ? localStorage.getItem('root-color-mode') : null) ?? 'auto' + export const npmTheme = deepmerge(theme, { colors: { logoBg: NPM_RED, @@ -37,7 +39,7 @@ export const npmTheme = deepmerge(theme, { }, }) -export const ThemeProvider = props => +export const ThemeProvider = props => export const Theme = React.forwardRef(function Theme({theme: colorMode, as = Box, ...props}, ref) { return (