@seekora-ai/ui-sdk-react 0.2.22 → 0.2.24

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.
@@ -107,6 +107,10 @@ export interface FacetsProps {
107
107
  useFiltersApi?: boolean;
108
108
  /** Fields that should use disjunctive (OR) faceting (only with useFiltersApi) */
109
109
  disjunctiveFacets?: string[];
110
+ /** Hide facets that have no selectable values (default: true) */
111
+ hideEmptyFacets?: boolean;
112
+ /** Fields that should start collapsed (collapsible variant only). Overrides defaultCollapsed for listed fields. */
113
+ defaultCollapsedFields?: string[];
110
114
  }
111
115
  export declare const Facets: React.FC<FacetsProps>;
112
116
  //# sourceMappingURL=Facets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Facets.d.ts","sourceRoot":"","sources":["../../src/components/Facets.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Facets.d.ts","sourceRoot":"","sources":["../../src/components/Facets.tsx"],"names":[],"mappings":"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"}
@@ -66,7 +66,7 @@ const CSS_VAR_DEFAULTS = {
66
66
  // ---------------------------------------------------------------------------
67
67
  // Component
68
68
  // ---------------------------------------------------------------------------
69
- export 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, }) => {
69
+ export 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, }) => {
70
70
  const { theme } = useSearchContext();
71
71
  const { results: stateResults, refinements, addRefinement, removeRefinement } = useSearchState();
72
72
  const facetsTheme = customTheme || {};
@@ -143,7 +143,10 @@ export const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange
143
143
  });
144
144
  return extracted;
145
145
  };
146
- const facets = extractFacets();
146
+ const rawFacetList = extractFacets();
147
+ const facets = hideEmptyFacets
148
+ ? rawFacetList.filter(f => f.items.length > 0 || f.stats != null)
149
+ : rawFacetList;
147
150
  // -------------------------------------------------------------------
148
151
  // Handlers
149
152
  // -------------------------------------------------------------------
@@ -180,17 +183,21 @@ export const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange
180
183
  }));
181
184
  };
182
185
  /** For collapsible variant — determine if a facet group is open. */
186
+ const isFieldDefaultCollapsed = (field) => {
187
+ if (defaultCollapsedFields?.includes(field))
188
+ return true;
189
+ return defaultCollapsed;
190
+ };
183
191
  const isFacetGroupOpen = (field) => {
184
192
  if (field in expandedFacets) {
185
193
  return expandedFacets[field];
186
194
  }
187
- // Default based on defaultCollapsed prop
188
- return !defaultCollapsed;
195
+ return !isFieldDefaultCollapsed(field);
189
196
  };
190
197
  const toggleCollapsible = (field) => {
191
198
  setExpandedFacets((prev) => ({
192
199
  ...prev,
193
- [field]: !(prev[field] ?? !defaultCollapsed),
200
+ [field]: !(prev[field] ?? !isFieldDefaultCollapsed(field)),
194
201
  }));
195
202
  };
196
203
  const getSearchTerm = (field) => searchTerms[field] || '';
@@ -1 +1 @@
1
- {"version":3,"file":"HitsPerPage.d.ts","sourceRoot":"","sources":["../../src/components/HitsPerPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,0CAA0C;IAC1C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,eAAe,EAAE,CAAC;KAC1B,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8GlD,CAAC"}
1
+ {"version":3,"file":"HitsPerPage.d.ts","sourceRoot":"","sources":["../../src/components/HitsPerPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAiC,MAAM,OAAO,CAAC;AAMtD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,0CAA0C;IAC1C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,eAAe,EAAE,CAAC;KAC1B,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8GlD,CAAC"}
@@ -8,6 +8,7 @@ import React, { useEffect } from 'react';
8
8
  import { useSearchContext } from './SearchProvider';
9
9
  import { useSearchState } from '../hooks/useSearchState';
10
10
  import { clsx } from 'clsx';
11
+ import { CustomSelect } from './primitives/CustomSelect';
11
12
  export const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, style, theme: customTheme, label = 'Show', syncWithState = true, }) => {
12
13
  const { theme } = useSearchContext();
13
14
  const { itemsPerPage, setPage } = useSearchState();
@@ -56,17 +57,26 @@ export const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, classNam
56
57
  fontSize: theme.typography.fontSize.medium,
57
58
  color: theme.colors.text,
58
59
  } }, label)),
