@asante-org/leybold-design-system 1.3.1 → 1.3.2

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 (57) hide show
  1. package/README.md +163 -163
  2. package/dist/.storybook/algoliaProvider.d.ts +3 -0
  3. package/dist/assets/.gitkeep +2 -2
  4. package/dist/assets/desktop-layout-alt.svg +27 -27
  5. package/dist/assets/desktop-layout.svg +29 -29
  6. package/dist/assets/globe.svg +7 -7
  7. package/dist/assets/leybold-footer-logo.svg +19 -19
  8. package/dist/assets/leybold-white.svg +18 -18
  9. package/dist/assets/list-product-overlay-tip-active.svg +3 -3
  10. package/dist/assets/list-product-overlay-tip.svg +3 -3
  11. package/dist/assets/logo-example.svg +9 -9
  12. package/dist/assets/logo.svg +19 -19
  13. package/dist/assets/phone-layout.svg +14 -14
  14. package/dist/assets/red-tip.svg +10 -10
  15. package/dist/assets/tablet-layout.svg +28 -28
  16. package/dist/index.esm.js +818 -402
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.esm.scss +701 -184
  19. package/dist/index.js +892 -470
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.scss +701 -184
  22. package/dist/src/components/Button/Button.d.ts +2 -2
  23. package/dist/src/components/CarouselCard/CarouselCard.d.ts +18 -0
  24. package/dist/src/components/CarouselCard/CarouselCard.stories.d.ts +6 -0
  25. package/dist/src/components/CarouselCard/index.d.ts +2 -0
  26. package/dist/src/components/ContentCardBase/ContentCardBase.d.ts +3 -0
  27. package/dist/src/components/ContentCardBase/index.d.ts +2 -0
  28. package/dist/src/components/ContentCardHorizontal/ContentCardHorizontal.d.ts +2 -2
  29. package/dist/src/components/ContentCardHorizontal/ContentCardHorizontal.stories.d.ts +1 -0
  30. package/dist/src/components/ContentCardVertical/ContentCardVertical.d.ts +3 -0
  31. package/dist/src/components/ContentCardVertical/ContentCardVertical.stories.d.ts +9 -0
  32. package/dist/src/components/ProductCardVertical/ProductCardVertical.d.ts +2 -2
  33. package/dist/src/components/SearchBar/SearchBar.d.ts +2 -2
  34. package/dist/src/components/SearchBar/SearchSubmitButton.d.ts +2 -2
  35. package/dist/src/components/SearchModal/SearchModal.d.ts +2 -2
  36. package/dist/src/experience/Carousel/Carousel.d.ts +1 -1
  37. package/dist/src/experience/algolia-dynamic-search/AlgoliaDynamicSearch.stories.d.ts +2 -0
  38. package/dist/src/experience/federated-search/FederatedSearchWithAlgolia.stories.d.ts +1 -0
  39. package/dist/src/experience/federated-search/components/FilterDrawer/FilterDrawer.d.ts +2 -2
  40. package/dist/src/index.d.ts +4 -1
  41. package/dist/src/stories/foundation/_components/StoryAccordion.d.ts +13 -0
  42. package/dist/src/types/cards.d.ts +32 -2
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +115 -115
  45. package/dist/app/layout.d.ts +0 -9
  46. package/dist/app/page.d.ts +0 -2
  47. package/dist/index.css +0 -88
  48. package/dist/index.d.ts +0 -3
  49. package/dist/index.esm.css +0 -88
  50. package/dist/stories/components/Algolia-dynamic-search/algolia-dynamic-search.d.ts +0 -4
  51. package/dist/stories/components/Algolia-dynamic-search/algolia-dynamic-search.stories.d.ts +0 -7
  52. package/dist/stories/components/Button/Button.d.ts +0 -35
  53. package/dist/stories/components/Button/Button.stories.d.ts +0 -15
  54. package/dist/stories/components/Button/index.d.ts +0 -2
  55. package/dist/stories/components/QRFormJourney/Qr-form/Qr-form.d.ts +0 -2
  56. package/dist/stories/components/QRFormJourney/Qr-form/Qr-form.stories.d.ts +0 -7
  57. package/dist/utils/styles/index.d.ts +0 -1
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState, useCallback, useRef, useEffect } from 'react';
1
+ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
2
2
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
3
  import { faArrowLeft, faArrowLeftLong, faArrowRight, faArrowRightLong, faChevronRight, faChevronLeft, faArrowUpRightFromSquare, faGlobe, faXmark as faXmark$1 } from '@fortawesome/free-solid-svg-icons';
4
4
  import { faInstagram, faYoutube, faLinkedinIn, faXTwitter, faFacebookF } from '@fortawesome/free-brands-svg-icons';
@@ -15,7 +15,7 @@ function _extends() {
15
15
  }, _extends.apply(null, arguments);
16
16
  }
17
17
 
18
- var styles$k = {"button":"Button-module__button___18Bed","button--primary":"Button-module__button--primary___VuF-P","button--disabled":"Button-module__button--disabled___IuOY8","button--secondary":"Button-module__button--secondary___lD5E3","button--solid-grey":"Button-module__button--solid-grey___oRbEy","button--solid-black":"Button-module__button--solid-black___1Ma5K","button--solid-white":"Button-module__button--solid-white___bE-Z0","button--outlined-grey":"Button-module__button--outlined-grey___xWGbB","button--outlined-black":"Button-module__button--outlined-black___qfX5o","button--outlined-white":"Button-module__button--outlined-white___QLXNP","button--link-text":"Button-module__button--link-text___R2kun","button__icon":"Button-module__button__icon___hlcHo","button--link-text-alt":"Button-module__button--link-text-alt___1p7wH","button--external-link":"Button-module__button--external-link___Mhxuk","button--carousel-arrow-left":"Button-module__button--carousel-arrow-left___Wx-Jo","button--carousel-arrow-right":"Button-module__button--carousel-arrow-right___3ZtgT","button__icon__default":"Button-module__button__icon__default___0qlF1","button__icon__hover":"Button-module__button__icon__hover___3xPGT","button--extra-small":"Button-module__button--extra-small___R0QTM","button--small":"Button-module__button--small___ADJQe","button--medium":"Button-module__button--medium___ZuR4Z","button--large":"Button-module__button--large___-FaV5","button__icon--left":"Button-module__button__icon--left___wMCeH","button__icon--right":"Button-module__button__icon--right___-pGHl"};
18
+ var styles$m = {"button":"Button-module__button___18Bed","button--primary":"Button-module__button--primary___VuF-P","button--disabled":"Button-module__button--disabled___IuOY8","button--secondary":"Button-module__button--secondary___lD5E3","button--solid-grey":"Button-module__button--solid-grey___oRbEy","button--solid-black":"Button-module__button--solid-black___1Ma5K","button--solid-white":"Button-module__button--solid-white___bE-Z0","button--outlined-grey":"Button-module__button--outlined-grey___xWGbB","button--outlined-black":"Button-module__button--outlined-black___qfX5o","button--outlined-white":"Button-module__button--outlined-white___QLXNP","button--link-text":"Button-module__button--link-text___R2kun","button__icon":"Button-module__button__icon___hlcHo","button--link-text-alt":"Button-module__button--link-text-alt___1p7wH","button--external-link":"Button-module__button--external-link___Mhxuk","button--carousel-arrow-left":"Button-module__button--carousel-arrow-left___Wx-Jo","button--carousel-arrow-right":"Button-module__button--carousel-arrow-right___3ZtgT","button__icon__default":"Button-module__button__icon__default___0qlF1","button__icon__hover":"Button-module__button__icon__hover___3xPGT","button--extra-small":"Button-module__button--extra-small___R0QTM","button--small":"Button-module__button--small___ADJQe","button--medium":"Button-module__button--medium___ZuR4Z","button--large":"Button-module__button--large___-FaV5","button__icon--left":"Button-module__button__icon--left___wMCeH","button__icon--right":"Button-module__button__icon--right___-pGHl"};
19
19
 
