@gfazioli/mantine-flip
Version:
Flip component is a wrapper for any component that can be flipped. It is used to create cards, flip boxes and more.
264 lines (260 loc) • 8.27 kB
JavaScript
'use client';
;
var React = require('react');
var core = require('@mantine/core');
var hooks = require('@mantine/hooks');
var Flip_context = require('./Flip.context.cjs');
var FlipBack = require('./FlipBack/FlipBack.cjs');
var FlipFront = require('./FlipFront/FlipFront.cjs');
var FlipTarget = require('./FlipTarget/FlipTarget.cjs');
var Flip_module = require('./Flip.module.css.cjs');
const defaultProps = {
direction: "horizontal",
directionFlipIn: "negative",
directionFlipOut: "positive"
};
const SPRING_EASING = "linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017, 1.077 21.8%, 1.121 24%, 1.149 26.3%, 1.159, 1.163 29%, 1.154 31.4%, 1.075 38.5%, 1.033 42.7%, 1.001 48%, 0.985 52.2%, 0.98 56.3%, 0.984 64.3%, 1 100%)";
const resolveEasing = (easing) => {
if (easing === void 0) {
return "ease-in-out";
}
if (easing === "spring") {
return SPRING_EASING;
}
return easing;
};
const varsResolver = core.createVarsResolver((_, { perspective, easing, duration }) => ({
root: {
"--flip-perspective": perspective === void 0 ? "1000px" : perspective,
"--flip-transition-duration": duration === void 0 ? ".8s" : `${duration}s`,
"--flip-transition-timing-function": resolveEasing(easing)
},
"flip-container": {},
"flip-front-face": {},
"flip-back-face": {}
}));
const Flip = core.polymorphicFactory((_props) => {
const { ref, ...restProps } = _props;
const props = core.useProps("Flip", defaultProps, restProps);
const {
perspective,
duration,
easing,
classNames,
style,
styles,
unstyled,
vars,
children,
className,
flipped,
defaultFlipped,
direction,
directionFlipIn,
directionFlipOut,
onChange,
onBack,
onFront,
onTransitionEnd,
disabled,
lazyBack,
swipeable,
swipeThreshold,
...others
} = props;
const initialFlipped = flipped ?? defaultFlipped ?? false;
const [rotateValue, setRotateValue] = React.useState(initialFlipped ? -180 : 0);
const backMountedRef = React.useRef(initialFlipped || !lazyBack);
const rootRef = React.useRef(null);
const touchStartRef = React.useRef(null);
const toggleFlipRef = React.useRef(() => {
});
if (!lazyBack && !backMountedRef.current) {
backMountedRef.current = true;
}
const [_flipped, setFlipped] = hooks.useUncontrolled({
value: flipped,
defaultValue: defaultFlipped,
finalValue: false,
onChange
});
const getStyles = core.useStyles({
name: "Flip",
props,
classes: Flip_module,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver
});
const directionResetRef = React.useRef(false);
hooks.useDidUpdate(() => {
if (_flipped) {
backMountedRef.current = true;
}
}, [_flipped]);
hooks.useDidUpdate(() => {
if (_flipped) {
directionResetRef.current = true;
}
setRotateValue(0);
if (flipped === void 0) {
setFlipped(false);
}
}, [directionFlipIn, directionFlipOut, direction]);
hooks.useDidUpdate(() => {
if (directionResetRef.current) {
directionResetRef.current = false;
return;
}
if (directionFlipIn === "negative" && directionFlipOut === "positive") {
setRotateValue((v) => v ? v + 180 : -180);
}
if (directionFlipIn === "negative" && directionFlipOut === "negative") {
setRotateValue((v) => v - 180);
}
if (directionFlipIn === "positive" && directionFlipOut === "negative") {
setRotateValue((v) => v ? v - 180 : 180);
}
if (directionFlipIn === "positive" && directionFlipOut === "positive") {
setRotateValue((v) => v + 180);
}
}, [_flipped]);
const childrenArray = React.Children.toArray(children);
const compoundFront = childrenArray.find(
(child) => React.isValidElement(child) && child.type?.displayName === "FlipFront"
);
const compoundBack = childrenArray.find(
(child) => React.isValidElement(child) && child.type?.displayName === "FlipBack"
);
const isCompound = !!(compoundFront || compoundBack);
if (isCompound) {
if (!compoundFront || !compoundBack) {
throw new Error(
"Flip component requires both Flip.Front and Flip.Back when using compound components"
);
}
if (childrenArray.length !== 2) {
throw new Error(
"Flip component with Flip.Front and Flip.Back must have exactly two children, no extra elements allowed"
);
}
} else if (childrenArray.length !== 2) {
throw new Error(
"Flip component must have exactly two children, or use Flip.Front and Flip.Back"
);
}
const frontChild = isCompound ? compoundFront.props.children : childrenArray[0];
const backChild = isCompound ? compoundBack.props.children : childrenArray[1];
const getDirectionIn = React.useMemo(() => {
if (direction === "horizontal") {
return { transform: `rotateY(${rotateValue}deg)` };
}
return { transform: `rotateX(${rotateValue}deg)` };
}, [direction, rotateValue]);
const getBackRotation = React.useMemo(() => {
if (direction === "horizontal") {
return { transform: "rotateY(180deg)" };
}
return { transform: "rotateX(180deg)" };
}, [direction]);
const toggleFlip = React.useCallback(() => {
if (disabled) {
return;
}
if (_flipped) {
setFlipped(false);
onFront?.();
} else {
backMountedRef.current = true;
setFlipped(true);
onBack?.();
}
}, [disabled, _flipped, onFront, onBack]);
toggleFlipRef.current = toggleFlip;
const handleTransitionEnd = React.useCallback(
(event) => {
if (event.propertyName === "transform" && event.target === event.currentTarget) {
onTransitionEnd?.();
}
},
[onTransitionEnd]
);
React.useEffect(() => {
if (!swipeable || disabled) {
return void 0;
}
const el = rootRef.current;
if (!el) {
return void 0;
}
const threshold = swipeThreshold ?? 50;
const handleTouchStart = (e) => {
const touch = e.touches[0];
touchStartRef.current = { x: touch.clientX, y: touch.clientY };
};
const handleTouchEnd = (e) => {
if (!touchStartRef.current) {
return;
}
const touch = e.changedTouches[0];
const dx = touch.clientX - touchStartRef.current.x;
const dy = touch.clientY - touchStartRef.current.y;
touchStartRef.current = null;
if (direction === "horizontal" && Math.abs(dx) > threshold && Math.abs(dx) > Math.abs(dy)) {
toggleFlipRef.current();
} else if (direction === "vertical" && Math.abs(dy) > threshold && Math.abs(dy) > Math.abs(dx)) {
toggleFlipRef.current();
}
};
el.addEventListener("touchstart", handleTouchStart, { passive: true });
el.addEventListener("touchend", handleTouchEnd, { passive: true });
return () => {
el.removeEventListener("touchstart", handleTouchStart);
el.removeEventListener("touchend", handleTouchEnd);
};
}, [swipeable, disabled, direction, swipeThreshold]);
return /* @__PURE__ */ React.createElement(
Flip_context.FlipContextProvider,
{
value: {
toggleFlip,
flipped: _flipped,
disabled: !!disabled
}
},
/* @__PURE__ */ React.createElement(core.Box, { ref: hooks.useMergedRef(ref, rootRef), ...getStyles("root"), "aria-live": "polite", ...others }, /* @__PURE__ */ React.createElement(
"div",
{
...getStyles("flip-container", { style: getDirectionIn }),
onTransitionEnd: handleTransitionEnd
},
/* @__PURE__ */ React.createElement(
"div",
{
...getStyles("flip-front-face", { style: { zIndex: 0 } }),
inert: _flipped || void 0
},
frontChild
),
/* @__PURE__ */ React.createElement(
"div",
{
...getStyles("flip-back-face", { style: getBackRotation }),
inert: !_flipped || void 0
},
backMountedRef.current ? backChild : null
)
))
);
});
Flip.classes = Flip_module;
Flip.displayName = "Flip";
Flip.Target = FlipTarget.FlipTarget;
Flip.Front = FlipFront.FlipFront;
Flip.Back = FlipBack.FlipBack;
exports.Flip = Flip;
//# sourceMappingURL=Flip.cjs.map