UNPKG

@shopify/shop-minis-react

Version:

React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)

130 lines (109 loc) 3.7 kB
import {toUint8Array} from 'js-base64' import {thumbHashToDataURL} from 'thumbhash' /** * Converts a thumbhash string to a data URL for use as an image placeholder * @param thumbhash Base64 encoded thumbhash string * @returns Data URL that can be used as image source or undefined if conversion fails */ export function getThumbhashDataURL(thumbhash?: string): string | undefined { if (!thumbhash) return try { const thumbhashArray = toUint8Array(thumbhash) return thumbHashToDataURL(thumbhashArray) } catch (error) { console.warn('Failed to decode thumbhash to data URL', error) return undefined } } /** * Converts a data URL to a Blob * @param dataURL The data URL to convert * @returns A Blob object */ export function dataURLToBlob(dataURL: string): Blob { const [header, base64Data] = dataURL.split(',') const mimeMatch = header.match(/:(.*?);/) const mime = mimeMatch ? mimeMatch[1] : 'image/png' const binary = atob(base64Data) const array = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i) } return new Blob([array], {type: mime}) } /** * Converts a thumbhash string to a blob URL for use as an image placeholder * This is useful when CSP restrictions prevent data URLs * @param thumbhash Base64 encoded thumbhash string * @returns Blob URL that can be used as image source or undefined if conversion fails */ export function getThumbhashBlobURL(thumbhash?: string): string | undefined { if (!thumbhash) return try { const dataURL = getThumbhashDataURL(thumbhash) if (!dataURL) return const blob = dataURLToBlob(dataURL) return URL.createObjectURL(blob) } catch (error) { console.warn('Failed to create thumbhash blob URL', error) return undefined } } /** Converts a file to a data URI * @param file The file to convert * @returns A promise that resolves to the data URI string */ export function fileToDataUri(file: File): Promise<string> { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = () => resolve(reader.result as string) reader.onerror = reject reader.readAsDataURL(file) }) } const ImageSizes = { xxsUrl: 32, xsUrl: 64, sUrl: 128, xxsmUrl: 256, xsmUrl: 384, smUrl: 512, mUrl: 640, lUrl: 1080, xlUrl: 2048, } as const type Key = keyof typeof ImageSizes /** * Acceptable offset for image sizes. An image could use the size that is within this offset. */ const offsetPercentage = 0.05 const sortedImageSizes = Object.entries(ImageSizes).sort( ([, firstSize], [, secondSize]) => firstSize - secondSize ) const getImageSizeKeyWithSize = (size: number): Key => { for (const [key, imgSize] of sortedImageSizes) { const upperBoundSize = imgSize + imgSize * offsetPercentage if (size <= upperBoundSize) return key as Key } return 'xlUrl' } const resizeImage = (imageUrl: string, width: number) => { const pattern = new RegExp(/\?+/g) const delimiter = pattern.test(imageUrl) ? '&' : '?' return `${imageUrl}${delimiter}width=${width}` } /** * Optimizes Shopify CDN image URLs by adding a width parameter based on screen size * @param url The image URL to optimize * @returns The optimized URL with width parameter if it's a Shopify CDN image, otherwise returns the original URL */ export const getResizedImageUrl = (url?: string): string => { if (!url) return '' // Only process Shopify CDN images if (!url.startsWith('https://cdn.shopify.com')) { return url } const width = window.innerWidth ?? screen.width const key = getImageSizeKeyWithSize(width) return resizeImage(url, ImageSizes[key]) }