@envive-ai/react-toolkit 0.1.10 → 0.1.12

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 (162) hide show
  1. package/dist/Accordion/index.cjs +3 -82
  2. package/dist/Accordion/index.d.cts +2 -2
  3. package/dist/Accordion/index.d.ts +2 -2
  4. package/dist/Accordion/index.js +2 -75
  5. package/dist/Accordion-Cp3Hx2zm.js +77 -0
  6. package/dist/Accordion-DBZpiQe7.cjs +88 -0
  7. package/dist/AppliedFiltersScrollbar/index.d.cts +2 -2
  8. package/dist/AppliedFiltersScrollbar/index.d.ts +2 -2
  9. package/dist/ButtonBase/index.cjs +2 -2
  10. package/dist/ButtonBase/index.d.cts +2 -2
  11. package/dist/ButtonBase/index.d.ts +2 -2
  12. package/dist/ButtonBase/index.js +2 -2
  13. package/dist/DynamicFiltersScrollbar/index.d.cts +2 -2
  14. package/dist/DynamicFiltersScrollbar/index.d.ts +2 -2
  15. package/dist/FilterScrollbar/index.cjs +1 -1
  16. package/dist/FilterScrollbar/index.d.cts +3 -3
  17. package/dist/FilterScrollbar/index.d.ts +3 -3
  18. package/dist/FilterScrollbar/index.js +1 -1
  19. package/dist/Headline/index.cjs +1 -1
  20. package/dist/Headline/index.d.cts +2 -2
  21. package/dist/Headline/index.d.ts +2 -2
  22. package/dist/Headline/index.js +1 -1
  23. package/dist/ImageWithFallback/index.d.cts +2 -2
  24. package/dist/ImageWithFallback/index.d.ts +2 -2
  25. package/dist/ModalSheet/index.cjs +3 -0
  26. package/dist/ModalSheet/index.d.cts +38 -0
  27. package/dist/ModalSheet/index.d.ts +38 -0
  28. package/dist/ModalSheet/index.js +3 -0
  29. package/dist/ModalSheet-01pxpy_K.js +326 -0
  30. package/dist/ModalSheet-Csz8HIxs.cjs +337 -0
  31. package/dist/ProductCard/index.cjs +3 -3
  32. package/dist/ProductCard/index.d.cts +1 -1
  33. package/dist/ProductCard/index.d.ts +1 -1
  34. package/dist/ProductCard/index.js +3 -3
  35. package/dist/{ProductCard-Dkkhl9pk.js → ProductCard-C3MjnfZJ.js} +3 -3
  36. package/dist/{ProductCard-CymUvJnC.cjs → ProductCard-FDyIMnZs.cjs} +5 -5
  37. package/dist/ProductGrid/index.cjs +4 -4
  38. package/dist/ProductGrid/index.d.cts +3 -3
  39. package/dist/ProductGrid/index.d.ts +3 -3
  40. package/dist/ProductGrid/index.js +4 -4
  41. package/dist/{ProductGrid-B3Ypqh7j.js → ProductGrid-C9OKsyB2.js} +1 -1
  42. package/dist/{ProductGrid-ZNRujkuN.cjs → ProductGrid-TlntVJ6h.cjs} +1 -1
  43. package/dist/RadioButton/index.cjs +6 -0
  44. package/dist/RadioButton/index.d.cts +30 -0
  45. package/dist/RadioButton/index.d.ts +30 -0
  46. package/dist/RadioButton/index.js +6 -0
  47. package/dist/RadioButton-Bf68dZl7.js +68 -0
  48. package/dist/RadioButton-DZ6QXkrN.cjs +77 -0
  49. package/dist/RadioButtonGroup/index.cjs +8 -0
  50. package/dist/RadioButtonGroup/index.d.cts +36 -0
  51. package/dist/RadioButtonGroup/index.d.ts +36 -0
  52. package/dist/RadioButtonGroup/index.js +7 -0
  53. package/dist/RadioButtonGroup-8k7hkJYB.js +37 -0
  54. package/dist/RadioButtonGroup-Dc_n5amh.cjs +51 -0
  55. package/dist/SearchAutocomplete/index.cjs +1 -1
  56. package/dist/SearchAutocomplete/index.js +1 -1
  57. package/dist/SearchFilter/index.cjs +9 -2
  58. package/dist/SearchFilter/index.d.cts +15 -9
  59. package/dist/SearchFilter/index.d.ts +15 -9
  60. package/dist/SearchFilter/index.js +9 -2
  61. package/dist/SearchFilter-CTVBi5s3.cjs +279 -0
  62. package/dist/SearchFilter-DvqBIXa1.js +258 -0
  63. package/dist/SearchInput/index.cjs +3 -3
  64. package/dist/SearchInput/index.d.cts +1 -1
  65. package/dist/SearchInput/index.d.ts +2 -2
  66. package/dist/SearchInput/index.js +3 -3
  67. package/dist/{SearchInput-BT1rrc4F.js → SearchInput-CVaCPWCE.js} +2 -2
  68. package/dist/{SearchInput-CdR_nsR3.cjs → SearchInput-vJMWoLzS.cjs} +2 -2
  69. package/dist/SearchInputForm/index.cjs +4 -4
  70. package/dist/SearchInputForm/index.d.cts +1 -1
  71. package/dist/SearchInputForm/index.d.ts +1 -1
  72. package/dist/SearchInputForm/index.js +4 -4
  73. package/dist/SearchResultsContent/index.cjs +14 -14
  74. package/dist/SearchResultsContent/index.d.cts +5 -5
  75. package/dist/SearchResultsContent/index.d.ts +5 -5
  76. package/dist/SearchResultsContent/index.js +10 -10
  77. package/dist/SearchResultsFilterSidebar/index.cjs +15 -8
  78. package/dist/SearchResultsFilterSidebar/index.d.cts +1 -1
  79. package/dist/SearchResultsFilterSidebar/index.d.ts +1 -1
  80. package/dist/SearchResultsFilterSidebar/index.js +13 -6
  81. package/dist/SearchResultsStates/index.cjs +9 -9
  82. package/dist/SearchResultsStates/index.d.cts +6 -6
  83. package/dist/SearchResultsStates/index.d.ts +6 -6
  84. package/dist/SearchResultsStates/index.js +9 -9
  85. package/dist/{SearchResultsStates-ESlrrf20.cjs → SearchResultsStates-CH4agenX.cjs} +5 -5
  86. package/dist/{SearchResultsStates-BxJphoL-.js → SearchResultsStates-iKz64Pd4.js} +5 -5
  87. package/dist/SparkleAnimation/index.cjs +1 -1
  88. package/dist/SparkleAnimation/index.d.cts +2 -2
  89. package/dist/SparkleAnimation/index.d.ts +2 -2
  90. package/dist/SparkleAnimation/index.js +1 -1
  91. package/dist/Spinner/index.cjs +1 -1
  92. package/dist/Spinner/index.d.cts +2 -2
  93. package/dist/Spinner/index.d.ts +2 -2
  94. package/dist/Spinner/index.js +1 -1
  95. package/dist/SuggestionButton/index.cjs +1 -1
  96. package/dist/SuggestionButton/index.d.cts +2 -2
  97. package/dist/SuggestionButton/index.d.ts +2 -2
  98. package/dist/SuggestionButton/index.js +1 -1
  99. package/dist/Text/index.d.cts +2 -2
  100. package/dist/Text/index.d.ts +3 -3
  101. package/dist/TextInput/index.cjs +1 -1
  102. package/dist/TextInput/index.d.ts +1 -1
  103. package/dist/TextInput/index.js +1 -1
  104. package/dist/ToggleButton/index.cjs +6 -0
  105. package/dist/ToggleButton/index.d.cts +30 -0
  106. package/dist/ToggleButton/index.d.ts +30 -0
  107. package/dist/ToggleButton/index.js +6 -0
  108. package/dist/ToggleButton-CK_vkMvt.cjs +68 -0
  109. package/dist/ToggleButton-GIVd2-Z4.js +60 -0
  110. package/dist/{index-DV2K-9lE.d.cts → index-B8l3muO3.d.ts} +5 -5
  111. package/dist/{index-agU5rfIs.d.ts → index-BMt66uqr.d.cts} +5 -5
  112. package/dist/{index-b4yAASuy.d.cts → index-CFH3KUVw.d.cts} +4 -4
  113. package/dist/{index-uU3sIiRx.d.ts → index-H_9LhS_1.d.cts} +2 -2
  114. package/dist/{index-DggZTKvc.d.ts → index-Vck3pox6.d.ts} +4 -4
  115. package/dist/{index-Bpjv4lP2.d.cts → index-h-QwQNnu.d.ts} +2 -2
  116. package/dist/{searchFilterSidebarVariants-BnIcOVWR.js → searchFilterSidebarVariants-CI782ylo.js} +1 -1
  117. package/dist/{searchFilterSidebarVariants-DVZ7wRlb.cjs → searchFilterSidebarVariants-DFfOTqjc.cjs} +1 -1
  118. package/package.json +20 -3
  119. package/src/components/AnimatedChevron/AnimatedChevron.tsx +37 -0
  120. package/src/components/ModalSheet/ModalSheet.tsx +42 -0
  121. package/src/components/ModalSheet/common/closeIcon.tsx +40 -0
  122. package/src/components/ModalSheet/desktop/desktopHeader.tsx +24 -0
  123. package/src/components/ModalSheet/desktop/index.tsx +119 -0
  124. package/src/components/ModalSheet/index.ts +1 -0
  125. package/src/components/ModalSheet/mobile/index.tsx +118 -0
  126. package/src/components/ModalSheet/mobile/mobileHeader.tsx +42 -0
  127. package/src/components/ModalSheet/types.ts +33 -0
  128. package/src/components/ProductCard/ProductCard.tsx +1 -1
  129. package/src/components/RadioButton/RadioButton.tsx +88 -0
  130. package/src/components/RadioButton/index.ts +1 -0
  131. package/src/components/RadioButtonGroup/RadioButtonGroup.tsx +72 -0
  132. package/src/components/RadioButtonGroup/index.ts +1 -0
  133. package/src/components/SearchFilter/SearchFilter.tsx +161 -64
  134. package/src/components/SearchFilter/SearchFilterFooter.tsx +55 -0
  135. package/src/components/SearchFilter/types.ts +2 -1
  136. package/src/components/SearchFilter/useHasFilterStateChanged.tsx +40 -0
  137. package/src/components/SearchFilter/utils.ts +15 -0
  138. package/src/components/SearchResultsContent/SearchResultsContent.tsx +1 -1
  139. package/src/components/SearchResultsFilterSidebar/SearchResultsFilter.tsx +2 -2
  140. package/src/components/ToggleButton/ToggleButton.tsx +98 -0
  141. package/src/components/ToggleButton/index.ts +1 -0
  142. package/dist/SearchFilter-CLYCSNAa.cjs +0 -137
  143. package/dist/SearchFilter-qQPpx-34.js +0 -117
  144. /package/dist/{ButtonBase-C_uKnl48.js → ButtonBase-0NN6wmX-.js} +0 -0
  145. /package/dist/{ButtonBase-DGbSm0SJ.js → ButtonBase-BAf-nlCm.js} +0 -0
  146. /package/dist/{ButtonBase-DbWQ25n-.cjs → ButtonBase-BIXx56hq.cjs} +0 -0
  147. /package/dist/{ButtonBase-DGpQBeLR.cjs → ButtonBase-Do88ndKa.cjs} +0 -0
  148. /package/dist/{DynamicFiltersScrollbar-Dev5vGsW.js → DynamicFiltersScrollbar-C4kdNSJ9.js} +0 -0
  149. /package/dist/{DynamicFiltersScrollbar-ChvmEiPB.cjs → DynamicFiltersScrollbar-CVw1PINp.cjs} +0 -0
  150. /package/dist/{Headline-DTaT30_m.js → Headline-DNEWF8ly.js} +0 -0
  151. /package/dist/{Headline-DSmu5Mg8.cjs → Headline-DTT4RSv2.cjs} +0 -0
  152. /package/dist/{SearchAutocomplete-C4RY0IoT.js → SearchAutocomplete-BlpII8Xs.js} +0 -0
  153. /package/dist/{SearchAutocomplete-hQDnKtwQ.cjs → SearchAutocomplete-DIEhLT4C.cjs} +0 -0
  154. /package/dist/{SparkleAnimation-fQHP7b-R.js → SparkleAnimation-BLfNojLv.js} +0 -0
  155. /package/dist/{SparkleAnimation-BY5iw7s0.cjs → SparkleAnimation-qi5WCJ9B.cjs} +0 -0
  156. /package/dist/{Spinner-BqTt55uu.js → Spinner-CjGLIRgs.js} +0 -0
  157. /package/dist/{Spinner-DjK8ts9E.cjs → Spinner-DFor2Szi.cjs} +0 -0
  158. /package/dist/{TextInput-BJrdkZsM.cjs → TextInput-B3dTeD3q.cjs} +0 -0
  159. /package/dist/{TextInput-DIjjsSMg.js → TextInput-DoM41M53.js} +0 -0
  160. /package/dist/{colorsConfig-CJTKbJsm.cjs → colorsConfig-D-MZuBvt.cjs} +0 -0
  161. /package/dist/{colorsConfig-CYZ8f_gj.js → colorsConfig-DEfiLHH0.js} +0 -0
  162. /package/dist/{textVariantClasses-ypYGLq0h.d.ts → textVariantClasses-kyZtL8F5.d.ts} +0 -0
