Merge pull request #263 from spotify/freben/ts

Some more TS weekend buffoonery
This commit is contained in:
Fredrik Adelöw
2020-03-16 11:10:28 +01:00
committed by GitHub
6 changed files with 193 additions and 194 deletions
@@ -1,105 +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 } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core';
import { Circle } from 'rc-progress';
import { COLORS, V1 } from '../theme/BackstageTheme';
const styles = theme => ({
root: {
position: 'relative',
lineHeight: 0,
},
overlay: {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -60%)',
fontSize: 45,
fontWeight: 'bold',
color: theme.palette.textSubtle,
},
circle: {
width: '80%',
transform: 'translate(10%, 0)',
},
});
class CircleProgress extends Component {
static propTypes = {
value: PropTypes.any.isRequired,
fractional: PropTypes.bool,
classes: PropTypes.object.isRequired,
inverse: PropTypes.bool,
unit: PropTypes.string,
max: PropTypes.number,
};
static defaultProps = {
fractional: true,
inverse: false,
unit: '%',
max: 100,
};
static getProgressColor(value, inverse, max /* , classes */) {
if (isNaN(value)) {
return V1.palette.textVerySubtle;
}
const actualMax = max ? max : CircleProgress.defaultProps.max;
const actualValue = inverse ? actualMax - value : value;
if (actualValue < actualMax / 3) {
return COLORS.STATUS.ERROR;
} else if (actualValue < actualMax * (2 / 3)) {
return COLORS.STATUS.WARNING;
}
return COLORS.STATUS.OK;
}
render() {
const { value, fractional, classes, inverse, unit, max } = this.props;
const asPercentage = fractional ? Math.round(value * max) : value;
const asActual = max !== 100 ? Math.round(value) : asPercentage;
return (
<div className={classes.root}>
<Circle
strokeLinecap="butt"
percent={asPercentage}
strokeWidth="12"
trailWidth="12"
strokeColor={CircleProgress.getProgressColor(
asActual,
inverse,
max,
classes,
)}
className={classes.circle}
/>
<div className={classes.overlay}>
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
</div>
</div>
);
}
}
export default withStyles(styles)(CircleProgress);
@@ -17,8 +17,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '../testUtils';
import CircleProgress from './CircleProgress';
import { COLORS, V1 } from '../theme/BackstageTheme';
import CircleProgress, { getProgressColor } from './CircleProgress';
import { COLORS } from '../theme/BackstageTheme';
describe('<CircleProgress />', () => {
it('renders without exploding', () => {
@@ -53,18 +53,16 @@ describe('<CircleProgress />', () => {
});
it('colors the progress correctly', () => {
expect(CircleProgress.getProgressColor()).toBe(V1.palette.textVerySubtle);
expect(CircleProgress.getProgressColor(10)).toBe(COLORS.STATUS.ERROR);
expect(CircleProgress.getProgressColor(50)).toBe(COLORS.STATUS.WARNING);
expect(CircleProgress.getProgressColor(90)).toBe(COLORS.STATUS.OK);
expect(getProgressColor()).toBe('#ddd');
expect(getProgressColor(10)).toBe(COLORS.STATUS.ERROR);
expect(getProgressColor(50)).toBe(COLORS.STATUS.WARNING);
expect(getProgressColor(90)).toBe(COLORS.STATUS.OK);
});
it('colors the inverse progress correctly', () => {
expect(CircleProgress.getProgressColor()).toBe(V1.palette.textVerySubtle);
expect(CircleProgress.getProgressColor(10, true)).toBe(COLORS.STATUS.OK);
expect(CircleProgress.getProgressColor(50, true)).toBe(
COLORS.STATUS.WARNING,
);
expect(CircleProgress.getProgressColor(90, true)).toBe(COLORS.STATUS.ERROR);
expect(getProgressColor()).toBe('#ddd');
expect(getProgressColor(10, true)).toBe(COLORS.STATUS.OK);
expect(getProgressColor(50, true)).toBe(COLORS.STATUS.WARNING);
expect(getProgressColor(90, true)).toBe(COLORS.STATUS.ERROR);
});
});
@@ -0,0 +1,103 @@
/*
* 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 { makeStyles } from '@material-ui/core';
import { Circle } from 'rc-progress';
import React, { FC } from 'react';
import { COLORS } from '../theme/BackstageTheme';
import { BackstageTheme } from '../theme/theme';
const useStyles = makeStyles((theme: BackstageTheme) => ({
root: {
position: 'relative',
lineHeight: 0,
},
overlay: {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -60%)',
fontSize: 45,
fontWeight: 'bold',
color: theme.palette.textSubtle,
},
circle: {
width: '80%',
transform: 'translate(10%, 0)',
},
colorUnknown: {},
}));
type Props = {
value: number;
fractional?: boolean;
inverse?: boolean;
unit?: string;
max?: number;
};
const defaultProps = {
fractional: true,
inverse: false,
unit: '%',
max: 100,
};
export function getProgressColor(value, inverse, max) {
if (isNaN(value)) {
return '#ddd';
}
const actualMax = max ? max : defaultProps.max;
const actualValue = inverse ? actualMax - value : value;
if (actualValue < actualMax / 3) {
return COLORS.STATUS.ERROR;
} else if (actualValue < actualMax * (2 / 3)) {
return COLORS.STATUS.WARNING;
}
return COLORS.STATUS.OK;
}
const CircleProgress: FC<Props> = props => {
const classes = useStyles(props);
const { value, fractional, inverse, unit, max } = {
...defaultProps,
...props,
};
const asPercentage = fractional ? Math.round(value * max) : value;
const asActual = max !== 100 ? Math.round(value) : asPercentage;
return (
<div className={classes.root}>
<Circle
strokeLinecap="butt"
percent={asPercentage}
strokeWidth={12}
trailWidth={12}
strokeColor={getProgressColor(asActual, inverse, max)}
className={classes.circle}
/>
<div className={classes.overlay}>
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
</div>
</div>
);
};
export default CircleProgress;
@@ -1,77 +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 } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core';
import InfoCard from '../layout/InfoCard';
import CircleProgress from './CircleProgress';
const styles = {
root: {
height: '100%',
width: 250,
},
};
class ProgressCard extends Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
subheader: PropTypes.string,
progress: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
deepLink: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
title: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
}),
]),
gacontext: PropTypes.string,
};
render() {
const {
title,
subheader,
progress,
deepLink,
classes,
variant,
} = this.props;
const link =
deepLink &&
(typeof deepLink === 'string'
? { title: 'View more', link: deepLink }
: deepLink);
return (
<div className={classes.root}>
<InfoCard
title={title}
subheader={subheader}
deepLink={link}
variant={variant}
>
<CircleProgress value={progress} />
</InfoCard>
</div>
);
}
}
export default withStyles(styles)(ProgressCard);
@@ -0,0 +1,67 @@
/*
* 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, { FC } from 'react';
import { makeStyles } from '@material-ui/core';
import InfoCard from '../layout/InfoCard';
import CircleProgress from './CircleProgress';
type Props = {
title: string;
subheader?: string;
variant?: string;
progress: number | string;
deepLink?: string | { title: string; link: string };
gacontext?: string;
};
const useStyles = makeStyles({
root: {
height: '100%',
width: 250,
},
});
const ProgressCard: FC<Props> = props => {
const classes = useStyles(props);
const { title, subheader, progress, deepLink, variant } = props;
// TODO(freben): Make type more strict when InfoCard is written in TS
let link: any = undefined;
if (deepLink) {
if (typeof deepLink === 'string') {
link = { title: 'View more', link: deepLink };
} else {
link = deepLink;
}
}
return (
<div className={classes.root}>
<InfoCard
title={title}
subheader={subheader}
deepLink={link}
variant={variant}
>
<CircleProgress value={Number(progress)} />
</InfoCard>
</div>
);
};
export default ProgressCard;
+13
View File
@@ -26,5 +26,18 @@ export type BackstageTheme = Theme & {
running: string;
background: string;
};
border: string;
textVerySubtle: string;
textSubtle: string;
highlight: string;
errorBackground: string;
warningBackground: string;
infoBackground: string;
errorText: string;
infoText: string;
warningText: string;
linkHover: string;
link: string;
gold: string;
};
};