@faststore/components 3.68.0 → 3.78.0
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/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.d.ts.map +1 -1
- package/dist/cjs/hooks/index.js +3 -1
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useProductComparison.d.ts +2 -0
- package/dist/cjs/hooks/useProductComparison.d.ts.map +1 -0
- package/dist/cjs/hooks/useProductComparison.js +14 -0
- package/dist/cjs/hooks/useProductComparison.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/molecules/ToggleField/ToggleField.d.ts +2 -1
- package/dist/cjs/molecules/ToggleField/ToggleField.d.ts.map +1 -1
- package/dist/cjs/molecules/ToggleField/ToggleField.js.map +1 -1
- package/dist/cjs/organisms/ProductComparison/ProductComparison.d.ts +7 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparison.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparison.js +11 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparison.js.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.d.ts +79 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.js +135 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.js.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.d.ts +9 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.js +25 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.js.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.d.ts +11 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.js +18 -0
- package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.js.map +1 -0
- package/dist/cjs/organisms/ProductComparison/index.d.ts +11 -0
- package/dist/cjs/organisms/ProductComparison/index.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/index.js +17 -0
- package/dist/cjs/organisms/ProductComparison/index.js.map +1 -0
- package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts +75 -0
- package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts.map +1 -0
- package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.js +50 -0
- package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.js.map +1 -0
- package/dist/cjs/organisms/SKUMatrix/SKUMatrixSidebar.d.ts +2 -2
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useProductComparison.d.ts +2 -0
- package/dist/esm/hooks/useProductComparison.d.ts.map +1 -0
- package/dist/esm/hooks/useProductComparison.js +10 -0
- package/dist/esm/hooks/useProductComparison.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/molecules/ToggleField/ToggleField.d.ts +2 -1
- package/dist/esm/molecules/ToggleField/ToggleField.d.ts.map +1 -1
- package/dist/esm/molecules/ToggleField/ToggleField.js.map +1 -1
- package/dist/esm/organisms/ProductComparison/ProductComparison.d.ts +7 -0
- package/dist/esm/organisms/ProductComparison/ProductComparison.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparison.js +8 -0
- package/dist/esm/organisms/ProductComparison/ProductComparison.js.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.d.ts +79 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.js +132 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.js.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.d.ts +9 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.js +22 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.js.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.d.ts +11 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.js +15 -0
- package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.js.map +1 -0
- package/dist/esm/organisms/ProductComparison/index.d.ts +11 -0
- package/dist/esm/organisms/ProductComparison/index.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/index.js +6 -0
- package/dist/esm/organisms/ProductComparison/index.js.map +1 -0
- package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts +75 -0
- package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts.map +1 -0
- package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.js +46 -0
- package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.js.map +1 -0
- package/dist/esm/organisms/SKUMatrix/SKUMatrixSidebar.d.ts +2 -2
- package/package.json +2 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useProductComparison.ts +15 -0
- package/src/index.ts +14 -0
- package/src/molecules/ToggleField/ToggleField.tsx +2 -2
- package/src/organisms/ProductComparison/ProductComparison.tsx +24 -0
- package/src/organisms/ProductComparison/ProductComparisonSidebar.tsx +452 -0
- package/src/organisms/ProductComparison/ProductComparisonToolbar.tsx +78 -0
- package/src/organisms/ProductComparison/ProductComparisonTrigger.tsx +49 -0
- package/src/organisms/ProductComparison/index.ts +14 -0
- package/src/organisms/ProductComparison/provider/ProductComparisonProvider.tsx +144 -0
- package/src/organisms/SKUMatrix/SKUMatrixSidebar.tsx +2 -2
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
type FunctionComponent,
|
|
3
|
+
Suspense,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import { useFadeEffect, useProductComparison } from '../../hooks'
|
|
9
|
+
import SlideOver, { SlideOverHeader, type SlideOverProps } from '../SlideOver'
|
|
10
|
+
import type { IProductComparison } from '.'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
Table,
|
|
14
|
+
TableBody,
|
|
15
|
+
TableCell,
|
|
16
|
+
TableHead,
|
|
17
|
+
TableRow,
|
|
18
|
+
} from '../../molecules/Table'
|
|
19
|
+
import ToggleField from '../../molecules/ToggleField'
|
|
20
|
+
|
|
21
|
+
import Badge from '../../atoms/Badge'
|
|
22
|
+
import Button from '../../atoms/Button'
|
|
23
|
+
import Select from '../../atoms/Select'
|
|
24
|
+
import Price, { type PriceFormatter } from '../../atoms/Price'
|
|
25
|
+
import ProductCard, {
|
|
26
|
+
ProductCardContent,
|
|
27
|
+
ProductCardImage,
|
|
28
|
+
} from '../../molecules/ProductCard'
|
|
29
|
+
import Dropdown, {
|
|
30
|
+
DropdownButton,
|
|
31
|
+
DropdownMenu,
|
|
32
|
+
DropdownItem,
|
|
33
|
+
} from '../../molecules/Dropdown'
|
|
34
|
+
import Icon from '../../atoms/Icon'
|
|
35
|
+
|
|
36
|
+
const SPECIFICATION = 'SPECIFICATION'
|
|
37
|
+
|
|
38
|
+
export interface SortOptions {
|
|
39
|
+
label: string
|
|
40
|
+
value: string
|
|
41
|
+
onChange: (productComparison: IProductComparison[]) => IProductComparison[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ImageComponentType = FunctionComponent<{
|
|
45
|
+
src: string
|
|
46
|
+
alt: string
|
|
47
|
+
width?: number
|
|
48
|
+
height?: number
|
|
49
|
+
}>
|
|
50
|
+
|
|
51
|
+
export interface ProductComparisonSidebarProps
|
|
52
|
+
extends Omit<SlideOverProps, 'children' | 'isOpen' | 'setIsOpen' | 'fade'> {
|
|
53
|
+
/**
|
|
54
|
+
* Defines the title of the SlideOver.
|
|
55
|
+
*/
|
|
56
|
+
title: string
|
|
57
|
+
/**
|
|
58
|
+
* Defines the sort label.
|
|
59
|
+
*/
|
|
60
|
+
sortLabel: string
|
|
61
|
+
/**
|
|
62
|
+
* Defines the filter label.
|
|
63
|
+
*/
|
|
64
|
+
filterLabel: string
|
|
65
|
+
/**
|
|
66
|
+
* Defines the product name label.
|
|
67
|
+
*/
|
|
68
|
+
productNameFilterLabel: string
|
|
69
|
+
/**
|
|
70
|
+
* Defines the toggle field label.
|
|
71
|
+
*/
|
|
72
|
+
toggleFieldLabel: string
|
|
73
|
+
/**
|
|
74
|
+
* Defines the price label.
|
|
75
|
+
*/
|
|
76
|
+
priceLabel: string
|
|
77
|
+
/**
|
|
78
|
+
* Defines the preference label.
|
|
79
|
+
*/
|
|
80
|
+
preferencesLabel: string
|
|
81
|
+
/**
|
|
82
|
+
* Defines the label from add to cart button.
|
|
83
|
+
*/
|
|
84
|
+
cartButtonLabel: string
|
|
85
|
+
/**
|
|
86
|
+
* Defines the label for the price including taxes.
|
|
87
|
+
*/
|
|
88
|
+
priceWithTaxLabel: string
|
|
89
|
+
/**
|
|
90
|
+
* Formatter function that transforms the raw price value and render the result.
|
|
91
|
+
*/
|
|
92
|
+
priceFormatter: PriceFormatter
|
|
93
|
+
/**
|
|
94
|
+
* Custom labels to introducing about products.
|
|
95
|
+
*/
|
|
96
|
+
technicalInformation?: {
|
|
97
|
+
title: string
|
|
98
|
+
description: string
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Custom function to sort the products.
|
|
102
|
+
*/
|
|
103
|
+
sortOptions?: SortOptions[]
|
|
104
|
+
/**
|
|
105
|
+
* Function to select the product that will be added to the cart.
|
|
106
|
+
*/
|
|
107
|
+
handleProductToBuy: (productId: string) => void
|
|
108
|
+
/**
|
|
109
|
+
* Event to handle the click on the add to cart button.
|
|
110
|
+
*/
|
|
111
|
+
setPendingEvent: (event: React.MouseEvent<HTMLButtonElement>) => void
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const ImageComponent: ImageComponentType = ({ src, alt, ...otherProps }) => (
|
|
115
|
+
<img src={src} alt={alt} {...otherProps} />
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
function useProductSpecifications(
|
|
119
|
+
products: IProductComparison[],
|
|
120
|
+
productSorted: IProductComparison[],
|
|
121
|
+
showOnlyDifferences: boolean
|
|
122
|
+
) {
|
|
123
|
+
return useMemo(() => {
|
|
124
|
+
const firstProduct = products[0]
|
|
125
|
+
const allSpecs = getAllSpecifications(firstProduct)
|
|
126
|
+
const diffSpecs = getDifferences(firstProduct, productSorted)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
specsToShow: showOnlyDifferences ? diffSpecs : allSpecs,
|
|
130
|
+
allSpecs,
|
|
131
|
+
diffSpecs,
|
|
132
|
+
}
|
|
133
|
+
}, [products, productSorted, showOnlyDifferences])
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function ProductComparisonSidebar({
|
|
137
|
+
title,
|
|
138
|
+
sortLabel,
|
|
139
|
+
filterLabel,
|
|
140
|
+
preferencesLabel,
|
|
141
|
+
productNameFilterLabel,
|
|
142
|
+
toggleFieldLabel,
|
|
143
|
+
priceLabel,
|
|
144
|
+
cartButtonLabel,
|
|
145
|
+
priceWithTaxLabel,
|
|
146
|
+
technicalInformation,
|
|
147
|
+
size = 'partial',
|
|
148
|
+
direction = 'rightSide',
|
|
149
|
+
priceFormatter,
|
|
150
|
+
overlayProps,
|
|
151
|
+
sortOptions,
|
|
152
|
+
handleProductToBuy,
|
|
153
|
+
setPendingEvent,
|
|
154
|
+
...otherProps
|
|
155
|
+
}: ProductComparisonSidebarProps) {
|
|
156
|
+
const { fade } = useFadeEffect()
|
|
157
|
+
const { isOpen, setIsOpen, products } = useProductComparison()
|
|
158
|
+
const [showTechnicalInfo, setShowTechnicalInfo] = useState<boolean>(true)
|
|
159
|
+
|
|
160
|
+
const [selectedFilter, setSelectedFilter] =
|
|
161
|
+
useState<SortOptions['value']>('productByName')
|
|
162
|
+
const [showOnlyDifferences, setShowOnlyDifferences] = useState(false)
|
|
163
|
+
|
|
164
|
+
const handleClickAddCart = (
|
|
165
|
+
event: React.MouseEvent<HTMLButtonElement>,
|
|
166
|
+
product: IProductComparison
|
|
167
|
+
) => {
|
|
168
|
+
event.preventDefault()
|
|
169
|
+
handleProductToBuy(product.id)
|
|
170
|
+
setPendingEvent(event)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const productSorted = useMemo(
|
|
174
|
+
() =>
|
|
175
|
+
sortOptions
|
|
176
|
+
?.find((option) => option.value === selectedFilter)
|
|
177
|
+
?.onChange(products) ?? products,
|
|
178
|
+
[selectedFilter, products]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const { specsToShow } = useProductSpecifications(
|
|
182
|
+
products,
|
|
183
|
+
productSorted,
|
|
184
|
+
showOnlyDifferences
|
|
185
|
+
)
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (isOpen) {
|
|
188
|
+
// Prevent scrolling when the drawer is open
|
|
189
|
+
document.body.style.overflow = 'hidden'
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Restore scrolling when the drawer is closed
|
|
194
|
+
document.body.style.overflow = ''
|
|
195
|
+
}, [isOpen])
|
|
196
|
+
|
|
197
|
+
const options = useMemo(
|
|
198
|
+
() =>
|
|
199
|
+
sortOptions?.reduce(
|
|
200
|
+
(acc: Record<string, string>, option) => {
|
|
201
|
+
acc[option.value] = option.label
|
|
202
|
+
return acc
|
|
203
|
+
},
|
|
204
|
+
{} as Record<string, string>
|
|
205
|
+
) || {},
|
|
206
|
+
[sortOptions]
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
function toggleInfo() {
|
|
210
|
+
setShowTechnicalInfo(!showTechnicalInfo)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<SlideOver
|
|
215
|
+
data-fs-product-comparison-sidebar
|
|
216
|
+
fade={fade}
|
|
217
|
+
size={size}
|
|
218
|
+
direction={direction}
|
|
219
|
+
isOpen={isOpen}
|
|
220
|
+
overlayProps={overlayProps}
|
|
221
|
+
{...otherProps}
|
|
222
|
+
>
|
|
223
|
+
<SlideOverHeader onClose={() => setIsOpen(false)}>
|
|
224
|
+
<div>
|
|
225
|
+
<h2 data-fs-product-comparison-sidebar-header-title>{title}</h2>
|
|
226
|
+
<Badge size="big" variant="neutral">
|
|
227
|
+
{products.length}
|
|
228
|
+
</Badge>
|
|
229
|
+
</div>
|
|
230
|
+
</SlideOverHeader>
|
|
231
|
+
|
|
232
|
+
<div data-fs-product-comparison-filters>
|
|
233
|
+
<div data-fs-product-comparison-container>
|
|
234
|
+
<p data-fs-product-comparison-filters-sort-label>{sortLabel}</p>
|
|
235
|
+
<Select
|
|
236
|
+
id="product-comparison-sort-by"
|
|
237
|
+
options={options}
|
|
238
|
+
value={selectedFilter}
|
|
239
|
+
onChange={(event) =>
|
|
240
|
+
setSelectedFilter(event.target.value as SortOptions['value'])
|
|
241
|
+
}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
<ToggleField
|
|
245
|
+
id="product-comparison-show-differences"
|
|
246
|
+
label={toggleFieldLabel}
|
|
247
|
+
checked={showOnlyDifferences}
|
|
248
|
+
onChange={() => setShowOnlyDifferences(!showOnlyDifferences)}
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<Suspense>
|
|
253
|
+
<Dropdown>
|
|
254
|
+
<DropdownButton data-fs-product-comparison-dropdown-button>
|
|
255
|
+
{filterLabel}
|
|
256
|
+
</DropdownButton>
|
|
257
|
+
<DropdownMenu className={overlayProps?.className}>
|
|
258
|
+
<div data-fs-product-comparison-dropdown-menu-content>
|
|
259
|
+
<DropdownItem asChild dismissOnClick={false}>
|
|
260
|
+
<div data-fs-product-comparison-dropdown-item-filter-type>
|
|
261
|
+
<span
|
|
262
|
+
data-fs-product-comparison-dropdown-item-filter-type-text
|
|
263
|
+
>
|
|
264
|
+
{preferencesLabel}
|
|
265
|
+
</span>
|
|
266
|
+
</div>
|
|
267
|
+
</DropdownItem>
|
|
268
|
+
|
|
269
|
+
<DropdownItem
|
|
270
|
+
dismissOnClick={false}
|
|
271
|
+
asChild
|
|
272
|
+
data-fs-product-comparison-toggle-field-mobile
|
|
273
|
+
>
|
|
274
|
+
<ToggleField
|
|
275
|
+
id="product-comparison-show-differences"
|
|
276
|
+
label={toggleFieldLabel}
|
|
277
|
+
checked={showOnlyDifferences}
|
|
278
|
+
onChange={() => setShowOnlyDifferences(!showOnlyDifferences)}
|
|
279
|
+
/>
|
|
280
|
+
</DropdownItem>
|
|
281
|
+
<DropdownItem asChild dismissOnClick={false}>
|
|
282
|
+
<div data-fs-product-comparison-dropdown-item-filter-type>
|
|
283
|
+
<span
|
|
284
|
+
data-fs-product-comparison-dropdown-item-filter-type-text
|
|
285
|
+
>
|
|
286
|
+
{sortLabel}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
</DropdownItem>
|
|
290
|
+
<DropdownItem
|
|
291
|
+
dismissOnClick={false}
|
|
292
|
+
onClick={() => setSelectedFilter('productByName')}
|
|
293
|
+
data-fs-dropdown-filter-selected={
|
|
294
|
+
selectedFilter === 'productByName' ? true : undefined
|
|
295
|
+
}
|
|
296
|
+
>
|
|
297
|
+
{selectedFilter === 'productByName' && (
|
|
298
|
+
<Icon name="Checked" width={16} height={16} />
|
|
299
|
+
)}
|
|
300
|
+
<p>{productNameFilterLabel}</p>
|
|
301
|
+
</DropdownItem>
|
|
302
|
+
<DropdownItem
|
|
303
|
+
dismissOnClick={false}
|
|
304
|
+
onClick={() => setSelectedFilter('productByPrice')}
|
|
305
|
+
data-fs-dropdown-filter-selected={
|
|
306
|
+
selectedFilter === 'productByPrice' ? true : undefined
|
|
307
|
+
}
|
|
308
|
+
>
|
|
309
|
+
{selectedFilter === 'productByPrice' && (
|
|
310
|
+
<Icon name="Checked" width={16} height={16} />
|
|
311
|
+
)}
|
|
312
|
+
<p>{priceLabel}</p>
|
|
313
|
+
</DropdownItem>
|
|
314
|
+
</div>
|
|
315
|
+
</DropdownMenu>
|
|
316
|
+
</Dropdown>
|
|
317
|
+
</Suspense>
|
|
318
|
+
|
|
319
|
+
<Table>
|
|
320
|
+
<TableHead>
|
|
321
|
+
<TableRow>
|
|
322
|
+
{products.map((product) => {
|
|
323
|
+
return (
|
|
324
|
+
<TableCell key={product.id}>
|
|
325
|
+
<ProductCard>
|
|
326
|
+
<ProductCardImage aspectRatio={1}>
|
|
327
|
+
<ImageComponent
|
|
328
|
+
src={product.image[0]?.url}
|
|
329
|
+
alt={product.image[0]?.alternateName}
|
|
330
|
+
/>
|
|
331
|
+
</ProductCardImage>
|
|
332
|
+
|
|
333
|
+
<ProductCardContent
|
|
334
|
+
title={product.name}
|
|
335
|
+
outOfStock={
|
|
336
|
+
product.offers.offers[0].availability !==
|
|
337
|
+
'https://schema.org/InStock'
|
|
338
|
+
}
|
|
339
|
+
price={{
|
|
340
|
+
value: product.offers.offers[0].price,
|
|
341
|
+
listPrice: product.offers.offers[0].listPrice,
|
|
342
|
+
formatter: priceFormatter,
|
|
343
|
+
}}
|
|
344
|
+
buttonLabel={cartButtonLabel}
|
|
345
|
+
showDiscountBadge
|
|
346
|
+
/>
|
|
347
|
+
</ProductCard>
|
|
348
|
+
<Button
|
|
349
|
+
variant="tertiary"
|
|
350
|
+
size="small"
|
|
351
|
+
onClick={(event) => handleClickAddCart(event, product)}
|
|
352
|
+
>
|
|
353
|
+
{cartButtonLabel}
|
|
354
|
+
</Button>
|
|
355
|
+
</TableCell>
|
|
356
|
+
)
|
|
357
|
+
})}
|
|
358
|
+
</TableRow>
|
|
359
|
+
</TableHead>
|
|
360
|
+
|
|
361
|
+
<TableBody>
|
|
362
|
+
<TableRow data-fs-product-comparison-row-header>
|
|
363
|
+
<TableCell>
|
|
364
|
+
<Button
|
|
365
|
+
data-fs-product-comparison-row-header-button
|
|
366
|
+
aria-label="Toggle technical information"
|
|
367
|
+
size="small"
|
|
368
|
+
iconPosition="right"
|
|
369
|
+
icon={
|
|
370
|
+
<Icon name={showTechnicalInfo ? 'CaretUp' : 'CaretDown'} />
|
|
371
|
+
}
|
|
372
|
+
onClick={toggleInfo}
|
|
373
|
+
>
|
|
374
|
+
<h2 data-fs-product-comparison-row-header-button-title>
|
|
375
|
+
{technicalInformation?.title}
|
|
376
|
+
</h2>
|
|
377
|
+
<h3 data-fs-product-comparison-row-header-button-description>
|
|
378
|
+
{technicalInformation?.description}
|
|
379
|
+
</h3>
|
|
380
|
+
</Button>
|
|
381
|
+
</TableCell>
|
|
382
|
+
</TableRow>
|
|
383
|
+
|
|
384
|
+
{showTechnicalInfo && (
|
|
385
|
+
<>
|
|
386
|
+
<TableRow>
|
|
387
|
+
{productSorted.map((product) => (
|
|
388
|
+
<TableCell key={product.id}>
|
|
389
|
+
<span data-fs-product-comparison-row-label>
|
|
390
|
+
{priceWithTaxLabel}
|
|
391
|
+
</span>
|
|
392
|
+
<Price
|
|
393
|
+
data-fs-product-comparison-row-text
|
|
394
|
+
formatter={priceFormatter}
|
|
395
|
+
value={product.offers.lowPriceWithTaxes}
|
|
396
|
+
variant="selling"
|
|
397
|
+
/>
|
|
398
|
+
</TableCell>
|
|
399
|
+
))}
|
|
400
|
+
</TableRow>
|
|
401
|
+
|
|
402
|
+
{specsToShow?.map((spec) => (
|
|
403
|
+
<TableRow key={spec}>
|
|
404
|
+
{productSorted.map((product) => (
|
|
405
|
+
<TableCell key={product.id}>
|
|
406
|
+
<span data-fs-product-comparison-row-label>{spec}</span>
|
|
407
|
+
<p data-fs-product-comparison-row-text>
|
|
408
|
+
{product.additionalProperty.find(
|
|
409
|
+
(property) =>
|
|
410
|
+
property.name === spec &&
|
|
411
|
+
property.valueReference === SPECIFICATION
|
|
412
|
+
)?.value || '-'}
|
|
413
|
+
</p>
|
|
414
|
+
</TableCell>
|
|
415
|
+
))}
|
|
416
|
+
</TableRow>
|
|
417
|
+
))}
|
|
418
|
+
</>
|
|
419
|
+
)}
|
|
420
|
+
</TableBody>
|
|
421
|
+
</Table>
|
|
422
|
+
</SlideOver>
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const getAllSpecifications = (product: IProductComparison) => {
|
|
427
|
+
return product?.skuSpecifications?.map((spec) => spec.field) || []
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const getDifferences = (
|
|
431
|
+
product: IProductComparison,
|
|
432
|
+
productsSorted: IProductComparison[]
|
|
433
|
+
) => {
|
|
434
|
+
const allSpecifications = getAllSpecifications(product)
|
|
435
|
+
const productsSpecs = productsSorted.map((product) =>
|
|
436
|
+
product.additionalProperty.map((property) => [
|
|
437
|
+
property.name,
|
|
438
|
+
property.value || '',
|
|
439
|
+
])
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
allSpecifications.filter((spec) => {
|
|
444
|
+
const values = productsSpecs.map(
|
|
445
|
+
(product) => product.find((value) => value[0] === spec)?.[1]
|
|
446
|
+
)
|
|
447
|
+
return !values.every((value, _, array) => value === array[0])
|
|
448
|
+
}) || []
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export default ProductComparisonSidebar
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Button, Label } from '../..'
|
|
3
|
+
import { useProductComparison } from '../../hooks/useProductComparison'
|
|
4
|
+
import type { ImageComponentType } from './ProductComparisonSidebar'
|
|
5
|
+
|
|
6
|
+
export interface ProductComparisonToolbarProps {
|
|
7
|
+
/*
|
|
8
|
+
* Set the label for the warning message when products are selected.
|
|
9
|
+
*/
|
|
10
|
+
selectionWarningLabel?: string
|
|
11
|
+
/*
|
|
12
|
+
* Set the label for the compare button
|
|
13
|
+
*/
|
|
14
|
+
compareButtonLabel?: string
|
|
15
|
+
/*
|
|
16
|
+
* Set the label for the clear selection button
|
|
17
|
+
*/
|
|
18
|
+
clearSelectionButtonLabel?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ImageComponent: ImageComponentType = ({ src, alt, ...otherProps }) => (
|
|
22
|
+
<img src={src} alt={alt} {...otherProps} />
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
function ProductComparisonToolbar({
|
|
26
|
+
clearSelectionButtonLabel,
|
|
27
|
+
compareButtonLabel,
|
|
28
|
+
selectionWarningLabel,
|
|
29
|
+
}: ProductComparisonToolbarProps) {
|
|
30
|
+
const { isOpen, setIsOpen, products, clearProducts } = useProductComparison()
|
|
31
|
+
|
|
32
|
+
const selectedProductsDisplay = products.slice(0, 3)
|
|
33
|
+
|
|
34
|
+
if (!products.length && isOpen) return null
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<footer data-fs-product-comparison-toolbar>
|
|
38
|
+
<div data-fs-product-comparison-toolbar-image>
|
|
39
|
+
{selectedProductsDisplay.map((product) => (
|
|
40
|
+
<ImageComponent
|
|
41
|
+
key={product.id}
|
|
42
|
+
src={product.image[0].url}
|
|
43
|
+
alt={product.name}
|
|
44
|
+
width={60}
|
|
45
|
+
height={60}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
|
|
49
|
+
{products.length > 3 && (
|
|
50
|
+
<div data-fs-product-comparison-toolbar-image-more>
|
|
51
|
+
<p>{`+${products.length - 3}`}</p>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{products.length === 1 && (
|
|
56
|
+
<Label data-fs-product-comparison-selection-warning-label>
|
|
57
|
+
{selectionWarningLabel}
|
|
58
|
+
</Label>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<Button variant="tertiary" onClick={() => clearProducts()}>
|
|
63
|
+
{clearSelectionButtonLabel}
|
|
64
|
+
</Button>
|
|
65
|
+
<Button
|
|
66
|
+
variant="primary"
|
|
67
|
+
disabled={products.length < 2}
|
|
68
|
+
onClick={() => setIsOpen(true)}
|
|
69
|
+
>
|
|
70
|
+
{products.length > 1
|
|
71
|
+
? `${compareButtonLabel} ${products.length}`
|
|
72
|
+
: compareButtonLabel}
|
|
73
|
+
</Button>
|
|
74
|
+
</footer>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default ProductComparisonToolbar
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import type { IProductComparison } from '.'
|
|
3
|
+
import CheckboxField, {
|
|
4
|
+
type CheckboxFieldProps,
|
|
5
|
+
} from '../../molecules/CheckboxField'
|
|
6
|
+
import { useProductComparison } from '../../hooks/useProductComparison'
|
|
7
|
+
|
|
8
|
+
export type ProductComparisonTriggerProps = CheckboxFieldProps & {
|
|
9
|
+
/*
|
|
10
|
+
* The product to be compared.
|
|
11
|
+
*/
|
|
12
|
+
product: IProductComparison
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ProductComparisonTrigger = forwardRef<
|
|
16
|
+
HTMLInputElement,
|
|
17
|
+
ProductComparisonTriggerProps
|
|
18
|
+
>(function ProductComparisonTrigger(
|
|
19
|
+
{ id, label, onChange, product, ...otherProps },
|
|
20
|
+
ref
|
|
21
|
+
) {
|
|
22
|
+
const { productIds, handleProductsIds } = useProductComparison()
|
|
23
|
+
|
|
24
|
+
const isSelected = productIds.some((productId) => productId === product.id)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div data-fs-product-comparison-trigger>
|
|
28
|
+
{product.hasSpecifications && (
|
|
29
|
+
<CheckboxField
|
|
30
|
+
data-fs-product-comparison-trigger-checkbox-field
|
|
31
|
+
ref={ref}
|
|
32
|
+
id={`product-comparison-trigger-${id}`}
|
|
33
|
+
label={label}
|
|
34
|
+
checked={isSelected}
|
|
35
|
+
onClick={(event) => {
|
|
36
|
+
event.stopPropagation()
|
|
37
|
+
}}
|
|
38
|
+
onChange={(event) => {
|
|
39
|
+
onChange?.(event)
|
|
40
|
+
handleProductsIds(product)
|
|
41
|
+
}}
|
|
42
|
+
{...otherProps}
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default ProductComparisonTrigger
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { default } from './ProductComparison'
|
|
2
|
+
export type { ProductComparisonProps } from './ProductComparison'
|
|
3
|
+
|
|
4
|
+
export { default as ProductComparisonSidebar } from './ProductComparisonSidebar'
|
|
5
|
+
export type { ProductComparisonSidebarProps } from './ProductComparisonSidebar'
|
|
6
|
+
|
|
7
|
+
export { default as ProductComparisonToolbar } from './ProductComparisonToolbar'
|
|
8
|
+
export type { ProductComparisonToolbarProps } from './ProductComparisonToolbar'
|
|
9
|
+
|
|
10
|
+
export { default as ProductComparisonTrigger } from './ProductComparisonTrigger'
|
|
11
|
+
export type { ProductComparisonTriggerProps } from './ProductComparisonTrigger'
|
|
12
|
+
|
|
13
|
+
export { default as ProductComparisonProvider } from './provider/ProductComparisonProvider'
|
|
14
|
+
export type { IProductComparison } from './provider/ProductComparisonProvider'
|