@@ -1,84 +1,181 @@
1
1
  import classNames from 'classnames';
2
- import { SearchFilterItem } from './SearchFilterItem';
3
- import { Text } from 'src/components/Text/Text';
4
- import { ButtonBase } from 'src/components/ButtonBase/ButtonBase';
5
2
  import { SearchFilterProps } from './types';
3
+ import { useCallback } from 'react';
4
+ import { SearchFilterHeader } from './SearchFilterHeader';
5
+ import { useHasFilterStateChanged } from './useHasFilterStateChanged';
6
+ import { getSelectedFilterItemsCount, getTotalSelectedFilterItemsCount } from './utils';
7
+ import { ModalSheet } from '../ModalSheet';
8
+ import { FilterItemProps, FilterProps } from '@envive-ai/react-hooks/types';
9
+ import { ToggleButton } from '../ToggleButton';
10
+ import { Accordion } from '../Accordion';
11
+ import { RadioButtonGroup } from '../RadioButtonGroup';
12
+ import { SearchFilterFooter } from './SearchFilterFooter';
13
+
14
+ const SortFilter = ({
15
+ filter,
16
+ selectFilterItem,
17
+ radioButtonFillColor,
18
+ radioButtonHoverColor,
19
+ radioButtonUncheckedBorderColor,
20
+ }: FilterProps) => {
21
+ const filterTitle = filter.displayName;
22
+
23
+ const options = filter.items.map((item) => ({
24
+ label: item.displayName,
25
+ value: item.filterItemId,
26
+ isSelected: item.isSelected,
27
+ }));
28
+
29
+ const content = (
30
+ <RadioButtonGroup
31
+ options={options}
32
+ name={filterTitle}
33
+ onChange={(value) => {
34
+ const selectedItem = filter.items.find((item) => item.filterItemId === value);
35
+ selectFilterItem({
36
+ filterId: filter.filterId,
37
+ filterItemId: value,
38
+ isSelected: true,
39
+ displayName: selectedItem?.displayName || value,
40
+ });
41
+ }}
42
+ gap="large"
43
+ textButtonGap="large"
44
+ value={filter.items.find((item) => item.isSelected)?.filterItemId}
45
+ fillColor={radioButtonFillColor}
46
+ hoverColor={radioButtonHoverColor}
47
+ uncheckedBorderColor={radioButtonUncheckedBorderColor}
48
+ />
49
+ );
50
+
51
+ return <Accordion title={filterTitle} content={content} />;
52
+ };
53
+
54
+ const FilterItem = ({ filterId, filterItem, selectFilterItem }: FilterItemProps) => (
55
+ <ToggleButton
56
+ label={filterItem.displayName}
57
+ secondaryLabel={`(${filterItem.productCount})`}
58
+ isSelected={filterItem.isSelected}
59
+ onClick={() =>
60
+ selectFilterItem({
61
+ filterId,
62
+ filterItemId: filterItem.filterItemId,
63
+ isSelected: !filterItem.isSelected,
64
+ displayName: filterItem.displayName,
65
+ })
66
+ }
67
+ variant="default"
68
+ />
69
+ );
70
+
71
+ const Filter = ({ filter, selectFilterItem }: FilterProps) => {
72
+ const filterContentClassName = classNames({
73
+ 'spiffy-tw-flex': true,
74
+ 'spiffy-tw-flex-wrap': true,
75
+ 'spiffy-tw-gap-[8px]': true,
76
+ 'spiffy-tw-pb-[1px]': true,
77
+ });
78
+
79
+ const selectedFilterItems = getSelectedFilterItemsCount(filter);
80
+
81
+ const filterTitle =
82
+ selectedFilterItems > 0 ? `${filter.displayName} (${selectedFilterItems})` : filter.displayName;
83
+ const content = (
84
+ <div className={filterContentClassName}>
85
+ {filter.items.map((item) => (
86
+ <FilterItem
87
+ key={item.filterItemId}
88
+ filterId={filter.filterId}
89
+ filterItem={item}
90
+ selectFilterItem={selectFilterItem}
91
+ />
92
+ ))}
93
+ </div>
94
+ );
95
+ return <Accordion title={filterTitle} content={content} />;
96
+ };
6
97
 
