From 1f0a3c312c4f59e1f323797d077d1ef15bca0509 Mon Sep 17 00:00:00 2001 From: Benjamin Sehl Date: Wed, 4 Jan 2023 21:47:07 -0500 Subject: [PATCH 1/5] Adds support for fixed dimension images --- packages/hydrogen/src/image.test.ts | 16 + packages/hydrogen/src/image.ts | 275 ++++++++++++++++++ packages/hydrogen/src/index.ts | 1 + .../demo-store/app/routes/image-demo.tsx | 72 +++++ 4 files changed, 364 insertions(+) create mode 100644 packages/hydrogen/src/image.test.ts create mode 100644 packages/hydrogen/src/image.ts create mode 100644 templates/demo-store/app/routes/image-demo.tsx diff --git a/packages/hydrogen/src/image.test.ts b/packages/hydrogen/src/image.test.ts new file mode 100644 index 0000000000..54b2a509e5 --- /dev/null +++ b/packages/hydrogen/src/image.test.ts @@ -0,0 +1,16 @@ +import {test, expect} from '@jest/globals'; +import {generateShopifySrcSet} from './image'; + +test('src set', () => { + expect( + generateShopifySrcSet( + 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', + [ + {width: 200, height: 200, crop: 'center'}, + {width: 400, height: 400, crop: 'center'}, + ], + ), + ).toBe( + 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w', + ); +}); diff --git a/packages/hydrogen/src/image.ts b/packages/hydrogen/src/image.ts new file mode 100644 index 0000000000..eb8469f7fa --- /dev/null +++ b/packages/hydrogen/src/image.ts @@ -0,0 +1,275 @@ +import React from 'react'; + +interface ImageConfig { + intervals: number; + startingWidth: number; + incrementSize: number; + placeholderWidth: number; +} + +type ImageComponent = { + as?: 'img' | 'source'; + src: string; + width?: string | number; + height?: string | number; + crop: Crop; + sizes: string; + aspectRatio: string; + scale?: number; + priority?: boolean; + config?: ImageConfig; + alt: string; +}; + +type Crop = 'center' | 'top' | 'bottom' | 'left' | 'right' | undefined; + +export function Image({ + as: Component = 'img', + src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', + width = '100%', + height, + crop = 'center', + sizes = '(min-width: 768px) 50vw, 100vw', + aspectRatio, + scale, + priority, + config = { + intervals: 10, + startingWidth: 300, + incrementSize: 300, + placeholderWidth: 100, + }, + alt = 'Test Alt Tag', + ...passthroughProps +}: ImageComponent) { + if (!isFixedWidth(width)) { + const {intervals, startingWidth, incrementSize, placeholderWidth} = config; + + const widths = generateImageWidths( + width, + intervals, + startingWidth, + incrementSize, + ); + + const sizesArray = generateSizes(widths, aspectRatio, crop); + + return React.createElement(Component, { + srcSet: generateShopifySrcSet(src, sizesArray), + src: generateImagerySrc( + src, + placeholderWidth, + parseAspectRatio(aspectRatio) * placeholderWidth, + ), + alt, + width, + sizes, + style: {aspectRatio}, + ...passthroughProps, + }); + } else { + // width is fixed + let intWidth: number | undefined = getNormalizedFixedUnit(width); + let intHeight: number | undefined = getNormalizedFixedUnit(height); + + let normalizedWidth: string = + getUnitValueParts(width.toString()).number + + getUnitValueParts(width.toString()).unit; + + let normalizedHeight: string = + height === undefined + ? 'auto' + : getUnitValueParts(height.toString()).number + + getUnitValueParts(height.toString()).unit; + + return React.createElement(Component, { + src: generateImagerySrc( + src, + intWidth, + (aspectRatio && intWidth + ? parseAspectRatio(aspectRatio) * intWidth + : intHeight) ?? undefined, + normalizedHeight === 'auto' ? undefined : crop, + ), + alt, + style: { + width: normalizedWidth, + height: normalizedHeight, + }, + ...passthroughProps, + }); + } +} + +function getUnitValueParts(value: string) { + const unit = value.replace(/[0-9.]/g, ''); + const number = parseFloat(value.replace(unit, '')); + + return { + unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit, + number, + }; +} + +function getNormalizedFixedUnit(value: string | number | undefined) { + if (value === undefined) { + return undefined; + } + + const {unit, number} = getUnitValueParts(value.toString()); + + switch (unit) { + case 'em': + return number * 16; + case 'rem': + return number * 16; + case 'px': + return number; + case '': + return number; + default: + return undefined; + } +} + +function isFixedWidth(width: string | number) { + const fixedEndings = new RegExp('px|em|rem', 'g'); + return ( + typeof width === 'number' || + (typeof width === 'string' && fixedEndings.test(width)) + ); +} + +export function generateShopifySrcSet( + src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', + sizesArray: Array<{width: number; height: number; crop: Crop}> | undefined, +) { + if (sizesArray?.length === 0 || !sizesArray) { + return src; + } + + return sizesArray + .map( + (size) => + generateImagerySrc(src, size.width, size.height, size.crop) + + ' ' + + size.width + + 'w', + ) + .join(`, `); + /* + Given: + src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg' + sizesArray = [ + {width: 200, height: 200, crop: 'center'}, + {width: 400, height: 400, crop: 'center'}, + ] + Returns: + 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w' + */ +} + +export function generateImageWidths( + width: string | number = '100%', + intervals = 20, + startingWidth = 200, + incrementSize = 100, + scale = 1, +) { + const responsive = Array.from( + {length: intervals}, + (_, i) => (i * incrementSize + startingWidth) * scale, + ); + + if (typeof width === 'string') { + if (width === '100%') { + return responsive; + } else if (width.endsWith('%') && width !== '100%') { + // width is set in percent + return responsive; + } else if (width.endsWith('px')) { + // width is set in pixels + } else if (width.endsWith('em')) { + // width is set in ems + } else if (width.endsWith('rem')) { + // width is set in rems + } else if (width == 'auto') { + // width is set to auto + } else { + // width is set using some other unit of measurement + } + } + + if (width === '100%') { + return; + /* + Given: + width = '100%' + intervals = 10 + startingWidth = 100 + incrementSize = 100 + Returns: + [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] + */ + } + // @TODO: if width !== 100% handle relative/fixed sizes: vw/em/rem/px + return [1000]; +} + +// Simple utility function to convert 1/1 to [1, 1] +export function parseAspectRatio(aspectRatio: string) { + const [width, height] = aspectRatio.split('/'); + return Number(width) / Number(height); + /* + Given: + '1/1' + Returns: + 0.5 + */ +} + +// Generate data needed for Imagery loader +export function generateSizes( + widths: number[] | undefined, + aspectRatio: string, + crop: Crop = 'center', +) { + if (!widths) return; + const sizes = widths.map((width: number) => { + return { + width, + height: width * parseAspectRatio(aspectRatio), + crop, + }; + }); + return sizes; + /* + Given: + ([100, 200], 1/1, 'center') + Returns: + [{width: 100, height: 100, crop: 'center'}, + {width: 200, height: 200, crop: 'center'}] + */ +} + +export function generateImagerySrc( + src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', + width?: number, + height?: number, + crop?: Crop, +) { + const url = new URL(src); + width && url.searchParams.append('width', width.toString()); + height && url.searchParams.append('height', height.toString()); + crop && url.searchParams.append('crop', crop); + return url.href; + /* + Given: + src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg' + width = 100 + height = 100 + crop = 'center' + Returns: + 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=100&height=100&crop=center' + */ +} diff --git a/packages/hydrogen/src/index.ts b/packages/hydrogen/src/index.ts index bffceb25f6..2118332d62 100644 --- a/packages/hydrogen/src/index.ts +++ b/packages/hydrogen/src/index.ts @@ -10,3 +10,4 @@ export {InMemoryCache} from './cache/in-memory'; export {RESOURCE_TYPES, REQUIRED_RESOURCES} from './routing/types'; export {notFoundMaybeRedirect} from './routing/redirect'; +export {Image} from './image'; diff --git a/templates/demo-store/app/routes/image-demo.tsx b/templates/demo-store/app/routes/image-demo.tsx new file mode 100644 index 0000000000..b441bd6dd0 --- /dev/null +++ b/templates/demo-store/app/routes/image-demo.tsx @@ -0,0 +1,72 @@ +import {type LoaderArgs} from '@shopify/remix-oxygen'; +import {useLoaderData} from '@remix-run/react'; +import {json} from '@shopify/remix-oxygen'; +import {Image} from '@shopify/hydrogen'; + +export async function loader({context: {storefront}}: LoaderArgs) { + const data: ImageRFCData = await storefront.query( + `#graphql + query { + product(handle: "snowboard") { + featuredImage { + ...Image + } + } + } + ${IMAGE_FRAGMENT} + `, + { + variables: {}, + }, + ); + + return json(data.product.featuredImage); +} + +const IMAGE_FRAGMENT = `#graphql + fragment Image on Image { + altText + url + } +`; + +type ImageRFCData = { + product: { + featuredImage: { + altText: string; + url: string; + }; + }; +}; + +export default function ImageRFC() { + const {altText, url} = useLoaderData(); + + return ( + <> + {altText} + {altText} + {altText} + + ); + + /* Picture component should look something like: + + + + + + When inside the component should render a element, + the last component should render an element. + */ +} From 6d38bb5599fc0f4198014a4c69bc05853476c52a Mon Sep 17 00:00:00 2001 From: Benjamin Sehl Date: Wed, 4 Jan 2023 22:01:01 -0500 Subject: [PATCH 2/5] Adds support for fluid images beyond 100% --- packages/hydrogen/src/image.ts | 78 +++++++------------ .../demo-store/app/routes/image-demo.tsx | 7 ++ 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/packages/hydrogen/src/image.ts b/packages/hydrogen/src/image.ts index eb8469f7fa..6732e41b27 100644 --- a/packages/hydrogen/src/image.ts +++ b/packages/hydrogen/src/image.ts @@ -42,6 +42,16 @@ export function Image({ alt = 'Test Alt Tag', ...passthroughProps }: ImageComponent) { + let normalizedWidth: string = + getUnitValueParts(width.toString()).number + + getUnitValueParts(width.toString()).unit; + + let normalizedHeight: string = + height === undefined + ? 'auto' + : getUnitValueParts(height.toString()).number + + getUnitValueParts(height.toString()).unit; + if (!isFixedWidth(width)) { const {intervals, startingWidth, incrementSize, placeholderWidth} = config; @@ -59,12 +69,15 @@ export function Image({ src: generateImagerySrc( src, placeholderWidth, - parseAspectRatio(aspectRatio) * placeholderWidth, + placeholderWidth * parseAspectRatio(aspectRatio), ), alt, - width, sizes, - style: {aspectRatio}, + style: { + width: normalizedWidth, + height: normalizedHeight, + aspectRatio, + }, ...passthroughProps, }); } else { @@ -72,22 +85,12 @@ export function Image({ let intWidth: number | undefined = getNormalizedFixedUnit(width); let intHeight: number | undefined = getNormalizedFixedUnit(height); - let normalizedWidth: string = - getUnitValueParts(width.toString()).number + - getUnitValueParts(width.toString()).unit; - - let normalizedHeight: string = - height === undefined - ? 'auto' - : getUnitValueParts(height.toString()).number + - getUnitValueParts(height.toString()).unit; - return React.createElement(Component, { src: generateImagerySrc( src, intWidth, (aspectRatio && intWidth - ? parseAspectRatio(aspectRatio) * intWidth + ? intWidth * parseAspectRatio(aspectRatio) : intHeight) ?? undefined, normalizedHeight === 'auto' ? undefined : crop, ), @@ -95,6 +98,7 @@ export function Image({ style: { width: normalizedWidth, height: normalizedHeight, + aspectRatio, }, ...passthroughProps, }); @@ -181,50 +185,22 @@ export function generateImageWidths( (_, i) => (i * incrementSize + startingWidth) * scale, ); - if (typeof width === 'string') { - if (width === '100%') { - return responsive; - } else if (width.endsWith('%') && width !== '100%') { - // width is set in percent - return responsive; - } else if (width.endsWith('px')) { - // width is set in pixels - } else if (width.endsWith('em')) { - // width is set in ems - } else if (width.endsWith('rem')) { - // width is set in rems - } else if (width == 'auto') { - // width is set to auto - } else { - // width is set using some other unit of measurement - } - } - - if (width === '100%') { - return; - /* - Given: - width = '100%' - intervals = 10 - startingWidth = 100 - incrementSize = 100 - Returns: - [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] - */ - } - // @TODO: if width !== 100% handle relative/fixed sizes: vw/em/rem/px - return [1000]; + return isFixedWidth(width) ? [getNormalizedFixedUnit(width)] : responsive; } // Simple utility function to convert 1/1 to [1, 1] export function parseAspectRatio(aspectRatio: string) { const [width, height] = aspectRatio.split('/'); - return Number(width) / Number(height); + return 1 / (Number(width) / Number(height)); /* Given: '1/1' Returns: - 0.5 + 0.5, + Given: + '4/3' + Returns: + 0.75 */ } @@ -259,8 +235,8 @@ export function generateImagerySrc( crop?: Crop, ) { const url = new URL(src); - width && url.searchParams.append('width', width.toString()); - height && url.searchParams.append('height', height.toString()); + width && url.searchParams.append('width', Math.round(width).toString()); + height && url.searchParams.append('height', Math.round(height).toString()); crop && url.searchParams.append('crop', crop); return url.href; /* diff --git a/templates/demo-store/app/routes/image-demo.tsx b/templates/demo-store/app/routes/image-demo.tsx index b441bd6dd0..b790599ebc 100644 --- a/templates/demo-store/app/routes/image-demo.tsx +++ b/templates/demo-store/app/routes/image-demo.tsx @@ -45,6 +45,13 @@ export default function ImageRFC() { return ( <> {altText} + {altText} {altText} {altText} From 6065e182df8c88331c3f2991d99ea36cb90d97a8 Mon Sep 17 00:00:00 2001 From: Benjamin Sehl Date: Thu, 5 Jan 2023 00:06:49 -0500 Subject: [PATCH 3/5] Adds inline docs --- packages/hydrogen/src/image.ts | 219 +++++++++++++----- .../demo-store/app/routes/image-demo.tsx | 9 +- 2 files changed, 168 insertions(+), 60 deletions(-) diff --git a/packages/hydrogen/src/image.ts b/packages/hydrogen/src/image.ts index 6732e41b27..db8ad64f47 100644 --- a/packages/hydrogen/src/image.ts +++ b/packages/hydrogen/src/image.ts @@ -1,5 +1,21 @@ import React from 'react'; +/* @TODO: + - [x] Support original aspect ratio + - [x] Support for non-100% widths + - [ ] Create guide/docs + - [ ] Picture element + - [ ] Support for third party data loaders + - [ ] Consider `loaded` render prop, for blurred placeholder + - [ ] Write tests + - [ ] Bikeshed on prop names + - [ ] Improve types / intellisense support +*/ + +/* + * An optional prop you can use to change the + * default srcSet generation behaviour + */ interface ImageConfig { intervals: number; startingWidth: number; @@ -7,41 +23,64 @@ interface ImageConfig { placeholderWidth: number; } -type ImageComponent = { - as?: 'img' | 'source'; - src: string; - width?: string | number; - height?: string | number; - crop: Crop; - sizes: string; - aspectRatio: string; - scale?: number; - priority?: boolean; - config?: ImageConfig; - alt: string; -}; +/* + * TODO: Expand to include focal point support; + * or switch this to be an SF API type + */ -type Crop = 'center' | 'top' | 'bottom' | 'left' | 'right' | undefined; +type Crop = 'center' | 'top' | 'bottom' | 'left' | 'right'; export function Image({ as: Component = 'img', - src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', + src, + /* + * Supports third party loaders, which are expected to provide + * a function that can generate a URL string + */ + loader = shopifyLoader, + /* + * The default behaviour is a responsive image that fills + * the width of its container. + */ width = '100%', height, + /* + * The default crop is center, in the event that AspectRatio is set, + * without specifying a crop, Imagery won't return the expected image. + */ crop = 'center', - sizes = '(min-width: 768px) 50vw, 100vw', + sizes, + /* + * aspectRatio is a string in the format of 'width/height' + * it's used to generate the srcSet URLs, and to set the + * aspect ratio of the image element to prevent CLS. + */ aspectRatio, - scale, - priority, config = { intervals: 10, startingWidth: 300, incrementSize: 300, placeholderWidth: 100, }, - alt = 'Test Alt Tag', + alt, + loading = 'lazy', ...passthroughProps -}: ImageComponent) { +}: { + as?: 'img' | 'source'; + src: string; + loader?: Function; + width?: string | number; + height?: string | number; + crop?: Crop; + sizes?: string; + aspectRatio?: string; + config?: ImageConfig; + alt?: string; + loading?: 'lazy' | 'eager'; +}) { + /* + * Sanitizes width and height inputs to account for 'number' type + */ let normalizedWidth: string = getUnitValueParts(width.toString()).number + getUnitValueParts(width.toString()).unit; @@ -52,59 +91,108 @@ export function Image({ : getUnitValueParts(height.toString()).number + getUnitValueParts(height.toString()).unit; - if (!isFixedWidth(width)) { - const {intervals, startingWidth, incrementSize, placeholderWidth} = config; + const {intervals, startingWidth, incrementSize, placeholderWidth} = config; - const widths = generateImageWidths( - width, - intervals, - startingWidth, - incrementSize, - ); + /* + * This function creates an array of widths to be used in srcSet + */ + const widths = generateImageWidths( + width, + intervals, + startingWidth, + incrementSize, + ); - const sizesArray = generateSizes(widths, aspectRatio, crop); + /* + * We check to see whether the image is fixed width or not, + * if fixed, we still provide a srcSet, but only to account for + * different pixel densities. + */ + if (isFixedWidth(width)) { + let intWidth: number | undefined = getNormalizedFixedUnit(width); + let intHeight: number | undefined = getNormalizedFixedUnit(height); + + /* + * The aspect ratio for fixed with images is taken from the explicitly + * set prop, but if that's not present, and both width and height are + * set, we calculate the aspect ratio from the width and height — as + * long as they share the same unit type (e.g. both are 'px'). + */ + const fixedAspectRatio = aspectRatio + ? aspectRatio + : unitsMatch(width, height) + ? `${intWidth}/${intHeight}` + : undefined; + + /* + * The Sizes Array generates an array of all of the parts + * that make up the srcSet, including the width, height, and crop + */ + const sizesArray = + widths === undefined + ? undefined + : generateSizes(widths, fixedAspectRatio, crop); return React.createElement(Component, { srcSet: generateShopifySrcSet(src, sizesArray), - src: generateImagerySrc( + src: loader( src, - placeholderWidth, - placeholderWidth * parseAspectRatio(aspectRatio), + intWidth, + intHeight + ? intHeight + : aspectRatio && intWidth + ? intWidth * (parseAspectRatio(aspectRatio) ?? 1) + : undefined, + normalizedHeight === 'auto' ? undefined : crop, ), alt, - sizes, + sizes: sizes || normalizedWidth, style: { width: normalizedWidth, height: normalizedHeight, aspectRatio, }, + loading, ...passthroughProps, }); } else { - // width is fixed - let intWidth: number | undefined = getNormalizedFixedUnit(width); - let intHeight: number | undefined = getNormalizedFixedUnit(height); + const sizesArray = + widths === undefined + ? undefined + : generateSizes(widths, aspectRatio, crop); return React.createElement(Component, { - src: generateImagerySrc( + srcSet: generateShopifySrcSet(src, sizesArray), + src: loader( src, - intWidth, - (aspectRatio && intWidth - ? intWidth * parseAspectRatio(aspectRatio) - : intHeight) ?? undefined, - normalizedHeight === 'auto' ? undefined : crop, + placeholderWidth, + aspectRatio && placeholderWidth + ? placeholderWidth * (parseAspectRatio(aspectRatio) ?? 1) + : undefined, ), alt, + sizes, style: { width: normalizedWidth, height: normalizedHeight, aspectRatio, }, + loading, ...passthroughProps, }); } } +function unitsMatch( + width: string | number = '100%', + height: string | number = 'auto', +) { + return ( + getUnitValueParts(width.toString()).unit === + getUnitValueParts(height.toString()).unit + ); +} + function getUnitValueParts(value: string) { const unit = value.replace(/[0-9.]/g, ''); const number = parseFloat(value.replace(unit, '')); @@ -115,9 +203,9 @@ function getUnitValueParts(value: string) { }; } -function getNormalizedFixedUnit(value: string | number | undefined) { +function getNormalizedFixedUnit(value?: string | number) { if (value === undefined) { - return undefined; + return; } const {unit, number} = getUnitValueParts(value.toString()); @@ -132,7 +220,7 @@ function getNormalizedFixedUnit(value: string | number | undefined) { case '': return number; default: - return undefined; + return; } } @@ -145,8 +233,8 @@ function isFixedWidth(width: string | number) { } export function generateShopifySrcSet( - src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', - sizesArray: Array<{width: number; height: number; crop: Crop}> | undefined, + src: string, + sizesArray?: Array<{width?: number; height?: number; crop?: Crop}>, ) { if (sizesArray?.length === 0 || !sizesArray) { return src; @@ -155,7 +243,7 @@ export function generateShopifySrcSet( return sizesArray .map( (size) => - generateImagerySrc(src, size.width, size.height, size.crop) + + shopifyLoader(src, size.width, size.height, size.crop) + ' ' + size.width + 'w', @@ -175,21 +263,26 @@ export function generateShopifySrcSet( export function generateImageWidths( width: string | number = '100%', - intervals = 20, - startingWidth = 200, - incrementSize = 100, - scale = 1, + intervals: number = 20, + startingWidth: number = 200, + incrementSize: number = 100, ) { const responsive = Array.from( {length: intervals}, - (_, i) => (i * incrementSize + startingWidth) * scale, + (_, i) => i * incrementSize + startingWidth, + ); + + const fixed = Array.from( + {length: 3}, + (_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0), ); - return isFixedWidth(width) ? [getNormalizedFixedUnit(width)] : responsive; + return isFixedWidth(width) ? fixed : responsive; } // Simple utility function to convert 1/1 to [1, 1] -export function parseAspectRatio(aspectRatio: string) { +export function parseAspectRatio(aspectRatio?: string) { + if (!aspectRatio) return; const [width, height] = aspectRatio.split('/'); return 1 / (Number(width) / Number(height)); /* @@ -206,15 +299,17 @@ export function parseAspectRatio(aspectRatio: string) { // Generate data needed for Imagery loader export function generateSizes( - widths: number[] | undefined, - aspectRatio: string, + widths?: number[], + aspectRatio?: string, crop: Crop = 'center', ) { if (!widths) return; const sizes = widths.map((width: number) => { return { width, - height: width * parseAspectRatio(aspectRatio), + height: aspectRatio + ? width * (parseAspectRatio(aspectRatio) ?? 1) + : undefined, crop, }; }); @@ -228,7 +323,13 @@ export function generateSizes( */ } -export function generateImagerySrc( +/* + * The shopifyLoader function is a simple utility function that takes a src, width, + * height, and crop and returns a string that can be used as the src for an image. + * It can be used with the Hydrogen Image component or with the next/image component. + * (or any others that accept equivalent configuration) + */ +export function shopifyLoader( src = 'https://cdn.shopify.com/static/sample-images/garnished.jpeg', width?: number, height?: number, diff --git a/templates/demo-store/app/routes/image-demo.tsx b/templates/demo-store/app/routes/image-demo.tsx index b790599ebc..2bd8ab19f0 100644 --- a/templates/demo-store/app/routes/image-demo.tsx +++ b/templates/demo-store/app/routes/image-demo.tsx @@ -44,7 +44,13 @@ export default function ImageRFC() { return ( <> - {altText} + {altText} {altText} + {altText} {altText} {altText} From 8505c5cc5e39dffb324935622062a4d33b011e80 Mon Sep 17 00:00:00 2001 From: Benjamin Sehl Date: Thu, 5 Jan 2023 00:12:58 -0500 Subject: [PATCH 4/5] Adds inline docs --- packages/hydrogen/src/image.ts | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/hydrogen/src/image.ts b/packages/hydrogen/src/image.ts index db8ad64f47..692b69cd17 100644 --- a/packages/hydrogen/src/image.ts +++ b/packages/hydrogen/src/image.ts @@ -191,6 +191,19 @@ function unitsMatch( getUnitValueParts(width.toString()).unit === getUnitValueParts(height.toString()).unit ); + /* + Given: + width = '100px' + height = 'auto' + Returns: + false + + Given: + width = '100px' + height = '50px' + Returns: + true + */ } function getUnitValueParts(value: string) { @@ -201,6 +214,15 @@ function getUnitValueParts(value: string) { unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit, number, }; + /* + Given: + value = '100px' + Returns: + { + unit: 'px', + number: 100 + } + */ } function getNormalizedFixedUnit(value?: string | number) { @@ -222,6 +244,17 @@ function getNormalizedFixedUnit(value?: string | number) { default: return; } + /* + Given: + value = 16px | 1rem | 1em | 16 + Returns: + 16 + + Given: + value = 100% + Returns: + undefined + */ } function isFixedWidth(width: string | number) { @@ -230,6 +263,12 @@ function isFixedWidth(width: string | number) { typeof width === 'number' || (typeof width === 'string' && fixedEndings.test(width)) ); + /* + Given: + width = 100 | '100px' | '100em' | '100rem' + Returns: + true + */ } export function generateShopifySrcSet( From 38183cb91e3752dce85e5a0a0c93c45e1d1e57d7 Mon Sep 17 00:00:00 2001 From: Benjamin Sehl Date: Thu, 5 Jan 2023 00:14:33 -0500 Subject: [PATCH 5/5] Removes inline TODO list --- packages/hydrogen/src/image.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/hydrogen/src/image.ts b/packages/hydrogen/src/image.ts index 692b69cd17..ef5f5868cc 100644 --- a/packages/hydrogen/src/image.ts +++ b/packages/hydrogen/src/image.ts @@ -1,17 +1,5 @@ import React from 'react'; -/* @TODO: - - [x] Support original aspect ratio - - [x] Support for non-100% widths - - [ ] Create guide/docs - - [ ] Picture element - - [ ] Support for third party data loaders - - [ ] Consider `loaded` render prop, for blurred placeholder - - [ ] Write tests - - [ ] Bikeshed on prop names - - [ ] Improve types / intellisense support -*/ - /* * An optional prop you can use to change the * default srcSet generation behaviour