@envive-ai/react-toolkit 0.1.0 → 0.1.1

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 (151) hide show
  1. package/LICENSE +2 -0
  2. package/package.json +10 -7
  3. package/src/atoms/search/types.ts +5 -0
  4. package/src/components/common/ButtonBase/ButtonBase.tsx +70 -0
  5. package/src/components/common/ButtonBase/types.ts +27 -0
  6. package/src/components/common/Headline/Headline.tsx +81 -0
  7. package/src/components/common/ImageWithFallback/ImageWithFallback.tsx +66 -0
  8. package/src/components/common/ProductCard/ProductCard.tsx +305 -0
  9. package/src/components/common/ProductCard/ProductCardSkeleton.tsx +83 -0
  10. package/src/components/common/ProductCard/productCardVariants.ts +63 -0
  11. package/src/components/common/ProductCard/types.ts +49 -0
  12. package/src/components/common/ProductGrid/ProductGrid.tsx +73 -0
  13. package/src/components/common/ProductGrid/productGridVariants.ts +31 -0
  14. package/src/components/common/SparkleAnimation/SparkleAnimation.tsx +105 -0
  15. package/src/components/common/SparkleAnimation/types.ts +6 -0
  16. package/src/components/common/Spinner/Spinner.tsx +30 -0
  17. package/src/components/common/SuggestionButton/SuggestionButton.tsx +258 -0
  18. package/src/components/common/SuggestionButton/types.ts +14 -0
  19. package/src/components/common/Text/Text.tsx +58 -0
  20. package/src/components/common/Text/textVariantClasses.ts +106 -0
  21. package/src/components/common/Text/types.ts +23 -0
  22. package/src/components/common/TextInput/TextInput.tsx +34 -0
  23. package/src/components/models/colorsConfig.ts +28 -0
  24. package/src/components/search/FilterScrollbar/AppliedFiltersScrollbar.tsx +70 -0
  25. package/src/components/search/FilterScrollbar/DynamicFiltersScrollbar.tsx +52 -0
  26. package/src/components/search/SearchFilter/SearchFilter.tsx +84 -0
  27. package/src/components/search/SearchFilter/SearchFilterHeader.tsx +42 -0
  28. package/src/components/search/SearchFilter/SearchFilterItem.tsx +42 -0
  29. package/src/components/search/SearchFilter/types.ts +48 -0
  30. package/src/components/search/SearchInput/SearchInput.tsx +135 -0
  31. package/src/components/search/SearchInput/searchInputVariants.ts +27 -0
  32. package/src/components/search/SearchInputAutocomplete/SearchAutocomplete.tsx +62 -0
  33. package/src/components/search/SearchInputForm/SearchInputForm.tsx +66 -0
  34. package/src/components/search/SearchResultsFilterSidebar/SearchResultsFilter.tsx +82 -0
  35. package/src/components/search/SearchResultsFilterSidebar/searchFilterSidebarVariants.ts +45 -0
  36. package/{dist/packages/components/src/components/search/SearchResultsFilterSidebar/types.d.ts → src/components/search/SearchResultsFilterSidebar/types.ts} +1 -1
  37. package/src/components/search/SearchResultsStates/NoSearchResultsFound.tsx +41 -0
  38. package/src/components/search/SearchResultsStates/SearchResultsGrid.tsx +105 -0
  39. package/src/components/search/SearchResultsStates/SearchResultsLoadingGrid.tsx +50 -0
  40. package/src/components/search/types.ts +5 -0
  41. package/{dist/packages/components/src/components/test/types.d.ts → src/components/test/types.ts} +1 -1
  42. package/src/config/chatElementDisplayLocation.ts +22 -0
  43. package/{dist/packages/components/src/index.js → src/index.ts} +10 -0
  44. package/src/logging/logger.ts +21 -0
  45. package/src/types/external.ts +24 -0
  46. package/{dist/packages/components/src/util/camelCase.d.ts → src/util/camelCase.ts} +33 -11
  47. package/src/util/camelCasedPropertiesDeep.ts +81 -0
  48. package/src/util/formatPrice.ts +14 -0
  49. package/src/util/internal.ts +95 -0
  50. package/{dist/packages/components/src/util/primitive.d.ts → src/util/primitive.ts} +2 -0
  51. package/src/util/splitWords.ts +72 -0
  52. package/{dist/packages/components/src/util/trim.d.ts → src/util/trim.ts} +5 -1
  53. package/dist/packages/components/src/atoms/search/types.d.ts +0 -5
  54. package/dist/packages/components/src/atoms/search/types.js +0 -1
  55. package/dist/packages/components/src/components/common/ButtonBase/ButtonBase.d.ts +0 -2
  56. package/dist/packages/components/src/components/common/ButtonBase/ButtonBase.js +0 -41
  57. package/dist/packages/components/src/components/common/ButtonBase/types.d.ts +0 -24
  58. package/dist/packages/components/src/components/common/ButtonBase/types.js +0 -6
  59. package/dist/packages/components/src/components/common/Headline/Headline.d.ts +0 -10
  60. package/dist/packages/components/src/components/common/Headline/Headline.js +0 -34
  61. package/dist/packages/components/src/components/common/ImageWithFallback/ImageWithFallback.d.ts +0 -12
  62. package/dist/packages/components/src/components/common/ImageWithFallback/ImageWithFallback.js +0 -26
  63. package/dist/packages/components/src/components/common/ProductCard/ProductCard.d.ts +0 -39
  64. package/dist/packages/components/src/components/common/ProductCard/ProductCard.js +0 -83
  65. package/dist/packages/components/src/components/common/ProductCard/ProductCardSkeleton.d.ts +0 -9
  66. package/dist/packages/components/src/components/common/ProductCard/ProductCardSkeleton.js +0 -15
  67. package/dist/packages/components/src/components/common/ProductCard/productCardVariants.d.ts +0 -8
  68. package/dist/packages/components/src/components/common/ProductCard/productCardVariants.js +0 -50
  69. package/dist/packages/components/src/components/common/ProductCard/types.d.ts +0 -39
  70. package/dist/packages/components/src/components/common/ProductCard/types.js +0 -10
  71. package/dist/packages/components/src/components/common/ProductGrid/ProductGrid.d.ts +0 -14
  72. package/dist/packages/components/src/components/common/ProductGrid/ProductGrid.js +0 -13
  73. package/dist/packages/components/src/components/common/ProductGrid/productGridVariants.d.ts +0 -10
  74. package/dist/packages/components/src/components/common/ProductGrid/productGridVariants.js +0 -16
  75. package/dist/packages/components/src/components/common/SparkleAnimation/SparkleAnimation.d.ts +0 -7
  76. package/dist/packages/components/src/components/common/SparkleAnimation/SparkleAnimation.js +0 -40
  77. package/dist/packages/components/src/components/common/SparkleAnimation/types.d.ts +0 -6
  78. package/dist/packages/components/src/components/common/SparkleAnimation/types.js +0 -7
  79. package/dist/packages/components/src/components/common/Spinner/Spinner.d.ts +0 -5
  80. package/dist/packages/components/src/components/common/Spinner/Spinner.js +0 -16
  81. package/dist/packages/components/src/components/common/SuggestionButton/SuggestionButton.d.ts +0 -18
  82. package/dist/packages/components/src/components/common/SuggestionButton/SuggestionButton.js +0 -210
  83. package/dist/packages/components/src/components/common/SuggestionButton/types.d.ts +0 -1
  84. package/dist/packages/components/src/components/common/SuggestionButton/types.js +0 -1
  85. package/dist/packages/components/src/components/common/Text/Text.d.ts +0 -12
  86. package/dist/packages/components/src/components/common/Text/Text.js +0 -26
  87. package/dist/packages/components/src/components/common/Text/textVariantClasses.d.ts +0 -171
  88. package/dist/packages/components/src/components/common/Text/textVariantClasses.js +0 -103
  89. package/dist/packages/components/src/components/common/Text/types.d.ts +0 -16
  90. package/dist/packages/components/src/components/common/Text/types.js +0 -6
  91. package/dist/packages/components/src/components/common/TextInput/TextInput.d.ts +0 -8
  92. package/dist/packages/components/src/components/common/TextInput/TextInput.js +0 -25
  93. package/dist/packages/components/src/components/models/colorsConfig.d.ts +0 -26
  94. package/dist/packages/components/src/components/models/colorsConfig.js +0 -23
  95. package/dist/packages/components/src/components/search/FilterScrollbar/AppliedFiltersScrollbar.d.ts +0 -11
  96. package/dist/packages/components/src/components/search/FilterScrollbar/AppliedFiltersScrollbar.js +0 -18
  97. package/dist/packages/components/src/components/search/FilterScrollbar/DynamicFiltersScrollbar.d.ts +0 -15
  98. package/dist/packages/components/src/components/search/FilterScrollbar/DynamicFiltersScrollbar.js +0 -15
  99. package/dist/packages/components/src/components/search/SearchFilter/SearchFilter.d.ts +0 -2
  100. package/dist/packages/components/src/components/search/SearchFilter/SearchFilter.js +0 -24
  101. package/dist/packages/components/src/components/search/SearchFilter/SearchFilterHeader.d.ts +0 -2
  102. package/dist/packages/components/src/components/search/SearchFilter/SearchFilterHeader.js +0 -9
  103. package/dist/packages/components/src/components/search/SearchFilter/SearchFilterItem.d.ts +0 -2
  104. package/dist/packages/components/src/components/search/SearchFilter/SearchFilterItem.js +0 -13
  105. package/dist/packages/components/src/components/search/SearchFilter/types.d.ts +0 -42
  106. package/dist/packages/components/src/components/search/SearchFilter/types.js +0 -1
  107. package/dist/packages/components/src/components/search/SearchInput/SearchInput.d.ts +0 -16
  108. package/dist/packages/components/src/components/search/SearchInput/SearchInput.js +0 -38
  109. package/dist/packages/components/src/components/search/SearchInput/searchInputVariants.d.ts +0 -13
  110. package/dist/packages/components/src/components/search/SearchInput/searchInputVariants.js +0 -12
  111. package/dist/packages/components/src/components/search/SearchInputAutocomplete/SearchAutocomplete.d.ts +0 -10
  112. package/dist/packages/components/src/components/search/SearchInputAutocomplete/SearchAutocomplete.js +0 -14
  113. package/dist/packages/components/src/components/search/SearchInputForm/SearchInputForm.d.ts +0 -16
  114. package/dist/packages/components/src/components/search/SearchInputForm/SearchInputForm.js +0 -13
  115. package/dist/packages/components/src/components/search/SearchInputForm/types.js +0 -1
  116. package/dist/packages/components/src/components/search/SearchResultsFilterSidebar/SearchResultsFilter.d.ts +0 -14
  117. package/dist/packages/components/src/components/search/SearchResultsFilterSidebar/SearchResultsFilter.js +0 -15
  118. package/dist/packages/components/src/components/search/SearchResultsFilterSidebar/searchFilterSidebarVariants.d.ts +0 -16
  119. package/dist/packages/components/src/components/search/SearchResultsFilterSidebar/searchFilterSidebarVariants.js +0 -29
  120. package/dist/packages/components/src/components/search/SearchResultsFilterSidebar/types.js +0 -1
  121. package/dist/packages/components/src/components/search/SearchResultsStates/NoSearchResultsFound.d.ts +0 -8
  122. package/dist/packages/components/src/components/search/SearchResultsStates/NoSearchResultsFound.js +0 -10
  123. package/dist/packages/components/src/components/search/SearchResultsStates/SearchResultsGrid.d.ts +0 -24
  124. package/dist/packages/components/src/components/search/SearchResultsStates/SearchResultsGrid.js +0 -19
  125. package/dist/packages/components/src/components/search/SearchResultsStates/SearchResultsLoadingGrid.d.ts +0 -6
  126. package/dist/packages/components/src/components/search/SearchResultsStates/SearchResultsLoadingGrid.js +0 -10
  127. package/dist/packages/components/src/components/search/types.d.ts +0 -5
  128. package/dist/packages/components/src/components/search/types.js +0 -6
  129. package/dist/packages/components/src/components/test/types.js +0 -1
  130. package/dist/packages/components/src/config/chatElementDisplayLocation.d.ts +0 -21
  131. package/dist/packages/components/src/config/chatElementDisplayLocation.js +0 -23
  132. package/dist/packages/components/src/index.d.ts +0 -45
  133. package/dist/packages/components/src/logging/logger.d.ts +0 -7
  134. package/dist/packages/components/src/logging/logger.js +0 -16
  135. package/dist/packages/components/src/types/external.d.ts +0 -21
  136. package/dist/packages/components/src/types/external.js +0 -5
  137. package/dist/packages/components/src/util/camelCase.js +0 -2
  138. package/dist/packages/components/src/util/camelCasedPropertiesDeep.d.ts +0 -53
  139. package/dist/packages/components/src/util/camelCasedPropertiesDeep.js +0 -1
  140. package/dist/packages/components/src/util/formatPrice.d.ts +0 -1
  141. package/dist/packages/components/src/util/formatPrice.js +0 -11
  142. package/dist/packages/components/src/util/internal.d.ts +0 -27
  143. package/dist/packages/components/src/util/internal.js +0 -4
  144. package/dist/packages/components/src/util/primitive.js +0 -2
  145. package/dist/packages/components/src/util/splitWords.d.ts +0 -55
  146. package/dist/packages/components/src/util/splitWords.js +0 -2
  147. package/dist/packages/components/src/util/trim.js +0 -2
  148. package/dist/packages/components/src/util/unknownArray.js +0 -1
  149. package/dist/tsconfig.tsbuildinfo +0 -1
  150. /package/{dist/packages/components/src/components/search/SearchInputForm/types.d.ts → src/components/search/SearchInputForm/types.ts} +0 -0
  151. /package/{dist/packages/components/src/util/unknownArray.d.ts → src/util/unknownArray.ts} +0 -0
