Merge pull request #408 from sgraczyk/feature/407-header

#407 Rewrite layout/Header to use TypeScript and hooks
This commit is contained in:
Stefan Ålund
2020-03-30 14:36:11 +02:00
committed by GitHub
4 changed files with 213 additions and 160 deletions
-157
View File
@@ -1,157 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Typography, withStyles, Tooltip } from '@material-ui/core';
import { Theme } from '../Page/Page';
// import { Link } from 'shared/components';
import Waves from './Waves';
import Helmet from 'react-helmet';
class Header extends Component {
static propTypes = {
type: PropTypes.string,
typeLink: PropTypes.string,
title: PropTypes.node.isRequired,
tooltip: PropTypes.string,
subtitle: PropTypes.node,
pageTitleOverride: PropTypes.string,
style: PropTypes.object,
component: PropTypes.object,
};
typeFragment() {
const { type, typeLink, classes } = this.props;
if (!type) {
return null;
}
return typeLink ? (
// <Link to={typeLink}>
<Typography className={classes.type}>{type}</Typography>
) : (
// </Link>
<Typography className={classes.type}>{type}</Typography>
);
}
titleFragment() {
const { title, pageTitleOverride, classes, tooltip } = this.props;
const FinalTitle = (
<Typography className={classes.title} variant="h4">
{title || pageTitleOverride}
</Typography>
);
if (tooltip) {
return (
<Tooltip title={tooltip} placement="top-start">
{FinalTitle}
</Tooltip>
);
}
return FinalTitle;
}
subtitleFragment() {
const { subtitle, classes } = this.props;
if (!subtitle) {
return null;
} else if (typeof subtitle !== 'string') {
return subtitle;
}
return (
<Typography className={classes.subtitle} variant="subtitle1">
{subtitle}
</Typography>
);
}
render() {
const { title, pageTitleOverride, children, style, classes } = this.props;
const pageTitle = pageTitleOverride || title;
return (
<Fragment>
<Helmet
titleTemplate={`${pageTitle} | %s | Backstage`}
defaultTitle={`${pageTitle} | Backstage`}
/>
<Theme.Consumer>
{theme => (
<header style={style} className={classes.header}>
<Waves theme={theme} />
<div className={classes.leftItemsBox}>
{this.typeFragment()}
{this.titleFragment()}
{this.subtitleFragment()}
</div>
<div className={classes.rightItemsBox}>{children}</div>
</header>
)}
</Theme.Consumer>
</Fragment>
);
}
}
const styles = theme => ({
header: {
gridArea: 'pageHeader',
padding: theme.spacing(3),
minHeight: 118,
width: '100%',
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
position: 'relative',
zIndex: 100,
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-end',
alignItems: 'center',
},
leftItemsBox: {
flex: '1 1 auto',
},
rightItemsBox: {
flex: '0 1 auto',
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
marginRight: theme.spacing(6),
},
title: {
color: theme.palette.bursts.fontColor,
lineHeight: '1.0em',
wordBreak: 'break-all',
fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))',
marginBottom: theme.spacing(1),
},
subtitle: {
color: 'rgba(255, 255, 255, 0.8)',
lineHeight: '1.0em',
},
type: {
textTransform: 'uppercase',
fontSize: 9,
opacity: 0.8,
marginBottom: 10,
color: theme.palette.bursts.fontColor,
},
});
export default withStyles(styles)(Header);
@@ -36,18 +36,24 @@ describe('<Header/>', () => {
});
it('should override document title', () => {
const rendered = render(wrapInThemedTestApp(<Header title="Title1" pageTitleOverride="Title2" />));
const rendered = render(
wrapInThemedTestApp(<Header title="Title1" pageTitleOverride="Title2" />),
);
rendered.getByText('Title1');
rendered.getByText('defaultTitle: Title2 | Backstage');
});
it('should have subtitle', () => {
const rendered = render(wrapInThemedTestApp(<Header title="Title" subtitle="Subtitle" />));
const rendered = render(
wrapInThemedTestApp(<Header title="Title" subtitle="Subtitle" />),
);
rendered.getByText('Subtitle');
});
it('should have type rendered', () => {
const rendered = render(wrapInThemedTestApp(<Header title="Title" type="tool" />));
const rendered = render(
wrapInThemedTestApp(<Header title="Title" type="tool" />),
);
rendered.getByText('tool');
});
});
+197
View File
@@ -0,0 +1,197 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { Fragment, ReactNode, CSSProperties, FC } from 'react';
import Helmet from 'react-helmet';
import { Typography, Tooltip, makeStyles } from '@material-ui/core';
import { Theme } from '../Page/Page';
// import { Link } from 'shared/components';
import { BackstageTheme } from '../../theme/theme';
import Waves from './Waves';
const useStyles = makeStyles<BackstageTheme>(theme => ({
header: {
gridArea: 'pageHeader',
padding: theme.spacing(3),
minHeight: 118,
width: '100%',
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
position: 'relative',
zIndex: 100,
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-end',
alignItems: 'center',
},
leftItemsBox: {
flex: '1 1 auto',
},
rightItemsBox: {
flex: '0 1 auto',
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
marginRight: theme.spacing(6),
},
title: {
color: theme.palette.bursts.fontColor,
lineHeight: '1.0em',
wordBreak: 'break-all',
fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))',
marginBottom: theme.spacing(1),
},
subtitle: {
color: 'rgba(255, 255, 255, 0.8)',
lineHeight: '1.0em',
},
type: {
textTransform: 'uppercase',
fontSize: 9,
opacity: 0.8,
marginBottom: 10,
color: theme.palette.bursts.fontColor,
},
}));
type HeaderStyles = ReturnType<typeof useStyles>;
type Props = {
component?: ReactNode;
pageTitleOverride?: string;
style?: CSSProperties;
subtitle?: ReactNode;
title: ReactNode;
tooltip?: string;
type?: string;
typeLink?: string;
};
type TypeFragmentProps = {
classes: HeaderStyles;
type?: Props['title'];
typeLink?: Props['typeLink'];
};
type TitleFragmentProps = {
classes: HeaderStyles;
pageTitle: string | ReactNode;
tooltip?: Props['tooltip'];
};
type SubtitleFragmentProps = {
classes: HeaderStyles;
subtitle?: Props['subtitle'];
};
const TypeFragment: FC<TypeFragmentProps> = ({ type, typeLink, classes }) => {
if (!type) {
return null;
}
if (!typeLink) {
return (
// </Link>
<Typography className={classes.type}>{type}</Typography>
);
}
return (
// <Link to={typeLink}>
<Typography className={classes.type}>{type}</Typography>
);
};
const TitleFragment: FC<TitleFragmentProps> = ({
pageTitle,
classes,
tooltip,
}) => {
const FinalTitle = (
<Typography className={classes.title} variant="h4">
{pageTitle}
</Typography>
);
if (!tooltip) {
return FinalTitle;
}
return (
<Tooltip title={tooltip} placement="top-start">
{FinalTitle}
</Tooltip>
);
};
const SubtitleFragment: FC<SubtitleFragmentProps> = ({ classes, subtitle }) => {
if (!subtitle) {
return null;
}
if (typeof subtitle !== 'string') {
return <>{subtitle}</>;
}
return (
<Typography className={classes.subtitle} variant="subtitle1">
{subtitle}
</Typography>
);
};
export const Header: FC<Props> = ({
children,
pageTitleOverride,
style,
subtitle,
title,
tooltip,
type,
typeLink,
}) => {
const classes = useStyles();
const documentTitle = pageTitleOverride || title;
const pageTitle = title || pageTitleOverride;
const titleTemplate = `${documentTitle} | %s | Backstage`;
const defaultTitle = `${documentTitle} | Backstage`;
return (
<Fragment>
<Helmet titleTemplate={titleTemplate} defaultTitle={defaultTitle} />
<Theme.Consumer>
{theme => (
<header style={style} className={classes.header}>
<Waves theme={theme} />
<div className={classes.leftItemsBox}>
<TypeFragment classes={classes} type={type} typeLink={typeLink} />
<TitleFragment
classes={classes}
pageTitle={pageTitle}
tooltip={tooltip}
/>
<SubtitleFragment classes={classes} subtitle={subtitle} />
</div>
<div className={classes.rightItemsBox}>{children}</div>
</header>
)}
</Theme.Consumer>
</Fragment>
);
};
export default Header;
+7
View File
@@ -39,5 +39,12 @@ export type BackstageTheme = Theme & {
linkHover: string;
link: string;
gold: string;
bursts: {
fontColor: string;
slackChannelText: string;
backgroundColor: {
default: string;
};
};
};
};