59
- React.createElement("select", { value: value, onChange: handleChange, className: hitsPerPageTheme.select, style: {
60
- padding: theme.spacing.small,
61
- paddingRight: theme.spacing.medium,
62
- fontSize: theme.typography.fontSize.medium,
63
- border: `1px solid ${theme.colors.border}`,
64
- borderRadius: typeof theme.borderRadius === 'string'
60
+ React.createElement(CustomSelect, { value: String(value), onChange: (val) => {
61
+ const newValue = parseInt(val, 10);
62
+ setInternalValue(newValue);
63
+ if (syncWithState) {
64
+ stateManager.setItemsPerPage(newValue, false);
65
+ setPage(1, true);
66
+ }
67
+ if (onHitsPerPageChange) {
68
+ onHitsPerPageChange(newValue);
69
+ }
70
+ }, options: items.map((item) => ({
71
+ value: String(item.value),
72
+ label: item.label,
73
+ })), "aria-label": "Results per page", className: hitsPerPageTheme.select, style: {
74
+ '--seekora-select-font-size': theme.typography.fontSize.medium,
75
+ '--seekora-select-border': theme.colors.border,
76
+ '--seekora-select-radius': typeof theme.borderRadius === 'string'
65
77
  ? theme.borderRadius
66
78
  : theme.borderRadius.medium,
67
- backgroundColor: theme.colors.background,
68
- color: theme.colors.text,
69
- cursor: 'pointer',
70
- outline: 'none',
71
- }, "aria-label": "Results per page" }, items.map((item) => (React.createElement("option", { key: item.value, value: item.value, className: hitsPerPageTheme.option }, item.label))))));
79
+ '--seekora-select-bg': theme.colors.background,
80
+ '--seekora-select-color': theme.colors.text,
81
+ } })));
72
82
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SortBy.d.ts","sourceRoot":"","sources":["../../src/components/SortBy.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAwC,MAAM,OAAO,CAAC;AAS7D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AACxE,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,OAAO,EAAE,UAAU,EAAE,CAAC;KACvB,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA8BD,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmSxC,CAAC"}
1
+ {"version":3,"file":"SortBy.d.ts","sourceRoot":"","sources":["../../src/components/SortBy.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAwC,MAAM,OAAO,CAAC;AAU7D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AACxE,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,OAAO,EAAE,UAAU,EAAE,CAAC;KACvB,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA8BD,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAuRxC,CAAC"}
@@ -19,6 +19,7 @@ import React, { useCallback, useEffect, useId } from 'react';
19
19
  import { useSearchContext } from './SearchProvider';
20
20
  import { useSearchState } from '../hooks/useSearchState';
21
21
  import { clsx } from 'clsx';
22
+ import { CustomSelect } from './primitives/CustomSelect';
22
23
  // ---------------------------------------------------------------------------
23
24
  // Helpers
24
25
  // ---------------------------------------------------------------------------
@@ -105,18 +106,14 @@ export const SortBy = ({ options, value: valueProp, defaultValue, onSortChange,
105
106
  }
106
107
  return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
107
108
  labelElement,
108
- React.createElement("select", { value: value, onChange: handleChange, className: clsx(sortByTheme.select), style: {
109
- padding,
110
- paddingRight: theme.spacing.medium,
111
- fontSize,
112
- border: '1px solid var(--seekora-sort-border)',
113
- borderRadius,
114
- backgroundColor: 'var(--seekora-sort-bg)',
115
- color: 'var(--seekora-sort-color)',
116
- cursor: 'pointer',
117
- outline: 'none',
109
+ React.createElement(CustomSelect, { value: value, onChange: applyValue, options: options, placeholder: placeholder, "aria-label": label || 'Sort results', className: clsx(sortByTheme.select), style: {
118
110
  width: '100%',
119
- }, "aria-label": label || 'Sort results' }, options.map((option) => (React.createElement("option", { key: option.value, value: option.value, className: sortByTheme.option }, option.label))))));
111
+ '--seekora-select-font-size': fontSize,
112
+ '--seekora-select-border': 'var(--seekora-sort-border)',
113
+ '--seekora-select-radius': borderRadius,
114
+ '--seekora-select-bg': 'var(--seekora-sort-bg)',
115
+ '--seekora-select-color': 'var(--seekora-sort-color)',
116
+ } })));
120
117
  }