@@ -0,0 +1,135 @@
1
+ import React, { useRef, useImperativeHandle } from 'react';
2
+ import classNames from 'classnames';
3
+ import MagnifyingGlassStarVariant from '@envive-ai/react-icons/src/MagnifyingGlassStarVariant';
4
+ import CloseIcon from '@envive-ai/react-icons/src/IconClose';
5
+ import { TextInput } from 'src/components/common/TextInput/TextInput';
6
+ import { searchInputVariantClasses } from './searchInputVariants';
7
+ import type { SearchInputVariant } from '../SearchInputForm/types';
8
+
9
+ export interface SearchInputProps {
10
+ searchInputVariant?: SearchInputVariant;
11
+ value: string;
12
+ placeholder?: string;
13
+ suggestions?: string[];
14
+ className?: string;
15
+ dataTestId?: string;
16
+ ariaActivedescendant?: string;
17
+ onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
18
+ onChange: (newValue: string) => void;
19
+ onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
20
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
21
+ }
22
+
23
+ export const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
24
+ (
25
+ {
26
+ searchInputVariant = 'standard',
27
+ value,
28
+ placeholder,
29
+ suggestions,
30
+ className,
31
+ dataTestId,
32
+ ariaActivedescendant,
33
+ onKeyDown,
34
+ onChange,
35
+ onFocus,
36
+ onBlur,
37
+ },
38
+ ref,
39
+ ) => {
40
+ const localInputRef = useRef<HTMLInputElement>(null);
41
+
42
+ const {
43
+ searchInputClasses: inputClassName,
44
+ searchInputIconColor: iconColor,
45
+ searchInputBorderColor: borderColor,
46
+ searchInputCloseIconClasses: closeIconClasses,
47
+ searchInputActiveStarClasses,
48
+ placeholderVariant,
49
+ } = searchInputVariantClasses[searchInputVariant];
50
+
51
+ // Expose the local ref through the forwarded ref
52
+ useImperativeHandle(ref, () => localInputRef.current!, []);
53
+ const magnifyingGlassClassesBase = [
54
+ 'spiffy-tw-w-[24px]',
55
+ 'spiffy-tw-h-[32px]',
56
+ 'spiffy-tw-stroke-width-1',
57
+ 'sm:spiffy-tw-w-[32px]',
58
+ ];
59
+
60
+ const magnifyingGlassClassesInactive = classNames(magnifyingGlassClassesBase);
61
+
62
+ const magnifyingGlassClassesActive = classNames([
63
+ ...magnifyingGlassClassesBase,
64
+ searchInputActiveStarClasses,
65
+ ]);
66
+
67
+ const containerClasses = classNames(
68
+ 'spiffy-global-search-input-container',
69
+ 'spiffy-tw-relative spiffy-tw-flex spiffy-tw-items-center',
70
+ className,
71
+ );
72
+
73
+ const inputClasses = classNames(
74
+ 'spiffy-global-search-input',
75
+ 'spiffy-tw-flex-grow spiffy-tw-p-2 spiffy-tw-px-[42px] sm:spiffy-tw-p-3 sm:spiffy-tw-px-[48px]',
76
+ 'spiffy-tw-border',
77
+ 'spiffy-tw-text-base',
78
+ inputClassName,
79
+ );
80
+ const closeButtonIconClassName = classNames([
81
+ 'spiffy-tw-w-[16px]',
82
+ 'spiffy-tw-h-[16px]',
83
+ 'sm:spiffy-tw-w-[24px]',
84
+ 'sm:spiffy-tw-h-[24px]',
85
+ 'spiffy-tw-cursor-pointer',
86
+ closeIconClasses,
87
+ ]);
88
+
89
+ return (
90
+ <div className={containerClasses} data-testid={dataTestId}>
91
+ <div className="spiffy-tw-absolute spiffy-tw-left-3 spiffy-tw-top-1/2 spiffy-tw--translate-y-1/2">
92
+ <MagnifyingGlassStarVariant
93
+ className={
94
+ value.length > 0 ? magnifyingGlassClassesActive : magnifyingGlassClassesInactive
95
+ }
96
+ stroke={iconColor}
97
+ strokeWidth="1px"
98
+ />
99
+ </div>
100
+ <TextInput
101
+ value={value}
102
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange(event.target.value)}
103
+ onFocus={onFocus}
104
+ onBlur={onBlur}
105
+ onKeyDown={onKeyDown}
106
+ placeholder={placeholder}
107
+ className={inputClasses}
108
+ placeholderVariant={placeholderVariant}
109
+ aria-label="Search"
110
+ aria-autocomplete="list"
111
+ aria-expanded={!!suggestions && suggestions.length > 0}
112
+ aria-controls="autocomplete-results"
113
+ aria-activedescendant={ariaActivedescendant}
114
+ borderRadius="xl"
115
+ borderColorClass={borderColor}
116
+ ref={localInputRef}
117
+ />
118
+
119
+ {value && (
120
+ <button
121
+ onClick={() => {
122
+ onChange('');
123
+ localInputRef.current?.focus();
124
+ }}
125
+ className="spiffy-tw-absolute spiffy-tw-right-5 spiffy-tw-top-1/2 spiffy-tw--translate-y-1/2 spiffy-tw-text-neutral-500 hover:spiffy-tw-text-neutral-700"
126
+ aria-label="Clear search input"
127
+ type="button"
128
+ >
129
+ <CloseIcon className={closeButtonIconClassName} />
130
+ </button>
131
+ )}
132
+ </div>
133
+ );
134
+ },
135
+ );
@@ -0,0 +1,27 @@
1
+ import type { TextStyleVariantMap } from 'src/components/common/Text/textVariantClasses';
2
+ import type { SearchInputVariant } from '../SearchInputForm/types';
3
+ import { ColorNames, colorVar } from 'src/components/models/colorsConfig';
4
+
5
+ interface SearchInputVariantClasses {
6
+ searchInputIconColor: string;
7
+ searchInputBorderColor: string;
8
+ searchInputCloseIconClasses: string;
9
+ searchInputActiveStarClasses: string;
10
+ autocompleteIconColor: string;
11
+ searchInputClasses: string;
12
+ placeholderVariant: keyof typeof TextStyleVariantMap;
13
+ }
14
+
15
+ export const searchInputVariantClasses: Record<SearchInputVariant, SearchInputVariantClasses> = {
16
+ standard: {
17
+ searchInputIconColor: 'transparent', // colorVar(ColorNames.TextSecondary),
18
+ searchInputBorderColor: 'spiffy-tw-border-[--spiffy-colors-border-medium]',
19
+ searchInputCloseIconClasses:
20
+ '[&>circle]:spiffy-tw-fill-[--spiffy-colors-text-secondary] [&>path]:spiffy-tw-fill-[#fff]',
21
+ searchInputActiveStarClasses: `first:[&>path]:spiffy-tw-stroke-[--spiffy-colors-accent-primary] first:[&>path]:spiffy-tw-fill-[--spiffy-colors-accent-primary]`,
22
+ autocompleteIconColor: colorVar(ColorNames.BackgroundPrimary),
23
+ searchInputClasses:
24
+ 'spiffy-tw-text-[--spiffy-colors-text-primary] placeholder:spiffy-tw-text-[--spiffy-colors-text-secondary] spiffy-tw-text-[16px] spiffy-tw-font-normal spiffy-tw-leading-[148%] placeholder:spiffy-tw-font-medium placeholder:spiffy-tw-leading-[124%]',
25
+ placeholderVariant: 'h2',
26
+ },
27
+ };
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import classNames from 'classnames';
4
+ import OutlinedStar from '@envive-ai/react-icons/src/OutlinedStar';
5
+
6
+ interface GlobalSearchAutocompleteProps {
7
+ id: string;
8
+ results: string[];
9
+ focusedIndex: number;
10
+ onSuggestionSelect: (suggestion: string, rankPosition: number) => void;
11
+ iconColor?: string;
12
+ }
13
+
14
+ export const SearchAutocomplete: React.FC<GlobalSearchAutocompleteProps> = ({
15
+ id,
16
+ results,
17
+ focusedIndex,
18
+ onSuggestionSelect,
19
+ iconColor,
20
+ }) => {
21
+ if (results.length === 0) {
22
+ return null;
23
+ }
24
+
25
+ return (
26
+ <motion.div
27
+ className="spiffy-tw-h-full"
28
+ initial={{ opacity: 0, y: -10 }}
29
+ animate={{ opacity: 1, y: 0 }}
30
+ exit={{ opacity: 0, y: -10 }}
31
+ transition={{ duration: 0.2 }}
32
+ >
33
+ <ul id={id} role="listbox" className="spiffy-tw-mt-4 spiffy-tw-space-y-2">
34
+ {results.map((result, index) => (
35
+ <li
36
+ key={index}
37
+ id={`option-${index}`}
38
+ role="option"
39
+ aria-selected={index === focusedIndex}
40
+ className={classNames(
41
+ 'spiffy-tw-flex spiffy-tw-items-center spiffy-tw-cursor-pointer spiffy-tw-py-1',
42
+ { 'spiffy-tw-bg-neutral-200': index === focusedIndex },
43
+ 'hover:spiffy-tw-bg-neutral-100',
44
+ )}
45
+ onClick={() => onSuggestionSelect(result, index)}
46
+ onKeyDown={(event) => {
47
+ if (event.key === 'Enter' || event.keyCode === 13) {
48
+ onSuggestionSelect(result, index);
49
+ }
50
+ }}
51
+ >
52
+ <OutlinedStar
53
+ className="spiffy-tw-w-[16px] spiffy-tw-h-[16px] spiffy-tw-mr-2"
54
+ fill={iconColor}
55
+ />
56
+ <span className="spiffy-tw-font-bold">{result}</span>
57
+ </li>
58
+ ))}
59
+ </ul>
60
+ </motion.div>
61
+ );
62
+ };
@@ -0,0 +1,66 @@
1
+ import type { SearchInputVariant } from './types';
2
+ import { SearchAutocomplete } from '../SearchInputAutocomplete/SearchAutocomplete';
3
+ import { searchInputVariantClasses } from '../SearchInput/searchInputVariants';
4
+ import { SearchInput } from '../SearchInput/SearchInput';
5
+
6
+ interface SearchInputFormProps {
7
+ searchInputVariant: SearchInputVariant;
8
+ searchText: string;
9
+ autocompleteResults: string[];
10
+ searchBoxPlaceholder: string;
11
+ focusedOptionId: string | undefined;
12
+ isDirty: boolean;
13
+ focusedIndex: number;
14
+ onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;
15
+ onAutocompleteSelect: (suggestion: string) => void;
16
+ onSearchInputChange: (value: string) => void;
17
+ onSearchSubmit: () => void;
18
+ }
19
+
20
+ export const SearchInputForm = ({
21
+ searchInputVariant,
22
+ searchText,
23
+ autocompleteResults,
24
+ searchBoxPlaceholder,
25
+ focusedOptionId,
26
+ isDirty,
27
+ focusedIndex,
28
+ onKeyDown,
29
+ onAutocompleteSelect,
30
+ onSearchInputChange,
31
+ onSearchSubmit,
32
+ }: SearchInputFormProps) => {
33
+ const { searchInputIconColor } = searchInputVariantClasses[searchInputVariant];
34
+
35
+ return (
36
+ <>
37
+ <form
38
+ onSubmit={(e) => {
39
+ e.preventDefault();
40
+ onSearchSubmit();
41
+ }}
42
+ className="spiffy-tw-grow"
43
+ >
44
+ <SearchInput
45
+ value={searchText}
46
+ placeholder={searchBoxPlaceholder}
47
+ suggestions={autocompleteResults}
48
+ ariaActivedescendant={focusedOptionId}
49
+ searchInputVariant={searchInputVariant}
50
+ // dataTestId={SEARCH_INPUT_TESTID}
51
+ onKeyDown={onKeyDown}
52
+ onChange={onSearchInputChange}
53
+ />
54
+ </form>
55
+ {isDirty && (
56
+ <SearchAutocomplete
57
+ id="autocomplete-results"
58
+ results={autocompleteResults}
59
+ focusedIndex={focusedIndex}
60
+ iconColor={searchInputIconColor}
61
+ onSuggestionSelect={onAutocompleteSelect}
62
+ />
63
+ )}
64
+ </>
65
+ );
66
+ };
@@ -0,0 +1,82 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { SearchFilter } from '../SearchFilter/SearchFilter';
4
+ import { SearchFilterHeader } from '../SearchFilter/SearchFilterHeader';
5
+ import type { SearchFilter as SearchFilterType, SelectFilterItem } from '../SearchFilter/types';
6
+ import type { SearchFilterSidebarVariant } from './types';
7
+ import { searchFilterSidebarVariantClasses } from './searchFilterSidebarVariants';
8
+ import { ButtonBase } from 'src/components/common/ButtonBase/ButtonBase';
9
+
10
+ export type SearchResultsFilterProps = {
11
+ productCount: number;
12
+ isOpen: boolean;
13
+ setIsOpen: (isOpen: boolean) => void;
14
+ searchFilters: SearchFilterType[];
15
+ searchFilterSidebarVariant: SearchFilterSidebarVariant;
16
+ searchText: string;
17
+ onSelectFilterItem: SelectFilterItem;
18
+ onClearAllFilters: () => void;
19
+ filterButtonText: string;
20
+ };
21
+
22
+ export const SearchResultsFilter = ({
23
+ productCount,
24
+ isOpen,
25
+ setIsOpen,
26
+ searchFilters,
27
+ searchFilterSidebarVariant,
28
+ onSelectFilterItem,
29
+ onClearAllFilters,
30
+ filterButtonText,
31
+ }: SearchResultsFilterProps) => {
32
+ const {
33
+ applyFiltersUnchangedClasses,
34
+ applyFiltersChangedClasses,
35
+ searchFilterHeaderClasses,
36
+ radioButtonFillColor,
37
+ radioButtonHoverColor,
38
+ radioButtonUncheckedBorderColor,
39
+ filterCloseIconVariant,
40
+ } = searchFilterSidebarVariantClasses[searchFilterSidebarVariant];
41
+
42
+ const openFilter = useCallback(() => {
43
+ setIsOpen(true);
44
+ }, [setIsOpen]);
45
+
46
+ return (
47
+ <>
48
+ <SearchFilter
49
+ isOpen={isOpen}
50
+ setIsOpen={setIsOpen}
51
+ filters={searchFilters}
52
+ productCount={productCount}
53
+ selectFilterItem={onSelectFilterItem}
54
+ clearAllFilters={onClearAllFilters}
55
+ applyFiltersUnchangedClasses={applyFiltersUnchangedClasses}
56
+ applyFiltersChangedClasses={applyFiltersChangedClasses}
57
+ filterButtonText={filterButtonText}
58
+ radioButtonFillColor={radioButtonFillColor}
59
+ radioButtonHoverColor={radioButtonHoverColor}
60
+ radioButtonUncheckedBorderColor={radioButtonUncheckedBorderColor}
61
+ filterCloseIconVariant={filterCloseIconVariant}
62
+ headerContent={
63
+ <SearchFilterHeader
64
+ closeModal={() => setIsOpen(false)}
65
+ productCount={productCount}
66
+ headerClassName={searchFilterHeaderClasses}
67
+ filterCloseIconVariant={filterCloseIconVariant}
68
+ />
69
+ }
70
+ />
71
+ <ButtonBase
72
+ onClick={openFilter}
73
+ // icon={SettingsVariant} // Icon is handled by user
74
+ iconClass="spiffy-tw-w-[28px] spiffy-tw-h-[28px] spiffy-tw-object-center"
75
+ text={filterButtonText}
76
+ textClass="spiffy-tw-uppercase spiffy-tw-text-[--spiffy-colors-text-primary] spiffy-tw-hidden md:spiffy-tw-block"
77
+ buttonClass="spiffy-tw-flex-row spiffy-tw-pl-[16px] sm:spiffy-tw-pl-0 sm:spiffy-tw-px-4 sm:spiffy-tw-gap-[8px]"
78
+ disablePadding
79
+ />
80
+ </>
81
+ );
82
+ };
@@ -0,0 +1,45 @@
1
+ import type { SearchFilterSidebarVariant, CloseIconVariant } from './types';
2
+ import { ColorNames, colorVar } from 'src/components/models/colorsConfig';
3
+
4
+ interface SearchFilterSidebarVariantClasses {
5
+ searchFilterHeaderClasses: string;
6
+ radioButtonFillColor: string;
7
+ radioButtonHoverColor: string;
8
+ radioButtonUncheckedBorderColor: string;
9
+ filterDefaultClasses: string;
10
+ filterActiveClasses: string;
11
+ filterHoverClasses: string;
12
+ appliedFilterBackgroundClasses: string;
13
+ applyFiltersUnchangedClasses: string;
14
+ applyFiltersChangedClasses: string;
15
+ filterCloseIconVariant: CloseIconVariant;
16
+ }
17
+
18
+ export const searchFilterSidebarVariantClasses: Record<SearchFilterSidebarVariant, SearchFilterSidebarVariantClasses> = {
19
+ darkButton: {
20
+ radioButtonFillColor: colorVar(ColorNames.BackgroundDark),
21
+ radioButtonHoverColor: colorVar(ColorNames.BackgroundSecondaryDark),
22
+ radioButtonUncheckedBorderColor: colorVar(ColorNames.BorderMedium),
23
+ searchFilterHeaderClasses: 'spiffy-tw-text-[--spiffy-colors-text-light]',
24
+ filterDefaultClasses: 'spiffy-tw-border-[--spiffy-colors-border-medium] spiffy-tw-bg-[--spiffy-colors-background-light] spiffy-tw-text-[--spiffy-colors-text-secondary]',
25
+ filterHoverClasses: 'hover:spiffy-tw-border-[--spiffy-colors-background-secondary-dark] hover:spiffy-tw-bg-[--spiffy-colors-background-secondary-dark] hover:spiffy-tw-text-[--spiffy-colors-text-primary]',
26
+ filterActiveClasses: 'spiffy-tw-border-[--spiffy-colors-border-outline] spiffy-tw-bg-[--spiffy-colors-background-dark] spiffy-tw-text-[--spiffy-colors-text-light]',
27
+ appliedFilterBackgroundClasses: 'spiffy-tw-bg-[--spiffy-colors-border-light]',
28
+ applyFiltersUnchangedClasses: 'spiffy-tw-bg-[--spiffy-colors-border-light] spiffy-tw-text-[--spiffy-colors-text-secondary]',
29
+ applyFiltersChangedClasses: 'spiffy-tw-bg-[--spiffy-colors-background-dark] spiffy-tw-text-[--spiffy-colors-text-light]',
30
+ filterCloseIconVariant: 'light',
31
+ },
32
+ lightButton: {
33
+ radioButtonFillColor: colorVar(ColorNames.BackgroundDark),
34
+ radioButtonHoverColor: colorVar(ColorNames.BackgroundSecondaryDark),
35
+ radioButtonUncheckedBorderColor: colorVar(ColorNames.BorderMedium),
36
+ searchFilterHeaderClasses: 'spiffy-tw-text-[--spiffy-colors-text-primary]',
37
+ filterDefaultClasses: 'spiffy-tw-border-[--spiffy-colors-border-medium] spiffy-tw-bg-[--spiffy-colors-background-light] spiffy-tw-text-[--spiffy-colors-text-secondary]',
38
+ filterHoverClasses: 'hover:spiffy-tw-border-[--spiffy-colors-background-secondary-dark] hover:spiffy-tw-bg-[--spiffy-colors-background-secondary-dark] hover:spiffy-tw-text-[--spiffy-colors-text-primary]',
39
+ filterActiveClasses: 'spiffy-tw-border-[--spiffy-colors-border-outline] spiffy-tw-bg-[--spiffy-colors-background-dark] spiffy-tw-text-[--spiffy-colors-text-light]',
40
+ appliedFilterBackgroundClasses: 'spiffy-tw-bg-[--spiffy-colors-border-light]',
41
+ applyFiltersUnchangedClasses: 'spiffy-tw-bg-[--spiffy-colors-border-light] spiffy-tw-text-[--spiffy-colors-text-secondary]',
42
+ applyFiltersChangedClasses: 'spiffy-tw-bg-[--spiffy-colors-background-primary] spiffy-tw-text-[--spiffy-colors-text-link]',
43
+ filterCloseIconVariant: 'dark',
44
+ },
45
+ };
@@ -1,2 +1,2 @@
1
1
  export type SearchFilterSidebarVariant = 'darkButton' | 'lightButton';
