@faststore/components 3.70.2 → 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.
Files changed (89) hide show
  1. package/dist/cjs/hooks/index.d.ts +1 -0
  2. package/dist/cjs/hooks/index.d.ts.map +1 -1
  3. package/dist/cjs/hooks/index.js +3 -1
  4. package/dist/cjs/hooks/index.js.map +1 -1
  5. package/dist/cjs/hooks/useProductComparison.d.ts +2 -0
  6. package/dist/cjs/hooks/useProductComparison.d.ts.map +1 -0
  7. package/dist/cjs/hooks/useProductComparison.js +14 -0
  8. package/dist/cjs/hooks/useProductComparison.js.map +1 -0
  9. package/dist/cjs/index.d.ts +2 -0
  10. package/dist/cjs/index.d.ts.map +1 -1
  11. package/dist/cjs/index.js +6 -1
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/molecules/ToggleField/ToggleField.d.ts +2 -1
  14. package/dist/cjs/molecules/ToggleField/ToggleField.d.ts.map +1 -1
  15. package/dist/cjs/molecules/ToggleField/ToggleField.js.map +1 -1
  16. package/dist/cjs/organisms/ProductComparison/ProductComparison.d.ts +7 -0
  17. package/dist/cjs/organisms/ProductComparison/ProductComparison.d.ts.map +1 -0
  18. package/dist/cjs/organisms/ProductComparison/ProductComparison.js +11 -0
  19. package/dist/cjs/organisms/ProductComparison/ProductComparison.js.map +1 -0
  20. package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.d.ts +79 -0
  21. package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.d.ts.map +1 -0
  22. package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.js +135 -0
  23. package/dist/cjs/organisms/ProductComparison/ProductComparisonSidebar.js.map +1 -0
  24. package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.d.ts +9 -0
  25. package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.d.ts.map +1 -0
  26. package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.js +25 -0
  27. package/dist/cjs/organisms/ProductComparison/ProductComparisonToolbar.js.map +1 -0
  28. package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.d.ts +11 -0
  29. package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.d.ts.map +1 -0
  30. package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.js +18 -0
  31. package/dist/cjs/organisms/ProductComparison/ProductComparisonTrigger.js.map +1 -0
  32. package/dist/cjs/organisms/ProductComparison/index.d.ts +11 -0
  33. package/dist/cjs/organisms/ProductComparison/index.d.ts.map +1 -0
  34. package/dist/cjs/organisms/ProductComparison/index.js +17 -0
  35. package/dist/cjs/organisms/ProductComparison/index.js.map +1 -0
  36. package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts +75 -0
  37. package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts.map +1 -0
  38. package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.js +50 -0
  39. package/dist/cjs/organisms/ProductComparison/provider/ProductComparisonProvider.js.map +1 -0
  40. package/dist/esm/hooks/index.d.ts +1 -0
  41. package/dist/esm/hooks/index.d.ts.map +1 -1
  42. package/dist/esm/hooks/index.js +1 -0
  43. package/dist/esm/hooks/index.js.map +1 -1
  44. package/dist/esm/hooks/useProductComparison.d.ts +2 -0
  45. package/dist/esm/hooks/useProductComparison.d.ts.map +1 -0
  46. package/dist/esm/hooks/useProductComparison.js +10 -0
  47. package/dist/esm/hooks/useProductComparison.js.map +1 -0
  48. package/dist/esm/index.d.ts +2 -0
  49. package/dist/esm/index.d.ts.map +1 -1
  50. package/dist/esm/index.js +1 -0
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/molecules/ToggleField/ToggleField.d.ts +2 -1
  53. package/dist/esm/molecules/ToggleField/ToggleField.d.ts.map +1 -1
  54. package/dist/esm/molecules/ToggleField/ToggleField.js.map +1 -1
  55. package/dist/esm/organisms/ProductComparison/ProductComparison.d.ts +7 -0
  56. package/dist/esm/organisms/ProductComparison/ProductComparison.d.ts.map +1 -0
  57. package/dist/esm/organisms/ProductComparison/ProductComparison.js +8 -0
  58. package/dist/esm/organisms/ProductComparison/ProductComparison.js.map +1 -0
  59. package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.d.ts +79 -0
  60. package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.d.ts.map +1 -0
  61. package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.js +132 -0
  62. package/dist/esm/organisms/ProductComparison/ProductComparisonSidebar.js.map +1 -0
  63. package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.d.ts +9 -0
  64. package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.d.ts.map +1 -0
  65. package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.js +22 -0
  66. package/dist/esm/organisms/ProductComparison/ProductComparisonToolbar.js.map +1 -0
  67. package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.d.ts +11 -0
  68. package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.d.ts.map +1 -0
  69. package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.js +15 -0
  70. package/dist/esm/organisms/ProductComparison/ProductComparisonTrigger.js.map +1 -0
  71. package/dist/esm/organisms/ProductComparison/index.d.ts +11 -0
  72. package/dist/esm/organisms/ProductComparison/index.d.ts.map +1 -0
  73. package/dist/esm/organisms/ProductComparison/index.js +6 -0
  74. package/dist/esm/organisms/ProductComparison/index.js.map +1 -0
  75. package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts +75 -0
  76. package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.d.ts.map +1 -0
  77. package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.js +46 -0
  78. package/dist/esm/organisms/ProductComparison/provider/ProductComparisonProvider.js.map +1 -0
  79. package/package.json +2 -2
  80. package/src/hooks/index.ts +1 -0
  81. package/src/hooks/useProductComparison.ts +15 -0
  82. package/src/index.ts +14 -0
  83. package/src/molecules/ToggleField/ToggleField.tsx +2 -2
  84. package/src/organisms/ProductComparison/ProductComparison.tsx +24 -0
  85. package/src/organisms/ProductComparison/ProductComparisonSidebar.tsx +452 -0
  86. package/src/organisms/ProductComparison/ProductComparisonToolbar.tsx +78 -0
  87. package/src/organisms/ProductComparison/ProductComparisonTrigger.tsx +49 -0
  88. package/src/organisms/ProductComparison/index.ts +14 -0
  89. package/src/organisms/ProductComparison/provider/ProductComparisonProvider.tsx +144 -0
@@ -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'