121
118
  // ------ Button group variant -------------------------------------------
122
119
  if (variant === 'button-group') {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * CustomSelect — lightweight, accessible custom dropdown replacement for native <select>.
3
+ *
4
+ * CSS Variables (apply on a parent element to customize):
5
+ * --seekora-select-bg — background color (default: #fff)
6
+ * --seekora-select-color — text color (default: inherit)
7
+ * --seekora-select-border — border color (default: rgba(128,128,128,0.3))
8
+ * --seekora-select-hover-bg — option hover background (default: #f3f4f6)
9
+ * --seekora-select-active-bg — selected option background (default: #eff6ff)
10
+ * --seekora-select-active-color — selected option text color (default: inherit)
11
+ * --seekora-select-radius — border radius (default: 6px)
12
+ * --seekora-select-font-size — font size (default: 0.875rem)
13
+ */
14
+ import React from 'react';
15
+ export interface CustomSelectOption {
16
+ value: string;
17
+ label: string;
18
+ disabled?: boolean;
19
+ }
20
+ export interface CustomSelectTheme {
21
+ trigger?: string;
22
+ menu?: string;
23
+ option?: string;
24
+ optionActive?: string;
25
+ optionDisabled?: string;
26
+ }
27
+ export interface CustomSelectProps {
28
+ value: string;
29
+ onChange: (value: string) => void;
30
+ options: CustomSelectOption[];
31
+ placeholder?: string;
32
+ className?: string;
33
+ style?: React.CSSProperties;
34
+ theme?: CustomSelectTheme;
35
+ /** aria-label for the trigger button */
36
+ 'aria-label'?: string;
37
+ disabled?: boolean;
38
+ }
39
+ export declare const CustomSelect: React.FC<CustomSelectProps>;
40
+ //# sourceMappingURL=CustomSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomSelect.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/CustomSelect.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAA0D,MAAM,OAAO,CAAC;AAO/E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAMD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAqQpD,CAAC"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * CustomSelect — lightweight, accessible custom dropdown replacement for native <select>.
3
+ *
4
+ * CSS Variables (apply on a parent element to customize):
5
+ * --seekora-select-bg — background color (default: #fff)
6
+ * --seekora-select-color — text color (default: inherit)
7
+ * --seekora-select-border — border color (default: rgba(128,128,128,0.3))
8
+ * --seekora-select-hover-bg — option hover background (default: #f3f4f6)
9
+ * --seekora-select-active-bg — selected option background (default: #eff6ff)
10
+ * --seekora-select-active-color — selected option text color (default: inherit)
11
+ * --seekora-select-radius — border radius (default: 6px)
12
+ * --seekora-select-font-size — font size (default: 0.875rem)
13
+ */
14
+ import React, { useState, useRef, useCallback, useEffect, useId } from 'react';
15
+ import { clsx } from 'clsx';
16
+ // ---------------------------------------------------------------------------
17
+ // Component
18
+ // ---------------------------------------------------------------------------
19
+ export const CustomSelect = ({ value, onChange, options, placeholder = 'Select...', className, style, theme: customTheme, 'aria-label': ariaLabel, disabled = false, }) => {
20
+ const [isOpen, setIsOpen] = useState(false);
21
+ const [activeIndex, setActiveIndex] = useState(-1);
22
+ const containerRef = useRef(null);
23
+ const menuRef = useRef(null);
24
+ const instanceId = useId();
25
+ const t = customTheme || {};
26
+ const selectedOption = options.find((o) => o.value === value);
27
+ const displayLabel = selectedOption?.label ?? placeholder;
28
+ // Close on click outside
29
+ useEffect(() => {
30
+ if (!isOpen)
31
+ return;
32
+ const handleMouseDown = (e) => {
33
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
34
+ setIsOpen(false);
35
+ setActiveIndex(-1);
36
+ }
37
+ };
38
+ document.addEventListener('mousedown', handleMouseDown);
39
+ return () => document.removeEventListener('mousedown', handleMouseDown);
40
+ }, [isOpen]);
41
+ // Scroll active item into view
42
+ useEffect(() => {
43
+ if (!isOpen || activeIndex < 0 || !menuRef.current)
44
+ return;
45
+ const item = menuRef.current.children[activeIndex];
46
+ item?.scrollIntoView?.({ block: 'nearest' });
47
+ }, [activeIndex, isOpen]);
48
+ const toggle = useCallback(() => {
49
+ if (disabled)
50
+ return;
51
+ setIsOpen((prev) => {
52
+ if (!prev) {
53
+ // Opening — highlight current selection
54
+ const idx = options.findIndex((o) => o.value === value);
55
+ setActiveIndex(idx >= 0 ? idx : 0);
56
+ }
57
+ return !prev;
58
+ });
59
+ }, [disabled, options, value]);
60
+ const selectOption = useCallback((opt) => {
61
+ if (opt.disabled)
62
+ return;
63
+ onChange(opt.value);
64
+ setIsOpen(false);
65
+ setActiveIndex(-1);
66
+ }, [onChange]);
67
+ // Find next non-disabled index in given direction
68
+ const findNextEnabled = (from, dir) => {
69
+ let idx = from;
70
+ for (let i = 0; i < options.length; i++) {
71
+ idx += dir;
72
+ if (idx < 0)
73
+ idx = options.length - 1;
74
+ if (idx >= options.length)
75
+ idx = 0;
76
+ if (!options[idx].disabled)
77
+ return idx;
78
+ }
79
+ return from;
80
+ };
81
+ const handleKeyDown = useCallback((e) => {
82
+ if (disabled)
83
+ return;
84
+ if (!isOpen) {
85
+ if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
86
+ e.preventDefault();
87
+ toggle();
88
+ }
89
+ return;
90
+ }
91
+ switch (e.key) {
92
+ case 'ArrowDown':
93
+ e.preventDefault();
94
+ setActiveIndex((prev) => findNextEnabled(prev, 1));
95
+ break;
96
+ case 'ArrowUp':
97
+ e.preventDefault();
98
+ setActiveIndex((prev) => findNextEnabled(prev, -1));
99
+ break;
100
+ case 'Enter':
101
+ case ' ':
102
+ e.preventDefault();
103
+ if (activeIndex >= 0 && !options[activeIndex].disabled) {
104
+ selectOption(options[activeIndex]);
105
+ }
106
+ break;
107
+ case 'Escape':
108
+ e.preventDefault();
109
+ setIsOpen(false);
110
+ setActiveIndex(-1);
111
+ break;
112
+ case 'Home':
113
+ e.preventDefault();
114
+ setActiveIndex(findNextEnabled(-1, 1));
115
+ break;
116
+ case 'End':
117
+ e.preventDefault();
118
+ setActiveIndex(findNextEnabled(options.length, -1));
119
+ break;
120
+ case 'Tab':
121
+ setIsOpen(false);
122
+ setActiveIndex(-1);
123
+ break;
124
+ }
125
+ }, [disabled, isOpen, activeIndex, options, toggle, selectOption]);
126
+ const menuId = `seekora-select-menu-${instanceId}`;
127
+ return (React.createElement("div", { ref: containerRef, className: clsx('seekora-select', className), style: { position: 'relative', display: 'inline-block', ...style }, onKeyDown: handleKeyDown },
128
+ React.createElement("button", { type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel, "aria-activedescendant": isOpen && activeIndex >= 0
129
+ ? `${menuId}-opt-${activeIndex}`
130
+ : undefined, disabled: disabled, onClick: toggle, className: clsx('seekora-select__trigger', t.trigger), style: {
131
+ display: 'flex',
132
+ alignItems: 'center',
133
+ justifyContent: 'space-between',
134
+ gap: 8,
135
+ width: '100%',
136
+ padding: '8px 12px',
137
+ fontSize: 'var(--seekora-select-font-size, 0.875rem)',
138
+ lineHeight: 1.4,
139
+ border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
140
+ borderRadius: 'var(--seekora-select-radius, 6px)',
141
+ backgroundColor: 'var(--seekora-select-bg, #fff)',
142
+ color: 'var(--seekora-select-color, inherit)',
143
+ cursor: disabled ? 'not-allowed' : 'pointer',
144
+ outline: 'none',
145
+ textAlign: 'left',
146
+ fontFamily: 'inherit',
147
+ opacity: disabled ? 0.5 : 1,
148
+ } },
149
+ React.createElement("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, displayLabel),
150
+ React.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", style: {
151
+ flexShrink: 0,
152
+ transition: 'transform 0.15s ease',
153
+ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
154
+ } },
155
+ React.createElement("path", { d: "M2.5 4.5L6 8L9.5 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))),
156
+ isOpen && (React.createElement("div", { ref: menuRef, id: menuId, role: "listbox", className: clsx('seekora-select__menu', t.menu), style: {
157
+ position: 'absolute',
158
+ top: '100%',
159
+ left: 0,
160
+ right: 0,
161
+ zIndex: 9999,
162
+ marginTop: 4,
163
+ padding: '4px 0',
164
+ border: '1px solid var(--seekora-select-border, rgba(128,128,128,0.3))',
165
+ borderRadius: 'var(--seekora-select-radius, 6px)',
166
+ backgroundColor: 'var(--seekora-select-bg, #fff)',
167
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
168
+ maxHeight: 220,
169
+ overflowY: 'auto',
170
+ } }, options.map((opt, idx) => {
171
+ const isSelected = opt.value === value;
172
+ const isHighlighted = idx === activeIndex;
173
+ 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) => {
174
+ e.preventDefault(); // Prevent blur
175
+ if (!opt.disabled)
176
+ selectOption(opt);
177
+ }, style: {
178
+ padding: '6px 12px',
179
+ fontSize: 'var(--seekora-select-font-size, 0.875rem)',
180
+ cursor: opt.disabled ? 'not-allowed' : 'pointer',
181
+ backgroundColor: isHighlighted
182
+ ? 'var(--seekora-select-hover-bg, #f3f4f6)'
183
+ : isSelected
184
+ ? 'var(--seekora-select-active-bg, #eff6ff)'
185
+ : 'transparent',
186
+ color: opt.disabled
187
+ ? 'rgba(128,128,128,0.5)'
188
+ : isSelected
189
+ ? 'var(--seekora-select-active-color, inherit)'
190
+ : 'var(--seekora-select-color, inherit)',
191
+ fontWeight: isSelected ? 500 : 400,
192
+ opacity: opt.disabled ? 0.5 : 1,
193
+ transition: 'background-color 0.1s ease',
194
+ } }, opt.label));
195
+ })))));
196
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"ImageZoom.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ImageZoom.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,aAAa,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,EACxB,GAAG,EACH,GAAQ,EACR,IAAa,EACb,SAAe,EACf,SAAS,EACT,KAAK,EACL,iBAAwB,EACxB,QAAc,EACd,aAA2C,EAC3C,MAAM,EACN,YAAgB,GACjB,EAAE,cAAc,qBAiiBhB"}
1
+ {"version":3,"file":"ImageZoom.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ImageZoom.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,aAAa,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,EACxB,GAAG,EACH,GAAQ,EACR,IAAa,EACb,SAAe,EACf,SAAS,EACT,KAAK,EACL,iBAAwB,EACxB,QAAc,EACd,aAA2C,EAC3C,MAAM,EACN,YAAgB,GACjB,EAAE,cAAc,qBAwjBhB"}
@@ -7,6 +7,7 @@
7
7
  * - click: Full-screen lightbox modal
8
8
  */
9
9
  import React, { useState, useRef, useCallback, useEffect } from 'react';
10
+ import { createPortal } from 'react-dom';
10
11
  import { clsx } from 'clsx';
11
12
  export function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, style, showZoomIndicator = true, lensSize = 150, zoomPanelSize = { width: 400, height: 400 }, images, currentIndex = 0, }) {
12
13
  const [isHovering, setIsHovering] = useState(false);
@@ -107,13 +108,30 @@ export function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, class
107
108
  objectFit: 'cover',
108
109
  display: 'block',
109
110
  };
111
+ // Compute the indicator rectangle (what portion of the image the zoom panel shows).
112
+ // indW / containerW = 1 / zoomLevel (the panel shows 1/zoomLevel of the image width).
113
+ // indH is derived from the zoom panel's aspect ratio so it's consistent with what the
114
+ // panel actually renders — no need for the image's natural dimensions.
115
+ const getIndicatorRect = useCallback(() => {
116
+ if (!containerRef.current)
117
+ return { indL: 0, indT: 0, indW: 0, indH: 0 };
118
+ const rect = containerRef.current.getBoundingClientRect();
119
+ const indW = rect.width / zoomLevel;
120
+ const indH = (zoomPanelSize.height / zoomPanelSize.width) * rect.width / zoomLevel;
121
+ const indL = Math.max(0, Math.min(rect.width - indW, cursorPos.x - indW / 2));
122
+ const indT = Math.max(0, Math.min(rect.height - indH, cursorPos.y - indH / 2));
123
+ return { indL, indT, indW, indH };
124
+ }, [cursorPos, zoomLevel, zoomPanelSize]);
110
125
  // Calculate zoom panel background position (Amazon-style)
111
126
  const getZoomPanelStyle = () => {
112
127
  if (!containerRef.current || !imageLoaded)
113
128
  return { display: 'none' };
114
129
  const rect = containerRef.current.getBoundingClientRect();
115
- const bgPosX = (cursorPos.x / rect.width) * 100;
116
- const bgPosY = (cursorPos.y / rect.height) * 100;
130
+ const { indL, indT, indW, indH } = getIndicatorRect();
131
+ // bgPos is derived from the clamped indicator position, not the raw cursor.
132
+ // At indL=0 → bgPosX=0% (show left), at indL=containerW-indW → bgPosX=100% (show right).
133
+ const bgPosX = (rect.width - indW) > 0 ? (indL / (rect.width - indW)) * 100 : 0;
134
+ const bgPosY = (rect.height - indH) > 0 ? (indT / (rect.height - indH)) * 100 : 0;
117
135
  // Calculate available space in all directions
118
136
  const viewportWidth = window.innerWidth;
119
137
  const viewportHeight = window.innerHeight;
@@ -241,19 +259,22 @@ export function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, class
241
259
  } }, "\uD83D\uDD0D")),
