import React, { FC } from 'react'; import styled from 'styled-components'; import { ColorScheme } from '../../../plugins'; import { PluginTheme } from '../../../plugins/misc'; import { useThemes } from '../../hooks/theme'; import { HelpTooltip } from '../help-tooltip'; const THEMES_PER_ROW = 5; const isDark = (mode: 'dark' | 'light') => mode === 'dark'; const isLight = (mode: 'dark' | 'light') => mode === 'light'; const RootWrapper = styled.div({}); const CheckboxWrapper = styled.div({ marginLeft: 'var(--padding-md)', }); const Themes = styled.div({ display: 'flex', flexWrap: 'wrap', }); const ThemeButton = styled.div<{ $isActive: boolean; $isInOsThemeMode: boolean }>(({ $isActive, $isInOsThemeMode }) => ({ position: 'relative', margin: 'var(--padding-md) var(--padding-md)', fontSize: 0, borderRadius: 'var(--radius-md)', transition: 'all 150ms ease-out', // This is a workaround for some anti-aliasing artifacts that impact the color scheme badges. // The box shadow is placed on a pseudo-element. When it is active, it is configured to overlap // 1px with the underlying geometry to prevent gaps caused by anti-aliasing. '&:before': { display: 'block', position: 'absolute', boxShadow: '0 0 0 1px var(--hl-sm)', transition: 'all 150ms ease-out', borderRadius: 'var(--radius-md)', width: '100%', height: '100%', content: "''", ...($isActive ? { boxShadow: '0 0 0 var(--padding-xs) var(--color-surprise)', width: 'calc(100% - 2px)', height: 'calc(100% - 2px)', margin: '1px', } : {}), }, '&:hover:before': { ...($isInOsThemeMode ? { boxShadow: '0 0 0 0 var(--color-surprise)' } : {}), }, ...($isActive ? { transform: 'scale(1.05)', } : {}), '&:hover': { transform: 'scale(1.05)', }, ...($isInOsThemeMode ? { '&:hover .overlay-wrapper': { display: 'flex', }, '&:hover .theme-preview': { visibility: 'hidden', // this prevents alpha-blending problems with the underlying svg bleeding through }, } : {}), })); const ThemeTitle = styled.h2({ marginTop: 0, marginBottom: 'var(--padding-xs)', fontSize: 'var(--font-size-md)', }); const ThemeWrapper = styled.div({ maxWidth: `${100 / THEMES_PER_ROW}%`, minWidth: 110, marginBottom: 'var(--padding-md)', marginTop: 'var(--padding-md)', textAlign: 'center', }); const ColorSchemeBadge = styled.div<{ $theme: 'dark' | 'light'}>(({ $theme }) => ({ position: 'absolute', top: 0, width: 12, height: 12, fill: 'white', padding: 4, transition: 'background-color 150ms ease-out', backgroundColor: 'var(--color-surprise)', ...(isDark($theme) ? { right: 0, borderTopRightRadius: 'var(--radius-md)', borderBottomLeftRadius: 'var(--radius-md)', } : {}), ...(isLight($theme) ? { left: 0, borderTopLeftRadius: 'var(--radius-md)', borderBottomRightRadius: 'var(--radius-md)', } : {}), })); const OverlayWrapper = styled.div({ position: 'absolute', height: '100%', width: '100%', alignItems: 'center', flexWrap: 'nowrap', justifyContent: 'space-evenly', boxSizing: 'content-box', display: 'none', // controlled by hover on the ThemeWrapper }); const OverlaySide = styled.div<{ $theme: 'dark' | 'light' }>(({ $theme }) => ({ display: 'flex', cursor: 'pointer', flexGrow: 1, alignItems: 'center', justifyContent: 'center', height: '100%', background: 'var(--color-bg)', '& svg': { opacity: 0.4, fill: 'var(--color-fg)', height: 20, }, '&:hover svg': { opacity: 1, fill: 'var(--color-surprise)', }, boxSizing: 'border-box', border: '1px solid var(--hl-sm)', ...(isDark($theme) ? { borderTopRightRadius: 'var(--radius-md)', borderBottomRightRadius: 'var(--radius-md)', borderLeftStyle: 'none', } : {}), ...(isLight($theme) ? { borderTopLeftRadius: 'var(--radius-md)', borderBottomLeftRadius: 'var(--radius-md)', borderRightStyle: 'none', } : {}), '&:hover': { border: '1px solid var(--color-surprise)', }, })); const SunSvg = () => ( ); const MoonSvg = () => ( ); const ThemePreview: FC<{ theme: PluginTheme }> = ({ theme: { name: themeName } }) => ( {/* A WORD TO THE WISE: If you, dear traveler from the future, are here for the purpose of theming things due to changes in the app structure, please remember to add `--sub` to your classes or else the selected class' theme variables will apply to all theme previews. Search your codebase for `--sub` to see more. */} {/* @ts-expect-error -- TSCONVERSION */} {/* App Header */} {/* Panes */} {/* Response Area */} {/* URL Bars */} {/* Send Button */} {/* Sidebar */} {/* Lines */} {/* Color Squares */} ); const IndividualTheme: FC<{ isActive: boolean; isDark: boolean; isLight: boolean; isInOsThemeMode: boolean; onChangeTheme: (name: string, mode: ColorScheme) => Promise; theme: PluginTheme; }> = ({ isActive, isDark, isLight, isInOsThemeMode, onChangeTheme, theme, }) => { const { displayName, name } = theme; const onClickThemeButton = () => { if (isInOsThemeMode) { // The overlays handle this behavior in OS theme mode. // React's event bubbling means that this will be fired when you click on an overlay, so we need to turn it off when in this mode. // Even still, we don't want to risk some potnetial subpixel or z-index nonsense accidentally setting the default when know we shouldn't. return; } return onChangeTheme(name, 'default'); }; return ( {displayName} {isInOsThemeMode ? ( <> { onChangeTheme(name, 'light'); }}> { onChangeTheme(name, 'dark'); }}> {isActive && isDark ? ( ) : null} {isActive && isLight ? ( ) : null} ) : null} ); }; export const ThemePanel: FC = () => { const { themes, activate, changeAutoDetect, isActive, isActiveDark, isActiveLight, autoDetectColorScheme, } = useThemes(); return ( {themes.map(theme => ( ))} ); };