@seekora-ai/ui-sdk-react 0.2.21 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -3114,6 +3114,201 @@ const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsP
3114
3114
  resolvedShowPageInfo && (React.createElement("li", { style: { marginLeft: theme.spacing.small } }, pageInfoElement)))));
3115
3115
  };
3116
3116
 
3117
+ /**
3118
+ * CustomSelect — lightweight, accessible custom dropdown replacement for native <select>.
3119
+ *
3120
+ * CSS Variables (apply on a parent element to customize):
3121
+ * --seekora-select-bg — background color (default: #fff)
3122
+ * --seekora-select-color — text color (default: inherit)
3123
+ * --seekora-select-border — border color (default: rgba(128,128,128,0.3))
3124
+ * --seekora-select-hover-bg — option hover background (default: #f3f4f6)
3125
+ * --seekora-select-active-bg — selected option background (default: #eff6ff)
3126
+ * --seekora-select-active-color — selected option text color (default: inherit)
3127
+ * --seekora-select-radius — border radius (default: 6px)
3128
+ * --seekora-select-font-size — font size (default: 0.875rem)
3129
+ */
3130
+ // ---------------------------------------------------------------------------
3131
+ // Component
3132
+ // ---------------------------------------------------------------------------
3133
+ const CustomSelect = ({ value, onChange, options, placeholder = 'Select...', className, style, theme: customTheme, 'aria-label': ariaLabel, disabled = false, }) => {
3134
+ const [isOpen, setIsOpen] = React.useState(false);
3135
+ const [activeIndex, setActiveIndex] = React.useState(-1);
3136
+ const containerRef = React.useRef(null);
3137
+ const menuRef = React.useRef(null);
3138
+ const instanceId = React.useId();
3139
+ const t = customTheme || {};
3140
+ const selectedOption = options.find((o) => o.value === value);
3141
+ const displayLabel = selectedOption?.label ?? placeholder;
3142
+ // Close on click outside
3143
+ React.useEffect(() => {
3144
+ if (!isOpen)
3145
+ return;
3146
+ const handleMouseDown = (e) => {
3147
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3148
+ setIsOpen(false);
3149
+ setActiveIndex(-1);
3150
+ }
3151
+ };
3152
+ document.addEventListener('mousedown', handleMouseDown);
3153
+ return () => document.removeEventListener('mousedown', handleMouseDown);
3154
+ }, [isOpen]);
3155
+ // Scroll active item into view
3156
+ React.useEffect(() => {
3157
+ if (!isOpen || activeIndex < 0 || !menuRef.current)
3158
+ return;
3159
+ const item = menuRef.current.children[activeIndex];
3160
+ item?.scrollIntoView?.({ block: 'nearest' });
3161
+ }, [activeIndex, isOpen]);
3162
+ const toggle = React.useCallback(() => {
3163
+ if (disabled)
3164
+ return;
3165
+ setIsOpen((prev) => {
3166
+ if (!prev) {
3167
+ // Opening — highlight current selection
3168
+ const idx = options.findIndex((o) => o.value === value);
3169
+ setActiveIndex(idx >= 0 ? idx : 0);
3170
+ }
3171
+ return !prev;
3172
+ });
3173
+ }, [disabled, options, value]);
3174
+ const selectOption = React.useCallback((opt) => {
3175
+ if (opt.disabled)
3176
+ return;
3177
+ onChange(opt.value);
3178
+ setIsOpen(false);
3179
+ setActiveIndex(-1);
3180
+ }, [onChange]);
3181
+ // Find next non-disabled index in given direction
3182
+ const findNextEnabled = (from, dir) => {
3183
+ let idx = from;
3184
+ for (let i = 0; i < options.length; i++) {
3185
+ idx += dir;
3186
+ if (idx < 0)
3187
+ idx = options.length - 1;
3188
+ if (idx >= options.length)
3189
+ idx = 0;
3190
+ if (!options[idx].disabled)
3191
+ return idx;
3192
+ }
3193
+ return from;
3194
+ };
3195
+ const handleKeyDown = React.useCallback((e) => {
3196
+ if (disabled)
3197
+ return;
3198
+ if (!isOpen) {
3199
+ if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
3200
+ e.preventDefault();
3201
+ toggle();
3202
+ }
3203
+ return;
3204
+ }
3205
+ switch (e.key) {
3206
+ case 'ArrowDown':
3207
+ e.preventDefault();
3208
+ setActiveIndex((prev) => findNextEnabled(prev, 1));
3209
+ break;
3210
+ case 'ArrowUp':
3211
+ e.preventDefault();
3212
+ setActiveIndex((prev) => findNextEnabled(prev, -1));
3213
+ break;
3214
+ case 'Enter':
3215
+ case ' ':
3216
+ e.preventDefault();
3217
+ if (activeIndex >= 0 && !options[activeIndex].disabled) {
3218
+ selectOption(options[activeIndex]);
3219
+ }
3220
+ break;
3221
+ case 'Escape':
3222
+ e.preventDefault();
3223
+ setIsOpen(false);
3224
+ setActiveIndex(-1);
3225
+ break;
3226
+ case 'Home':
3227
+ e.preventDefault();
3228
+ setActiveIndex(findNextEnabled(-1, 1));
3229
+ break;
3230
+ case 'End':
3231
+ e.preventDefault();
3232
+ setActiveIndex(findNextEnabled(options.length, -1));
3233
+ break;
3234
+ case 'Tab':
3235
+ setIsOpen(false);
3236
+ setActiveIndex(-1);
3237
+ break;
3238
+ }
3239
+ }, [disabled, isOpen, activeIndex, options, toggle, selectOption]);
3240
+ const menuId = `seekora-select-menu-${instanceId}`;
3241
+ return (React.createElement("div", { ref: containerRef, className: clsx('seekora-select', className), style: { position: 'relative', display: 'inline-block', ...style }, onKeyDown: handleKeyDown },
3242
+ React.createElement("button", { type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel, "aria-activedescendant": isOpen && activeIndex >= 0
3243
+ ? `${menuId}-opt-${activeIndex}`
3244
+ : undefined, disabled: disabled, onClick: toggle, className: clsx('seekora-select__trigger', t.trigger), style: {
3245
+ display: 'flex',
3246
+ alignItems: 'center',
3247
+ justifyContent: 'space-between',
3248
+ gap: 8,
3249
+ width: '100%',
3250
+ padding: '8px 12px',
3251
+ fontSize: 'var(--seekora-select-font-size, 0.875rem)',
3252
+ lineHeight: 1.4,
3253
+ border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
3254
+ borderRadius: 'var(--seekora-select-radius, 6px)',
3255
+ backgroundColor: 'var(--seekora-select-bg, #fff)',
3256
+ color: 'var(--seekora-select-color, inherit)',
3257
+ cursor: disabled ? 'not-allowed' : 'pointer',
3258
+ outline: 'none',
3259
+ textAlign: 'left',
3260
+ fontFamily: 'inherit',
3261
+ opacity: disabled ? 0.5 : 1,
3262
+ } },
3263
+ React.createElement("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, displayLabel),
3264
+ React.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: {
3265
+ flexShrink: 0,
3266
+ transition: 'transform 0.15s ease',
3267
+ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
3268
+ } },
3269
+ React.createElement("path", { d: "M2.5 4.5L6 8L9.5 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))),
3270
+ isOpen && (React.createElement("div", { ref: menuRef, id: menuId, role: "listbox", className: clsx('seekora-select__menu', t.menu), style: {
3271
+ position: 'absolute',
3272
+ top: '100%',
3273
+ left: 0,
3274
+ right: 0,
3275
+ zIndex: 9999,
3276
+ marginTop: 4,
3277
+ padding: '4px 0',
3278
+ border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
3279
+ borderRadius: 'var(--seekora-select-radius, 6px)',
3280
+ backgroundColor: 'var(--seekora-select-bg, #fff)',
3281
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
3282
+ maxHeight: 220,
3283
+ overflowY: 'auto',
3284
+ } }, options.map((opt, idx) => {
3285
+ const isSelected = opt.value === value;
3286
+ const isHighlighted = idx === activeIndex;
3287
+ return (React.createElement("div", { key: opt.value, id: `${menuId}-opt-${idx}`, role: "option", "aria-selected": isSelected, "aria-disabled": opt.disabled || undefined, className: clsx('seekora-select__option', t.option, isSelected && 'seekora-select__option--selected', isHighlighted && (t.optionActive || 'seekora-select__option--highlighted'), opt.disabled && (t.optionDisabled || 'seekora-select__option--disabled')), onMouseEnter: () => !opt.disabled && setActiveIndex(idx), onMouseDown: (e) => {
3288
+ e.preventDefault(); // Prevent blur
3289
+ if (!opt.disabled)
3290
+ selectOption(opt);
3291
+ }, style: {
3292
+ padding: '6px 12px',
3293
+ fontSize: 'var(--seekora-select-font-size, 0.875rem)',
3294
+ cursor: opt.disabled ? 'not-allowed' : 'pointer',
3295
+ backgroundColor: isHighlighted
3296
+ ? 'var(--seekora-select-hover-bg, #f3f4f6)'
3297
+ : isSelected
3298
+ ? 'var(--seekora-select-active-bg, #eff6ff)'
3299
+ : 'transparent',
3300
+ color: opt.disabled
3301
+ ? 'rgba(128,128,128,0.5)'
3302
+ : isSelected
3303
+ ? 'var(--seekora-select-active-color, inherit)'
3304
+ : 'var(--seekora-select-color, inherit)',
3305
+ fontWeight: isSelected ? 500 : 400,
3306
+ opacity: opt.disabled ? 0.5 : 1,
3307
+ transition: 'background-color 0.1s ease',
3308
+ } }, opt.label));
3309
+ })))));
3310
+ };
3311
+
3117
3312
  /**
3118
3313
  * SortBy Component
3119
3314
  *
@@ -3217,18 +3412,14 @@ const SortBy = ({ options, value: valueProp, defaultValue, onSortChange, renderS
3217
3412
  }
3218
3413
  return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
3219
3414
  labelElement,
3220
- React.createElement("select", { value: value, onChange: handleChange, className: clsx(sortByTheme.select), style: {
3221
- padding,
3222
- paddingRight: theme.spacing.medium,
3223
- fontSize,
3224
- border: '1px solid var(--seekora-sort-border)',
3225
- borderRadius,
3226
- backgroundColor: 'var(--seekora-sort-bg)',
3227
- color: 'var(--seekora-sort-color)',
3228
- cursor: 'pointer',
3229
- outline: 'none',
3415
+ React.createElement(CustomSelect, { value: value, onChange: applyValue, options: options, placeholder: placeholder, "aria-label": label || 'Sort results', className: clsx(sortByTheme.select), style: {
3230
3416
  width: '100%',
3231
- }, "aria-label": label || 'Sort results' }, options.map((option) => (React.createElement("option", { key: option.value, value: option.value, className: sortByTheme.option }, option.label))))));
3417
+ '--seekora-select-font-size': fontSize,
3418
+ '--seekora-select-border': 'var(--seekora-sort-border)',
3419
+ '--seekora-select-radius': borderRadius,
3420
+ '--seekora-select-bg': 'var(--seekora-sort-bg)',
3421
+ '--seekora-select-color': 'var(--seekora-sort-color)',
3422
+ } })));
3232
3423
  }
3233
3424
  // ------ Button group variant -------------------------------------------
3234
3425
  if (variant === 'button-group') {
@@ -3678,7 +3869,7 @@ const CSS_VAR_DEFAULTS = {
3678
3869
  // ---------------------------------------------------------------------------
3679
3870
  // Component
3680
3871
  // ---------------------------------------------------------------------------
3681
- const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, useFiltersApi = false, disjunctiveFacets, }) => {
3872
+ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, useFiltersApi = false, disjunctiveFacets, hideEmptyFacets = true, defaultCollapsedFields, }) => {
3682
3873
  const { theme } = useSearchContext();
3683
3874
  const { results: stateResults, refinements, addRefinement, removeRefinement } = useSearchState();
3684
3875
  const facetsTheme = customTheme || {};
@@ -3755,7 +3946,10 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
3755
3946
  });
3756
3947
  return extracted;
3757
3948
  };
3758
- const facets = extractFacets();
3949
+ const rawFacetList = extractFacets();
3950
+ const facets = hideEmptyFacets
3951
+ ? rawFacetList.filter(f => f.items.length > 0 || f.stats != null)
3952
+ : rawFacetList;
3759
3953
  // -------------------------------------------------------------------
3760
3954
  // Handlers
3761
3955
  // -------------------------------------------------------------------
@@ -3792,17 +3986,21 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
3792
3986
  }));
3793
3987
  };
3794
3988
  /** For collapsible variant — determine if a facet group is open. */
3989
+ const isFieldDefaultCollapsed = (field) => {
3990
+ if (defaultCollapsedFields?.includes(field))
3991
+ return true;
3992
+ return defaultCollapsed;
3993
+ };
3795
3994
  const isFacetGroupOpen = (field) => {
3796
3995
  if (field in expandedFacets) {
3797
3996
  return expandedFacets[field];
3798
3997
  }
3799
- // Default based on defaultCollapsed prop
3800
- return !defaultCollapsed;
3998
+ return !isFieldDefaultCollapsed(field);
3801
3999
  };
3802
4000
  const toggleCollapsible = (field) => {
3803
4001
  setExpandedFacets((prev) => ({
3804
4002
  ...prev,
3805
- [field]: !(prev[field] ?? !defaultCollapsed),
4003
+ [field]: !(prev[field] ?? !isFieldDefaultCollapsed(field)),
3806
4004
  }));
3807
4005
  };
3808
4006
  const getSearchTerm = (field) => searchTerms[field] || '';
@@ -4048,8 +4246,6 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
4048
4246
  borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
4049
4247
  } },