242
260
  supportsLens && isHovering && imageLoaded && (React.createElement("div", { style: getLensStyle() },
243
261
  React.createElement("img", { src: src, alt: "", style: getLensImageStyle() }))),
244
- supportsHover && isHovering && imageLoaded && containerRef.current && (React.createElement("div", { style: {
245
- position: 'absolute',
246
- left: cursorPos.x - 75,
247
- top: cursorPos.y - 75,
248
- width: 150,
249
- height: 150,
250
- border: 'var(--seekora-hover-area-border, 2px solid rgba(0,0,0,0.3))',
251
- backgroundColor: 'var(--seekora-hover-area-bg, rgba(255,255,255,0.1))',
252
- pointerEvents: 'none',
253
- zIndex: 50,
254
- } })),
255
- supportsHover && isHovering && imageLoaded && (React.createElement("div", { style: getZoomPanelStyle() }))),
256
- isLightboxOpen && (React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
262
+ supportsHover && isHovering && imageLoaded && containerRef.current && (() => {
263
+ const { indL, indT, indW, indH } = getIndicatorRect();
264
+ return (React.createElement("div", { style: {
265
+ position: 'absolute',
266
+ left: indL,
267
+ top: indT,
268
+ width: indW,
269
+ height: indH,
270
+ border: 'var(--seekora-hover-area-border, 2px solid rgba(0,0,0,0.3))',
271
+ backgroundColor: 'var(--seekora-hover-area-bg, rgba(255,255,255,0.1))',
272
+ pointerEvents: 'none',
273
+ zIndex: 50,
274
+ } }));
275
+ })()),
276
+ supportsHover && isHovering && imageLoaded && typeof document !== 'undefined' && createPortal(React.createElement("div", { style: getZoomPanelStyle() }), document.body),
277
+ isLightboxOpen && typeof document !== 'undefined' && createPortal(React.createElement("div", { className: "seekora-image-zoom-lightbox", style: {
257
278
  position: 'fixed',
258
279
  top: 0,
259
280
  left: 0,
@@ -408,5 +429,5 @@ export function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, class
408
429
  backgroundColor: 'var(--seekora-lightbox-instructions-bg, rgba(0,0,0,0.5))',
409
430
  padding: '8px 16px',
410
431
  borderRadius: 12,
411
- } }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')))));
432
+ } }, hasMultipleImages ? 'Use arrow keys or click thumbnails to navigate • ESC to close' : 'Click outside or press ESC to close')), document.body)));
412
433
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VariantSelector.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/VariantSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE9E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;IACrE,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAqDD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAqB,EACrB,QAAQ,EACR,gBAAuB,EACvB,eAAe,EAAE,gBAAgB,EACjC,SAAS,EACT,KAAK,GACN,EAAE,oBAAoB,4BA0KtB"}
1
+ {"version":3,"file":"VariantSelector.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/VariantSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG9E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;IACrE,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAqDD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAqB,EACrB,QAAQ,EACR,gBAAuB,EACvB,eAAe,EAAE,gBAAgB,EACjC,SAAS,EACT,KAAK,GACN,EAAE,oBAAoB,4BAsKtB"}
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import React from 'react';
10
10
  import { clsx } from 'clsx';
