@envive-ai/react-widgets 0.1.0-arthur-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.
- package/dist/index-BPfKr14f.d.ts +6 -0
- package/dist/index-VWNd4lyI.d.cts +6 -0
- package/dist/index.cjs +714 -0
- package/dist/index.js +689 -0
- package/package.json +78 -0
- package/src/SearchResults/SearchResults.tsx +144 -0
- package/src/SearchResults/SearchResultsWidget.tsx +43 -0
- package/src/SearchResults/index.ts +1 -0
- package/src/SearchResults/types.ts +19 -0
- package/src/SearchResults/withSearchResults.tsx +22 -0
- package/src/config/BaseWidgetConfig.ts +7 -0
- package/src/config/WidgetType.ts +18 -0
- package/src/stories/SearchResults.stories.tsx +29 -0
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@envive-ai/react-widgets",
|
|
3
|
+
"version": "0.1.0-arthur-1",
|
|
4
|
+
"description": "React widget library for Envive services.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"components",
|
|
8
|
+
"envive",
|
|
9
|
+
"ui"
|
|
10
|
+
],
|
|
11
|
+
"author": "Envive AI",
|
|
12
|
+
"license": "UNLICENSED",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index-VWNd4lyI.d.cts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src",
|
|
20
|
+
"!**/*.tsbuildinfo",
|
|
21
|
+
"tailwind-preset.js"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"build:watch": "tsdown --watch",
|
|
26
|
+
"prepublish": "npm run build",
|
|
27
|
+
"typecheck": "tsc",
|
|
28
|
+
"test": "vitest",
|
|
29
|
+
"storybook": "npm run get-merchants && storybook dev -p 6006",
|
|
30
|
+
"build-storybook": "npm run get-merchants && storybook build",
|
|
31
|
+
"dev": "npm run get-merchants && storybook dev -p 6006",
|
|
32
|
+
"get-merchants": "tsx ./.storybook/getMerchants.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@envive-ai/react-hooks": "0.2.7",
|
|
36
|
+
"@envive-ai/react-toolkit": "*",
|
|
37
|
+
"@tailwindcss/typography": "^0.5.15",
|
|
38
|
+
"classnames": "^2.5.1",
|
|
39
|
+
"framer-motion": "^12.23.24"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=18.3.1",
|
|
43
|
+
"react-dom": ">=18.3.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@chromatic-com/storybook": "^4.1.1",
|
|
47
|
+
"@mdx-js/react": "^3.1.1",
|
|
48
|
+
"@storybook/addon-a11y": "^9.1.4",
|
|
49
|
+
"@storybook/addon-docs": "^9.1.4",
|
|
50
|
+
"@storybook/addon-onboarding": "^9.1.4",
|
|
51
|
+
"@storybook/addon-styling-webpack": "^2.0.0",
|
|
52
|
+
"@storybook/addon-vitest": "^9.1.10",
|
|
53
|
+
"@storybook/icons": "^1.6.0",
|
|
54
|
+
"@storybook/react-vite": "^9.1.4",
|
|
55
|
+
"@types/node": "^24.3.2",
|
|
56
|
+
"@types/react": "^19.1.12",
|
|
57
|
+
"eslint-plugin-storybook": "^9.1.4",
|
|
58
|
+
"postcss": "^8.5.6",
|
|
59
|
+
"storybook": "^9.1.4",
|
|
60
|
+
"tailwindcss": "^3.4.17",
|
|
61
|
+
"tsdown": "^0.14.2",
|
|
62
|
+
"typescript": "^5.4.3",
|
|
63
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
64
|
+
"vitest": "^3.2.4"
|
|
65
|
+
},
|
|
66
|
+
"exports": {
|
|
67
|
+
".": {
|
|
68
|
+
"import": "./dist/index.js",
|
|
69
|
+
"require": "./dist/index.cjs"
|
|
70
|
+
},
|
|
71
|
+
"./package.json": "./package.json"
|
|
72
|
+
},
|
|
73
|
+
"eslintConfig": {
|
|
74
|
+
"extends": [
|
|
75
|
+
"plugin:storybook/recommended"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { SearchResultsContent } from '@envive-ai/react-toolkit/SearchResultsContent';
|
|
4
|
+
import { SearchResultsFilterModal } from '@envive-ai/react-toolkit/SearchResultsFilterModal';
|
|
5
|
+
import { SearchResultsToolbar } from '@envive-ai/react-toolkit/SearchResultsToolbar';
|
|
6
|
+
import { useStickyVisibility } from '@envive-ai/react-toolkit/util'
|
|
7
|
+
import { SearchResultsHocProps } from '@envive-ai/react-hooks/hooks/Search';
|
|
8
|
+
import { SearchResultsEntryPointWidgetConfig } from './types';
|
|
9
|
+
import { withSearchResults } from './withSearchResults';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface SearchResultsProps extends SearchResultsHocProps {
|
|
14
|
+
widgetConfig: SearchResultsEntryPointWidgetConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SearchResultsComponent = ({
|
|
18
|
+
widgetConfig,
|
|
19
|
+
productCardConfig,
|
|
20
|
+
merchantShortName,
|
|
21
|
+
productList,
|
|
22
|
+
autocompleteResults,
|
|
23
|
+
searchFilters,
|
|
24
|
+
availableDynamicFilters,
|
|
25
|
+
selectedFilterOptions,
|
|
26
|
+
searchText,
|
|
27
|
+
query,
|
|
28
|
+
searchResultsState,
|
|
29
|
+
isFilterOpen,
|
|
30
|
+
shouldShowAutocomplete,
|
|
31
|
+
focusedIndex,
|
|
32
|
+
focusedOptionId,
|
|
33
|
+
recommendedProducts,
|
|
34
|
+
|
|
35
|
+
// UI
|
|
36
|
+
filterButtonText,
|
|
37
|
+
|
|
38
|
+
// Callbacks
|
|
39
|
+
onSearchInputChange,
|
|
40
|
+
onSubmitSearch,
|
|
41
|
+
onAutocompleteSelect,
|
|
42
|
+
onKeyDown,
|
|
43
|
+
onSearchInputFocus,
|
|
44
|
+
onSearchInputBlur,
|
|
45
|
+
onToggleDynamicFilter,
|
|
46
|
+
onSelectFilterItem,
|
|
47
|
+
onRemoveFilter,
|
|
48
|
+
onClearAllFilters,
|
|
49
|
+
setIsFilterOpen,
|
|
50
|
+
|
|
51
|
+
// Refs
|
|
52
|
+
searchResultsRef,
|
|
53
|
+
}: SearchResultsProps) => {
|
|
54
|
+
const {
|
|
55
|
+
searchInputVariant,
|
|
56
|
+
searchFilterSidebarVariant,
|
|
57
|
+
productGridVariant,
|
|
58
|
+
searchBoxPlaceholder,
|
|
59
|
+
noResultsFoundText,
|
|
60
|
+
isSearchInputSticky = false,
|
|
61
|
+
} = widgetConfig;
|
|
62
|
+
|
|
63
|
+
const { toolbarRef: ref, isVisible, toolbarHeight: height } = useStickyVisibility();
|
|
64
|
+
|
|
65
|
+
const toolbarRef = isSearchInputSticky ? ref : null;
|
|
66
|
+
const finalIsVisible = isSearchInputSticky ? isVisible : true;
|
|
67
|
+
const toolbarHeight = isSearchInputSticky ? height : 0;
|
|
68
|
+
|
|
69
|
+
const stickyToolbarClasses = classNames(
|
|
70
|
+
'spiffy-tw-fixed',
|
|
71
|
+
'spiffy-tw-top-0',
|
|
72
|
+
'spiffy-tw-left-0',
|
|
73
|
+
'spiffy-tw-right-0',
|
|
74
|
+
'spiffy-tw-transition-transform',
|
|
75
|
+
'spiffy-tw-duration-300',
|
|
76
|
+
'spiffy-tw-py-[8px]',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const containerXPaddingClasses = classNames('spiffy-tw-px-[16px]', 'sm:spiffy-tw-px-[46px]');
|
|
80
|
+
|
|
81
|
+
// Scroll to top when new search results are loaded
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
window.scrollTo({
|
|
84
|
+
top: 0,
|
|
85
|
+
behavior: 'smooth',
|
|
86
|
+
});
|
|
87
|
+
}, [productList]);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
<SearchResultsFilterModal
|
|
92
|
+
isOpen={isFilterOpen}
|
|
93
|
+
setIsOpen={setIsFilterOpen}
|
|
94
|
+
searchFilters={searchFilters}
|
|
95
|
+
productCount={productList.length}
|
|
96
|
+
onSelectFilterItem={onSelectFilterItem}
|
|
97
|
+
onClearAllFilters={onClearAllFilters}
|
|
98
|
+
searchFilterSidebarVariant={searchFilterSidebarVariant}
|
|
99
|
+
filterButtonText={filterButtonText}
|
|
100
|
+
/>
|
|
101
|
+
<SearchResultsToolbar
|
|
102
|
+
className={isSearchInputSticky ? stickyToolbarClasses : 'spiffy-tw-py-[8px]'}
|
|
103
|
+
toolbarRef={toolbarRef}
|
|
104
|
+
searchInputVariant={searchInputVariant}
|
|
105
|
+
searchBoxPlaceholder={searchBoxPlaceholder}
|
|
106
|
+
searchText={searchText}
|
|
107
|
+
focusedIndex={focusedIndex}
|
|
108
|
+
focusedOptionId={focusedOptionId}
|
|
109
|
+
autocompleteResults={autocompleteResults}
|
|
110
|
+
filterButtonText={filterButtonText}
|
|
111
|
+
onKeyDown={onKeyDown}
|
|
112
|
+
onSearchInputChange={onSearchInputChange}
|
|
113
|
+
onSubmitSearch={onSubmitSearch}
|
|
114
|
+
onAutocompleteSelect={onAutocompleteSelect}
|
|
115
|
+
onSearchInputFocus={onSearchInputFocus}
|
|
116
|
+
onSearchInputBlur={onSearchInputBlur}
|
|
117
|
+
shouldShowAutocomplete={shouldShowAutocomplete}
|
|
118
|
+
setIsFilterOpen={setIsFilterOpen}
|
|
119
|
+
containerXPaddingClasses={containerXPaddingClasses}
|
|
120
|
+
isVisible={finalIsVisible}
|
|
121
|
+
/>
|
|
122
|
+
<SearchResultsContent
|
|
123
|
+
searchResultsState={searchResultsState}
|
|
124
|
+
productList={productList}
|
|
125
|
+
recommendedProducts={recommendedProducts}
|
|
126
|
+
productCardConfig={productCardConfig}
|
|
127
|
+
merchantShortName={merchantShortName}
|
|
128
|
+
searchFilterSidebarVariant={searchFilterSidebarVariant}
|
|
129
|
+
noResultsFoundText={noResultsFoundText}
|
|
130
|
+
productGridVariant={productGridVariant}
|
|
131
|
+
containerXPaddingClasses={containerXPaddingClasses}
|
|
132
|
+
selectedFilterOptions={selectedFilterOptions}
|
|
133
|
+
availableDynamicFilters={availableDynamicFilters}
|
|
134
|
+
onRemoveFilter={onRemoveFilter}
|
|
135
|
+
onToggleDynamicFilter={onToggleDynamicFilter}
|
|
136
|
+
searchResultsRef={searchResultsRef}
|
|
137
|
+
toolbarHeight={toolbarHeight}
|
|
138
|
+
query={query}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const SearchResults = withSearchResults(SearchResultsComponent);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
|
+
import { useSetAtom } from 'jotai';
|
|
4
|
+
import { isSearchResultsOpenAtom } from '@envive-ai/react-hooks/atoms/globalSearch';
|
|
5
|
+
import { searchSystemAtom } from '@envive-ai/react-hooks/atoms/search';
|
|
6
|
+
import { useNewOrgConfig } from '@envive-ai/react-hooks/hooks/NewOrgConfig';
|
|
7
|
+
import { SearchResults } from './SearchResults';
|
|
8
|
+
import { SearchResultsEntryPointWidgetConfig } from './types';
|
|
9
|
+
|
|
10
|
+
export const SearchResultsWidget = () => {
|
|
11
|
+
const setIsSearchResultsOpen = useSetAtom(isSearchResultsOpenAtom);
|
|
12
|
+
const setSearchSystem = useSetAtom(searchSystemAtom);
|
|
13
|
+
const newConfig = useNewOrgConfig();
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setSearchSystem(true);
|
|
17
|
+
return () => setSearchSystem(false);
|
|
18
|
+
}, [setSearchSystem]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setIsSearchResultsOpen(true);
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
setIsSearchResultsOpen(false);
|
|
24
|
+
};
|
|
25
|
+
}, [setIsSearchResultsOpen]);
|
|
26
|
+
|
|
27
|
+
const widgetConfig = useMemo(() => {
|
|
28
|
+
if (newConfig && newConfig.frontendConfig?.widgetConfigs) {
|
|
29
|
+
const baseWidgetConfig = (newConfig.frontendConfig?.widgetConfigs as unknown as any[]).find(
|
|
30
|
+
(widget: { key: string }) => widget.key === 'searchResultsEntryPoint',
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
...baseWidgetConfig.config,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}, [newConfig]);
|
|
38
|
+
if (!widgetConfig) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return <SearchResults widgetConfig={widgetConfig as SearchResultsEntryPointWidgetConfig} />;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SearchResultsWidget } from './SearchResultsWidget';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { SearchFilterSidebarVariant } from '@envive-ai/react-toolkit/SearchResultsFilterSidebar';
|
|
5
|
+
import { ProductGridVariant } from '@envive-ai/react-toolkit/ProductCard';
|
|
6
|
+
import { BaseWidgetConfig } from 'src/config/BaseWidgetConfig';
|
|
7
|
+
import { WidgetType } from 'src/config/WidgetType';
|
|
8
|
+
|
|
9
|
+
export type SearchInputVariant = 'standard';
|
|
10
|
+
|
|
11
|
+
export interface SearchResultsEntryPointWidgetConfig
|
|
12
|
+
extends BaseWidgetConfig<WidgetType.SearchResultsEntryPoint> {
|
|
13
|
+
searchInputVariant: SearchInputVariant;
|
|
14
|
+
searchFilterSidebarVariant: SearchFilterSidebarVariant;
|
|
15
|
+
productGridVariant: ProductGridVariant;
|
|
16
|
+
searchBoxPlaceholder: string;
|
|
17
|
+
noResultsFoundText?: string;
|
|
18
|
+
isSearchInputSticky?: boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import { SearchResultsHocProps, useSearch } from '@envive-ai/react-hooks/hooks/Search';
|
|
3
|
+
import type { SearchResultsProps } from './SearchResults';
|
|
4
|
+
|
|
5
|
+
export function withSearchResults(Component: ComponentType<SearchResultsProps>) {
|
|
6
|
+
// The HOC will accept the props that are not provided by the useSearch hook.
|
|
7
|
+
type HocProps = Omit<SearchResultsProps, keyof SearchResultsHocProps>;
|
|
8
|
+
|
|
9
|
+
return function SearchResultsHoc(props: HocProps) {
|
|
10
|
+
const searchHookResult = useSearch();
|
|
11
|
+
|
|
12
|
+
// The props passed to the HOC and the results from the hook are combined
|
|
13
|
+
// and passed to the wrapped component.
|
|
14
|
+
return (
|
|
15
|
+
<Component
|
|
16
|
+
{...props}
|
|
17
|
+
{...searchHookResult}
|
|
18
|
+
productList={searchHookResult.searchData?.products || []}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export enum WidgetType {
|
|
2
|
+
ChatPreview = 'ChatPreview',
|
|
3
|
+
SocialProofV2 = 'SocialProofV2',
|
|
4
|
+
ChatPreviewV2 = 'ChatPreviewV2',
|
|
5
|
+
ChatPreviewIsLoading = 'ChatPreviewIsLoading',
|
|
6
|
+
ChatPreviewPostInteraction = 'ChatPreviewPostInteraction',
|
|
7
|
+
ChatPreviewProductComparison = 'ChatPreviewProductComparison',
|
|
8
|
+
SuggestionBar = 'SuggestionBar',
|
|
9
|
+
SuggestionBarV2 = 'SuggestionBarV2',
|
|
10
|
+
ImagePromptCard = 'ImagePromptCard',
|
|
11
|
+
ImageBanner = 'ImageBanner',
|
|
12
|
+
SingleImagePrompt = 'SingleImagePrompt',
|
|
13
|
+
SearchPrompt = 'SearchPrompt',
|
|
14
|
+
SearchZeroStateEntryPoint = 'SearchZeroStateEntryPoint',
|
|
15
|
+
SearchResultsEntryPoint = 'SearchResultsEntryPoint',
|
|
16
|
+
SearchOverlayHost = 'SearchOverlayHost',
|
|
17
|
+
NoOp = 'NoOp',
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { SearchResults } from 'src/SearchResults/SearchResults';
|
|
4
|
+
import { WidgetType } from '@envive-ai/react-hooks/contexts/types';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Widgets/Search/SearchResults',
|
|
8
|
+
component: SearchResults,
|
|
9
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'fullscreen',
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
widgetConfig: {
|
|
16
|
+
searchInputVariant: 'standard',
|
|
17
|
+
searchFilterSidebarVariant: 'darkButton',
|
|
18
|
+
productGridVariant: 'standard',
|
|
19
|
+
searchBoxPlaceholder: '',
|
|
20
|
+
widgetConfigId: '',
|
|
21
|
+
type: WidgetType.SearchResultsEntryPoint
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof SearchResults>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {};
|