20
20
  function getDefaultExportFromCjs (x) {
21
21
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -92,20 +92,20 @@ function requireClassnames() {
92
92
  var classnamesExports = requireClassnames();
93
93
  var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
94
94
 
95
- /**
96
- * Button variant types
95
+ /**
96
+ * Button variant types
97
97
  */
98
98
 
99
- /**
100
- * Icon types for button
99
+ /**
100
+ * Icon types for button
101
101
  */
102
102
 
103
- /**
104
- * Icon position
103
+ /**
104
+ * Icon position
105
105
  */
106
106
 
107
- /**
108
- * Get the icon component based on icon type
107
+ /**
108
+ * Get the icon component based on icon type
109
109
  */
110
110
  const getIcon = icon => {
111
111
  switch (icon) {
@@ -130,8 +130,8 @@ const getIcon = icon => {
130
130
  }
131
131
  };
132
132
 
133
- /**
134
- * Get default icon for certain variants
133
+ /**
134
+ * Get default icon for certain variants
135
135
  */
136
136
  const getDefaultIcon = variant => {
137
137
  switch (variant) {
@@ -151,8 +151,8 @@ const getDefaultIcon = variant => {
151
151
  }
152
152
  };
153
153
 
154
- /**
155
- * Map alias variants to their actual CSS class variants
154
+ /**
155
+ * Map alias variants to their actual CSS class variants
156
156
  */
157
157
  const mapVariantToClass = variant => {
158
158
  switch (variant) {
@@ -163,8 +163,8 @@ const mapVariantToClass = variant => {
163
163
  }
164
164
  };
165
165
 
166
- /**
167
- * Primary UI component for user interaction
166
+ /**
167
+ * Primary UI component for user interaction
168
168
  */
169
169
  const Button = ({
170
170
  children,
@@ -191,10 +191,10 @@ const Button = ({
191
191
  const isIconOnly = variant === "carousel-arrow-left" || variant === "carousel-arrow-right";
192
192
  const isCarouselLeft = variant === "carousel-arrow-left";
193
193
  const isCarouselRight = variant === "carousel-arrow-right";
194
- const buttonClasses = classNames(styles$k.button, styles$k[`button--${cssVariant}`], styles$k[`button--${size}`], {
195
- [styles$k["button--disabled"]]: disabled
194
+ const buttonClasses = classNames(styles$m.button, styles$m[`button--${cssVariant}`], styles$m[`button--${size}`], {
195
+ [styles$m["button--disabled"]]: disabled
196
196
  }, className);
197
- const iconClasses = classNames(styles$k.button__icon, styles$k[`button__icon--${iconPosition}`]);
197
+ const iconClasses = classNames(styles$m.button__icon, styles$m[`button__icon--${iconPosition}`]);
198
198
 
199
199
  // Generate accessible label for icon-only buttons
200
200
  const accessibleLabel = ariaLabel || (isIconOnly ? variant === "carousel-arrow-left" ? "Previous" : "Next" : undefined);
@@ -204,32 +204,194 @@ const Button = ({
204
204
  disabled: disabled,
205
205
  onClick: onClick,
206
206
  "aria-label": accessibleLabel,
207
- "data-force-state": props['data-force-state']
207
+ "data-force-state": props["data-force-state"]
208
208
  }, props), !isIconOnly && children, isCarouselLeft && /*#__PURE__*/React.createElement("span", {
209
209
  className: iconClasses
210
210
  }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
211
211
  icon: faArrowLeft,
212
- className: styles$k.button__icon__default
212
+ className: styles$m.button__icon__default
213
213
  }), /*#__PURE__*/React.createElement(FontAwesomeIcon, {
214
214
  icon: faArrowLeftLong,
215
- className: styles$k.button__icon__hover
215
+ className: styles$m.button__icon__hover
216
216
  })), isCarouselRight && /*#__PURE__*/React.createElement("span", {
217
217
  className: iconClasses
218
218
  }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
219
219
  icon: faArrowRight,
220
- className: styles$k.button__icon__default
220
+ className: styles$m.button__icon__default
221
221
  }), /*#__PURE__*/React.createElement(FontAwesomeIcon, {
222
222
  icon: faArrowRightLong,
223
- className: styles$k.button__icon__hover
223
+ className: styles$m.button__icon__hover
224
224
  })), !isIconOnly && iconElement && /*#__PURE__*/React.createElement("span", {
225
225
  className: iconClasses
226
226
  }, iconElement));
227
227
  };
228
228
 
229
+ var styles$l = {"imageContainer":"Image-module__imageContainer___iD5Kd","loading":"Image-module__loading___Sh1zO","image":"Image-module__image___1pa50","error":"Image-module__error___0LGZ2","skeleton":"Image-module__skeleton___0bGS6","shimmer":"Image-module__shimmer___aanrl","errorState":"Image-module__errorState___TjV-8","errorIcon":"Image-module__errorIcon___QCKvj","errorText":"Image-module__errorText___Q8pnb"};
230
+
231
+ const Image = ({
232
+ src,
233
+ alt,
234
+ width,
235
+ height,
236
+ loading = "lazy",
237
+ decoding = "async",
238
+ objectFit = "cover",
239
+ objectPosition = "center",
240
+ aspectRatio,
241
+ isDecorative = false,
242
+ fallbackSrc,
243
+ onLoad,
244
+ onError,
245
+ className = "",
246
+ srcSet,
247
+ sizes
248
+ }) => {
249
+ const [isLoading, setIsLoading] = useState(true);
250
+ const [hasError, setHasError] = useState(false);
251
+ const [currentSrc, setCurrentSrc] = useState(src);
252
+
253
+ // Keep internal state in sync when parent updates `src` (e.g. async data).
254
+ useEffect(() => {
255
+ setCurrentSrc(src);
256
+ setIsLoading(true);
257
+ setHasError(false);
258
+ }, [src]);
259
+ const handleLoad = useCallback(() => {
260
+ setIsLoading(false);
261
+ setHasError(false);
262
+ onLoad?.();
263
+ }, [onLoad]);
264
+ const handleError = useCallback(() => {
265
+ setIsLoading(false);
266
+ setHasError(true);
267
+
268
+ // Try fallback if available and not already using it
269
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
270
+ setCurrentSrc(fallbackSrc);
271
+ setHasError(false);
272
+ setIsLoading(true);
273
+ } else {
274
+ onError?.();
275
+ }
276
+ }, [fallbackSrc, currentSrc, onError]);
277
+
278
+ // Accessibility: decorative images should have empty alt and be hidden from screen readers
279
+ const accessibilityProps = isDecorative ? {
280
+ alt: "",
281
+ "aria-hidden": true,
282
+ role: "presentation"
283
+ } : {
284
+ alt
285
+ };
286
+ const containerStyle = {
287
+ ...(aspectRatio && {
288
+ aspectRatio
289
+ }),
290
+ ...(width && {
291
+ width: typeof width === "number" ? `${width}px` : width
292
+ }),
293
+ ...(height && {
294
+ height: typeof height === "number" ? `${height}px` : height
295
+ })
296
+ };
297
+ const imageStyle = {
298
+ objectFit,
299
+ objectPosition
300
+ };
301
+ return /*#__PURE__*/React.createElement("div", {
302
+ className: `${styles$l.imageContainer} ${className} ${isLoading ? styles$l.loading : ""} ${hasError ? styles$l.error : ""}`,
303
+ style: containerStyle
304
+ }, isLoading && /*#__PURE__*/React.createElement("div", {
305
+ className: styles$l.skeleton,
306
+ "aria-hidden": "true"
307
+ }, /*#__PURE__*/React.createElement("div", {
308
+ className: styles$l.shimmer
309
+ })), hasError && !fallbackSrc ? /*#__PURE__*/React.createElement("div", {
310
+ className: styles$l.errorState,
311
+ role: "img",
312
+ "aria-label": alt || "Image failed to load"
313
+ }, /*#__PURE__*/React.createElement("svg", {
314
+ xmlns: "http://www.w3.org/2000/svg",
315
+ viewBox: "0 0 24 24",
316
+ fill: "none",
317
+ stroke: "currentColor",
318
+ strokeWidth: "1.5",
319
+ strokeLinecap: "round",
320
+ strokeLinejoin: "round",
321
+ className: styles$l.errorIcon,
322
+ "aria-hidden": "true"
323
+ }, /*#__PURE__*/React.createElement("rect", {
324
+ x: "3",
325
+ y: "3",
326
+ width: "18",
327
+ height: "18",
328
+ rx: "2",
329
+ ry: "2"
330
+ }), /*#__PURE__*/React.createElement("circle", {
331
+ cx: "8.5",
332
+ cy: "8.5",
333
+ r: "1.5"
334
+ }), /*#__PURE__*/React.createElement("polyline", {
335
+ points: "21 15 16 10 5 21"
336
+ })), /*#__PURE__*/React.createElement("span", {
337
+ className: styles$l.errorText
338
+ }, "Image unavailable")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", _extends({
339
+ src: currentSrc
340
+ }, accessibilityProps, {
341
+ width: width,
342
+ height: height,
343
+ loading: loading,
344
+ decoding: decoding,
345
+ onLoad: handleLoad,
346
+ onError: handleError,
347
+ className: styles$l.image,
348
+ style: imageStyle,
349
+ srcSet: srcSet,
350
+ sizes: sizes
351
+ }))));
352
+ };
353
+
354
+ var styles$k = {"carouselCard":"CarouselCard-module__carouselCard___7SltX","carouselCard__content":"CarouselCard-module__carouselCard__content___SEDzm","carouselCard__linkText":"CarouselCard-module__carouselCard__linkText___fe3NV","carouselCard__icon":"CarouselCard-module__carouselCard__icon___sQ26Z","carouselCard__imageWrapper":"CarouselCard-module__carouselCard__imageWrapper___i-uYe","carouselCard__title":"CarouselCard-module__carouselCard__title___PN7o3"};
355
+
356
+ const CarouselCard = ({
357
+ imageUrl,
358
+ imageAlt,
359
+ title,
360
+ linkText,
361
+ linkUrl,
362
+ className,
363
+ ariaLabel
364
+ }) => {
365
+ return /*#__PURE__*/React.createElement("a", {
366
+ href: linkUrl,
367
+ className: classNames(styles$k.carouselCard, className),
368
+ "aria-label": ariaLabel
369
+ }, /*#__PURE__*/React.createElement(Image, {
370
+ src: imageUrl,
371
+ alt: imageAlt,
372
+ className: styles$k.carouselCard__imageWrapper,
373
+ width: "100%",
374
+ height: "100%",
375
+ objectFit: "cover",
376
+ objectPosition: "center"
377
+ }), /*#__PURE__*/React.createElement("div", {
378
+ className: styles$k.carouselCard__content
379
+ }, /*#__PURE__*/React.createElement("h3", {
380
+ className: styles$k.carouselCard__title
381
+ }, title), /*#__PURE__*/React.createElement("span", {
382
+ className: styles$k.carouselCard__linkText
383
+ }, linkText, /*#__PURE__*/React.createElement("span", {
384
+ className: styles$k.carouselCard__icon,
385
+ "aria-hidden": "true"
386
+ }, /*#__PURE__*/React.createElement(FontAwesomeIcon, {
387
+ icon: faArrowRight
388
+ })))));
389
+ };
390
+
229
391
  var styles$j = {"footer":"Footer-module__footer___Oavyx","footer--mobile":"Footer-module__footer--mobile___9HQC-","footer__container":"Footer-module__footer__container___ohvnm","footer__links":"Footer-module__footer__links___DdVK8","footer__linkGroup":"Footer-module__footer__linkGroup___1B7JA","footer__linkList":"Footer-module__footer__linkList___j-DWF","footer__linkHeading":"Footer-module__footer__linkHeading___LtE6X","footer__link":"Footer-module__footer__link___G5HPW","footer__cta":"Footer-module__footer__cta___MRJLr","footer__socialIcons":"Footer-module__footer__socialIcons___j0aRp","footer__socialIcon":"Footer-module__footer__socialIcon___1uVTm","footer__countrySelector":"Footer-module__footer__countrySelector___pVtN5","footer__bottom":"Footer-module__footer__bottom___77bPL","footer__bottomLeft":"Footer-module__footer__bottomLeft___tlst1","footer__bottomLinks":"Footer-module__footer__bottomLinks___s22h7","footer__bottomLink":"Footer-module__footer__bottomLink___SLzwY","footer__copyright":"Footer-module__footer__copyright___4bZOF","footer__brandSection":"Footer-module__footer__brandSection___BBr5f","footer__logo":"Footer-module__footer__logo___mv-9M","footer__leftCol":"Footer-module__footer__leftCol___vmKNN","footer__motto":"Footer-module__footer__motto___QtB2m","footer__main":"Footer-module__footer__main___NSU9d","footer__badge":"Footer-module__footer__badge___faejb","footer__badgeIcon":"Footer-module__footer__badgeIcon___xJ519","footer__badgeText":"Footer-module__footer__badgeText___imXgG","footer__linkText":"Footer-module__footer__linkText___Gc18Z","footer__linkIcon":"Footer-module__footer__linkIcon___IZzJM","footer__countrySelectorIcon":"Footer-module__footer__countrySelectorIcon___hhEd4"};
230
392
 
231
- /**
232
- * External link icon (↗) for footer links that open in new tabs
393
+ /**
394
+ * External link icon (↗) for footer links that open in new tabs
233
395
  */
234
396
  const ExternalLinkIcon = ({
235
397
  className,
@@ -250,8 +412,8 @@ const ExternalLinkIcon = ({
250
412
  strokeLinejoin: "round"
251
413
  }));
252
414
 
253
- /**
254
- * Atlas Copco Group badge icon (square)
415
+ /**
416
+ * Atlas Copco Group badge icon (square)
255
417
  */
256
418
  const AtlasCopcoIcon = ({
257
419
  className,
@@ -427,11 +589,11 @@ const FooterBottom = ({
427
589
  // MAIN FOOTER COMPONENT
428
590
  // =============================================================================
429
591
 
430
- /**
431
- * Footer component with brand identity, navigation links, social icons, and legal information.
432
- * Supports two layout variants:
433
- * - desktop: Logo at top, 3-column main content
434
- * - mobile: Logo at bottom, stacked layout with CTA button
592
+ /**
593
+ * Footer component with brand identity, navigation links, social icons, and legal information.
594
+ * Supports two layout variants:
595
+ * - desktop: Logo at top, 3-column main content
596
+ * - mobile: Logo at bottom, stacked layout with CTA button
435
597
  */
436
598
  const Footer = ({
437
599
  variant = 'desktop',
@@ -631,8 +793,8 @@ const PaginationEllipsis = ({
631
793
  }, "..."));
632
794
  };
633
795
 
634
- /**
635
- * Generate page numbers to display with ellipsis logic
796
+ /**
797
+ * Generate page numbers to display with ellipsis logic
636
798
  */
637
799
  const generatePageNumbers = (currentPage, totalPages, maxVisiblePages) => {
638
800
  if (totalPages <= maxVisiblePages) {
@@ -745,125 +907,7 @@ const Pagination = ({
745
907
  })));
746
908
  };
747
909
 
748
- var styles$h = {"productCard":"ProductCardHorizontal-module__productCard___Nl4jK","productWrapper":"ProductCardHorizontal-module__productWrapper___gs8fn","withImage":"ProductCardHorizontal-module__withImage___pPFw2","productTitle":"ProductCardHorizontal-module__productTitle___xBuu7","productPrice":"ProductCardHorizontal-module__productPrice___lf32A","productImage":"ProductCardHorizontal-module__productImage___sQsUO","productId":"ProductCardHorizontal-module__productId___3oOQV","productTitleDescriptionWrapper":"ProductCardHorizontal-module__productTitleDescriptionWrapper___Obv-j","productPriceCtaWrapper":"ProductCardHorizontal-module__productPriceCtaWrapper___Npkfl","productArrowIcon":"ProductCardHorizontal-module__productArrowIcon___1-A76"};
749
-
750
- var styles$g = {"imageContainer":"Image-module__imageContainer___iD5Kd","loading":"Image-module__loading___Sh1zO","image":"Image-module__image___1pa50","error":"Image-module__error___0LGZ2","skeleton":"Image-module__skeleton___0bGS6","shimmer":"Image-module__shimmer___aanrl","errorState":"Image-module__errorState___TjV-8","errorIcon":"Image-module__errorIcon___QCKvj","errorText":"Image-module__errorText___Q8pnb"};
751
-
752
- const Image = ({
753
- src,
754
- alt,
755
- width,
756
- height,
757
- loading = "lazy",
758
- decoding = "async",
759
- objectFit = "cover",
760
- objectPosition = "center",
761
- aspectRatio,
762
- isDecorative = false,
763
- fallbackSrc,
764
- onLoad,
765
- onError,
766
- className = "",
767
- srcSet,
768
- sizes
769
- }) => {
770
- const [isLoading, setIsLoading] = useState(true);
771
- const [hasError, setHasError] = useState(false);
772
- const [currentSrc, setCurrentSrc] = useState(src);
773
- const handleLoad = useCallback(() => {
774
- setIsLoading(false);
775
- setHasError(false);
776
- onLoad?.();
777
- }, [onLoad]);
778
- const handleError = useCallback(() => {
779
- setIsLoading(false);
780
- setHasError(true);
781
-
782
- // Try fallback if available and not already using it
783
- if (fallbackSrc && currentSrc !== fallbackSrc) {
784
- setCurrentSrc(fallbackSrc);
785
- setHasError(false);
786
- setIsLoading(true);
787
- } else {
788
- onError?.();
789
- }
790
- }, [fallbackSrc, currentSrc, onError]);
791
-
792
- // Accessibility: decorative images should have empty alt and be hidden from screen readers
793
- const accessibilityProps = isDecorative ? {
794
- alt: "",
795
- "aria-hidden": true,
796
- role: "presentation"
797
- } : {
798
- alt
799
- };
800
- const containerStyle = {
801
- ...(aspectRatio && {
802
- aspectRatio
803
- }),
804
- ...(width && {
805
- width: typeof width === "number" ? `${width}px` : width
806
- }),
807
- ...(height && {
808
- height: typeof height === "number" ? `${height}px` : height
809
- })
810
- };
811
- const imageStyle = {
812
- objectFit,
813
- objectPosition
814
- };
815
- return /*#__PURE__*/React.createElement("div", {
816
- className: `${styles$g.imageContainer} ${className} ${isLoading ? styles$g.loading : ""} ${hasError ? styles$g.error : ""}`,
817
- style: containerStyle
818
- }, isLoading && /*#__PURE__*/React.createElement("div", {
819
- className: styles$g.skeleton,
820
- "aria-hidden": "true"
821
- }, /*#__PURE__*/React.createElement("div", {
822
- className: styles$g.shimmer
823
- })), hasError && !fallbackSrc ? /*#__PURE__*/React.createElement("div", {
824
- className: styles$g.errorState,
825
- role: "img",
826
- "aria-label": alt || "Image failed to load"
827
- }, /*#__PURE__*/React.createElement("svg", {
828
- xmlns: "http://www.w3.org/2000/svg",
829
- viewBox: "0 0 24 24",
830
- fill: "none",
831
- stroke: "currentColor",
832
- strokeWidth: "1.5",
833
- strokeLinecap: "round",
834
- strokeLinejoin: "round",
835
- className: styles$g.errorIcon,
836
- "aria-hidden": "true"
837
- }, /*#__PURE__*/React.createElement("rect", {
838
- x: "3",
839
- y: "3",
840
- width: "18",
841
- height: "18",
842
- rx: "2",
843
- ry: "2"
844
- }), /*#__PURE__*/React.createElement("circle", {
845
- cx: "8.5",
846
- cy: "8.5",
847
- r: "1.5"
848
- }), /*#__PURE__*/React.createElement("polyline", {
849
- points: "21 15 16 10 5 21"
850
- })), /*#__PURE__*/React.createElement("span", {
851
- className: styles$g.errorText
852
- }, "Image unavailable")) : /*#__PURE__*/React.createElement("img", _extends({
853
- src: currentSrc
854
- }, accessibilityProps, {
855
- width: width,
856
- height: height,
857
- loading: loading,
858
- decoding: decoding,
859
- onLoad: handleLoad,
860
- onError: handleError,
861
- className: styles$g.image,
862
- style: imageStyle,
863
- srcSet: srcSet,
864
- sizes: sizes
865
- })));
866
- };
910
+ var styles$h = {"productCard":"ProductCardHorizontal-module__productCard___Nl4jK","productWrapper":"ProductCardHorizontal-module__productWrapper___gs8fn","withImage":"ProductCardHorizontal-module__withImage___pPFw2","withPlaceholder":"ProductCardHorizontal-module__withPlaceholder___x8IUh","productTitle":"ProductCardHorizontal-module__productTitle___xBuu7","productPrice":"ProductCardHorizontal-module__productPrice___lf32A","productImage":"ProductCardHorizontal-module__productImage___sQsUO","placeholderImage":"ProductCardHorizontal-module__placeholderImage___kT1sp","productId":"ProductCardHorizontal-module__productId___3oOQV","productTitleDescriptionWrapper":"ProductCardHorizontal-module__productTitleDescriptionWrapper___Obv-j","productPriceCtaWrapper":"ProductCardHorizontal-module__productPriceCtaWrapper___Npkfl","productArrowIcon":"ProductCardHorizontal-module__productArrowIcon___1-A76"};
867
911
 
868
912
  /*!
869
913
  * Font Awesome Pro 6.7.2 by @fontawesome - https://fontawesome.com
@@ -876,9 +920,16 @@ const faArrowUpRight = {
876
920
  icon: [384, 512, [], "e09f", "M328 96c13.3 0 24 10.7 24 24l0 240c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-182.1L73 409c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l231-231L88 144c-13.3 0-24-10.7-24-24s10.7-24 24-24l240 0z"]
877
921
  };
878
922
 
923
+ const PLACEHOLDER_INDICATOR$1 = "?placeholder-storybook";
924
+ const PLACEHOLDER_SVG$1 = `<svg xmlns="http://www.w3.org/2000/svg" width="440" height="133" viewBox="218.446 219.139 440 133" role="img" aria-label="Leybold placeholder logo"><g fill="#ffff"><path d="M290.317,313.442h37.784v12.782h-53.654v-70.62h15.87V313.442z"/><path d="M386.309,310.883c-3.912,12.593-15.135,16.722-24.761,16.722c-15.878,0-28.046-7.084-28.046-28.62c0-6.296,2.322-26.458,26.98-26.458c11.116,0,26.457,4.917,26.457,28.819v2.457h-38.626c0.428,3.935,1.274,12.785,13.232,12.785c4.125,0,8.356-1.965,9.521-5.704L386.309,310.883L386.309,310.883z M371.709,294.852c-0.85-8.456-6.666-11.017-11.434-11.017c-6.979,0-10.686,4.129-11.532,11.017H371.709z"/><path d="M423.556,325.343c-6.986,19.374-8.892,21.932-21.799,21.932c-2.014,0-4.553-0.096-6.665-0.194v-11.02c0.629,0.104,1.582,0.197,2.854,0.197c5.397,0,8.36-0.688,9.737-7.864l-20.741-54.488h16.508l12.062,38.553h0.209l11.539-38.553h15.658L423.556,325.343z"/><path d="M463.54,280.689h0.213c2.328-3.344,6.775-8.164,16.297-8.164c12.385,0,23.389,8.854,23.389,26.949c0,14.359-6.98,28.129-23.703,28.129c-6.141,0-12.701-2.067-16.299-7.968h-0.211v6.589h-14.498v-70.62h14.812V280.689z M475.604,284.427c-10.158,0-12.701,8.455-12.701,16.815c0,7.773,3.707,15.049,13.129,15.049c9.521,0,12.168-9.641,12.168-15.83C488.198,292.192,485.02,284.427,475.604,284.427z"/><path d="M538.094,327.604c-15.141,0-28.787-8.652-28.787-27.542c0-18.881,13.646-27.536,28.787-27.536c15.129,0,28.785,8.655,28.785,27.536C566.88,318.95,553.223,327.604,538.094,327.604z M538.094,284.129c-11.434,0-13.547,9.244-13.547,15.934c0,6.691,2.113,15.939,13.547,15.939c11.426,0,13.547-9.248,13.547-15.939C551.641,293.373,549.52,284.129,538.094,284.129z"/><path d="M588.811,326.225h-14.814v-70.62h14.814V326.225z"/><path d="M651.969,326.225h-14.5v-6.589h-0.213c-3.598,5.898-10.156,7.968-16.295,7.968c-16.727,0-23.703-13.77-23.703-28.129c0-18.097,11.004-26.949,23.381-26.949c9.523,0,13.975,4.82,16.295,8.164h0.221v-25.085h14.814V326.225z M624.663,316.291c9.418,0,13.121-7.273,13.121-15.05c0-8.359-2.537-16.814-12.703-16.814c-9.416,0-12.592,7.767-12.592,16.034C612.49,306.651,615.135,316.291,624.663,316.291z"/></g><g fill="#ffff"><path d="M268.323,226.771h-41.551v39.685C230.996,246.516,247.442,230.811,268.323,226.771z"/><path d="M226.771,286.726v39.688h41.551C247.442,322.371,230.996,306.67,226.771,286.726z"/><path d="M331.122,266.455V226.77h-41.552C310.449,230.811,326.892,246.516,331.122,266.455z"/></g></svg>`;
925
+ const PLACEHOLDER_IMAGE_DATA_URI$1 = `data:image/svg+xml,${encodeURIComponent(PLACEHOLDER_SVG$1)}`;
926
+ const isPlaceholderImg$1 = url => {
927
+ return !!url && url.includes(PLACEHOLDER_INDICATOR$1);
928
+ };
879
929
  const ProductCardHorizontal = ({
880
930
  id,
881
931
  imageUrl,
932
+ showProductImage = true,
882
933
  url,
883
934
  title,
884
935
  description,
@@ -886,34 +937,43 @@ const ProductCardHorizontal = ({
886
937
  productId,
887
938
  button,
888
939
  utm,
889
- className = ""
940
+ className = "",
941
+ style,
942
+ code,
943
+ showProductPrice
890
944
  }) => {
891
945
  if (!title) {
892
946
  return null;
893
947
  }
948
+ const hasPlaceholderImage = showProductImage && (!imageUrl || isPlaceholderImg$1(imageUrl));
949
+ const productImage = hasPlaceholderImage === true ? PLACEHOLDER_IMAGE_DATA_URI$1 : imageUrl;
894
950
  const href = !utm || url && url.indexOf("utm") > -1 ? url : url + `?utm_source=${utm.utmSource}&utm_medium=${utm.utmMedium}&utm_campaign=${utm.utmCampaign}`;
895
951
  return /*#__PURE__*/React.createElement("a", {
896
952
  href: href,
897
953
  className: `${styles$h.productCard} ${className}`,
898
954
  "aria-label": `View product: ${title}`
899
955
  }, /*#__PURE__*/React.createElement("div", {
900
- className: `${styles$h.productWrapper} ${imageUrl ? styles$h.withImage : ""}`
901
- }, imageUrl && /*#__PURE__*/React.createElement("div", {
956
+ className: `${styles$h.productWrapper} ${showProductImage ? hasPlaceholderImage ? styles$h.withPlaceholder : styles$h.withImage : ""}`,
957
+ style: style
958
+ }, showProductImage && /*#__PURE__*/React.createElement("div", {
902
959
  className: styles$h.productImage
903
960
  }, /*#__PURE__*/React.createElement(Image, {
904
- src: imageUrl,
905
- alt: title
961
+ src: productImage,
962
+ alt: title,
963
+ className: hasPlaceholderImage ? styles$h.placeholderImage : "",
964
+ objectFit: hasPlaceholderImage ? "contain" : "cover",
965
+ fallbackSrc: PLACEHOLDER_IMAGE_DATA_URI$1
906
966
  })), /*#__PURE__*/React.createElement("div", {
907
967
  className: styles$h.productTitleDescriptionWrapper
908
968
  }, /*#__PURE__*/React.createElement("h3", {
909
969
  className: styles$h.productTitle
910
- }, title), productId && /*#__PURE__*/React.createElement("p", {
970
+ }, title), productId || code ? /*#__PURE__*/React.createElement("p", {
911
971
  className: styles$h.productId
912
- }, productId)), /*#__PURE__*/React.createElement("div", {
972
+ }, productId || code) : null), /*#__PURE__*/React.createElement("div", {
913
973
  className: styles$h.productPriceCtaWrapper
914
974
  }, /*#__PURE__*/React.createElement("p", {
915
975
  className: styles$h.productPrice
916
- }, price || " "), button ? /*#__PURE__*/React.createElement(Button, {
976
+ }, showProductPrice ? price || "" : ""), button ? /*#__PURE__*/React.createElement(Button, {
917
977
  className: styles$h.productButton,
918
978
  onClick: button.onClick,
919
979
  size: "extra-small"
@@ -927,7 +987,7 @@ const ProductCardHorizontal = ({
927
987
  // Alias for backward compatibility
928
988
  const ProductCardHorizontalParts = ProductCardHorizontal;
929
989
 
930
- var styles$f = {"productDetails":"ProductCardDetails-module__productDetails___-sx2l","spareCard":"ProductCardDetails-module__spareCard___vC1Ju"};
990
+ var styles$g = {"productDetails":"ProductCardDetails-module__productDetails___-sx2l","spareCard":"ProductCardDetails-module__spareCard___vC1Ju"};
931
991
 
932
992
  // Adapter to map design system props to ProductCardHorizontalParts props
933
993
  const SpareCardAdapter = props => {
@@ -936,13 +996,16 @@ const SpareCardAdapter = props => {
936
996
  ...rest
937
997
  } = props;
938
998
  return /*#__PURE__*/React.createElement(ProductCardHorizontalParts, {
939
- className: styles$f.spareCard,
999
+ className: styles$g.spareCard,
940
1000
  url: spare?.url || spare?.link || "#",
941
1001
  title: spare?.name || spare?.title || "",
942
1002
  description: spare?.description || "",
943
- price: spare?.priceValue || spare?.price || "",
944
- imageUrl: spare?.["img-product"] || spare?.image || "",
1003
+ price: spare?.priceValue || spare?.price || props?.productPrice || ""
1004
+ // imageUrl={spare?.["img-product"] || spare?.image || ""}
1005
+ ,
945
1006
  utm: props.utm,
1007
+ showProductPrice: props.showProductPrice,
1008
+ showProductImage: false,
946
1009
  button: {
947
1010
  label: rest.cta || "View Product",
948
1011
  onClick: () => window.open(spare?.url || spare?.link || "#", "_blank")
@@ -963,7 +1026,9 @@ const ProductCardDetails = ({
963
1026
  utm,
964
1027
  className = "",
965
1028
  ProductCardComponent,
966
- hidespares
1029
+ hidespares,
1030
+ showProductPrice,
1031
+ showProductImage = true
967
1032
  }) => {
968
1033
  // Build the hit object expected by ProductDetailsCard
969
1034
  const hitData = hit || {
@@ -975,23 +1040,146 @@ const ProductCardDetails = ({
975
1040
  image: imageUrl
976
1041
  };
977
1042
  return /*#__PURE__*/React.createElement("div", {
978
- className: styles$f.productDetails
1043
+ className: styles$g.productDetails
979
1044
  }, /*#__PURE__*/React.createElement(ProductDetailsCard, {
980
1045
  className: `${className}`,
981
1046
  title: title,
982
- imageUrl: imageUrl,
1047
+ imageUrl: showProductImage ? imageUrl : "",
983
1048
  imageAlt: title,
984
1049
  hit: hitData,
985
1050
  utm: utm,
986
1051
  facets: facets,
987
1052
  usePlainClasses: true,
988
1053
  ProductCardComponent: ProductCardComponent || SpareCardAdapter,
989
- hidespares: hidespares
1054
+ hidespares: hidespares,
1055
+ showProductPrice: showProductPrice,
1056
+ showProductImage: showProductImage
990
1057
  }));
991
1058
  };
992
1059
 
1060
+ const ContentCardBase = ({
1061
+ title,
1062
+ variant,
1063
+ url,
1064
+ imageUrl,
1065
+ category,
1066
+ meta = "something interesting",
1067
+ excerpt,
1068
+ className = "",
1069
+ style,
1070
+ ariaLabel,
1071
+ contentMetaClassName,
1072
+ contentCategoryClassName,
1073
+ contentMetaTextClassName,
1074
+ contentTitleClassName,
1075
+ contentExcerptClassName,
1076
+ showButton
1077
+ }) => {
1078
+ console.log("variant in ContentCardBase:", variant);
1079
+ const fallbackImage = "/assets/list-card.png";
1080
+ const isCareerView = variant === "career-view";
1081
+ const seperator = category && meta ? " | " : "";
1082
+ const defaultAriaLabel = isCareerView ? `View career opportunity: ${title}` : `View content: ${title}`;
1083
+ return /*#__PURE__*/React.createElement("a", {
1084
+ href: url,
1085
+ className: className,
1086
+ style: style,
1087
+ "aria-label": ariaLabel || defaultAriaLabel
1088
+ }, imageUrl && /*#__PURE__*/React.createElement("div", {
1089
+ className: "content-card-image-wrapper"
1090
+ }, /*#__PURE__*/React.createElement(Image, {
1091
+ src: imageUrl,
1092
+ alt: title,
1093
+ className: "content-card-image",
1094
+ aspectRatio: "16 / 9",
1095
+ loading: "lazy",
1096
+ decoding: "async",
1097
+ fallbackSrc: fallbackImage
1098
+ })), isCareerView ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h3", {
1099
+ className: contentTitleClassName
1100
+ }, title), category && /*#__PURE__*/React.createElement("span", {
1101
+ className: contentCategoryClassName
1102
+ }, category), meta && /*#__PURE__*/React.createElement("span", {
1103
+ className: contentMetaTextClassName
1104
+ }, meta), /*#__PURE__*/React.createElement(Button, {
1105
+ "aria-label": ariaLabel || defaultAriaLabel,
1106
+ variant: "link-text-alt",
1107
+ className: "career-button"
1108
+ }, "See more")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
1109
+ className: contentMetaClassName
1110
+ }, category && /*#__PURE__*/React.createElement("span", {
1111
+ className: contentCategoryClassName
1112
+ }, category), seperator && /*#__PURE__*/React.createElement("span", {
1113
+ className: `${contentCategoryClassName} separator`
1114
+ }, seperator), meta && /*#__PURE__*/React.createElement("span", {
1115
+ className: contentMetaTextClassName
1116
+ }, meta)), /*#__PURE__*/React.createElement("h3", {
1117
+ className: contentTitleClassName
1118
+ }, title), excerpt && /*#__PURE__*/React.createElement("p", {
1119
+ className: contentExcerptClassName
1120
+ }, excerpt), showButton && /*#__PURE__*/React.createElement(Button, {
1121
+ "aria-label": ariaLabel || defaultAriaLabel
1122
+ }, "Read More")));
1123
+ };
1124
+
1125
+ var styles$f = {"contentCard":"ContentCardVertical-module__contentCard___EoYej","contentCard--instant-view":"ContentCardVertical-module__contentCard--instant-view___dsIUf","contentCard--results-view":"ContentCardVertical-module__contentCard--results-view___6mSHQ","contentMeta":"ContentCardVertical-module__contentMeta___l2GtO","contentCategory":"ContentCardVertical-module__contentCategory___78vrj","contentMetaText":"ContentCardVertical-module__contentMetaText___Rl-ln","contentTitle":"ContentCardVertical-module__contentTitle___rEiHm","contentExcerpt":"ContentCardVertical-module__contentExcerpt___5Gb2G"};
1126
+
1127
+ const ContentCardVertical = ({
1128
+ id,
1129
+ title,
1130
+ url,
1131
+ imageUrl,
1132
+ category,
1133
+ meta,
1134
+ excerpt,
1135
+ className = "",
1136
+ variant
1137
+ }) => {
1138
+ const cardClasses = classNames(styles$f.contentCard, styles$f[`contentCard--${variant}`], className);
1139
+ console.log("Rendering ContentCardVertical with variant:", variant);
1140
+ return /*#__PURE__*/React.createElement(ContentCardBase, {
1141
+ id: id,
1142
+ variant: variant,
1143
+ title: title,
1144
+ url: url,
1145
+ imageUrl: imageUrl,
1146
+ category: category,
1147
+ meta: meta,
1148
+ excerpt: excerpt,
1149
+ className: cardClasses,
1150
+ contentMetaClassName: styles$f.contentMeta,
1151
+ contentCategoryClassName: styles$f.contentCategory,
1152
+ contentMetaTextClassName: styles$f.contentMetaText,
1153
+ contentTitleClassName: styles$f.contentTitle,
1154
+ contentExcerptClassName: styles$f.contentExcerpt
1155
+ });
1156
+ };
1157
+
993
1158
  // Adapter component that maps props from AlgoliaDynamicSearchRaw to ProductCardHorizontal
994
1159
  // AlgoliaDynamicSearchRaw passes: title, cardLink, productPrice, cta, hit (original data)
1160
+
1161
+ const cardAdapter = props => {
1162
+ const {
1163
+ hit,
1164
+ title,
1165
+ cardLink,
1166
+ productPrice,
1167
+ showProductPrice,
1168
+ className,
1169
+ queryType
1170
+ } = props;
1171
+ return /*#__PURE__*/React.createElement(ContentCardVertical, {
1172
+ id: hit?.id || "",
1173
+ variant: queryType && queryType === "careers" ? "career-view" : "content-view",
1174
+ title: title || hit?.title || "",
1175
+ url: cardLink || hit?.link || "#",
1176
+ imageUrl: hit?.image || "",
1177
+ category: hit?.category || "",
1178
+ meta: showProductPrice ? productPrice || hit?.price || "" : "",
1179
+ excerpt: hit?.description || "",
1180
+ className: className
1181
+ });
1182
+ };
995
1183
  const ProductCardAdapter = props => {
996
1184
  const {
997
1185
  hit,
@@ -1000,20 +1188,22 @@ const ProductCardAdapter = props => {
1000
1188
  productPrice,
1001
1189
  cta,
1002
1190
  showProductPrice,
1191
+ showProductImage,
1003
1192
  className,
1004
1193
  code,
1005
1194
  utm
1006
1195
  } = props;
1007
- console.log(props);
1196
+
1008
1197
  // Get values from direct props or fallback to hit object
1009
1198
  const url = cardLink || hit?.link || "#";
1010
- const imageUrl = "";
1199
+ const imageUrl = hit?.image || "";
1011
1200
  const price = showProductPrice ? productPrice || hit?.price || "" : "";
1012
1201
  const productTitle = title || hit?.title || "";
1013
1202
  const productCode = code || hit?.code || "";
1014
1203
  const buttonLabel = cta || "View Product";
1015
1204
  return /*#__PURE__*/React.createElement(ProductCardHorizontalParts, {
1016
1205
  imageUrl: imageUrl,
1206
+ showProductImage: showProductImage,
1017
1207
  url: url,
1018
1208
  utm: utm,
1019
1209
  title: productTitle,
@@ -1023,17 +1213,23 @@ const ProductCardAdapter = props => {
1023
1213
  label: buttonLabel,
1024
1214
  onClick: () => window.open(url, "_blank")
1025
1215
  },
1026
- className: className
1216
+ className: className,
1217
+ style: {
1218
+ ...(showProductImage ? {
1219
+ minHeight: "130px"
1220
+ } : {})
1221
+ },
1222
+ code: productCode,
1223
+ showProductPrice: showProductPrice
1027
1224
  });
1028
1225
  };
1029
1226
  const ProductCardDetailsAdapter = props => {
1030
1227
  const {
1031
1228
  hit,
1032
1229
  title,
1033
- cardLink,
1034
1230
  productPrice,
1035
- cta,
1036
1231
  showProductPrice,
1232
+ showProductImage,
1037
1233
  className,
1038
1234
  code,
1039
1235
  utm,
@@ -1055,13 +1251,44 @@ const ProductCardDetailsAdapter = props => {
1055
1251
  className: className,
1056
1252
  facets: facets,
1057
1253
  relatedProducts: hit?.related_products || related_products,
1058
- hidespares: hidespares
1254
+ hidespares: hidespares,
1255
+ showProductPrice: showProductPrice,
1256
+ showProductImage: showProductImage
1059
1257
  });
1060
1258
  };
1259
+ const ButtonAdapter = props => {
1260
+ const {
1261
+ label,
1262
+ onClick,
1263
+ className
1264
+ } = props;
1265
+ return /*#__PURE__*/React.createElement(Button, {
1266
+ className: className,
1267
+ onClick: onClick
1268
+ }, label);
1269
+ };
1061
1270
  const AlgoliaDynamicSearchLeybold = props => {
1271
+ const parentComponentProps = {
1272
+ queryType: props?.queryType,
1273
+ showProductPrice: props?.showProductPrice,
1274
+ showProductImage: props?.showProductImage,
1275
+ hitCta: props?.hitCta,
1276
+ hidespares: props?.hidespares
1277
+ };
1062
1278
  const innerComponents = {
1063
- ProductCard: ProductCardAdapter,
1064
- ProductDetailsCard: ProductCardDetailsAdapter
1279
+ Card: innerProps => cardAdapter({
1280
+ ...parentComponentProps,
1281
+ ...innerProps
1282
+ }),
1283
+ ProductCard: innerProps => ProductCardAdapter({
1284
+ ...parentComponentProps,
1285
+ ...innerProps
1286
+ }),
1287
+ ProductDetailsCard: innerProps => ProductCardDetailsAdapter({
1288
+ ...parentComponentProps,
1289
+ ...innerProps
1290
+ }),
1291
+ Button: ButtonAdapter
1065
1292
  };
1066
1293
  return /*#__PURE__*/React.createElement(AlgoliaDynamicSearchRaw, _extends({}, props, {
1067
1294
  innerComponents: innerComponents
@@ -1121,7 +1348,7 @@ const QrFormLeybold = props => {
1121
1348
  })));
1122
1349
  };
1123
1350
 
1124
- var styles$e = {"overlay":"SearchModal-module__overlay___Vbvg9","modal":"SearchModal-module__modal___1k5gO","closeButton":"SearchModal-module__closeButton___AN0zt","closeButton__icon":"SearchModal-module__closeButton__icon___vISSw","closeButton__text":"SearchModal-module__closeButton__text___-4EH1","closeButtonDesktop":"SearchModal-module__closeButtonDesktop___Lxxb0","closeButtonMobile":"SearchModal-module__closeButtonMobile___WIIMS","stickyHeader":"SearchModal-module__stickyHeader___wp-gA","scrollableContent":"SearchModal-module__scrollableContent___lrZP3"};
1351
+ var styles$e = {"overlay":"SearchModal-module__overlay___Vbvg9","overlayOpen":"SearchModal-module__overlayOpen___Ipajg","overlayClosing":"SearchModal-module__overlayClosing___yXsw3","modal":"SearchModal-module__modal___1k5gO","modalOpen":"SearchModal-module__modalOpen___t3hpr","modalClosing":"SearchModal-module__modalClosing___W3m8i","closeButton":"SearchModal-module__closeButton___AN0zt","closeButton__icon":"SearchModal-module__closeButton__icon___vISSw","closeButton__text":"SearchModal-module__closeButton__text___-4EH1","closeButtonDesktop":"SearchModal-module__closeButtonDesktop___Lxxb0","closeButtonMobile":"SearchModal-module__closeButtonMobile___WIIMS","closeButtonMobileWrapper":"SearchModal-module__closeButtonMobileWrapper___LSynI","closeButtonMobileWrapperHidden":"SearchModal-module__closeButtonMobileWrapperHidden___O9nRE","stickyHeader":"SearchModal-module__stickyHeader___wp-gA","stickyHeaderHidden":"SearchModal-module__stickyHeaderHidden___-UI7O","scrollableContent":"SearchModal-module__scrollableContent___lrZP3"};
1125
1352
 
1126
1353
  const ModalCloseButton = ({
1127
1354
  onClick,
@@ -1159,16 +1386,111 @@ const ModalCloseButton = ({
1159
1386
  }, "CLOSE X"));
1160
1387
  };
1161
1388
 
1389
+ const CLOSE_ANIMATION_MS = 600;
1390
+ const prefersReducedMotion = () => {
1391
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1392
+ return false;
1393
+ }
1394
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1395
+ };
1162
1396
  const SearchModal = ({
1163
1397
  isOpen,
1164
1398
  onClose,
1165
- title = 'Search',
1399
+ title = "Search",
1166
1400
  children,
1167
- className = '',
1401
+ className = "",
1168
1402
  stickyHeaderContent
1169
1403
  }) => {
1170
1404
  const modalRef = useRef(null);
1405
+ const scrollableContentRef = useRef(null);
1171
1406
  const previouslyFocusedElement = useRef(null);
1407
+ const lastScrollTopRef = useRef(0);
1408
+ const scrollTickingRef = useRef(false);
1409
+ const [shouldRender, setShouldRender] = useState(isOpen);
1410
+ const [isClosing, setIsClosing] = useState(false);
1411
+ const closeTimerRef = useRef(null);
1412
+ const [isMobileCloseVisible, setIsMobileCloseVisible] = useState(true);
1413
+ const stateClassNames = useMemo(() => {
1414
+ if (isClosing) {
1415
+ return {
1416
+ overlay: styles$e.overlayClosing,
1417
+ modal: styles$e.modalClosing
1418
+ };
1419
+ }
1420
+ return {
1421
+ overlay: styles$e.overlayOpen,
1422
+ modal: styles$e.modalOpen
1423
+ };
1424
+ }, [isClosing]);
1425
+ useEffect(() => {
1426
+ return () => {
1427
+ if (closeTimerRef.current) {
1428
+ window.clearTimeout(closeTimerRef.current);
1429
+ }
1430
+ };
1431
+ }, []);
1432
+ useEffect(() => {
1433
+ if (isOpen) {
1434
+ if (closeTimerRef.current) {
1435
+ window.clearTimeout(closeTimerRef.current);
1436
+ closeTimerRef.current = null;
1437
+ }
1438
+ setShouldRender(true);
1439
+ setIsClosing(false);
1440
+ setIsMobileCloseVisible(true);
1441
+ return;
1442
+ }
1443
+ if (!shouldRender) return;
1444
+ setIsClosing(true);
1445
+ const duration = prefersReducedMotion() ? 0 : CLOSE_ANIMATION_MS;
1446
+ closeTimerRef.current = window.setTimeout(() => {
1447
+ setShouldRender(false);
1448
+ setIsClosing(false);
1449
+ closeTimerRef.current = null;
1450
+ }, duration);
1451
+ }, [isOpen, shouldRender]);
1452
+
1453
+ // Mobile-only: hide close button when scrolling down, show when scrolling up.
1454
+ useEffect(() => {
1455
+ if (!isOpen) return;
1456
+ // Important: this component renders `null` until `shouldRender` becomes true.
1457
+ // We need to wait for that render so `scrollableContentRef.current` exists.
1458
+ if (!shouldRender) return;
1459
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1460
+ const isMobile = window.matchMedia("(max-width: 768px)").matches;
1461
+ if (!isMobile) return;
1462
+ const el = scrollableContentRef.current;
1463
+ if (!el) return;
1464
+ lastScrollTopRef.current = el.scrollTop;
1465
+ setIsMobileCloseVisible(true);
1466
+ const thresholdPx = 10;
1467
+ const topRevealPx = 8;
1468
+ const update = () => {
1469
+ scrollTickingRef.current = false;
1470
+ const current = el.scrollTop;
1471
+ const prev = lastScrollTopRef.current;
1472
+ const delta = current - prev;
1473
+ if (current <= topRevealPx) {
1474
+ setIsMobileCloseVisible(true);
1475
+ } else if (delta > thresholdPx) {
1476
+ setIsMobileCloseVisible(false);
1477
+ } else if (delta < -thresholdPx) {
1478
+ setIsMobileCloseVisible(true);
1479
+ }
1480
+ lastScrollTopRef.current = current;
1481
+ };
1482
+ const onScroll = () => {
1483
+ if (scrollTickingRef.current) return;
1484
+ scrollTickingRef.current = true;
1485
+ window.requestAnimationFrame(update);
1486
+ };
1487
+ el.addEventListener("scroll", onScroll, {
1488
+ passive: true
1489
+ });
1490
+ return () => {
1491
+ el.removeEventListener("scroll", onScroll);
1492
+ };
1493
+ }, [isOpen, shouldRender]);
1172
1494
  useEffect(() => {
1173
1495
  if (isOpen) {
1174
1496
  // Store the previously focused element
@@ -1180,28 +1502,32 @@ const SearchModal = ({
1180
1502
  }
1181
1503
 
1182
1504
  // Prevent body scroll
1183
- document.body.style.overflow = 'hidden';
1505
+ document.body.style.overflow = "hidden";
1184
1506
  } else {
1185
- // Restore body scroll
1186
- document.body.style.overflow = '';
1187
-
1188
1507
  // Return focus to previously focused element
1189
1508
  if (previouslyFocusedElement.current) {
1190
1509
  previouslyFocusedElement.current.focus();
1191
1510
  }
1192
1511
  }
1512
+ }, [isOpen]);
1513
+ useEffect(() => {
1514
+ if (shouldRender) {
1515
+ document.body.style.overflow = "hidden";
1516
+ } else {
1517
+ document.body.style.overflow = "";
1518
+ }
1193
1519
  return () => {
1194
- document.body.style.overflow = '';
1520
+ document.body.style.overflow = "";
1195
1521
  };
1196
- }, [isOpen]);
1522
+ }, [shouldRender]);
1197
1523
  useEffect(() => {
1198
1524
  const handleEscape = event => {
1199
- if (event.key === 'Escape' && isOpen) {
1525
+ if (event.key === "Escape" && isOpen) {
1200
1526
  onClose();
1201
1527
  }
1202
1528
  };
1203
- document.addEventListener('keydown', handleEscape);
1204
- return () => document.removeEventListener('keydown', handleEscape);
1529
+ document.addEventListener("keydown", handleEscape);
1530
+ return () => document.removeEventListener("keydown", handleEscape);
1205
1531
  }, [isOpen, onClose]);
1206
1532
 
1207
1533
  // Focus trap implementation
@@ -1212,7 +1538,7 @@ const SearchModal = ({
1212
1538
  const firstElement = focusableElements[0];
1213
1539
  const lastElement = focusableElements[focusableElements.length - 1];
1214
1540
  const handleTab = event => {
1215
- if (event.key !== 'Tab') return;
1541
+ if (event.key !== "Tab") return;
1216
1542
  if (event.shiftKey) {
1217
1543
  // Shift + Tab
1218
1544
  if (document.activeElement === firstElement) {
@@ -1227,17 +1553,18 @@ const SearchModal = ({
1227
1553
  }
1228
1554
  }
1229
1555
  };
1230
- modal.addEventListener('keydown', handleTab);
1231
- return () => modal.removeEventListener('keydown', handleTab);
1556
+ modal.addEventListener("keydown", handleTab);
1557
+ return () => modal.removeEventListener("keydown", handleTab);
1232
1558
  }, [isOpen]);
1233
- if (!isOpen) return null;
1559
+ if (!shouldRender) return null;
1234
1560
  const handleOverlayClick = event => {
1561
+ if (isClosing) return;
1235
1562
  if (event.target === event.currentTarget) {
1236
1563
  onClose();
1237
1564
  }
1238
1565
  };
1239
1566
  return /*#__PURE__*/React.createElement("div", {
1240
- className: styles$e.overlay,
1567
+ className: `${styles$e.overlay} ${stateClassNames.overlay}`,
1241
1568
  onClick: handleOverlayClick,
1242
1569
  "aria-hidden": "true"
1243
1570
  }, /*#__PURE__*/React.createElement("div", {
@@ -1246,13 +1573,16 @@ const SearchModal = ({
1246
1573
  "aria-modal": "true",
1247
1574
  "aria-label": title,
1248
1575
  tabIndex: -1,
1249
- className: `${styles$e.modal} ${className}`
1576
+ className: `${styles$e.modal} ${stateClassNames.modal} ${className}`
1577
+ }, /*#__PURE__*/React.createElement("div", {
1578
+ className: `${styles$e.stickyHeader}` + ` ${!isMobileCloseVisible ? styles$e.stickyHeaderHidden : ""}`
1250
1579
  }, /*#__PURE__*/React.createElement("div", {
1251
- className: styles$e.stickyHeader
1580
+ className: `${styles$e.closeButtonMobileWrapper} ${isMobileCloseVisible ? "" : styles$e.closeButtonMobileWrapperHidden}`
1252
1581
  }, /*#__PURE__*/React.createElement(ModalCloseButton, {
1253
1582
  onClick: onClose,
1254
1583
  className: styles$e.closeButtonMobile
1255
- }), stickyHeaderContent), /*#__PURE__*/React.createElement("div", {
1584
+ })), stickyHeaderContent), /*#__PURE__*/React.createElement("div", {
1585
+ ref: scrollableContentRef,
1256
1586
  className: styles$e.scrollableContent
1257
1587
  }, /*#__PURE__*/React.createElement(ModalCloseButton, {
1258
1588
  onClick: onClose,
@@ -1310,17 +1640,23 @@ const SearchInput = ({
1310
1640
  const SearchSubmitButton = ({
1311
1641
  onClick,
1312
1642
  disabled = false,
1313
- ariaLabel = 'Search',
1314
- label = 'Search',
1315
- variant = 'instant',
1316
- className = ''
1643
+ ariaLabel = "Search",
1644
+ label = "Search",
1645
+ variant = "instant",
1646
+ className = ""
1317
1647
  }) => {
1318
- return /*#__PURE__*/React.createElement("button", {
1648
+ const transformVariant = variant === "instant" ? "solid-grey" : "primary";
1649
+ return /*#__PURE__*/React.createElement(Button, {
1319
1650
  type: "submit",
1651
+ size: "small",
1320
1652
  onClick: onClick,
1321
1653
  disabled: disabled,
1322
1654
  "aria-label": ariaLabel,
1323
- className: `${styles$d.submitButton} ${className}`
1655
+ className: `${styles$d.submitButton} ${className}`,
1656
+ variant: transformVariant,
1657
+ style: {
1658
+ minWidth: "unset" // Ensure button doesn't shrink too much when only showing icon
1659
+ }
1324
1660
  }, /*#__PURE__*/React.createElement("span", {
1325
1661
  className: styles$d.submitButton__text
1326
1662
  }, label), /*#__PURE__*/React.createElement("span", {
@@ -1334,21 +1670,21 @@ const SearchBar = ({
1334
1670
  onSubmit,
1335
1671
  placeholder,
1336
1672
  autoFocus = false,
1337
- className = '',
1338
- variant = 'instant'
1673
+ className = "",
1674
+ variant = "instant"
1339
1675
  }) => {
1340
1676
  const handleSubmit = event => {
1341
1677
  event.preventDefault();
1342
1678
  onSubmit();
1343
1679
  };
1344
1680
  const handleKeyDown = event => {
1345
- if (event.key === 'Enter') {
1681
+ if (event.key === "Enter") {
1346
1682
  event.preventDefault();
1347
1683
  onSubmit();
1348
1684
  }
1349
1685
  };
1350
1686
  const handleClear = () => {
1351
- onChange('');
1687
+ onChange("");
1352
1688
  };
1353
1689
  const showClearButton = value.length > 0;
1354
1690
  return /*#__PURE__*/React.createElement("form", {
@@ -1370,7 +1706,7 @@ const SearchBar = ({
1370
1706
  }, "CLEAR X")), /*#__PURE__*/React.createElement(SearchSubmitButton, {
1371
1707
  onClick: onSubmit,
1372
1708
  label: "SEE ALL RESULTS",
1373
- variant: variant
1709
+ variant: "results"
1374
1710
  }));
1375
1711
  };
1376
1712
 
@@ -1387,21 +1723,20 @@ const ContentCardHorizontal = ({
1387
1723
  variant = "instant-view"
1388
1724
  }) => {
1389
1725
  const cardClasses = classNames(styles$c.contentCard, styles$c[`contentCard--${variant}`], className);
1390
- return /*#__PURE__*/React.createElement("a", {
1391
- href: url,
1726
+ return /*#__PURE__*/React.createElement(ContentCardBase, {
1727
+ id: id,
1728
+ title: title,
1729
+ url: url,
1730
+ category: category,
1731
+ meta: meta,
1732
+ excerpt: excerpt,
1392
1733
  className: cardClasses,
1393
- "aria-label": `View content: ${title}`
1394
- }, /*#__PURE__*/React.createElement("div", {
1395
- className: styles$c.contentMeta
1396
- }, category && /*#__PURE__*/React.createElement("span", {
1397
- className: styles$c.contentCategory
1398
- }, category), meta && /*#__PURE__*/React.createElement("span", {
1399
- className: styles$c.contentMetaText
1400
- }, meta)), /*#__PURE__*/React.createElement("h3", {
1401
- className: styles$c.contentTitle
1402
- }, title), excerpt && /*#__PURE__*/React.createElement("p", {
1403
- className: styles$c.contentExcerpt
1404
- }, excerpt));
1734
+ contentMetaClassName: styles$c.contentMeta,
1735
+ contentCategoryClassName: styles$c.contentCategory,
1736
+ contentMetaTextClassName: styles$c.contentMetaText,
1737
+ contentTitleClassName: styles$c.contentTitle,
1738
+ contentExcerptClassName: styles$c.contentExcerpt
1739
+ });
1405
1740
  };
1406
1741
 
1407
1742
  var styles$b = {"instantResultsLayout":"InstantResults-module__instantResultsLayout___oy-o4","columnsGrid":"InstantResults-module__columnsGrid___bHRUM","resultsColumn":"InstantResults-module__resultsColumn___ZBSlT","columnHeader":"InstantResults-module__columnHeader___VYYhi","showingText":"InstantResults-module__showingText___lECiA","seeAllButton":"InstantResults-module__seeAllButton___xEDAX","chevronIcon":"InstantResults-module__chevronIcon___bjb3q","resultsList":"InstantResults-module__resultsList___7s3PT","divider":"InstantResults-module__divider___Ky6zK","loadingState":"InstantResults-module__loadingState___l0fMq","errorState":"InstantResults-module__errorState___hTBbE","spinner":"InstantResults-module__spinner___85jF-"};
@@ -1462,7 +1797,10 @@ const FederatedInstantResultsLayout = ({
1462
1797
  className: styles$b.resultsList
1463
1798
  }, displayProducts.map((product, index) => /*#__PURE__*/React.createElement(React.Fragment, {
1464
1799
  key: product.id
1465
- }, /*#__PURE__*/React.createElement(ProductCardHorizontal, product), index < displayProducts.length - 1 && /*#__PURE__*/React.createElement("hr", {
1800
+ }, /*#__PURE__*/React.createElement(ProductCardHorizontal, _extends({}, product, {
1801
+ showProductPrice: true,
1802
+ className: "search-result"
1803
+ })), index < displayProducts.length - 1 && /*#__PURE__*/React.createElement("hr", {
1466
1804
  className: styles$b.divider
1467
1805
  })))) : null), /*#__PURE__*/React.createElement("div", {
1468
1806
  className: styles$b.resultsColumn
@@ -1601,8 +1939,8 @@ const faXmark = {
1601
1939
  icon: [384, 512, [128473, 10005, 10006, 10060, 215, "close", "multiply", "remove", "times"], "f00d", "M324.5 411.1c6.2 6.2 16.4 6.2 22.6 0s6.2-16.4 0-22.6L214.6 256 347.1 123.5c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L192 233.4 59.6 100.9c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6L169.4 256 36.9 388.5c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L192 278.6 324.5 411.1z"]
1602
1940
  };
1603
1941
 
1604
- /**
1605
- * FilterSearch - Search input for filtering facet values
1942
+ /**
1943
+ * FilterSearch - Search input for filtering facet values
1606
1944
  */
1607
1945
  const FilterSearch = ({
1608
1946
  value,
@@ -1650,8 +1988,8 @@ const CheckIcon = () => /*#__PURE__*/React.createElement("svg", {
1650
1988
  fill: "#E2001A"
1651
1989
  }));
1652
1990
 
1653
- /**
1654
- * FilterItem - Individual facet item with checkbox and count
1991
+ /**
1992
+ * FilterItem - Individual facet item with checkbox and count
1655
1993
  */
1656
1994
  const FilterItem = ({
1657
1995
  value,
@@ -1663,6 +2001,13 @@ const FilterItem = ({
1663
2001
  const handleChange = () => {
1664
2002
  onToggle(value.value);
1665
2003
  };
2004
+ const handleCheckboxKeyDown = e => {
2005
+ // Space toggles native checkboxes by default; add Enter support.
2006
+ if (e.key === "Enter") {
2007
+ e.preventDefault();
2008
+ handleChange();
2009
+ }
2010
+ };
1666
2011
  const handleKeyDown = e => {
1667
2012
  if (e.key === "Enter" || e.key === " ") {
1668
2013
  e.preventDefault();
@@ -1702,12 +2047,12 @@ const FilterItem = ({
1702
2047
 
1703
2048
  // Desktop with checkbox (Products tab): Multi-select checkboxes
1704
2049
  return /*#__PURE__*/React.createElement("label", {
1705
- className: `${styles$7.filterItem} ${className || ""}`,
1706
- onKeyDown: handleKeyDown
2050
+ className: `${styles$7.filterItem} ${className || ""}`
1707
2051
  }, /*#__PURE__*/React.createElement("input", {
1708
2052
  type: "checkbox",
1709
2053
  checked: value.isRefined,
1710
2054
  onChange: handleChange,
2055
+ onKeyDown: handleCheckboxKeyDown,
1711
2056
  className: styles$7.filterCheckboxInput,
1712
2057
  "aria-label": `${value.value} (${value.count} results)`
1713
2058
  }), /*#__PURE__*/React.createElement("div", {
@@ -1719,8 +2064,8 @@ const FilterItem = ({
1719
2064
  }, "(", value.count, ")"));
1720
2065
  };
1721
2066
 
1722
- /**
1723
- * FilterAccordion - Collapsible facet section with optional search
2067
+ /**
2068
+ * FilterAccordion - Collapsible facet section with optional search
1724
2069
  */
1725
2070
  const FilterAccordion = ({
1726
2071
  facet,
@@ -1728,7 +2073,7 @@ const FilterAccordion = ({
1728
2073
  onToggle,
1729
2074
  onValueToggle,
1730
2075
  className,
1731
- variant = 'desktop',
2076
+ variant = "desktop",
1732
2077
  showCheckbox = true
1733
2078
  }) => {
1734
2079
  const [searchQuery, setSearchQuery] = useState("");
@@ -1785,7 +2130,7 @@ const FilterAccordion = ({
1785
2130
  }))), isExpanded && /*#__PURE__*/React.createElement("div", {
1786
2131
  id: `accordion-${facet.id}`,
1787
2132
  className: styles$7.accordionContent
1788
- }, variant === 'desktop' && facet.searchable && facet.values.length > 5 && /*#__PURE__*/React.createElement("div", {
2133
+ }, facet.searchable && facet.values.length > 5 && (variant === "desktop" || variant === "mobile" && showCheckbox) && /*#__PURE__*/React.createElement("div", {
1789
2134
  className: styles$7.accordionSearch
1790
2135
  }, /*#__PURE__*/React.createElement(FilterSearch, {
1791
2136
  value: searchQuery,
@@ -1804,8 +2149,8 @@ const FilterAccordion = ({
1804
2149
  }, "No results found"))));
1805
2150
  };
1806
2151
 
1807
- /**
1808
- * AppliedFilterTag - Individual removable filter tag/chip
2152
+ /**
2153
+ * AppliedFilterTag - Individual removable filter tag/chip
1809
2154
  */
1810
2155
  const AppliedFilterTag = ({
1811
2156
  attribute,
@@ -1849,8 +2194,8 @@ const AppliedFilterTag = ({
1849
2194
  }))));
1850
2195
  };
1851
2196
 
1852
- /**
1853
- * AppliedFilters - Shows all active filters as removable tags
2197
+ /**
2198
+ * AppliedFilters - Shows all active filters as removable tags
1854
2199
  */
1855
2200
  const AppliedFilters = ({
1856
2201
  facets,
@@ -1895,12 +2240,12 @@ const AppliedFilters = ({
1895
2240
  }))));
1896
2241
  };
1897
2242
 
1898
- /**
1899
- * FiltersPanel - Main container for all filters
1900
- *
1901
- * Fully controlled component - renders based on facets prop.
1902
- * When facets change (e.g., after API refetch), component re-renders with new data.
1903
- * Accordion expansion state persists across refetches, with new facets respecting defaultExpanded.
2243
+ /**
2244
+ * FiltersPanel - Main container for all filters
2245
+ *
2246
+ * Fully controlled component - renders based on facets prop.
2247
+ * When facets change (e.g., after API refetch), component re-renders with new data.
2248
+ * Accordion expansion state persists across refetches, with new facets respecting defaultExpanded.
1904
2249
  */
1905
2250
  const FiltersPanel = ({
1906
2251
  facets,
@@ -2000,25 +2345,32 @@ const FiltersPanel = ({
2000
2345
  }))));
2001
2346
  };
2002
2347
 
2003
- var styles$6 = {"productCardVertical":"ProductCardVertical-module__productCardVertical___kXgmt","productCardVertical--hover":"ProductCardVertical-module__productCardVertical--hover___8Nsyp","productCardVertical__button":"ProductCardVertical-module__productCardVertical__button___oQPJG","productCardVertical--focus":"ProductCardVertical-module__productCardVertical--focus___8U57I","productCardVertical__imageWrapper":"ProductCardVertical-module__productCardVertical__imageWrapper___SgKoZ","productCardVertical__image":"ProductCardVertical-module__productCardVertical__image___kg-QU","productCardVertical__content":"ProductCardVertical-module__productCardVertical__content___sZdOs","productCardVertical__title":"ProductCardVertical-module__productCardVertical__title___PPKWb","productCardVertical__description":"ProductCardVertical-module__productCardVertical__description___Ai90p","productCardVertical__footer":"ProductCardVertical-module__productCardVertical__footer___rv6BH"};
2004
-
2005
- /**
2006
- * ProductCardVertical - Vertical product card for grid layouts
2007
- *
2008
- * Used in full results view with 4-column grid (desktop) or 2-column grid (mobile).
2009
- * Figma spec: Default (no shadow) → Hover (shadow + grey button) → Focus (border + shadow)
2010
- *
2011
- * @example
2012
- * ```tsx
2013
- * <ProductCardVertical
2014
- * title="TURBOVAC i - High vacuum turbopump"
2015
- * url="/products/turbovac-i"
2016
- * imageUrl="/assets/search-product.png"
2017
- * productId="21312VISH"
2018
- * description="Premium high-performance turbomolecular pump"
2019
- * buttonLabel="BUY ONLINE"
2020
- * />
2021
- * ```
2348
+ var styles$6 = {"productCardVertical":"ProductCardVertical-module__productCardVertical___kXgmt","productCardVertical--hover":"ProductCardVertical-module__productCardVertical--hover___8Nsyp","productCardVertical__button":"ProductCardVertical-module__productCardVertical__button___oQPJG","productCardVertical--focus":"ProductCardVertical-module__productCardVertical--focus___8U57I","productCardVertical__imageWrapper":"ProductCardVertical-module__productCardVertical__imageWrapper___SgKoZ","productCardVertical__image":"ProductCardVertical-module__productCardVertical__image___kg-QU","productCardVertical__placeholderImage":"ProductCardVertical-module__productCardVertical__placeholderImage___w0sd0","productCardVertical__content":"ProductCardVertical-module__productCardVertical__content___sZdOs","productCardVertical__title":"ProductCardVertical-module__productCardVertical__title___PPKWb","productCardVertical__description":"ProductCardVertical-module__productCardVertical__description___Ai90p","productCardVertical__footer":"ProductCardVertical-module__productCardVertical__footer___rv6BH"};
2349
+
2350
+ const PLACEHOLDER_INDICATOR = "?placeholder-storybook";
2351
+ const PLACEHOLDER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="440" height="133" viewBox="218.446 219.139 440 133" role="img" aria-label="Leybold placeholder logo"><g fill="#ffff"><path d="M290.317,313.442h37.784v12.782h-53.654v-70.62h15.87V313.442z"/><path d="M386.309,310.883c-3.912,12.593-15.135,16.722-24.761,16.722c-15.878,0-28.046-7.084-28.046-28.62c0-6.296,2.322-26.458,26.98-26.458c11.116,0,26.457,4.917,26.457,28.819v2.457h-38.626c0.428,3.935,1.274,12.785,13.232,12.785c4.125,0,8.356-1.965,9.521-5.704L386.309,310.883L386.309,310.883z M371.709,294.852c-0.85-8.456-6.666-11.017-11.434-11.017c-6.979,0-10.686,4.129-11.532,11.017H371.709z"/><path d="M423.556,325.343c-6.986,19.374-8.892,21.932-21.799,21.932c-2.014,0-4.553-0.096-6.665-0.194v-11.02c0.629,0.104,1.582,0.197,2.854,0.197c5.397,0,8.36-0.688,9.737-7.864l-20.741-54.488h16.508l12.062,38.553h0.209l11.539-38.553h15.658L423.556,325.343z"/><path d="M463.54,280.689h0.213c2.328-3.344,6.775-8.164,16.297-8.164c12.385,0,23.389,8.854,23.389,26.949c0,14.359-6.98,28.129-23.703,28.129c-6.141,0-12.701-2.067-16.299-7.968h-0.211v6.589h-14.498v-70.62h14.812V280.689z M475.604,284.427c-10.158,0-12.701,8.455-12.701,16.815c0,7.773,3.707,15.049,13.129,15.049c9.521,0,12.168-9.641,12.168-15.83C488.198,292.192,485.02,284.427,475.604,284.427z"/><path d="M538.094,327.604c-15.141,0-28.787-8.652-28.787-27.542c0-18.881,13.646-27.536,28.787-27.536c15.129,0,28.785,8.655,28.785,27.536C566.88,318.95,553.223,327.604,538.094,327.604z M538.094,284.129c-11.434,0-13.547,9.244-13.547,15.934c0,6.691,2.113,15.939,13.547,15.939c11.426,0,13.547-9.248,13.547-15.939C551.641,293.373,549.52,284.129,538.094,284.129z"/><path d="M588.811,326.225h-14.814v-70.62h14.814V326.225z"/><path d="M651.969,326.225h-14.5v-6.589h-0.213c-3.598,5.898-10.156,7.968-16.295,7.968c-16.727,0-23.703-13.77-23.703-28.129c0-18.097,11.004-26.949,23.381-26.949c9.523,0,13.975,4.82,16.295,8.164h0.221v-25.085h14.814V326.225z M624.663,316.291c9.418,0,13.121-7.273,13.121-15.05c0-8.359-2.537-16.814-12.703-16.814c-9.416,0-12.592,7.767-12.592,16.034C612.49,306.651,615.135,316.291,624.663,316.291z"/></g><g fill="#ffff"><path d="M268.323,226.771h-41.551v39.685C230.996,246.516,247.442,230.811,268.323,226.771z"/><path d="M226.771,286.726v39.688h41.551C247.442,322.371,230.996,306.67,226.771,286.726z"/><path d="M331.122,266.455V226.77h-41.552C310.449,230.811,326.892,246.516,331.122,266.455z"/></g></svg>`;
2352
+ const PLACEHOLDER_IMAGE_DATA_URI = `data:image/svg+xml,${encodeURIComponent(PLACEHOLDER_SVG)}`;
2353
+ const isPlaceholderImg = url => {
2354
+ return !!url && url.includes(PLACEHOLDER_INDICATOR);
2355
+ };
2356
+
2357
+ /**
2358
+ * ProductCardVertical - Vertical product card for grid layouts
2359
+ *
2360
+ * Used in full results view with 4-column grid (desktop) or 2-column grid (mobile).
2361
+ * Figma spec: Default (no shadow) → Hover (shadow + grey button) → Focus (border + shadow)
2362
+ *
2363
+ * @example
2364
+ * ```tsx
2365
+ * <ProductCardVertical
2366
+ * title="TURBOVAC i - High vacuum turbopump"
2367
+ * url="/products/turbovac-i"
2368
+ * imageUrl="/assets/search-product.png"
2369
+ * productId="21312VISH"
2370
+ * description="Premium high-performance turbomolecular pump"
2371
+ * buttonLabel="BUY ONLINE"
2372
+ * />
2373
+ * ```
2022
2374
  */
2023
2375
  const ProductCardVertical = ({
2024
2376
  id,
@@ -2027,35 +2379,38 @@ const ProductCardVertical = ({
2027
2379
  imageUrl,
2028
2380
  productId,
2029
2381
  description,
2030
- buttonLabel = 'BUY ONLINE',
2382
+ buttonLabel = "BUY ONLINE",
2031
2383
  onButtonClick,
2032
2384
  className,
2033
- variant = 'desktop',
2034
- 'data-force-state': forceState
2385
+ variant = "desktop",
2386
+ "data-force-state": forceState
2035
2387
  }) => {
2388
+ const hasPlaceholderImage = !imageUrl || isPlaceholderImg(imageUrl);
2389
+ const productImage = hasPlaceholderImage ? PLACEHOLDER_IMAGE_DATA_URI : imageUrl;
2036
2390
  const handleButtonClick = e => {
2037
2391
  e.preventDefault(); // Prevent card link navigation
2038
2392
  e.stopPropagation(); // Stop event bubbling
2039
2393
  onButtonClick?.();
2040
2394
  };
2041
2395
  const cardClasses = classNames(styles$6.productCardVertical, styles$6[`productCardVertical--${variant}`], {
2042
- [styles$6['productCardVertical--hover']]: forceState === 'hover',
2043
- [styles$6['productCardVertical--focus']]: forceState === 'focus'
2396
+ [styles$6["productCardVertical--hover"]]: forceState === "hover",
2397
+ [styles$6["productCardVertical--focus"]]: forceState === "focus"
2044
2398
  }, className);
2045
2399
  return /*#__PURE__*/React.createElement("a", {
2046
2400
  href: url,
2047
2401
  className: cardClasses,
2048
2402
  "aria-label": `View product: ${title}`,
2049
2403
  "data-product-id": id
2050
- }, imageUrl && /*#__PURE__*/React.createElement("div", {
2404
+ }, /*#__PURE__*/React.createElement("div", {
2051
2405
  className: styles$6.productCardVertical__imageWrapper
2052
2406
  }, /*#__PURE__*/React.createElement(Image, {
2053
- src: imageUrl,
2407
+ src: productImage,
2054
2408
  alt: title,
2055
- className: styles$6.productCardVertical__image,
2409
+ className: styles$6.productCardVertical__image + (hasPlaceholderImage ? ` ${styles$6.productCardVertical__placeholderImage}` : ""),
2056
2410
  objectFit: "contain",
2057
2411
  objectPosition: "center",
2058
- loading: "lazy"
2412
+ loading: "lazy",
2413
+ fallbackSrc: PLACEHOLDER_IMAGE_DATA_URI
2059
2414
  })), /*#__PURE__*/React.createElement("div", {
2060
2415
  className: styles$6.productCardVertical__content
2061
2416
  }, /*#__PURE__*/React.createElement("h3", {
@@ -2074,7 +2429,7 @@ const ProductCardVertical = ({
2074
2429
  })));
2075
2430
  };
2076
2431
 
2077
- var styles$5 = {"resultsView":"ResultsView-module__resultsView___S4Wh2","searchBarWrapper":"ResultsView-module__searchBarWrapper___XNtwR","tabsContainer":"ResultsView-module__tabsContainer___mB-Q2","tabs":"ResultsView-module__tabs___rOexd","tabSeparator":"ResultsView-module__tabSeparator___MSq9p","mobileFilterButton":"ResultsView-module__mobileFilterButton___ZrMQY","resultsContainer":"ResultsView-module__resultsContainer___J4URa","sidebar":"ResultsView-module__sidebar___nQq5J","assistanceBanner":"ResultsView-module__assistanceBanner___1r72a","mainContent":"ResultsView-module__mainContent___S9eIG","productsGrid":"ResultsView-module__productsGrid___gnAQ-","contentsList":"ResultsView-module__contentsList___tcfNG","contentDivider":"ResultsView-module__contentDivider___5n6sl","resultsList":"ResultsView-module__resultsList___8eYNX","resultsSection":"ResultsView-module__resultsSection___sCUaO","sectionTitle":"ResultsView-module__sectionTitle___mfvH3","loadingState":"ResultsView-module__loadingState___W5YXx","errorState":"ResultsView-module__errorState___UkkG-","emptyState":"ResultsView-module__emptyState___D0Iyn","spinner":"ResultsView-module__spinner___nk8E5","emptyIcon":"ResultsView-module__emptyIcon___fes8T","paginationWrapper":"ResultsView-module__paginationWrapper___mDTyl","searchBarDesktopOnly":"ResultsView-module__searchBarDesktopOnly___dZHUw","tabsContainerDesktopOnly":"ResultsView-module__tabsContainerDesktopOnly___-MQpa"};
2432
+ var styles$5 = {"resultsView":"ResultsView-module__resultsView___S4Wh2","searchBarWrapper":"ResultsView-module__searchBarWrapper___XNtwR","tabsContainer":"ResultsView-module__tabsContainer___mB-Q2","tabs":"ResultsView-module__tabs___rOexd","mobileFilterButton":"ResultsView-module__mobileFilterButton___ZrMQY","resultsContainer":"ResultsView-module__resultsContainer___J4URa","sidebar":"ResultsView-module__sidebar___nQq5J","assistanceBanner":"ResultsView-module__assistanceBanner___1r72a","mainContent":"ResultsView-module__mainContent___S9eIG","productsGrid":"ResultsView-module__productsGrid___gnAQ-","contentsList":"ResultsView-module__contentsList___tcfNG","contentDivider":"ResultsView-module__contentDivider___5n6sl","resultsList":"ResultsView-module__resultsList___8eYNX","resultsSection":"ResultsView-module__resultsSection___sCUaO","sectionTitle":"ResultsView-module__sectionTitle___mfvH3","loadingState":"ResultsView-module__loadingState___W5YXx","errorState":"ResultsView-module__errorState___UkkG-","emptyState":"ResultsView-module__emptyState___D0Iyn","spinner":"ResultsView-module__spinner___nk8E5","emptyIcon":"ResultsView-module__emptyIcon___fes8T","paginationWrapper":"ResultsView-module__paginationWrapper___mDTyl","searchBarDesktopOnly":"ResultsView-module__searchBarDesktopOnly___dZHUw","tabsContainerDesktopOnly":"ResultsView-module__tabsContainerDesktopOnly___-MQpa"};
2078
2433
 
2079
2434
  const ResultsList = ({
2080
2435
  type,
@@ -2173,32 +2528,32 @@ const ResultsList = ({
2173
2528
 
2174
2529
  var styles$4 = {"tabButton":"TabButton-module__tabButton___cMU45","tabButton__count":"TabButton-module__tabButton__count___nlS1p","tabButton__label":"TabButton-module__tabButton__label___YGpiC","tabButton__results":"TabButton-module__tabButton__results___Nr6gr","tabButton--desktop":"TabButton-module__tabButton--desktop___fbAaC","tabButton--active":"TabButton-module__tabButton--active___gj6Jp","tabButton--mobile":"TabButton-module__tabButton--mobile___AnLwy"};
2175
2530
 
2176
- /**
2177
- * TabButton - Tab navigation component with result counts
2178
- *
2179
- * Supports two layout variants:
2180
- * - Desktop: Background colours, top red underline when active, rounded corners
2181
- * - Mobile: Text-only with bottom border underline when active
2182
- *
2183
- * @example
2184
- * ```tsx
2185
- * // Desktop variant
2186
- * <TabButton
2187
- * label="WEB SHOP RESULTS"
2188
- * count={542}
2189
- * isActive={true}
2190
- * onClick={() => setActiveTab('products')}
2191
- * variant="desktop"
2192
- * />
2193
- *
2194
- * // Mobile variant
2195
- * <TabButton
2196
- * label="WEB SHOP"
2197
- * isActive={false}
2198
- * onClick={() => setActiveTab('content')}
2199
- * variant="mobile"
2200
- * />
2201
- * ```
2531
+ /**
2532
+ * TabButton - Tab navigation component with result counts
2533
+ *
2534
+ * Supports two layout variants:
2535
+ * - Desktop: Background colours, top red underline when active, rounded corners
2536
+ * - Mobile: Text-only with bottom border underline when active
2537
+ *
2538
+ * @example
2539
+ * ```tsx
2540
+ * // Desktop variant
2541
+ * <TabButton
2542
+ * label="WEB SHOP RESULTS"
2543
+ * count={542}
2544
+ * isActive={true}
2545
+ * onClick={() => setActiveTab('products')}
2546
+ * variant="desktop"
2547
+ * />
2548
+ *
2549
+ * // Mobile variant
2550
+ * <TabButton
2551
+ * label="WEB SHOP"
2552
+ * isActive={false}
2553
+ * onClick={() => setActiveTab('content')}
2554
+ * variant="mobile"
2555
+ * />
2556
+ * ```
2202
2557
  */
2203
2558
  const TabButton = ({
2204
2559
  label,
@@ -2247,23 +2602,23 @@ const AssistanceIcon = props => {
2247
2602
 
2248
2603
  var styles$3 = {"assistanceBanner":"AssistanceBanner-module__assistanceBanner___b97es","assistanceBanner__icon":"AssistanceBanner-module__assistanceBanner__icon___8NzJj","assistanceBanner__title":"AssistanceBanner-module__assistanceBanner__title___Wtkkt","assistanceBanner__description":"AssistanceBanner-module__assistanceBanner__description___AjoOr","assistanceBanner__link":"AssistanceBanner-module__assistanceBanner__link___5L45N","assistanceBanner__chevron":"AssistanceBanner-module__assistanceBanner__chevron___4km-f"};
2249
2604
 
2250
- /**
2251
- * AssistanceBanner - Help widget for search sidebar
2252
- *
2253
- * Displays a call-to-action for users who need assistance finding products.
2254
- * Features chat icon, heading, description, and contact link with chevron.
2255
- *
2256
- * Desktop only - hidden on mobile to save space.
2257
- *
2258
- * @example
2259
- * ```tsx
2260
- * <AssistanceBanner
2261
- * title="Need Assistance?"
2262
- * description="Can't find what you're looking for? Our team is ready to help."
2263
- * linkText="Contact support"
2264
- * linkUrl="/contact"
2265
- * />
2266
- * ```
2605
+ /**
2606
+ * AssistanceBanner - Help widget for search sidebar
2607
+ *
2608
+ * Displays a call-to-action for users who need assistance finding products.
2609
+ * Features chat icon, heading, description, and contact link with chevron.
2610
+ *
2611
+ * Desktop only - hidden on mobile to save space.
2612
+ *
2613
+ * @example
2614
+ * ```tsx
2615
+ * <AssistanceBanner
2616
+ * title="Need Assistance?"
2617
+ * description="Can't find what you're looking for? Our team is ready to help."
2618
+ * linkText="Contact support"
2619
+ * linkUrl="/contact"
2620
+ * />
2621
+ * ```
2267
2622
  */
2268
2623
  const AssistanceBanner = ({
2269
2624
  title = 'Need Assistance?',
@@ -2310,27 +2665,31 @@ const AssistanceBanner = ({
2310
2665
  }))));
2311
2666
  };
2312
2667
 
2313
- var styles$2 = {"drawerOverlay":"FilterDrawer-module__drawerOverlay___P6M4y","drawerOverlay--open":"FilterDrawer-module__drawerOverlay--open___blJZo","drawerPanel":"FilterDrawer-module__drawerPanel___35h-U","drawerPanel--open":"FilterDrawer-module__drawerPanel--open___Fw1SY","drawerCloseButton":"FilterDrawer-module__drawerCloseButton___cfMmf","drawerContent":"FilterDrawer-module__drawerContent___KBff6"};
2314
-
2315
- /**
2316
- * FilterDrawer - Mobile slide-in filter panel
2317
- *
2318
- * Slides in from the right with a dark overlay. Used on mobile to show filters
2319
- * when screen space is limited. Includes focus trap, escape key handling, and
2320
- * body scroll prevention.
2321
- *
2322
- * Desktop: Hidden (filters shown in sidebar)
2323
- * Mobile: Slide-in drawer overlay
2324
- *
2325
- * @example
2326
- * ```tsx
2327
- * <FilterDrawer
2328
- * isOpen={isFilterDrawerOpen}
2329
- * onClose={() => setIsFilterDrawerOpen(false)}
2330
- * >
2331
- * <FiltersPanel facets={facets} onFacetToggle={handleToggle} />
2332
- * </FilterDrawer>
2333
- * ```
2668
+ var styles$2 = {"drawerOverlay":"FilterDrawer-module__drawerOverlay___P6M4y","drawerOverlay--open":"FilterDrawer-module__drawerOverlay--open___blJZo","drawerOverlay--closing":"FilterDrawer-module__drawerOverlay--closing___diQr0","drawerPanel":"FilterDrawer-module__drawerPanel___35h-U","drawerPanel--open":"FilterDrawer-module__drawerPanel--open___Fw1SY","drawerCloseButton":"FilterDrawer-module__drawerCloseButton___cfMmf","drawerContent":"FilterDrawer-module__drawerContent___KBff6"};
2669
+
2670
+ const OVERLAY_FADE_MS = 300;
2671
+ const PANEL_SLIDE_MS = 300;
2672
+ const CLOSE_ANIMATION_BUFFER_MS = 50;
2673
+
2674
+ /**
2675
+ * FilterDrawer - Mobile slide-in filter panel
2676
+ *
2677
+ * Slides in from the right with a dark overlay. Used on mobile to show filters
2678
+ * when screen space is limited. Includes focus trap, escape key handling, and
2679
+ * body scroll prevention.
2680
+ *
2681
+ * Desktop: Hidden (filters shown in sidebar)
2682
+ * Mobile: Slide-in drawer overlay
2683
+ *
2684
+ * @example
2685
+ * ```tsx
2686
+ * <FilterDrawer
2687
+ * isOpen={isFilterDrawerOpen}
2688
+ * onClose={() => setIsFilterDrawerOpen(false)}
2689
+ * >
2690
+ * <FiltersPanel facets={facets} onFacetToggle={handleToggle} />
2691
+ * </FilterDrawer>
2692
+ * ```
2334
2693
  */
2335
2694
  const FilterDrawer = ({
2336
2695
  isOpen,
@@ -2340,54 +2699,95 @@ const FilterDrawer = ({
2340
2699
  }) => {
2341
2700
  const drawerRef = useRef(null);
2342
2701
  const previouslyFocusedElement = useRef(null);
2702
+ const [renderState, setRenderState] = useState(isOpen ? "opening" : "closed");
2343
2703
 
2344
- // Focus management
2704
+ // Mount/unmount sequencing for transitions.
2345
2705
  useEffect(() => {
2706
+ let closeTimer;
2707
+ let raf1;
2708
+ let raf2;
2346
2709
  if (isOpen) {
2347
- // Store previously focused element
2348
- previouslyFocusedElement.current = document.activeElement;
2710
+ // Ensure it is mounted in the "closed" styles first, then flip to "open" next frame.
2711
+ setRenderState("opening");
2712
+
2713
+ // Double rAF: guarantees at least one paint with the base styles
2714
+ // before we apply the `--open` classes.
2715
+ raf1 = window.requestAnimationFrame(() => {
2716
+ raf2 = window.requestAnimationFrame(() => {
2717
+ setRenderState("open");
2718
+ });
2719
+ });
2720
+ return () => {
2721
+ if (raf1) window.cancelAnimationFrame(raf1);
2722
+ if (raf2) window.cancelAnimationFrame(raf2);
2723
+ };
2724
+ }
2349
2725
 
2350
- // Focus the drawer
2351
- if (drawerRef.current) {
2352
- drawerRef.current.focus();
2353
- }
2726
+ // If we're currently rendered, play exit animation before unmounting.
2727
+ setRenderState(prev => prev === "closed" ? "closed" : "closing");
2728
+ closeTimer = window.setTimeout(() => {
2729
+ setRenderState("closed");
2730
+ }, PANEL_SLIDE_MS + OVERLAY_FADE_MS + CLOSE_ANIMATION_BUFFER_MS);
2731
+ return () => {
2732
+ if (closeTimer) window.clearTimeout(closeTimer);
2733
+ };
2734
+ }, [isOpen]);
2354
2735
 
2355
- // Prevent body scroll
2356
- document.body.style.overflow = 'hidden';
2357
- } else {
2736
+ // Focus management
2737
+ useEffect(() => {
2738
+ const isRendered = renderState !== "closed";
2739
+ if (!isRendered) {
2358
2740
  // Restore body scroll
2359
- document.body.style.overflow = '';
2741
+ document.body.style.overflow = "";
2360
2742
 
2361
2743
  // Return focus to previously focused element
2362
2744
  if (previouslyFocusedElement.current) {
2363
2745
  previouslyFocusedElement.current.focus();
2364
2746
  }
2747
+
2748
+ // Reset for next open cycle
2749
+ previouslyFocusedElement.current = null;
2750
+ return;
2751
+ }
2752
+
2753
+ // Store previously focused element (once per open)
2754
+ if (!previouslyFocusedElement.current) {
2755
+ previouslyFocusedElement.current = document.activeElement;
2756
+ }
2757
+
2758
+ // Prevent body scroll for the whole time the drawer is mounted (open + closing animation)
2759
+ document.body.style.overflow = "hidden";
2760
+
2761
+ // Focus the drawer once it reaches open state
2762
+ if (renderState === "open" && drawerRef.current) {
2763
+ drawerRef.current.focus();
2365
2764
  }
2366
2765
  return () => {
2367
- document.body.style.overflow = '';
2766
+ // In case the component unmounts unexpectedly.
2767
+ document.body.style.overflow = "";
2368
2768
  };
2369
- }, [isOpen]);
2769
+ }, [renderState]);
2370
2770
 
2371
2771
  // Escape key handler
2372
2772
  useEffect(() => {
2373
2773
  const handleEscape = event => {
2374
- if (event.key === 'Escape' && isOpen) {
2774
+ if (event.key === "Escape" && renderState !== "closed") {
2375
2775
  onClose();
2376
2776
  }
2377
2777
  };
2378
- document.addEventListener('keydown', handleEscape);
2379
- return () => document.removeEventListener('keydown', handleEscape);
2380
- }, [isOpen, onClose]);
2778
+ document.addEventListener("keydown", handleEscape);
2779
+ return () => document.removeEventListener("keydown", handleEscape);
2780
+ }, [renderState, onClose]);
2381
2781
 
2382
2782
  // Focus trap implementation
2383
2783
  useEffect(() => {
2384
- if (!isOpen || !drawerRef.current) return;
2784
+ if (renderState !== "open" || !drawerRef.current) return;
2385
2785
  const drawer = drawerRef.current;
2386
2786
  const focusableElements = drawer.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
2387
2787
  const firstElement = focusableElements[0];
2388
2788
  const lastElement = focusableElements[focusableElements.length - 1];
2389
2789
  const handleTab = event => {
2390
- if (event.key !== 'Tab') return;
2790
+ if (event.key !== "Tab") return;
2391
2791
  if (event.shiftKey) {
2392
2792
  // Shift + Tab
2393
2793
  if (document.activeElement === firstElement) {
@@ -2402,20 +2802,21 @@ const FilterDrawer = ({
2402
2802
  }
2403
2803
  }
2404
2804
  };
2405
- drawer.addEventListener('keydown', handleTab);
2406
- return () => drawer.removeEventListener('keydown', handleTab);
2407
- }, [isOpen]);
2408
- if (!isOpen) return null;
2805
+ drawer.addEventListener("keydown", handleTab);
2806
+ return () => drawer.removeEventListener("keydown", handleTab);
2807
+ }, [renderState]);
2808
+ if (renderState === "closed") return null;
2409
2809
  const handleOverlayClick = event => {
2410
2810
  if (event.target === event.currentTarget) {
2411
2811
  onClose();
2412
2812
  }
2413
2813
  };
2414
2814
  const overlayClasses = classNames(styles$2.drawerOverlay, {
2415
- [styles$2['drawerOverlay--open']]: isOpen
2815
+ [styles$2["drawerOverlay--open"]]: renderState === "open",
2816
+ [styles$2["drawerOverlay--closing"]]: renderState === "closing"
2416
2817
  });
2417
2818
  const panelClasses = classNames(styles$2.drawerPanel, {
2418
- [styles$2['drawerPanel--open']]: isOpen
2819
+ [styles$2["drawerPanel--open"]]: renderState === "open"
2419
2820
  }, className);
2420
2821
  return /*#__PURE__*/React.createElement("div", {
2421
2822
  className: overlayClasses,
@@ -2461,6 +2862,23 @@ const FederatedResultsView = ({
2461
2862
  onFilterDrawerToggle,
2462
2863
  onFilterDrawerClose
2463
2864
  }) => {
2865
+ const [isMobile, setIsMobile] = useState(false);
2866
+ useEffect(() => {
2867
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
2868
+ return;
2869
+ }
2870
+ const mql = window.matchMedia("(max-width: 768px)");
2871
+ const update = () => setIsMobile(mql.matches);
2872
+ update();
2873
+
2874
+ // Safari < 14 fallback
2875
+ if (typeof mql.addEventListener === "function") {
2876
+ mql.addEventListener("change", update);
2877
+ return () => mql.removeEventListener("change", update);
2878
+ }
2879
+ mql.addListener(update);
2880
+ return () => mql.removeListener(update);
2881
+ }, []);
2464
2882
  const isLoading = activeTab === "products" && isLoadingProducts || activeTab === "content" && isLoadingContents;
2465
2883
  const error = activeTab === "products" ? productsError : contentsError;
2466
2884
 
@@ -2524,8 +2942,6 @@ const FederatedResultsView = ({
2524
2942
  isActive: activeTab === "products",
2525
2943
  onClick: () => onTabChange("products"),
2526
2944
  variant: "desktop"
2527
- }), /*#__PURE__*/React.createElement("div", {
2528
- className: styles$5.tabSeparator
2529
2945
  }), /*#__PURE__*/React.createElement(TabButton, {
2530
2946
  label: "WEBSITE RESULTS",
2531
2947
  count: contents.length,
@@ -2574,7 +2990,7 @@ const FederatedResultsView = ({
2574
2990
  currentPage: currentPage,
2575
2991
  totalPages: totalPages,
2576
2992
  onPageChange: onPageChange,
2577
- maxVisiblePages: 10,
2993
+ maxVisiblePages: isMobile ? 3 : 10,
2578
2994
  showPrevious: false,
2579
2995
  ariaLabel: `${activeTab === "products" ? "Products" : "Content"} pagination`
2580
2996
  }))))), onFilterDrawerClose && /*#__PURE__*/React.createElement(FilterDrawer, {
@@ -2590,41 +3006,41 @@ const FederatedResultsView = ({
2590
3006
 
2591
3007
  var styles$1 = {"searchExperience":"FederatedSearchExperience-module__searchExperience___gug9X","searchBarSection":"FederatedSearchExperience-module__searchBarSection___f0Ykq","searchBarDesktopOnly":"FederatedSearchExperience-module__searchBarDesktopOnly___CetKZ","searchBarMobileOnly":"FederatedSearchExperience-module__searchBarMobileOnly___Ityxp","resultsHeaderMobileOnly":"FederatedSearchExperience-module__resultsHeaderMobileOnly___09ef-","tabsContainerSticky":"FederatedSearchExperience-module__tabsContainerSticky___-HdY5","tabs":"FederatedSearchExperience-module__tabs___Vatv8","tabSeparator":"FederatedSearchExperience-module__tabSeparator___AUjhW","mobileFilterButtonWrapper":"FederatedSearchExperience-module__mobileFilterButtonWrapper___JkRtk","mobileFilterButton":"FederatedSearchExperience-module__mobileFilterButton___sRc-w","filterIndicatorDot":"FederatedSearchExperience-module__filterIndicatorDot___vqVi2","instantResultsSection":"FederatedSearchExperience-module__instantResultsSection___AG3qn","fullResultsSection":"FederatedSearchExperience-module__fullResultsSection___NH16U"};
2592
3008
 
2593
- /**
2594
- * FederatedSearchExperience - Top-level search experience component
2595
- *
2596
- * This component orchestrates the entire federated search experience, including:
2597
- * - Modal container
2598
- * - Search input
2599
- * - Instant results (2-column layout)
2600
- * - Full results view with tabs and filters
2601
- *
2602
- * The component is fully controlled and does not contain any data fetching logic.
2603
- * All data and callbacks are passed in via props, making it suitable for use in AEM
2604
- * where a controller layer will handle Algolia integration.
2605
- *
2606
- * @example
2607
- * ```tsx
2608
- * <FederatedSearchExperience
2609
- * isOpen={isOpen}
2610
- * onOpen={handleOpen}
2611
- * onClose={handleClose}
2612
- * query={query}
2613
- * onQueryChange={setQuery}
2614
- * onSearchSubmit={handleSubmit}
2615
- * products={products}
2616
- * contents={contents}
2617
- * isLoadingProducts={isLoadingProducts}
2618
- * isLoadingContents={isLoadingContents}
2619
- * onSeeAllProducts={handleSeeAllProducts}
2620
- * onSeeAllContents={handleSeeAllContents}
2621
- * onSeeAllCombined={handleSeeAllCombined}
2622
- * activeView={activeView}
2623
- * onChangeView={setActiveView}
2624
- * activeTab={activeTab}
2625
- * onTabChange={setActiveTab}
2626
- * />
2627
- * ```
3009
+ /**
3010
+ * FederatedSearchExperience - Top-level search experience component
3011
+ *
3012
+ * This component orchestrates the entire federated search experience, including:
3013
+ * - Modal container
3014
+ * - Search input
3015
+ * - Instant results (2-column layout)
3016
+ * - Full results view with tabs and filters
3017
+ *
3018
+ * The component is fully controlled and does not contain any data fetching logic.
3019
+ * All data and callbacks are passed in via props, making it suitable for use in AEM
3020
+ * where a controller layer will handle Algolia integration.
3021
+ *
3022
+ * @example
3023
+ * ```tsx
3024
+ * <FederatedSearchExperience
3025
+ * isOpen={isOpen}
3026
+ * onOpen={handleOpen}
3027
+ * onClose={handleClose}
3028
+ * query={query}
3029
+ * onQueryChange={setQuery}
3030
+ * onSearchSubmit={handleSubmit}
3031
+ * products={products}
3032
+ * contents={contents}
3033
+ * isLoadingProducts={isLoadingProducts}
3034
+ * isLoadingContents={isLoadingContents}
3035
+ * onSeeAllProducts={handleSeeAllProducts}
3036
+ * onSeeAllContents={handleSeeAllContents}
3037
+ * onSeeAllCombined={handleSeeAllCombined}
3038
+ * activeView={activeView}
3039
+ * onChangeView={setActiveView}
3040
+ * activeTab={activeTab}
3041
+ * onTabChange={setActiveTab}
3042
+ * />
3043
+ * ```
2628
3044
  */
2629
3045
  const FederatedSearchExperience = ({
2630
3046
  isOpen,
@@ -2700,7 +3116,7 @@ const FederatedSearchExperience = ({
2700
3116
  variant: "mobile"
2701
3117
  })), /*#__PURE__*/React.createElement("div", {
2702
3118
  className: styles$1.mobileFilterButtonWrapper
2703
- }, /*#__PURE__*/React.createElement("button", {
3119
+ }, onFilterDrawerToggle && /*#__PURE__*/React.createElement("button", {
2704
3120
  type: "button",
2705
3121
  className: styles$1.mobileFilterButton,
2706
3122
  onClick: onFilterDrawerToggle
@@ -2828,5 +3244,5 @@ const SearchTriggerButton = ({
2828
3244
  }, label));
2829
3245
  };
2830
3246
 
2831
- export { AlgoliaDynamicSearchLeybold as AlgoliaDynamicSearch, AlgoliaDynamicSearchLeybold, AppliedFilterTag, AppliedFilters, Button, ContentCardHorizontal, FederatedInstantResultsLayout, FederatedResultsView, FederatedSearchExperience, FilterAccordion, FilterItem, FilterSearch, FiltersPanel, Footer, FooterBottom, FooterLink, FooterLinkGroup, FooterSocialIcon, FooterSocialIcons, ModalCloseButton, Pagination, PaginationButton, PaginationEllipsis, PaginationItem, ProductCardHorizontal, ProductCardVertical, QrFormLeybold as QrForm, QrFormLeybold, ResultsColumn, ResultsCount, ResultsList, SearchBar, SearchIcon, SearchInput, SearchModal, SearchSubmitButton, SearchTriggerButton, SeeAllLinkButton };
3247
+ export { AlgoliaDynamicSearchLeybold as AlgoliaDynamicSearch, AlgoliaDynamicSearchLeybold, AppliedFilterTag, AppliedFilters, Button, CarouselCard, ContentCardBase, ContentCardHorizontal, FederatedInstantResultsLayout, FederatedResultsView, FederatedSearchExperience, FilterAccordion, FilterItem, FilterSearch, FiltersPanel, Footer, FooterBottom, FooterLink, FooterLinkGroup, FooterSocialIcon, FooterSocialIcons, ModalCloseButton, Pagination, PaginationButton, PaginationEllipsis, PaginationItem, ProductCardHorizontal, ProductCardVertical, QrFormLeybold as QrForm, QrFormLeybold, ResultsColumn, ResultsCount, ResultsList, SearchBar, SearchIcon, SearchInput, SearchModal, SearchSubmitButton, SearchTriggerButton, SeeAllLinkButton };
2832
3248
  //# sourceMappingURL=index.esm.js.map