2
- export type CloseIconVariant = 'light' | 'tertiary' | 'dark';
2
+ export type CloseIconVariant = 'light' | 'tertiary' | 'dark';
@@ -0,0 +1,41 @@
1
+ import classNames from 'classnames';
2
+ import Sparkles from '@envive-ai/react-icons/src/Sparkles';
3
+ import { Text } from 'src/components/common/Text/Text';
4
+
5
+ interface NoSearchResultsFoundProps {
6
+ containerPaddingClasses: string;
7
+ includeBottomMargin?: boolean; // manually add about a page of bottom margin to push unsupressed content down
8
+ noResultsFoundText?: string;
9
+ sparkleIconColor?: string; // example format: var(--spiffy-colors-accent-primary)
10
+ }
11
+
12
+ export const NoSearchResultsFound = ({
13
+ containerPaddingClasses,
14
+ includeBottomMargin = false,
15
+ noResultsFoundText,
16
+ sparkleIconColor = 'var(--spiffy-colors-accent-primary)',
17
+ }: NoSearchResultsFoundProps) => {
18
+ const containerClasses = classNames(
19
+ containerPaddingClasses,
20
+ includeBottomMargin ? 'spiffy-tw-mb-[100vh]' : '',
21
+ 'spiffy-tw-mt-6',
22
+ );
23
+
24
+ const textPrompt =
25
+ noResultsFoundText ||
26
+ 'I’m sorry, I wasn’t able to find an exact match. Try changing your filters or adjusting your search query.';
27
+
28
+ return (
29
+ <div className={containerClasses}>
30
+ <div className="spiffy-tw-flex spiffy-tw-items-start spiffy-tw-gap-2">
31
+ <div>
32
+ <Sparkles
33
+ className="spiffy-tw-w-[24px] spiffy-tw-h-[24px]"
34
+ style={{ color: sparkleIconColor }}
35
+ />
36
+ </div>
37
+ <Text variant="body2">{textPrompt}</Text>
38
+ </div>
39
+ </div>
40
+ );
41
+ };
@@ -0,0 +1,105 @@
1
+ import { motion } from 'framer-motion';
2
+ import type { SelectedFilterOption } from 'src/atoms/search/types';
3
+ import classNames from 'classnames';
4
+ import type { SearchResponseProduct } from 'src/types/external';
5
+ import { ProductGrid } from 'src/components/common/ProductGrid/ProductGrid';
6
+ import { AppliedFiltersScrollbar } from '../FilterScrollbar/AppliedFiltersScrollbar';
7
+ import { DynamicFiltersScrollbar } from '../FilterScrollbar/DynamicFiltersScrollbar';
8
+ import { searchFilterSidebarVariantClasses } from '../SearchResultsFilterSidebar/searchFilterSidebarVariants';
9
+ import type { SearchFilterSidebarVariant } from '../SearchResultsFilterSidebar/types';
10
+ import { ProductGridVariant } from 'src/components/common/ProductCard/types';
11
+
12
+ interface SearchResultsGridProps {
13
+ productList: SearchResponseProduct[];
14
+ availableDynamicFilters: { name: string; displayName: string }[];
15
+ searchFilterSidebarVariant: SearchFilterSidebarVariant;
16
+ productGridVariant: ProductGridVariant;
17
+ selectedFilterOptions: SelectedFilterOption[];
18
+ searchResponseId: string;
19
+ containerXPaddingClasses: string;
20
+ productGridClasses: string;
21
+ onRemoveFilter: (filter: SelectedFilterOption) => void;
22
+ onToggleDynamicFilter: ({
23
+ filter,
24
+ dynamicFilterDisplayName,
25
+ }: {
26
+ filter: string;
27
+ dynamicFilterDisplayName: string;
28
+ }) => void;
29
+ }
30
+
31
+ export const SearchResultsGrid = ({
32
+ productList,
33
+ availableDynamicFilters,
34
+ searchFilterSidebarVariant,
35
+ productGridVariant,
36
+ selectedFilterOptions,
37
+ searchResponseId,
38
+ containerXPaddingClasses,
39
+ productGridClasses,
40
+ onRemoveFilter,
41
+ onToggleDynamicFilter,
42
+ }: SearchResultsGridProps) => {
43
+ const sharedFilterBarClasses = classNames(
44
+ 'spiffy-suggestion-bar',
45
+ 'spiffy-tw-no-scrollbar',
46
+ 'spiffy-tw-flex',
47
+ 'spiffy-tw-flex-row',
48
+ 'spiffy-tw-gap-2',
49
+ 'spiffy-tw-mb-[16px]',
50
+ 'spiffy-tw-overflow-x-scroll',
51
+ containerXPaddingClasses,
52
+ );
53
+
54
+ const filterBarClasses = classNames(sharedFilterBarClasses, 'spiffy-tw-mt-[24px]');
55
+
56
+ const appliedFilterBarClasses = classNames(
57
+ sharedFilterBarClasses,
58
+ 'spiffy-tw-mb-[32px]',
59
+ 'spiffy-tw-mt-[8px]',
60
+ );
61
+
62
+ const {
63
+ // We may want to pull these into their own variant separated from the filter sidebar (but also used in it) In this case, we need them for dynamic filters
64
+ filterDefaultClasses,
65
+ filterHoverClasses,
66
+ appliedFilterBackgroundClasses,
67
+ } = searchFilterSidebarVariantClasses[searchFilterSidebarVariant];
68
+
69
+ return (
70
+ <motion.div
71
+ className="spiffy-tw-justify-center spiffy-tw-w-full spiffy-tw-overflow-hidden"
72
+ initial={{ opacity: 0 }}
73
+ animate={{ opacity: 1 }}
74
+ exit={{ opacity: 0 }}
75
+ transition={{ duration: 0.2 }}
76
+ >
77
+ <AppliedFiltersScrollbar
78
+ selectedFilterOptions={selectedFilterOptions}
79
+ filterBarClassNames={appliedFilterBarClasses}
80
+ filterDefaultClasses={filterDefaultClasses}
81
+ filterHoverClasses={filterHoverClasses}
82
+ appliedFilterBackgroundClasses={appliedFilterBackgroundClasses}
83
+ onRemoveFilter={onRemoveFilter}
84
+ />
85
+ <DynamicFiltersScrollbar
86
+ availableDynamicFilters={availableDynamicFilters}
87
+ filterBarClassNames={filterBarClasses}
88
+ filterDefaultClasses={filterDefaultClasses}
89
+ filterHoverClasses={filterHoverClasses}
90
+ onToggleDynamicFilter={onToggleDynamicFilter}
91
+ />
92
+ <ProductGrid
93
+ productList={productList}
94
+ productGridVariant={productGridVariant}
95
+ productGridClasses={productGridClasses}
96
+ searchResponseId={searchResponseId}
97
+ merchantShortName={''}
98
+ handleClick={function (clickedUrl: string): void {
99
+ console.log(clickedUrl);
100
+ throw new Error('Function not implemented.');
101
+ }}
102
+ />
103
+ </motion.div>
104
+ );
105
+ };
@@ -0,0 +1,50 @@
1
+ import { Text } from 'src/components/common/Text/Text';
2
+ import { SparkleAnimation } from 'src/components/common/SparkleAnimation/SparkleAnimation';
3
+ import { motion } from 'framer-motion';
4
+ import { ProductGridVariant } from 'src/components/common/ProductCard/types';
5
+ import { ProductCardSkeleton } from 'src/components/common/ProductCard/ProductCardSkeleton';
6
+ import { productGridVariantClasses } from 'src/components/common/ProductGrid/productGridVariants';
7
+
8
+ export const SearchResultsLoadingGrid = ({
9
+ productGridVariant,
10
+ productGridClasses,
11
+ sparkleIconColor = 'var(--spiffy-colors-accent-primary)',
12
+ }: {
13
+ productGridVariant: ProductGridVariant;
14
+ productGridClasses: string;
15
+ sparkleIconColor?: string;
16
+ }) => {
17
+ const { productCardLayoutVariant, productCardImageAspectRatio } =
18
+ productGridVariantClasses[productGridVariant];
19
+
20
+ return (
21
+ <motion.div
22
+ className="spiffy-tw-justify-center spiffy-tw-w-full spiffy-tw-overflow-hidden"
23
+ initial={{ opacity: 0 }}
24
+ animate={{ opacity: 1 }}
25
+ exit={{ opacity: 0 }}
26
+ transition={{ duration: 0.2 }}
27
+ >
28
+ <div className="spiffy-tw-mt-6 spiffy-tw-w-full">
29
+ <div className="spiffy-tw-flex spiffy-tw-items-center spiffy-tw-justify-between spiffy-tw-mb-4">
30
+ <div className="spiffy-tw-flex spiffy-tw-items-center spiffy-tw-gap-2 spiffy-tw-ml-[16px] md:spiffy-tw-ml-[80px]">
31
+ <div className="spiffy-tw-width-[24px]">
32
+ <SparkleAnimation color={sparkleIconColor} animate={true} />
33
+ </div>
34
+ <Text variant="body2">Finding the best options for you...</Text>
35
+ </div>
36
+ </div>
37
+ <div className={productGridClasses}>
38
+ {Array.from({ length: 8 }).map((_, index) => (
39
+ <ProductCardSkeleton
40
+ key={index}
41
+ layoutVariant={productCardLayoutVariant}
42
+ aspectRatio={productCardImageAspectRatio}
43
+ growWithContainer={true}
44
+ />
45
+ ))}
46
+ </div>
47
+ </div>
48
+ </motion.div>
49
+ );
50
+ };
@@ -0,0 +1,5 @@
1
+ export enum SearchResultsState {
2
+ NoResults = 'noResults',
3
+ Results = 'results',
4
+ Loading = 'loading',
5
+ }
@@ -1,3 +1,3 @@
1
1
  export interface TestProps {
2
- dataTestId?: string;
2
+ dataTestId?: string;
3
3
  }
