@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/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,7 @@
1
+ import { WidgetType } from './WidgetType';
2
+
3
+ export interface BaseWidgetConfig<T extends WidgetType> {
4
+ widgetConfigId: string;
5
+ type: T;
6
+ contentId?: string;
7
+ }
@@ -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 = {};