@shopify/shop-minis-react 0.13.0 → 0.13.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"content-wrapper.js","sources":["../../../src/components/atoms/content-wrapper.tsx"],"sourcesContent":["import {useContent} from '../../hooks/content/useContent'\nimport {Content} from '../../types'\n\nimport {ContentMonitor} from './content-monitor'\n\ninterface BaseContentWrapperProps {\n children: ({\n content,\n loading,\n }: {\n content?: Content\n loading: boolean\n }) => React.JSX.Element | null\n}\n\ninterface PublicIdContentWrapperProps extends BaseContentWrapperProps {\n publicId: string\n externalId?: never\n}\n\ninterface ExternalIdContentWrapperProps extends BaseContentWrapperProps {\n externalId: string\n publicId?: never\n}\n\ntype ContentWrapperProps =\n | PublicIdContentWrapperProps\n | ExternalIdContentWrapperProps\n\n// It's too messy in the docs to show the complete types here so we show a simplified version\nexport interface ContentWrapperPropsForDocs extends BaseContentWrapperProps {\n publicId?: string\n externalId?: string\n}\n\nexport function ContentWrapper({\n publicId,\n externalId,\n children,\n}: ContentWrapperProps) {\n const {content, loading} = useContent({\n identifiers: [{publicId, externalId}],\n skip: !publicId && !externalId,\n })\n\n const contentItem = content?.[0]\n\n if (loading || !contentItem) {\n return children({loading})\n }\n\n return (\n <ContentMonitor publicId={contentItem.publicId}>\n {children({content: contentItem, loading})}\n </ContentMonitor>\n )\n}\n"],"names":["ContentWrapper","publicId","externalId","children","content","loading","useContent","contentItem","jsx","ContentMonitor"],"mappings":";;;AAmCO,SAASA,EAAe;AAAA,EAC7B,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AACF,GAAwB;AACtB,QAAM,EAAC,SAAAC,GAAS,SAAAC,EAAO,IAAIC,EAAW;AAAA,IACpC,aAAa,CAAC,EAAC,UAAAL,GAAU,YAAAC,GAAW;AAAA,IACpC,MAAM,CAACD,KAAY,CAACC;AAAA,EAAA,CACrB,GAEKK,IAAcH,IAAU,CAAC;AAE3B,SAAAC,KAAW,CAACE,IACPJ,EAAS,EAAC,SAAAE,GAAQ,IAIzB,gBAAAG,EAACC,GAAe,EAAA,UAAUF,EAAY,UACnC,UAASJ,EAAA,EAAC,SAASI,GAAa,SAAAF,EAAO,CAAC,EAC3C,CAAA;AAEJ;"}
1
+ {"version":3,"file":"content-wrapper.js","sources":["../../../src/components/atoms/content-wrapper.tsx"],"sourcesContent":["import {useContent} from '../../hooks/content/useContent'\nimport {Content} from '../../types'\n\nimport {ContentMonitor} from './content-monitor'\n\ninterface BaseContentWrapperProps {\n /** A render function that receives the content data and loading state, and returns a React element. */\n children: ({\n content,\n loading,\n }: {\n content?: Content\n loading: boolean\n }) => React.JSX.Element | null\n}\n\ninterface PublicIdContentWrapperProps extends BaseContentWrapperProps {\n publicId: string\n externalId?: never\n}\n\ninterface ExternalIdContentWrapperProps extends BaseContentWrapperProps {\n externalId: string\n publicId?: never\n}\n\ntype ContentWrapperProps =\n | PublicIdContentWrapperProps\n | ExternalIdContentWrapperProps\n\n// It's too messy in the docs to show the complete types here so we show a simplified version\nexport interface ContentWrapperPropsForDocs extends BaseContentWrapperProps {\n /** The public ID of the content item (use this OR externalId). */\n publicId?: string\n /** The external ID of the content item (use this OR publicId). */\n externalId?: string\n}\n\nexport function ContentWrapper({\n publicId,\n externalId,\n children,\n}: ContentWrapperProps) {\n const {content, loading} = useContent({\n identifiers: [{publicId, externalId}],\n skip: !publicId && !externalId,\n })\n\n const contentItem = content?.[0]\n\n if (loading || !contentItem) {\n return children({loading})\n }\n\n return (\n <ContentMonitor publicId={contentItem.publicId}>\n {children({content: contentItem, loading})}\n </ContentMonitor>\n )\n}\n"],"names":["ContentWrapper","publicId","externalId","children","content","loading","useContent","contentItem","jsx","ContentMonitor"],"mappings":";;;AAsCO,SAASA,EAAe;AAAA,EAC7B,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AACF,GAAwB;AACtB,QAAM,EAAC,SAAAC,GAAS,SAAAC,EAAO,IAAIC,EAAW;AAAA,IACpC,aAAa,CAAC,EAAC,UAAAL,GAAU,YAAAC,GAAW;AAAA,IACpC,MAAM,CAACD,KAAY,CAACC;AAAA,EAAA,CACrB,GAEKK,IAAcH,IAAU,CAAC;AAE3B,SAAAC,KAAW,CAACE,IACPJ,EAAS,EAAC,SAAAE,GAAQ,IAIzB,gBAAAG,EAACC,GAAe,EAAA,UAAUF,EAAY,UACnC,UAASJ,EAAA,EAAC,SAASI,GAAa,SAAAF,EAAO,CAAC,EAC3C,CAAA;AAEJ;"}
@@ -1,34 +1,44 @@
1
- import { useState as g, useCallback as p } from "react";
2
- import { useHandleAction as f } from "../../internal/useHandleAction.js";
3
- import { useShopActions as d } from "../../internal/useShopActions.js";
4
- import { useImageUpload as I } from "../storage/useImageUpload.js";
5
- const U = () => {
6
- const { createContent: t } = d(), { uploadImage: a } = I(), [r, o] = g(!1), i = p(
1
+ import { useState as f, useCallback as I } from "react";
2
+ import { useHandleAction as w } from "../../internal/useHandleAction.js";
3
+ import { useShopActions as C } from "../../internal/useShopActions.js";
4
+ import { useImageUpload as y } from "../storage/useImageUpload.js";
5
+ const v = () => {
6
+ const { createContent: t } = C(), { uploadImage: o } = y(), [n, a] = f(!1), i = I(
7
7
  async (s) => {
8
- o(!0);
9
- const { image: e, contentTitle: l, visibility: m } = s;
8
+ a(!0);
9
+ const {
10
+ image: e,
11
+ contentTitle: l,
12
+ visibility: m,
13
+ externalId: c,
14
+ description: p,
15
+ productIds: u
16
+ } = s;
10
17
  if (!e.type)
11
18
  throw new Error("Unable to determine file type");
12
19
  if (!e.type.startsWith("image/"))
13
20
  throw new Error("Invalid file type: must be an image");
14
- const [c] = await a(e), n = c.imageUrl;
15
- if (!n)
21
+ const [d] = await o(e), r = d.imageUrl;
22
+ if (!r)
16
23
  throw new Error("Image upload failed");
17
- const u = await t({
24
+ const g = await t({
18
25
  title: l,
19
- imageUrl: n,
20
- visibility: m
26
+ imageUrl: r,
27
+ visibility: m,
28
+ externalId: c,
29
+ description: p,
30
+ productIds: u
21
31
  });
22
- return o(!1), u;
32
+ return a(!1), g;
23
33
  },
24
- [t, a]
34
+ [t, o]
25
35
  );
26
36
  return {
27
- createImageContent: f(i),
28
- loading: r
37
+ createImageContent: w(i),
38
+ loading: n
29
39
  };
30
40
  };
31
41
  export {
32
- U as useCreateImageContent
42
+ v as useCreateImageContent
33
43
  };
34
44
  //# sourceMappingURL=useCreateImageContent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCreateImageContent.js","sources":["../../../src/hooks/content/useCreateImageContent.ts"],"sourcesContent":["import {useCallback, useState} from 'react'\n\nimport {\n ContentVisibility,\n Content,\n ContentCreateUserErrors,\n} from '@shopify/shop-minis-platform'\n\nimport {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\nimport {useImageUpload} from '../storage/useImageUpload'\n\ninterface CreateImageContentParams {\n image: File\n contentTitle: string\n visibility?: ContentVisibility[] | null\n}\n\ninterface UseCreateImageContentReturns {\n /**\n * Upload an image and create content.\n */\n createImageContent: (\n params: CreateImageContentParams\n ) => Promise<{data: Content; userErrors?: ContentCreateUserErrors[]}>\n /**\n * Whether the content is being created.\n */\n loading: boolean\n}\n\nexport const useCreateImageContent = (): UseCreateImageContentReturns => {\n const {createContent} = useShopActions()\n const {uploadImage} = useImageUpload()\n const [loading, setLoading] = useState(false)\n\n const createImageContent = useCallback(\n async (params: CreateImageContentParams) => {\n setLoading(true)\n\n const {image, contentTitle, visibility} = params\n\n if (!image.type) {\n throw new Error('Unable to determine file type')\n }\n if (!image.type.startsWith('image/')) {\n throw new Error('Invalid file type: must be an image')\n }\n\n const [uploadImageResult] = await uploadImage(image)\n const uploadImageUrl = uploadImageResult.imageUrl\n\n if (!uploadImageUrl) {\n throw new Error('Image upload failed')\n }\n\n const createContentResult = await createContent({\n title: contentTitle,\n imageUrl: uploadImageUrl,\n visibility,\n })\n\n setLoading(false)\n\n return createContentResult\n },\n [createContent, uploadImage]\n )\n\n return {\n createImageContent: useHandleAction(createImageContent),\n loading,\n }\n}\n"],"names":["useCreateImageContent","createContent","useShopActions","uploadImage","useImageUpload","loading","setLoading","useState","createImageContent","useCallback","params","image","contentTitle","visibility","uploadImageResult","uploadImageUrl","createContentResult","useHandleAction"],"mappings":";;;;AA+BO,MAAMA,IAAwB,MAAoC;AACjE,QAAA,EAAC,eAAAC,EAAa,IAAIC,EAAe,GACjC,EAAC,aAAAC,EAAW,IAAIC,EAAe,GAC/B,CAACC,GAASC,CAAU,IAAIC,EAAS,EAAK,GAEtCC,IAAqBC;AAAA,IACzB,OAAOC,MAAqC;AAC1C,MAAAJ,EAAW,EAAI;AAEf,YAAM,EAAC,OAAAK,GAAO,cAAAC,GAAc,YAAAC,EAAc,IAAAH;AAEtC,UAAA,CAACC,EAAM;AACH,cAAA,IAAI,MAAM,+BAA+B;AAEjD,UAAI,CAACA,EAAM,KAAK,WAAW,QAAQ;AAC3B,cAAA,IAAI,MAAM,qCAAqC;AAGvD,YAAM,CAACG,CAAiB,IAAI,MAAMX,EAAYQ,CAAK,GAC7CI,IAAiBD,EAAkB;AAEzC,UAAI,CAACC;AACG,cAAA,IAAI,MAAM,qBAAqB;AAGjC,YAAAC,IAAsB,MAAMf,EAAc;AAAA,QAC9C,OAAOW;AAAA,QACP,UAAUG;AAAA,QACV,YAAAF;AAAA,MAAA,CACD;AAED,aAAAP,EAAW,EAAK,GAETU;AAAA,IACT;AAAA,IACA,CAACf,GAAeE,CAAW;AAAA,EAC7B;AAEO,SAAA;AAAA,IACL,oBAAoBc,EAAgBT,CAAkB;AAAA,IACtD,SAAAH;AAAA,EACF;AACF;"}
1
+ {"version":3,"file":"useCreateImageContent.js","sources":["../../../src/hooks/content/useCreateImageContent.ts"],"sourcesContent":["import {useCallback, useState} from 'react'\n\nimport {\n ContentVisibility,\n Content,\n ContentCreateUserErrors,\n} from '@shopify/shop-minis-platform'\n\nimport {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\nimport {useImageUpload} from '../storage/useImageUpload'\n\ninterface CreateImageContentParams {\n /**\n * The image file to upload.\n */\n image: File\n /**\n * The title for the content entry.\n */\n contentTitle: string\n /**\n * Visibility options for the content. Use `['DISCOVERABLE']` to appear in\n * recommendations, `['LINKABLE']` to enable shareable URLs, or both. Pass\n * `null` or `[]` to keep content private within your Mini.\n */\n visibility?: ContentVisibility[] | null\n /**\n * A unique identifier from your own system that lets you look up content\n * later via `ContentWrapper` using an ID you already know.\n * If not provided, the content can only be retrieved by its `publicId`.\n * Each `externalId` must be unique per Mini — creating content with a\n * duplicate returns a `DUPLICATE_EXTERNAL_ID` error.\n */\n externalId?: string\n /**\n * A text description for the content. Displayed alongside the image in Shop\n * surfaces such as feeds and content detail views. Use this to provide\n * context about the image, such as a caption or review text.\n */\n description?: string\n /**\n * An array of Shopify product GIDs (e.g. `'gid://shopify/Product/123'`) to\n * associate with the content. Maximum 20 products. Associated products\n * appear alongside the content, enabling shoppable content experiences.\n */\n productIds?: string[]\n}\n\ninterface UseCreateImageContentReturns {\n /**\n * Upload an image and create content.\n */\n createImageContent: (\n params: CreateImageContentParams\n ) => Promise<{data: Content; userErrors?: ContentCreateUserErrors[]}>\n /**\n * Whether the content is being created.\n */\n loading: boolean\n}\n\nexport const useCreateImageContent = (): UseCreateImageContentReturns => {\n const {createContent} = useShopActions()\n const {uploadImage} = useImageUpload()\n const [loading, setLoading] = useState(false)\n\n const createImageContent = useCallback(\n async (params: CreateImageContentParams) => {\n setLoading(true)\n\n const {\n image,\n contentTitle,\n visibility,\n externalId,\n description,\n productIds,\n } = params\n\n if (!image.type) {\n throw new Error('Unable to determine file type')\n }\n if (!image.type.startsWith('image/')) {\n throw new Error('Invalid file type: must be an image')\n }\n\n const [uploadImageResult] = await uploadImage(image)\n const uploadImageUrl = uploadImageResult.imageUrl\n\n if (!uploadImageUrl) {\n throw new Error('Image upload failed')\n }\n\n const createContentResult = await createContent({\n title: contentTitle,\n imageUrl: uploadImageUrl,\n visibility,\n externalId,\n description,\n productIds,\n })\n\n setLoading(false)\n\n return createContentResult\n },\n [createContent, uploadImage]\n )\n\n return {\n createImageContent: useHandleAction(createImageContent),\n loading,\n }\n}\n"],"names":["useCreateImageContent","createContent","useShopActions","uploadImage","useImageUpload","loading","setLoading","useState","createImageContent","useCallback","params","image","contentTitle","visibility","externalId","description","productIds","uploadImageResult","uploadImageUrl","createContentResult","useHandleAction"],"mappings":";;;;AA8DO,MAAMA,IAAwB,MAAoC;AACjE,QAAA,EAAC,eAAAC,EAAa,IAAIC,EAAe,GACjC,EAAC,aAAAC,EAAW,IAAIC,EAAe,GAC/B,CAACC,GAASC,CAAU,IAAIC,EAAS,EAAK,GAEtCC,IAAqBC;AAAA,IACzB,OAAOC,MAAqC;AAC1C,MAAAJ,EAAW,EAAI;AAET,YAAA;AAAA,QACJ,OAAAK;AAAA,QACA,cAAAC;AAAA,QACA,YAAAC;AAAA,QACA,YAAAC;AAAA,QACA,aAAAC;AAAA,QACA,YAAAC;AAAA,MAAA,IACEN;AAEA,UAAA,CAACC,EAAM;AACH,cAAA,IAAI,MAAM,+BAA+B;AAEjD,UAAI,CAACA,EAAM,KAAK,WAAW,QAAQ;AAC3B,cAAA,IAAI,MAAM,qCAAqC;AAGvD,YAAM,CAACM,CAAiB,IAAI,MAAMd,EAAYQ,CAAK,GAC7CO,IAAiBD,EAAkB;AAEzC,UAAI,CAACC;AACG,cAAA,IAAI,MAAM,qBAAqB;AAGjC,YAAAC,IAAsB,MAAMlB,EAAc;AAAA,QAC9C,OAAOW;AAAA,QACP,UAAUM;AAAA,QACV,YAAAL;AAAA,QACA,YAAAC;AAAA,QACA,aAAAC;AAAA,QACA,YAAAC;AAAA,MAAA,CACD;AAED,aAAAV,EAAW,EAAK,GAETa;AAAA,IACT;AAAA,IACA,CAAClB,GAAeE,CAAW;AAAA,EAC7B;AAEO,SAAA;AAAA,IACL,oBAAoBiB,EAAgBZ,CAAkB;AAAA,IACtD,SAAAH;AAAA,EACF;AACF;"}
@@ -1,12 +1,15 @@
1
+ import { useCallback as s } from "react";
1
2
  import { useHandleAction as i } from "../../internal/useHandleAction.js";
2
- import { useShopActions as e } from "../../internal/useShopActions.js";
3
- const t = () => {
4
- const { closeMini: o } = e();
3
+ import { useShopActions as n } from "../../internal/useShopActions.js";
4
+ const l = () => {
5
+ const { closeMini: e } = n(), o = i(e);
5
6
  return {
6
- closeMini: i(o)
7
+ // Wrapper prevents React SyntheticEvent args from leaking through
8
+ // when used directly as an event handler (e.g. onClick={closeMini})
9
+ closeMini: s(() => o(), [o])
7
10
  };
8
11
  };
9
12
  export {
10
- t as useCloseMini
13
+ l as useCloseMini
11
14
  };
12
15
  //# sourceMappingURL=useCloseMini.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCloseMini.js","sources":["../../../src/hooks/navigation/useCloseMini.ts"],"sourcesContent":["import {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\n\ninterface UseCloseMiniReturns {\n /**\n * Closes the Mini viewer.\n */\n closeMini: () => void\n}\n\nexport const useCloseMini = (): UseCloseMiniReturns => {\n const {closeMini} = useShopActions()\n\n return {\n closeMini: useHandleAction(closeMini),\n }\n}\n"],"names":["useCloseMini","closeMini","useShopActions","useHandleAction"],"mappings":";;AAUO,MAAMA,IAAe,MAA2B;AAC/C,QAAA,EAAC,WAAAC,EAAS,IAAIC,EAAe;AAE5B,SAAA;AAAA,IACL,WAAWC,EAAgBF,CAAS;AAAA,EACtC;AACF;"}
1
+ {"version":3,"file":"useCloseMini.js","sources":["../../../src/hooks/navigation/useCloseMini.ts"],"sourcesContent":["import {useCallback} from 'react'\n\nimport {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\n\ninterface UseCloseMiniReturns {\n /**\n * Closes the Mini viewer.\n */\n closeMini: () => void\n}\n\nexport const useCloseMini = (): UseCloseMiniReturns => {\n const {closeMini} = useShopActions()\n const handleClose = useHandleAction(closeMini)\n\n return {\n // Wrapper prevents React SyntheticEvent args from leaking through\n // when used directly as an event handler (e.g. onClick={closeMini})\n closeMini: useCallback(() => handleClose(), [handleClose]),\n }\n}\n"],"names":["useCloseMini","closeMini","useShopActions","handleClose","useHandleAction","useCallback"],"mappings":";;;AAYO,MAAMA,IAAe,MAA2B;AAC/C,QAAA,EAAC,WAAAC,EAAS,IAAIC,EAAe,GAC7BC,IAAcC,EAAgBH,CAAS;AAEtC,SAAA;AAAA;AAAA;AAAA,IAGL,WAAWI,EAAY,MAAMF,EAAe,GAAA,CAACA,CAAW,CAAC;AAAA,EAC3D;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSafeArea.js","sources":["../../../src/hooks/util/useSafeArea.ts"],"sourcesContent":["import type {SafeAreaInsets} from '@shopify/shop-minis-platform'\n\nconst DEFAULT_INSETS: SafeAreaInsets = {top: 0, right: 0, bottom: 0, left: 0}\n\n/**\n * Returns the safe area insets for the current device.\n *\n * These values represent the areas of the screen that are obscured by\n * system UI (home indicator, navigation bar, etc). Use them to ensure\n * content isn't hidden behind system chrome.\n *\n * The values are also available as CSS custom properties:\n * `--safe-area-inset-top`, `--safe-area-inset-right`,\n * `--safe-area-inset-bottom`, `--safe-area-inset-left`\n *\n * @example\n * ```tsx\n * const {bottom} = useSafeArea()\n * return <div style={{paddingBottom: bottom}}>Content</div>\n * ```\n */\nexport function useSafeArea(): SafeAreaInsets {\n return window.minisParams?.safeAreaInsets ?? DEFAULT_INSETS\n}\n"],"names":["DEFAULT_INSETS","useSafeArea"],"mappings":"AAEA,MAAMA,IAAiC,EAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,EAAC;AAmBrE,SAASC,IAA8B;AACrC,SAAA,OAAO,aAAa,kBAAkBD;AAC/C;"}
1
+ {"version":3,"file":"useSafeArea.js","sources":["../../../src/hooks/util/useSafeArea.ts"],"sourcesContent":["import type {SafeAreaInsets} from '@shopify/shop-minis-platform'\n\n/** @docs Return type for the useSafeArea hook */\nexport type UseSafeAreaGeneratedType = SafeAreaInsets\n\nconst DEFAULT_INSETS: SafeAreaInsets = {top: 0, right: 0, bottom: 0, left: 0}\n\n/**\n * Returns the safe area insets for the current device.\n *\n * These values represent the areas of the screen that are obscured by\n * system UI (home indicator, navigation bar, etc). Use them to ensure\n * content isn't hidden behind system chrome.\n *\n * The values are also available as CSS custom properties:\n * `--safe-area-inset-top`, `--safe-area-inset-right`,\n * `--safe-area-inset-bottom`, `--safe-area-inset-left`\n *\n * @example\n * ```tsx\n * const {bottom} = useSafeArea()\n * return <div style={{paddingBottom: bottom}}>Content</div>\n * ```\n */\nexport function useSafeArea(): SafeAreaInsets {\n return window.minisParams?.safeAreaInsets ?? DEFAULT_INSETS\n}\n"],"names":["DEFAULT_INSETS","useSafeArea"],"mappings":"AAKA,MAAMA,IAAiC,EAAC,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,EAAC;AAmBrE,SAASC,IAA8B;AACrC,SAAA,OAAO,aAAa,kBAAkBD;AAC/C;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.13.0",
4
+ "version": "0.13.2",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -4,6 +4,7 @@ import {Content} from '../../types'
4
4
  import {ContentMonitor} from './content-monitor'
5
5
 
6
6
  interface BaseContentWrapperProps {
7
+ /** A render function that receives the content data and loading state, and returns a React element. */
7
8
  children: ({
8
9
  content,
9
10
  loading,
@@ -29,7 +30,9 @@ type ContentWrapperProps =
29
30
 
30
31
  // It's too messy in the docs to show the complete types here so we show a simplified version
31
32
  export interface ContentWrapperPropsForDocs extends BaseContentWrapperProps {
33
+ /** The public ID of the content item (use this OR externalId). */
32
34
  publicId?: string
35
+ /** The external ID of the content item (use this OR publicId). */
33
36
  externalId?: string
34
37
  }
35
38
 
@@ -120,6 +120,9 @@ describe('useCreateImageContent', () => {
120
120
  title: 'Test Content',
121
121
  imageUrl: 'https://example.com/uploaded-image.jpg',
122
122
  visibility: ['DISCOVERABLE'],
123
+ externalId: undefined,
124
+ description: undefined,
125
+ productIds: undefined,
123
126
  })
124
127
  expect(mockCreateContent).toHaveBeenCalledTimes(1)
125
128
  })
@@ -253,6 +256,36 @@ describe('useCreateImageContent', () => {
253
256
  title: 'Test Content',
254
257
  imageUrl: 'https://example.com/uploaded-image.jpg',
255
258
  visibility: null,
259
+ externalId: undefined,
260
+ description: undefined,
261
+ productIds: undefined,
262
+ })
263
+ })
264
+
265
+ it('passes externalId, description, and productIds to createContent', async () => {
266
+ const {result} = renderHook(() => useCreateImageContent())
267
+
268
+ const imageFile = new File(['test'], 'test.jpg', {type: 'image/jpeg'})
269
+ const params = {
270
+ image: imageFile,
271
+ contentTitle: 'Test Content',
272
+ visibility: ['DISCOVERABLE'] as any,
273
+ externalId: 'ext-123',
274
+ description: 'A test description',
275
+ productIds: ['gid://shopify/Product/1', 'gid://shopify/Product/2'],
276
+ }
277
+
278
+ await act(async () => {
279
+ await result.current.createImageContent(params)
280
+ })
281
+
282
+ expect(mockCreateContent).toHaveBeenCalledWith({
283
+ title: 'Test Content',
284
+ imageUrl: 'https://example.com/uploaded-image.jpg',
285
+ visibility: ['DISCOVERABLE'],
286
+ externalId: 'ext-123',
287
+ description: 'A test description',
288
+ productIds: ['gid://shopify/Product/1', 'gid://shopify/Product/2'],
256
289
  })
257
290
  })
258
291
 
@@ -11,9 +11,40 @@ import {useShopActions} from '../../internal/useShopActions'
11
11
  import {useImageUpload} from '../storage/useImageUpload'
12
12
 
13
13
  interface CreateImageContentParams {
14
+ /**
15
+ * The image file to upload.
16
+ */
14
17
  image: File
18
+ /**
19
+ * The title for the content entry.
20
+ */
15
21
  contentTitle: string
22
+ /**
23
+ * Visibility options for the content. Use `['DISCOVERABLE']` to appear in
24
+ * recommendations, `['LINKABLE']` to enable shareable URLs, or both. Pass
25
+ * `null` or `[]` to keep content private within your Mini.
26
+ */
16
27
  visibility?: ContentVisibility[] | null
28
+ /**
29
+ * A unique identifier from your own system that lets you look up content
30
+ * later via `ContentWrapper` using an ID you already know.
31
+ * If not provided, the content can only be retrieved by its `publicId`.
32
+ * Each `externalId` must be unique per Mini — creating content with a
33
+ * duplicate returns a `DUPLICATE_EXTERNAL_ID` error.
34
+ */
35
+ externalId?: string
36
+ /**
37
+ * A text description for the content. Displayed alongside the image in Shop
38
+ * surfaces such as feeds and content detail views. Use this to provide
39
+ * context about the image, such as a caption or review text.
40
+ */
41
+ description?: string
42
+ /**
43
+ * An array of Shopify product GIDs (e.g. `'gid://shopify/Product/123'`) to
44
+ * associate with the content. Maximum 20 products. Associated products
45
+ * appear alongside the content, enabling shoppable content experiences.
46
+ */
47
+ productIds?: string[]
17
48
  }
18
49
 
19
50
  interface UseCreateImageContentReturns {
@@ -38,7 +69,14 @@ export const useCreateImageContent = (): UseCreateImageContentReturns => {
38
69
  async (params: CreateImageContentParams) => {
39
70
  setLoading(true)
40
71
 
41
- const {image, contentTitle, visibility} = params
72
+ const {
73
+ image,
74
+ contentTitle,
75
+ visibility,
76
+ externalId,
77
+ description,
78
+ productIds,
79
+ } = params
42
80
 
43
81
  if (!image.type) {
44
82
  throw new Error('Unable to determine file type')
@@ -58,6 +96,9 @@ export const useCreateImageContent = (): UseCreateImageContentReturns => {
58
96
  title: contentTitle,
59
97
  imageUrl: uploadImageUrl,
60
98
  visibility,
99
+ externalId,
100
+ description,
101
+ productIds,
61
102
  })
62
103
 
63
104
  setLoading(false)
@@ -1,3 +1,5 @@
1
+ import {useCallback} from 'react'
2
+
1
3
  import {useHandleAction} from '../../internal/useHandleAction'
2
4
  import {useShopActions} from '../../internal/useShopActions'
3
5
 
@@ -10,8 +12,11 @@ interface UseCloseMiniReturns {
10
12
 
11
13
  export const useCloseMini = (): UseCloseMiniReturns => {
12
14
  const {closeMini} = useShopActions()
15
+ const handleClose = useHandleAction(closeMini)
13
16
 
14
17
  return {
15
- closeMini: useHandleAction(closeMini),
18
+ // Wrapper prevents React SyntheticEvent args from leaking through
19
+ // when used directly as an event handler (e.g. onClick={closeMini})
20
+ closeMini: useCallback(() => handleClose(), [handleClose]),
16
21
  }
17
22
  }
@@ -1,5 +1,8 @@
1
1
  import type {SafeAreaInsets} from '@shopify/shop-minis-platform'
2
2
 
3
+ /** @docs Return type for the useSafeArea hook */
4
+ export type UseSafeAreaGeneratedType = SafeAreaInsets
5
+
3
6
  const DEFAULT_INSETS: SafeAreaInsets = {top: 0, right: 0, bottom: 0, left: 0}
4
7
 
5
8
  /**