@riosst100/pwa-marketplace 2.1.4 → 2.1.6
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/package.json +1 -1
- package/src/componentOverrideMapping.js +7 -0
- package/src/components/FavoriteSeller/AddToListButton/addToListButton.js +54 -54
- package/src/components/FavoriteSeller/AddToListButton/addToListButton.module.css +17 -17
- package/src/components/FavoriteSeller/AddToListButton/index.js +1 -1
- package/src/components/FavoriteSeller/AddToListButton/useCommonToasts.js +33 -33
- package/src/components/FilterTop/CustomFilters/customFilters.js +130 -132
- package/src/components/FilterTop/filterTop.js +1 -8
- package/src/components/LinkToOtherStores/index.js +61 -0
- package/src/components/NonSportCardsSets/nonSportCardsSets.js +0 -2
- package/src/components/RFQ/index.js +6 -3
- package/src/components/SellerDetail/sellerDetail.js +18 -1
- package/src/components/SellerInformation/sellerInformation.js +5 -3
- package/src/components/SellerSocialMedia/index.js +96 -0
- package/src/overwrites/pagebuilder/lib/ContentTypes/Products/products.js +13 -0
- package/src/overwrites/peregrine/lib/talons/MagentoRoute/magentoRoute.gql.js +27 -0
- package/src/overwrites/peregrine/lib/talons/MagentoRoute/useMagentoRoute.js +193 -0
- package/src/overwrites/peregrine/lib/talons/ProductFullDetail/useProductFullDetail.js +48 -11
- package/src/overwrites/peregrine/lib/talons/ProductImageCarousel/useProductImageCarousel.js +77 -0
- package/src/overwrites/peregrine/lib/talons/ProductOptions/useOption.js +59 -0
- package/src/overwrites/peregrine/lib/talons/ProductOptions/useTile.js +47 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Category/categoryFragments.gql.js +13 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/product.gql.js +5 -2
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/productDetailFragment.gql.js +23 -0
- package/src/overwrites/peregrine/lib/talons/RootComponents/Product/useProduct.js +121 -0
- package/src/overwrites/venia-ui/lib/RootComponents/Category/categoryContent.js +0 -6
- package/src/overwrites/venia-ui/lib/components/AccountInformationPage/accountInformationPage.js +0 -1
- package/src/overwrites/venia-ui/lib/components/AccountInformationPage/editForm.js +0 -1
- package/src/overwrites/venia-ui/lib/components/Breadcrumbs/breadcrumbs.js +0 -3
- package/src/overwrites/venia-ui/lib/components/CheckoutPage/OrderSummary/orderSummary.js +0 -1
- package/src/overwrites/venia-ui/lib/components/Gallery/item.js +17 -3
- package/src/overwrites/venia-ui/lib/components/Price/price.js +113 -0
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/components/auctionDetail.js +1 -1
- package/src/overwrites/venia-ui/lib/components/ProductFullDetail/productFullDetail.js +107 -105
- package/src/overwrites/venia-ui/lib/components/ProductImageCarousel/carousel.js +3 -1
- package/src/overwrites/venia-ui/lib/components/ProductOptions/option.js +112 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/option.module.css +30 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/options.js +49 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.js +118 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tile.module.css +68 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.js +78 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.module.css +6 -0
- package/src/overwrites/venia-ui/lib/components/ProductOptions/tileList.shimmer.js +32 -0
- package/src/talons/CustomFilters/useCustomFilters.js +0 -2
- package/src/talons/FavoriteSeller/AddToListButton/addToListButton.gql.js +30 -30
- package/src/talons/FavoriteSeller/AddToListButton/useAddToFavoriteListButton.js +0 -1
- package/src/talons/LegoSets/useLegoSets.js +0 -5
- package/src/talons/TrainsSets/useTrainsSets.js +0 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, Fragment, Suspense } from 'react';
|
|
1
|
+
import React, { useState, useMemo, Fragment, Suspense } from 'react';
|
|
2
2
|
import { FormattedMessage, useIntl } from 'react-intl';
|
|
3
3
|
import { arrayOf, bool, number, shape, string } from 'prop-types';
|
|
4
4
|
import { Form } from 'informed';
|
|
@@ -36,7 +36,9 @@ import CrossSeller from '@riosst100/pwa-marketplace/src/components/CrossSeller';
|
|
|
36
36
|
import RelatedProduct from '@riosst100/pwa-marketplace/src/components/RelatedProduct';
|
|
37
37
|
import ProductLabel from '@riosst100/pwa-marketplace/src/components/ProductLabel';
|
|
38
38
|
import RFQ from '@riosst100/pwa-marketplace/src/components/RFQ';
|
|
39
|
+
import LinkToOtherStores from '@riosst100/pwa-marketplace/src/components/LinkToOtherStores';
|
|
39
40
|
import Collapsible from '@riosst100/pwa-marketplace/src/components/commons/Collapsible';
|
|
41
|
+
import { useLocation } from 'react-router-dom';
|
|
40
42
|
|
|
41
43
|
// Correlate a GQL error message to a field. GQL could return a longer error
|
|
42
44
|
// string but it may contain contextual info such as product id. We can use
|
|
@@ -74,7 +76,12 @@ const ProductDetailsCollapsible = (props) => {
|
|
|
74
76
|
const ProductFullDetail = props => {
|
|
75
77
|
const { product } = props;
|
|
76
78
|
|
|
77
|
-
const
|
|
79
|
+
const { search } = useLocation();
|
|
80
|
+
|
|
81
|
+
const params = new URLSearchParams(search);
|
|
82
|
+
const isPreview = params.get('preview');
|
|
83
|
+
|
|
84
|
+
const talonProps = useProductFullDetail({ product, isPreview });
|
|
78
85
|
|
|
79
86
|
const {
|
|
80
87
|
breadcrumbCategoryId,
|
|
@@ -87,22 +94,26 @@ const ProductFullDetail = props => {
|
|
|
87
94
|
isAddToCartDisabled,
|
|
88
95
|
isSupportedProductType,
|
|
89
96
|
mediaGalleryEntries,
|
|
97
|
+
selectedMedia,
|
|
90
98
|
productDetails,
|
|
91
99
|
customAttributes,
|
|
92
100
|
wishlistButtonProps,
|
|
93
101
|
sellerDetails
|
|
94
102
|
} = talonProps;
|
|
95
|
-
console.log("🚀 ~ ProductFullDetail ~ talonProps:", talonProps)
|
|
96
103
|
|
|
97
104
|
const { formatMessage } = useIntl();
|
|
98
105
|
|
|
99
106
|
const classes = useStyle(defaultClasses, props.classes);
|
|
100
107
|
|
|
108
|
+
const [hoveredMedia, setHoveredMedia] = useState(null);
|
|
109
|
+
|
|
101
110
|
const options = isProductConfigurable(product) ? (
|
|
102
111
|
<Suspense fallback={<ProductOptionsShimmer />}>
|
|
103
112
|
<Options
|
|
104
113
|
onSelectionChange={handleSelectionChange}
|
|
105
114
|
options={product.configurable_options}
|
|
115
|
+
setHoveredMedia={setHoveredMedia}
|
|
116
|
+
variants={product.variants}
|
|
106
117
|
isEverythingOutOfStock={isEverythingOutOfStock}
|
|
107
118
|
outOfStockVariants={outOfStockVariants}
|
|
108
119
|
/>
|
|
@@ -223,28 +234,7 @@ const ProductFullDetail = props => {
|
|
|
223
234
|
// Error message for screen reader
|
|
224
235
|
const cartActionContent = isSupportedProductType ? (
|
|
225
236
|
<section className={cn(classes.actButton, 'flex gap-x-[30px]')}>
|
|
226
|
-
<
|
|
227
|
-
data-cy="ProductFullDetail-addToCartButton"
|
|
228
|
-
disabled={isAddToCartDisabled}
|
|
229
|
-
aria-disabled={isAddToCartDisabled}
|
|
230
|
-
aria-label={
|
|
231
|
-
isEverythingOutOfStock
|
|
232
|
-
? formatMessage({
|
|
233
|
-
id: 'productFullDetail.outOfStockProduct',
|
|
234
|
-
defaultMessage:
|
|
235
|
-
'This item is currently out of stock'
|
|
236
|
-
})
|
|
237
|
-
: ''
|
|
238
|
-
}
|
|
239
|
-
classes={{
|
|
240
|
-
content: 'normal-case font-medium text-[16px]'
|
|
241
|
-
}}
|
|
242
|
-
priority="low"
|
|
243
|
-
type="submit"
|
|
244
|
-
>
|
|
245
|
-
Make An Offer
|
|
246
|
-
</Button>
|
|
247
|
-
|
|
237
|
+
<RFQ disabled={isAddToCartDisabled} />
|
|
248
238
|
<Button
|
|
249
239
|
data-cy="ProductFullDetail-addToCartButton"
|
|
250
240
|
disabled={isAddToCartDisabled}
|
|
@@ -399,47 +389,87 @@ const ProductFullDetail = props => {
|
|
|
399
389
|
|
|
400
390
|
|
|
401
391
|
const dataTabs =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
392
|
+
[
|
|
393
|
+
{
|
|
394
|
+
id: 'product-detail',
|
|
395
|
+
title: 'Description',
|
|
396
|
+
content: <ProductDescription />
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: 'product-more-info',
|
|
400
|
+
title: 'Details',
|
|
401
|
+
content: <ProductMoreInfo />
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: 'product-tnc',
|
|
405
|
+
title: 'Term & Conditions',
|
|
406
|
+
content: <ProductTNC />
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: 'product-shipping-policy',
|
|
410
|
+
title: 'Shipping Policy',
|
|
411
|
+
content: <ShippingPolicy />
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'product-return-policy',
|
|
415
|
+
title: 'Return Policy',
|
|
416
|
+
content: <ReturnPolicy />
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: 'product-faq',
|
|
420
|
+
title: 'FAQ',
|
|
421
|
+
content: <ProductFAQ />
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: 'product-reviews',
|
|
425
|
+
title: 'Reviews',
|
|
426
|
+
content: <ProductReviews className={cn(contentContainerClass)} />
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const getSellerAddressDisplay = (seller) => {
|
|
431
|
+
let city = seller?.city;
|
|
432
|
+
let country = seller?.country;
|
|
433
|
+
|
|
434
|
+
let result = '';
|
|
435
|
+
if (city && country) {
|
|
436
|
+
result = `${city}, ${country}`;
|
|
437
|
+
}
|
|
438
|
+
if (city && !country) {
|
|
439
|
+
result = `${city}`;
|
|
440
|
+
}
|
|
441
|
+
if (!city && country) {
|
|
442
|
+
result = `${country}`;
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const productPreviewMessages = isPreview ? (
|
|
448
|
+
<div
|
|
449
|
+
style={{
|
|
450
|
+
backgroundColor: "white", // Light gray background
|
|
451
|
+
border: "1px solid rgb(242 101 102)", // Subtle border
|
|
452
|
+
borderRadius: "5px", // Rounded corners
|
|
453
|
+
padding: "10px 15px", // Space inside the box
|
|
454
|
+
margin: "10px 0px 20px 0", // Space outside the box
|
|
455
|
+
fontSize: "14px", // Readable text size
|
|
456
|
+
color: "rgb(242 101 102)", // Dark gray text
|
|
457
|
+
}}
|
|
458
|
+
>
|
|
459
|
+
<span
|
|
460
|
+
style={{
|
|
461
|
+
fontWeight: "500", // Slightly bold text
|
|
462
|
+
}}
|
|
463
|
+
>
|
|
464
|
+
This is a preview of your product. Only you can see this page.
|
|
465
|
+
</span>
|
|
466
|
+
</div>
|
|
467
|
+
) : null;
|
|
439
468
|
|
|
440
469
|
return (
|
|
441
470
|
<Fragment>
|
|
442
471
|
{breadcrumbs}
|
|
472
|
+
{productPreviewMessages}
|
|
443
473
|
<Form
|
|
444
474
|
className={classes.root}
|
|
445
475
|
data-cy="ProductFullDetail-root"
|
|
@@ -447,7 +477,7 @@ const ProductFullDetail = props => {
|
|
|
447
477
|
>
|
|
448
478
|
<section className={cn(classes.leftContainer, 'relative')}>
|
|
449
479
|
<ProductLabel item={productDetails} />
|
|
450
|
-
<Carousel images={mediaGalleryEntries} />
|
|
480
|
+
<Carousel images={mediaGalleryEntries} hoveredMedia={hoveredMedia} selectedMedia={selectedMedia}/>
|
|
451
481
|
<div className='product_group-actions flex gap-x-[18px] gap-y-4 justify-center items-center mt-2 lg_mt-5 mb-6 lg_mb-0'>
|
|
452
482
|
<Suspense fallback={null}>
|
|
453
483
|
<WishlistButton
|
|
@@ -469,7 +499,7 @@ const ProductFullDetail = props => {
|
|
|
469
499
|
<div
|
|
470
500
|
className={cn(
|
|
471
501
|
classes.title,
|
|
472
|
-
'mb-
|
|
502
|
+
'mb-xs',
|
|
473
503
|
)}
|
|
474
504
|
>
|
|
475
505
|
<h1
|
|
@@ -500,7 +530,7 @@ const ProductFullDetail = props => {
|
|
|
500
530
|
<div
|
|
501
531
|
className={cn(
|
|
502
532
|
'product_short_description',
|
|
503
|
-
'mb-
|
|
533
|
+
'mb-xs',
|
|
504
534
|
)}
|
|
505
535
|
>
|
|
506
536
|
{shortDescription}
|
|
@@ -514,7 +544,7 @@ const ProductFullDetail = props => {
|
|
|
514
544
|
<div
|
|
515
545
|
className={cn(
|
|
516
546
|
'product_price_container',
|
|
517
|
-
'py-
|
|
547
|
+
'py-xs flex justify-between flex-wrap gap-y-5'
|
|
518
548
|
)}
|
|
519
549
|
>
|
|
520
550
|
<div className='flex flex-col gap-y-5 max-w-[448px]'>
|
|
@@ -528,10 +558,11 @@ const ProductFullDetail = props => {
|
|
|
528
558
|
>
|
|
529
559
|
<Price
|
|
530
560
|
currencyCode={productDetails.price.currency}
|
|
561
|
+
priceRange={productDetails.price_range}
|
|
531
562
|
value={productDetails.price.value}
|
|
532
563
|
/>
|
|
533
564
|
</p>
|
|
534
|
-
<p
|
|
565
|
+
{/* <p
|
|
535
566
|
data-cy="ProductFullDetail-productOldPrice"
|
|
536
567
|
className={cn(
|
|
537
568
|
classes.productPrice,
|
|
@@ -542,7 +573,7 @@ const ProductFullDetail = props => {
|
|
|
542
573
|
currencyCode={productDetails.price.currency}
|
|
543
574
|
value={productDetails.price.value}
|
|
544
575
|
/>
|
|
545
|
-
</p>
|
|
576
|
+
</p> */}
|
|
546
577
|
</div>
|
|
547
578
|
<AuctionDetail className="auction_detail-container" />
|
|
548
579
|
<PreorderDetail className={'preorder_detail-container'} />
|
|
@@ -564,7 +595,7 @@ const ProductFullDetail = props => {
|
|
|
564
595
|
errors={errors.get('form') || []}
|
|
565
596
|
/>
|
|
566
597
|
<section className={classes.options}>{options}</section>
|
|
567
|
-
<section className={cn(classes.quantity, 'py-
|
|
598
|
+
<section className={cn(classes.quantity, 'py-xs !border-none')}>
|
|
568
599
|
{/* <span
|
|
569
600
|
data-cy="ProductFullDetail-quantityTitle"
|
|
570
601
|
className={classes.quantityTitle}
|
|
@@ -579,7 +610,7 @@ const ProductFullDetail = props => {
|
|
|
579
610
|
message={errors.get('quantity')}
|
|
580
611
|
/>
|
|
581
612
|
|
|
582
|
-
<div className='product_shipping-information mb-[30px] leading-[18px] mt-[25px]'>
|
|
613
|
+
{/* <div className='product_shipping-information mb-[30px] leading-[18px] mt-[25px]'>
|
|
583
614
|
{sellerDetails &&
|
|
584
615
|
<div className='text-xs'>
|
|
585
616
|
Ship From <span className='font-medium'>{sellerDetails.country}</span>
|
|
@@ -590,48 +621,19 @@ const ProductFullDetail = props => {
|
|
|
590
621
|
<div className='text-xs'>
|
|
591
622
|
Shiping Method <span className='font-medium'>Store Pick Up | Meet Up</span>
|
|
592
623
|
</div>
|
|
593
|
-
</div>
|
|
624
|
+
</div> */}
|
|
594
625
|
|
|
595
626
|
<div className='product_actions-wrapper'>
|
|
596
627
|
{cartActionContent}
|
|
597
628
|
</div>
|
|
598
629
|
</section>
|
|
599
|
-
<Divider />
|
|
630
|
+
{/* <Divider />
|
|
600
631
|
<section className={cn(classes.quantity, 'py-[30px] !border-none')}>
|
|
601
632
|
<RFQ />
|
|
602
|
-
</section>
|
|
603
|
-
<Divider />
|
|
604
|
-
<section className='product_from-other-platform py-[30px]'>
|
|
605
|
-
<div className='text-sm flex mb-5'>
|
|
606
|
-
Also available in
|
|
607
|
-
</div>
|
|
608
|
-
<div className='platform-container flex gap-x-2.5'>
|
|
609
|
-
<Link
|
|
610
|
-
className='platform_logo-wrapper rounded-md px-5 py-2.5 border border-gray-100 flex items-center'
|
|
611
|
-
to=""
|
|
612
|
-
>
|
|
613
|
-
<img
|
|
614
|
-
alt="tokopedia"
|
|
615
|
-
width={60}
|
|
616
|
-
height={30}
|
|
617
|
-
src={'https://upload.wikimedia.org/wikipedia/commons/a/a7/Tokopedia.svg'}
|
|
618
|
-
/>
|
|
619
|
-
</Link>
|
|
620
|
-
<Link
|
|
621
|
-
className='platform_logo-wrapper rounded-md px-5 py-2.5 border border-gray-100 flex items-center'
|
|
622
|
-
to=""
|
|
623
|
-
>
|
|
624
|
-
<img
|
|
625
|
-
alt="shopee"
|
|
626
|
-
width={60}
|
|
627
|
-
height={30}
|
|
628
|
-
src={'https://upload.wikimedia.org/wikipedia/commons/f/fe/Shopee.svg'}
|
|
629
|
-
/>
|
|
630
|
-
</Link>
|
|
631
|
-
</div>
|
|
632
|
-
</section>
|
|
633
|
+
</section> */}
|
|
633
634
|
<Divider />
|
|
634
|
-
<
|
|
635
|
+
<LinkToOtherStores productDetails={productDetails} />
|
|
636
|
+
<section className='seller-information py-xs'>
|
|
635
637
|
<div className='flex xs_flex-col md_flex-row xs_items-center md_items-start items-start gap-[15px] relative'>
|
|
636
638
|
<div className='flex flex-row justify-between relative w-full'>
|
|
637
639
|
<div className='flex flex-col xs_items-center md_items-start gap-[6px] relative'>
|
|
@@ -644,7 +646,7 @@ const ProductFullDetail = props => {
|
|
|
644
646
|
</div>
|
|
645
647
|
</div>
|
|
646
648
|
<div class="relative w-fit font-normal text-[#999999] text-[12px] tracking-[0] leading-[14px] whitespace-nowrap">
|
|
647
|
-
{sellerDetails ? sellerDetails
|
|
649
|
+
{sellerDetails ? getSellerAddressDisplay(sellerDetails) : ''}
|
|
648
650
|
</div>
|
|
649
651
|
</div>
|
|
650
652
|
<div className='flex flex-wrap items-start gap-4 relative'>
|
|
@@ -32,10 +32,12 @@ const IMAGE_WIDTH = 495;
|
|
|
32
32
|
* @returns {React.Element} React carousel component that displays a product image
|
|
33
33
|
*/
|
|
34
34
|
const ProductImageCarousel = props => {
|
|
35
|
-
const { images } = props;
|
|
35
|
+
const { images, selectedMedia, hoveredMedia } = props;
|
|
36
36
|
const { formatMessage } = useIntl();
|
|
37
37
|
const talonProps = useProductImageCarousel({
|
|
38
38
|
images,
|
|
39
|
+
selectedMedia,
|
|
40
|
+
hoveredMedia,
|
|
39
41
|
imageWidth: IMAGE_WIDTH
|
|
40
42
|
});
|
|
41
43
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { FormattedMessage } from 'react-intl';
|
|
3
|
+
import {
|
|
4
|
+
arrayOf,
|
|
5
|
+
func,
|
|
6
|
+
number,
|
|
7
|
+
object,
|
|
8
|
+
oneOfType,
|
|
9
|
+
shape,
|
|
10
|
+
string
|
|
11
|
+
} from 'prop-types';
|
|
12
|
+
|
|
13
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
14
|
+
import getOptionType from '@magento/venia-ui/lib/components/ProductOptions/getOptionType';
|
|
15
|
+
import SwatchList from '@magento/venia-ui/lib/components/ProductOptions/swatchList';
|
|
16
|
+
import TileList from './tileList';
|
|
17
|
+
import defaultClasses from './option.module.css';
|
|
18
|
+
import { useOption } from '@magento/peregrine/lib/talons/ProductOptions/useOption';
|
|
19
|
+
|
|
20
|
+
const getItemKey = ({ value_index }) => value_index;
|
|
21
|
+
|
|
22
|
+
// TODO: get an explicit field from the API
|
|
23
|
+
// that identifies an attribute as a swatch
|
|
24
|
+
const getListComponent = (attribute_code, values) => {
|
|
25
|
+
const optionType = getOptionType({ attribute_code, values });
|
|
26
|
+
|
|
27
|
+
return optionType === 'swatch' ? SwatchList : TileList;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Option = props => {
|
|
31
|
+
const {
|
|
32
|
+
attribute_code,
|
|
33
|
+
attribute_id,
|
|
34
|
+
label,
|
|
35
|
+
onSelectionChange,
|
|
36
|
+
selectedValue,
|
|
37
|
+
values,
|
|
38
|
+
variants,
|
|
39
|
+
setHoveredMedia,
|
|
40
|
+
isEverythingOutOfStock,
|
|
41
|
+
outOfStockVariants
|
|
42
|
+
} = props;
|
|
43
|
+
|
|
44
|
+
const talonProps = useOption({
|
|
45
|
+
attribute_id,
|
|
46
|
+
attribute_code,
|
|
47
|
+
label,
|
|
48
|
+
variants,
|
|
49
|
+
onSelectionChange,
|
|
50
|
+
selectedValue,
|
|
51
|
+
values
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
handleSelectionChange,
|
|
56
|
+
initialSelection,
|
|
57
|
+
selectedValueDescription,
|
|
58
|
+
filteredVariants
|
|
59
|
+
} = talonProps;
|
|
60
|
+
|
|
61
|
+
const ValueList = useMemo(() => getListComponent(attribute_code, values), [
|
|
62
|
+
attribute_code,
|
|
63
|
+
values
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={classes.root} data-cy="ProductOptions-Option-root">
|
|
70
|
+
<span className={classes.title}>{label}</span>
|
|
71
|
+
<ValueList
|
|
72
|
+
getItemKey={getItemKey}
|
|
73
|
+
selectedValue={initialSelection}
|
|
74
|
+
items={values}
|
|
75
|
+
setHoveredMedia={setHoveredMedia}
|
|
76
|
+
onSelectionChange={handleSelectionChange}
|
|
77
|
+
isEverythingOutOfStock={isEverythingOutOfStock}
|
|
78
|
+
outOfStockVariants={outOfStockVariants}
|
|
79
|
+
filteredVariants={filteredVariants}
|
|
80
|
+
attributeLabel={label}
|
|
81
|
+
/>
|
|
82
|
+
<dl className={classes.selection} style={{ display: 'none' }}>
|
|
83
|
+
<dt
|
|
84
|
+
data-cy="ProductOptions-Option-selectedLabel"
|
|
85
|
+
className={classes.selectionLabel}
|
|
86
|
+
>
|
|
87
|
+
<FormattedMessage
|
|
88
|
+
id="productOptions.selectedLabel"
|
|
89
|
+
defaultMessage="Selected {label}:"
|
|
90
|
+
values={{ label }}
|
|
91
|
+
/>
|
|
92
|
+
</dt>
|
|
93
|
+
<dd>{selectedValueDescription}</dd>
|
|
94
|
+
</dl>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
Option.propTypes = {
|
|
100
|
+
attribute_code: string.isRequired,
|
|
101
|
+
attribute_id: string,
|
|
102
|
+
classes: shape({
|
|
103
|
+
root: string,
|
|
104
|
+
title: string
|
|
105
|
+
}),
|
|
106
|
+
label: string.isRequired,
|
|
107
|
+
onSelectionChange: func,
|
|
108
|
+
selectedValue: oneOfType([number, string]),
|
|
109
|
+
values: arrayOf(object).isRequired
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default Option;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
composes: border-b from global;
|
|
3
|
+
composes: border-solid from global;
|
|
4
|
+
/* composes: border-subtle from global; */
|
|
5
|
+
/* composes: mx-sm from global; */
|
|
6
|
+
composes: my-0 from global;
|
|
7
|
+
composes: px-0 from global;
|
|
8
|
+
composes: py-xs from global;
|
|
9
|
+
|
|
10
|
+
border-color: rgb(230 233 234);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.title {
|
|
14
|
+
composes: block from global;
|
|
15
|
+
composes: font-semibold from global;
|
|
16
|
+
composes: leading-normal from global;
|
|
17
|
+
composes: mb-xs from global;
|
|
18
|
+
composes: text-colorDefault from global;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.selection {
|
|
22
|
+
composes: flex from global;
|
|
23
|
+
composes: leading-normal from global;
|
|
24
|
+
composes: mt-xs from global;
|
|
25
|
+
composes: text-colorDefault from global;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.selectionLabel {
|
|
29
|
+
composes: mr-xs from global;
|
|
30
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { array, func } from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import Option from './option';
|
|
5
|
+
import { useOptions } from '@magento/peregrine/lib/talons/ProductOptions/useOptions';
|
|
6
|
+
|
|
7
|
+
const Options = props => {
|
|
8
|
+
const {
|
|
9
|
+
classes,
|
|
10
|
+
onSelectionChange,
|
|
11
|
+
options,
|
|
12
|
+
variants,
|
|
13
|
+
setHoveredMedia,
|
|
14
|
+
selectedValues = [],
|
|
15
|
+
isEverythingOutOfStock,
|
|
16
|
+
outOfStockVariants
|
|
17
|
+
} = props;
|
|
18
|
+
|
|
19
|
+
const talonProps = useOptions({
|
|
20
|
+
onSelectionChange,
|
|
21
|
+
selectedValues,
|
|
22
|
+
options
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { handleSelectionChange, selectedValueMap } = talonProps;
|
|
26
|
+
|
|
27
|
+
// Render a list of options passing in any pre-selected values.
|
|
28
|
+
return options.map(option => (
|
|
29
|
+
<Option
|
|
30
|
+
{...option}
|
|
31
|
+
classes={classes}
|
|
32
|
+
key={option.attribute_id}
|
|
33
|
+
variants={variants}
|
|
34
|
+
setHoveredMedia={setHoveredMedia}
|
|
35
|
+
onSelectionChange={handleSelectionChange}
|
|
36
|
+
selectedValue={selectedValueMap.get(option.label)}
|
|
37
|
+
isEverythingOutOfStock={isEverythingOutOfStock}
|
|
38
|
+
outOfStockVariants={outOfStockVariants}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
Options.propTypes = {
|
|
44
|
+
onSelectionChange: func,
|
|
45
|
+
options: array.isRequired,
|
|
46
|
+
selectedValues: array
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default Options;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { bool, func, number, oneOfType, shape, string } from 'prop-types';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
5
|
+
import defaultClasses from './tile.module.css';
|
|
6
|
+
import { useTile } from '@magento/peregrine/lib/talons/ProductOptions/useTile';
|
|
7
|
+
import Image from '@magento/venia-ui/lib/components/Image';
|
|
8
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
9
|
+
|
|
10
|
+
const getClassName = (
|
|
11
|
+
name,
|
|
12
|
+
isSelected,
|
|
13
|
+
hasFocus,
|
|
14
|
+
isOptionOutOfStock,
|
|
15
|
+
isEverythingOutOfStock
|
|
16
|
+
) =>
|
|
17
|
+
`${name}${isSelected ? '_selected' : ''}${hasFocus ? '_focused' : ''}${
|
|
18
|
+
isEverythingOutOfStock || isOptionOutOfStock ? '_outOfStock' : ''
|
|
19
|
+
}`;
|
|
20
|
+
|
|
21
|
+
const Tile = props => {
|
|
22
|
+
const {
|
|
23
|
+
hasFocus,
|
|
24
|
+
isSelected,
|
|
25
|
+
item: { label, value_index },
|
|
26
|
+
onClick,
|
|
27
|
+
isEverythingOutOfStock,
|
|
28
|
+
isOptionOutOfStock,
|
|
29
|
+
setHoveredMedia,
|
|
30
|
+
itemVariant
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const talonProps = useTile({
|
|
34
|
+
onClick,
|
|
35
|
+
itemVariant,
|
|
36
|
+
setHoveredMedia,
|
|
37
|
+
value_index
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const { handleMouseLeave, handleMouseEnter, handleClick, variantImg } = talonProps;
|
|
41
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
42
|
+
const className =
|
|
43
|
+
classes[
|
|
44
|
+
getClassName(
|
|
45
|
+
'root',
|
|
46
|
+
isSelected,
|
|
47
|
+
hasFocus,
|
|
48
|
+
isOptionOutOfStock,
|
|
49
|
+
isEverythingOutOfStock
|
|
50
|
+
)
|
|
51
|
+
];
|
|
52
|
+
const { formatMessage } = useIntl();
|
|
53
|
+
const ariaLabelView = formatMessage(
|
|
54
|
+
{
|
|
55
|
+
id: 'ProductOptions.productSize',
|
|
56
|
+
defaultMessage: 'Fashion size {label}'
|
|
57
|
+
},
|
|
58
|
+
{ label: label }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const ariaLabelSelected = formatMessage(
|
|
62
|
+
{
|
|
63
|
+
id: 'productOptions.selectedSize',
|
|
64
|
+
defaultMessage: 'Fashion size {label} button Selected'
|
|
65
|
+
},
|
|
66
|
+
{ label: label }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const result = isSelected ? ariaLabelSelected : ariaLabelView;
|
|
70
|
+
|
|
71
|
+
// variantImg
|
|
72
|
+
let image = (
|
|
73
|
+
<Image
|
|
74
|
+
alt=''
|
|
75
|
+
classes={{
|
|
76
|
+
root: classes.variantImageSwatch
|
|
77
|
+
}}
|
|
78
|
+
resource={variantImg}
|
|
79
|
+
width={24}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button
|
|
85
|
+
className={className}
|
|
86
|
+
onClick={handleClick}
|
|
87
|
+
onMouseEnter={handleMouseEnter}
|
|
88
|
+
onFocus={handleMouseEnter}
|
|
89
|
+
onTouchStart={handleMouseEnter}
|
|
90
|
+
onMouseLeave={handleMouseLeave}
|
|
91
|
+
title={label}
|
|
92
|
+
type="button"
|
|
93
|
+
data-cy="Tile-button"
|
|
94
|
+
aria-label={result}
|
|
95
|
+
disabled={isEverythingOutOfStock || isOptionOutOfStock}
|
|
96
|
+
>
|
|
97
|
+
{image}
|
|
98
|
+
<span className={classes.label}>{label}</span>
|
|
99
|
+
</button>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default Tile;
|
|
104
|
+
|
|
105
|
+
Tile.propTypes = {
|
|
106
|
+
hasFocus: bool,
|
|
107
|
+
isSelected: bool,
|
|
108
|
+
item: shape({
|
|
109
|
+
label: string.isRequired,
|
|
110
|
+
value_index: oneOfType([number, string]).isRequired
|
|
111
|
+
}).isRequired,
|
|
112
|
+
onClick: func.isRequired
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
Tile.defaultProps = {
|
|
116
|
+
hasFocus: false,
|
|
117
|
+
isSelected: false
|
|
118
|
+
};
|