@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,141 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { SuggestionButton } from '@envive-ai/react-toolkit/SuggestionButton';
|
|
3
|
+
import { SpiffyWidgets } from '@envive-ai/react-hooks/application/models';
|
|
4
|
+
import { SUGGESTION_BAR_BUTTON_TESTID } from '@envive-ai/react-hooks/config';
|
|
5
|
+
import { useIsSmallScreen } from '@envive-ai/react-hooks/hooks/IsSmallScreen';
|
|
6
|
+
import { useTrackComponentVisibleEvent } from '@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent';
|
|
7
|
+
import { useHorizontalScrollAnimation } from 'src/util/useHorizontalScrollAnimation';
|
|
8
|
+
import { SuggestionButtonContainerProps } from './types';
|
|
9
|
+
|
|
10
|
+
// ButtonContainer props
|
|
11
|
+
interface ButtonContainerProps {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ButtonContainer is reused twice within SuggestionBarV2, so we declare it as a seperate functional component
|
|
16
|
+
function ButtonContainer({ children }: ButtonContainerProps) {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className="spiffy-tw-flex
|
|
20
|
+
spiffy-tw-flex-row
|
|
21
|
+
spiffy-tw-items-center
|
|
22
|
+
spiffy-tw-space-x-2
|
|
23
|
+
spiffy-tw-h-full
|
|
24
|
+
spiffy-tw-pl-0"
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const SuggestionButtonContainer: React.FC<SuggestionButtonContainerProps> = ({
|
|
33
|
+
buttonVariation,
|
|
34
|
+
hoverButtonVariation,
|
|
35
|
+
buttonTexts,
|
|
36
|
+
boldFirstButton = false,
|
|
37
|
+
twoRowsOnMobile = false,
|
|
38
|
+
animationSpeed = 'none',
|
|
39
|
+
buttonBorderRadius = 'lg',
|
|
40
|
+
scrollContainerRef,
|
|
41
|
+
onButtonClick,
|
|
42
|
+
}: Readonly<SuggestionButtonContainerProps>) => {
|
|
43
|
+
// Refs
|
|
44
|
+
const componentVisibleTriggerRef = useRef<HTMLDivElement>(null);
|
|
45
|
+
|
|
46
|
+
// Hooks
|
|
47
|
+
useHorizontalScrollAnimation({
|
|
48
|
+
scrollContainerRef,
|
|
49
|
+
animationSpeed,
|
|
50
|
+
})
|
|
51
|
+
const isSmallScreen = useIsSmallScreen();
|
|
52
|
+
|
|
53
|
+
const isAnimated = animationSpeed !== 'none';
|
|
54
|
+
|
|
55
|
+
// Track component visibility
|
|
56
|
+
useTrackComponentVisibleEvent(
|
|
57
|
+
SpiffyWidgets.SuggestionBar,
|
|
58
|
+
componentVisibleTriggerRef,
|
|
59
|
+
{ animated: isAnimated },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const visibleButtonsFirstRow = buttonTexts.slice(
|
|
63
|
+
0,
|
|
64
|
+
twoRowsOnMobile && isSmallScreen ? Math.ceil((buttonTexts.length + 1) / 2) : undefined,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const visibleButtonsSecondRow = buttonTexts.slice(
|
|
68
|
+
Math.ceil((buttonTexts.length + 1) / 2),
|
|
69
|
+
buttonTexts.length,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="spiffy-tw-overflow-x-scroll spiffy-tw-no-scrollbar spiffy-tw-w-full spiffy-tw-whitespace-nowrap">
|
|
74
|
+
<ButtonContainer>
|
|
75
|
+
{visibleButtonsFirstRow.map((suggestion, i) => (
|
|
76
|
+
<SuggestionButton
|
|
77
|
+
key={i}
|
|
78
|
+
variant={buttonVariation}
|
|
79
|
+
hoverVariant={hoverButtonVariation}
|
|
80
|
+
isDisabled={false}
|
|
81
|
+
content={suggestion}
|
|
82
|
+
boldText={boldFirstButton && i === 0}
|
|
83
|
+
borderRadius={buttonBorderRadius}
|
|
84
|
+
onClick={() => onButtonClick(suggestion)}
|
|
85
|
+
dataTestId={SUGGESTION_BAR_BUTTON_TESTID}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
|
|
89
|
+
{isAnimated &&
|
|
90
|
+
buttonTexts.map((suggestion, i) => (
|
|
91
|
+
<SuggestionButton
|
|
92
|
+
key={`animation-dupe-${i}`}
|
|
93
|
+
variant={buttonVariation}
|
|
94
|
+
hoverVariant={hoverButtonVariation}
|
|
95
|
+
isDisabled={false}
|
|
96
|
+
content={suggestion}
|
|
97
|
+
boldText={boldFirstButton && i === 0}
|
|
98
|
+
borderRadius={buttonBorderRadius}
|
|
99
|
+
onClick={() => onButtonClick(suggestion)}
|
|
100
|
+
dataTestId={SUGGESTION_BAR_BUTTON_TESTID}
|
|
101
|
+
/>
|
|
102
|
+
))}
|
|
103
|
+
</ButtonContainer>
|
|
104
|
+
{twoRowsOnMobile && isSmallScreen && (
|
|
105
|
+
<div className="spiffy-tw-mt-1.5">
|
|
106
|
+
<ButtonContainer>
|
|
107
|
+
{visibleButtonsSecondRow.map((suggestion, i) => (
|
|
108
|
+
<SuggestionButton
|
|
109
|
+
key={i}
|
|
110
|
+
variant={buttonVariation}
|
|
111
|
+
hoverVariant={hoverButtonVariation}
|
|
112
|
+
isDisabled={false}
|
|
113
|
+
content={suggestion}
|
|
114
|
+
boldText={boldFirstButton && i === 0}
|
|
115
|
+
borderRadius={buttonBorderRadius}
|
|
116
|
+
onClick={() => onButtonClick(suggestion)}
|
|
117
|
+
dataTestId={SUGGESTION_BAR_BUTTON_TESTID}
|
|
118
|
+
/>
|
|
119
|
+
))}
|
|
120
|
+
{isAnimated &&
|
|
121
|
+
visibleButtonsSecondRow.map((suggestion, i) => (
|
|
122
|
+
<SuggestionButton
|
|
123
|
+
key={`animation-dupe-${i}`}
|
|
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
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</ButtonContainer>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { SuggestionButtonContainer };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
2
|
+
import { TestProps } from "@envive-ai/react-hooks/types";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// SuggestionButtonContainer Props
|
|
6
|
+
export interface SuggestionButtonContainerProps extends TestProps {
|
|
7
|
+
buttonVariation: SuggestionButtonVariant;
|
|
8
|
+
hoverButtonVariation: SuggestionButtonVariant;
|
|
9
|
+
buttonTexts: string[];
|
|
10
|
+
onButtonClick: (text: string) => void;
|
|
11
|
+
scrollContainerRef: React.RefObject<HTMLDivElement>;
|
|
12
|
+
boldFirstButton?: boolean | undefined;
|
|
13
|
+
twoRowsOnMobile?: boolean | undefined;
|
|
14
|
+
animationSpeed?: 'standard' | 'slow' | 'none';
|
|
15
|
+
buttonBorderRadius?: 'sm' | 'md' | 'lg';
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { SearchZeroState } from 'src/SearchZeroState';
|
|
4
|
+
import { SearchEntryPointWidgetConfig, WidgetType } from '@envive-ai/react-hooks/contexts/types';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Widgets/Search/SearchZeroState',
|
|
8
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
searchInputVariant: 'standard',
|
|
15
|
+
searchBoxPlaceholder: '',
|
|
16
|
+
widgetConfigId: '',
|
|
17
|
+
type: WidgetType.SearchZeroStateEntryPoint,
|
|
18
|
+
searchZeroStateVariant: 'backgroundTertiary',
|
|
19
|
+
suggestionButtonConfig: {
|
|
20
|
+
variant: 'primary',
|
|
21
|
+
hoverVariant: 'primary',
|
|
22
|
+
borderRadius: 'sm',
|
|
23
|
+
},
|
|
24
|
+
layout: 'input',
|
|
25
|
+
initialIsOpen: false,
|
|
26
|
+
},
|
|
27
|
+
argTypes: {
|
|
28
|
+
initialIsOpen: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
},
|
|
31
|
+
searchZeroStateVariant: {
|
|
32
|
+
control: 'select',
|
|
33
|
+
options: ['backgroundTertiary', 'backgroundPrimary', 'backgroundSecondary', 'backgroundQuaternary'],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
render: (args: SearchEntryPointWidgetConfig & { initialIsOpen: boolean }) => (
|
|
37
|
+
<SearchZeroState widgetConfig={{ ...args }} initialIsOpen={args.initialIsOpen} />
|
|
38
|
+
),
|
|
39
|
+
} satisfies Meta;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { SuggestionBar, SuggestionBarLocationForMetrics } from 'src/SuggestionBar';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof SuggestionBar> = {
|
|
5
|
+
title: 'Common/SuggestionBar',
|
|
6
|
+
component: SuggestionBar,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
},
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {
|
|
12
|
+
animationSpeed: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: ['none', 'standard', 'slow'],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
render: (args) => (
|
|
18
|
+
<div style={{ width: '300px', height: '100px' }}>
|
|
19
|
+
<SuggestionBar {...args} />
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof SuggestionBar>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
id: 'suggestion-bar',
|
|
30
|
+
locationForMetrics: SuggestionBarLocationForMetrics.SUGGESTION_BAR_TOP,
|
|
31
|
+
buttonTexts: ['Button 1', 'Button 2', 'Button 3'],
|
|
32
|
+
buttonVariation: 'primary',
|
|
33
|
+
hoverButtonVariation: 'primary',
|
|
34
|
+
animationSpeed: 'none',
|
|
35
|
+
handleReply: () => {},
|
|
36
|
+
boldFirstButton: false,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Animated: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
...Default.args,
|
|
44
|
+
animationSpeed: 'standard',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useEffect, RefObject, useRef } from 'react';
|
|
2
|
+
import { useReducedMotionWithOverride } from 'src/util/useReducedMotionWithOverride';
|
|
3
|
+
|
|
4
|
+
// IMPORTANT: All refs passed to this hook must be mutable (not Readonly)
|
|
5
|
+
interface UseHorizontalScrollAnimationOptions {
|
|
6
|
+
scrollContainerRef: RefObject<HTMLDivElement>;
|
|
7
|
+
animationSpeed?: 'standard' | 'slow' | 'none';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// When using this hook, ensure that scrouuContainerRef is not undefined. It is allowed to
|
|
11
|
+
// prevent issued elsewhere in the codebase, but it should be dealt with whenever you use this hook.
|
|
12
|
+
export function useHorizontalScrollAnimation({
|
|
13
|
+
scrollContainerRef,
|
|
14
|
+
animationSpeed = 'standard',
|
|
15
|
+
}: UseHorizontalScrollAnimationOptions): void {
|
|
16
|
+
const reducedMotion = useReducedMotionWithOverride();
|
|
17
|
+
const resumeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
18
|
+
const scrollAnimationRef = useRef<number | null>(null);
|
|
19
|
+
|
|
20
|
+
const pauseOnHover = true; // This is unlikely to need to be configurable but I'm throwing it up here just in case.
|
|
21
|
+
|
|
22
|
+
// Animation constants: time-based
|
|
23
|
+
let PIXELS_PER_SECOND = 40;
|
|
24
|
+
switch (animationSpeed) {
|
|
25
|
+
case 'standard':
|
|
26
|
+
PIXELS_PER_SECOND = 40;
|
|
27
|
+
break;
|
|
28
|
+
case 'slow':
|
|
29
|
+
PIXELS_PER_SECOND = 25;
|
|
30
|
+
break;
|
|
31
|
+
case 'none':
|
|
32
|
+
PIXELS_PER_SECOND = 0;
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
PIXELS_PER_SECOND = 40;
|
|
36
|
+
}
|
|
37
|
+
const RESUME_DELAY_MS = 2000;
|
|
38
|
+
const isAnimated = animationSpeed !== 'none';
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!isAnimated || reducedMotion || !scrollContainerRef) {
|
|
42
|
+
return () => {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const container = scrollContainerRef.current;
|
|
46
|
+
if (!container) {
|
|
47
|
+
return () => {};
|
|
48
|
+
}
|
|
49
|
+
if (container.scrollWidth <= container.clientWidth) {
|
|
50
|
+
return () => {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let isPaused = false;
|
|
54
|
+
let lastTimestamp: number | null = null;
|
|
55
|
+
let accumulatedScroll = 0; // Accumulate fractional pixels
|
|
56
|
+
|
|
57
|
+
const step = (timestamp: number) => {
|
|
58
|
+
if (lastTimestamp === null) lastTimestamp = timestamp;
|
|
59
|
+
if (!isPaused) {
|
|
60
|
+
const delta = timestamp - lastTimestamp;
|
|
61
|
+
lastTimestamp = timestamp;
|
|
62
|
+
|
|
63
|
+
// Accumulate the scroll amount (including fractional pixels)
|
|
64
|
+
accumulatedScroll += PIXELS_PER_SECOND * (delta / 1000);
|
|
65
|
+
|
|
66
|
+
// Only apply whole pixel movements
|
|
67
|
+
const pixelsToScroll = Math.floor(accumulatedScroll);
|
|
68
|
+
if (pixelsToScroll > 0) {
|
|
69
|
+
container.scrollLeft += pixelsToScroll;
|
|
70
|
+
accumulatedScroll -= pixelsToScroll; // Keep the fractional remainder
|
|
71
|
+
|
|
72
|
+
if (Math.ceil(container.scrollLeft) >= container.scrollWidth - container.clientWidth) {
|
|
73
|
+
container.scrollLeft = 0; // Reset scroll to create a looping effect
|
|
74
|
+
accumulatedScroll = 0; // Reset accumulated scroll when looping
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Start animation
|
|
82
|
+
scrollAnimationRef.current = requestAnimationFrame(step);
|
|
83
|
+
|
|
84
|
+
// Pause/resume logic
|
|
85
|
+
const pauseAnimation = () => {
|
|
86
|
+
isPaused = true;
|
|
87
|
+
if (resumeTimeoutRef.current) {
|
|
88
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
89
|
+
resumeTimeoutRef.current = null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const scheduleResumeAnimation = () => {
|
|
93
|
+
resumeTimeoutRef.current = setTimeout(() => {
|
|
94
|
+
isPaused = false;
|
|
95
|
+
lastTimestamp = null; // Reset timestamp so delta is correct after pause
|
|
96
|
+
}, RESUME_DELAY_MS);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (pauseOnHover) {
|
|
100
|
+
container.addEventListener('mouseenter', pauseAnimation);
|
|
101
|
+
container.addEventListener('mouseleave', scheduleResumeAnimation);
|
|
102
|
+
container.addEventListener('touchstart', pauseAnimation);
|
|
103
|
+
container.addEventListener('touchend', scheduleResumeAnimation);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return function cleanup() {
|
|
107
|
+
if (scrollAnimationRef.current) {
|
|
108
|
+
cancelAnimationFrame(scrollAnimationRef.current);
|
|
109
|
+
}
|
|
110
|
+
if (pauseOnHover) {
|
|
111
|
+
container.removeEventListener('mouseenter', pauseAnimation);
|
|
112
|
+
container.removeEventListener('mouseleave', scheduleResumeAnimation);
|
|
113
|
+
container.removeEventListener('touchstart', pauseAnimation);
|
|
114
|
+
container.removeEventListener('touchend', scheduleResumeAnimation);
|
|
115
|
+
}
|
|
116
|
+
if (resumeTimeoutRef.current) {
|
|
117
|
+
clearTimeout(resumeTimeoutRef.current);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}, [isAnimated, reducedMotion, PIXELS_PER_SECOND, pauseOnHover, scrollContainerRef]);
|
|
121
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useReducedMotionConfig } from 'framer-motion';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
// TODO: Should this be moved?
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
_spiffy?: {
|
|
8
|
+
reducedMotionOverride?: boolean;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useReducedMotionWithOverride = () => {
|
|
14
|
+
const reducedMotionConfig = useReducedMotionConfig();
|
|
15
|
+
const reducedMotion = useMemo(() => {
|
|
16
|
+
if (window?._spiffy?.reducedMotionOverride) {
|
|
17
|
+
return window?._spiffy?.reducedMotionOverride;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return reducedMotionConfig;
|
|
21
|
+
}, [reducedMotionConfig]);
|
|
22
|
+
|
|
23
|
+
return reducedMotion;
|
|
24
|
+
};
|
|
File without changes
|
|
File without changes
|