4050
4248
  React.createElement("h3", { className: facetsTheme.facetTitle, style: {
4051
- fontSize: theme.typography.fontSize.large,
4052
- fontWeight: 'bold',
4053
4249
  margin: 0,
4054
4250
  marginBottom: theme.spacing.medium,
4055
4251
  color: theme.colors.text,
@@ -4073,8 +4269,6 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
4073
4269
  borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
4074
4270
  } },
4075
4271
  React.createElement("h3", { className: facetsTheme.facetTitle, style: {
4076
- fontSize: theme.typography.fontSize.large,
4077
- fontWeight: 'bold',
4078
4272
  margin: 0,
4079
4273
  marginBottom: theme.spacing.medium,
4080
4274
  color: theme.colors.text,
@@ -4120,8 +4314,6 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
4120
4314
  textAlign: 'left',
4121
4315
  } },
4122
4316
  React.createElement("span", { className: facetsTheme.facetTitle, style: {
4123
- fontSize: theme.typography.fontSize.large,
4124
- fontWeight: 'bold',
4125
4317
  color: theme.colors.text,
4126
4318
  flex: 1,
4127
4319
  } }, facet.label || facet.field),
@@ -4240,8 +4432,6 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
4240
4432
  borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
4241
4433
  } },
4242
4434
  React.createElement("h3", { className: facetsTheme.facetTitle, style: {
4243
- fontSize: theme.typography.fontSize.large,
4244
- fontWeight: 'bold',
4245
4435
  margin: 0,
4246
4436
  marginBottom: theme.spacing.medium,
4247
4437
  color: theme.colors.text,
@@ -4322,8 +4512,6 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
4322
4512
  } },
4323
4513
  React.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: theme.spacing.small } },
