@instockng/storefront-ui 1.0.10 → 1.0.12
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/README.md +26 -0
- package/dist/components/Checkout.d.ts.map +1 -1
- package/dist/components/ShoppingCart.d.ts.map +1 -1
- package/dist/contexts/CartContext.d.ts.map +1 -1
- package/dist/index10.mjs +144 -141
- package/dist/index101.mjs +1 -1
- package/dist/index102.mjs +3 -3
- package/dist/index103.mjs +3 -3
- package/dist/index105.mjs +1 -1
- package/dist/index111.mjs +1 -1
- package/dist/index20.mjs +2 -2
- package/dist/index21.mjs +1 -1
- package/dist/index28.mjs +11 -11
- package/dist/index3.mjs +88 -78
- package/dist/index37.mjs +1 -1
- package/dist/index41.mjs +36 -23
- package/dist/index42.mjs +44 -36
- package/dist/index43.mjs +99 -44
- package/dist/index44.mjs +112 -99
- package/dist/index45.mjs +44 -80
- package/dist/index46.mjs +64 -53
- package/dist/index47.mjs +65 -48
- package/dist/index48.mjs +54 -73
- package/dist/index49.mjs +52 -63
- package/dist/index50.mjs +14 -70
- package/dist/index51.mjs +13 -14
- package/dist/index52.mjs +58 -13
- package/dist/index53.mjs +101 -34
- package/dist/index54.mjs +99 -95
- package/dist/index55.mjs +22 -132
- package/dist/index62.mjs +30 -231
- package/dist/index63.mjs +42 -5
- package/dist/index64.mjs +228 -127
- package/dist/index65.mjs +4 -66
- package/dist/index66.mjs +124 -77
- package/dist/index67.mjs +65 -26
- package/dist/index68.mjs +84 -6
- package/dist/index69.mjs +26 -72
- package/dist/index70.mjs +8 -3
- package/dist/index71.mjs +75 -2
- package/dist/index72.mjs +3 -82
- package/dist/index73.mjs +2 -54
- package/dist/index74.mjs +82 -5
- package/dist/index75.mjs +53 -4
- package/dist/index76.mjs +5 -178
- package/dist/index77.mjs +5 -53
- package/dist/index78.mjs +178 -68
- package/dist/index79.mjs +50 -31
- package/dist/index8.mjs +8 -7
- package/dist/index80.mjs +69 -43
- package/dist/index81.mjs +2 -2
- package/dist/index82.mjs +1 -1
- package/dist/index83.mjs +6 -6
- package/dist/index84.mjs +2 -2
- package/dist/index85.mjs +2 -2
- package/dist/index87.mjs +2 -2
- package/dist/index88.mjs +2 -2
- package/dist/index89.mjs +1 -1
- package/dist/index91.mjs +4 -4
- package/dist/index92.mjs +3 -3
- package/dist/index93.mjs +12 -30
- package/dist/index94.mjs +7 -11
- package/dist/index95.mjs +30 -3
- package/dist/index96.mjs +10 -3
- package/dist/index97.mjs +4 -13
- package/dist/index98.mjs +4 -7
- package/dist/index99.mjs +1 -1
- package/dist/styles.css +1 -0
- package/package.json +14 -13
- package/src/components/CartItem.stories.tsx +94 -0
- package/src/components/CartItem.tsx +141 -0
- package/src/components/Checkout.stories.tsx +380 -0
- package/src/components/Checkout.tsx +954 -0
- package/src/components/DiscountCodeInput.stories.tsx +76 -0
- package/src/components/DiscountCodeInput.tsx +162 -0
- package/src/components/OrderConfirmation.stories.tsx +142 -0
- package/src/components/OrderConfirmation.tsx +301 -0
- package/src/components/ProductCard.stories.tsx +112 -0
- package/src/components/ProductCard.tsx +195 -0
- package/src/components/ProductGrid.stories.tsx +137 -0
- package/src/components/ProductGrid.tsx +141 -0
- package/src/components/ShoppingCart.stories.tsx +459 -0
- package/src/components/ShoppingCart.tsx +263 -0
- package/src/components/ui/badge.tsx +37 -0
- package/src/components/ui/button.tsx +71 -0
- package/src/components/ui/card.tsx +79 -0
- package/src/components/ui/form-input.tsx +78 -0
- package/src/components/ui/form-select.tsx +73 -0
- package/src/components/ui/modal.tsx +181 -0
- package/src/contexts/CartContext.tsx +316 -0
- package/src/hooks/usePaystackPayment.ts +137 -0
- package/src/index.ts +51 -0
- package/src/lib/utils.ts +45 -0
- package/src/paystack.svg +67 -0
- package/src/providers/StorefrontProvider.tsx +70 -0
- package/src/styles.css +1 -0
- package/src/test-utils/MockCartProvider.tsx +424 -0
- package/src/vite-env.d.ts +12 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopping Cart Storybook Stories
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the cart context API - open/close cart from anywhere using useCart().
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
8
|
+
import { useCart } from '../contexts/CartContext';
|
|
9
|
+
import { MockCartProvider } from '../test-utils/MockCartProvider';
|
|
10
|
+
import { fn } from '@storybook/test';
|
|
11
|
+
import { Button } from './ui/button';
|
|
12
|
+
import { ShoppingCart as ShoppingCartIcon } from 'lucide-react';
|
|
13
|
+
import type { Cart } from '@oms/api-client';
|
|
14
|
+
|
|
15
|
+
// Demo component showing how to use the cart context
|
|
16
|
+
const CartDemo = ({ title, description }: { title?: string; description?: string }) => {
|
|
17
|
+
const { open, cart } = useCart();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="min-h-screen bg-gray-50 p-8">
|
|
21
|
+
<div className="max-w-4xl mx-auto">
|
|
22
|
+
<h1 className="text-3xl font-bold mb-4">
|
|
23
|
+
{title || 'Shopping Cart Context Demo'}
|
|
24
|
+
</h1>
|
|
25
|
+
<p className="text-gray-600 mb-6">
|
|
26
|
+
{description || 'Click the button below to open the cart. The cart state is managed by CartContext - you can call open() from anywhere in your app!'}
|
|
27
|
+
</p>
|
|
28
|
+
<div className="flex gap-4 items-center">
|
|
29
|
+
<Button onClick={open} size="lg">
|
|
30
|
+
<ShoppingCartIcon className="h-5 w-5" />
|
|
31
|
+
Open Cart ({cart?.items?.length || 0} items)
|
|
32
|
+
</Button>
|
|
33
|
+
<div className="text-sm text-gray-500">
|
|
34
|
+
Try this: <code className="bg-gray-200 px-2 py-1 rounded">const {'{ open }'} = useCart();</code>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Mock cart data
|
|
43
|
+
const mockCart: Cart = {
|
|
44
|
+
id: 'mock-cart-123',
|
|
45
|
+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
|
|
46
|
+
brand: {
|
|
47
|
+
id: 'mock-brand-456',
|
|
48
|
+
name: 'Demo Store',
|
|
49
|
+
slug: 'demo-store',
|
|
50
|
+
logoUrl: 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200&h=80&fit=crop',
|
|
51
|
+
siteUrl: 'https://demo-store.com',
|
|
52
|
+
domain: 'f',
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
updatedAt: new Date().toISOString(),
|
|
55
|
+
deletedAt: null,
|
|
56
|
+
},
|
|
57
|
+
customerPhone: null,
|
|
58
|
+
customerEmail: null,
|
|
59
|
+
customerFirstName: null,
|
|
60
|
+
customerLastName: null,
|
|
61
|
+
availablePaymentMethods: ['cod'],
|
|
62
|
+
deliveryZone: null,
|
|
63
|
+
recoveryAttempts: 0,
|
|
64
|
+
lastRecoveryAttemptAt: null,
|
|
65
|
+
recoveryDiscountCode: null,
|
|
66
|
+
items: [
|
|
67
|
+
{
|
|
68
|
+
id: 'item-1',
|
|
69
|
+
variant: {
|
|
70
|
+
id: 'variant-1',
|
|
71
|
+
productId: 'product-1',
|
|
72
|
+
name: 'Medium - Blue',
|
|
73
|
+
price: 2999,
|
|
74
|
+
sku: 'TSHIRT-MD-BLUE',
|
|
75
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
|
|
76
|
+
trackInventory: true,
|
|
77
|
+
lowStockThreshold: 10,
|
|
78
|
+
isActive: true,
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
updatedAt: new Date().toISOString(),
|
|
81
|
+
deletedAt: null,
|
|
82
|
+
product: {
|
|
83
|
+
id: 'product-1',
|
|
84
|
+
name: 'Premium Cotton T-Shirt',
|
|
85
|
+
slug: 'premium-cotton-t-shirt',
|
|
86
|
+
brandId: 'mock-brand-456',
|
|
87
|
+
isActive: true,
|
|
88
|
+
description: 'Comfortable premium cotton t-shirt',
|
|
89
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=400',
|
|
90
|
+
createdAt: new Date().toISOString(),
|
|
91
|
+
updatedAt: new Date().toISOString(),
|
|
92
|
+
deletedAt: null,
|
|
93
|
+
quantityDiscounts: null,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
quantity: 2,
|
|
97
|
+
basePrice: 2999,
|
|
98
|
+
discountPercent: 0,
|
|
99
|
+
finalPrice: 2599,
|
|
100
|
+
subtotal: 5198,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'item-2',
|
|
104
|
+
variant: {
|
|
105
|
+
id: 'variant-2',
|
|
106
|
+
productId: 'product-2',
|
|
107
|
+
name: 'Large - Black',
|
|
108
|
+
price: 3499,
|
|
109
|
+
sku: 'JEANS-LG-BLACK',
|
|
110
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
|
|
111
|
+
trackInventory: true,
|
|
112
|
+
lowStockThreshold: 10,
|
|
113
|
+
isActive: true,
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
updatedAt: new Date().toISOString(),
|
|
116
|
+
deletedAt: null,
|
|
117
|
+
product: {
|
|
118
|
+
id: 'product-2',
|
|
119
|
+
name: 'Slim Fit Jeans',
|
|
120
|
+
slug: 'slim-fit-jeans',
|
|
121
|
+
brandId: 'mock-brand-456',
|
|
122
|
+
isActive: true,
|
|
123
|
+
description: 'Modern slim fit jeans',
|
|
124
|
+
thumbnailUrl: 'https://images.unsplash.com/photo-1542272604-787c3835535d?w=400',
|
|
125
|
+
createdAt: new Date().toISOString(),
|
|
126
|
+
updatedAt: new Date().toISOString(),
|
|
127
|
+
deletedAt: null,
|
|
128
|
+
quantityDiscounts: null,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
quantity: 1,
|
|
132
|
+
basePrice: 3499,
|
|
133
|
+
discountPercent: 0,
|
|
134
|
+
finalPrice: 3499,
|
|
135
|
+
subtotal: 3499,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
pricing: {
|
|
139
|
+
subtotal: 8697,
|
|
140
|
+
deliveryCharge: 0,
|
|
141
|
+
discount: null,
|
|
142
|
+
total: 8697,
|
|
143
|
+
},
|
|
144
|
+
createdAt: new Date().toISOString(),
|
|
145
|
+
updatedAt: new Date().toISOString(),
|
|
146
|
+
convertedToOrderId: null,
|
|
147
|
+
wasRecovered: false,
|
|
148
|
+
recoveryUrl: 'https://demo-store.com?cartId=mock-cart-123',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Create a wrapper type for our stories
|
|
152
|
+
type CartProviderStory = StoryObj<{
|
|
153
|
+
cart: Partial<Cart>;
|
|
154
|
+
shoppingCartProps: any;
|
|
155
|
+
}>;
|
|
156
|
+
|
|
157
|
+
const meta: Meta<{
|
|
158
|
+
cart: Partial<Cart>;
|
|
159
|
+
shoppingCartProps: any;
|
|
160
|
+
}> = {
|
|
161
|
+
title: 'Components/ShoppingCart',
|
|
162
|
+
parameters: {
|
|
163
|
+
layout: 'fullscreen',
|
|
164
|
+
},
|
|
165
|
+
tags: ['autodocs'],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export default meta;
|
|
169
|
+
|
|
170
|
+
// Default shopping cart
|
|
171
|
+
export const Default: CartProviderStory = {
|
|
172
|
+
render: (args) => (
|
|
173
|
+
<MockCartProvider
|
|
174
|
+
cart={args.cart}
|
|
175
|
+
shoppingCartProps={{
|
|
176
|
+
onCheckout: fn(),
|
|
177
|
+
onContinueShopping: fn(),
|
|
178
|
+
...args.shoppingCartProps,
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<CartDemo />
|
|
182
|
+
</MockCartProvider>
|
|
183
|
+
),
|
|
184
|
+
args: {
|
|
185
|
+
cart: mockCart,
|
|
186
|
+
shoppingCartProps: {},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Cart with discount code input
|
|
191
|
+
export const WithDiscountCode: CartProviderStory = {
|
|
192
|
+
render: (args) => (
|
|
193
|
+
<MockCartProvider
|
|
194
|
+
cart={args.cart}
|
|
195
|
+
shoppingCartProps={{
|
|
196
|
+
showDiscountCode: true,
|
|
197
|
+
onCheckout: fn(),
|
|
198
|
+
onContinueShopping: fn(),
|
|
199
|
+
...args.shoppingCartProps,
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
<CartDemo
|
|
203
|
+
title="Cart with Discount Code"
|
|
204
|
+
description="The cart includes a discount code input. Try entering 'SAVE10' to see it in action!"
|
|
205
|
+
/>
|
|
206
|
+
</MockCartProvider>
|
|
207
|
+
),
|
|
208
|
+
args: {
|
|
209
|
+
cart: mockCart,
|
|
210
|
+
shoppingCartProps: { showDiscountCode: true },
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Without discount code input
|
|
215
|
+
export const WithoutDiscountCode: CartProviderStory = {
|
|
216
|
+
render: (args) => (
|
|
217
|
+
<MockCartProvider
|
|
218
|
+
cart={args.cart}
|
|
219
|
+
shoppingCartProps={{
|
|
220
|
+
showDiscountCode: false,
|
|
221
|
+
onCheckout: fn(),
|
|
222
|
+
onContinueShopping: fn(),
|
|
223
|
+
...args.shoppingCartProps,
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
<CartDemo
|
|
227
|
+
title="Cart without Discount Code"
|
|
228
|
+
description="This cart hides the discount code input field."
|
|
229
|
+
/>
|
|
230
|
+
</MockCartProvider>
|
|
231
|
+
),
|
|
232
|
+
args: {
|
|
233
|
+
cart: mockCart,
|
|
234
|
+
shoppingCartProps: { showDiscountCode: false },
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Custom button text
|
|
239
|
+
export const CustomButtonText: CartProviderStory = {
|
|
240
|
+
render: (args) => (
|
|
241
|
+
<MockCartProvider
|
|
242
|
+
cart={args.cart}
|
|
243
|
+
shoppingCartProps={{
|
|
244
|
+
checkoutButtonText: 'Complete Purchase',
|
|
245
|
+
continueShoppingText: 'Keep Browsing',
|
|
246
|
+
onCheckout: fn(),
|
|
247
|
+
onContinueShopping: fn(),
|
|
248
|
+
...args.shoppingCartProps,
|
|
249
|
+
}}
|
|
250
|
+
>
|
|
251
|
+
<CartDemo
|
|
252
|
+
title="Custom Button Text"
|
|
253
|
+
description="The checkout and continue shopping buttons have custom text."
|
|
254
|
+
/>
|
|
255
|
+
</MockCartProvider>
|
|
256
|
+
),
|
|
257
|
+
args: {
|
|
258
|
+
cart: mockCart,
|
|
259
|
+
shoppingCartProps: {
|
|
260
|
+
checkoutButtonText: 'Complete Purchase',
|
|
261
|
+
continueShoppingText: 'Keep Browsing',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Checkout button disabled
|
|
267
|
+
export const CheckoutDisabled: CartProviderStory = {
|
|
268
|
+
render: (args) => (
|
|
269
|
+
<MockCartProvider
|
|
270
|
+
cart={args.cart}
|
|
271
|
+
shoppingCartProps={{
|
|
272
|
+
disableCheckout: true,
|
|
273
|
+
onCheckout: fn(),
|
|
274
|
+
onContinueShopping: fn(),
|
|
275
|
+
...args.shoppingCartProps,
|
|
276
|
+
}}
|
|
277
|
+
>
|
|
278
|
+
<CartDemo
|
|
279
|
+
title="Checkout Disabled"
|
|
280
|
+
description="The checkout button is disabled (useful when items are out of stock or other validation fails)."
|
|
281
|
+
/>
|
|
282
|
+
</MockCartProvider>
|
|
283
|
+
),
|
|
284
|
+
args: {
|
|
285
|
+
cart: mockCart,
|
|
286
|
+
shoppingCartProps: { disableCheckout: true },
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Empty cart state
|
|
291
|
+
export const EmptyCart: CartProviderStory = {
|
|
292
|
+
render: (args) => (
|
|
293
|
+
<MockCartProvider
|
|
294
|
+
cart={args.cart}
|
|
295
|
+
shoppingCartProps={{
|
|
296
|
+
emptyMessage: 'Your shopping cart is empty. Start adding items!',
|
|
297
|
+
onCheckout: fn(),
|
|
298
|
+
onContinueShopping: fn(),
|
|
299
|
+
...args.shoppingCartProps,
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
<CartDemo
|
|
303
|
+
title="Empty Cart"
|
|
304
|
+
description="The cart has no items. See the empty state message."
|
|
305
|
+
/>
|
|
306
|
+
</MockCartProvider>
|
|
307
|
+
),
|
|
308
|
+
args: {
|
|
309
|
+
cart: {
|
|
310
|
+
...mockCart,
|
|
311
|
+
items: [],
|
|
312
|
+
pricing: {
|
|
313
|
+
subtotal: 0,
|
|
314
|
+
discount: null,
|
|
315
|
+
deliveryCharge: 0,
|
|
316
|
+
total: 0,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
shoppingCartProps: {
|
|
320
|
+
emptyMessage: 'Your shopping cart is empty. Start adding items!',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Cart with discount applied
|
|
326
|
+
export const WithDiscount: CartProviderStory = {
|
|
327
|
+
render: (args) => (
|
|
328
|
+
<MockCartProvider
|
|
329
|
+
cart={args.cart}
|
|
330
|
+
shoppingCartProps={{
|
|
331
|
+
showDiscountCode: true,
|
|
332
|
+
onCheckout: fn(),
|
|
333
|
+
onContinueShopping: fn(),
|
|
334
|
+
...args.shoppingCartProps,
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
<CartDemo
|
|
338
|
+
title="Cart with Discount Applied"
|
|
339
|
+
description="A 10% discount has been applied to this cart."
|
|
340
|
+
/>
|
|
341
|
+
</MockCartProvider>
|
|
342
|
+
),
|
|
343
|
+
args: {
|
|
344
|
+
cart: {
|
|
345
|
+
...mockCart,
|
|
346
|
+
pricing: {
|
|
347
|
+
subtotal: 8697,
|
|
348
|
+
discount: {
|
|
349
|
+
code: 'SAVE10',
|
|
350
|
+
type: 'percentage',
|
|
351
|
+
value: 10,
|
|
352
|
+
amount: 870,
|
|
353
|
+
description: '10% off your order',
|
|
354
|
+
},
|
|
355
|
+
deliveryCharge: 0,
|
|
356
|
+
total: 7827,
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
shoppingCartProps: { showDiscountCode: true },
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Integrated checkout
|
|
364
|
+
export const WithIntegratedCheckout: CartProviderStory = {
|
|
365
|
+
render: (args) => (
|
|
366
|
+
<MockCartProvider
|
|
367
|
+
cart={args.cart}
|
|
368
|
+
shoppingCartProps={{
|
|
369
|
+
onOrderSuccess: fn(),
|
|
370
|
+
onCheckoutError: fn(),
|
|
371
|
+
...args.shoppingCartProps,
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
<CartDemo
|
|
375
|
+
title="Integrated Checkout"
|
|
376
|
+
description="This cart includes the checkout flow. Click 'Checkout' to see the checkout modal."
|
|
377
|
+
/>
|
|
378
|
+
</MockCartProvider>
|
|
379
|
+
),
|
|
380
|
+
args: {
|
|
381
|
+
cart: mockCart,
|
|
382
|
+
shoppingCartProps: {
|
|
383
|
+
onOrderSuccess: fn(),
|
|
384
|
+
onCheckoutError: fn(),
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Multiple open buttons example
|
|
390
|
+
const MultipleButtonsDemo = () => {
|
|
391
|
+
const { open, cart } = useCart();
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div className="min-h-screen bg-gray-50 p-8">
|
|
395
|
+
<div className="max-w-4xl mx-auto space-y-8">
|
|
396
|
+
<div>
|
|
397
|
+
<h1 className="text-3xl font-bold mb-4">
|
|
398
|
+
Open Cart from Anywhere
|
|
399
|
+
</h1>
|
|
400
|
+
<p className="text-gray-600 mb-6">
|
|
401
|
+
Since the cart is managed by context, you can open it from any component in your app!
|
|
402
|
+
</p>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
406
|
+
<h2 className="text-xl font-semibold mb-4">Header Component</h2>
|
|
407
|
+
<Button onClick={open} variant="outline">
|
|
408
|
+
<ShoppingCartIcon className="h-4 w-4" />
|
|
409
|
+
Cart ({cart?.items?.length || 0})
|
|
410
|
+
</Button>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
414
|
+
<h2 className="text-xl font-semibold mb-4">Product Card</h2>
|
|
415
|
+
<p className="text-gray-600 mb-4">After adding to cart, you might want to show the cart:</p>
|
|
416
|
+
<Button onClick={open} size="lg">
|
|
417
|
+
<ShoppingCartIcon className="h-5 w-5" />
|
|
418
|
+
View Cart
|
|
419
|
+
</Button>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
423
|
+
<h2 className="text-xl font-semibold mb-4">Floating Action Button</h2>
|
|
424
|
+
<div className="fixed bottom-8 right-8">
|
|
425
|
+
<Button onClick={open} size="lg" className="rounded-full shadow-lg">
|
|
426
|
+
<ShoppingCartIcon className="h-6 w-6" />
|
|
427
|
+
</Button>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div className="bg-blue-50 border border-blue-200 p-4 rounded-lg">
|
|
432
|
+
<p className="text-sm text-blue-800">
|
|
433
|
+
<strong>💡 Tip:</strong> All these buttons use the same <code className="bg-blue-100 px-1 rounded">open()</code> function from <code className="bg-blue-100 px-1 rounded">useCart()</code>.
|
|
434
|
+
No prop drilling needed!
|
|
435
|
+
</p>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
export const OpenFromAnywhere: CartProviderStory = {
|
|
443
|
+
render: (args) => (
|
|
444
|
+
<MockCartProvider
|
|
445
|
+
cart={args.cart}
|
|
446
|
+
shoppingCartProps={{
|
|
447
|
+
onCheckout: fn(),
|
|
448
|
+
onContinueShopping: fn(),
|
|
449
|
+
...args.shoppingCartProps,
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
<MultipleButtonsDemo />
|
|
453
|
+
</MockCartProvider>
|
|
454
|
+
),
|
|
455
|
+
args: {
|
|
456
|
+
cart: mockCart,
|
|
457
|
+
shoppingCartProps: {},
|
|
458
|
+
},
|
|
459
|
+
};
|