Skip to content

Commit cb250ff

Browse files
committed
feat: use named export instead of default export
BREAKING CHANGE: The component is exported with a named export instead of a default export.
1 parent 81a04a3 commit cb250ff

File tree

6 files changed

+71
-79
lines changed

6 files changed

+71
-79
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ npm install react-layered-image
2222
import * as React from "react";
2323
import { render } from "react-dom";
2424

25-
import LayeredImage from "react-layered-image";
25+
import { LayeredImage } from "react-layered-image";
2626

2727
const style = {
2828
position: "absolute",

example/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from "react"
22
import { render } from "react-dom"
33

4-
import LayeredImage from "../lib"
4+
import { LayeredImage } from "../lib"
55

66
const style: React.CSSProperties = {
77
position: "absolute",

lib/LayeredImage.tsx

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from "react"
22
import { createRef, useEffect, useMemo, useRef, useState } from "react"
33

4-
import { clamp, isFunction, isSafariDesktop } from "./utils"
4+
import { applyStyles, clamp, isFunction } from "./utils"
55

66
interface Size {
77
width: number
@@ -18,7 +18,7 @@ enum Interaction {
1818
interface ILayeredImageStyles {
1919
root?: React.CSSProperties
2020
container?: React.CSSProperties
21-
layers?: React.CSSProperties
21+
stack?: React.CSSProperties
2222
layer?: React.CSSProperties | ((index: number) => React.CSSProperties)
2323
light?: React.CSSProperties
2424
shadow?: React.CSSProperties
@@ -57,10 +57,10 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
5757
const elementsRef = useRef({
5858
root: createRef<HTMLDivElement>(),
5959
container: createRef<HTMLDivElement>(),
60+
layers: layers.map(() => createRef<HTMLDivElement>()),
6061
shadow: createRef<HTMLDivElement>(),
6162
light: createRef<HTMLDivElement>(),
6263
})
63-
const layerRef = useRef(layers.map(() => createRef<HTMLDivElement>()))
6464

6565
const defaultStyles = useMemo<ILayeredImageStyles>(
6666
() => getDefaultStyles(transitionDuration, lightColor, shadowColor),
@@ -79,10 +79,10 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
7979
...defaultStyles.container,
8080
...staticStyles.container,
8181
},
82-
layers: {
82+
stack: {
8383
borderRadius,
84-
...defaultStyles.layers,
85-
...staticStyles.layers,
84+
...defaultStyles.stack,
85+
...staticStyles.stack,
8686
},
8787
layer: {
8888
transitionTimingFunction,
@@ -107,11 +107,12 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
107107
)
108108

109109
const getDimensions = () => {
110+
const containerRef = elementsRef.current.container
110111
// prettier-ignore
111112
const width =
112-
elementsRef.current.container.current.offsetWidth ||
113-
elementsRef.current.container.current.clientWidth ||
114-
elementsRef.current.container.current.scrollWidth;
113+
containerRef.current.offsetWidth ||
114+
containerRef.current.clientWidth ||
115+
containerRef.current.scrollWidth;
115116
const height = Math.round(width / aspectRatio)
116117

117118
return { width, height }
@@ -138,12 +139,12 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
138139
document.scrollingElement.scrollLeft ||
139140
window.scrollX ||
140141
window.pageXOffset
141-
const containerClientRect = elementsRef.current.container.current.getBoundingClientRect()
142+
const containerRect = elementsRef.current.container.current.getBoundingClientRect()
142143

143-
const offsetX = (pageX - containerClientRect.left - bodyScrollLeft) / width
144-
const offsetY = (pageY - containerClientRect.top - bodyScrollTop) / height
145-
const containerCenterX = pageX - containerClientRect.left - bodyScrollLeft - width / 2
146-
const containerCenterY = pageY - containerClientRect.top - bodyScrollTop - height / 2
144+
const offsetX = (pageX - containerRect.left - bodyScrollLeft) / width
145+
const offsetY = (pageY - containerRect.top - bodyScrollTop) / height
146+
const containerCenterX = pageX - containerRect.left - bodyScrollLeft - width / 2
147+
const containerCenterY = pageY - containerRect.top - bodyScrollTop - height / 2
147148

148149
const containerRotationX = ((offsetY - containerCenterY) / (height / 2)) * 8
149150
const containerRotationY = ((containerCenterX - offsetX) / (width / 2)) * 8
@@ -152,8 +153,13 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
152153
const lightAngle = (Math.atan2(containerCenterY, containerCenterX) * 180) / Math.PI - 90
153154

154155
const computedStyles: ILayeredImageStyles = {
155-
[Interaction.None]: { ...defaultStyles },
156-
[Interaction.Resize]: { root: { height: `${height}px`, transform: `perspective(${width * 3}px)` } },
156+
[Interaction.None]: defaultStyles,
157+
[Interaction.Resize]: {
158+
root: {
159+
height: `${height}px`,
160+
transform: `perspective(${width * 3}px)`,
161+
},
162+
},
157163
[Interaction.Hover]: {
158164
container: {
159165
transform: `rotateX(${-clamp(containerRotationX, -8, 8)}deg)
@@ -176,10 +182,10 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
176182
},
177183
[Interaction.Active]: {
178184
container: {
185+
transitionDuration: "0.075s",
179186
transform: `rotateX(${containerRotationX / 1.4}deg)
180187
rotateY(${containerRotationY / 1.4}deg)
181188
scale(1)`,
182-
transitionDuration: "0.075s",
183189
},
184190
layer: (index: number) => ({
185191
transform: `translateX(${-layerTranslationX * index}px)
@@ -200,17 +206,15 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
200206
event.preventDefault()
201207
}
202208

203-
Object.keys(computedStyles).forEach((element) => {
204-
const styles = computedStyles[element]
205-
209+
for (const [element, styles] of Object.entries(computedStyles)) {
206210
if (element === "layer") {
207211
layers.forEach((_, index) =>
208-
applyStyles(layerRef.current[index].current, isFunction(styles) ? styles(index) : styles),
212+
applyStyles(elementsRef.current.layers[index].current, isFunction(styles) ? styles(index) : styles),
209213
)
210214
} else {
211215
applyStyles(elementsRef.current[element].current, styles)
212216
}
213-
})
217+
}
214218

215219
setSize({ width, height })
216220
setInteraction(_interaction)
@@ -230,9 +234,9 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
230234

231235
const handleInteractionEnd = () => computeStyles(Interaction.None)
232236

233-
const handleWindowResize = () => computeStyles(Interaction.Resize)
234-
235237
useEffect(() => {
238+
const handleWindowResize = () => computeStyles(Interaction.Resize)
239+
236240
layers.forEach((layer) => {
237241
const image = new Image()
238242

@@ -268,15 +272,15 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
268272
>
269273
<div style={styles.container} ref={elementsRef.current.container}>
270274
<div style={styles.shadow} ref={elementsRef.current.shadow} />
271-
<div style={styles.layers}>
275+
<div style={styles.stack}>
272276
{layers.map((src, index) => (
273277
<div
274278
style={{
275279
...styles.layer,
276280
backgroundImage: `url(${src})`,
277281
opacity: loaded === layers.length ? 1 : 0,
278282
}}
279-
ref={layerRef.current[index]}
283+
ref={elementsRef.current.layers[index]}
280284
key={index}
281285
/>
282286
))}
@@ -288,8 +292,34 @@ export const LayeredImage: React.FC<ILayeredImageProps> = ({
288292
}
289293
LayeredImage.displayName = "LayeredImage"
290294

291-
export default LayeredImage
295+
/*
296+
* Initial styles in resting state.
297+
*/
298+
const getDefaultStyles = (
299+
transitionDuration: ILayeredImageProps["transitionDuration"],
300+
lightColor: ILayeredImageProps["lightColor"],
301+
shadowColor: ILayeredImageProps["shadowColor"],
302+
): ILayeredImageStyles => ({
303+
container: {
304+
transform: "none",
305+
transitionDuration: `${transitionDuration}s`,
306+
},
307+
layer: {
308+
transform: "none",
309+
transitionDuration: `${transitionDuration}s, 500ms`,
310+
},
311+
light: {
312+
backgroundImage: `linear-gradient(180deg, ${lightColor} 0%, transparent 80%)`,
313+
},
314+
shadow: {
315+
boxShadow: `0 10px 30px ${shadowColor}, 0 6px 10px ${shadowColor}`,
316+
transitionDuration: `${transitionDuration}s`,
317+
},
318+
})
292319

320+
/*
321+
* Static styles that never change.
322+
*/
293323
const staticStyles: ILayeredImageStyles = {
294324
root: {
295325
position: "relative",
@@ -305,7 +335,7 @@ const staticStyles: ILayeredImageStyles = {
305335
transitionProperty: "transform",
306336
transformStyle: "preserve-3d",
307337
},
308-
layers: {
338+
stack: {
309339
position: "absolute",
310340
width: "100%",
311341
height: "100%",
@@ -337,39 +367,3 @@ const staticStyles: ILayeredImageStyles = {
337367
transform: "translateZ(-10px) scale(0.95)",
338368
},
339369
}
340-
341-
const getDefaultStyles = (
342-
transitionDuration: ILayeredImageProps["transitionDuration"],
343-
lightColor: ILayeredImageProps["lightColor"],
344-
shadowColor: ILayeredImageProps["shadowColor"],
345-
): ILayeredImageStyles => ({
346-
container: {
347-
transform: "none",
348-
transitionDuration: `${transitionDuration}s`,
349-
},
350-
layer: {
351-
transform: "none",
352-
transitionDuration: `${transitionDuration}s, 500ms`,
353-
},
354-
light: {
355-
backgroundImage: `linear-gradient(180deg, ${lightColor} 0%, transparent 80%)`,
356-
},
357-
shadow: {
358-
boxShadow: `0 10px 30px ${shadowColor}, 0 6px 10px ${shadowColor}`,
359-
transitionDuration: `${transitionDuration}s`,
360-
},
361-
})
362-
363-
const applyStyles = (element: HTMLDivElement, styles: React.CSSProperties) => {
364-
Object.keys(styles).forEach((style) => {
365-
// `requestAnimationFrame` doesn't play nice with CSS transition duration on
366-
// desktop Safari
367-
if (isSafariDesktop()) {
368-
element.style[style] = styles[style]
369-
} else {
370-
requestAnimationFrame(() => {
371-
element.style[style] = styles[style]
372-
})
373-
}
374-
})
375-
}

lib/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import LayeredImage, { ILayeredImageProps } from "./LayeredImage"
1+
import { ILayeredImageProps, LayeredImage } from "./LayeredImage"
22

3-
export { ILayeredImageProps }
4-
export default LayeredImage
3+
export { ILayeredImageProps, LayeredImage }

lib/utils.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
/**
2+
* Apply a set of styles to an HTML element.
3+
*/
4+
export const applyStyles = (element: HTMLDivElement, styles: React.CSSProperties) => {
5+
for (const [style, value] of Object.entries(styles)) {
6+
element.style[style] = value
7+
}
8+
}
9+
110
/**
211
* Clamp the `value` between `min` and `max` inclusively.
312
*/
@@ -12,12 +21,3 @@ export const clamp = (value: number, min: number, max: number) => {
1221
*/
1322
// eslint-disable-next-line @typescript-eslint/ban-types
1423
export const isFunction = (value: unknown): value is Function => typeof value === "function"
15-
16-
/**
17-
* Detect whether the current browser is a desktop version of Safari.
18-
*/
19-
export const isSafariDesktop = () => {
20-
const { userAgent, vendor } = navigator
21-
22-
return /Safari/i.test(userAgent) && /Apple Computer/.test(vendor) && !/Mobi|Android/i.test(userAgent)
23-
}

types/LayeredImage.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ export interface ILayeredImageProps extends React.HTMLProps<HTMLDivElement> {
1111
shadowOpacity?: React.CSSProperties["opacity"]
1212
}
1313
export declare const LayeredImage: React.FC<ILayeredImageProps>
14-
export default LayeredImage

0 commit comments

Comments
 (0)