7
98
  export const SearchFilter = ({
8
- isOpen,
9
- setIsOpen,
10
99
  filters,
11
- // productCount,
100
+ productCount,
12
101
  selectFilterItem,
13
102
  clearAllFilters,
14
- // applyFiltersUnchangedClasses,
15
- // applyFiltersChangedClasses,
16
- filterButtonText,
103
+ isOpen,
104
+ setIsOpen,
105
+ applyFiltersUnchangedClasses,
106
+ applyFiltersChangedClasses,
107
+ headerContent,
108
+ footerContent,
109
+ headerClassName,
17
110
  radioButtonFillColor,
18
111
  radioButtonHoverColor,
19
112
  radioButtonUncheckedBorderColor,
20
- // filterCloseIconVariant,
21
- headerContent,
113
+ filterCloseIconVariant,
22
114
  }: SearchFilterProps) => {
23
- const modalClasses = classNames(
24
- 'spiffy-tw-fixed spiffy-tw-inset-0 spiffy-tw-z-50 spiffy-tw-flex spiffy-tw-transform spiffy-tw-transition-all',
25
- {
26
- 'spiffy-tw-translate-x-full': !isOpen,
27
- 'spiffy-tw-translate-x-0': isOpen,
28
- },
29
- );
115
+ const closeModal = useCallback(() => setIsOpen(false), [setIsOpen]);
116
+ const filterCount = getTotalSelectedFilterItemsCount(filters);
30
117
 
31
- const overlayClasses = classNames(
32
- 'spiffy-tw-absolute spiffy-tw-inset-0 spiffy-tw-bg-black spiffy-tw-bg-opacity-50',
33
- {
34
- 'spiffy-tw-opacity-0': !isOpen,
35
- 'spiffy-tw-opacity-100': isOpen,
36
- },
37
- );
118
+ const hasFiltersChanged = useHasFilterStateChanged(filters, isOpen);
38
119
 
39
- const sidebarClasses = classNames(
40
- 'spiffy-tw-relative spiffy-tw-ml-auto spiffy-tw-flex spiffy-tw-h-full spiffy-tw-w-full spiffy-tw-max-w-xs spiffy-tw-flex-col spiffy-tw-overflow-y-auto spiffy-tw-bg-[--spiffy-colors-background-light] spiffy-tw-py-4 spiffy-tw-shadow-xl',
41
- );
120
+ const filterWrapperClassName = classNames({
121
+ 'spiffy-tw-flex': true,
122
+ 'spiffy-tw-flex-col': true,
123
+ 'spiffy-tw-gap-[16px]': true,
124
+ 'spiffy-tw-px-[24px]': true,
125
+ 'spiffy-tw-py-[16px]': true,
126
+ 'spiffy-tw-max-h-screen': true,
127
+ 'spiffy-tw-overflow-y-auto': true,
128
+ });
42
129
 
43
130
  return (
44
- <div className={modalClasses}>
45
- <div className={overlayClasses} onClick={() => setIsOpen(false)} />
46
- <div className={sidebarClasses}>
47
- {headerContent}
48
- <div className="spiffy-tw-flex-1 spiffy-tw-px-4 spiffy-tw-py-6">
49
- {filters.map((filter) => (
50
- <div key={filter.filterId} className="spiffy-tw-mb-6">
51
- <Text variant="body2" className="spiffy-tw-font-medium spiffy-tw-mb-3">
52
- {filter.displayName}
53
- </Text>
54
- <ul>
55
- {filter.items.map((item) => (
56
- <SearchFilterItem
57
- key={item.filterItemId}
58
- filterItem={{ ...item, filterId: filter.filterId }}
59
- onSelectFilterItem={selectFilterItem}
60
- radioButtonFillColor={radioButtonFillColor}
61
- radioButtonHoverColor={radioButtonHoverColor}
62
- radioButtonUncheckedBorderColor={radioButtonUncheckedBorderColor}
63
- />
64
- ))}
65
- </ul>
66
- </div>
67
- ))}
68
- </div>
69
- <div className="spiffy-tw-border-t spiffy-tw-border-[--spiffy-colors-border-light] spiffy-tw-px-4 spiffy-tw-py-3">
70
- <ButtonBase
71
- onClick={clearAllFilters}
72
- text="Clear All"
73
- buttonClass="spiffy-tw-w-full spiffy-tw-mb-2"
131
+ <ModalSheet
132
+ isOpen={isOpen}
133
+ closeModal={closeModal}
134
+ headerProps={{
135
+ headerContent: headerContent || (
136
+ <SearchFilterHeader
137
+ closeModal={closeModal}
138
+ productCount={productCount}
139
+ headerClassName={headerClassName ?? ''}
140
+ filterCloseIconVariant={filterCloseIconVariant}
74
141
  />
75
- <ButtonBase
76
- onClick={() => setIsOpen(false)}
77
- text={filterButtonText}
78
- buttonClass="spiffy-tw-w-full"
142
+ ),
143
+ chevronColor: '#000',
144
+ }}
145
+ footerProps={{
146
+ footerContent: footerContent || (
147
+ <SearchFilterFooter
148
+ closeModal={closeModal}
149
+ clearAllFilters={clearAllFilters}
150
+ filterCount={filterCount}
151
+ hasFiltersChanged={hasFiltersChanged}
152
+ applyFiltersUnchangedClasses={applyFiltersUnchangedClasses}
153
+ applyFiltersChangedClasses={applyFiltersChangedClasses}
79
154
  />
80
- </div>
155
+ ),
156
+ }}
157
+ >
158
+ <div className={filterWrapperClassName}>
159
+ {filters
160
+ .filter((filter) => filter.displayName !== '')
161
+ .map((filter) => {
162
+ if (filter.filterId === 'sort') {
163
+ return (
164
+ <SortFilter
165
+ key={filter.filterId}
166
+ filter={filter}
167
+ selectFilterItem={selectFilterItem}
168
+ radioButtonFillColor={radioButtonFillColor}
169
+ radioButtonHoverColor={radioButtonHoverColor}
170
+ radioButtonUncheckedBorderColor={radioButtonUncheckedBorderColor}
171
+ />
172
+ );
173
+ }
174
+ return (
175
+ <Filter key={filter.filterId} filter={filter} selectFilterItem={selectFilterItem} />
176
+ );
177
+ })}
81
178
  </div>
82
- </div>
179
+ </ModalSheet>
83
180
  );
84
181
  };
