Created
February 26, 2021 14:57
-
-
Save monecchi/f6d73655f47e07be1cc00b469cfa93b6 to your computer and use it in GitHub Desktop.
Integrate NextJs `next/image` with Chakra-UI styling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* ! 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 | |
*/ | |
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |
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' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { chakra, ThemingProps, useStyleConfig } from '@chakra-ui/react' | |
import NextImage, { ImageProps as NextImageProps } from 'next/image' | |
import { ReactElement } from 'react' | |
import { Sizes } from '../../theme/variables/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} /> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment