@shopify/shop-minis-react 0.0.19 → 0.0.21
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.
- package/dist/components/MinisContainer.js +13 -11
- package/dist/components/MinisContainer.js.map +1 -1
- package/dist/components/atoms/list.js +52 -0
- package/dist/components/atoms/list.js.map +1 -0
- package/dist/components/atoms/pagination.js +10 -0
- package/dist/components/atoms/pagination.js.map +1 -0
- package/dist/components/atoms/tracking-pixel.js +32 -0
- package/dist/components/atoms/tracking-pixel.js.map +1 -0
- package/dist/hooks/storage/useImageUpload.js.map +1 -1
- package/dist/hooks/util/useErrorScreen.js.map +1 -1
- package/dist/hooks/util/useErrorToast.js.map +1 -1
- package/dist/index.js +194 -189
- package/dist/index.js.map +1 -1
- package/dist/internal/useShopActions.js.map +1 -1
- package/dist/internal/useShopActionsDataFetching.js +26 -26
- package/dist/internal/useShopActionsDataFetching.js.map +1 -1
- package/dist/internal/useShopActionsPaginatedDataFetching.js.map +1 -1
- package/dist/mocks.js +267 -0
- package/dist/mocks.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js +8 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js +13 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/inheritsLoose.js +8 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/inheritsLoose.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js +9 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/memoize-one@5.2.1/node_modules/memoize-one/dist/memoize-one.esm.js +28 -0
- package/dist/shop-minis-react/node_modules/.pnpm/memoize-one@5.2.1/node_modules/memoize-one/dist/memoize-one.esm.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.16.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js +135 -0
- package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.16.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/react-window@1.8.11_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-window/dist/index.esm.js +375 -0
- package/dist/shop-minis-react/node_modules/.pnpm/react-window@1.8.11_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-window/dist/index.esm.js.map +1 -0
- package/dist/shop-minis-react.css +1 -0
- package/package.json +9 -4
- package/src/components/MinisContainer.tsx +4 -1
- package/src/components/atoms/list.tsx +101 -0
- package/src/components/atoms/pagination.tsx +19 -0
- package/src/components/atoms/tracking-pixel.tsx +40 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/navigation/useCloseMini.example.tsx +1 -1
- package/src/hooks/navigation/useShopNavigation.example.tsx +12 -9
- package/src/hooks/product/useProductListActions.example.tsx +19 -16
- package/src/hooks/shop/useShopCartActions.example.tsx +2 -2
- package/src/hooks/storage/useImageUpload.example.tsx +1 -1
- package/src/hooks/storage/useImageUpload.ts +3 -2
- package/src/hooks/user/useFollowedShopsActions.example.tsx +6 -8
- package/src/hooks/user/useSavedProductsActions.example.tsx +8 -6
- package/src/hooks/util/useErrorScreen.example.tsx +1 -2
- package/src/hooks/util/useErrorScreen.ts +2 -1
- package/src/hooks/util/useErrorToast.example.tsx +1 -1
- package/src/hooks/util/useErrorToast.ts +2 -1
- package/src/hooks/util/useImagePicker.example.tsx +4 -12
- package/src/index.css +1 -0
- package/src/internal/useShopActions.ts +1 -1
- package/src/internal/useShopActionsDataFetching.ts +6 -7
- package/src/internal/useShopActionsPaginatedDataFetching.ts +3 -6
- package/src/mocks.ts +20 -8
- package/src/stories/List.stories.tsx +68 -0
- package/src/styles/utilities.css +22 -0
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.0.
|
|
4
|
+
"version": "0.0.21",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"typescript": ">=5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@shopify/shop-minis-platform": "0.0.
|
|
41
|
+
"@shopify/shop-minis-platform": "0.0.7",
|
|
42
42
|
"@tailwindcss/vite": "4.1.8",
|
|
43
43
|
"@types/url-parse": "1.4.9",
|
|
44
44
|
"@vitejs/plugin-react": "4.5.1",
|
|
@@ -51,8 +51,10 @@
|
|
|
51
51
|
"motion": "12.17.3",
|
|
52
52
|
"next-themes": "0.4.6",
|
|
53
53
|
"radix-ui": "1.4.2",
|
|
54
|
+
"react-intersection-observer": "^9.13.1",
|
|
54
55
|
"react-resizable-panels": "3.0.2",
|
|
55
56
|
"react-router": "^7.7.0",
|
|
57
|
+
"react-window": "^1.8.11",
|
|
56
58
|
"sonner": "2.0.5",
|
|
57
59
|
"tailwind-merge": "2.6.0",
|
|
58
60
|
"tailwindcss": "4.1.8",
|
|
@@ -68,7 +70,8 @@
|
|
|
68
70
|
"@types/node": "^20.12.1",
|
|
69
71
|
"@types/react": "^19.1.6",
|
|
70
72
|
"@types/react-dom": "^19.1.2",
|
|
71
|
-
"
|
|
73
|
+
"@types/react-window": "^1.8.8",
|
|
74
|
+
"eslint": "^8.57.0",
|
|
72
75
|
"eslint-plugin-storybook": "^9.0.16",
|
|
73
76
|
"react": "^19.1.0",
|
|
74
77
|
"react-dom": "^19.1.0",
|
|
@@ -102,6 +105,8 @@
|
|
|
102
105
|
"type-check": "tsc --noEmit",
|
|
103
106
|
"clean": "rm -rf dist",
|
|
104
107
|
"storybook": "storybook dev -p 6006",
|
|
105
|
-
"build-storybook": "storybook build"
|
|
108
|
+
"build-storybook": "storybook build",
|
|
109
|
+
"lint": "eslint . --format codeframe --cache",
|
|
110
|
+
"lint:fix": "eslint . --format codeframe --cache --fix"
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, {useEffect, useState} from 'react'
|
|
2
2
|
|
|
3
|
+
import {injectMocks} from '../mocks'
|
|
3
4
|
import {ImagePickerProvider} from '../providers/ImagePickerProvider'
|
|
4
5
|
|
|
6
|
+
injectMocks()
|
|
7
|
+
|
|
5
8
|
export function MinisContainer({children}: {children: React.ReactNode}) {
|
|
6
9
|
const [isSDKReady, setIsSDKReady] = useState(false)
|
|
7
10
|
|
|
@@ -42,7 +45,7 @@ export function MinisContainer({children}: {children: React.ReactNode}) {
|
|
|
42
45
|
|
|
43
46
|
// Cleanup
|
|
44
47
|
return () => {
|
|
45
|
-
|
|
48
|
+
clearInterval(pollInterval)
|
|
46
49
|
window.removeEventListener('message', handleSDKReady)
|
|
47
50
|
document.removeEventListener('message', handleSDKReady)
|
|
48
51
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {ComponentType, useCallback} from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
VariableSizeList as _VariableSizeList,
|
|
5
|
+
VariableSizeListProps,
|
|
6
|
+
} from 'react-window'
|
|
7
|
+
|
|
8
|
+
import {cn} from '../../lib/utils'
|
|
9
|
+
import '../../styles/utilities.css'
|
|
10
|
+
|
|
11
|
+
import {Pagination} from './pagination'
|
|
12
|
+
|
|
13
|
+
const VariableSizeList =
|
|
14
|
+
_VariableSizeList as unknown as ComponentType<VariableSizeListProps>
|
|
15
|
+
|
|
16
|
+
interface Props<T = any>
|
|
17
|
+
extends Omit<
|
|
18
|
+
VariableSizeListProps<T>,
|
|
19
|
+
'children' | 'itemCount' | 'width' | 'itemSize' | 'direction'
|
|
20
|
+
> {
|
|
21
|
+
items: T[]
|
|
22
|
+
renderItem: (item: T, index: number) => React.ReactNode
|
|
23
|
+
itemSizeForRow: (index: number) => number
|
|
24
|
+
showScrollbar?: boolean
|
|
25
|
+
header?: React.ReactNode
|
|
26
|
+
headerHeight?: number
|
|
27
|
+
fetchMore?: () => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function List<T = any>({
|
|
31
|
+
items,
|
|
32
|
+
height,
|
|
33
|
+
renderItem,
|
|
34
|
+
className,
|
|
35
|
+
showScrollbar = false,
|
|
36
|
+
header,
|
|
37
|
+
headerHeight,
|
|
38
|
+
itemSizeForRow,
|
|
39
|
+
fetchMore,
|
|
40
|
+
overscanCount,
|
|
41
|
+
...listProps
|
|
42
|
+
}: Props<T>) {
|
|
43
|
+
const rowRenderer = useCallback(
|
|
44
|
+
({index, style}: {index: number; style: React.CSSProperties}) => {
|
|
45
|
+
// prepend the header to the first row if it exists
|
|
46
|
+
if (header && index === 0) {
|
|
47
|
+
return (
|
|
48
|
+
<div style={style}>
|
|
49
|
+
{header}
|
|
50
|
+
<div style={{top: headerHeight}}>{renderItem(items[0], 0)}</div>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// append the pagination spinner to the last row if fetchMore exists
|
|
56
|
+
if (fetchMore && index === items.length - 1) {
|
|
57
|
+
return (
|
|
58
|
+
<div style={style}>
|
|
59
|
+
{renderItem(items[index], index)}
|
|
60
|
+
<div style={{bottom: 0}}>
|
|
61
|
+
<Pagination fetchMore={fetchMore} />
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return <div style={style}>{renderItem(items[index], index)}</div>
|
|
68
|
+
},
|
|
69
|
+
[items, renderItem, header, fetchMore, headerHeight]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const getItemSize = useCallback(
|
|
73
|
+
(index: number) => {
|
|
74
|
+
// include the header height in the first row height
|
|
75
|
+
if (header && index === 0) {
|
|
76
|
+
const _headerHeight = headerHeight || 0
|
|
77
|
+
return _headerHeight + itemSizeForRow(index)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return itemSizeForRow(index)
|
|
81
|
+
},
|
|
82
|
+
[itemSizeForRow, header, headerHeight]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const classNames = cn(showScrollbar ? undefined : 'no-scrollbars', className)
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<VariableSizeList
|
|
89
|
+
className={classNames}
|
|
90
|
+
height={height}
|
|
91
|
+
direction="vertical"
|
|
92
|
+
width="100%"
|
|
93
|
+
itemCount={items.length}
|
|
94
|
+
overscanCount={overscanCount}
|
|
95
|
+
itemSize={getItemSize}
|
|
96
|
+
{...listProps}
|
|
97
|
+
>
|
|
98
|
+
{rowRenderer}
|
|
99
|
+
</VariableSizeList>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {Skeleton} from '../ui/skeleton'
|
|
2
|
+
|
|
3
|
+
import {TrackingPixel} from './tracking-pixel'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
loadingComponent?: React.ReactNode
|
|
7
|
+
isFetchingMore?: boolean
|
|
8
|
+
fetchMore: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Pagination({loadingComponent, fetchMore}: Props) {
|
|
12
|
+
const loadingPlaceholder = loadingComponent ?? (
|
|
13
|
+
<Skeleton className="h-10 w-full p-8" />
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<TrackingPixel onImpression={fetchMore}>{loadingPlaceholder}</TrackingPixel>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {useCallback} from 'react'
|
|
2
|
+
|
|
3
|
+
import {InView} from 'react-intersection-observer'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
onImpression?: () => void
|
|
8
|
+
triggerOnce?: boolean
|
|
9
|
+
threshold?: number
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const TrackingPixel = ({
|
|
14
|
+
children,
|
|
15
|
+
onImpression,
|
|
16
|
+
triggerOnce = false,
|
|
17
|
+
threshold,
|
|
18
|
+
className,
|
|
19
|
+
}: Props) => {
|
|
20
|
+
const onChange = useCallback(
|
|
21
|
+
(inView: boolean) => {
|
|
22
|
+
if (inView) {
|
|
23
|
+
onImpression?.()
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
[onImpression]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<InView
|
|
31
|
+
as="div"
|
|
32
|
+
onChange={onChange}
|
|
33
|
+
triggerOnce={triggerOnce}
|
|
34
|
+
className={className}
|
|
35
|
+
threshold={threshold}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</InView>
|
|
39
|
+
)
|
|
40
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -15,11 +15,13 @@ export * from './atoms/icon-button'
|
|
|
15
15
|
export * from './atoms/thumbhash-image'
|
|
16
16
|
export * from './atoms/touchable'
|
|
17
17
|
export * from './atoms/alert-dialog'
|
|
18
|
+
export * from './atoms/list'
|
|
18
19
|
|
|
19
20
|
export * from './ui/accordion'
|
|
20
21
|
export * from './ui/alert'
|
|
21
22
|
export * from './ui/alert-dialog'
|
|
22
23
|
export * from './ui/avatar'
|
|
24
|
+
export * from './ui/badge'
|
|
23
25
|
export * from './ui/card'
|
|
24
26
|
export * from './ui/carousel'
|
|
25
27
|
export * from './ui/checkbox'
|
|
@@ -7,23 +7,26 @@ export default function MyComponent() {
|
|
|
7
7
|
return (
|
|
8
8
|
<>
|
|
9
9
|
<Button
|
|
10
|
-
|
|
10
|
+
onClick={() =>
|
|
11
11
|
navigateToProduct({productId: 'gid://shopify/Product/123'})
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
>
|
|
14
|
+
View Product
|
|
15
|
+
</Button>
|
|
15
16
|
<Button
|
|
16
|
-
|
|
17
|
+
onClick={() =>
|
|
17
18
|
navigateToShop({
|
|
18
19
|
shopId: 'gid://shopify/Shop/123',
|
|
19
20
|
})
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
>
|
|
23
|
+
Go to Shop
|
|
24
|
+
</Button>
|
|
23
25
|
<Button
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
onClick={() => navigateToOrder({orderId: 'gid://shopify/Order/123'})}
|
|
27
|
+
>
|
|
28
|
+
View Order
|
|
29
|
+
</Button>
|
|
27
30
|
</>
|
|
28
31
|
)
|
|
29
32
|
}
|
|
@@ -11,25 +11,26 @@ export default function MyComponent() {
|
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<>
|
|
14
|
+
<Button onClick={() => addProductList({name: 'My product list'})}>
|
|
15
|
+
Add product list
|
|
16
|
+
</Button>
|
|
14
17
|
<Button
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
onClick={() => removeProductList({id: 'gid://shopapp/ProductList/123'})}
|
|
19
|
+
>
|
|
20
|
+
Remove product list
|
|
21
|
+
</Button>
|
|
18
22
|
<Button
|
|
19
|
-
|
|
20
|
-
text="Remove product list"
|
|
21
|
-
/>
|
|
22
|
-
<Button
|
|
23
|
-
onPress={() =>
|
|
23
|
+
onClick={() =>
|
|
24
24
|
renameProductList({
|
|
25
25
|
id: 'gid://shopapp/ProductList/123',
|
|
26
26
|
name: 'My renamed product list',
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
>
|
|
30
|
+
Rename product list
|
|
31
|
+
</Button>
|
|
31
32
|
<Button
|
|
32
|
-
|
|
33
|
+
onClick={() =>
|
|
33
34
|
addProductListItem({
|
|
34
35
|
shopId: 'gid://shopify/Shop/42',
|
|
35
36
|
productId: 'gid://shopify/Product/123',
|
|
@@ -38,10 +39,11 @@ export default function MyComponent() {
|
|
|
38
39
|
publicListId: 'abc123',
|
|
39
40
|
})
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
>
|
|
43
|
+
Add product to list
|
|
44
|
+
</Button>
|
|
43
45
|
<Button
|
|
44
|
-
|
|
46
|
+
onClick={() =>
|
|
45
47
|
removeProductListItem({
|
|
46
48
|
shopId: 'gid://shopify/Shop/42',
|
|
47
49
|
productId: 'gid://shopify/Product/123',
|
|
@@ -50,8 +52,9 @@ export default function MyComponent() {
|
|
|
50
52
|
publicListId: 'abc123',
|
|
51
53
|
})
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
>
|
|
56
|
+
Remove product from list
|
|
57
|
+
</Button>
|
|
55
58
|
</>
|
|
56
59
|
)
|
|
57
60
|
}
|
|
@@ -21,8 +21,8 @@ export default function MyComponent() {
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<>
|
|
24
|
-
<Button
|
|
25
|
-
<Button
|
|
24
|
+
<Button onClick={handleAddToCart}>Add to cart</Button>
|
|
25
|
+
<Button onClick={handleBuyNow}>Buy now</Button>
|
|
26
26
|
</>
|
|
27
27
|
)
|
|
28
28
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {useCallback} from 'react'
|
|
2
2
|
|
|
3
3
|
import {useShopActions} from '../../internal/useShopActions'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import type {UploadTarget} from '@shopify/shop-minis-platform/actions'
|
|
5
6
|
|
|
6
7
|
export interface UploadImageParams {
|
|
7
8
|
/**
|
|
@@ -45,7 +46,7 @@ const uploadFileToGCS = async (
|
|
|
45
46
|
target: UploadTarget
|
|
46
47
|
) => {
|
|
47
48
|
const formData = new FormData()
|
|
48
|
-
target.parameters.forEach(({name, value}) => {
|
|
49
|
+
target.parameters.forEach(({name, value}: {name: string; value: string}) => {
|
|
49
50
|
formData.append(name, value)
|
|
50
51
|
})
|
|
51
52
|
// Append the actual file data last
|
|
@@ -5,14 +5,12 @@ export default function MyComponent() {
|
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
7
|
<>
|
|
8
|
-
<Button
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
text="Unfollow shop"
|
|
15
|
-
/>
|
|
8
|
+
<Button onClick={() => followShop({shopId: 'gid://shopify/Shop/123'})}>
|
|
9
|
+
Follow shop
|
|
10
|
+
</Button>
|
|
11
|
+
<Button onClick={() => unfollowShop({shopId: 'gid://shopify/Shop/123'})}>
|
|
12
|
+
Unfollow shop
|
|
13
|
+
</Button>
|
|
16
14
|
</>
|
|
17
15
|
)
|
|
18
16
|
}
|
|
@@ -6,25 +6,27 @@ export default function MyComponent() {
|
|
|
6
6
|
return (
|
|
7
7
|
<>
|
|
8
8
|
<Button
|
|
9
|
-
|
|
9
|
+
onClick={() =>
|
|
10
10
|
saveProduct({
|
|
11
11
|
productId: 'gid://shopify/Product/123',
|
|
12
12
|
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
13
13
|
shopId: 'gid://shopify/Shop/42',
|
|
14
14
|
})
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
>
|
|
17
|
+
Save product
|
|
18
|
+
</Button>
|
|
18
19
|
<Button
|
|
19
|
-
|
|
20
|
+
onClick={() =>
|
|
20
21
|
unsaveProduct({
|
|
21
22
|
productId: 'gid://shopify/Product/123',
|
|
22
23
|
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
23
24
|
shopId: 'gid://shopify/Shop/42',
|
|
24
25
|
})
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
>
|
|
28
|
+
Unsave product
|
|
29
|
+
</Button>
|
|
28
30
|
</>
|
|
29
31
|
)
|
|
30
32
|
}
|
|
@@ -8,9 +8,8 @@ export default function MyComponent() {
|
|
|
8
8
|
const handleError = useCallback(() => {
|
|
9
9
|
showErrorScreen({
|
|
10
10
|
message: 'Something went wrong',
|
|
11
|
-
title: 'Error',
|
|
12
11
|
})
|
|
13
12
|
}, [showErrorScreen])
|
|
14
13
|
|
|
15
|
-
return <Button
|
|
14
|
+
return <Button onClick={handleError}>Show error screen</Button>
|
|
16
15
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import React, {useState} from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {Button} from '../../components/atoms/button'
|
|
5
4
|
import {Alert, AlertDescription, AlertTitle} from '../../components/ui/alert'
|
|
6
|
-
import {Button} from '../../components/ui/button'
|
|
7
5
|
import {
|
|
8
6
|
Card,
|
|
9
7
|
CardContent,
|
|
@@ -16,14 +14,12 @@ import {useImagePicker} from './useImagePicker'
|
|
|
16
14
|
export const UseImagePickerExample = () => {
|
|
17
15
|
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
|
18
16
|
const [error, setError] = useState<string | null>(null)
|
|
19
|
-
const {openCamera, openGallery
|
|
20
|
-
cameraFacing: 'user',
|
|
21
|
-
})
|
|
17
|
+
const {openCamera, openGallery} = useImagePicker()
|
|
22
18
|
|
|
23
19
|
const handleCameraCapture = async () => {
|
|
24
20
|
try {
|
|
25
21
|
setError(null)
|
|
26
|
-
const file = await openCamera()
|
|
22
|
+
const file = await openCamera('front')
|
|
27
23
|
const url = URL.createObjectURL(file)
|
|
28
24
|
setSelectedImage(url)
|
|
29
25
|
} catch (err) {
|
|
@@ -48,16 +44,12 @@ export const UseImagePickerExample = () => {
|
|
|
48
44
|
<CardTitle>Image Picker Example</CardTitle>
|
|
49
45
|
</CardHeader>
|
|
50
46
|
<CardContent className="space-y-4">
|
|
51
|
-
<ImagePickerInputs />
|
|
52
|
-
|
|
53
47
|
<div className="flex gap-2">
|
|
54
|
-
<Button onClick={handleCameraCapture} variant="
|
|
55
|
-
<Camera className="mr-2 h-4 w-4" />
|
|
48
|
+
<Button onClick={handleCameraCapture} variant="default">
|
|
56
49
|
Open Camera
|
|
57
50
|
</Button>
|
|
58
51
|
|
|
59
52
|
<Button onClick={handleGallerySelect} variant="secondary">
|
|
60
|
-
<Image className="mr-2 h-4 w-4" />
|
|
61
53
|
Open Gallery
|
|
62
54
|
</Button>
|
|
63
55
|
</div>
|
package/src/index.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@import './styles/theme.css';
|
|
5
5
|
@import './styles/globals.css';
|
|
6
6
|
@import './styles/animations.css';
|
|
7
|
+
@import './styles/utilities.css';
|
|
7
8
|
|
|
8
9
|
/* When consumed as npm package, this path resolves from the consuming app's location */
|
|
9
10
|
@source '../node_modules/@shopify/shop-minis-react/src';
|
|
@@ -41,13 +41,12 @@ export const useShopActionsDataFetching = <
|
|
|
41
41
|
validator?.(dataToValidate)
|
|
42
42
|
return null
|
|
43
43
|
} catch (err) {
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
44
|
+
if (err instanceof Error) return err
|
|
45
|
+
|
|
46
|
+
return new MiniError({
|
|
47
|
+
hook,
|
|
48
|
+
message: 'Validation failed',
|
|
49
|
+
})
|
|
51
50
|
}
|
|
52
51
|
},
|
|
53
52
|
[validator, hook]
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
PaginationInfo,
|
|
7
|
-
ShopAction,
|
|
8
|
-
} from '../types'
|
|
3
|
+
import {ShopAction, PaginationInfo} from '@shopify/shop-minis-platform/actions'
|
|
4
|
+
|
|
5
|
+
import {DataHookFetchPolicy, PaginatedDataHookReturnsBase} from '../types'
|
|
9
6
|
import {formatError, MiniError} from '../utils/errors'
|
|
10
7
|
|
|
11
8
|
export interface ShopActionsDataFetchingResult<S>
|
package/src/mocks.ts
CHANGED
|
@@ -288,15 +288,27 @@ function makeMockActions(): ShopActions {
|
|
|
288
288
|
return mock as ShopActions
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
// Detect if running on a mobile device
|
|
292
|
+
const isMobile = (): boolean => {
|
|
293
|
+
const userAgent = navigator.userAgent.toLowerCase()
|
|
294
|
+
const isIOS = /iphone|ipad|ipod/.test(userAgent)
|
|
295
|
+
const isAndroid = /android/.test(userAgent)
|
|
296
|
+
|
|
297
|
+
return isIOS || isAndroid
|
|
298
|
+
}
|
|
299
|
+
|
|
291
300
|
export const injectMocks = () => {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
initialUrl: '/mock-initial-url',
|
|
296
|
-
platform: 'ios',
|
|
301
|
+
// Only inject mocks if we aren't on a mobile device
|
|
302
|
+
if (isMobile()) {
|
|
303
|
+
return
|
|
297
304
|
}
|
|
298
|
-
}
|
|
299
305
|
|
|
300
|
-
if (!window.minisSDK) {
|
|
301
|
-
|
|
306
|
+
if (!window.minisSDK) {
|
|
307
|
+
window.minisSDK = makeMockActions()
|
|
308
|
+
window.minisParams = {
|
|
309
|
+
handle: 'mock-handle',
|
|
310
|
+
initialUrl: '/mock-initial-url',
|
|
311
|
+
platform: 'web',
|
|
312
|
+
}
|
|
313
|
+
}
|
|
302
314
|
}
|