Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 100 additions & 145 deletions src/components/Cube/Cube.jsx
Original file line number Diff line number Diff line change
@@ -1,134 +1,69 @@
// Import External Dependencies
import PropTypes from "prop-types";
import { Component } from "react";

export default class Cube extends Component {
static propTypes = {
hover: PropTypes.bool,
theme: PropTypes.string,
depth: PropTypes.number,
repeatDelay: PropTypes.number,
className: PropTypes.string,
continuous: PropTypes.bool,
};

static defaultProps = {
hover: false,
theme: "dark",
depth: 30,
repeatDelay: 1000,
};
import { useEffect, useRef, useState } from "react";

state = {
x: 0,
y: 0,
z: 0,
iteration: 0,
};
export default function Cube({
hover = false,
theme = "dark",
depth = 30,
repeatDelay = 1000,
className = "",
continuous,
}) {
const [state, setState] = useState({ x: 0, y: 0, z: 0, iteration: 0 });
const containerRef = useRef(null);
const timeoutRef = useRef(null);

render() {
const { x, y, z } = this.state;
const { theme, depth, className = "" } = this.props;
const { x, y, z, iteration } = state;

return (
<div
className={`cube__container ${className}`}
style={{
width: `${depth * 1.5}px`,
height: `${depth * 1.5}px`,
paddingLeft: `${depth / 1.7}px`,
}}
>
<span
ref={(ref) => (this.container = ref)}
className={`cube cube--${theme} relative block [transform-style:preserve-3d]`}
style={{
width: `${depth}px`,
paddingBottom: `${depth * 0.5}px`,
transform: "rotateX(-35.5deg) rotateY(45deg)",
}}
>
<figure
className="cube__outer inline-block [transform-style:preserve-3d] transition-transform duration-1000"
style={{
width: `${depth}px`,
height: `${depth}px`,
transform: `translateX(-50%)
scale3d(1,1,1)
rotateX(${x}deg)
rotateY(${y}deg)
rotateZ(${z}deg)`,
}}
>
{this._getFaces("outer")}
</figure>
<figure
className="cube__inner absolute -top-[2px] left-0 inline-block [transform-style:preserve-3d] transition-transform duration-1000"
style={{
width: `${depth}px`,
height: `${depth}px`,
transform: `translateX(-50%) translateY(2px)
scale3d(0.5,0.5,0.5)
rotateX(${-x}deg)
rotateY(${-y}deg)
rotateZ(${-z}deg)`,
}}
>
{this._getFaces("inner")}
</figure>
</span>
</div>
);
}

componentDidMount() {
const { hover, continuous, repeatDelay } = this.props;
useEffect(() => {
const container = containerRef.current;

if (hover) {
this.container.addEventListener("mouseenter", this._spin);
this.container.addEventListener("mouseleave", this._reset);
const spin = () => {
const axes = ["x", "y", "z", "iteration"];
const axis = axes[Math.floor(Math.random() * axes.length)];
const sign = Math.random() < 0.5 ? -1 : 1;
setState((prev) => ({ ...prev, [axis]: sign * 90 }));
};

const reset = () => {
setState((prev) => ({ ...prev, x: 0, y: 0, z: 0 }));
};

container.addEventListener("mouseenter", spin);
container.addEventListener("mouseleave", reset);

return () => {
container.removeEventListener("mouseenter", spin);
container.removeEventListener("mouseleave", reset);
};
} else if (continuous) {
let degrees = 0;
const axis = "y";

const animation = () => {
const obj = {};
obj[axis] = degrees += 90;
this.setState({
setState((prev) => ({
...prev,
...obj,
iteration: (this.state.iteration + 1) % 4,
});
iteration: (prev.iteration + 1) % 4,
}));
// eslint-disable-next-line no-use-before-define
tick();
};

const tick = () =>
setTimeout(() => requestAnimationFrame(animation), repeatDelay);

this._timeout = tick();
}
}

componentWillUnmount() {
const { hover, continuous } = this.props;
timeoutRef.current = tick();

if (hover) {
this.container.removeEventListener("mouseenter", this._spin);
this.container.removeEventListener("mouseleave", this._reset);
} else if (continuous) {
clearTimeout(this._timeout);
return () => clearTimeout(timeoutRef.current);
}
}

/**
* Get all faces for a cube
*
* @param {'inner' | 'outer' } type
* @return {array} - An array of nodes
*/
_getFaces(type) {
const { iteration } = this.state;
}, []); // eslint-disable-line react-hooks/exhaustive-deps

const getFaces = (type) => {
// Keep the thicker border on
// the outside on each iteration
const borderWidthMap = {
Expand Down Expand Up @@ -201,50 +136,70 @@ export default class Cube extends Component {
key={i}
className={`cube__face ${baseFaceClasses} ${variantClasses}`}
style={{
transform: `${rotation} translateZ(${this.props.depth / 2}px)`,
transform: `${rotation} translateZ(${depth / 2}px)`,
...borderStyles,
}}
/>
);
});
}

/**
* Get a random axis
*
* @return {string} - A random axis (i.e. x, y, or z)
*/
_getRandomAxis() {
const axes = Object.keys(this.state);

return axes[Math.floor(Math.random() * axes.length)];
}

/**
* Spin the cubes in opposite directions semi-randomly
*
* @param {object} e - Native event
*/
_spin = () => {
const obj = {};
const axis = this._getRandomAxis();
const sign = Math.random() < 0.5 ? -1 : 1;

obj[axis] = sign * 90;

this.setState(obj);
};

/**
* Rotate the cubes back to their original position
*
* @param {object} e - Native event
*/
_reset = () => {
this.setState({
x: 0,
y: 0,
z: 0,
});
};
return (
<div
className={`cube__container ${className}`}
style={{
width: `${depth * 1.5}px`,
height: `${depth * 1.5}px`,
paddingLeft: `${depth / 1.7}px`,
}}
>
<span
ref={containerRef}
className={`cube cube--${theme} relative block [transform-style:preserve-3d]`}
style={{
width: `${depth}px`,
paddingBottom: `${depth * 0.5}px`,
transform: "rotateX(-35.5deg) rotateY(45deg)",
}}
>
<figure
className="cube__outer inline-block [transform-style:preserve-3d] transition-transform duration-1000"
style={{
width: `${depth}px`,
height: `${depth}px`,
transform: `translateX(-50%)
scale3d(1,1,1)
rotateX(${x}deg)
rotateY(${y}deg)
rotateZ(${z}deg)`,
}}
>
{getFaces("outer")}
</figure>
<figure
className="cube__inner absolute -top-[2px] left-0 inline-block [transform-style:preserve-3d] transition-transform duration-1000"
style={{
width: `${depth}px`,
height: `${depth}px`,
transform: `translateX(-50%) translateY(2px)
scale3d(0.5,0.5,0.5)
rotateX(${-x}deg)
rotateY(${-y}deg)
rotateZ(${-z}deg)`,
}}
>
{getFaces("inner")}
</figure>
</span>
</div>
);
}

Cube.propTypes = {
hover: PropTypes.bool,
theme: PropTypes.string,
depth: PropTypes.number,
repeatDelay: PropTypes.number,
className: PropTypes.string,
continuous: PropTypes.bool,
};
Loading