4324
4514
  React.createElement("h3", { className: facetsTheme.facetTitle, style: {
4325
- fontSize: theme.typography.fontSize.large,
4326
- fontWeight: 'bold',
4327
4515
  margin: 0,
4328
4516
  color: theme.colors.text,
4329
4517
  } }, facet.label || facet.field),
@@ -5449,19 +5637,28 @@ const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, styl
5449
5637
  fontSize: theme.typography.fontSize.medium,
5450
5638
  color: theme.colors.text,
5451
5639
  } }, label)),
5452
- React.createElement("select", { value: value, onChange: handleChange, className: hitsPerPageTheme.select, style: {
5453
- padding: theme.spacing.small,
5454
- paddingRight: theme.spacing.medium,
5455
- fontSize: theme.typography.fontSize.medium,
5456
- border: `1px solid ${theme.colors.border}`,
5457
- borderRadius: typeof theme.borderRadius === 'string'
5640
+ React.createElement(CustomSelect, { value: String(value), onChange: (val) => {
5641
+ const newValue = parseInt(val, 10);
5642
+ setInternalValue(newValue);
5643
+ if (syncWithState) {
5644
+ stateManager.setItemsPerPage(newValue, false);
5645
+ setPage(1, true);
5646
+ }
5647
+ if (onHitsPerPageChange) {
5648
+ onHitsPerPageChange(newValue);
5649
+ }
5650
+ }, options: items.map((item) => ({
5651
+ value: String(item.value),
5652
+ label: item.label,
5653
+ })), "aria-label": "Results per page", className: hitsPerPageTheme.select, style: {
5654
+ '--seekora-select-font-size': theme.typography.fontSize.medium,
5655
+ '--seekora-select-border': theme.colors.border,
5656
+ '--seekora-select-radius': typeof theme.borderRadius === 'string'
5458
5657
  ? theme.borderRadius
5459
5658
  : theme.borderRadius.medium,
5460
- backgroundColor: theme.colors.background,
5461
- color: theme.colors.text,
5462
- cursor: 'pointer',
5463
- outline: 'none',
5464
- }, "aria-label": "Results per page" }, items.map((item) => (React.createElement("option", { key: item.value, value: item.value, className: hitsPerPageTheme.option }, item.label))))));
5659
+ '--seekora-select-bg': theme.colors.background,
5660
+ '--seekora-select-color': theme.colors.text,
5661
+ } })));
5465
5662
  };