@@ -0,0 +1,55 @@
1
+ import classNames from 'classnames';
2
+ import { ButtonBase } from '../ButtonBase';
3
+ import { FilterFooterProps } from '@envive-ai/react-hooks/types';
4
+
5
+ export const SearchFilterFooter = ({
6
+ closeModal,
7
+ clearAllFilters,
8
+ filterCount,
9
+ applyFiltersUnchangedClasses,
10
+ applyFiltersChangedClasses,
11
+ hasFiltersChanged,
12
+ }: FilterFooterProps) => {
13
+ const clearAllButtonEnabled = filterCount > 0;
14
+
15
+ const footerWrapperClassName = classNames({
16
+ 'spiffy-tw-flex': true,
17
+ 'spiffy-tw-flex-col': true,
18
+ 'spiffy-tw-items-center': true,
19
+ 'spiffy-tw-justify-between': true,
20
+ 'spiffy-tw-w-full': true,
21
+ 'spiffy-tw-border-t': true,
22
+ 'spiffy-tw-border-t-[--spiffy-colors-border-light]': true,
23
+ 'spiffy-tw-p-[16px]': true,
24
+ 'spiffy-tw-gap-[16px]': true,
25
+ });
26
+ const applyFilterButtonClassName = classNames(
27
+ 'spiffy-tw-flex spiffy-tw-flex-row spiffy-tw-justify-center spiffy-tw-rounded-[8px] spiffy-tw-w-full',
28
+ hasFiltersChanged ? applyFiltersChangedClasses : applyFiltersUnchangedClasses,
29
+ );
30
+ const clearAllButtonClassName = classNames({
31
+ 'spiffy-tw-flex': true,
32
+ 'spiffy-tw-flex-row': true,
33
+ 'spiffy-tw-justify-center': true,
34
+ 'spiffy-tw-w-full': true,
35
+ 'spiffy-tw-text-[--spiffy-colors-text-primary]': clearAllButtonEnabled,
36
+ 'spiffy-tw-text-[--spiffy-colors-text-secondary]': !clearAllButtonEnabled,
37
+ });
38
+
39
+ return (
40
+ <div className={footerWrapperClassName}>
41
+ <ButtonBase
42
+ isDisabled={!hasFiltersChanged}
43
+ buttonClass={applyFilterButtonClassName}
44
+ text="Apply Filters"
45
+ onClick={closeModal}
46
+ />
47
+ <ButtonBase
48
+ isDisabled={!clearAllButtonEnabled}
49
+ buttonClass={clearAllButtonClassName}
50
+ text="Clear All"
51
+ onClick={clearAllFilters}
52
+ />
53
+ </div>
54
+ );
55
+ };
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CloseIconVariant,
2
3
  SearchFilterDatum,
