@shopify/shop-minis-react 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/_virtual/index2.js +4 -4
  2. package/dist/_virtual/index3.js +4 -4
  3. package/dist/_virtual/index4.js +2 -2
  4. package/dist/_virtual/index5.js +3 -2
  5. package/dist/_virtual/index5.js.map +1 -1
  6. package/dist/_virtual/index6.js +2 -2
  7. package/dist/_virtual/index7.js +2 -3
  8. package/dist/_virtual/index7.js.map +1 -1
  9. package/dist/_virtual/index8.js +2 -2
  10. package/dist/_virtual/index9.js +2 -2
  11. package/dist/components/atoms/image.js +52 -0
  12. package/dist/components/atoms/image.js.map +1 -0
  13. package/dist/components/commerce/merchant-card.js +188 -245
  14. package/dist/components/commerce/merchant-card.js.map +1 -1
  15. package/dist/components/commerce/product-card.js +11 -11
  16. package/dist/components/commerce/product-card.js.map +1 -1
  17. package/dist/components/content/image-content-wrapper.js +29 -22
  18. package/dist/components/content/image-content-wrapper.js.map +1 -1
  19. package/dist/hooks/content/useCreateImageContent.js +16 -22
  20. package/dist/hooks/content/useCreateImageContent.js.map +1 -1
  21. package/dist/hooks/storage/useImageUpload.js +36 -37
  22. package/dist/hooks/storage/useImageUpload.js.map +1 -1
  23. package/dist/index.js +252 -246
  24. package/dist/shop-minis-platform/src/types/content.js.map +1 -1
  25. package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
  26. package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
  27. package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
  28. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
  29. package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
  30. package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
  31. package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
  32. package/dist/utils/colors.js +1 -1
  33. package/dist/utils/image.js +45 -9
  34. package/dist/utils/image.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/components/atoms/{thumbhash-image.tsx → image.tsx} +14 -14
  37. package/src/components/commerce/merchant-card.tsx +224 -225
  38. package/src/components/commerce/product-card.tsx +2 -2
  39. package/src/components/content/image-content-wrapper.tsx +9 -2
  40. package/src/components/index.ts +1 -1
  41. package/src/hooks/content/useCreateImageContent.ts +1 -7
  42. package/src/hooks/storage/useImageUpload.ts +22 -20
  43. package/src/stories/MerchantCard.stories.tsx +0 -3
  44. package/src/utils/image.ts +72 -0
  45. package/src/utils/index.ts +1 -1
  46. package/dist/components/atoms/thumbhash-image.js +0 -54
  47. package/dist/components/atoms/thumbhash-image.js.map +0 -1
  48. package/dist/utils/imageToDataUri.js +0 -10
  49. package/dist/utils/imageToDataUri.js.map +0 -1
  50. package/src/utils/imageToDataUri.ts +0 -8
@@ -1,10 +1,18 @@
1
1
  import {useCallback} from 'react'
2
2
 
3
3
  import {useShopActions} from '../../internal/useShopActions'
4
+ import {fileToDataUri} from '../../utils'
4
5
 
5
6
  import type {UploadTarget} from '@shopify/shop-minis-platform/actions'
6
7
 
