diff --git a/src/App.tsx b/src/App.tsx index 17457efb8e2487d649aef2ff473cd2514147c7db..e7ee6f5cf003e09ced35ee56eccec175ddc7fbf3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,44 @@ import { ThemeProvider } from '@material-ui/styles'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import './App.css'; import CommonLogic from './components/CommonLogic/index'; import NavBar from './components/NavBar'; import { OpenSettingsContext } from './helpers/context'; -import { backgroundTheme } from './helpers/themes'; +import { themes } from './helpers/themes'; import BarChartPage from './pages/BarChartPage'; import Home from './pages/Home/index'; import SettingsPage from './pages/SettingsPage/index'; import FeatsVsFixesPage from './pages/FeatsVsFixesPage'; import CommitsPerBranchPage from './pages/CommitsPerBranch'; import TimePerIssueLabelPage from './pages/TimePerIssueLabelPage/index'; +import { Theme } from '@material-ui/core'; +import { useLocalStorage } from './helpers/hooks'; function App() { const [openSettings, setOpenSettings] = useState(false); + const [theme, setTheme] = useState<Theme>(themes.light); + const [themeName, setThemeName] = useLocalStorage<keyof typeof themes>(`theme`, 'light'); + + // Get the theme from localstorage + useEffect(() => { + if (themeName !== undefined) { + setTheme(themes[themeName]); + } + }, []); + return ( <div className="App"> <OpenSettingsContext.Provider value={[openSettings, setOpenSettings]}> - <ThemeProvider theme={backgroundTheme}> + <ThemeProvider theme={theme}> + <SettingsPage + open={openSettings} + onClose={() => setOpenSettings(false)} + setTheme={setTheme} + themeName={themeName} + setThemeName={setThemeName} + /> <NavBar title="CoolWebsiteName" /> - <SettingsPage open={openSettings} onClose={() => setOpenSettings(false)} /> <Router> <CommonLogic /> <Switch> diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx index 8f6891d7f82fe67d8828a4fc5c0b8bc614313073..6adb94a628d43e1109fbd9c7ee7471ca536f8900 100644 --- a/src/components/NavBar/index.tsx +++ b/src/components/NavBar/index.tsx @@ -38,7 +38,7 @@ export default function NavBar(props: MenuProps) { return ( <div> - <AppBar position="sticky" color="secondary"> + <AppBar position="sticky" className={classes.appBar}> <Toolbar className={classes.toolBar}> <Link href={'/'} underline={'none'}> <Typography variant="h6" component="div" className={classes.title}> diff --git a/src/components/NavBar/styles.ts b/src/components/NavBar/styles.ts index 54fbef3b5c9dbb920828911742eadb86978967a6..88b7006e4c4b8d027b14251237cb06351a7e4201 100644 --- a/src/components/NavBar/styles.ts +++ b/src/components/NavBar/styles.ts @@ -9,12 +9,15 @@ const useStyles = makeStyles((theme) => color: theme.palette.secondary.contrastText, }, menuItem: { - color: theme.palette.secondary.main, + color: 'black', }, toolBar: { display: 'flex', justifyContent: 'space-between', }, + appBar: { + backgroundColor: theme.palette.secondary.main, + }, linkContainer: { display: 'flex', }, diff --git a/src/components/PageContainer/index.tsx b/src/components/PageContainer/index.tsx index aa4f62d6b5f41c4bc5f35b9e00155f05cc87a2a1..4ca9d0c9d297603e0fc0e6402a2ecd5441ea04b7 100644 --- a/src/components/PageContainer/index.tsx +++ b/src/components/PageContainer/index.tsx @@ -9,7 +9,7 @@ type PageContainerProps = { export default function PageContainer(props: PageContainerProps) { const style = useStyles(); return ( - <Grid container> + <Grid container className={style.background}> <Container className={style.main} maxWidth="md"> {props.title ? <h3>{props.title}</h3> : false} {props.children} diff --git a/src/components/PageContainer/styles.ts b/src/components/PageContainer/styles.ts index d908698745a17b124e315edcb6773a35f5542d9e..a60d0a81e77e4501e5edd37cf039f27dadc56911 100644 --- a/src/components/PageContainer/styles.ts +++ b/src/components/PageContainer/styles.ts @@ -11,6 +11,11 @@ const useStyles = makeStyles((theme) => borderRadius: '0 0 10px 10px', }, }, + background: { + backgroundColor: theme.palette.background.default, + width: '100%', + minHeight: '100vh', + }, }), ); diff --git a/src/components/Popup/index.tsx b/src/components/Popup/index.tsx index 71054d2956b528eb0437ff786a9d799c80de7c5e..7d86312e1cecef3056651a6062e7becb103db26d 100644 --- a/src/components/Popup/index.tsx +++ b/src/components/Popup/index.tsx @@ -2,9 +2,10 @@ import { XIcon } from '@heroicons/react/outline'; import { Dialog, DialogProps } from '@material-ui/core'; import IconButton from '@material-ui/core/IconButton'; import { useStyles } from './styles'; +import { ReactNode } from 'react'; type PopupProps = { - children?: JSX.Element; + children?: ReactNode; open: boolean; onClose: () => void; title: string; diff --git a/src/components/Popup/styles.ts b/src/components/Popup/styles.ts index d21a1914f23c45d6870dfd14fa4acbefcd1a0076..2f4b62813efc5dc42338f723d33709a62d890b8f 100644 --- a/src/components/Popup/styles.ts +++ b/src/components/Popup/styles.ts @@ -16,6 +16,7 @@ export const useStyles = makeStyles((theme: Theme) => }, dialog: { backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, }, }), ); diff --git a/src/components/ThemeRadioGroup/index.tsx b/src/components/ThemeRadioGroup/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dfbc7fe9467ad834f9558152929c3ae4a7b241a4 --- /dev/null +++ b/src/components/ThemeRadioGroup/index.tsx @@ -0,0 +1,37 @@ +import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@material-ui/core'; +import { useStyles } from './styles'; +import { ChangeEvent } from 'react'; +import { themes } from '../../helpers/themes'; + +type ThemeRadioGroupProps = { + onChange: (event: ChangeEvent<HTMLInputElement>, value: string) => void; + themeName: string; +}; + +/** + * A component giving the user the choice between all the current themes + */ +export default function ThemeRadioGroup(props: ThemeRadioGroupProps) { + const classes = useStyles(); + return ( + <FormControl component="fieldset"> + <FormLabel component="legend" className={classes.legend}> + Theme + </FormLabel> + <RadioGroup onChange={props.onChange}> + {Object.keys(themes).map((themeName) => { + return ( + <FormControlLabel + className={classes.radio} + key={themeName} + value={themeName} + checked={themeName === props.themeName} + label={themeName} + control={<Radio className={classes.radio} />} + /> + ); + })} + </RadioGroup> + </FormControl> + ); +} diff --git a/src/components/ThemeRadioGroup/styles.ts b/src/components/ThemeRadioGroup/styles.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbbbcf24c46115196dea244993d1a5deffc478fd --- /dev/null +++ b/src/components/ThemeRadioGroup/styles.ts @@ -0,0 +1,13 @@ +import { createStyles, makeStyles, Theme } from '@material-ui/core'; + +export const useStyles = makeStyles((theme: Theme) => + createStyles({ + radio: { + color: theme.palette.primary.contrastText + '!important', + }, + legend: { + color: theme.palette.primary.contrastText + '!important', + marginBottom: '1rem', + }, + }), +); diff --git a/src/helpers/hooks.ts b/src/helpers/hooks.ts index 43e7110e25fa69e7058168c75be51f77c0dec720..96bd49f21ffef581c81d9954c8ac099ec54a3efb 100644 --- a/src/helpers/hooks.ts +++ b/src/helpers/hooks.ts @@ -16,8 +16,10 @@ function useStorage<ValueType>( storageObject: StorageObject, ): [ValueType, Dispatch<SetStateAction<ValueType>>] { const [value, setValue] = useState<ValueType>(() => { - const jsonValue = storageObject.getItem(key); - if (jsonValue != null) return JSON.parse(jsonValue); + const value = storageObject.getItem(key); + if (value != null) { + return JSON.parse(value); + } return defaultValue; }); diff --git a/src/helpers/themes.ts b/src/helpers/themes.ts index 3887d325a53ed4d15234fdbb1d3a3c06657dd4c7..663c60ef040a7472f65b61b3ab90be7c0ad14aae 100644 --- a/src/helpers/themes.ts +++ b/src/helpers/themes.ts @@ -1,18 +1,72 @@ import { createTheme } from '@material-ui/core'; -export const backgroundTheme = createTheme({ - palette: { - primary: { - main: '#f1f1e9', // Temporary secondary theme - contrastText: '#1b1f28', +const lightTheme = createTheme( + { + palette: { + primary: { + main: '#f8f6f3', + contrastText: '#1b1f28', + }, + secondary: { + main: '#1f1f1e', + contrastText: '#dedede', + }, + info: { + main: '#f1f6f6', + contrastText: '#3d393e', + }, + background: { + default: '#f4fffe', + }, }, - secondary: { - main: '#342f32', // Temporary primary theme - contrastText: '#dedede', + }, + { name: 'light' }, +); + +const darkTheme = createTheme( + { + palette: { + primary: { + main: '#0c0c0c', + contrastText: '#d4daec', + }, + secondary: { + main: '#222020', + contrastText: '#d7cdcd', + }, + info: { + main: '#323737', + contrastText: '#afa7b1', + }, + background: { + default: '#070606', + }, }, - info: { - main: '#f1f6f6', // Temporary info theme - contrastText: '#3d393e', + }, + { name: 'dark' }, +); + +const funkTheme = createTheme( + { + palette: { + primary: { + main: '#a70f0f', + contrastText: '#022c53', + }, + secondary: { + main: '#316d72', + contrastText: '#31ff0d', + }, + info: { + main: '#941681', + contrastText: '#e9cb14', + }, + background: { + default: '#03801f', + }, }, }, -}); + { name: 'dark' }, +); + +export const themes = { light: lightTheme, dark: darkTheme, wayTooManyColors: funkTheme }; diff --git a/src/pages/FeatsVsFixesPage/index.tsx b/src/pages/FeatsVsFixesPage/index.tsx index 9d112126e6273d861edbc414f7a9fecf233c8fcf..607980aaa94be86f8e011c5ef9bf85e3f91d2554 100644 --- a/src/pages/FeatsVsFixesPage/index.tsx +++ b/src/pages/FeatsVsFixesPage/index.tsx @@ -6,6 +6,7 @@ import { getAllCommitsFromAPI } from '../../helpers/api-calls'; import { useSessionStorage } from '../../helpers/hooks'; import { CommitAuthor } from '../../helpers/types'; import { parseCommitData } from './utils'; +import { useStyles } from './styles'; export default function FeatsVsFixesPage() { // The retrieved athor data form the api. @@ -16,6 +17,7 @@ export default function FeatsVsFixesPage() { new Array(authorData.length).fill(true), ); + const classes = useStyles(); const featsFixesGraphData: Array<{ commitType: string; val: number }> = [ { commitType: 'feat', val: 0 }, { commitType: 'fix', val: 0 }, @@ -54,6 +56,7 @@ export default function FeatsVsFixesPage() { <div key={JSON.stringify(m)}> Person {i + 1} <Checkbox + className={classes.checkbox} checked={selectedAuthors[i]} onChange={() => { if (!selectedAuthors) return; // selectedAuthors will never be undefined diff --git a/src/pages/FeatsVsFixesPage/styles.ts b/src/pages/FeatsVsFixesPage/styles.ts new file mode 100644 index 0000000000000000000000000000000000000000..24785b8ae7ccafb8d0c8e2ea6b4409ef378b2bfb --- /dev/null +++ b/src/pages/FeatsVsFixesPage/styles.ts @@ -0,0 +1,9 @@ +import { createStyles, makeStyles, Theme } from '@material-ui/core'; + +export const useStyles = makeStyles((theme: Theme) => + createStyles({ + checkbox: { + color: theme.palette.primary.contrastText + '!important', + }, + }), +); diff --git a/src/pages/SettingsPage/index.tsx b/src/pages/SettingsPage/index.tsx index 987e0998707f220999e7d493638698ef91825c40..441c7266b9c711b613da2f0ac8ab97ee89bc4f7f 100644 --- a/src/pages/SettingsPage/index.tsx +++ b/src/pages/SettingsPage/index.tsx @@ -1,14 +1,26 @@ import Popup from '../../components/Popup'; +import { Theme } from '@material-ui/core'; +import { themes } from '../../helpers/themes'; +import { ChangeEvent } from 'react'; +import ThemeRadioGroup from '../../components/ThemeRadioGroup'; type SettingsPageProps = { open: boolean; onClose: () => void; + setTheme: (t: Theme) => void; + themeName: string; + setThemeName: (theme: keyof typeof themes) => void; }; export default function SettingsPage(props: SettingsPageProps) { + function changeTheme(event: ChangeEvent<HTMLInputElement>, value: string) { + const theme = value as keyof typeof themes; + props.setTheme(themes[theme]); + props.setThemeName(theme); + } return ( <Popup title="Settings" open={props.open} onClose={props.onClose} maxWidth="sm"> - <div>Here comes the settings page</div> + <ThemeRadioGroup onChange={changeTheme} themeName={props.themeName} /> </Popup> ); } diff --git a/src/pages/TimePerIssueLabelPage/index.tsx b/src/pages/TimePerIssueLabelPage/index.tsx index dcf7c8cdd3aee9b5e83a06cf71ce101392aa750a..e2b504f1e0d63bd3a23b527e9e4b1f5f893b59b2 100644 --- a/src/pages/TimePerIssueLabelPage/index.tsx +++ b/src/pages/TimePerIssueLabelPage/index.tsx @@ -52,10 +52,11 @@ export default function TimePerIssueLabelPage() { </p> <div> <FormControl className={classes.dropdown}> - <InputLabel id="demo-simple-select-label">Showing labels</InputLabel> + <InputLabel>Showing labels</InputLabel> <Select labelId="demo-simple-select-label" id="demo-simple-select" + className={classes.select} value={selected} onChange={(e) => { const newValue = e.target.value as string; diff --git a/src/pages/TimePerIssueLabelPage/styles.ts b/src/pages/TimePerIssueLabelPage/styles.ts index 8d699d24497bbdf13da4b371cc493efa9abe4e76..37c009a2807b5ac7a2e2c5f277a5b52a378898c0 100644 --- a/src/pages/TimePerIssueLabelPage/styles.ts +++ b/src/pages/TimePerIssueLabelPage/styles.ts @@ -1,9 +1,17 @@ -import { createStyles, makeStyles } from '@material-ui/core'; +import { createStyles, makeStyles, Theme } from '@material-ui/core'; -const useStyles = makeStyles(() => +const useStyles = makeStyles((theme: Theme) => createStyles({ dropdown: { width: '10em', + '& label': { + color: theme.palette.primary.contrastText + '!important', + }, + }, + select: { + color: theme.palette.info.contrastText + '!important', + backgroundColor: theme.palette.info.main, + paddingLeft: '1rem', }, }), );