aboutsummaryrefslogtreecommitdiffstats
path: root/webui/src/components
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2021-04-09 13:01:14 +0200
committerMichael Muré <batolettre@gmail.com>2021-04-09 13:01:14 +0200
commit1520f678f7a2bc6e01d9b01df5ce49f2f46be7d7 (patch)
treef6d71c1f29cf06ccab9e4ae434b19ab17caa4385 /webui/src/components
parent0fd570171d171aa574d7f01d6033a9c01d668465 (diff)
parentbc5f618eba812859bf87ce2c31b278bd518d4555 (diff)
downloadgit-bug-1520f678f7a2bc6e01d9b01df5ce49f2f46be7d7.tar.gz
Merge remote-tracking branch 'origin/master' into dev-gh-bridge
Diffstat (limited to 'webui/src/components')
-rw-r--r--webui/src/components/BackToListButton.tsx38
-rw-r--r--webui/src/components/BugTitleForm/BugTitleForm.tsx46
-rw-r--r--webui/src/components/BugTitleForm/BugTitleInput.tsx40
-rw-r--r--webui/src/components/CloseBugButton/CloseBugButton.tsx13
-rw-r--r--webui/src/components/CommentInput/CommentInput.tsx5
-rw-r--r--webui/src/components/Content/PreTag.tsx2
-rw-r--r--webui/src/components/Header/Header.tsx78
-rw-r--r--webui/src/components/ReopenBugButton/ReopenBugButton.tsx2
-rw-r--r--webui/src/components/Themer.tsx65
9 files changed, 250 insertions, 39 deletions
diff --git a/webui/src/components/BackToListButton.tsx b/webui/src/components/BackToListButton.tsx
new file mode 100644
index 00000000..41e1d68a
--- /dev/null
+++ b/webui/src/components/BackToListButton.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import Button from '@material-ui/core/Button';
+import { makeStyles } from '@material-ui/core/styles';
+import ArrowBackIcon from '@material-ui/icons/ArrowBack';
+
+const useStyles = makeStyles((theme) => ({
+ backButton: {
+ position: 'sticky',
+ top: '80px',
+ backgroundColor: theme.palette.primary.dark,
+ color: theme.palette.primary.contrastText,
+ '&:hover': {
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastText,
+ },
+ },
+}));
+
+function BackToListButton() {
+ const classes = useStyles();
+
+ return (
+ <Button
+ variant="contained"
+ className={classes.backButton}
+ aria-label="back to issue list"
+ component={Link}
+ to="/"
+ >
+ <ArrowBackIcon />
+ Back to List
+ </Button>
+ );
+}
+
+export default BackToListButton;
diff --git a/webui/src/components/BugTitleForm/BugTitleForm.tsx b/webui/src/components/BugTitleForm/BugTitleForm.tsx
index c47eab31..529f23a2 100644
--- a/webui/src/components/BugTitleForm/BugTitleForm.tsx
+++ b/webui/src/components/BugTitleForm/BugTitleForm.tsx
@@ -1,12 +1,7 @@
import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
-import {
- Button,
- fade,
- makeStyles,
- TextField,
- Typography,
-} from '@material-ui/core';
+import { Button, makeStyles, Typography } from '@material-ui/core';
import { TimelineDocument } from '../../pages/bug/TimelineQuery.generated';
import IfLoggedIn from '../IfLoggedIn/IfLoggedIn';
@@ -14,6 +9,7 @@ import Author from 'src/components/Author';
import Date from 'src/components/Date';
import { BugFragment } from 'src/pages/bug/Bug.generated';
+import BugTitleInput from './BugTitleInput';
import { useSetTitleMutation } from './SetTitle.generated';
/**
@@ -45,26 +41,16 @@ const useStyles = makeStyles((theme) => ({
marginLeft: theme.spacing(2),
},
greenButton: {
- marginLeft: '8px',
- backgroundColor: '#2ea44fd9',
- color: '#fff',
+ marginLeft: theme.spacing(1),
+ backgroundColor: theme.palette.success.main,
+ color: theme.palette.success.contrastText,
'&:hover': {
- backgroundColor: '#2ea44f',
+ backgroundColor: theme.palette.success.dark,
+ color: theme.palette.primary.contrastText,
},
},
- titleInput: {
- borderRadius: theme.shape.borderRadius,
- borderColor: fade(theme.palette.primary.main, 0.2),
- borderStyle: 'solid',
- borderWidth: '1px',
- backgroundColor: fade(theme.palette.primary.main, 0.05),
- padding: theme.spacing(0, 0),
- minWidth: 336,
- transition: theme.transitions.create([
- 'width',
- 'borderColor',
- 'backgroundColor',
- ]),
+ saveButton: {
+ marginRight: theme.spacing(1),
},
}));
@@ -85,7 +71,7 @@ function BugTitleForm({ bug }: Props) {
function isFormValid() {
if (issueTitleInput) {
- return issueTitleInput.value.length > 0 ? true : false;
+ return issueTitleInput.value.length > 0;
} else {
return false;
}
@@ -122,11 +108,11 @@ function BugTitleForm({ bug }: Props) {
function editableBugTitle() {
return (
<form className={classes.headerTitle} onSubmit={submitNewTitle}>
- <TextField
+ <BugTitleInput
inputRef={(node) => {
issueTitleInput = node;
}}
- className={classes.titleInput}
+ label="Title"
variant="outlined"
fullWidth
margin="dense"
@@ -135,6 +121,7 @@ function BugTitleForm({ bug }: Props) {
/>
<div className={classes.editButtonContainer}>
<Button
+ className={classes.saveButton}
size="small"
variant="contained"
type="submit"
@@ -171,9 +158,10 @@ function BugTitleForm({ bug }: Props) {
className={classes.greenButton}
size="small"
variant="contained"
- href="/new"
+ component={Link}
+ to="/new"
>
- New issue
+ New bug
</Button>
</div>
)}
diff --git a/webui/src/components/BugTitleForm/BugTitleInput.tsx b/webui/src/components/BugTitleForm/BugTitleInput.tsx
new file mode 100644
index 00000000..d2b060a2
--- /dev/null
+++ b/webui/src/components/BugTitleForm/BugTitleInput.tsx
@@ -0,0 +1,40 @@
+import { createStyles, fade, withStyles, TextField } from '@material-ui/core';
+import { Theme } from '@material-ui/core/styles';
+
+const BugTitleInput = withStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ '& .MuiInputLabel-outlined': {
+ color: theme.palette.text.primary,
+ },
+ '& input:valid + fieldset': {
+ color: theme.palette.text.primary,
+ borderColor: theme.palette.divider,
+ borderWidth: 2,
+ },
+ '& input:valid:hover + fieldset': {
+ color: theme.palette.text.primary,
+ borderColor: fade(theme.palette.divider, 0.3),
+ borderWidth: 2,
+ },
+ '& input:valid:focus + fieldset': {
+ color: theme.palette.text.primary,
+ borderColor: theme.palette.divider,
+ },
+ '& input:invalid + fieldset': {
+ borderColor: theme.palette.error.main,
+ borderWidth: 2,
+ },
+ '& input:invalid:hover + fieldset': {
+ borderColor: theme.palette.error.main,
+ borderWidth: 2,
+ },
+ '& input:invalid:focus + fieldset': {
+ borderColor: theme.palette.error.main,
+ borderWidth: 2,
+ },
+ },
+ })
+)(TextField);
+
+export default BugTitleInput;
diff --git a/webui/src/components/CloseBugButton/CloseBugButton.tsx b/webui/src/components/CloseBugButton/CloseBugButton.tsx
index 19f56cab..9f098483 100644
--- a/webui/src/components/CloseBugButton/CloseBugButton.tsx
+++ b/webui/src/components/CloseBugButton/CloseBugButton.tsx
@@ -1,12 +1,21 @@
import React from 'react';
import Button from '@material-ui/core/Button';
+import { makeStyles, Theme } from '@material-ui/core/styles';
+import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
import { BugFragment } from 'src/pages/bug/Bug.generated';
import { TimelineDocument } from 'src/pages/bug/TimelineQuery.generated';
import { useCloseBugMutation } from './CloseBug.generated';
+const useStyles = makeStyles((theme: Theme) => ({
+ closeIssueIcon: {
+ color: theme.palette.secondary.dark,
+ paddingTop: '0.1rem',
+ },
+}));
+
interface Props {
bug: BugFragment;
disabled: boolean;
@@ -14,6 +23,7 @@ interface Props {
function CloseBugButton({ bug, disabled }: Props) {
const [closeBug, { loading, error }] = useCloseBugMutation();
+ const classes = useStyles();
function closeBugAction() {
closeBug({
@@ -45,8 +55,9 @@ function CloseBugButton({ bug, disabled }: Props) {
variant="contained"
onClick={() => closeBugAction()}
disabled={bug.status === 'CLOSED' || disabled}
+ startIcon={<ErrorOutlineIcon className={classes.closeIssueIcon} />}
>
- Close issue
+ Close bug
</Button>
</div>
);
diff --git a/webui/src/components/CommentInput/CommentInput.tsx b/webui/src/components/CommentInput/CommentInput.tsx
index 86cc7dbb..c574538e 100644
--- a/webui/src/components/CommentInput/CommentInput.tsx
+++ b/webui/src/components/CommentInput/CommentInput.tsx
@@ -51,6 +51,7 @@ const a11yProps = (index: number) => ({
type Props = {
inputProps?: any;
+ inputText?: string;
loading: boolean;
onChange: (comment: string) => void;
};
@@ -62,8 +63,8 @@ type Props = {
* @param loading Disable input when component not ready yet
* @param onChange Callback to return input value changes
*/
-function CommentInput({ inputProps, loading, onChange }: Props) {
- const [input, setInput] = useState<string>('');
+function CommentInput({ inputProps, inputText, loading, onChange }: Props) {
+ const [input, setInput] = useState<string>(inputText ? inputText : '');
const [tab, setTab] = useState(0);
const classes = useStyles();
diff --git a/webui/src/components/Content/PreTag.tsx b/webui/src/components/Content/PreTag.tsx
index 5256ab12..8e352153 100644
--- a/webui/src/components/Content/PreTag.tsx
+++ b/webui/src/components/Content/PreTag.tsx
@@ -11,7 +11,7 @@ const useStyles = makeStyles({
const PreTag = (props: React.HTMLProps<HTMLPreElement>) => {
const classes = useStyles();
- return <pre className={classes.tag} {...props}></pre>;
+ return <pre className={classes.tag} {...props} />;
};
export default PreTag;
diff --git a/webui/src/components/Header/Header.tsx b/webui/src/components/Header/Header.tsx
index 3e39b5f3..3064f6e4 100644
--- a/webui/src/components/Header/Header.tsx
+++ b/webui/src/components/Header/Header.tsx
@@ -1,11 +1,15 @@
import React from 'react';
-import { Link } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
import AppBar from '@material-ui/core/AppBar';
+import Tab, { TabProps } from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
import Toolbar from '@material-ui/core/Toolbar';
+import Tooltip from '@material-ui/core/Tooltip/Tooltip';
import { makeStyles } from '@material-ui/core/styles';
import CurrentIdentity from '../CurrentIdentity/CurrentIdentity';
+import { LightSwitch } from '../Themer';
const useStyles = makeStyles((theme) => ({
offset: {
@@ -14,35 +18,99 @@ const useStyles = makeStyles((theme) => ({
filler: {
flexGrow: 1,
},
+ appBar: {
+ backgroundColor: theme.palette.primary.dark,
+ color: theme.palette.primary.contrastText,
+ },
appTitle: {
...theme.typography.h6,
- color: 'white',
+ color: theme.palette.primary.contrastText,
textDecoration: 'none',
display: 'flex',
alignItems: 'center',
},
+ lightSwitch: {
+ padding: '0 20px',
+ },
logo: {
height: '42px',
marginRight: theme.spacing(2),
},
}));
+function a11yProps(index: any) {
+ return {
+ id: `nav-tab-${index}`,
+ 'aria-controls': `nav-tabpanel-${index}`,
+ };
+}
+
+const DisabledTabWithTooltip = (props: TabProps) => {
+ /*The span elements around disabled tabs are needed, as the tooltip
+ * won't be triggered by disabled elements.
+ * See: https://material-ui.com/components/tooltips/#disabled-elements
+ * This must be done in a wrapper component, otherwise the TabS component
+ * cannot pass it styles down to the Tab component. Resulting in (console)
+ * warnings. This wrapper acceps the passed down TabProps and pass it around
+ * the span element to the Tab component.
+ */
+ const msg = `This feature doesn't exist yet. Come help us build it.`;
+ return (
+ <Tooltip title={msg}>
+ <span>
+ <Tab disabled {...props} />
+ </span>
+ </Tooltip>
+ );
+};
+
function Header() {
const classes = useStyles();
+ const location = useLocation();
+ const [selectedTab, setTab] = React.useState(location.pathname);
+
+ const handleTabClick = (
+ event: React.ChangeEvent<{}>,
+ newTabValue: string
+ ) => {
+ setTab(newTabValue);
+ };
return (
<>
- <AppBar position="fixed" color="primary">
+ <AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<Link to="/" className={classes.appTitle}>
- <img src="/logo.svg" className={classes.logo} alt="git-bug" />
+ <img src="/logo.svg" className={classes.logo} alt="git-bug logo" />
git-bug
</Link>
- <div className={classes.filler}></div>
+ <div className={classes.filler} />
+ <div className={classes.lightSwitch}>
+ <LightSwitch />
+ </div>
<CurrentIdentity />
</Toolbar>
</AppBar>
<div className={classes.offset} />
+ <Tabs
+ centered
+ value={selectedTab}
+ onChange={handleTabClick}
+ aria-label="nav tabs"
+ >
+ <DisabledTabWithTooltip label="Code" value="/code" {...a11yProps(1)} />
+ <Tab label="Bugs" value="/" component={Link} to="/" {...a11yProps(2)} />
+ <DisabledTabWithTooltip
+ label="Pull Requests"
+ value="/pulls"
+ {...a11yProps(3)}
+ />
+ <DisabledTabWithTooltip
+ label="Settings"
+ value="/settings"
+ {...a11yProps(4)}
+ />
+ </Tabs>
</>
);
}
diff --git a/webui/src/components/ReopenBugButton/ReopenBugButton.tsx b/webui/src/components/ReopenBugButton/ReopenBugButton.tsx
index 195ca512..e3e792fc 100644
--- a/webui/src/components/ReopenBugButton/ReopenBugButton.tsx
+++ b/webui/src/components/ReopenBugButton/ReopenBugButton.tsx
@@ -46,7 +46,7 @@ function ReopenBugButton({ bug, disabled }: Props) {
onClick={() => openBugAction()}
disabled={bug.status === 'OPEN' || disabled}
>
- Reopen issue
+ Reopen bug
</Button>
</div>
);
diff --git a/webui/src/components/Themer.tsx b/webui/src/components/Themer.tsx
new file mode 100644
index 00000000..b4877974
--- /dev/null
+++ b/webui/src/components/Themer.tsx
@@ -0,0 +1,65 @@
+import React, { createContext, useContext, useState } from 'react';
+
+import { fade, ThemeProvider } from '@material-ui/core';
+import IconButton from '@material-ui/core/IconButton/IconButton';
+import Tooltip from '@material-ui/core/Tooltip/Tooltip';
+import { Theme } from '@material-ui/core/styles';
+import { NightsStayRounded, WbSunnyRounded } from '@material-ui/icons';
+import { makeStyles } from '@material-ui/styles';
+
+const ThemeContext = createContext({
+ toggleMode: () => {},
+ mode: '',
+});
+
+const useStyles = makeStyles((theme: Theme) => ({
+ iconButton: {
+ color: fade(theme.palette.primary.contrastText, 0.5),
+ },
+}));
+
+const LightSwitch = () => {
+ const { mode, toggleMode } = useContext(ThemeContext);
+ const nextMode = mode === 'light' ? 'dark' : 'light';
+ const description = `Switch to ${nextMode} theme`;
+ const classes = useStyles();
+
+ return (
+ <Tooltip title={description}>
+ <IconButton
+ onClick={toggleMode}
+ aria-label={description}
+ className={classes.iconButton}
+ >
+ {mode === 'light' ? <WbSunnyRounded /> : <NightsStayRounded />}
+ </IconButton>
+ </Tooltip>
+ );
+};
+
+type Props = {
+ children: React.ReactNode;
+ lightTheme: Theme;
+ darkTheme: Theme;
+};
+const Themer = ({ children, lightTheme, darkTheme }: Props) => {
+ const savedMode = localStorage.getItem('themeMode');
+ const preferedMode = savedMode != null ? savedMode : 'light';
+ const [mode, setMode] = useState(preferedMode);
+
+ const toggleMode = () => {
+ const preferedMode = mode === 'light' ? 'dark' : 'light';
+ localStorage.setItem('themeMode', preferedMode);
+ setMode(preferedMode);
+ };
+
+ const preferedTheme = mode === 'dark' ? darkTheme : lightTheme;
+
+ return (
+ <ThemeContext.Provider value={{ toggleMode: toggleMode, mode: mode }}>
+ <ThemeProvider theme={preferedTheme}>{children}</ThemeProvider>
+ </ThemeContext.Provider>
+ );
+};
+
+export { Themer as default, LightSwitch };