11
+ import { CustomSelect } from './CustomSelect';
11
12
  const COLOR_NAMES = {
12
13
  black: '#000', white: '#fff', red: '#ef4444', blue: '#3b82f6',
13
14
  green: '#22c55e', yellow: '#eab308', orange: '#f97316', purple: '#a855f7',
@@ -80,30 +81,22 @@ export function VariantSelector({ options, variants, selections, onSelectionChan
80
81
  } },
81
82
  option.name,
82
83
  selected && (React.createElement("span", { style: { fontWeight: 400, marginLeft: 6, color: 'var(--seekora-text-secondary, inherit)' } }, selected))),
83
- mode === 'dropdown' ? (React.createElement("select", { className: "seekora-variant-dropdown", value: selected ?? '', onChange: (e) => {
84
- e.stopPropagation();
85
- onSelectionChange(option.name, e.target.value);
86
- }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), style: {
87
- padding: '8px 12px',
88
- fontSize: '0.875rem',
89
- borderRadius: 6,
90
- border: '1px solid var(--seekora-border-color, rgba(128,128,128,0.2))',
91
- backgroundColor: 'var(--seekora-bg-surface, transparent)',
92
- color: 'var(--seekora-text-primary, inherit)',
93
- cursor: 'pointer',
94
- minWidth: 120,
95
- } },
96
- React.createElement("option", { value: "" },
97
- "Select ",
98
- option.name),
99
- option.values.map((value) => {
100
- const available = showAvailability
101
- ? getAvailability(option.name, value, options, variants, selections)
102
- : true;
103
- return (React.createElement("option", { key: value, value: value, disabled: !available },
104
- value,
105
- !available ? ' (Unavailable)' : ''));
106
- }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
84
+ mode === 'dropdown' ? (React.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() },
85
+ 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) => {
86
+ const available = showAvailability
87
+ ? getAvailability(option.name, val, options, variants, selections)
88
+ : true;
89
+ return {
90
+ value: val,
91
+ label: available ? val : `${val} (Unavailable)`,
92
+ disabled: !available,
93
+ };
94
+ }), style: {
95
+ minWidth: 120,
96
+ '--seekora-select-border': 'var(--seekora-border-color, rgba(128,128,128,0.2))',
97
+ '--seekora-select-bg': 'var(--seekora-bg-surface, transparent)',
98
+ '--seekora-select-color': 'var(--seekora-text-primary, inherit)',
99
+ } }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
107
100
  const isActive = selected === value;
