@envive-ai/react-widgets 0.1.1 → 0.1.2-arthur-2
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/SearchResults/index-DCTxvwmv.d.cts +6 -0
- package/dist/{index.cjs → SearchResults/index.cjs} +2 -24
- package/dist/SearchZeroState/index-DSFtalZR.d.ts +27 -0
- package/dist/SearchZeroState/index-bEcxYOSF.d.cts +27 -0
- package/dist/SearchZeroState/index.cjs +3049 -0
- package/dist/SearchZeroState/index.js +3045 -0
- package/dist/SuggestionBar/index-DZU9kbWS.d.cts +39 -0
- package/dist/SuggestionBar/index-DyXd4-b7.d.ts +39 -0
- package/dist/SuggestionBar/index.cjs +5 -0
- package/dist/SuggestionBar/index.js +4 -0
- package/dist/SuggestionBar-BOThXJvJ.cjs +453 -0
- package/dist/SuggestionBar-DeMmAK4M.js +131 -0
- package/dist/SuggestionButtonContainer/index-B_X537jw.d.cts +20 -0
- package/dist/SuggestionButtonContainer/index-vwelzDzM.d.ts +20 -0
- package/dist/SuggestionButtonContainer/index.cjs +3 -0
- package/dist/SuggestionButtonContainer/index.js +3 -0
- package/dist/SuggestionButtonContainer-BeWPpeQk.cjs +173 -0
- package/dist/SuggestionButtonContainer-CZhOkZaJ.js +167 -0
- package/dist/chunk-DWy1uDak.cjs +39 -0
- package/package.json +18 -6
- package/src/SearchZeroState/SearchIcon.tsx +57 -0
- package/src/SearchZeroState/SearchOverlay.tsx +81 -0
- package/src/SearchZeroState/SearchZeroState.tsx +264 -0
- package/src/SearchZeroState/SearchZeroStateWidget.tsx +33 -0
- package/src/SearchZeroState/components/RecommendedProducts.tsx +118 -0
- package/src/SearchZeroState/index.ts +8 -0
- package/src/SearchZeroState/overlay/overlayHostLocator.ts +19 -0
- package/src/SearchZeroState/types.ts +9 -0
- package/src/SearchZeroState/zeroStateSearchVariants.ts +24 -0
- package/src/SuggestionBar/SuggestionBar.tsx +139 -0
- package/src/SuggestionBar/index.ts +2 -0
- package/src/SuggestionBar/types.ts +4 -0
- package/src/SuggestionButtonContainer/SuggestionButtonContainer.tsx +141 -0
- package/src/SuggestionButtonContainer/index.ts +2 -0
- package/src/SuggestionButtonContainer/types.ts +16 -0
- package/src/stories/SearchZeroState.stories.tsx +44 -0
- package/src/stories/SuggestionBar.stories.tsx +46 -0
- package/src/util/useHorizontalScrollAnimation.ts +121 -0
- package/src/util/useReducedMotionWithOverride.ts +24 -0
- package/dist/index-VWNd4lyI.d.cts +0 -6
- /package/dist/{index-BPfKr14f.d.ts → SearchResults/index-CYPV3XE0.d.ts} +0 -0
- /package/dist/{index.js → SearchResults/index.js} +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
2
|
+
import { TestProps } from "@envive-ai/react-hooks/types";
|
|
3
|
+
|
|
4
|
+
//#region src/SuggestionButtonContainer/types.d.ts
|
|
5
|
+
interface SuggestionButtonContainerProps extends TestProps {
|
|
6
|
+
buttonVariation: SuggestionButtonVariant;
|
|
7
|
+
hoverButtonVariation: SuggestionButtonVariant;
|
|
8
|
+
buttonTexts: string[];
|
|
9
|
+
onButtonClick: (text: string) => void;
|
|
10
|
+
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
|
11
|
+
boldFirstButton?: boolean | undefined;
|
|
12
|
+
twoRowsOnMobile?: boolean | undefined;
|
|
13
|
+
animationSpeed?: 'standard' | 'slow' | 'none';
|
|
14
|
+
buttonBorderRadius?: 'sm' | 'md' | 'lg';
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/SuggestionButtonContainer/SuggestionButtonContainer.d.ts
|
|
18
|
+
declare const SuggestionButtonContainer: React.FC<SuggestionButtonContainerProps>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { SuggestionButtonContainer, SuggestionButtonContainerProps };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TestProps } from "@envive-ai/react-hooks/types";
|
|
2
|
+
import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
3
|
+
|
|
4
|
+
//#region src/SuggestionButtonContainer/types.d.ts
|
|
5
|
+
interface SuggestionButtonContainerProps extends TestProps {
|
|
6
|
+
buttonVariation: SuggestionButtonVariant;
|
|
7
|
+
hoverButtonVariation: SuggestionButtonVariant;
|
|
8
|
+
buttonTexts: string[];
|
|
9
|
+
onButtonClick: (text: string) => void;
|
|
10
|
+
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
|
11
|
+
boldFirstButton?: boolean | undefined;
|
|
12
|
+
twoRowsOnMobile?: boolean | undefined;
|
|
13
|
+
animationSpeed?: 'standard' | 'slow' | 'none';
|
|
14
|
+
buttonBorderRadius?: 'sm' | 'md' | 'lg';
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/SuggestionButtonContainer/SuggestionButtonContainer.d.ts
|
|
18
|
+
declare const SuggestionButtonContainer: React.FC<SuggestionButtonContainerProps>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { SuggestionButtonContainer, SuggestionButtonContainerProps };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-DWy1uDak.cjs');
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
let framer_motion = require("framer-motion");
|
|
5
|
+
let __envive_ai_react_hooks_application_models = require("@envive-ai/react-hooks/application/models");
|
|
6
|
+
let __envive_ai_react_hooks_config = require("@envive-ai/react-hooks/config");
|
|
7
|
+
let __envive_ai_react_hooks_hooks_IsSmallScreen = require("@envive-ai/react-hooks/hooks/IsSmallScreen");
|
|
8
|
+
let __envive_ai_react_hooks_hooks_TrackComponentVisibleEvent = require("@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent");
|
|
9
|
+
let __envive_ai_react_toolkit_SuggestionButton = require("@envive-ai/react-toolkit/SuggestionButton");
|
|
10
|
+
|
|
11
|
+
//#region src/util/useReducedMotionWithOverride.ts
|
|
12
|
+
const useReducedMotionWithOverride = () => {
|
|
13
|
+
const reducedMotionConfig = (0, framer_motion.useReducedMotionConfig)();
|
|
14
|
+
return (0, react.useMemo)(() => {
|
|
15
|
+
if (window?._spiffy?.reducedMotionOverride) return window?._spiffy?.reducedMotionOverride;
|
|
16
|
+
return reducedMotionConfig;
|
|
17
|
+
}, [reducedMotionConfig]);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/util/useHorizontalScrollAnimation.ts
|
|
22
|
+
function useHorizontalScrollAnimation({ scrollContainerRef, animationSpeed = "standard" }) {
|
|
23
|
+
const reducedMotion = useReducedMotionWithOverride();
|
|
24
|
+
const resumeTimeoutRef = (0, react.useRef)(null);
|
|
25
|
+
const scrollAnimationRef = (0, react.useRef)(null);
|
|
26
|
+
const pauseOnHover = true;
|
|
27
|
+
let PIXELS_PER_SECOND = 40;
|
|
28
|
+
switch (animationSpeed) {
|
|
29
|
+
case "standard":
|
|
30
|
+
PIXELS_PER_SECOND = 40;
|
|
31
|
+
break;
|
|
32
|
+
case "slow":
|
|
33
|
+
PIXELS_PER_SECOND = 25;
|
|
34
|
+
break;
|
|
35
|
+
case "none":
|
|
36
|
+
PIXELS_PER_SECOND = 0;
|
|
37
|
+
break;
|
|
38
|
+
default: PIXELS_PER_SECOND = 40;
|
|
39
|
+
}
|
|
40
|
+
const RESUME_DELAY_MS = 2e3;
|
|
41
|
+
const isAnimated = animationSpeed !== "none";
|
|
42
|
+
(0, react.useEffect)(() => {
|
|
43
|
+
if (!isAnimated || reducedMotion || !scrollContainerRef) return () => {};
|
|
44
|
+
const container = scrollContainerRef.current;
|
|
45
|
+
if (!container) return () => {};
|
|
46
|
+
if (container.scrollWidth <= container.clientWidth) return () => {};
|
|
47
|
+
let isPaused = false;
|
|
48
|
+
let lastTimestamp = null;
|
|
49
|
+
let accumulatedScroll = 0;
|
|
50
|
+
const step = (timestamp) => {
|
|
51
|
+
if (lastTimestamp === null) lastTimestamp = timestamp;
|
|
52
|
+
if (!isPaused) {
|
|
53
|
+
const delta = timestamp - lastTimestamp;
|
|
54
|
+
lastTimestamp = timestamp;
|
|
55
|
+
accumulatedScroll += PIXELS_PER_SECOND * (delta / 1e3);
|
|
56
|
+
const pixelsToScroll = Math.floor(accumulatedScroll);
|
|
57
|
+
if (pixelsToScroll > 0) {
|
|
58
|
+
container.scrollLeft += pixelsToScroll;
|
|
59
|
+
accumulatedScroll -= pixelsToScroll;
|
|
60
|
+
if (Math.ceil(container.scrollLeft) >= container.scrollWidth - container.clientWidth) {
|
|
61
|
+
container.scrollLeft = 0;
|
|
62
|
+
accumulatedScroll = 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
67
|
+
};
|
|
68
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
69
|
+
const pauseAnimation = () => {
|
|
70
|
+
isPaused = true;
|
|
71
|
+
if (resumeTimeoutRef.current) {
|
|
72
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
73
|
+
resumeTimeoutRef.current = null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const scheduleResumeAnimation = () => {
|
|
77
|
+
resumeTimeoutRef.current = setTimeout(() => {
|
|
78
|
+
isPaused = false;
|
|
79
|
+
lastTimestamp = null;
|
|
80
|
+
}, RESUME_DELAY_MS);
|
|
81
|
+
};
|
|
82
|
+
container.addEventListener("mouseenter", pauseAnimation);
|
|
83
|
+
container.addEventListener("mouseleave", scheduleResumeAnimation);
|
|
84
|
+
container.addEventListener("touchstart", pauseAnimation);
|
|
85
|
+
container.addEventListener("touchend", scheduleResumeAnimation);
|
|
86
|
+
return function cleanup() {
|
|
87
|
+
if (scrollAnimationRef.current) cancelAnimationFrame(scrollAnimationRef.current);
|
|
88
|
+
container.removeEventListener("mouseenter", pauseAnimation);
|
|
89
|
+
container.removeEventListener("mouseleave", scheduleResumeAnimation);
|
|
90
|
+
container.removeEventListener("touchstart", pauseAnimation);
|
|
91
|
+
container.removeEventListener("touchend", scheduleResumeAnimation);
|
|
92
|
+
if (resumeTimeoutRef.current) clearTimeout(resumeTimeoutRef.current);
|
|
93
|
+
};
|
|
94
|
+
}, [
|
|
95
|
+
isAnimated,
|
|
96
|
+
reducedMotion,
|
|
97
|
+
PIXELS_PER_SECOND,
|
|
98
|
+
pauseOnHover,
|
|
99
|
+
scrollContainerRef
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/SuggestionButtonContainer/SuggestionButtonContainer.tsx
|
|
105
|
+
function ButtonContainer({ children }) {
|
|
106
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
107
|
+
className: "spiffy-tw-flex \n spiffy-tw-flex-row\n spiffy-tw-items-center\n spiffy-tw-space-x-2\n spiffy-tw-h-full\n spiffy-tw-pl-0",
|
|
108
|
+
children
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const SuggestionButtonContainer = ({ buttonVariation, hoverButtonVariation, buttonTexts, boldFirstButton = false, twoRowsOnMobile = false, animationSpeed = "none", buttonBorderRadius = "lg", scrollContainerRef, onButtonClick }) => {
|
|
112
|
+
const componentVisibleTriggerRef = (0, react.useRef)(null);
|
|
113
|
+
useHorizontalScrollAnimation({
|
|
114
|
+
scrollContainerRef,
|
|
115
|
+
animationSpeed
|
|
116
|
+
});
|
|
117
|
+
const isSmallScreen = (0, __envive_ai_react_hooks_hooks_IsSmallScreen.useIsSmallScreen)();
|
|
118
|
+
const isAnimated = animationSpeed !== "none";
|
|
119
|
+
(0, __envive_ai_react_hooks_hooks_TrackComponentVisibleEvent.useTrackComponentVisibleEvent)(__envive_ai_react_hooks_application_models.SpiffyWidgets.SuggestionBar, componentVisibleTriggerRef, { animated: isAnimated });
|
|
120
|
+
const visibleButtonsFirstRow = buttonTexts.slice(0, twoRowsOnMobile && isSmallScreen ? Math.ceil((buttonTexts.length + 1) / 2) : void 0);
|
|
121
|
+
const visibleButtonsSecondRow = buttonTexts.slice(Math.ceil((buttonTexts.length + 1) / 2), buttonTexts.length);
|
|
122
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
123
|
+
className: "spiffy-tw-overflow-x-scroll spiffy-tw-no-scrollbar spiffy-tw-w-full spiffy-tw-whitespace-nowrap",
|
|
124
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ButtonContainer, { children: [visibleButtonsFirstRow.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__envive_ai_react_toolkit_SuggestionButton.SuggestionButton, {
|
|
125
|
+
variant: buttonVariation,
|
|
126
|
+
hoverVariant: hoverButtonVariation,
|
|
127
|
+
isDisabled: false,
|
|
128
|
+
content: suggestion,
|
|
129
|
+
boldText: boldFirstButton && i === 0,
|
|
130
|
+
borderRadius: buttonBorderRadius,
|
|
131
|
+
onClick: () => onButtonClick(suggestion),
|
|
132
|
+
dataTestId: __envive_ai_react_hooks_config.SUGGESTION_BAR_BUTTON_TESTID
|
|
133
|
+
}, i)), isAnimated && buttonTexts.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__envive_ai_react_toolkit_SuggestionButton.SuggestionButton, {
|
|
134
|
+
variant: buttonVariation,
|
|
135
|
+
hoverVariant: hoverButtonVariation,
|
|
136
|
+
isDisabled: false,
|
|
137
|
+
content: suggestion,
|
|
138
|
+
boldText: boldFirstButton && i === 0,
|
|
139
|
+
borderRadius: buttonBorderRadius,
|
|
140
|
+
onClick: () => onButtonClick(suggestion),
|
|
141
|
+
dataTestId: __envive_ai_react_hooks_config.SUGGESTION_BAR_BUTTON_TESTID
|
|
142
|
+
}, `animation-dupe-${i}`))] }), twoRowsOnMobile && isSmallScreen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
143
|
+
className: "spiffy-tw-mt-1.5",
|
|
144
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ButtonContainer, { children: [visibleButtonsSecondRow.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__envive_ai_react_toolkit_SuggestionButton.SuggestionButton, {
|
|
145
|
+
variant: buttonVariation,
|
|
146
|
+
hoverVariant: hoverButtonVariation,
|
|
147
|
+
isDisabled: false,
|
|
148
|
+
content: suggestion,
|
|
149
|
+
boldText: boldFirstButton && i === 0,
|
|
150
|
+
borderRadius: buttonBorderRadius,
|
|
151
|
+
onClick: () => onButtonClick(suggestion),
|
|
152
|
+
dataTestId: __envive_ai_react_hooks_config.SUGGESTION_BAR_BUTTON_TESTID
|
|
153
|
+
}, i)), isAnimated && visibleButtonsSecondRow.map((suggestion, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__envive_ai_react_toolkit_SuggestionButton.SuggestionButton, {
|
|
154
|
+
variant: buttonVariation,
|
|
155
|
+
hoverVariant: hoverButtonVariation,
|
|
156
|
+
isDisabled: false,
|
|
157
|
+
content: suggestion,
|
|
158
|
+
boldText: boldFirstButton && i === 0,
|
|
159
|
+
borderRadius: buttonBorderRadius,
|
|
160
|
+
onClick: () => onButtonClick(suggestion),
|
|
161
|
+
dataTestId: __envive_ai_react_hooks_config.SUGGESTION_BAR_BUTTON_TESTID
|
|
162
|
+
}, `animation-dupe-${i}`))] })
|
|
163
|
+
})]
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
Object.defineProperty(exports, 'SuggestionButtonContainer', {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: function () {
|
|
171
|
+
return SuggestionButtonContainer;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useReducedMotionConfig } from "framer-motion";
|
|
4
|
+
import { SpiffyWidgets } from "@envive-ai/react-hooks/application/models";
|
|
5
|
+
import { SUGGESTION_BAR_BUTTON_TESTID } from "@envive-ai/react-hooks/config";
|
|
6
|
+
import { useIsSmallScreen } from "@envive-ai/react-hooks/hooks/IsSmallScreen";
|
|
7
|
+
import { useTrackComponentVisibleEvent } from "@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent";
|
|
8
|
+
import { SuggestionButton } from "@envive-ai/react-toolkit/SuggestionButton";
|
|
9
|
+
|
|
10
|
+
//#region src/util/useReducedMotionWithOverride.ts
|
|
11
|
+
const useReducedMotionWithOverride = () => {
|
|
12
|
+
const reducedMotionConfig = useReducedMotionConfig();
|
|
13
|
+
return useMemo(() => {
|
|
14
|
+
if (window?._spiffy?.reducedMotionOverride) return window?._spiffy?.reducedMotionOverride;
|
|
15
|
+
return reducedMotionConfig;
|
|
16
|
+
}, [reducedMotionConfig]);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/util/useHorizontalScrollAnimation.ts
|
|
21
|
+
function useHorizontalScrollAnimation({ scrollContainerRef, animationSpeed = "standard" }) {
|
|
22
|
+
const reducedMotion = useReducedMotionWithOverride();
|
|
23
|
+
const resumeTimeoutRef = useRef(null);
|
|
24
|
+
const scrollAnimationRef = useRef(null);
|
|
25
|
+
const pauseOnHover = true;
|
|
26
|
+
let PIXELS_PER_SECOND = 40;
|
|
27
|
+
switch (animationSpeed) {
|
|
28
|
+
case "standard":
|
|
29
|
+
PIXELS_PER_SECOND = 40;
|
|
30
|
+
break;
|
|
31
|
+
case "slow":
|
|
32
|
+
PIXELS_PER_SECOND = 25;
|
|
33
|
+
break;
|
|
34
|
+
case "none":
|
|
35
|
+
PIXELS_PER_SECOND = 0;
|
|
36
|
+
break;
|
|
37
|
+
default: PIXELS_PER_SECOND = 40;
|
|
38
|
+
}
|
|
39
|
+
const RESUME_DELAY_MS = 2e3;
|
|
40
|
+
const isAnimated = animationSpeed !== "none";
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!isAnimated || reducedMotion || !scrollContainerRef) return () => {};
|
|
43
|
+
const container = scrollContainerRef.current;
|
|
44
|
+
if (!container) return () => {};
|
|
45
|
+
if (container.scrollWidth <= container.clientWidth) return () => {};
|
|
46
|
+
let isPaused = false;
|
|
47
|
+
let lastTimestamp = null;
|
|
48
|
+
let accumulatedScroll = 0;
|
|
49
|
+
const step = (timestamp) => {
|
|
50
|
+
if (lastTimestamp === null) lastTimestamp = timestamp;
|
|
51
|
+
if (!isPaused) {
|
|
52
|
+
const delta = timestamp - lastTimestamp;
|
|
53
|
+
lastTimestamp = timestamp;
|
|
54
|
+
accumulatedScroll += PIXELS_PER_SECOND * (delta / 1e3);
|
|
55
|
+
const pixelsToScroll = Math.floor(accumulatedScroll);
|
|
56
|
+
if (pixelsToScroll > 0) {
|
|
57
|
+
container.scrollLeft += pixelsToScroll;
|
|
58
|
+
accumulatedScroll -= pixelsToScroll;
|
|
59
|
+
if (Math.ceil(container.scrollLeft) >= container.scrollWidth - container.clientWidth) {
|
|
60
|
+
container.scrollLeft = 0;
|
|
61
|
+
accumulatedScroll = 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
66
|
+
};
|
|
67
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
68
|
+
const pauseAnimation = () => {
|
|
69
|
+
isPaused = true;
|
|
70
|
+
if (resumeTimeoutRef.current) {
|
|
71
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
72
|
+
resumeTimeoutRef.current = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const scheduleResumeAnimation = () => {
|
|
76
|
+
resumeTimeoutRef.current = setTimeout(() => {
|
|
77
|
+
isPaused = false;
|
|
78
|
+
lastTimestamp = null;
|
|
79
|
+
}, RESUME_DELAY_MS);
|
|
80
|
+
};
|
|
81
|
+
container.addEventListener("mouseenter", pauseAnimation);
|
|
82
|
+
container.addEventListener("mouseleave", scheduleResumeAnimation);
|
|
83
|
+
container.addEventListener("touchstart", pauseAnimation);
|
|
84
|
+
container.addEventListener("touchend", scheduleResumeAnimation);
|
|
85
|
+
return function cleanup() {
|
|
86
|
+
if (scrollAnimationRef.current) cancelAnimationFrame(scrollAnimationRef.current);
|
|
87
|
+
container.removeEventListener("mouseenter", pauseAnimation);
|
|
88
|
+
container.removeEventListener("mouseleave", scheduleResumeAnimation);
|
|
89
|
+
container.removeEventListener("touchstart", pauseAnimation);
|
|
90
|
+
container.removeEventListener("touchend", scheduleResumeAnimation);
|
|
91
|
+
if (resumeTimeoutRef.current) clearTimeout(resumeTimeoutRef.current);
|
|
92
|
+
};
|
|
93
|
+
}, [
|
|
94
|
+
isAnimated,
|
|
95
|
+
reducedMotion,
|
|
96
|
+
PIXELS_PER_SECOND,
|
|
97
|
+
pauseOnHover,
|
|
98
|
+
scrollContainerRef
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/SuggestionButtonContainer/SuggestionButtonContainer.tsx
|
|
104
|
+
function ButtonContainer({ children }) {
|
|
105
|
+
return /* @__PURE__ */ jsx("div", {
|
|
106
|
+
className: "spiffy-tw-flex \n spiffy-tw-flex-row\n spiffy-tw-items-center\n spiffy-tw-space-x-2\n spiffy-tw-h-full\n spiffy-tw-pl-0",
|
|
107
|
+
children
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const SuggestionButtonContainer = ({ buttonVariation, hoverButtonVariation, buttonTexts, boldFirstButton = false, twoRowsOnMobile = false, animationSpeed = "none", buttonBorderRadius = "lg", scrollContainerRef, onButtonClick }) => {
|
|
111
|
+
const componentVisibleTriggerRef = useRef(null);
|
|
112
|
+
useHorizontalScrollAnimation({
|
|
113
|
+
scrollContainerRef,
|
|
114
|
+
animationSpeed
|
|
115
|
+
});
|
|
116
|
+
const isSmallScreen = useIsSmallScreen();
|
|
117
|
+
const isAnimated = animationSpeed !== "none";
|
|
118
|
+
useTrackComponentVisibleEvent(SpiffyWidgets.SuggestionBar, componentVisibleTriggerRef, { animated: isAnimated });
|
|
119
|
+
const visibleButtonsFirstRow = buttonTexts.slice(0, twoRowsOnMobile && isSmallScreen ? Math.ceil((buttonTexts.length + 1) / 2) : void 0);
|
|
120
|
+
const visibleButtonsSecondRow = buttonTexts.slice(Math.ceil((buttonTexts.length + 1) / 2), buttonTexts.length);
|
|
121
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
122
|
+
className: "spiffy-tw-overflow-x-scroll spiffy-tw-no-scrollbar spiffy-tw-w-full spiffy-tw-whitespace-nowrap",
|
|
123
|
+
children: [/* @__PURE__ */ jsxs(ButtonContainer, { children: [visibleButtonsFirstRow.map((suggestion, i) => /* @__PURE__ */ jsx(SuggestionButton, {
|
|
124
|
+
variant: buttonVariation,
|
|
125
|
+
hoverVariant: hoverButtonVariation,
|
|
126
|
+
isDisabled: false,
|
|
127
|
+
content: suggestion,
|
|
128
|
+
boldText: boldFirstButton && i === 0,
|
|
129
|
+
borderRadius: buttonBorderRadius,
|
|
130
|
+
onClick: () => onButtonClick(suggestion),
|
|
131
|
+
dataTestId: SUGGESTION_BAR_BUTTON_TESTID
|
|
132
|
+
}, i)), isAnimated && buttonTexts.map((suggestion, i) => /* @__PURE__ */ jsx(SuggestionButton, {
|
|
133
|
+
variant: buttonVariation,
|
|
134
|
+
hoverVariant: hoverButtonVariation,
|
|
135
|
+
isDisabled: false,
|
|
136
|
+
content: suggestion,
|
|
137
|
+
boldText: boldFirstButton && i === 0,
|
|
138
|
+
borderRadius: buttonBorderRadius,
|
|
139
|
+
onClick: () => onButtonClick(suggestion),
|
|
140
|
+
dataTestId: SUGGESTION_BAR_BUTTON_TESTID
|
|
141
|
+
}, `animation-dupe-${i}`))] }), twoRowsOnMobile && isSmallScreen && /* @__PURE__ */ jsx("div", {
|
|
142
|
+
className: "spiffy-tw-mt-1.5",
|
|
143
|
+
children: /* @__PURE__ */ jsxs(ButtonContainer, { children: [visibleButtonsSecondRow.map((suggestion, i) => /* @__PURE__ */ jsx(SuggestionButton, {
|
|
144
|
+
variant: buttonVariation,
|
|
145
|
+
hoverVariant: hoverButtonVariation,
|
|
146
|
+
isDisabled: false,
|
|
147
|
+
content: suggestion,
|
|
148
|
+
boldText: boldFirstButton && i === 0,
|
|
149
|
+
borderRadius: buttonBorderRadius,
|
|
150
|
+
onClick: () => onButtonClick(suggestion),
|
|
151
|
+
dataTestId: SUGGESTION_BAR_BUTTON_TESTID
|
|
152
|
+
}, i)), isAnimated && visibleButtonsSecondRow.map((suggestion, i) => /* @__PURE__ */ jsx(SuggestionButton, {
|
|
153
|
+
variant: buttonVariation,
|
|
154
|
+
hoverVariant: hoverButtonVariation,
|
|
155
|
+
isDisabled: false,
|
|
156
|
+
content: suggestion,
|
|
157
|
+
boldText: boldFirstButton && i === 0,
|
|
158
|
+
borderRadius: buttonBorderRadius,
|
|
159
|
+
onClick: () => onButtonClick(suggestion),
|
|
160
|
+
dataTestId: SUGGESTION_BAR_BUTTON_TESTID
|
|
161
|
+
}, `animation-dupe-${i}`))] })
|
|
162
|
+
})]
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
export { SuggestionButtonContainer as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
13
|
+
key = keys[i];
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
+
value: mod,
|
|
23
|
+
enumerable: true
|
|
24
|
+
}) : target, mod));
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
|
|
28
|
+
Object.defineProperty(exports, '__commonJS', {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
get: function () {
|
|
31
|
+
return __commonJS;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(exports, '__toESM', {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get: function () {
|
|
37
|
+
return __toESM;
|
|
38
|
+
}
|
|
39
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envive-ai/react-widgets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-arthur-2",
|
|
4
4
|
"description": "React widget library for Envive services.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"get-merchants": "tsx ./.storybook/getMerchants.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@envive-ai/react-hooks": "0.2.
|
|
36
|
-
"@envive-ai/react-toolkit": "
|
|
35
|
+
"@envive-ai/react-hooks": "0.2.8",
|
|
36
|
+
"@envive-ai/react-toolkit": "0.2.13-arthur-1",
|
|
37
37
|
"@tailwindcss/typography": "^0.5.15",
|
|
38
38
|
"classnames": "^2.5.1",
|
|
39
39
|
"framer-motion": "^12.23.24",
|
|
@@ -65,9 +65,21 @@
|
|
|
65
65
|
"vitest": "^3.2.4"
|
|
66
66
|
},
|
|
67
67
|
"exports": {
|
|
68
|
-
"
|
|
69
|
-
"import": "./dist/index.js",
|
|
70
|
-
"require": "./dist/index.cjs"
|
|
68
|
+
"./SearchResults": {
|
|
69
|
+
"import": "./dist/SearchResults/index.js",
|
|
70
|
+
"require": "./dist/SearchResults/index.cjs"
|
|
71
|
+
},
|
|
72
|
+
"./SearchZeroState": {
|
|
73
|
+
"import": "./dist/SearchZeroState/index.js",
|
|
74
|
+
"require": "./dist/SearchZeroState/index.cjs"
|
|
75
|
+
},
|
|
76
|
+
"./SuggestionBar": {
|
|
77
|
+
"import": "./dist/SuggestionBar/index.js",
|
|
78
|
+
"require": "./dist/SuggestionBar/index.cjs"
|
|
79
|
+
},
|
|
80
|
+
"./SuggestionButtonContainer": {
|
|
81
|
+
"import": "./dist/SuggestionButtonContainer/index.js",
|
|
82
|
+
"require": "./dist/SuggestionButtonContainer/index.cjs"
|
|
71
83
|
},
|
|
72
84
|
"./package.json": "./package.json"
|
|
73
85
|
},
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SearchIconVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
2
|
+
import AiSearchBold from "@envive-ai/react-icons/AiSearchBold";
|
|
3
|
+
import AiSearchThin from "@envive-ai/react-icons/AiSearchThin";
|
|
4
|
+
import { Typography } from "@envive-ai/react-toolkit/Typography";
|
|
5
|
+
import classNames from "classnames";
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
const SEARCH_ENTRYPOINT_BUTTON_TESTID = "spiffy-search-entrypoint-button";
|
|
9
|
+
|
|
10
|
+
interface SearchIconProps {
|
|
11
|
+
size?: number; // in px
|
|
12
|
+
variant?: SearchIconVariant;
|
|
13
|
+
color?: string;
|
|
14
|
+
onClick: () => void;
|
|
15
|
+
label?: string;
|
|
16
|
+
entryPointRef?: React.Ref<HTMLButtonElement>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SearchIcon: React.FC<SearchIconProps> = ({
|
|
20
|
+
size = 22,
|
|
21
|
+
variant = 'thin',
|
|
22
|
+
color = 'currentColor',
|
|
23
|
+
onClick,
|
|
24
|
+
label,
|
|
25
|
+
entryPointRef,
|
|
26
|
+
}) => {
|
|
27
|
+
const containerClasses = classNames([
|
|
28
|
+
'spiffy-global-search-input-container',
|
|
29
|
+
'spiffy-tw-relative',
|
|
30
|
+
'spiffy-tw-flex',
|
|
31
|
+
'spiffy-tw-items-center',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const iconStrokeWidth = variant === 'thin' ? 'inherit' : '2px';
|
|
35
|
+
|
|
36
|
+
const IconComponent = {
|
|
37
|
+
thin: AiSearchThin,
|
|
38
|
+
bold: AiSearchBold,
|
|
39
|
+
}[variant];
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
className={containerClasses}
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={onClick}
|
|
46
|
+
data-testid={SEARCH_ENTRYPOINT_BUTTON_TESTID}
|
|
47
|
+
ref={entryPointRef}
|
|
48
|
+
>
|
|
49
|
+
<IconComponent style={{ width: size, height: size }} fill={color} strokeWidth={iconStrokeWidth} />
|
|
50
|
+
{label && (
|
|
51
|
+
<Typography variant="body3" className="spiffy-global-search-text">
|
|
52
|
+
{label}
|
|
53
|
+
</Typography>
|
|
54
|
+
)}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { getOverlayPortalTarget } from './overlay/overlayHostLocator';
|
|
6
|
+
|
|
7
|
+
export interface SearchOverlayProps {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
dataTestId?: string;
|
|
11
|
+
role?: string;
|
|
12
|
+
ariaModal?: boolean;
|
|
13
|
+
ariaLabelledby?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
usingPortal?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SearchOverlay = React.forwardRef<HTMLDivElement, SearchOverlayProps>(
|
|
19
|
+
({ children, className, dataTestId, role, ariaModal, ariaLabelledby, id, usingPortal }, ref) => {
|
|
20
|
+
const overlayClasses = classNames(
|
|
21
|
+
'spiffy-search-overlay',
|
|
22
|
+
'spiffy-tw-fixed',
|
|
23
|
+
'spiffy-tw-top-[0]',
|
|
24
|
+
'spiffy-tw-left-[0]',
|
|
25
|
+
'spiffy-tw-h-[100vh]',
|
|
26
|
+
'spiffy-tw-w-full',
|
|
27
|
+
'spiffy-h-[calc(100%-1rem)]',
|
|
28
|
+
'spiffy-tw-px-[24px] spiffy-tw-pt-[16px] sm:spiffy-tw-px-[41px] sm:spiffy-tw-pt-[40px]',
|
|
29
|
+
'spiffy-tw-z-[2147483647]',
|
|
30
|
+
'spiffy-tw-overflow-y-auto',
|
|
31
|
+
'spiffy-tw-overflow-x-hidden',
|
|
32
|
+
className,
|
|
33
|
+
);
|
|
34
|
+
const overlayContentClasses = classNames(
|
|
35
|
+
'spiffy-search-overlay-content',
|
|
36
|
+
'spiffy-tw-flex spiffy-tw-flex-col',
|
|
37
|
+
'spiffy-tw-min-h-full',
|
|
38
|
+
className,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const node = (
|
|
42
|
+
<motion.div
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={overlayClasses}
|
|
45
|
+
initial={{ opacity: 0 }}
|
|
46
|
+
animate={{ opacity: 1 }}
|
|
47
|
+
exit={{ opacity: 0 }}
|
|
48
|
+
transition={{ duration: 0.2 }}
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
className={overlayContentClasses}
|
|
52
|
+
data-testid={dataTestId}
|
|
53
|
+
role={role}
|
|
54
|
+
aria-modal={ariaModal}
|
|
55
|
+
aria-labelledby={ariaLabelledby}
|
|
56
|
+
id={id}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</motion.div>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (!usingPortal) {
|
|
64
|
+
return node;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If we are usingPortal, we need to create a portal widget in the orgConfig
|
|
68
|
+
// so that we can properly identify the portal target. This is often used to
|
|
69
|
+
// get around z-index context hell because it will move the overlay wherever
|
|
70
|
+
// you need to insert it. In most cases, appending it as a child of body is
|
|
71
|
+
// sufficient. Typically this is not needed, but look into how it's done in
|
|
72
|
+
// Bandolier for more info.
|
|
73
|
+
try {
|
|
74
|
+
const target = getOverlayPortalTarget();
|
|
75
|
+
return createPortal(node, target);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// If host is missing (misconfig), fall back inline overlay
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
);
|