diff options
-rw-r--r-- | src/components/Image/BackgroundImage.tsx | 35 | ||||
-rw-r--r-- | src/components/Image/Image.tsx | 100 | ||||
-rw-r--r-- | src/components/PollCard/PollCard.tsx | 29 | ||||
-rw-r--r-- | src/containers/Home/Home.tsx | 7 |
4 files changed, 148 insertions, 23 deletions
diff --git a/src/components/Image/BackgroundImage.tsx b/src/components/Image/BackgroundImage.tsx new file mode 100644 index 0000000..e749d10 --- /dev/null +++ b/src/components/Image/BackgroundImage.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Image from './Image'; + +interface PropTypes { + src?: string; + alt?: string; +} + +const useStyles = makeStyles({ + root: { + position: 'absolute', + width: '100%', + height: '100%' + }, + image: { + objectFit: 'cover', + pointerEvents: 'none', + width: '100%', + height: '100%' + } +}); + +const BackgroundImage: React.FC<PropTypes> = ({ src, alt }) => { + const classes = useStyles(); + + return ( + <picture className={classes.root}> + <Image src={src} alt={alt} className={classes.image} /> + </picture> + ); +}; + +export default BackgroundImage; + diff --git a/src/components/Image/Image.tsx b/src/components/Image/Image.tsx new file mode 100644 index 0000000..1898716 --- /dev/null +++ b/src/components/Image/Image.tsx @@ -0,0 +1,100 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import ErrorIcon from '@material-ui/icons/BrokenImage'; +import grey from '@material-ui/core/colors/grey'; + +interface PropTypes { + src?: string; + alt?: string; + className?: string; + animationDuration?: number; + disableLoading?: boolean; +} + +type Status = 'success' | 'loading' | 'error'; + +const useStyles = makeStyles(theme => ({ + container: { + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }, + success: { + opacity: '100%', + filterBrightness: '100%', + filterSaturate: '100%' + }, + loading: { + opacity: 0, + filterBrightness: 0, + filterSaturate: '20%', + position: 'absolute' + }, + error: { + display: 'none' + }, + errorIcon: { + color: grey[300], + width: theme.spacing(6), + height: theme.spacing(6) + } +})); + +const Image: React.FC<PropTypes> = React.memo(({ + src, + alt, + className, + animationDuration = 1000, + disableLoading = false +}) => { + const classes = useStyles(); + const [status, setStatus] = useState<Status>('loading'); + + const handleLoad = useCallback((): void => { + setStatus('success'); + }, [setStatus]); + + const handleError = useCallback((): void => { + setStatus('error'); + }, [setStatus]); + + const transition = useMemo(() => ` + filterBrightness ${animationDuration * 0.75}ms cubic-bezier(0.4, 0.0, 0.2, 1), + filterSaturate ${animationDuration}ms cubic-bezier(0.4, 0.0, 0.2, 1), + opacity ${animationDuration / 2}ms cubic-bezier(0.4, 0.0, 0.2, 1) + `, [animationDuration]); + + const image = ( + <img + src={src} + alt={alt} + className={`${className} ${classes[status]}`} + style={{ transition }} + onLoad={handleLoad} + onError={handleError} + /> + ); + + return ( + <> + {src && image} + { + status !== 'success' && ( + <div className={classes.container}> + { + (status === 'error' || !src) + ? <ErrorIcon className={classes.errorIcon} /> + : (!disableLoading && <CircularProgress color="primary" />) + } + </div> + ) + } + </> + ); +}); + +export default Image; + diff --git a/src/components/PollCard/PollCard.tsx b/src/components/PollCard/PollCard.tsx index 689e872..a4c2144 100644 --- a/src/components/PollCard/PollCard.tsx +++ b/src/components/PollCard/PollCard.tsx @@ -1,15 +1,12 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { - Card, - CardActionArea, - CardMedia -} from '@material-ui/core/'; +import { Card, CardActionArea } from '@material-ui/core/'; import { Which, Poll } from 'which-types'; import { useSnackbar } from 'notistack'; import PercentageBar from './PercentageBar'; import UserStrip from '../UserStrip/UserStrip'; +import BackgroundImage from '../Image/BackgroundImage'; import { post } from '../../requests'; import { useAuth } from '../../hooks/useAuth'; @@ -27,12 +24,10 @@ const DATE_FORMAT = { }; const useStyles = makeStyles(theme => ({ - images: { + media: { + display: 'flex', height: theme.spacing(50) }, - imagesBlock: { - display: 'flex' - }, rateLine: { position: 'relative', width: '100%', @@ -101,19 +96,13 @@ const PollCard: React.FC<PropTypes> = ({ poll, setPoll }) => { return ( <Card> <UserStrip user={author} info={date} /> - <div className={classes.imagesBlock}> - <CardActionArea onDoubleClick={handleLeft}> - <CardMedia - className={classes.images} - image={left.url} - /> + <div className={classes.media}> + <CardActionArea onDoubleClick={handleLeft} className={classes.media}> + <BackgroundImage src={left.url} /> <PercentageBar value={leftPercentage} which="left" like={vote?.which === 'left'} /> </CardActionArea> - <CardActionArea onDoubleClick={handleRight}> - <CardMedia - className={classes.images} - image={right.url} - /> + <CardActionArea onDoubleClick={handleRight} className={classes.media}> + <BackgroundImage src={right.url} /> <PercentageBar value={rightPercentage} which="right" like={vote?.which === 'right'} /> </CardActionArea> </div> diff --git a/src/containers/Home/Home.tsx b/src/containers/Home/Home.tsx index c0ca00e..072e2fa 100644 --- a/src/containers/Home/Home.tsx +++ b/src/containers/Home/Home.tsx @@ -13,10 +13,11 @@ import TrendingUpIcon from '@material-ui/icons/TrendingUp'; import { Rating } from '@material-ui/lab'; import { Feedback } from 'which-types'; -import { useAuth } from '../../hooks/useAuth'; -import { useFeedback } from '../../hooks/APIClient'; import ReviewCard from '../../components/ReviewCard/ReviewCard'; +import Image from '../../components/Image/Image'; import ReviewForm from './ReviewForm'; +import { useAuth } from '../../hooks/useAuth'; +import { useFeedback } from '../../hooks/APIClient'; const useStyles = makeStyles(theme => ({ root: { @@ -109,7 +110,7 @@ const Home: React.FC = () => { <Grid item xs={12} md={4}> <Grid container direction="column" spacing={1} alignItems="center"> <Grid item> - <img src={`${process.env.PUBLIC_URL}/which-logo-512.png`} alt="logo" className={classes.logo} /> + <Image src={`${process.env.PUBLIC_URL}/which-logo-512.png`} alt="logo" className={classes.logo} /> </Grid> <Grid item> {rating !== 0 && <Rating value={rating} readOnly size="large" />} |