108
101
  const available = showAvailability
109
102
  ? getAvailability(option.name, value, options, variants, selections)
@@ -8,4 +8,5 @@ export { VariantSelector, type VariantSelectorProps } from './VariantSelector';
8
8
  export { ActionButtons, type ActionButtonsProps, type ActionButton, type ActionButtonType } from './ActionButtons';
9
9
  export { withAnalytics, type WithAnalyticsConfig, type WithAnalyticsInjectedProps } from './withAnalytics';
10
10
  export { AnalyticsProvider, useAnalyticsProvider, type AnalyticsProviderProps } from './AnalyticsProvider';
11
+ export { CustomSelect, type CustomSelectProps, type CustomSelectOption, type CustomSelectTheme } from './CustomSelect';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC3G,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -8,3 +8,4 @@ export { VariantSelector } from './VariantSelector';
8
8
  export { ActionButtons } from './ActionButtons';
9
9
  export { withAnalytics } from './withAnalytics';
10
10
  export { AnalyticsProvider, useAnalyticsProvider } from './AnalyticsProvider';
11
+ export { CustomSelect } from './CustomSelect';
package/dist/index.d.ts CHANGED
@@ -34,8 +34,8 @@ export { FederatedDropdown } from './components/FederatedDropdown';
34
34
  export { SearchBarWithSuggestions } from './components/SearchBarWithSuggestions';