3
4
  SearchFilterItemDatum,
4
5
  SelectFilterItem,
@@ -40,5 +41,5 @@ export type SearchFilterProps = {
40
41
  radioButtonFillColor: string;
41
42
  radioButtonHoverColor?: string;
42
43
  radioButtonUncheckedBorderColor?: string;
43
- // filterCloseIconVariant: CloseIconVariant;
44
+ filterCloseIconVariant: CloseIconVariant;
44
45
  };
@@ -0,0 +1,40 @@
1
+ import { useState, useMemo, useEffect } from 'react';
2
+ import { SearchFilterDatum } from '@envive-ai/react-hooks/types';
3
+
4
+ export const useHasFilterStateChanged = (filters: SearchFilterDatum[], isOpen: boolean) => {
5
+ // Track initial filter states when modal opens
6
+ const [initialFilterStates, setInitialFilterStates] = useState<Record<string, boolean>>({});
7
+
8
+ // Create current filter states map to check for filter changes
9
+ const currentFilterStates = useMemo(() => {
10
+ const states: Record<string, boolean> = {};
11
+ filters.forEach((filter) => {
12
+ filter.items.forEach((item) => {
13
+ states[`${filter.filterId}-${item.filterItemId}`] = item.isSelected;
14
+ });
15
+ });
16
+ return states;
17
+ }, [filters]);
18
+
19
+ // Check if filters have changed by comparing states
20
+ const hasFiltersChanged = useMemo(() => {
21
+ // If no initial states captured yet, no changes
22
+ if (Object.keys(initialFilterStates).length === 0) {
23
+ return false;
24
+ }
25
+
26
+ const changed = Object.keys(currentFilterStates).some(
27
+ (key) => currentFilterStates[key] !== initialFilterStates[key],
28
+ );
29
+
30
+ return changed;
31
+ }, [currentFilterStates, initialFilterStates]);
32
+
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ setInitialFilterStates(currentFilterStates);
36
+ }
37
+ }, [isOpen]);
38
+
39
+ return hasFiltersChanged;
40
+ };
@@ -0,0 +1,15 @@
1
+ import { SearchFilterDatum, SearchFilterItemDatum } from '@envive-ai/react-hooks/types';
2
+
3
+ export const getSelectedFilterItemsCount = (filter: SearchFilterDatum) => {
4
+ return filter.items.filter((item: SearchFilterItemDatum) => item.isSelected).length;
5
+ };
6
+
7
+ // Gets the count of total active filters excluding sort filters
8
+ export const getTotalSelectedFilterItemsCount = (filters: SearchFilterDatum[]) => {
9
+ return filters.reduce((acc: number, filter: SearchFilterDatum) => {
10
+ if (filter.filterId === 'sort') {
11
+ return acc;
12
+ }
13
+ return acc + getSelectedFilterItemsCount(filter);
14
+ }, 0);
15
+ };
@@ -10,7 +10,7 @@ import {
10
10
  SearchResultsGrid,
11
11
  SearchResultsLoadingGrid,
12
12
  } from '../SearchResultsStates';