5466
5663
 
5467
5664
  /**
@@ -9730,13 +9927,30 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
9730
9927
  objectFit: 'cover',
9731
9928
  display: 'block',
9732
9929
  };
9930
+ // Compute the indicator rectangle (what portion of the image the zoom panel shows).
9931
+ // indW / containerW = 1 / zoomLevel (the panel shows 1/zoomLevel of the image width).
9932
+ // indH is derived from the zoom panel's aspect ratio so it's consistent with what the
9933
+ // panel actually renders — no need for the image's natural dimensions.
9934
+ const getIndicatorRect = React.useCallback(() => {
9935
+ if (!containerRef.current)
9936
+ return { indL: 0, indT: 0, indW: 0, indH: 0 };
9937
+ const rect = containerRef.current.getBoundingClientRect();
9938
+ const indW = rect.width / zoomLevel;
9939
+ const indH = (zoomPanelSize.height / zoomPanelSize.width) * rect.width / zoomLevel;
9940
+ const indL = Math.max(0, Math.min(rect.width - indW, cursorPos.x - indW / 2));
9941
+ const indT = Math.max(0, Math.min(rect.height - indH, cursorPos.y - indH / 2));
9942
+ return { indL, indT, indW, indH };
9943
+ }, [cursorPos, zoomLevel, zoomPanelSize]);
9733
9944
  // Calculate zoom panel background position (Amazon-style)
9734
9945
  const getZoomPanelStyle = () => {
9735
9946
  if (!containerRef.current || !imageLoaded)
9736
9947
  return { display: 'none' };
9737
9948
  const rect = containerRef.current.getBoundingClientRect();
9738
- const bgPosX = (cursorPos.x / rect.width) * 100;
9739
- const bgPosY = (cursorPos.y / rect.height) * 100;
9949
+ const { indL, indT, indW, indH } = getIndicatorRect();
9950
+ // bgPos is derived from the clamped indicator position, not the raw cursor.
9951
+ // At indL=0 → bgPosX=0% (show left), at indL=containerW-indW → bgPosX=100% (show right).
9952
+ const bgPosX = (rect.width - indW) > 0 ? (indL / (rect.width - indW)) * 100 : 0;
9953
+ const bgPosY = (rect.height - indH) > 0 ? (indT / (rect.height - indH)) * 100 : 0;
9740
9954
  // Calculate available space in all directions
9741
9955
  const viewportWidth = window.innerWidth;
9742
9956
  const viewportHeight = window.innerHeight;
@@ -9864,19 +10078,22 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
9864
10078
  } }, "\uD83D\uDD0D")),
9865
10079
  supportsLens && isHovering && imageLoaded && (React.createElement("div", { style: getLensStyle() },
9866
10080
  React.createElement("img", { src: src, alt: "", style: getLensImageStyle() }))),
9867
- supportsHover && isHovering && imageLoaded && containerRef.current && (React.createElement("div", { style: {
9868
- position: 'absolute',
9869
- left: cursorPos.x - 75,
9870
- top: cursorPos.y - 75,
9871
- width: 150,
9872
- height: 150,
9873
- border: 'var(--seekora-hover-area-border, 2px solid rgba(0,0,0,0.3))',
9874
- backgroundColor: 'var(--seekora-hover-area-bg, rgba(255,255,255,0.1))',
9875
- pointerEvents: 'none',
9876
- zIndex: 50,
9877
- } })),
9878
- supportsHover && isHovering && imageLoaded && (React.createElement("div", { style: getZoomPanelStyle() }))),
9879
- isLightboxOpen && (React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
10081
+ supportsHover && isHovering && imageLoaded && containerRef.current && (() => {
10082
+ const { indL, indT, indW, indH } = getIndicatorRect();
10083
+ return (React.createElement("div", { style: {
10084
+ position: 'absolute',
10085
+ left: indL,
10086
+ top: indT,
10087
+ width: indW,
10088
+ height: indH,
10089
+ border: 'var(--seekora-hover-area-border, 2px solid rgba(0,0,0,0.3))',
10090
+ backgroundColor: 'var(--seekora-hover-area-bg, rgba(255,255,255,0.1))',
10091
+ pointerEvents: 'none',
10092
+ zIndex: 50,
10093
+ } }));
10094
+ })()),
10095
+ supportsHover && isHovering && imageLoaded && typeof document !== 'undefined' && reactDom.createPortal(React.createElement("div", { style: getZoomPanelStyle() }), document.body),
10096
+ isLightboxOpen && typeof document !== 'undefined' && reactDom.createPortal(React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
9880
10097
  position: 'fixed',
9881
10098
  top: 0,
9882
10099
  left: 0,
@@ -10031,7 +10248,7 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
10031
10248
  backgroundColor: 'var(--seekora-lightbox-instructions-bg, rgba(0,0,0,0.5))',
10032
10249
  padding: '8px 16px',
10033
10250
  borderRadius: 12,
10034
- } }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')))));
10251
+ } }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')), document.body)));
10035
10252
  }
10036
10253
 
10037
10254
  /**
@@ -11862,30 +12079,22 @@ function VariantSelector({ options, variants, selections, onSelectionChange, opt
11862
12079
  } },
11863
12080
  option.name,
11864
12081
  selected && (React.createElement("span", { style: { fontWeight: 400, marginLeft: 6, color: 'var(--seekora-text-secondary, inherit)' } }, selected))),
11865
- mode === 'dropdown' ? (React.createElement("select", { className: "seekora-variant-dropdown", value: selected ?? '', onChange: (e) => {
11866
- e.stopPropagation();
11867
- onSelectionChange(option.name, e.target.value);
11868
- }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), style: {
11869
- padding: '8px 12px',
11870
- fontSize: '0.875rem',
11871
- borderRadius: 6,
11872
- border: '1px solid var(--seekora-border-color, rgba(128,128,128,0.2))',
11873
- backgroundColor: 'var(--seekora-bg-surface, transparent)',
11874
- color: 'var(--seekora-text-primary, inherit)',
11875
- cursor: 'pointer',
11876
- minWidth: 120,
11877
- } },
11878
- React.createElement("option", { value: "" },
11879
- "Select ",
11880
- option.name),
11881
- option.values.map((value) => {
11882
- const available = showAvailability
11883
- ? getAvailability(option.name, value, options, variants, selections)
11884
- : true;
11885
- return (React.createElement("option", { key: value, value: value, disabled: !available },
11886
- value,
11887
- !available ? ' (Unavailable)' : ''));
11888
- }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
12082
+ mode === 'dropdown' ? (React.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() },
12083
+ React.createElement(CustomSelect, { className: "seekora-variant-dropdown", value: selected ?? '', onChange: (val) => onSelectionChange(option.name, val), placeholder: `Select ${option.name}`, "aria-label": `Select ${option.name}`, options: option.values.map((val) => {
12084
+ const available = showAvailability
12085
+ ? getAvailability(option.name, val, options, variants, selections)
12086
+ : true;
12087
+ return {
12088
+ value: val,
12089
+ label: available ? val : `${val} (Unavailable)`,
12090
+ disabled: !available,
12091
+ };
12092
+ }), style: {
12093
+ minWidth: 120,
12094
+ '--seekora-select-border': 'var(--seekora-border-color, rgba(128,128,128,0.2))',
12095
+ '--seekora-select-bg': 'var(--seekora-bg-surface, transparent)',
12096
+ '--seekora-select-color': 'var(--seekora-text-primary, inherit)',
12097
+ } }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
11889
12098
  const isActive = selected === value;
11890
12099
  const available = showAvailability
11891
12100
  ? getAvailability(option.name, value, options, variants, selections)
@@ -18657,6 +18866,7 @@ exports.Breadcrumb = Breadcrumb;
18657
18866
  exports.CategoriesTabs = CategoriesTabs;
18658
18867
  exports.ClearRefinements = ClearRefinements;
18659
18868
  exports.CurrentRefinements = CurrentRefinements;
18869
+ exports.CustomSelect = CustomSelect;
18660
18870
  exports.DocSearch = DocSearch;
18661
18871
  exports.DocSearchButton = DocSearchButton;
18662
18872
  exports.DropdownPanel = DropdownPanel;