From 7c0b915e737036128eddf811ff1ba49ab32b5814 Mon Sep 17 00:00:00 2001 From: tarjelavik <Tarje.Lavik@ub.uib.no> Date: Mon, 13 Sep 2021 13:54:46 +0200 Subject: [PATCH] Fix lint errors and missing PT component --- web/components/Image/image.ts | 45 ++++++ web/components/Image/index.js | 36 ----- web/components/Image/index.tsx | 133 ++++++++++++++++++ web/components/Layout/Footer.js | 16 ++- web/components/Layout/Meta.js | 32 +++-- .../Sections/IllustrationWithCaption.js | 3 +- web/pages/[...slug].js | 2 +- web/pages/actors/index.js | 2 +- web/pages/nansen-i-biblioteket/index.js | 2 +- 9 files changed, 220 insertions(+), 51 deletions(-) create mode 100644 web/components/Image/image.ts delete mode 100644 web/components/Image/index.js create mode 100644 web/components/Image/index.tsx diff --git a/web/components/Image/image.ts b/web/components/Image/image.ts new file mode 100644 index 0000000..a12a396 --- /dev/null +++ b/web/components/Image/image.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-unused-vars */ +/** + * ! Important in optimizing images + * + * Keep the values in sync between: + * - `deviceSizes` in `next.config.js` + * - `deviceSizes` in `image.ts` + * + * ! Recommended + * NextJs optimize images according to your viewport. This is wonderful for mobile, but for desktop with a 4k screen, NextJs would + * download the 3840px version of your image. + * + * To workaround this unfortunate situation, I highly recommend you pass `size` to images with the highest width value being the + * max width an image can have on your site. + * + * For example, content on my site is centered and cannot be more than 960px wide, thus I make sure that 960 is in `deviceSizes` and + * I use `Sizes.main` to limit the image to only 960px. This considerably reduce the size in KB of my images and they're much + * better optimized on screens larger than 960px. + * + * This file is a way to generate the strings to pass to Image's `size` property and put the results in an Enum for easier consumption + */ +const deviceSizes = [320, 480, 640, 750, 828, 960, 1080, 1200, 1440, 1920, 2048, 2560, 3840] +const deviceSizesMax = Math.max(...deviceSizes) + +/** + * ? `generateSizes` will create the strings necessary for `Sizes` enum + * + * ? Simply uncomment the `console.log` and adjust values + */ + +const generateSizes = (upperLimit: number = deviceSizesMax): string => { + const sizes = [...deviceSizes.filter((v) => v < upperLimit), upperLimit] + return sizes + .map((v, i) => { + return i < sizes.length - 1 ? ` (max-width: ${v}px) ${v}px` : ` ${v}px` + }) + .join() +} +// console.log(generateSizes(960)) // I use a variable, but since it's easier to understand with a real number... +// console.log(generateSizes()) + +export enum Sizes { + main = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, 960px', + full = '(max-width: 320px) 320px, (max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 750px) 750px, (max-width: 828px) 828px, (max-width: 960px) 960px, (max-width: 1080px) 1080px, (max-width: 1200px) 1200px, (max-width: 1440px) 1440px, (max-width: 1920px) 1920px, (max-width: 2048px) 2048px, (max-width: 2560px) 2560px, 3840px', +} diff --git a/web/components/Image/index.js b/web/components/Image/index.js deleted file mode 100644 index f8482df..0000000 --- a/web/components/Image/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import { Box, chakra } from '@chakra-ui/react' -import NextImage from 'next/image' - -const CoverImg = chakra(NextImage, { - shouldForwardProp: (prop) => - ['width', 'height', 'src', 'alt', 'quality', 'placeholder', 'blurDataURL', 'loader '].includes( - prop, - ), -}) - -const myLoader = ({ src, width, quality }) => { - return `${src}?w=${width}&q=${quality}` -} - -export const Image = (props) => { - const { src, alt, ...rest } = props - return ( - <Box pos="relative" cursor="pointer" className="group" {...rest}> - <CoverImg - w="auto" - h="auto" - loader={myLoader} - width={600} - quality={50} - height={384} - placeholder="blur" - src={src} - alt={alt} - transition="all 0.2s" - _groupHover={{ - transform: 'scale(1.05)', - }} - /> - </Box> - ) -} diff --git a/web/components/Image/index.tsx b/web/components/Image/index.tsx new file mode 100644 index 0000000..ba8c146 --- /dev/null +++ b/web/components/Image/index.tsx @@ -0,0 +1,133 @@ +import { chakra, ThemingProps, useStyleConfig } from '@chakra-ui/react' +import NextImage, { ImageProps as NextImageProps } from 'next/image' +import { ReactElement } from 'react' +import { Sizes } from './image' + +// TODO review props when NextJs is updated so we don't have to defined it here +/** + * ? Because NextJs typing is preventing auto-suggest for layout, width and height, + * ? we declare the styles differently in this component and will manage the switch + * ? to NextJs typings when calling NextJs `next/image` component + */ +type LayoutValue = 'fixed' | 'intrinsic' | 'responsive' | undefined + +type LayoutAndSize = { layout: 'fill' } | { layout: LayoutValue; height: number; width: number } + +/** + * Types for the Image component itself + */ +type ImageProps = Pick< + NextImageProps, + | 'className' + | 'loading' + | 'objectFit' + | 'objectPosition' + | 'priority' + | 'quality' + | 'src' + | 'unoptimized' +> & + Pick<Required<NextImageProps>, 'alt'> & + Pick<ThemingProps, 'variant'> & { + dimensions?: [number, number] + layout?: 'fill' | LayoutValue + sizes?: Sizes // could be a string too, this one is just a way to make it easier + } + +/** + * Wraps NextJs `next/image` component in Chakra's factory function + * This is what will allow to use the theme and the styling properties on the component + */ +const ImageWithChakra = chakra( + ({ + className, + dimensions = [0, 0], + layout = 'fill', + loading, + objectFit, + objectPosition, + priority, + quality, + sizes, + src, + unoptimized, + ...nextjsInternals + }: ImageProps): ReactElement => { + /** + * ? As explained earlier, NextJs typing is preventing auto-suggest for layout, width and height + * ? Here we actually convert our component typing to NextJs typing + */ + const [width, height] = dimensions + + const layoutAndSize: LayoutAndSize = + height > 0 || width > 0 + ? { height, layout: layout === 'fill' ? 'intrinsic' : layout, width } + : { layout: 'fill' } + + return ( + <NextImage + className={className} + loading={loading} + objectFit={objectFit} + objectPosition={objectPosition} + priority={priority} + quality={quality} + sizes={sizes} + src={src} + unoptimized={unoptimized} + // eslint-disable-next-line react/jsx-props-no-spreading + {...layoutAndSize} + // eslint-disable-next-line react/jsx-props-no-spreading + {...nextjsInternals} + /> + ) + }, +) + +export const Image = ({ variant, ...props }: ImageProps): ReactElement => { + /** + * ? This components serves as an interface to pass Chakra's styles + * ? You can use the theme and/or styling properties (eg. backgroundColor='red.200') + */ + const styles = useStyleConfig('Image', { variant }) + // eslint-disable-next-line react/jsx-props-no-spreading + return <ImageWithChakra sx={styles} {...props} /> +} + +/* import { Box, chakra } from '@chakra-ui/react' +import NextImage from 'next/image' + +const CoverImg = chakra(NextImage, { + shouldForwardProp: (prop) => + ['width', 'height', 'src', 'alt', 'quality', 'placeholder', 'blurDataURL', 'loader '].includes( + prop, + ), +}) + +const myLoader = ({ src, width, quality }) => { + return `${src}?w=${width}&q=${quality}` +} + +export const Image = (props) => { + const { src, alt, ...rest } = props + return ( + <Box pos="relative" cursor="pointer" className="group" {...rest}> + <CoverImg + w="auto" + h="auto" + loader={myLoader} + width={600} + quality={50} + height={384} + placeholder="blur" + src={src} + alt={alt} + transition="all 0.2s" + _groupHover={{ + transform: 'scale(1.05)', + }} + /> + </Box> + ) +} + */ diff --git a/web/components/Layout/Footer.js b/web/components/Layout/Footer.js index e1d8f3e..87b3720 100644 --- a/web/components/Layout/Footer.js +++ b/web/components/Layout/Footer.js @@ -5,7 +5,9 @@ import RenderSections from '../Sections/RenderSection' import License from '../License' import React from 'react' import Publisher from '../Publisher' -import Image from '../Image' +// import { Image } from '../Image' + +// import footerBorder from '../../public/img/taakeheimen-footer.svg' /* const MenuItem = ({ children }) => ( <Text @@ -35,10 +37,14 @@ export default function Footer(props) { gridArea="footer" maxW="full" minH="100px" - py="2" + mt="10" + py="6" px="0" zIndex="100" color={color} + backgroundImage={`url('${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg')`} + backgroundPosition="50% 4%" + backgroundRepeat="no-repeat" > <Flex pl="5"> <Button variant="link" onClick={toggleColorMode}> @@ -46,7 +52,11 @@ export default function Footer(props) { </Button> </Flex> - <Image src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg`} alt="" /> + {/* <Image + layout="responsive" + src={`${process.env.NEXT_PUBLIC_BASE_PATH}/img/taakeheimen-footer.svg`} + alt="" + /> */} <Container maxW="4xl" p="0" sx={{ perspective: '492px' }}> {/* <Flex pb="0"> diff --git a/web/components/Layout/Meta.js b/web/components/Layout/Meta.js index 45c7952..33d32df 100644 --- a/web/components/Layout/Meta.js +++ b/web/components/Layout/Meta.js @@ -1,20 +1,36 @@ import * as React from 'react' import Head from 'next/head' +const basePath = process.env.NEXT_PUBLIC_BASE_PATH + export default function Meta() { return ( <React.Fragment> <Head> - <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" /> - <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" /> - <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" /> - <link rel="manifest" href="/favicon/site.webmanifest" /> - <link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#000000" /> - <link rel="shortcut icon" href="/favicon/favicon.ico" /> + <link + rel="apple-touch-icon" + sizes="180x180" + href={`${basePath}/favicon/apple-touch-icon.png`} + /> + <link + rel="icon" + type="image/png" + sizes="32x32" + href={`${basePath}/favicon/favicon-32x32.png`} + /> + <link + rel="icon" + type="image/png" + sizes="16x16" + href={`${basePath}/favicon/favicon-16x16.png`} + /> + <link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} /> + <link rel="mask-icon" href={`${basePath}/favicon/safari-pinned-tab.svg`} color="#000000" /> + <link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} /> <meta name="msapplication-TileColor" content="#000000" /> - <meta name="msapplication-config" content="/favicon/browserconfig.xml" /> + <meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} /> <meta name="theme-color" content="#000" /> - <link rel="alternate" type="application/rss+xml" href="/feed.xml" /> + <link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} /> <meta name="description" content={'A statically generated blog example using Next.js.'} /> {/* <meta property="og:image" content={HOME_OG_IMAGE_URL} /> */} </Head> diff --git a/web/components/Sections/IllustrationWithCaption.js b/web/components/Sections/IllustrationWithCaption.js index eab2271..760f398 100644 --- a/web/components/Sections/IllustrationWithCaption.js +++ b/web/components/Sections/IllustrationWithCaption.js @@ -1,6 +1,7 @@ import { imageBuilder } from '../../lib/sanity' -import { Flex, Grid, Box, Image } from '@chakra-ui/react' +import { Flex, Grid, Box } from '@chakra-ui/react' import Caption from './shared/caption' +import { Image } from '../Image' export default function IllustrationWithCaption(props) { if ((!props && !props.illustration) || props.disabled === true) { diff --git a/web/pages/[...slug].js b/web/pages/[...slug].js index cdce62c..e91dd03 100644 --- a/web/pages/[...slug].js +++ b/web/pages/[...slug].js @@ -30,7 +30,7 @@ export default function Page({ data, preview }) { </Box> )} - <Text mt="5" color="gray.500" fontSize="sm"> + <Text mt="10" color="gray.500" fontSize="sm"> Oppdatert: <Date>{_updatedAt}</Date> </Text> </Container> diff --git a/web/pages/actors/index.js b/web/pages/actors/index.js index bf0ebdd..3feea2c 100644 --- a/web/pages/actors/index.js +++ b/web/pages/actors/index.js @@ -3,7 +3,7 @@ import { getAllActors } from '../../lib/api' import { Grid, Avatar, Box, Heading, Flex, Badge, Container } from '@chakra-ui/react' import Layout from '../../components/Layout' import Link from '../../components/Link' -import PortableTextBlock from '../../components/PortableTextBlock' +import PortableTextBlock from '../../components/PT/PortableTextBlock' export default function Actors({ data, preview }) { /* let actors = data.items.reduce((r, e) => { diff --git a/web/pages/nansen-i-biblioteket/index.js b/web/pages/nansen-i-biblioteket/index.js index f371ac2..edd1d59 100644 --- a/web/pages/nansen-i-biblioteket/index.js +++ b/web/pages/nansen-i-biblioteket/index.js @@ -1,7 +1,7 @@ import { getPhysicalExhibitionCopy } from '../../lib/api' import Head from 'next/head' import { Container, Heading } from '@chakra-ui/react' -import PortableTextBlock from '../../components/PortableTextBlock' +import PortableTextBlock from '../../components/PT/PortableTextBlock' import Layout from '../../components/Layout' export default function PhysicalExhibition({ data, preview }) { -- GitLab