7
8
  export interface UploadImageParams {
9
+ /**
10
+ * The file to upload.
11
+ */
12
+ image: File
13
+ }
14
+
15
+ interface ProcessedImage {
8
16
  /**
9
17
  * The MIME type of the image.
10
18
  */
@@ -12,11 +20,11 @@ export interface UploadImageParams {
12
20
  /**
13
21
  * The size of the image in bytes.
14
22
  */
15
- fileSize?: number
23
+ fileSize: number
16
24
  /**
17
- * The URI of the image to upload.
25
+ * The file blob of the image.
18
26
  */
19
- uri: string
27
+ fileBlob: Blob
20
28
  }
21
29
 
22
30
  export interface UploadedImage {
@@ -36,28 +44,27 @@ export interface UploadedImage {
36
44
 
37
45
  interface UseImageUploadReturns {
38
46
  /**
39
- * Upload an image attached to the current user.
47
+ * Upload an image which will be attached to the current user.
40
48
  */
41
- uploadImage: (params: UploadImageParams[]) => Promise<UploadedImage[]>
49
+ uploadImage: (image: File) => Promise<UploadedImage[]>
42
50
  }
43
51
 
44
52
  // Fetch file data and detect file sizes if not provided
45
53
  // Works with file://, data:, and http(s):// URIs
46
- const processFileData = async (image: UploadImageParams) => {
47
- const response = await fetch(image.uri)
54
+ const processFileData = async (image: File): Promise<ProcessedImage> => {
55
+ const uri = await fileToDataUri(image)
56
+
57
+ const response = await fetch(uri)
48
58
  const blob = await response.blob()
49
59
 
50
60
  return {
51
- ...image,
52
- fileSize: image.fileSize ?? blob.size,
61
+ mimeType: image.type,
62
+ fileSize: image.size ?? blob.size,
53
63
  fileBlob: blob,
54
64
  }
55
65
  }
56
66
 
57
- const uploadFileToGCS = async (
58
- image: UploadImageParams & {fileSize: number; fileBlob: Blob},
59
- target: UploadTarget
60
- ) => {
67
+ const uploadFileToGCS = async (image: ProcessedImage, target: UploadTarget) => {
61
68
  const formData = new FormData()
62
69
  target.parameters.forEach(({name, value}: {name: string; value: string}) => {
63
70
  formData.append(name, value)
@@ -84,13 +91,8 @@ export const useImageUpload = (): UseImageUploadReturns => {
84
91
  const {createImageUploadLink, completeImageUpload} = useShopActions()
85
92
 
86
93
  const uploadImage = useCallback(
87
- async (params: UploadImageParams[]) => {
88
- if (params.length > 1) {
89
- throw new Error('Multiple image upload is not supported yet')
90
- }
91
-
92
- const imageParams = params[0]
93
- const processedImageParams = await processFileData(imageParams)
94
+ async (image: File) => {
95
+ const processedImageParams = await processFileData(image)
94
96
 
95
97
  const links = await createImageUploadLink({
96
98
  input: [
@@ -15,9 +15,6 @@ const meta = {
15
15
  touchable: {
16
16
  control: 'boolean',
17
17
  },
18
- fixedHeight: {
19
- control: 'boolean',
20
- },
21
18
  featuredImagesLimit: {
22
19
  control: 'number',
23
20
  },
@@ -16,3 +16,75 @@ export function getThumbhashDataURL(thumbhash?: string): string | undefined {
16
16
  console.warn('Failed to decode thumbhash to data URL', error)
17
17
  }
18
18
  }
19
+
20
+ /** Converts a file to a data URI
21
+ * @param file The file to convert
22
+ * @returns A promise that resolves to the data URI string
23
+ */
24
+ export function fileToDataUri(file: File): Promise<string> {
25
+ return new Promise((resolve, reject) => {
26
+ const reader = new FileReader()
27
+ reader.onloadend = () => resolve(reader.result as string)
28
+ reader.onerror = reject
29
+ reader.readAsDataURL(file)
30
+ })
31
+ }
32
+
33
+ const ImageSizes = {
34
+ xxsUrl: 32,
35
+ xsUrl: 64,
36
+ sUrl: 128,
37
+ xxsmUrl: 256,
38
+ xsmUrl: 384,
39
+ smUrl: 512,
40
+ mUrl: 640,
41
+ lUrl: 1080,
42
+ xlUrl: 2048,
43
+ } as const
44
+
45
+ type Key = keyof typeof ImageSizes
46
+
47
+ /**
48
+ * Acceptable offset for image sizes. An image could use the size that is within this offset.
49
+ */
50
+ const offsetPercentage = 0.05
51
+
52
+ const sortedImageSizes = Object.entries(ImageSizes).sort(
53
+ ([, firstSize], [, secondSize]) => firstSize - secondSize
54
+ )
55
+
56
+ const getImageSizeKeyWithSize = (size: number): Key => {
57
+ for (const [key, imgSize] of sortedImageSizes) {
58
+ const upperBoundSize = imgSize + imgSize * offsetPercentage
59
+ if (size <= upperBoundSize) return key as Key
60
+ }
61
+
62
+ return 'xlUrl'
63
+ }
64
+
65
+ const resizeImage = (imageUrl: string, width: number) => {
66
+ const pattern = new RegExp(/\?+/g)
67
+ const delimiter = pattern.test(imageUrl) ? '&' : '?'
68
+ return `${imageUrl}${delimiter}width=${width}`
69
+ }
70
+
71
+ /**
72
+ * Optimizes Shopify CDN image URLs by adding a width parameter based on screen size
73
+ * @param url The image URL to optimize
74
+ * @returns The optimized URL with width parameter if it's a Shopify CDN image, otherwise returns the original URL
75
+ */
76
+
77
+ export const getResizedImageUrl = (url?: string): string => {
78
+ if (!url) return ''
79
+
80
+ // Only process Shopify CDN images
81
+ if (!url.startsWith('https://cdn.shopify.com')) {
82
+ return url
83
+ }
84
+
85
+ const width = window.innerWidth ?? screen.width
86
+
87
+ const key = getImageSizeKeyWithSize(width)
88
+
89
+ return resizeImage(url, ImageSizes[key])
90
+ }
@@ -1,4 +1,4 @@
1
1
  export * from './errors'
2
2
  export * from './merchant-card'
3
3
  export * from './parseUrl'
4
- export * from './imageToDataUri'
4
+ export * from './image'
@@ -1,54 +0,0 @@
1
- import { jsx as t } from "react/jsx-runtime";
2
- import { memo as p, useState as g, useMemo as L, useCallback as v } from "react";
3
- import { cn as s } from "../../lib/utils.js";
4
- import { getThumbhashDataURL as k } from "../../utils/image.js";
5
- const j = p(function(r) {
6
- const {
7
- src: c,
8
- alt: m,
9
- thumbhash: o,
10
- onLoad: a,
11
- className: l,
12
- style: i,
13
- aspectRatio: n = "auto",
14
- ...u
15
- } = r, [d, h] = g(!1), e = L(
16
- () => k(o ?? void 0),
17
- [o]
18
- ), b = v(
19
- (f) => {
20
- h(!0), a?.(f);
21
- },
22
- [a]
23
- );
24
- return /* @__PURE__ */ t(
25
- "div",
26
- {
27
- className: s("relative w-full ", l),
28
- style: {
29
- ...i,
30
- aspectRatio: n,
31
- backgroundImage: e ? `url(${e})` : void 0,
32
- backgroundSize: "cover",
33
- backgroundPosition: "center"
34
- },
35
- children: /* @__PURE__ */ t(
36
- "img",
37
- {
38
- className: s(
39
- "absolute inset-0 w-full h-full opacity-0 object-cover",
40
- d && "opacity-100"
41
- ),
42
- src: c,
43
- alt: m,
44
- onLoad: b,
45
- ...u
46
- }
47
- )
48
- }
49
- );
50
- });
51
- export {
52
- j as ThumbhashImage
53
- };
54
- //# sourceMappingURL=thumbhash-image.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"thumbhash-image.js","sources":["../../../src/components/atoms/thumbhash-image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {ImgHTMLAttributes, useCallback, useMemo, memo, useState} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL} from '../../utils/image'\n\ntype ThumbhashImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src: string\n thumbhash: string\n alt?: string | null\n aspectRatio?: number | string\n}\n\nexport const ThumbhashImage = memo(function ThumbhashImage(\n props: ThumbhashImageProps\n) {\n const {\n src,\n alt,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n\n const dataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n return (\n <div\n className={cn('relative w-full ', className)}\n style={{\n ...style,\n aspectRatio,\n backgroundImage: dataURL ? `url(${dataURL})` : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n 'absolute inset-0 w-full h-full opacity-0 object-cover',\n isLoaded && 'opacity-100'\n )}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n {...restProps}\n />\n </div>\n )\n})\n"],"names":["ThumbhashImage","memo","props","src","alt","thumbhash","onLoad","className","style","aspectRatio","restProps","isLoaded","setIsLoaded","useState","dataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","jsx","cn"],"mappings":";;;;AAaO,MAAMA,IAAiBC,EAAK,SACjCC,GACA;AACM,QAAA;AAAA,IACJ,KAAAC;AAAA,IACA,KAAAC;AAAA,IACA,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,aAAAC,IAAc;AAAA,IACd,GAAGC;AAAA,EAAA,IACDR,GAEE,CAACS,GAAUC,CAAW,IAAIC,EAAS,EAAK,GAExCC,IAAUC;AAAA,IACd,MAAMC,EAAoBX,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMY,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAP,EAAY,EAAI,GAChBN,IAASa,CAAK;AAAA,IAChB;AAAA,IACA,CAACb,CAAM;AAAA,EACT;AAGE,SAAA,gBAAAc;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,oBAAoBd,CAAS;AAAA,MAC3C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,aAAAC;AAAA,QACA,iBAAiBK,IAAU,OAAOA,CAAO,MAAM;AAAA,QAC/C,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAM;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACT;AAAA,YACAV,KAAY;AAAA,UACd;AAAA,UACA,KAAAR;AAAA,UACA,KAAAC;AAAA,UACA,QAAQa;AAAA,UACP,GAAGP;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
@@ -1,10 +0,0 @@
1
- function a(r) {
2
- return new Promise((n, o) => {
3
- const e = new FileReader();
4
- e.onloadend = () => n(e.result), e.onerror = o, e.readAsDataURL(r);
5
- });
6
- }
7
- export {
8
- a as fileToDataUri
9
- };
10
- //# sourceMappingURL=imageToDataUri.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageToDataUri.js","sources":["../../src/utils/imageToDataUri.ts"],"sourcesContent":["export function fileToDataUri(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onloadend = () => resolve(reader.result as string)\n reader.onerror = reject\n reader.readAsDataURL(file)\n })\n}\n"],"names":["fileToDataUri","file","resolve","reject","reader"],"mappings":"AAAO,SAASA,EAAcC,GAA6B;AACzD,SAAO,IAAI,QAAQ,CAACC,GAASC,MAAW;AAChC,UAAAC,IAAS,IAAI,WAAW;AAC9B,IAAAA,EAAO,YAAY,MAAMF,EAAQE,EAAO,MAAgB,GACxDA,EAAO,UAAUD,GACjBC,EAAO,cAAcH,CAAI;AAAA,EAAA,CAC1B;AACH;"}
@@ -1,8 +0,0 @@
1
- export function fileToDataUri(file: File): Promise<string> {
2
- return new Promise((resolve, reject) => {
3
- const reader = new FileReader()
4
- reader.onloadend = () => resolve(reader.result as string)
5
- reader.onerror = reject
6
- reader.readAsDataURL(file)
7
- })
8
- }