@shopify/app-bridge-react 4.1.8 → 4.1.10
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/CHANGELOG.md +12 -0
- package/build/cjs/components/Modal.cjs +104 -0
- package/build/cjs/components/NavMenu.cjs +13 -0
- package/build/cjs/components/SaveBar.cjs +67 -0
- package/build/cjs/components/TitleBar.cjs +14 -0
- package/build/cjs/hooks/useAppBridge.cjs +49 -0
- package/build/cjs/index.cjs +17 -0
- package/build/esm/components/Modal.js +96 -0
- package/build/esm/components/NavMenu.js +9 -0
- package/build/esm/components/SaveBar.js +63 -0
- package/build/esm/components/TitleBar.js +10 -0
- package/build/esm/hooks/useAppBridge.js +45 -0
- package/build/esm/index.js +5 -0
- package/build/esnext/components/Modal.esnext +96 -0
- package/build/esnext/components/NavMenu.esnext +9 -0
- package/build/esnext/components/SaveBar.esnext +63 -0
- package/build/esnext/components/TitleBar.esnext +10 -0
- package/build/esnext/hooks/useAppBridge.esnext +45 -0
- package/build/esnext/index.esnext +5 -0
- package/build/types/cjs/components/Modal.d.cts +38 -0
- package/build/types/cjs/components/Modal.d.cts.map +1 -0
- package/build/types/cjs/components/NavMenu.d.cts +23 -0
- package/build/types/cjs/components/NavMenu.d.cts.map +1 -0
- package/build/types/cjs/components/SaveBar.d.cts +37 -0
- package/build/types/cjs/components/SaveBar.d.cts.map +1 -0
- package/build/types/cjs/components/TitleBar.d.cts +24 -0
- package/build/types/cjs/components/TitleBar.d.cts.map +1 -0
- package/build/types/cjs/hooks/useAppBridge.d.cts +28 -0
- package/build/types/cjs/hooks/useAppBridge.d.cts.map +1 -0
- package/build/types/cjs/index.d.cts +12 -0
- package/build/types/cjs/index.d.cts.map +1 -0
- package/build/types/esm/components/Modal.d.ts +38 -0
- package/build/types/esm/components/Modal.d.ts.map +1 -0
- package/build/types/esm/components/NavMenu.d.ts +23 -0
- package/build/types/esm/components/NavMenu.d.ts.map +1 -0
- package/build/types/esm/components/SaveBar.d.ts +37 -0
- package/build/types/esm/components/SaveBar.d.ts.map +1 -0
- package/build/types/esm/components/TitleBar.d.ts +24 -0
- package/build/types/esm/components/TitleBar.d.ts.map +1 -0
- package/build/types/esm/hooks/useAppBridge.d.ts +28 -0
- package/build/types/esm/hooks/useAppBridge.d.ts.map +1 -0
- package/build/types/esm/index.d.ts +12 -0
- package/build/types/esm/index.d.ts.map +1 -0
- package/package.json +40 -32
- package/dist/index.cjs +0 -28
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -115
- package/dist/index.js +0 -740
- package/dist/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#392](https://github.com/Shopify/extensibility/pull/392) [`f5e596d9dba93eb0f846ef4a1cac8ab670a3efc3`](https://github.com/Shopify/extensibility/commit/f5e596d9dba93eb0f846ef4a1cac8ab670a3efc3) Thanks [@henrytao-me](https://github.com/henrytao-me)! - Remove node version in package.json for app-bridge-react
|
|
8
|
+
|
|
9
|
+
## 4.1.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#383](https://github.com/Shopify/extensibility/pull/383) [`699da8fa064a60883caea326804b0a5b309e5c00`](https://github.com/Shopify/extensibility/commit/699da8fa064a60883caea326804b0a5b309e5c00) Thanks [@henrytao-me](https://github.com/henrytao-me)! - Fix build tools for app-bridge-react and all extensibility packages to support both ESM and CommonJS build output
|
|
14
|
+
|
|
3
15
|
## 4.1.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var ReactDOM = require('react-dom');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This component is a wrapper around the App Bridge `ui-modal` element.
|
|
15
|
+
* It is used to display an overlay that prevents interaction with the
|
|
16
|
+
* rest of the app until dismissed.
|
|
17
|
+
*
|
|
18
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/modal}
|
|
19
|
+
*/
|
|
20
|
+
const Modal = /*#__PURE__*/react.forwardRef(function InternalModal({
|
|
21
|
+
open,
|
|
22
|
+
onShow,
|
|
23
|
+
onHide,
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}, forwardedRef) {
|
|
27
|
+
const [modal, setModal] = react.useState();
|
|
28
|
+
const {
|
|
29
|
+
titleBar,
|
|
30
|
+
saveBar,
|
|
31
|
+
modalContent
|
|
32
|
+
} = react.Children.toArray(children).reduce((acc, node) => {
|
|
33
|
+
const nodeName = getNodeName(node);
|
|
34
|
+
const isTitleBar = nodeName === 'ui-title-bar';
|
|
35
|
+
const isSaveBar = nodeName === 'ui-save-bar';
|
|
36
|
+
const belongToModalContent = !isTitleBar && !isSaveBar;
|
|
37
|
+
if (belongToModalContent) {
|
|
38
|
+
acc.modalContent.push(node);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
...acc,
|
|
42
|
+
titleBar: isTitleBar ? node : acc.titleBar,
|
|
43
|
+
saveBar: isSaveBar ? node : acc.saveBar
|
|
44
|
+
};
|
|
45
|
+
}, {
|
|
46
|
+
modalContent: []
|
|
47
|
+
});
|
|
48
|
+
const contentPortal = modal && modal.content ? /*#__PURE__*/ReactDOM__default.default.createPortal(modalContent, modal.content) : null;
|
|
49
|
+
react.useEffect(() => {
|
|
50
|
+
if (!modal) return;
|
|
51
|
+
if (open) {
|
|
52
|
+
modal.show();
|
|
53
|
+
} else {
|
|
54
|
+
modal.hide();
|
|
55
|
+
}
|
|
56
|
+
}, [modal, open]);
|
|
57
|
+
react.useEffect(() => {
|
|
58
|
+
if (!modal || !onShow) return;
|
|
59
|
+
modal.addEventListener('show', onShow);
|
|
60
|
+
return () => {
|
|
61
|
+
modal.removeEventListener('show', onShow);
|
|
62
|
+
};
|
|
63
|
+
}, [modal, onShow]);
|
|
64
|
+
react.useEffect(() => {
|
|
65
|
+
if (!modal || !onHide) return;
|
|
66
|
+
modal.addEventListener('hide', onHide);
|
|
67
|
+
return () => {
|
|
68
|
+
modal.removeEventListener('hide', onHide);
|
|
69
|
+
};
|
|
70
|
+
}, [modal, onHide]);
|
|
71
|
+
react.useEffect(() => {
|
|
72
|
+
if (!modal) return;
|
|
73
|
+
return () => {
|
|
74
|
+
modal.hide();
|
|
75
|
+
};
|
|
76
|
+
}, [modal]);
|
|
77
|
+
return /*#__PURE__*/jsxRuntime.jsxs("ui-modal", {
|
|
78
|
+
...rest,
|
|
79
|
+
ref: modal => {
|
|
80
|
+
setModal(modal);
|
|
81
|
+
if (forwardedRef) {
|
|
82
|
+
if (typeof forwardedRef === 'function') {
|
|
83
|
+
forwardedRef(modal);
|
|
84
|
+
} else {
|
|
85
|
+
forwardedRef.current = modal;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
children: [titleBar, saveBar, /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
90
|
+
children: contentPortal
|
|
91
|
+
})]
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
Modal.displayName = 'ui-modal';
|
|
95
|
+
function getNodeName(node) {
|
|
96
|
+
if (!node) return;
|
|
97
|
+
const rawNodeType = typeof node === 'object' && 'type' in node ? node.type : undefined;
|
|
98
|
+
const nodeType = typeof rawNodeType === 'string' ? rawNodeType : undefined;
|
|
99
|
+
const rawDisplayName = typeof rawNodeType === 'object' ? rawNodeType.displayName : undefined;
|
|
100
|
+
const displayName = typeof rawDisplayName === 'string' ? rawDisplayName : undefined;
|
|
101
|
+
return nodeType || displayName;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exports.Modal = Modal;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This component is a wrapper around the App Bridge `ui-nav-menu` element.
|
|
7
|
+
* It is used to create a navigation menu for your app.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/navmenu}
|
|
10
|
+
*/
|
|
11
|
+
const NavMenu = 'ui-nav-menu';
|
|
12
|
+
|
|
13
|
+
exports.NavMenu = NavMenu;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This component is a wrapper around the App Bridge `ui-save-bar` element.
|
|
10
|
+
* It is used to display a contextual save bar to signal dirty state in the app.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/save-bar}
|
|
13
|
+
*/
|
|
14
|
+
const SaveBar = /*#__PURE__*/react.forwardRef(function InternalSaveBar({
|
|
15
|
+
open,
|
|
16
|
+
onShow,
|
|
17
|
+
onHide,
|
|
18
|
+
children,
|
|
19
|
+
...rest
|
|
20
|
+
}, forwardedRef) {
|
|
21
|
+
const [saveBar, setSaveBar] = react.useState();
|
|
22
|
+
react.useEffect(() => {
|
|
23
|
+
if (!saveBar) return;
|
|
24
|
+
if (open) {
|
|
25
|
+
saveBar.show();
|
|
26
|
+
} else {
|
|
27
|
+
saveBar.hide();
|
|
28
|
+
}
|
|
29
|
+
}, [saveBar, open]);
|
|
30
|
+
react.useEffect(() => {
|
|
31
|
+
if (!saveBar || !onShow) return;
|
|
32
|
+
saveBar.addEventListener('show', onShow);
|
|
33
|
+
return () => {
|
|
34
|
+
saveBar.removeEventListener('show', onShow);
|
|
35
|
+
};
|
|
36
|
+
}, [saveBar, onShow]);
|
|
37
|
+
react.useEffect(() => {
|
|
38
|
+
if (!saveBar || !onHide) return;
|
|
39
|
+
saveBar.addEventListener('hide', onHide);
|
|
40
|
+
return () => {
|
|
41
|
+
saveBar.removeEventListener('hide', onHide);
|
|
42
|
+
};
|
|
43
|
+
}, [saveBar, onHide]);
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
if (!saveBar) return;
|
|
46
|
+
return () => {
|
|
47
|
+
saveBar.hide();
|
|
48
|
+
};
|
|
49
|
+
}, [saveBar]);
|
|
50
|
+
return /*#__PURE__*/jsxRuntime.jsx("ui-save-bar", {
|
|
51
|
+
...rest,
|
|
52
|
+
ref: saveBar => {
|
|
53
|
+
setSaveBar(saveBar);
|
|
54
|
+
if (forwardedRef) {
|
|
55
|
+
if (typeof forwardedRef === 'function') {
|
|
56
|
+
forwardedRef(saveBar);
|
|
57
|
+
} else {
|
|
58
|
+
forwardedRef.current = saveBar;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
children: children
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
SaveBar.displayName = 'ui-save-bar';
|
|
66
|
+
|
|
67
|
+
exports.SaveBar = SaveBar;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This component is a wrapper around the App Bridge `ui-title-bar` element.
|
|
7
|
+
* It is used to to populate the app title bar with button actions or the
|
|
8
|
+
* modal header and footer when used within the Modal component.
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/titlebar}
|
|
11
|
+
*/
|
|
12
|
+
const TitleBar = 'ui-title-bar';
|
|
13
|
+
|
|
14
|
+
exports.TitleBar = TitleBar;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This proxy is used to throw a helpful error message when trying to access
|
|
7
|
+
* the `shopify` global in a server environment.
|
|
8
|
+
*/
|
|
9
|
+
const serverProxy = new Proxy({}, {
|
|
10
|
+
get(_, prop) {
|
|
11
|
+
throw Error(`shopify.${String(prop)} can't be used in a server environment. You likely need to move this code into an Effect.`);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* This hook returns the `shopify` global variable to use
|
|
18
|
+
* App Bridge APIs such as `toast` and `resourcePicker`.
|
|
19
|
+
*
|
|
20
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-hooks/useappbridge}
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```jsx
|
|
24
|
+
* import {useAppBridge} from '@shopify/app-bridge-react';
|
|
25
|
+
* function GenerateBlogPostButton() {
|
|
26
|
+
* const shopify = useAppBridge();
|
|
27
|
+
*
|
|
28
|
+
* function generateBlogPost() {
|
|
29
|
+
* // Handle generating
|
|
30
|
+
* shopify.toast.show('Blog post template generated');
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* return <button onClick={generateBlogPost}>Generate Blog Post</button>;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @returns `shopify` variable or a Proxy that throws when incorrectly accessed when not in a browser context
|
|
38
|
+
*/
|
|
39
|
+
function useAppBridge() {
|
|
40
|
+
if (typeof window === 'undefined') {
|
|
41
|
+
return serverProxy;
|
|
42
|
+
}
|
|
43
|
+
if (!window.shopify) {
|
|
44
|
+
throw Error('The shopify global is not defined. This likely means the App Bridge script tag was not added correctly to this page');
|
|
45
|
+
}
|
|
46
|
+
return window.shopify;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exports.useAppBridge = useAppBridge;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var components_Modal = require('./components/Modal.cjs');
|
|
6
|
+
var components_NavMenu = require('./components/NavMenu.cjs');
|
|
7
|
+
var components_TitleBar = require('./components/TitleBar.cjs');
|
|
8
|
+
var components_SaveBar = require('./components/SaveBar.cjs');
|
|
9
|
+
var hooks_useAppBridge = require('./hooks/useAppBridge.cjs');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
exports.Modal = components_Modal.Modal;
|
|
14
|
+
exports.NavMenu = components_NavMenu.NavMenu;
|
|
15
|
+
exports.TitleBar = components_TitleBar.TitleBar;
|
|
16
|
+
exports.SaveBar = components_SaveBar.SaveBar;
|
|
17
|
+
exports.useAppBridge = hooks_useAppBridge.useAppBridge;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { forwardRef, useState, Children, useEffect } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This component is a wrapper around the App Bridge `ui-modal` element.
|
|
7
|
+
* It is used to display an overlay that prevents interaction with the
|
|
8
|
+
* rest of the app until dismissed.
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/modal}
|
|
11
|
+
*/
|
|
12
|
+
const Modal = /*#__PURE__*/forwardRef(function InternalModal({
|
|
13
|
+
open,
|
|
14
|
+
onShow,
|
|
15
|
+
onHide,
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}, forwardedRef) {
|
|
19
|
+
const [modal, setModal] = useState();
|
|
20
|
+
const {
|
|
21
|
+
titleBar,
|
|
22
|
+
saveBar,
|
|
23
|
+
modalContent
|
|
24
|
+
} = Children.toArray(children).reduce((acc, node) => {
|
|
25
|
+
const nodeName = getNodeName(node);
|
|
26
|
+
const isTitleBar = nodeName === 'ui-title-bar';
|
|
27
|
+
const isSaveBar = nodeName === 'ui-save-bar';
|
|
28
|
+
const belongToModalContent = !isTitleBar && !isSaveBar;
|
|
29
|
+
if (belongToModalContent) {
|
|
30
|
+
acc.modalContent.push(node);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...acc,
|
|
34
|
+
titleBar: isTitleBar ? node : acc.titleBar,
|
|
35
|
+
saveBar: isSaveBar ? node : acc.saveBar
|
|
36
|
+
};
|
|
37
|
+
}, {
|
|
38
|
+
modalContent: []
|
|
39
|
+
});
|
|
40
|
+
const contentPortal = modal && modal.content ? /*#__PURE__*/ReactDOM.createPortal(modalContent, modal.content) : null;
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!modal) return;
|
|
43
|
+
if (open) {
|
|
44
|
+
modal.show();
|
|
45
|
+
} else {
|
|
46
|
+
modal.hide();
|
|
47
|
+
}
|
|
48
|
+
}, [modal, open]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!modal || !onShow) return;
|
|
51
|
+
modal.addEventListener('show', onShow);
|
|
52
|
+
return () => {
|
|
53
|
+
modal.removeEventListener('show', onShow);
|
|
54
|
+
};
|
|
55
|
+
}, [modal, onShow]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!modal || !onHide) return;
|
|
58
|
+
modal.addEventListener('hide', onHide);
|
|
59
|
+
return () => {
|
|
60
|
+
modal.removeEventListener('hide', onHide);
|
|
61
|
+
};
|
|
62
|
+
}, [modal, onHide]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!modal) return;
|
|
65
|
+
return () => {
|
|
66
|
+
modal.hide();
|
|
67
|
+
};
|
|
68
|
+
}, [modal]);
|
|
69
|
+
return /*#__PURE__*/jsxs("ui-modal", {
|
|
70
|
+
...rest,
|
|
71
|
+
ref: modal => {
|
|
72
|
+
setModal(modal);
|
|
73
|
+
if (forwardedRef) {
|
|
74
|
+
if (typeof forwardedRef === 'function') {
|
|
75
|
+
forwardedRef(modal);
|
|
76
|
+
} else {
|
|
77
|
+
forwardedRef.current = modal;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
children: [titleBar, saveBar, /*#__PURE__*/jsx("div", {
|
|
82
|
+
children: contentPortal
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
Modal.displayName = 'ui-modal';
|
|
87
|
+
function getNodeName(node) {
|
|
88
|
+
if (!node) return;
|
|
89
|
+
const rawNodeType = typeof node === 'object' && 'type' in node ? node.type : undefined;
|
|
90
|
+
const nodeType = typeof rawNodeType === 'string' ? rawNodeType : undefined;
|
|
91
|
+
const rawDisplayName = typeof rawNodeType === 'object' ? rawNodeType.displayName : undefined;
|
|
92
|
+
const displayName = typeof rawDisplayName === 'string' ? rawDisplayName : undefined;
|
|
93
|
+
return nodeType || displayName;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { Modal };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component is a wrapper around the App Bridge `ui-nav-menu` element.
|
|
3
|
+
* It is used to create a navigation menu for your app.
|
|
4
|
+
*
|
|
5
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/navmenu}
|
|
6
|
+
*/
|
|
7
|
+
const NavMenu = 'ui-nav-menu';
|
|
8
|
+
|
|
9
|
+
export { NavMenu };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { forwardRef, useState, useEffect } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This component is a wrapper around the App Bridge `ui-save-bar` element.
|
|
6
|
+
* It is used to display a contextual save bar to signal dirty state in the app.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/save-bar}
|
|
9
|
+
*/
|
|
10
|
+
const SaveBar = /*#__PURE__*/forwardRef(function InternalSaveBar({
|
|
11
|
+
open,
|
|
12
|
+
onShow,
|
|
13
|
+
onHide,
|
|
14
|
+
children,
|
|
15
|
+
...rest
|
|
16
|
+
}, forwardedRef) {
|
|
17
|
+
const [saveBar, setSaveBar] = useState();
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!saveBar) return;
|
|
20
|
+
if (open) {
|
|
21
|
+
saveBar.show();
|
|
22
|
+
} else {
|
|
23
|
+
saveBar.hide();
|
|
24
|
+
}
|
|
25
|
+
}, [saveBar, open]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!saveBar || !onShow) return;
|
|
28
|
+
saveBar.addEventListener('show', onShow);
|
|
29
|
+
return () => {
|
|
30
|
+
saveBar.removeEventListener('show', onShow);
|
|
31
|
+
};
|
|
32
|
+
}, [saveBar, onShow]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!saveBar || !onHide) return;
|
|
35
|
+
saveBar.addEventListener('hide', onHide);
|
|
36
|
+
return () => {
|
|
37
|
+
saveBar.removeEventListener('hide', onHide);
|
|
38
|
+
};
|
|
39
|
+
}, [saveBar, onHide]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!saveBar) return;
|
|
42
|
+
return () => {
|
|
43
|
+
saveBar.hide();
|
|
44
|
+
};
|
|
45
|
+
}, [saveBar]);
|
|
46
|
+
return /*#__PURE__*/jsx("ui-save-bar", {
|
|
47
|
+
...rest,
|
|
48
|
+
ref: saveBar => {
|
|
49
|
+
setSaveBar(saveBar);
|
|
50
|
+
if (forwardedRef) {
|
|
51
|
+
if (typeof forwardedRef === 'function') {
|
|
52
|
+
forwardedRef(saveBar);
|
|
53
|
+
} else {
|
|
54
|
+
forwardedRef.current = saveBar;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
children: children
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
SaveBar.displayName = 'ui-save-bar';
|
|
62
|
+
|
|
63
|
+
export { SaveBar };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component is a wrapper around the App Bridge `ui-title-bar` element.
|
|
3
|
+
* It is used to to populate the app title bar with button actions or the
|
|
4
|
+
* modal header and footer when used within the Modal component.
|
|
5
|
+
*
|
|
6
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/titlebar}
|
|
7
|
+
*/
|
|
8
|
+
const TitleBar = 'ui-title-bar';
|
|
9
|
+
|
|
10
|
+
export { TitleBar };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This proxy is used to throw a helpful error message when trying to access
|
|
3
|
+
* the `shopify` global in a server environment.
|
|
4
|
+
*/
|
|
5
|
+
const serverProxy = new Proxy({}, {
|
|
6
|
+
get(_, prop) {
|
|
7
|
+
throw Error(`shopify.${String(prop)} can't be used in a server environment. You likely need to move this code into an Effect.`);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* This hook returns the `shopify` global variable to use
|
|
14
|
+
* App Bridge APIs such as `toast` and `resourcePicker`.
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-hooks/useappbridge}
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```jsx
|
|
20
|
+
* import {useAppBridge} from '@shopify/app-bridge-react';
|
|
21
|
+
* function GenerateBlogPostButton() {
|
|
22
|
+
* const shopify = useAppBridge();
|
|
23
|
+
*
|
|
24
|
+
* function generateBlogPost() {
|
|
25
|
+
* // Handle generating
|
|
26
|
+
* shopify.toast.show('Blog post template generated');
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* return <button onClick={generateBlogPost}>Generate Blog Post</button>;
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @returns `shopify` variable or a Proxy that throws when incorrectly accessed when not in a browser context
|
|
34
|
+
*/
|
|
35
|
+
function useAppBridge() {
|
|
36
|
+
if (typeof window === 'undefined') {
|
|
37
|
+
return serverProxy;
|
|
38
|
+
}
|
|
39
|
+
if (!window.shopify) {
|
|
40
|
+
throw Error('The shopify global is not defined. This likely means the App Bridge script tag was not added correctly to this page');
|
|
41
|
+
}
|
|
42
|
+
return window.shopify;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { useAppBridge };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { forwardRef, useState, Children, useEffect } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This component is a wrapper around the App Bridge `ui-modal` element.
|
|
7
|
+
* It is used to display an overlay that prevents interaction with the
|
|
8
|
+
* rest of the app until dismissed.
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/modal}
|
|
11
|
+
*/
|
|
12
|
+
const Modal = /*#__PURE__*/forwardRef(function InternalModal({
|
|
13
|
+
open,
|
|
14
|
+
onShow,
|
|
15
|
+
onHide,
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}, forwardedRef) {
|
|
19
|
+
const [modal, setModal] = useState();
|
|
20
|
+
const {
|
|
21
|
+
titleBar,
|
|
22
|
+
saveBar,
|
|
23
|
+
modalContent
|
|
24
|
+
} = Children.toArray(children).reduce((acc, node) => {
|
|
25
|
+
const nodeName = getNodeName(node);
|
|
26
|
+
const isTitleBar = nodeName === 'ui-title-bar';
|
|
27
|
+
const isSaveBar = nodeName === 'ui-save-bar';
|
|
28
|
+
const belongToModalContent = !isTitleBar && !isSaveBar;
|
|
29
|
+
if (belongToModalContent) {
|
|
30
|
+
acc.modalContent.push(node);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...acc,
|
|
34
|
+
titleBar: isTitleBar ? node : acc.titleBar,
|
|
35
|
+
saveBar: isSaveBar ? node : acc.saveBar
|
|
36
|
+
};
|
|
37
|
+
}, {
|
|
38
|
+
modalContent: []
|
|
39
|
+
});
|
|
40
|
+
const contentPortal = modal && modal.content ? /*#__PURE__*/ReactDOM.createPortal(modalContent, modal.content) : null;
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!modal) return;
|
|
43
|
+
if (open) {
|
|
44
|
+
modal.show();
|
|
45
|
+
} else {
|
|
46
|
+
modal.hide();
|
|
47
|
+
}
|
|
48
|
+
}, [modal, open]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!modal || !onShow) return;
|
|
51
|
+
modal.addEventListener('show', onShow);
|
|
52
|
+
return () => {
|
|
53
|
+
modal.removeEventListener('show', onShow);
|
|
54
|
+
};
|
|
55
|
+
}, [modal, onShow]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!modal || !onHide) return;
|
|
58
|
+
modal.addEventListener('hide', onHide);
|
|
59
|
+
return () => {
|
|
60
|
+
modal.removeEventListener('hide', onHide);
|
|
61
|
+
};
|
|
62
|
+
}, [modal, onHide]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!modal) return;
|
|
65
|
+
return () => {
|
|
66
|
+
modal.hide();
|
|
67
|
+
};
|
|
68
|
+
}, [modal]);
|
|
69
|
+
return /*#__PURE__*/jsxs("ui-modal", {
|
|
70
|
+
...rest,
|
|
71
|
+
ref: modal => {
|
|
72
|
+
setModal(modal);
|
|
73
|
+
if (forwardedRef) {
|
|
74
|
+
if (typeof forwardedRef === 'function') {
|
|
75
|
+
forwardedRef(modal);
|
|
76
|
+
} else {
|
|
77
|
+
forwardedRef.current = modal;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
children: [titleBar, saveBar, /*#__PURE__*/jsx("div", {
|
|
82
|
+
children: contentPortal
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
Modal.displayName = 'ui-modal';
|
|
87
|
+
function getNodeName(node) {
|
|
88
|
+
if (!node) return;
|
|
89
|
+
const rawNodeType = typeof node === 'object' && 'type' in node ? node.type : undefined;
|
|
90
|
+
const nodeType = typeof rawNodeType === 'string' ? rawNodeType : undefined;
|
|
91
|
+
const rawDisplayName = typeof rawNodeType === 'object' ? rawNodeType.displayName : undefined;
|
|
92
|
+
const displayName = typeof rawDisplayName === 'string' ? rawDisplayName : undefined;
|
|
93
|
+
return nodeType || displayName;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { Modal };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This component is a wrapper around the App Bridge `ui-nav-menu` element.
|
|
3
|
+
* It is used to create a navigation menu for your app.
|
|
4
|
+
*
|
|
5
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/navmenu}
|
|
6
|
+
*/
|
|
7
|
+
const NavMenu = 'ui-nav-menu';
|
|
8
|
+
|
|
9
|
+
export { NavMenu };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { forwardRef, useState, useEffect } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This component is a wrapper around the App Bridge `ui-save-bar` element.
|
|
6
|
+
* It is used to display a contextual save bar to signal dirty state in the app.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link https://shopify.dev/docs/api/app-bridge-library/react-components/save-bar}
|
|
9
|
+
*/
|
|
10
|
+
const SaveBar = /*#__PURE__*/forwardRef(function InternalSaveBar({
|
|
11
|
+
open,
|
|
12
|
+
onShow,
|
|
13
|
+
onHide,
|
|
14
|
+
children,
|
|
15
|
+
...rest
|
|
16
|
+
}, forwardedRef) {
|
|
17
|
+
const [saveBar, setSaveBar] = useState();
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!saveBar) return;
|
|
20
|
+
if (open) {
|
|
21
|
+
saveBar.show();
|
|
22
|
+
} else {
|
|
23
|
+
saveBar.hide();
|
|
24
|
+
}
|
|
25
|
+
}, [saveBar, open]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!saveBar || !onShow) return;
|
|
28
|
+
saveBar.addEventListener('show', onShow);
|
|
29
|
+
return () => {
|
|
30
|
+
saveBar.removeEventListener('show', onShow);
|
|
31
|
+
};
|
|
32
|
+
}, [saveBar, onShow]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!saveBar || !onHide) return;
|
|
35
|
+
saveBar.addEventListener('hide', onHide);
|
|
36
|
+
return () => {
|
|
37
|
+
saveBar.removeEventListener('hide', onHide);
|
|
38
|
+
};
|
|
39
|
+
}, [saveBar, onHide]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!saveBar) return;
|
|
42
|
+
return () => {
|
|
43
|
+
saveBar.hide();
|
|
44
|
+
};
|
|
45
|
+
}, [saveBar]);
|
|
46
|
+
return /*#__PURE__*/jsx("ui-save-bar", {
|
|
47
|
+
...rest,
|
|
48
|
+
ref: saveBar => {
|
|
49
|
+
setSaveBar(saveBar);
|
|
50
|
+
if (forwardedRef) {
|
|
51
|
+
if (typeof forwardedRef === 'function') {
|
|
52
|
+
forwardedRef(saveBar);
|
|
53
|
+
} else {
|
|
54
|
+
forwardedRef.current = saveBar;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
children: children
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
SaveBar.displayName = 'ui-save-bar';
|
|
62
|
+
|
|
63
|
+
export { SaveBar };
|