diff options
author | Michael Muré <batolettre@gmail.com> | 2021-03-29 10:58:10 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2021-03-29 11:02:08 +0200 |
commit | aa0449a3eafa42c8c0d44bbdc2b79c5f47bd2d32 (patch) | |
tree | cd0c72a0229bb38cf8096ce198384cdd972f66e2 /webui/src/components | |
parent | 5215634d0dca37c545904fbc8a12ddd9b8eb72df (diff) | |
parent | e985653701e8438e27ee5f925fd0aa7c0eef09fe (diff) | |
download | git-bug-aa0449a3eafa42c8c0d44bbdc2b79c5f47bd2d32.tar.gz |
Merge remote-tracking branch 'origin/master' into dag-entity
Diffstat (limited to 'webui/src/components')
-rw-r--r-- | webui/src/components/BackToListButton.tsx | 36 | ||||
-rw-r--r-- | webui/src/components/BugTitleForm/BugTitleForm.tsx | 42 | ||||
-rw-r--r-- | webui/src/components/BugTitleForm/BugTitleInput.tsx | 40 | ||||
-rw-r--r-- | webui/src/components/CloseBugButton/CloseBugButton.tsx | 13 | ||||
-rw-r--r-- | webui/src/components/CommentInput/CommentInput.tsx | 5 | ||||
-rw-r--r-- | webui/src/components/Content/PreTag.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/Header/Header.tsx | 78 | ||||
-rw-r--r-- | webui/src/components/ReopenBugButton/ReopenBugButton.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/Themer.tsx | 65 |
9 files changed, 245 insertions, 38 deletions
diff --git a/webui/src/components/BackToListButton.tsx b/webui/src/components/BackToListButton.tsx new file mode 100644 index 00000000..7ca53ad0 --- /dev/null +++ b/webui/src/components/BackToListButton.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +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" + href="/" + > + <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..a7d5a820 100644 --- a/webui/src/components/BugTitleForm/BugTitleForm.tsx +++ b/webui/src/components/BugTitleForm/BugTitleForm.tsx @@ -1,12 +1,6 @@ import React, { useState } from 'react'; -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 +8,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 +40,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 +70,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 +107,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 +120,7 @@ function BugTitleForm({ bug }: Props) { /> <div className={classes.editButtonContainer}> <Button + className={classes.saveButton} size="small" variant="contained" type="submit" @@ -173,7 +159,7 @@ function BugTitleForm({ bug }: Props) { variant="contained" href="/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 }; |