13
- import { SearchResultsState } from '@envive-ai/react-hooks/hooks';
13
+ import { SearchResultsState } from '@envive-ai/react-hooks/hooks/utils';
14
14
 
15
15
  interface SearchResultsContentProps {
16
16
  searchResultsState: SearchResultsState;
@@ -1,11 +1,11 @@
1
1
  import { useCallback } from 'react';
2
2
 
3
3
  import { searchFilterSidebarVariantClasses } from './searchFilterSidebarVariants';
4
- import SettingsVariant from '@envive-ai/react-icons/SettingsVariant';
5
4
  import { ButtonBase } from '../ButtonBase';
6
5
  import { SearchFilter, SearchFilterHeader } from '../SearchFilter';
7
6
  import { SearchFilterSidebarVariant } from './types';
8
7
  import { SearchFilterDatum, SelectFilterItem } from '@envive-ai/react-hooks/types';
8
+ import SettingsVariant from '@envive-ai/react-icons/src/SettingsVariant.js';
9
9
 
10
10
  export type SearchResultsFilterProps = {
11
11
  productCount: number;
@@ -58,7 +58,7 @@ export const SearchResultsFilter = ({
58
58
  radioButtonFillColor={radioButtonFillColor}
59
59
  radioButtonHoverColor={radioButtonHoverColor}
60
60
  radioButtonUncheckedBorderColor={radioButtonUncheckedBorderColor}
61
- // filterCloseIconVariant={filterCloseIconVariant}
61
+ filterCloseIconVariant={filterCloseIconVariant}
62
62
  headerContent={
63
63
  <SearchFilterHeader
64
64
  closeModal={() => setIsOpen(false)}
@@ -0,0 +1,98 @@
1
+ import classNames from 'classnames';
2
+ import { Text } from 'src/components/Text';
3
+
4
+ export type ToggleButtonProps = {
5
+ isSelected: boolean;
6
+ variant: ToggleButtonVariant;
7
+ onClick: () => void;
8
+ label: string;
9
+ buttonClassName?: string;
10
+ labelClassName?: string;
11
+ secondaryLabel?: string;
12
+ secondaryLabelClassName?: string;
13
+ icon?: React.ReactNode;
14
+ iconClassName?: string;
15
+ };
16
+
17
+ export type ToggleButtonVariant = 'default';
18
+
19
+ type ToggleButtonVariantProps = {
20
+ isSelected: boolean;
21
+ };
22
+ type ToggleButtonClassNameFn = (props: ToggleButtonVariantProps) => string;
23
+
24
+ const toggleButtonVariants: Record<
25
+ ToggleButtonVariant,
26
+ Record<'button' | 'label' | 'secondaryLabel', ToggleButtonClassNameFn>
27
+ > = {
28
+ default: {
29
+ button: ({ isSelected }: ToggleButtonVariantProps): string =>
30
+ classNames({
31
+ 'spiffy-tw-bg-[--spiffy-colors-background-dark]': isSelected,
32
+ 'spiffy-tw-border-[--spiffy-colors-background-dark]': isSelected,
33
+ 'spiffy-tw-text-[--spiffy-colors-text-light]': isSelected,
34
+ 'hover:spiffy-tw-bg-[--spiffy-colors-background-secondary-dark]': true,
35
+ 'hover:spiffy-tw-text-[--spiffy-colors-text-primary]': isSelected,
36
+ 'spiffy-tw-bg-[--spiffy-colors-background-light]': !isSelected,
37
+ 'spiffy-tw-border-[--spiffy-colors-border-light]': !isSelected,
38
+ 'spiffy-tw-text-[--spiffy-colors-text-primary]': !isSelected,
39
+ }),
40
+ label: () => '',
41
+ secondaryLabel: ({ isSelected }: ToggleButtonVariantProps) =>
42
+ classNames({
43
+ 'spiffy-tw-text-[--spiffy-colors-text-secondary]': !isSelected,
44
+ 'hover:spiffy-tw-text-[--spiffy-colors-text-secondary]': !isSelected,
45
+ }),
46
+ },
47
+ };
48
+
49
+ export const ToggleButton = ({
50
+ isSelected,
51
+ onClick,
52
+ label,
53
+ buttonClassName,
54
+ labelClassName,
55
+ secondaryLabel,
56
+ secondaryLabelClassName,
57
+ icon,
58
+ iconClassName,
59
+ variant,
60
+ }: ToggleButtonProps) => {
61
+ const buttonClassNames = classNames(
62
+ {
63
+ 'spiffy-tw-flex': true,
64
+ 'spiffy-tw-items-center': true,
65
+ 'spiffy-tw-gap-[4px]': true,
66
+ 'spiffy-tw-cursor-pointer': true,
67
+ 'spiffy-tw-py-[8px]': true,
68
+ 'spiffy-tw-px-[12px]': true,
69
+ 'spiffy-tw-rounded-[200px]': true,
70
+ 'spiffy-tw-border': true,
71
+ },
72
+ toggleButtonVariants?.[variant]?.button({ isSelected }),
73
+ buttonClassName,
74
+ );
75
+ const labelClassNames = classNames(
76
+ {},
77
+ toggleButtonVariants?.[variant]?.label({ isSelected }),
78
+ labelClassName,
79
+ );
80
+ const secondaryLabelClassNames = classNames(
81
+ {},
82
+ toggleButtonVariants?.[variant]?.secondaryLabel({ isSelected }),
83
+ secondaryLabelClassName,
84
+ );
85
+ return (
86
+ <button className={buttonClassNames} onClick={onClick} type="button">
87
+ {icon && <div className={iconClassName}>{icon}</div>}
88
+ <Text variant="body3" className={labelClassNames}>
89
+ {label}
90
+ </Text>
91
+ {secondaryLabel && (
92
+ <Text variant="body4" className={secondaryLabelClassNames}>
93
+ {secondaryLabel}
94
+ </Text>
95
+ )}
96
+ </button>
97
+ );
98
+ };
@@ -0,0 +1 @@
1
+ export * from './ToggleButton';
@@ -1,137 +0,0 @@
1
- const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const require_Text = require('./Text-MQjxqgZZ.cjs');
3
- const require_ButtonBase = require('./ButtonBase-DGpQBeLR.cjs');
4
- let classnames = require("classnames");
5
- classnames = require_chunk.__toESM(classnames);
6
- let react_jsx_runtime = require("react/jsx-runtime");
7
- react_jsx_runtime = require_chunk.__toESM(react_jsx_runtime);
8
-
9
- //#region src/components/SearchFilter/SearchFilterItem.tsx
10
- const SearchFilterItem = ({ filterItem, onSelectFilterItem, radioButtonFillColor, radioButtonHoverColor, radioButtonUncheckedBorderColor }) => {
11
- const { displayName, isSelected } = filterItem;
12
- const radioClasses = (0, classnames.default)("spiffy-tw-w-4 spiffy-tw-h-4 spiffy-tw-rounded-full spiffy-tw-border-2", "spiffy-tw-flex spiffy-tw-items-center spiffy-tw-justify-center", radioButtonUncheckedBorderColor, radioButtonHoverColor, { [radioButtonFillColor]: isSelected });
13
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
14
- className: "spiffy-tw-flex spiffy-tw-items-center spiffy-tw-justify-between spiffy-tw-py-2 spiffy-tw-cursor-pointer",
15
- onClick: () => onSelectFilterItem(filterItem),
16
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
17
- variant: "body3",
18
- children: displayName
19
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
20
- className: radioClasses,
21
- children: isSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, classnames.default)("spiffy-tw-w-2 spiffy-tw-h-2 spiffy-tw-rounded-full", {
22
- "spiffy-tw-bg-white": radioButtonFillColor.includes("spiffy-tw-bg-"),
23
- "spiffy-tw-bg-black": radioButtonFillColor.includes("spiffy-tw-bg-white")
24
- }) })
25
- })]
26
- });
27
- };
28
-
29
- //#endregion
30
- //#region src/components/SearchFilter/SearchFilter.tsx
31
- const SearchFilter = ({ isOpen, setIsOpen, filters, selectFilterItem, clearAllFilters, filterButtonText, radioButtonFillColor, radioButtonHoverColor, radioButtonUncheckedBorderColor, headerContent }) => {
32
- const modalClasses = (0, classnames.default)("spiffy-tw-fixed spiffy-tw-inset-0 spiffy-tw-z-50 spiffy-tw-flex spiffy-tw-transform spiffy-tw-transition-all", {
33
- "spiffy-tw-translate-x-full": !isOpen,
34
- "spiffy-tw-translate-x-0": isOpen
35
- });
36
- const overlayClasses = (0, classnames.default)("spiffy-tw-absolute spiffy-tw-inset-0 spiffy-tw-bg-black spiffy-tw-bg-opacity-50", {
37
- "spiffy-tw-opacity-0": !isOpen,
38
- "spiffy-tw-opacity-100": isOpen
39
- });
40
- const sidebarClasses = (0, classnames.default)("spiffy-tw-relative spiffy-tw-ml-auto spiffy-tw-flex spiffy-tw-h-full spiffy-tw-w-full spiffy-tw-max-w-xs spiffy-tw-flex-col spiffy-tw-overflow-y-auto spiffy-tw-bg-[--spiffy-colors-background-light] spiffy-tw-py-4 spiffy-tw-shadow-xl");
41
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
42
- className: modalClasses,
43
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
44
- className: overlayClasses,
45
- onClick: () => setIsOpen(false)
46
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47
- className: sidebarClasses,
48
- children: [
49
- headerContent,
50
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
51
- className: "spiffy-tw-flex-1 spiffy-tw-px-4 spiffy-tw-py-6",
52
- children: filters.map((filter) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53
- className: "spiffy-tw-mb-6",
54
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Text.Text, {
55
- variant: "body2",
56
- className: "spiffy-tw-font-medium spiffy-tw-mb-3",
57
- children: filter.displayName
58
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: filter.items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchFilterItem, {
59
- filterItem: {
60
- ...item,
61
- filterId: filter.filterId
62
- },
63
- onSelectFilterItem: selectFilterItem,
64
- radioButtonFillColor,
65
- radioButtonHoverColor,
66
- radioButtonUncheckedBorderColor
67
- }, item.filterItemId)) })]
68
- }, filter.filterId))
69
- }),
70
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
71
- className: "spiffy-tw-border-t spiffy-tw-border-[--spiffy-colors-border-light] spiffy-tw-px-4 spiffy-tw-py-3",
72
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ButtonBase.ButtonBase, {
73
- onClick: clearAllFilters,
74
- text: "Clear All",
75
- buttonClass: "spiffy-tw-w-full spiffy-tw-mb-2"
76
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ButtonBase.ButtonBase, {
77
- onClick: () => setIsOpen(false),
78
- text: filterButtonText,
79
- buttonClass: "spiffy-tw-w-full"
80
- })]
81
- })
82
- ]
83
- })]
84
- });
85
- };
86
-
87
- //#endregion
88
- //#region src/components/SearchFilter/SearchFilterHeader.tsx
89
- const SearchFilterHeader = ({ closeModal, productCount, headerClassName, filterCloseIconVariant }) => {
90
- const closeIconColor = filterCloseIconVariant === "dark" ? "var(--spiffy-colors-text-primary)" : "var(--spiffy-colors-text-light)";
91
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
92
- className: (0, classnames.default)("spiffy-tw-flex spiffy-tw-items-center spiffy-tw-justify-between spiffy-tw-px-4 spiffy-tw-py-3", headerClassName),
93
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_Text.Text, {
94
- variant: "body2",
95
- className: "spiffy-tw-font-medium",
96
- children: [
97
- "Filters (",
98
- productCount,
99
- ")"
100
- ]
101
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
102
- onClick: closeModal,
103
- className: "spiffy-tw-p-2",
104
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
105
- xmlns: "http://www.w3.org/2000/svg",
106
- width: "14",
107
- height: "14",
108
- viewBox: "0 0 14 14",
109
- fill: "none",
110
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
111
- d: "M13.7071 1.70711C14.0976 1.31658 14.0976 0.683417 13.7071 0.292893C13.3166 -0.0976311 12.6834 -0.0976311 12.2929 0.292893L7 5.58579L1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L5.58579 7L0.292893 12.2929C-0.0976311 12.6834 -0.0976311 13.3166 0.292893 13.7071C0.683417 14.0976 1.31658 14.0976 1.70711 13.7071L7 8.41421L12.2929 13.7071C12.6834 14.0976 13.3166 14.0976 13.7071 13.7071C14.0976 13.3166 14.0976 12.6834 13.7071 12.2929L8.41421 7L13.7071 1.70711Z",
112
- fill: closeIconColor
113
- })
114
- })
115
- })]
116
- });
117
- };
118
-
119
- //#endregion
120
- Object.defineProperty(exports, 'SearchFilter', {
121
- enumerable: true,
122
- get: function () {
123
- return SearchFilter;
124
- }
125
- });
126
- Object.defineProperty(exports, 'SearchFilterHeader', {
127
- enumerable: true,
128
- get: function () {
129
- return SearchFilterHeader;
130
- }
131
- });
132
- Object.defineProperty(exports, 'SearchFilterItem', {
133
- enumerable: true,
134
- get: function () {
135
- return SearchFilterItem;
136
- }
137
- });