35
35
  export { SuggestionsProvider, useSuggestionsContext, SearchInput, DropdownPanel, SuggestionList, SuggestionItem, ItemCard, ItemGrid, ProductCard, ProductGrid, CategoriesTabs, SuggestionsError, SuggestionsDropdownComposition, RecentSearchesList, TrendingList, parseHighlightMarkup, } from './components/suggestions-primitives';
36
36
  export type { SuggestionsDropdownCompositionProps, RecentSearchesListProps, TrendingListProps, SuggestionsContextValue, SuggestionsProviderProps, SearchInputProps, DropdownPanelProps, SuggestionListProps, SuggestionItemProps, HighlightMarkupOptions, GenericItem, ItemCardProps, ItemGridProps, ProductCardProps, ProductGridProps, CategoriesTabsProps, SuggestionsErrorProps, ItemCardImageVariant, ProductCardImageVariant, } from './components/suggestions-primitives';
37
- export { ImageDisplay, ImageZoom, PriceDisplay, BadgeList, RatingDisplay, VariantSwatches, VariantSelector, ActionButtons, withAnalytics, AnalyticsProvider, useAnalyticsProvider, } from './components/primitives';
38
- export type { ImageDisplayVariant, ImageDisplayProps, ImageZoomProps, ImageZoomMode, PriceDisplayProps, BadgeListProps, RatingDisplayProps, RatingVariant, RatingSize, VariantSwatchesProps, VariantSelectorProps, ActionButtonsProps, ActionButton, ActionButtonType, WithAnalyticsConfig, WithAnalyticsInjectedProps, AnalyticsProviderProps, } from './components/primitives';
37
+ export { ImageDisplay, ImageZoom, PriceDisplay, BadgeList, RatingDisplay, VariantSwatches, VariantSelector, ActionButtons, withAnalytics, AnalyticsProvider, useAnalyticsProvider, CustomSelect, } from './components/primitives';
38
+ export type { ImageDisplayVariant, ImageDisplayProps, ImageZoomProps, ImageZoomMode, PriceDisplayProps, BadgeListProps, RatingDisplayProps, RatingVariant, RatingSize, VariantSwatchesProps, VariantSelectorProps, ActionButtonsProps, ActionButton, ActionButtonType, WithAnalyticsConfig, WithAnalyticsInjectedProps, AnalyticsProviderProps, CustomSelectProps, CustomSelectOption, CustomSelectTheme, } from './components/primitives';
39
39
  export { SectionSearchProvider, useSectionSearchContext, SectionItemGrid, SectionError, } from './components/section-primitives';
40
40
  export type { SectionSearchProviderProps, SectionSearchContextValue, RefinementInput, SectionItemGridProps, SectionErrorProps, } from './components/section-primitives';
41
41
  export { ProductGallery, ProductInfo, ProductRecommendations } from './components/product-page';