paris 0.17.8 → 0.17.10
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/CHANGELOG.md +12 -0
- package/package.json +1 -2
- package/src/stories/menu/Menu.module.scss +9 -0
- package/src/stories/menu/Menu.tsx +7 -15
- package/src/stories/tilt/Tilt.module.scss +22 -1
- package/src/stories/tilt/Tilt.tsx +309 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# paris
|
|
2
2
|
|
|
3
|
+
## 0.17.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 24b86aa: fix(menu): Replace external Transition wrapper with built-in transition prop on MenuItems to fix TypeError crash during unmount
|
|
8
|
+
|
|
9
|
+
## 0.17.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7d24348: fix(tilt): Replace react-parallax-tilt with a custom functional component to fix TypeError crash during React 19 concurrent rendering unmount
|
|
14
|
+
|
|
3
15
|
## 0.17.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "paris",
|
|
3
3
|
"author": "Sanil Chawla <sanil@slingshot.fm> (https://sanil.co)",
|
|
4
4
|
"description": "Paris is Slingshot's React design system. It's a collection of reusable components, design tokens, and guidelines that help us build consistent, accessible, and performant user interfaces.",
|
|
5
|
-
"version": "0.17.
|
|
5
|
+
"version": "0.17.10",
|
|
6
6
|
"homepage": "https://paris.slingshot.fm",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"framer-motion": "^12.24.10",
|
|
78
78
|
"pte": "^0.5.0",
|
|
79
79
|
"react-hot-toast": "^2.4.1",
|
|
80
|
-
"react-parallax-tilt": "^1.7.315",
|
|
81
80
|
"react-tiny-popover": "^8.1.6",
|
|
82
81
|
"ts-deepmerge": "^6.0.3"
|
|
83
82
|
},
|
|
@@ -3,12 +3,11 @@ import type {
|
|
|
3
3
|
MenuProps, MenuItemsProps, MenuItemProps, MenuButtonProps,
|
|
4
4
|
} from '@headlessui/react';
|
|
5
5
|
import {
|
|
6
|
-
Menu as HeadlessMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem,
|
|
6
|
+
Menu as HeadlessMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem,
|
|
7
7
|
} from '@headlessui/react';
|
|
8
8
|
import { clsx } from 'clsx';
|
|
9
9
|
|
|
10
10
|
import styles from './Menu.module.scss';
|
|
11
|
-
import dropdownStyles from '../utility/Dropdown.module.scss';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Wraps the `HeadlessMenu` component from `@headlessui/react` to provide a styled dropdown menu.
|
|
@@ -56,20 +55,13 @@ export const MenuItems: FC<MenuItemsProps<React.ElementType> & {
|
|
|
56
55
|
}> = ({
|
|
57
56
|
className, children, position = 'left', ...props
|
|
58
57
|
}) => (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
className={
|
|
62
|
-
|
|
63
|
-
enterFrom={dropdownStyles.enterFrom}
|
|
64
|
-
enterTo={dropdownStyles.enterTo}
|
|
65
|
-
leave={dropdownStyles.transition}
|
|
66
|
-
leaveFrom={dropdownStyles.leaveFrom}
|
|
67
|
-
leaveTo={dropdownStyles.leaveTo}
|
|
58
|
+
<HMenuItems
|
|
59
|
+
transition
|
|
60
|
+
className={clsx(styles.menuItems, position === 'left' && styles.leftPosition, position === 'right' && styles.rightPosition, className)}
|
|
61
|
+
{...props}
|
|
68
62
|
>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
</HMenuItems>
|
|
72
|
-
</Transition>
|
|
63
|
+
{children}
|
|
64
|
+
</HMenuItems>
|
|
73
65
|
);
|
|
74
66
|
|
|
75
67
|
/**
|
|
@@ -1 +1,22 @@
|
|
|
1
|
-
.container {
|
|
1
|
+
.container {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.glareWrapper {
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 0;
|
|
8
|
+
left: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
-webkit-mask-image: -webkit-radial-gradient(white, black);
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.glare {
|
|
17
|
+
position: absolute;
|
|
18
|
+
top: 50%;
|
|
19
|
+
left: 50%;
|
|
20
|
+
transform-origin: 0% 0%;
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
}
|
|
@@ -1,26 +1,116 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import type {
|
|
10
|
+
CSSProperties,
|
|
11
|
+
FC,
|
|
12
|
+
MouseEvent as ReactMouseEvent,
|
|
13
|
+
ReactNode,
|
|
14
|
+
TouchEvent as ReactTouchEvent,
|
|
15
|
+
} from 'react';
|
|
16
|
+
import { clsx } from 'clsx';
|
|
5
17
|
import styles from './Tilt.module.scss';
|
|
6
18
|
|
|
19
|
+
type GlarePosition = 'top' | 'right' | 'bottom' | 'left' | 'all';
|
|
20
|
+
|
|
21
|
+
export type OnMoveParams = {
|
|
22
|
+
tiltAngleX: number;
|
|
23
|
+
tiltAngleY: number;
|
|
24
|
+
tiltAngleXPercentage: number;
|
|
25
|
+
tiltAngleYPercentage: number;
|
|
26
|
+
glareAngle: number;
|
|
27
|
+
glareOpacity: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
7
30
|
export type TiltProps = {
|
|
8
|
-
/**
|
|
9
|
-
* Disables the tilt effect.
|
|
10
|
-
* @default false
|
|
11
|
-
*/
|
|
31
|
+
/** Disables the tilt effect. @default false */
|
|
12
32
|
disableTilt?: boolean;
|
|
13
|
-
/** The contents of the Tilt. */
|
|
14
33
|
children?: ReactNode | ReactNode[];
|
|
15
|
-
/** The class names to apply to this element. */
|
|
16
34
|
className?: string;
|
|
17
|
-
|
|
35
|
+
style?: CSSProperties;
|
|
36
|
+
/** Scale on hover (1.05 = 105%). @default 1.05 */
|
|
37
|
+
scale?: number;
|
|
38
|
+
/** Max tilt on X axis in degrees. @default 12.5 */
|
|
39
|
+
tiltMaxAngleX?: number;
|
|
40
|
+
/** Max tilt on Y axis in degrees. @default 12.5 */
|
|
41
|
+
tiltMaxAngleY?: number;
|
|
42
|
+
/** Manual tilt on X axis in degrees. Overrides mouse input when non-null. */
|
|
43
|
+
tiltAngleXManual?: number | null;
|
|
44
|
+
/** Manual tilt on Y axis in degrees. Overrides mouse input when non-null. */
|
|
45
|
+
tiltAngleYManual?: number | null;
|
|
46
|
+
/** CSS perspective distance in px. @default 1000 */
|
|
47
|
+
perspective?: number;
|
|
48
|
+
/** Transition speed in ms for enter/leave. @default 400 */
|
|
49
|
+
transitionSpeed?: number;
|
|
50
|
+
/** Transition easing function. @default 'cubic-bezier(.03,.98,.52,.99)' */
|
|
51
|
+
transitionEasing?: string;
|
|
52
|
+
/** Reset tilt on mouse leave. @default true */
|
|
53
|
+
reset?: boolean;
|
|
54
|
+
/** Enable glare overlay. @default true */
|
|
55
|
+
glareEnable?: boolean;
|
|
56
|
+
/** Max glare opacity (0-1). @default 0.5 */
|
|
57
|
+
glareMaxOpacity?: number;
|
|
58
|
+
/** Glare gradient color. @default '#ffffff' */
|
|
59
|
+
glareColor?: string;
|
|
60
|
+
/** Glare gradient origin position. @default 'bottom' */
|
|
61
|
+
glarePosition?: GlarePosition;
|
|
62
|
+
/** Reverse glare direction. @default false */
|
|
63
|
+
glareReverse?: boolean;
|
|
64
|
+
/** CSS border-radius for glare wrapper. Derived from style.borderRadius by default. */
|
|
65
|
+
glareBorderRadius?: string;
|
|
66
|
+
onEnter?: (params: { event: ReactMouseEvent | ReactTouchEvent }) => void;
|
|
67
|
+
onLeave?: (params: { event: ReactMouseEvent | ReactTouchEvent }) => void;
|
|
68
|
+
onMove?: (params: OnMoveParams) => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type TiltState = {
|
|
72
|
+
tiltAngleX: number;
|
|
73
|
+
tiltAngleY: number;
|
|
74
|
+
currentScale: number;
|
|
75
|
+
glareAngle: number;
|
|
76
|
+
glareOpacity: number;
|
|
77
|
+
transitioning: boolean;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const INITIAL_STATE: TiltState = {
|
|
81
|
+
tiltAngleX: 0,
|
|
82
|
+
tiltAngleY: 0,
|
|
83
|
+
currentScale: 1,
|
|
84
|
+
glareAngle: 0,
|
|
85
|
+
glareOpacity: 0,
|
|
86
|
+
transitioning: false,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
90
|
+
|
|
91
|
+
function computeGlareOpacity(
|
|
92
|
+
xPct: number,
|
|
93
|
+
yPct: number,
|
|
94
|
+
position: GlarePosition,
|
|
95
|
+
reverse: boolean,
|
|
96
|
+
maxOpacity: number,
|
|
97
|
+
): number {
|
|
98
|
+
const g = reverse ? -1 : 1;
|
|
99
|
+
let raw = 0;
|
|
100
|
+
switch (position) {
|
|
101
|
+
case 'top': raw = -xPct * g; break;
|
|
102
|
+
case 'right': raw = yPct * g; break;
|
|
103
|
+
case 'left': raw = -yPct * g; break;
|
|
104
|
+
case 'all': raw = Math.hypot(xPct, yPct); break;
|
|
105
|
+
case 'bottom':
|
|
106
|
+
default: raw = xPct * g; break;
|
|
107
|
+
}
|
|
108
|
+
return clamp(raw, 0, 100) * maxOpacity / 100;
|
|
109
|
+
}
|
|
18
110
|
|
|
19
111
|
/**
|
|
20
112
|
* Tilt components allow you to add a parallax tilt effect to any component.
|
|
21
113
|
*
|
|
22
|
-
* Based on [react-parallax-tilt](https://github.com/mkosir/react-parallax-tilt), customized with our preferred defaults.
|
|
23
|
-
*
|
|
24
114
|
* > Card components include a Tilt component by default, by setting the `tilt` prop to `true`. You can override any of the underlying Tilt props by passing them to the Card component's `overrides.tilt` prop.
|
|
25
115
|
*
|
|
26
116
|
* <hr />
|
|
@@ -37,29 +127,215 @@ export const Tilt: FC<TiltProps> = ({
|
|
|
37
127
|
scale = 1.05,
|
|
38
128
|
tiltMaxAngleX = 12.5,
|
|
39
129
|
tiltMaxAngleY = 12.5,
|
|
40
|
-
glareEnable = true,
|
|
41
|
-
glareMaxOpacity = 0.5,
|
|
42
|
-
style = {},
|
|
43
130
|
tiltAngleXManual = null,
|
|
44
131
|
tiltAngleYManual = null,
|
|
45
132
|
perspective = 1000,
|
|
133
|
+
transitionSpeed = 400,
|
|
134
|
+
transitionEasing = 'cubic-bezier(.03,.98,.52,.99)',
|
|
135
|
+
reset = true,
|
|
136
|
+
glareEnable = true,
|
|
137
|
+
glareMaxOpacity = 0.5,
|
|
138
|
+
glareColor = '#ffffff',
|
|
139
|
+
glarePosition = 'bottom',
|
|
140
|
+
glareReverse = false,
|
|
141
|
+
glareBorderRadius,
|
|
142
|
+
style = {},
|
|
143
|
+
className,
|
|
46
144
|
children,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
145
|
+
onEnter,
|
|
146
|
+
onLeave,
|
|
147
|
+
onMove,
|
|
148
|
+
}) => {
|
|
149
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
150
|
+
const rafID = useRef<number | null>(null);
|
|
151
|
+
const rectRef = useRef<DOMRect | null>(null);
|
|
152
|
+
const pendingRef = useRef<TiltState | null>(null);
|
|
153
|
+
|
|
154
|
+
const [tilt, setTilt] = useState<TiltState>(INITIAL_STATE);
|
|
155
|
+
|
|
156
|
+
const effectiveScale = disableTilt ? 1 : scale;
|
|
157
|
+
const effectiveMaxX = disableTilt ? 0 : tiltMaxAngleX;
|
|
158
|
+
const effectiveMaxY = disableTilt ? 0 : tiltMaxAngleY;
|
|
159
|
+
const effectiveGlare = !disableTilt && glareEnable;
|
|
160
|
+
const effectiveGlareMax = disableTilt ? 0 : glareMaxOpacity;
|
|
161
|
+
const effectiveManualX = disableTilt ? 0 : tiltAngleXManual;
|
|
162
|
+
const effectiveManualY = disableTilt ? 0 : tiltAngleYManual;
|
|
163
|
+
|
|
164
|
+
const computedGlareBorderRadius = glareBorderRadius ?? `calc(${style?.borderRadius || '1px'} - 1px)`;
|
|
165
|
+
const isManual = effectiveManualX !== null || effectiveManualY !== null;
|
|
166
|
+
|
|
167
|
+
// Cancel any pending rAF on unmount
|
|
168
|
+
useEffect(() => () => {
|
|
169
|
+
if (rafID.current !== null) cancelAnimationFrame(rafID.current);
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
const flushToState = useCallback(() => {
|
|
173
|
+
if (!pendingRef.current) return;
|
|
174
|
+
const next = pendingRef.current;
|
|
175
|
+
pendingRef.current = null;
|
|
176
|
+
setTilt(next);
|
|
177
|
+
}, []);
|
|
178
|
+
|
|
179
|
+
const scheduleUpdate = useCallback((next: TiltState) => {
|
|
180
|
+
pendingRef.current = next;
|
|
181
|
+
if (rafID.current !== null) cancelAnimationFrame(rafID.current);
|
|
182
|
+
rafID.current = requestAnimationFrame(flushToState);
|
|
183
|
+
}, [flushToState]);
|
|
184
|
+
|
|
185
|
+
const computeTilt = useCallback((pageX: number, pageY: number): TiltState => {
|
|
186
|
+
const rect = rectRef.current;
|
|
187
|
+
if (!rect) return { ...INITIAL_STATE, currentScale: effectiveScale };
|
|
188
|
+
|
|
189
|
+
const xPct = clamp(((pageX - rect.left) / rect.width) * 200 - 100, -100, 100);
|
|
190
|
+
const yPct = clamp(((pageY - rect.top) / rect.height) * 200 - 100, -100, 100);
|
|
191
|
+
|
|
192
|
+
const angleX = (effectiveManualX !== null) ? effectiveManualX : (yPct * effectiveMaxX / 100);
|
|
193
|
+
const angleY = (effectiveManualY !== null) ? effectiveManualY : (xPct * effectiveMaxY / 100 * -1);
|
|
194
|
+
|
|
195
|
+
const glareAngle = xPct ? Math.atan2(yPct, -xPct) * (180 / Math.PI) : 0;
|
|
196
|
+
const glareOpacity = effectiveGlare
|
|
197
|
+
? computeGlareOpacity(xPct, yPct, glarePosition, glareReverse, effectiveGlareMax)
|
|
198
|
+
: 0;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
tiltAngleX: clamp(angleX, -90, 90),
|
|
202
|
+
tiltAngleY: clamp(angleY, -90, 90),
|
|
203
|
+
currentScale: effectiveScale,
|
|
204
|
+
glareAngle,
|
|
205
|
+
glareOpacity,
|
|
206
|
+
transitioning: false,
|
|
207
|
+
};
|
|
208
|
+
}, [effectiveScale, effectiveMaxX, effectiveMaxY, effectiveManualX, effectiveManualY, effectiveGlare, effectiveGlareMax, glarePosition, glareReverse]);
|
|
209
|
+
|
|
210
|
+
const handleMouseEnter = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {
|
|
211
|
+
if (!containerRef.current) return;
|
|
212
|
+
rectRef.current = containerRef.current.getBoundingClientRect();
|
|
213
|
+
|
|
214
|
+
const next = computeTilt(e.pageX, e.pageY);
|
|
215
|
+
setTilt({ ...next, transitioning: true });
|
|
216
|
+
onEnter?.({ event: e });
|
|
217
|
+
}, [computeTilt, onEnter]);
|
|
218
|
+
|
|
219
|
+
const handleMouseMove = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {
|
|
220
|
+
if (!containerRef.current || !rectRef.current) return;
|
|
221
|
+
if (effectiveManualX !== null || effectiveManualY !== null) return;
|
|
222
|
+
|
|
223
|
+
const next = computeTilt(e.pageX, e.pageY);
|
|
224
|
+
scheduleUpdate(next);
|
|
225
|
+
|
|
226
|
+
if (onMove) {
|
|
227
|
+
onMove({
|
|
228
|
+
tiltAngleX: next.tiltAngleX,
|
|
229
|
+
tiltAngleY: next.tiltAngleY,
|
|
230
|
+
tiltAngleXPercentage: effectiveMaxX ? (next.tiltAngleX / effectiveMaxX) * 100 : 0,
|
|
231
|
+
tiltAngleYPercentage: effectiveMaxY ? (next.tiltAngleY / effectiveMaxY) * 100 : 0,
|
|
232
|
+
glareAngle: next.glareAngle,
|
|
233
|
+
glareOpacity: next.glareOpacity,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}, [computeTilt, scheduleUpdate, onMove, effectiveManualX, effectiveManualY, effectiveMaxX, effectiveMaxY]);
|
|
237
|
+
|
|
238
|
+
const handleMouseLeave = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {
|
|
239
|
+
if (!containerRef.current) return;
|
|
240
|
+
rectRef.current = null;
|
|
241
|
+
|
|
242
|
+
if (reset) {
|
|
243
|
+
setTilt({ ...INITIAL_STATE, transitioning: true });
|
|
244
|
+
}
|
|
245
|
+
onLeave?.({ event: e });
|
|
246
|
+
}, [reset, onLeave]);
|
|
247
|
+
|
|
248
|
+
const handleTouchStart = useCallback((e: ReactTouchEvent<HTMLDivElement>) => {
|
|
249
|
+
if (!containerRef.current || !e.touches[0]) return;
|
|
250
|
+
rectRef.current = containerRef.current.getBoundingClientRect();
|
|
251
|
+
|
|
252
|
+
const touch = e.touches[0];
|
|
253
|
+
const next = computeTilt(touch.pageX, touch.pageY);
|
|
254
|
+
setTilt({ ...next, transitioning: true });
|
|
255
|
+
onEnter?.({ event: e });
|
|
256
|
+
}, [computeTilt, onEnter]);
|
|
257
|
+
|
|
258
|
+
const handleTouchMove = useCallback((e: ReactTouchEvent<HTMLDivElement>) => {
|
|
259
|
+
if (!containerRef.current || !rectRef.current || !e.touches[0]) return;
|
|
260
|
+
if (effectiveManualX !== null || effectiveManualY !== null) return;
|
|
261
|
+
|
|
262
|
+
const touch = e.touches[0];
|
|
263
|
+
const next = computeTilt(touch.pageX, touch.pageY);
|
|
264
|
+
scheduleUpdate(next);
|
|
265
|
+
}, [computeTilt, scheduleUpdate, effectiveManualX, effectiveManualY]);
|
|
266
|
+
|
|
267
|
+
const handleTouchEnd = useCallback((e: ReactTouchEvent<HTMLDivElement>) => {
|
|
268
|
+
if (!containerRef.current) return;
|
|
269
|
+
rectRef.current = null;
|
|
270
|
+
|
|
271
|
+
if (reset) {
|
|
272
|
+
setTilt({ ...INITIAL_STATE, transitioning: true });
|
|
273
|
+
}
|
|
274
|
+
onLeave?.({ event: e });
|
|
275
|
+
}, [reset, onLeave]);
|
|
276
|
+
|
|
277
|
+
// Compute glare element size (diagonal of container)
|
|
278
|
+
const [glareSize, setGlareSize] = useState(0);
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (!effectiveGlare || !containerRef.current) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
const el = containerRef.current;
|
|
284
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
285
|
+
if (!entry) return;
|
|
286
|
+
const { width, height } = entry.contentRect;
|
|
287
|
+
setGlareSize(Math.sqrt(width ** 2 + height ** 2));
|
|
288
|
+
});
|
|
289
|
+
observer.observe(el);
|
|
290
|
+
return () => observer.disconnect();
|
|
291
|
+
}, [effectiveGlare]);
|
|
292
|
+
|
|
293
|
+
// Apply manual angle overrides at render time
|
|
294
|
+
const finalAngleX = isManual ? (effectiveManualX ?? 0) : tilt.tiltAngleX;
|
|
295
|
+
const finalAngleY = isManual ? (effectiveManualY ?? 0) : tilt.tiltAngleY;
|
|
296
|
+
const finalScale = tilt.currentScale;
|
|
297
|
+
|
|
298
|
+
const transform = `perspective(${perspective}px) rotateX(${finalAngleX}deg) rotateY(${finalAngleY}deg) scale3d(${finalScale},${finalScale},${finalScale})`;
|
|
299
|
+
|
|
300
|
+
const transition = tilt.transitioning || isManual
|
|
301
|
+
? `transform ${transitionSpeed}ms ${transitionEasing}`
|
|
302
|
+
: undefined;
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<div
|
|
306
|
+
ref={containerRef}
|
|
307
|
+
onMouseEnter={handleMouseEnter}
|
|
308
|
+
onMouseMove={handleMouseMove}
|
|
309
|
+
onMouseLeave={handleMouseLeave}
|
|
310
|
+
onTouchStart={handleTouchStart}
|
|
311
|
+
onTouchMove={handleTouchMove}
|
|
312
|
+
onTouchEnd={handleTouchEnd}
|
|
313
|
+
className={clsx(styles.container, className)}
|
|
314
|
+
style={{
|
|
315
|
+
...style,
|
|
316
|
+
transform,
|
|
317
|
+
transition,
|
|
318
|
+
willChange: tilt.transitioning || finalScale !== 1 || finalAngleX !== 0 || finalAngleY !== 0 ? 'transform' : undefined,
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
{children}
|
|
322
|
+
{effectiveGlare && (
|
|
323
|
+
<div
|
|
324
|
+
className={styles.glareWrapper}
|
|
325
|
+
style={{ borderRadius: computedGlareBorderRadius }}
|
|
326
|
+
>
|
|
327
|
+
<div
|
|
328
|
+
className={styles.glare}
|
|
329
|
+
style={{
|
|
330
|
+
width: glareSize,
|
|
331
|
+
height: glareSize,
|
|
332
|
+
transform: `rotate(${tilt.glareAngle}deg) translate(-50%, -50%)`,
|
|
333
|
+
opacity: tilt.glareOpacity,
|
|
334
|
+
background: `linear-gradient(0deg, rgba(255,255,255,0) 0%, ${glareColor} 100%)`,
|
|
335
|
+
}}
|
|
336
|
+
/>
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
};
|