@vela-ui/react
Version:
Vela UI React components
143 lines (140 loc) • 4.25 kB
JavaScript
import {
useCallbackRef
} from "./chunk-PARVIETC.mjs";
import {
useIsHydrated
} from "./chunk-CTLD3YOK.mjs";
import {
createContext
} from "./chunk-UHLSIXAN.mjs";
// src/components/avatar.tsx
import React, { useEffect, useLayoutEffect, useState } from "react";
import { tv } from "tailwind-variants";
import { jsx } from "react/jsx-runtime";
var avatarVariants = tv({
slots: {
root: "relative inline-flex shrink-0 overflow-hidden",
image: "aspect-square size-full",
fallback: "bg-muted flex size-full items-center justify-center select-none"
},
variants: {
size: {
sm: { root: "size-8" },
md: { root: "size-10" },
lg: { root: "size-12" }
},
shape: {
circle: { root: "rounded-full" },
square: { root: "rounded-sm" }
}
},
defaultVariants: {
size: "md",
shape: "circle"
}
});
var { root, image, fallback } = avatarVariants();
var [AvatarProvider, useAvatarContext] = createContext({
name: "AvatarContext"
});
function Avatar({ className, shape, size, ...props }) {
const [imageLoadingStatus, setImageLoadingStatus] = React.useState("idle");
return /* @__PURE__ */ jsx(
AvatarProvider,
{
value: {
shape,
size,
imageLoadingStatus,
onImageLoadingStatusChange: setImageLoadingStatus
},
children: /* @__PURE__ */ jsx("span", { "data-slot": "avatar", className: root({ className, shape, size }), ...props })
}
);
}
function AvatarImage({
src,
className,
onLoadingStatusChange = () => {
},
...props
}) {
const context = useAvatarContext();
const imageLoadingStatus = useImageLoadingStatus(src, props);
const handleLoadingStatusChange = useCallbackRef((status) => {
onLoadingStatusChange(status);
context.onImageLoadingStatusChange(status);
});
useLayoutEffect(() => {
if (imageLoadingStatus !== "idle") {
handleLoadingStatusChange(imageLoadingStatus);
}
}, [imageLoadingStatus, handleLoadingStatusChange]);
return imageLoadingStatus === "loaded" ? /* @__PURE__ */ jsx("img", { "data-slot": "avatar-image", className: image({ className }), src, ...props }) : null;
}
function AvatarFallback({ className, delayMs, ...props }) {
const context = useAvatarContext();
const [canRender, setCanRender] = useState(delayMs === void 0);
useEffect(() => {
if (delayMs !== void 0) {
const timerId = window.setTimeout(() => setCanRender(true), delayMs);
return () => window.clearTimeout(timerId);
}
}, [delayMs]);
return canRender && context.imageLoadingStatus !== "loaded" ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-fallback", className: fallback({ className }), ...props }) : null;
}
function resolveLoadingStatus(image2, src) {
if (!image2) {
return "idle";
}
if (!src) {
return "error";
}
if (image2.src !== src) {
image2.src = src;
}
return image2.complete && image2.naturalWidth > 0 ? "loaded" : "loading";
}
function useImageLoadingStatus(src, { referrerPolicy, crossOrigin }) {
const isHydrated = useIsHydrated();
const imageRef = React.useRef(null);
const image2 = (() => {
if (!isHydrated) return null;
if (!imageRef.current) {
imageRef.current = new window.Image();
}
return imageRef.current;
})();
const [loadingStatus, setLoadingStatus] = React.useState(
() => resolveLoadingStatus(image2, src)
);
useLayoutEffect(() => {
setLoadingStatus(resolveLoadingStatus(image2, src));
}, [image2, src]);
useLayoutEffect(() => {
const updateStatus = (status) => () => {
setLoadingStatus(status);
};
if (!image2) return;
const handleLoad = updateStatus("loaded");
const handleError = updateStatus("error");
image2.addEventListener("load", handleLoad);
image2.addEventListener("error", handleError);
if (referrerPolicy) {
image2.referrerPolicy = referrerPolicy;
}
if (typeof crossOrigin === "string") {
image2.crossOrigin = crossOrigin;
}
return () => {
image2.removeEventListener("load", handleLoad);
image2.removeEventListener("error", handleError);
};
}, [image2, crossOrigin, referrerPolicy]);
return loadingStatus;
}
export {
Avatar,
AvatarImage,
AvatarFallback
};