@shopify/shop-minis-react 0.2.9 → 0.3.1
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/commerce/add-to-cart.js +70 -53
- package/dist/components/commerce/add-to-cart.js.map +1 -1
- package/dist/components/commerce/buy-now.js +75 -0
- package/dist/components/commerce/buy-now.js.map +1 -0
- package/dist/index.js +230 -230
- package/dist/{hooks/shop → internal}/useShopCartActions.js +2 -2
- package/dist/internal/useShopCartActions.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
- package/eslint/config.cjs +6 -0
- package/eslint/index.cjs +2 -0
- package/eslint/rules/prefer-sdk-hooks.cjs +131 -0
- package/generated-hook-maps/hook-actions-map.json +0 -4
- package/package.json +5 -4
- package/src/components/commerce/add-to-cart.test.tsx +218 -3
- package/src/components/commerce/add-to-cart.tsx +40 -16
- package/src/components/commerce/buy-now.test.tsx +272 -0
- package/src/components/commerce/buy-now.tsx +108 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +0 -1
- package/src/{hooks/shop → internal}/useShopCartActions.ts +2 -2
- package/src/stories/AddToCart.stories.tsx +75 -10
- package/dist/hooks/shop/useShopCartActions.js.map +0 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {Product} from '@shopify/shop-minis-platform'
|
|
2
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
mockMinisSDK,
|
|
8
|
+
resetAllMocks,
|
|
9
|
+
userEvent,
|
|
10
|
+
waitFor,
|
|
11
|
+
} from '../../test-utils'
|
|
12
|
+
|
|
13
|
+
import {BuyNowButton} from './buy-now'
|
|
14
|
+
|
|
15
|
+
// Mock hooks
|
|
16
|
+
const mockShowErrorToast = vi.fn()
|
|
17
|
+
|
|
18
|
+
vi.mock('../../internal/useShopCartActions', () => ({
|
|
19
|
+
useShopCartActions: () => ({
|
|
20
|
+
addToCart: mockMinisSDK.addToCart,
|
|
21
|
+
buyProduct: mockMinisSDK.buyProduct,
|
|
22
|
+
}),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
vi.mock('../../hooks', () => ({
|
|
26
|
+
useShopNavigation: () => ({
|
|
27
|
+
navigateToProduct: mockMinisSDK.navigateToProduct,
|
|
28
|
+
}),
|
|
29
|
+
useErrorToast: () => ({
|
|
30
|
+
showErrorToast: mockShowErrorToast,
|
|
31
|
+
}),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
describe('BuyNowButton', () => {
|
|
35
|
+
const mockProduct: Product = {
|
|
36
|
+
id: 'gid://shopify/Product/123',
|
|
37
|
+
title: 'Test Product',
|
|
38
|
+
reviewAnalytics: {
|
|
39
|
+
averageRating: null,
|
|
40
|
+
reviewCount: null,
|
|
41
|
+
},
|
|
42
|
+
shop: {
|
|
43
|
+
id: 'gid://shopify/Shop/1',
|
|
44
|
+
name: 'Test Shop',
|
|
45
|
+
},
|
|
46
|
+
defaultVariantId: 'gid://shopify/ProductVariant/456',
|
|
47
|
+
isFavorited: false,
|
|
48
|
+
price: {
|
|
49
|
+
amount: '10.00',
|
|
50
|
+
currencyCode: 'USD',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const defaultProps = {
|
|
55
|
+
product: mockProduct,
|
|
56
|
+
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// eslint-disable-next-line jest/require-top-level-describe
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
resetAllMocks()
|
|
62
|
+
mockShowErrorToast.mockClear()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('renders with default text', () => {
|
|
66
|
+
render(<BuyNowButton {...defaultProps} />)
|
|
67
|
+
|
|
68
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
69
|
+
expect(screen.getByText('Buy now')).toBeInTheDocument()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('renders with required props', () => {
|
|
73
|
+
render(<BuyNowButton {...defaultProps} />)
|
|
74
|
+
|
|
75
|
+
const button = screen.getByRole('button')
|
|
76
|
+
expect(button).toBeInTheDocument()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('respects disabled prop', () => {
|
|
80
|
+
render(<BuyNowButton {...defaultProps} disabled />)
|
|
81
|
+
|
|
82
|
+
const button = screen.getByRole('button')
|
|
83
|
+
expect(button).toBeDisabled()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('applies custom className', () => {
|
|
87
|
+
render(<BuyNowButton {...defaultProps} className="custom-class" />)
|
|
88
|
+
|
|
89
|
+
const button = screen.getByRole('button')
|
|
90
|
+
expect(button).toHaveClass('custom-class')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('renders with different sizes', () => {
|
|
94
|
+
const {rerender} = render(<BuyNowButton {...defaultProps} size="sm" />)
|
|
95
|
+
|
|
96
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
97
|
+
|
|
98
|
+
rerender(<BuyNowButton {...defaultProps} size="default" />)
|
|
99
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
100
|
+
|
|
101
|
+
rerender(<BuyNowButton {...defaultProps} size="lg" />)
|
|
102
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('renders with discount code prop', () => {
|
|
106
|
+
const discountCode = 'SUMMER20'
|
|
107
|
+
|
|
108
|
+
render(<BuyNowButton {...defaultProps} discountCode={discountCode} />)
|
|
109
|
+
|
|
110
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('calls buyProduct when clicked and not a referral product', async () => {
|
|
114
|
+
const user = userEvent.setup()
|
|
115
|
+
mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})
|
|
116
|
+
|
|
117
|
+
render(<BuyNowButton {...defaultProps} />)
|
|
118
|
+
|
|
119
|
+
const button = screen.getByRole('button')
|
|
120
|
+
await user.click(button)
|
|
121
|
+
|
|
122
|
+
expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
|
|
123
|
+
productId: mockProduct.id,
|
|
124
|
+
productVariantId: defaultProps.productVariantId,
|
|
125
|
+
quantity: 1,
|
|
126
|
+
discountCode: undefined,
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('navigates to product page when product is referral', async () => {
|
|
131
|
+
const user = userEvent.setup()
|
|
132
|
+
const referralProduct: Product = {...mockProduct, referral: true}
|
|
133
|
+
|
|
134
|
+
render(<BuyNowButton {...defaultProps} product={referralProduct} />)
|
|
135
|
+
|
|
136
|
+
// Button should show "View product" instead of "Buy now"
|
|
137
|
+
expect(screen.getByText('View product')).toBeInTheDocument()
|
|
138
|
+
expect(screen.queryByText('Buy now')).not.toBeInTheDocument()
|
|
139
|
+
|
|
140
|
+
const button = screen.getByRole('button')
|
|
141
|
+
await user.click(button)
|
|
142
|
+
|
|
143
|
+
expect(mockMinisSDK.navigateToProduct).toHaveBeenCalledWith({
|
|
144
|
+
productId: referralProduct.id,
|
|
145
|
+
})
|
|
146
|
+
expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('shows processing state while purchasing', async () => {
|
|
150
|
+
const user = userEvent.setup()
|
|
151
|
+
mockMinisSDK.buyProduct.mockImplementation(() => new Promise(() => {})) // Never resolves
|
|
152
|
+
|
|
153
|
+
render(<BuyNowButton {...defaultProps} />)
|
|
154
|
+
|
|
155
|
+
const button = screen.getByRole('button')
|
|
156
|
+
await user.click(button)
|
|
157
|
+
|
|
158
|
+
// Check for processing state - button should be disabled and have aria-busy
|
|
159
|
+
await waitFor(() => {
|
|
160
|
+
expect(button).toBeDisabled()
|
|
161
|
+
expect(button).toHaveAttribute('aria-busy', 'true')
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('handles buy product error gracefully', async () => {
|
|
166
|
+
const user = userEvent.setup()
|
|
167
|
+
const error = new Error('Purchase failed')
|
|
168
|
+
mockMinisSDK.buyProduct.mockRejectedValueOnce(error)
|
|
169
|
+
|
|
170
|
+
render(<BuyNowButton {...defaultProps} />)
|
|
171
|
+
|
|
172
|
+
const button = screen.getByRole('button')
|
|
173
|
+
await user.click(button)
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(mockMinisSDK.buyProduct).toHaveBeenCalled()
|
|
177
|
+
expect(mockShowErrorToast).toHaveBeenCalledWith({
|
|
178
|
+
message: 'Failed to complete purchase',
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Button should be enabled again after error
|
|
183
|
+
expect(button).toBeEnabled()
|
|
184
|
+
expect(button).toHaveAttribute('aria-busy', 'false')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('does not call buyProduct when disabled', async () => {
|
|
188
|
+
const user = userEvent.setup()
|
|
189
|
+
|
|
190
|
+
render(<BuyNowButton {...defaultProps} disabled />)
|
|
191
|
+
|
|
192
|
+
const button = screen.getByRole('button')
|
|
193
|
+
await user.click(button)
|
|
194
|
+
|
|
195
|
+
expect(mockMinisSDK.buyProduct).not.toHaveBeenCalled()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('passes discount code to buyProduct', async () => {
|
|
199
|
+
const user = userEvent.setup()
|
|
200
|
+
const discountCode = 'DISCOUNT10'
|
|
201
|
+
mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})
|
|
202
|
+
|
|
203
|
+
render(<BuyNowButton {...defaultProps} discountCode={discountCode} />)
|
|
204
|
+
|
|
205
|
+
const button = screen.getByRole('button')
|
|
206
|
+
await user.click(button)
|
|
207
|
+
|
|
208
|
+
expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
|
|
209
|
+
productId: mockProduct.id,
|
|
210
|
+
productVariantId: defaultProps.productVariantId,
|
|
211
|
+
quantity: 1,
|
|
212
|
+
discountCode,
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('handles product without shop data', async () => {
|
|
217
|
+
const user = userEvent.setup()
|
|
218
|
+
mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})
|
|
219
|
+
|
|
220
|
+
const productWithoutShop: Product = {
|
|
221
|
+
...mockProduct,
|
|
222
|
+
shop: undefined as any,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
render(
|
|
226
|
+
<BuyNowButton
|
|
227
|
+
product={productWithoutShop}
|
|
228
|
+
productVariantId="gid://shopify/ProductVariant/456"
|
|
229
|
+
/>
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
const button = screen.getByRole('button')
|
|
233
|
+
await user.click(button)
|
|
234
|
+
|
|
235
|
+
expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
|
|
236
|
+
productId: productWithoutShop.id,
|
|
237
|
+
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
238
|
+
quantity: 1,
|
|
239
|
+
discountCode: undefined,
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('handles product with partial shop data', async () => {
|
|
244
|
+
const user = userEvent.setup()
|
|
245
|
+
mockMinisSDK.buyProduct.mockResolvedValueOnce({ok: true})
|
|
246
|
+
|
|
247
|
+
const productWithPartialShop: Product = {
|
|
248
|
+
...mockProduct,
|
|
249
|
+
shop: {
|
|
250
|
+
id: 'gid://shopify/Shop/1',
|
|
251
|
+
name: undefined as any,
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
render(
|
|
256
|
+
<BuyNowButton
|
|
257
|
+
product={productWithPartialShop}
|
|
258
|
+
productVariantId="gid://shopify/ProductVariant/456"
|
|
259
|
+
/>
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const button = screen.getByRole('button')
|
|
263
|
+
await user.click(button)
|
|
264
|
+
|
|
265
|
+
expect(mockMinisSDK.buyProduct).toHaveBeenCalledWith({
|
|
266
|
+
productId: productWithPartialShop.id,
|
|
267
|
+
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
268
|
+
quantity: 1,
|
|
269
|
+
discountCode: undefined,
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {useState, useCallback} from 'react'
|
|
2
|
+
|
|
3
|
+
import {Product} from '@shopify/shop-minis-platform'
|
|
4
|
+
import {motion, AnimatePresence} from 'motion/react'
|
|
5
|
+
|
|
6
|
+
import {useErrorToast, useShopNavigation} from '../../hooks'
|
|
7
|
+
import {useShopCartActions} from '../../internal/useShopCartActions'
|
|
8
|
+
import {cn} from '../../lib/utils'
|
|
9
|
+
import {Button} from '../atoms/button'
|
|
10
|
+
|
|
11
|
+
interface BuyNowButtonProps {
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
className?: string
|
|
14
|
+
size?: 'default' | 'sm' | 'lg'
|
|
15
|
+
/**
|
|
16
|
+
* The discount code to apply to the purchase.
|
|
17
|
+
*/
|
|
18
|
+
discountCode?: string
|
|
19
|
+
/**
|
|
20
|
+
* The GID of the product variant. E.g. `gid://shopify/ProductVariant/456`.
|
|
21
|
+
*/
|
|
22
|
+
productVariantId: string
|
|
23
|
+
/**
|
|
24
|
+
* The product to buy now.
|
|
25
|
+
*/
|
|
26
|
+
product?: Product
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function BuyNowButton({
|
|
30
|
+
disabled = false,
|
|
31
|
+
className,
|
|
32
|
+
size = 'default',
|
|
33
|
+
productVariantId,
|
|
34
|
+
discountCode,
|
|
35
|
+
product,
|
|
36
|
+
}: BuyNowButtonProps) {
|
|
37
|
+
const {buyProduct} = useShopCartActions()
|
|
38
|
+
const {navigateToProduct} = useShopNavigation()
|
|
39
|
+
const [isPurchasing, setIsPurchasing] = useState(false)
|
|
40
|
+
const {id, referral} = product ?? {}
|
|
41
|
+
|
|
42
|
+
const {showErrorToast} = useErrorToast()
|
|
43
|
+
|
|
44
|
+
const handleClick = useCallback(async () => {
|
|
45
|
+
if (disabled) return
|
|
46
|
+
|
|
47
|
+
if (id && referral) {
|
|
48
|
+
navigateToProduct({
|
|
49
|
+
productId: id,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (isPurchasing) return
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (id && productVariantId) {
|
|
59
|
+
setIsPurchasing(true)
|
|
60
|
+
|
|
61
|
+
await buyProduct({
|
|
62
|
+
productId: id,
|
|
63
|
+
productVariantId,
|
|
64
|
+
quantity: 1,
|
|
65
|
+
discountCode,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
setIsPurchasing(false)
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
showErrorToast({message: 'Failed to complete purchase'})
|
|
72
|
+
setIsPurchasing(false)
|
|
73
|
+
}
|
|
74
|
+
}, [
|
|
75
|
+
disabled,
|
|
76
|
+
id,
|
|
77
|
+
referral,
|
|
78
|
+
isPurchasing,
|
|
79
|
+
navigateToProduct,
|
|
80
|
+
productVariantId,
|
|
81
|
+
buyProduct,
|
|
82
|
+
discountCode,
|
|
83
|
+
showErrorToast,
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Button
|
|
88
|
+
onClick={handleClick}
|
|
89
|
+
disabled={disabled || isPurchasing}
|
|
90
|
+
className={cn('relative overflow-hidden', className)}
|
|
91
|
+
size={size}
|
|
92
|
+
aria-live="polite"
|
|
93
|
+
aria-busy={isPurchasing}
|
|
94
|
+
>
|
|
95
|
+
<AnimatePresence mode="wait">
|
|
96
|
+
<motion.span
|
|
97
|
+
key="text"
|
|
98
|
+
initial={{opacity: 0, y: 10}}
|
|
99
|
+
animate={{opacity: 1, y: 0}}
|
|
100
|
+
exit={{opacity: 0, y: -10}}
|
|
101
|
+
transition={{duration: 0.2}}
|
|
102
|
+
>
|
|
103
|
+
{referral ? 'View product' : 'Buy now'}
|
|
104
|
+
</motion.span>
|
|
105
|
+
</AnimatePresence>
|
|
106
|
+
</Button>
|
|
107
|
+
)
|
|
108
|
+
}
|
package/src/components/index.ts
CHANGED
package/src/hooks/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
BuyProductParams,
|
|
4
4
|
} from '@shopify/shop-minis-platform/actions'
|
|
5
5
|
|
|
6
|
-
import {useHandleAction} from '
|
|
7
|
-
import {useShopActions} from '
|
|
6
|
+
import {useHandleAction} from './useHandleAction'
|
|
7
|
+
import {useShopActions} from './useShopActions'
|
|
8
8
|
|
|
9
9
|
interface UseShopCartActionsReturns {
|
|
10
10
|
/**
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
import {Product} from '@shopify/shop-minis-platform'
|
|
2
|
+
|
|
1
3
|
import {AddToCartButton} from '../components/commerce/add-to-cart'
|
|
2
4
|
|
|
3
5
|
import type {Meta, StoryObj} from '@storybook/react-vite'
|
|
4
6
|
|
|
7
|
+
// Mock product for stories
|
|
8
|
+
const mockProduct: Product = {
|
|
9
|
+
id: 'gid://shopify/Product/123',
|
|
10
|
+
title: 'Test Product',
|
|
11
|
+
reviewAnalytics: {
|
|
12
|
+
averageRating: 4.5,
|
|
13
|
+
reviewCount: 100,
|
|
14
|
+
},
|
|
15
|
+
shop: {
|
|
16
|
+
id: 'gid://shopify/Shop/1',
|
|
17
|
+
name: 'Test Shop',
|
|
18
|
+
},
|
|
19
|
+
defaultVariantId: 'gid://shopify/ProductVariant/456',
|
|
20
|
+
isFavorited: false,
|
|
21
|
+
price: {
|
|
22
|
+
amount: '29.99',
|
|
23
|
+
currencyCode: 'USD',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
const meta = {
|
|
6
28
|
title: 'Commerce/AddToCartButton',
|
|
7
29
|
component: AddToCartButton,
|
|
@@ -21,9 +43,9 @@ const meta = {
|
|
|
21
43
|
control: 'text',
|
|
22
44
|
description: 'Additional CSS classes',
|
|
23
45
|
},
|
|
24
|
-
|
|
25
|
-
control: '
|
|
26
|
-
description: 'The
|
|
46
|
+
product: {
|
|
47
|
+
control: 'object',
|
|
48
|
+
description: 'The product object containing product details',
|
|
27
49
|
},
|
|
28
50
|
productVariantId: {
|
|
29
51
|
control: 'text',
|
|
@@ -38,7 +60,7 @@ const meta = {
|
|
|
38
60
|
args: {
|
|
39
61
|
disabled: false,
|
|
40
62
|
size: 'default',
|
|
41
|
-
|
|
63
|
+
product: mockProduct,
|
|
42
64
|
productVariantId: 'gid://shopify/ProductVariant/456',
|
|
43
65
|
},
|
|
44
66
|
} satisfies Meta<typeof AddToCartButton>
|
|
@@ -97,6 +119,23 @@ export const Disabled: Story = {
|
|
|
97
119
|
},
|
|
98
120
|
}
|
|
99
121
|
|
|
122
|
+
export const ReferralProduct: Story = {
|
|
123
|
+
args: {
|
|
124
|
+
product: {...mockProduct, referral: true},
|
|
125
|
+
},
|
|
126
|
+
render: args => {
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-2">
|
|
129
|
+
<p className="text-sm text-gray-600">
|
|
130
|
+
This is a referral product - button shows “View product”
|
|
131
|
+
instead
|
|
132
|
+
</p>
|
|
133
|
+
<AddToCartButton {...args} />
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
100
139
|
export const CustomStyling: Story = {
|
|
101
140
|
args: {
|
|
102
141
|
className: 'bg-purple-600 hover:bg-purple-700 text-white rounded-full px-8',
|
|
@@ -120,17 +159,17 @@ export const AllSizes: Story = {
|
|
|
120
159
|
<h3 className="text-lg font-semibold">Button Sizes</h3>
|
|
121
160
|
<div className="flex gap-4 items-center">
|
|
122
161
|
<AddToCartButton
|
|
123
|
-
|
|
162
|
+
product={mockProduct}
|
|
124
163
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
125
164
|
size="sm"
|
|
126
165
|
/>
|
|
127
166
|
<AddToCartButton
|
|
128
|
-
|
|
167
|
+
product={mockProduct}
|
|
129
168
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
130
169
|
size="default"
|
|
131
170
|
/>
|
|
132
171
|
<AddToCartButton
|
|
133
|
-
|
|
172
|
+
product={mockProduct}
|
|
134
173
|
productVariantId="gid://shopify/ProductVariant/456"
|
|
135
174
|
size="lg"
|
|
136
175
|
/>
|
|
@@ -142,6 +181,32 @@ export const AllSizes: Story = {
|
|
|
142
181
|
|
|
143
182
|
export const InteractiveExample: Story = {
|
|
144
183
|
render: () => {
|
|
184
|
+
const productA: Product = {
|
|
185
|
+
...mockProduct,
|
|
186
|
+
id: 'gid://shopify/Product/111',
|
|
187
|
+
title: 'Product A',
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const productB: Product = {
|
|
191
|
+
...mockProduct,
|
|
192
|
+
id: 'gid://shopify/Product/222',
|
|
193
|
+
title: 'Product B',
|
|
194
|
+
price: {
|
|
195
|
+
amount: '49.99',
|
|
196
|
+
currencyCode: 'USD',
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const productC: Product = {
|
|
201
|
+
...mockProduct,
|
|
202
|
+
id: 'gid://shopify/Product/333',
|
|
203
|
+
title: 'Product C',
|
|
204
|
+
price: {
|
|
205
|
+
amount: '19.99',
|
|
206
|
+
currencyCode: 'USD',
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
145
210
|
return (
|
|
146
211
|
<div className="flex flex-col items-center space-y-6 p-8">
|
|
147
212
|
<div className="text-center">
|
|
@@ -155,7 +220,7 @@ export const InteractiveExample: Story = {
|
|
|
155
220
|
<div className="text-center">
|
|
156
221
|
<p className="text-xs text-gray-500 mb-2">Product A</p>
|
|
157
222
|
<AddToCartButton
|
|
158
|
-
|
|
223
|
+
product={productA}
|
|
159
224
|
productVariantId="gid://shopify/ProductVariant/111"
|
|
160
225
|
/>
|
|
161
226
|
</div>
|
|
@@ -165,7 +230,7 @@ export const InteractiveExample: Story = {
|
|
|
165
230
|
Product B (with discount)
|
|
166
231
|
</p>
|
|
167
232
|
<AddToCartButton
|
|
168
|
-
|
|
233
|
+
product={productB}
|
|
169
234
|
productVariantId="gid://shopify/ProductVariant/222"
|
|
170
235
|
discountCodes={['SAVE10']}
|
|
171
236
|
/>
|
|
@@ -174,7 +239,7 @@ export const InteractiveExample: Story = {
|
|
|
174
239
|
<div className="text-center">
|
|
175
240
|
<p className="text-xs text-gray-500 mb-2">Product C (small size)</p>
|
|
176
241
|
<AddToCartButton
|
|
177
|
-
|
|
242
|
+
product={productC}
|
|
178
243
|
productVariantId="gid://shopify/ProductVariant/333"
|
|
179
244
|
size="sm"
|
|
180
245
|
/>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useShopCartActions.js","sources":["../../../src/hooks/shop/useShopCartActions.ts"],"sourcesContent":["import {\n AddToCartParams,\n BuyProductParams,\n} from '@shopify/shop-minis-platform/actions'\n\nimport {useHandleAction} from '../../internal/useHandleAction'\nimport {useShopActions} from '../../internal/useShopActions'\n\ninterface UseShopCartActionsReturns {\n /**\n * Add a product to the cart\n */\n addToCart: (params: AddToCartParams) => Promise<void>\n /**\n * Buy a product directly\n */\n buyProduct: (params: BuyProductParams) => Promise<void>\n}\n\nexport const useShopCartActions = (): UseShopCartActionsReturns => {\n const {addToCart, buyProduct} = useShopActions()\n\n return {\n addToCart: useHandleAction(addToCart),\n buyProduct: useHandleAction(buyProduct),\n }\n}\n"],"names":["useShopCartActions","addToCart","buyProduct","useShopActions","useHandleAction"],"mappings":";;AAmBO,MAAMA,IAAqB,MAAiC;AACjE,QAAM,EAAC,WAAAC,GAAW,YAAAC,EAAU,IAAIC,EAAe;AAExC,SAAA;AAAA,IACL,WAAWC,EAAgBH,CAAS;AAAA,IACpC,YAAYG,EAAgBF,CAAU;AAAA,EACxC;AACF;"}
|