From d02125201f39a0620aedab9f350cff84fca22bd3 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 3 Dec 2022 03:34:31 +0100 Subject: refactor(frontend): theme improvements --- subprojects/frontend/src/TopBar.tsx | 18 +- subprojects/frontend/src/editor/AnimatedButton.tsx | 14 +- subprojects/frontend/src/editor/EditorPane.tsx | 6 - subprojects/frontend/src/editor/EditorTheme.ts | 10 +- subprojects/frontend/src/editor/GenerateButton.tsx | 13 +- subprojects/frontend/src/theme/ThemeProvider.tsx | 441 +++++++++++---------- 6 files changed, 275 insertions(+), 227 deletions(-) (limited to 'subprojects/frontend') diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index 5f72dc96..81e11ac8 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx @@ -58,7 +58,7 @@ export default observer(function TopBar(): JSX.Element { const { editorStore } = useRootStore(); const overlayVisible = useWindowControlsOverlayVisible(); const { breakpoints } = useTheme(); - const showGenerateButton = useMediaQuery(breakpoints.down('sm')); + const small = useMediaQuery(breakpoints.down('sm')); return ( - Refinery - {import.meta.env.DEV && ( - <> - {' '} - Dev - - )} + Refinery {import.meta.env.DEV && Dev} - {showGenerateButton && ( - - )} + diff --git a/subprojects/frontend/src/editor/AnimatedButton.tsx b/subprojects/frontend/src/editor/AnimatedButton.tsx index 7f6c61f0..f75d4617 100644 --- a/subprojects/frontend/src/editor/AnimatedButton.tsx +++ b/subprojects/frontend/src/editor/AnimatedButton.tsx @@ -1,6 +1,6 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import { styled } from '@mui/material/styles'; +import { styled, type SxProps, type Theme } from '@mui/material/styles'; import { type ReactNode, useLayoutEffect, useState } from 'react'; const AnimatedButtonBase = styled(Button, { @@ -9,7 +9,9 @@ const AnimatedButtonBase = styled(Button, { // Transition copied from `@mui/material/Button`. const colorTransition = theme.transitions.create( ['background-color', 'box-shadow', 'border-color', 'color'], - { duration: theme.transitions.duration.short }, + { + duration: theme.transitions.duration.short, + }, ); return { width, @@ -19,7 +21,6 @@ const AnimatedButtonBase = styled(Button, { ${colorTransition}, ${theme.transitions.create(['width'], { duration: theme.transitions.duration.short, - easing: theme.transitions.easing.easeOut, })} `, '@media (prefers-reduced-motion: reduce)': { @@ -34,6 +35,7 @@ export default function AnimatedButton({ color, disabled, startIcon, + sx, children, }: { 'aria-label'?: string; @@ -41,6 +43,7 @@ export default function AnimatedButton({ color: 'error' | 'warning' | 'primary' | 'inherit'; disabled?: boolean; startIcon: JSX.Element; + sx?: SxProps | undefined; children?: ReactNode; }): JSX.Element { const [width, setWidth] = useState(); @@ -65,9 +68,9 @@ export default function AnimatedButton({ - {showGenerateButton && } {editorStore === undefined ? ( diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index e9907e83..3f2a3bff 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts @@ -161,7 +161,7 @@ export default styled('div', { background: 'transparent', }, '.cm-cursor, .cm-cursor-primary': { - borderLeft: `2px solid ${theme.palette.highlight.cursor}`, + borderLeft: `2px solid ${theme.palette.info.main}`, }, '.cm-selectionBackground': { background: theme.palette.highlight.selection, @@ -265,7 +265,7 @@ export default styled('div', { }, '.cm-indentation-marker': { display: 'inline-block', - boxShadow: `1px 0 0 ${theme.palette.highlight.lineNumber} inset`, + boxShadow: `1px 0 0 ${theme.palette.text.disabled} inset`, '&.active': { boxShadow: `1px 0 0 ${theme.palette.text.primary} inset`, }, @@ -273,7 +273,7 @@ export default styled('div', { '.cm-scroller-selection': { position: 'absolute', right: 0, - boxShadow: `0 2px 0 ${theme.palette.highlight.cursor} inset`, + boxShadow: `0 2px 0 ${theme.palette.info.main} inset`, zIndex: 200, }, '.cm-scroller-occurrence': { @@ -286,7 +286,7 @@ export default styled('div', { const lineNumberStyle: CSSObject = { '.cm-lineNumbers': { ...editorFontStyle, - color: theme.palette.highlight.lineNumber, + color: theme.palette.text.disabled, ...(!showLineNumbers && { display: 'none !important', }), @@ -426,7 +426,7 @@ export default styled('div', { '&[aria-selected="true"]': { color: theme.palette.text.primary, background: 'transparent', - fontWeight: theme.typography.fontWeightMedium, + fontWeight: theme.typography.fontWeightBold, }, ':hover': { background: alpha( diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx index 1a32f5ce..2036fc28 100644 --- a/subprojects/frontend/src/editor/GenerateButton.tsx +++ b/subprojects/frontend/src/editor/GenerateButton.tsx @@ -1,6 +1,7 @@ import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import Button from '@mui/material/Button'; +import type { SxProps, Theme } from '@mui/material/styles'; import { observer } from 'mobx-react-lite'; import AnimatedButton from './AnimatedButton'; @@ -11,13 +12,20 @@ const GENERATE_LABEL = 'Generate'; const GenerateButton = observer(function GenerateButton({ editorStore, hideWarnings, + sx, }: { editorStore: EditorStore | undefined; hideWarnings?: boolean | undefined; + sx?: SxProps | undefined; }): JSX.Element { if (editorStore === undefined) { return ( - ); @@ -41,6 +49,7 @@ const GenerateButton = observer(function GenerateButton({ onClick={() => editorStore.nextDiagnostic()} color="error" startIcon={} + {...(sx === undefined ? {} : { sx })} > {summary} @@ -52,6 +61,7 @@ const GenerateButton = observer(function GenerateButton({ disabled={!editorStore.opened} color={warningCount > 0 ? 'warning' : 'primary'} startIcon={} + {...(sx === undefined ? {} : { sx })} > {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`} @@ -60,6 +70,7 @@ const GenerateButton = observer(function GenerateButton({ GenerateButton.defaultProps = { hideWarnings: false, + sx: undefined, }; export default GenerateButton; diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 85844d3c..7bda1ede 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx @@ -1,15 +1,13 @@ import { alpha, createTheme, - type Components, - type CSSObject, responsiveFontSizes, type Theme, type ThemeOptions, ThemeProvider as MaterialUiThemeProvider, type TypographyStyle, - type TypographyVariantsOptions, useTheme, + type CSSObject, } from '@mui/material/styles'; import { observer } from 'mobx-react-lite'; import { type ReactNode, createContext, useContext } from 'react'; @@ -22,13 +20,11 @@ interface OuterPalette { } interface HighlightPalette { - cursor: string; number: string; parameter: string; comment: string; activeLine: string; selection: string; - lineNumber: string; foldPlaceholder: string; activeLintRange: string; occurences: { @@ -49,11 +45,10 @@ declare module '@mui/material/styles' { editor: TypographyStyle; } - // eslint-disable-next-line @typescript-eslint/no-shadow -- Augment imported interface. interface TypographyVariantsOptions { - fontWeightEditorNormal: number; - fontWeightEditorBold: number; - editor: TypographyStyle; + fontWeightEditorNormal?: number; + fontWeightEditorBold?: number; + editor?: TypographyStyle; } interface Palette { @@ -62,221 +57,269 @@ declare module '@mui/material/styles' { } interface PaletteOptions { - outer: OuterPalette; - highlight: HighlightPalette; + outer?: Partial; + highlight?: Partial; } } -const typography: TypographyVariantsOptions = { - fontFamily: - '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', - fontWeightMedium: 600, - fontWeightEditorNormal: 400, - fontWeightEditorBold: 700, - button: { - // 24px line height for 14px button text means 36px high buttons. - // Making sure the button has whole pixel height reduces redering errors on Android. - lineHeight: 1.7143, - }, - editor: { - fontFamily: - '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', - fontFeatureSettings: '"liga", "calt"', - // `rem` for JetBrains MonoVariable make the text too large in Safari. - fontSize: '16px', - fontWeight: 400, - lineHeight: 1.5, - letterSpacing: 0, - textRendering: 'optimizeLegibility', - }, -}; - -const components: Components = { - MuiButton: { - styleOverrides: { - root: { - '&.rounded': { borderRadius: '50em' }, - '.MuiButton-startIcon': { marginRight: 6 }, - '.MuiButton-endIcon': { marginLeft: 6 }, +function createResponsiveTheme( + options: ThemeOptions, + overrides: ThemeOptions = {}, +): Theme { + const theme = createTheme({ + ...options, + typography: { + fontFamily: + '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', + fontWeightMedium: 600, + fontWeightEditorNormal: 400, + fontWeightEditorBold: 700, + button: { + // 24px line height for 14px button text to fix browser rounding errors. + lineHeight: 1.714286, }, - sizeSmall: { fontSize: '0.75rem' }, - sizeLarge: { fontSize: '1rem' }, - text: { '&.rounded': { padding: '6px 14px' } }, - textSizeSmall: { '&.rounded': { padding: '4px 8px' } }, - textSizeLarge: { '&.rounded': { padding: '8px 20px' } }, - outlined: { '&.rounded': { padding: '5px 13px' } }, - outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } }, - outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } }, - }, - }, - MuiToggleButton: { - styleOverrides: { - root: { '&.iconOnly': { borderRadius: '100%' } }, + editor: { + fontFamily: + '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', + fontFeatureSettings: '"liga", "calt"', + // `rem` for JetBrains MonoVariable make the text too large in Safari. + fontSize: '16px', + fontWeight: 400, + lineHeight: 1.5, + letterSpacing: 0, + textRendering: 'optimizeLegibility', + }, + ...(options.typography ?? {}), }, - }, - MuiToggleButtonGroup: { - styleOverrides: { - root: { - '&.rounded .MuiToggleButtonGroup-groupedHorizontal': { - ':first-of-type': { - paddingLeft: 15, - borderRadius: '50em 0 0 50em', - }, - ':last-of-type': { - paddingRight: 15, - borderRadius: '0 50em 50em 0', - }, - '&.MuiToggleButton-sizeSmall': { - ':first-of-type': { paddingLeft: 9 }, - ':last-of-type': { paddingRight: 9 }, + }); + + function shadedButtonStyle(color: string): CSSObject { + const opacity = theme.palette.action.focusOpacity; + return { + background: alpha(color, opacity), + ':hover': { + background: alpha(color, opacity + theme.palette.action.hoverOpacity), + '@media(hover: none)': { + background: alpha(color, opacity), + }, + }, + '&.Mui-disabled': { + background: alpha(theme.palette.text.disabled, opacity), + }, + }; + } + + const themeWithComponents = createTheme(theme, { + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + overscrollBehavior: 'contain', }, - '&.MuiToggleButton-sizeLarge': { - ':first-of-type': { paddingLeft: 21 }, - ':last-of-type': { paddingRight: 21 }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + '&.rounded': { borderRadius: '50em' }, + '.MuiButton-startIcon': { marginRight: 6 }, + '.MuiButton-endIcon': { marginLeft: 6 }, + '&.shaded': { + ...shadedButtonStyle(theme.palette.text.primary), + ...( + [ + 'primary', + 'secondary', + 'error', + 'warning', + 'success', + 'info', + ] as const + ).reduce((accumulator: CSSObject, color) => { + const colorCapitalized = + (color[0] ?? '').toUpperCase() + color.substring(1); + return { + ...accumulator, + [`&.MuiButton-text${colorCapitalized}, &.MuiButton-outlined${colorCapitalized}`]: + shadedButtonStyle(theme.palette[color].main), + }; + }, {}), + }, }, + sizeSmall: { fontSize: '0.75rem' }, + sizeLarge: { fontSize: '1rem' }, + text: { '&.rounded': { padding: '6px 14px' } }, + textSizeSmall: { '&.rounded': { padding: '4px 8px' } }, + textSizeLarge: { '&.rounded': { padding: '8px 20px' } }, + outlined: { '&.rounded': { padding: '5px 13px' } }, + outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } }, + outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } }, }, }, - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - background: alpha('#212121', 0.93), - color: '#fff', + MuiToggleButton: { + styleOverrides: { + root: { '&.iconOnly': { borderRadius: '100%' } }, + }, }, - arrow: { - color: alpha('#212121', 0.93), + MuiToggleButtonGroup: { + styleOverrides: { + root: { + '&.rounded .MuiToggleButtonGroup-groupedHorizontal': { + ':first-of-type': { + paddingLeft: 15, + borderRadius: '50em 0 0 50em', + }, + ':last-of-type': { + paddingRight: 15, + borderRadius: '0 50em 50em 0', + }, + '&.MuiToggleButton-sizeSmall': { + ':first-of-type': { paddingLeft: 9 }, + ':last-of-type': { paddingRight: 9 }, + }, + '&.MuiToggleButton-sizeLarge': { + ':first-of-type': { paddingLeft: 21 }, + ':last-of-type': { paddingRight: 21 }, + }, + }, + }, + }, + }, + MuiTooltip: { + styleOverrides: { + tooltip: { + background: alpha('#212121', 0.93), + color: '#fff', + }, + arrow: { + color: alpha('#212121', 0.93), + }, + }, }, }, - }, -}; + }); -function createResponsiveTheme(options: ThemeOptions): Theme { - return responsiveFontSizes(createTheme(options)); + const themeWithOverrides = createTheme(themeWithComponents, overrides); + + return responsiveFontSizes(themeWithOverrides); } -const lightTheme = createResponsiveTheme({ - typography, - components, - palette: { - mode: 'light', - primary: { main: '#038a99' }, - secondary: { main: '#e45649' }, - error: { main: '#ca1243' }, - warning: { main: '#c18401' }, - success: { main: '#50a14f' }, - info: { main: '#4078f2' }, - background: { - default: '#fff', - paper: '#fff', - }, - text: { - primary: '#19202b', - secondary: '#696c77', - disabled: '#a0a1a7', - }, - divider: alpha('#19202b', 0.16), - outer: { - background: '#f5f5f5', - border: '#c8c8c8', - }, - highlight: { - cursor: '#4078f2', - number: '#0084bc', - parameter: '#6a3e3e', - comment: '#a0a1a7', - activeLine: '#f5f5f5', - selection: '#c8e4fb', - lineNumber: '#a0a1a7', - foldPlaceholder: alpha('#19202b', 0.08), - activeLintRange: alpha('#f2a60d', 0.28), - occurences: { - read: alpha('#19202b', 0.16), - write: alpha('#19202b', 0.16), +const lightTheme = (() => { + const primaryText = '#19202b'; + const disabledText = '#a0a1a7'; + const darkBackground = '#f5f5f5'; + + return createResponsiveTheme({ + palette: { + mode: 'light', + primary: { main: '#038a99' }, + secondary: { main: '#e45649' }, + error: { main: '#ca1243' }, + warning: { main: '#c18401' }, + success: { main: '#50a14f' }, + info: { main: '#4078f2' }, + background: { + default: '#fff', + paper: '#fff', }, - search: { - match: '#00bcd4', - selected: '#d500f9', - contrastText: '#ffffff', + text: { + primary: primaryText, + secondary: '#696c77', + disabled: disabledText, }, - }, - }, -}); - -const darkTheme = createResponsiveTheme({ - typography: { - ...typography, - fontWeightEditorNormal: 350, - fontWeightEditorBold: 650, - }, - components: { - ...components, - MuiSnackbarContent: { - styleOverrides: { - root: { - color: '#f00', - backgroundColor: '#000', - border: `10px solid #ff0`, + divider: alpha(primaryText, 0.16), + outer: { + background: darkBackground, + border: '#c8c8c8', + }, + highlight: { + number: '#0084bc', + parameter: '#6a3e3e', + comment: disabledText, + activeLine: darkBackground, + selection: '#c8e4fb', + foldPlaceholder: alpha(primaryText, 0.08), + activeLintRange: alpha('#f2a60d', 0.28), + occurences: { + read: alpha(primaryText, 0.16), + write: alpha(primaryText, 0.16), + }, + search: { + match: '#00bcd4', + selected: '#d500f9', + contrastText: '#fff', }, }, }, - MuiTooltip: { - ...(components.MuiTooltip || {}), - styleOverrides: { - ...(components.MuiTooltip?.styleOverrides || {}), - tooltip: { - ...((components.MuiTooltip?.styleOverrides?.tooltip as - | CSSObject - | undefined) || {}), - color: '#ebebff', + }); +})(); + +const darkTheme = (() => { + const primaryText = '#ebebff'; + const secondaryText = '#abb2bf'; + const darkBackground = '#21252b'; + + return createResponsiveTheme( + { + typography: { + fontWeightEditorNormal: 350, + fontWeightEditorBold: 650, + }, + palette: { + mode: 'dark', + primary: { main: '#56b6c2' }, + secondary: { main: '#be5046' }, + error: { main: '#e06c75' }, + warning: { main: '#e5c07b' }, + success: { main: '#98c379' }, + info: { main: '#61afef' }, + background: { + default: '#282c34', + paper: darkBackground, + }, + text: { + primary: primaryText, + secondary: secondaryText, + disabled: '#5c6370', + }, + divider: alpha(secondaryText, 0.24), + outer: { + background: darkBackground, + border: '#181a1f', + }, + highlight: { + number: '#6188a6', + parameter: '#c8ae9d', + comment: '#7f848e', + activeLine: '#2c313c', + selection: '#404859', + foldPlaceholder: alpha(primaryText, 0.12), + activeLintRange: alpha('#fbc346', 0.28), + occurences: { + read: alpha(primaryText, 0.14), + write: alpha(primaryText, 0.14), + }, + search: { + match: '#33eaff', + selected: '#dd33fa', + contrastText: darkBackground, + }, }, }, }, - }, - palette: { - mode: 'dark', - primary: { main: '#56b6c2' }, - secondary: { main: '#be5046' }, - error: { main: '#e06c75' }, - warning: { main: '#e5c07b' }, - success: { main: '#98c379' }, - info: { main: '#61afef' }, - background: { - default: '#282c34', - paper: '#21252b', - }, - text: { - primary: '#ebebff', - secondary: '#abb2bf', - disabled: '#5c6370', - }, - divider: alpha('#abb2bf', 0.24), - outer: { - background: '#21252b', - border: '#181a1f', - }, - highlight: { - cursor: '#61afef', - number: '#6188a6', - parameter: '#c8ae9d', - comment: '#7f848e', - activeLine: '#2c313c', - selection: '#404859', - lineNumber: '#5c6370', - foldPlaceholder: alpha('#ebebff', 0.12), - activeLintRange: alpha('#fbc346', 0.28), - occurences: { - read: alpha('#ebebff', 0.14), - write: alpha('#ebebff', 0.14), - }, - search: { - match: '#33eaff', - selected: '#dd33fa', - contrastText: '#21252b', + { + components: { + MuiTooltip: { + styleOverrides: { + tooltip: { + color: primaryText, + }, + }, + }, }, }, - }, -}); + ); +})(); const ContrastThemeContext = createContext(undefined); -- cgit v1.2.3-54-g00ecf