diff --git a/src/custom/FlipCard/FlipCard.tsx b/src/custom/FlipCard/FlipCard.tsx
index 97aea50cf..c7ee3f4f5 100644
--- a/src/custom/FlipCard/FlipCard.tsx
+++ b/src/custom/FlipCard/FlipCard.tsx
@@ -4,51 +4,49 @@ import { BUTTON_MODAL_DARK, WHITE } from '../../theme/colors/colors';
export type FlipCardProps = {
duration?: number;
- onClick?: () => void;
- onShow?: () => void;
- children: [React.ReactNode, React.ReactNode];
disableFlip?: boolean;
- padding?: string;
+ frontElement: React.ReactNode;
+ backElement: React.ReactNode;
+ flipAction?: 'hover' | 'click';
};
-/**
- * Helper function to get the front or back child component from the children array
- * @param children Array containing exactly two child components
- * @param key Index to retrieve (0 for front, 1 for back)
- * @throws Error if children is undefined or doesn't contain exactly two components
- * @returns The selected child component
- */
-function GetChild(children: [React.ReactNode, React.ReactNode], key: number) {
- if (!children) throw Error('FlipCard requires exactly two child components');
- if (children.length != 2) throw Error('FlipCard requires exactly two child components');
-
- return children[key];
-}
-const Card = styled('div')(({ theme }) => ({
+const Card = styled('div')({
height: '100%',
- backgroundColor: 'transparent',
- perspective: theme.spacing(125)
-}));
+ width: '100%',
+ perspective: '1000px',
+});
-const InnerCard = styled('div')(({ theme }) => ({
- padding: theme.spacing(2),
- borderRadius: theme.spacing(1),
+const InnerCard = styled('div', {
+ // Prevent 'flipped' prop from leaking to the DOM element
+ shouldForwardProp: (prop) => prop !== 'flipped',
+})<{ flipped: boolean }>(({ flipped }) => ({
+ position: 'relative',
+ height: '100%',
+ width: '100%',
transformStyle: 'preserve-3d',
- boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)',
- backgroundColor: theme.palette.mode === 'dark' ? BUTTON_MODAL_DARK : WHITE,
- cursor: 'pointer',
- transformOrigin: '50% 50% 10%'
+ transform: flipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
}));
const FrontContent = styled('div')({
- backfaceVisibility: 'hidden'
+ position: 'absolute',
+ height: '100%',
+ width: '100%',
+ backfaceVisibility: 'hidden',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
});
const BackContent = styled('div')({
+ position: 'absolute',
+ height: '100%',
+ width: '100%',
backfaceVisibility: 'hidden',
- transform: 'scale(-1, 1)',
- wordBreak: 'break-word'
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ transform: 'rotateY(180deg)',
});
/**
@@ -56,78 +54,65 @@ const BackContent = styled('div')({
*
* @component
* @param props.duration - Animation duration in milliseconds (default: 500)
- * @param props.onClick - Callback function triggered on card click
- * @param props.onShow - Additional callback function triggered when card shows new face
- * @param props.children - Array of exactly two child components (front and back)
+ * @param props.flipAction - The action that triggers the flip animation ('hover' or 'click') (default: 'click')
+ * @param props.frontElement - React node to be displayed on the front face of the card
+ * @param props.backElement - React node to be displayed on the back face of the card
* @param props.disableFlip - When true, prevents the card from flipping (default: false)
*
* @example
* ```tsx
- *
- * Front Content
- * Back Content
- *
+ * Front Content}
+ * backElement={Back Content
}
+ * flipAction="hover"
+ * />
* ```
*/
export function FlipCard({
duration = 500,
- onClick,
- onShow,
- children,
+ frontElement,
+ backElement,
disableFlip = false,
- padding
+ flipAction = 'click'
}: FlipCardProps) {
const [flipped, setFlipped] = React.useState(false);
- const [activeBack, setActiveBack] = React.useState(false);
-
- const timeout = React.useRef(null);
-
- const Front = GetChild(children, 0);
- const Back = GetChild(children, 1);
-
- React.useEffect(() => {
- // This function makes sure that the inner content of the card disappears roughly
- // after 30 deg rotation has already occured. It will ensure that the user doesn't gets
- // a "blank" card while the card is rotating
- //
- // This guarantee can be offered because of two main reasons:
- // 1. In sufficiently modern browsers JS and CSS are handled in different threads
- // hence ones execution doesn't blocks another.
- // 2. setTimeout will put its callback at the end of current context's end hence ensuring
- // this callback doesn't gets blocked by another JS process.
+
+ const handleFlip = () => {
+ if (!disableFlip) setFlipped((prev) => !prev);
+ };
- if (timeout.current) clearTimeout(timeout.current);
+ // Determine triggers
+ const triggerProps = flipAction === 'click'
+ ? {
+ onClick: handleFlip,
+ onKeyDown: (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleFlip();
+ }
+ },
+ role: 'button',
+ tabIndex: 0,
+ 'aria-pressed': flipped
+ }
+ : {
+ onMouseEnter: () => !disableFlip && setFlipped(true),
+ onMouseLeave: () => !disableFlip && setFlipped(false)
+ };
- timeout.current = setTimeout(() => {
- setActiveBack(flipped);
- }, duration / 6);
- }, [flipped, duration]);
return (
- {
- if (disableFlip) return;
- setFlipped((flipped) => !flipped);
- if (onClick) {
- onClick();
- }
- if (onShow) {
- onShow();
- }
- }}
- >
+
- {!activeBack ? (
- {React.isValidElement(Front) ? Front : null}
- ) : (
- {React.isValidElement(Back) ? Back : null}
- )}
+ {frontElement}
+
+ {backElement}
+
);