@@ -0,0 +1,22 @@
1
+ // Chat element display location enum
2
+ export enum ChatElementDisplayLocation {
3
+ IN_CHAT = 'in_chat',
4
+ CHAT_PREVIEW = 'chat_preview',
5
+ FLOATING_BUTTON = 'floating_button',
6
+ HELP_ME_CHOOSE = 'help_me_choose',
7
+ PLP_IMAGE_BANNER = 'plp_image_banner',
8
+ TOP_REVIEWS_SNIPPET = 'top_reviews_snippet',
9
+ BOTTOM_REVIEWS_SNIPPET = 'bottom_reviews_snippet',
10
+ BLOCK_BACK_BUTTON = 'block_back_button',
11
+ SWITCH_TO_AGENT = 'switch_to_agent',
12
+ CONVERSATIONAL_SEARCH = 'conversational_search',
13
+ GLOBAL_SEARCH_ENTRYPOINT = 'global_search_entrypoint',
14
+ SEARCH_NAV_ENTRYPOINT = 'search_nav_entrypoint',
15
+ SEARCH_PROMPT = 'search_prompt',
16
+ SEARCH_PROMPT_BUTTON = 'search_prompt_button',
17
+ PRODUCT_GRID = 'product_grid',
18
+ UNSPECIFIED = 'unspecified',
19
+ FILTER_MODAL = 'filter_modal',
20
+ PROMPT_CARD = 'prompt_card',
21
+ WINDOW_API_CALL = 'window_api_call',
22
+ }