import React, { ComponentPropsWithoutRef, ComponentRef, forwardRef, } from "react" import { tv, VariantProps } from "tailwind-variants" import { useIsHydrated } from "../../hooks" import { classNames } from "../../utils/classNames" import { Avatar, AvatarImageProps } from "../Avatar" import { Flex, FlexProps } from "../Flex" // You only need to change this if scale or translate-x is adjusted on the left / right cards // This is calculated by: // 1. Removing border radius // 2. Setting size to something large like 400px // 3. Nudging the margin in devtools until the container is aligned to the left side of the leftmost card (e.g. -256px) // 4. Dividing that margin by 400 (the new size) const NEGATIVE_MARGIN_MULTIPLIER = 0.64 const animationBaseClassNames = classNames( "transition duration-1000 ease-out-quint", ) const avatarVariants = tv({ base: classNames("size-[140px] rounded-[3%]", animationBaseClassNames), variants: { show: { true: "", false: "", }, position: { center: "*:rounded-[3%]", left: "translate-x-[60%] rotate-[-15deg]", right: "translate-x-[-60%] rotate-[15deg]", }, }, compoundVariants: [ { position: ["left", "right"], className: "-z-10 translate-y-[4.5%] scale-75 overflow-hidden delay-150", }, { position: "center", show: true, className: "drop-shadow-sm", }, { position: "center", show: false, className: "translate-y-[4.5%] scale-95", }, { position: ["left", "right"], show: false, // 12.6% (bottom aligned) className: "translate-y-[12.6%] rotate-0", }, { position: "left", show: false, className: "translate-x-full", }, { position: "right", show: false, className: "-translate-x-full", }, ], }) type Position = Exclude< VariantProps["position"], undefined > type PositionOverrides = Partial> type AnimationOverrides = { className?: string } type Overrides = { base?: Partial position?: PositionOverrides> } export type ImageStackProps = { srcs?: AvatarImageProps["src"][] size?: AvatarImageProps["size"] show?: boolean animate?: "fade-in" overrides?: { Avatar?: Overrides animation?: Overrides centerBackground?: FlexProps } } & Omit, "children"> export const ImageStack = forwardRef< ComponentRef, ImageStackProps >(function ImageStack( { srcs, size = 140, show: propsShow = true, overrides, className, animate, ...rest }, ref, ) { const isHydrated = useIsHydrated() const [centerSrc, rightSrc, leftSrc] = srcs || [] const negativeMargin = size * NEGATIVE_MARGIN_MULTIPLIER * -1 const avatarOverrides = (position: Position) => ({ ...overrides?.Avatar?.base, ...overrides?.Avatar?.position?.[position], }) const animationClassNames = (position: Position) => classNames( overrides?.animation?.base?.className, overrides?.animation?.position?.[position]?.className, ) const avatarClassNames = (position: Position) => classNames( overrides?.Avatar?.base?.className, overrides?.Avatar?.position?.[position]?.className, animationClassNames(position), ) const animationPresets: Record< Exclude, boolean > = { "fade-in": isHydrated, } const show = typeof animate !== "undefined" ? animationPresets[animate] : propsShow return ( {leftSrc && ( )} {centerSrc && ( )} {rightSrc && ( )} ) })