import { forwardRef } from "react"; import type { CSSProperties, ImgHTMLAttributes, ReactNode } from "react"; import { ImageIcon } from "lucide-react"; import { Skeleton } from "@/shared/components/ui/skeleton"; import { MAX_IMAGE_BRIGHTNESS, normalizeImageAlign, normalizeImageBrightness, } from "@/shared/editor/schema/image-model"; import { bidAttribute, type BlockIdentityProps } from "./attrs"; export const IMAGE_FRAME_FALLBACK_ASPECT_RATIO = 16 / 9; export interface ImagePresentationProps extends BlockIdentityProps { src?: string | null; alt?: string | null; title?: string | null; align?: unknown; width?: number | null; brightness?: unknown; } interface ImageElementProps extends ImgHTMLAttributes { brightness?: unknown; } export const ImageElement = forwardRef(function ImageElement( { brightness, style, ...props }, ref, ) { const value = normalizeImageBrightness(brightness); const imageStyle = value === MAX_IMAGE_BRIGHTNESS ? style : { ...style, filter: `brightness(${value / 100})` }; return ( ); }); export function resolveImageAlignClass(align: unknown): string { const normalized = normalizeImageAlign(align); return normalized === "center" ? "tiptap-image-node--align-center" : normalized === "right" ? "tiptap-image-node--align-right" : "tiptap-image-node--align-left"; } export interface ImageFrameStyleInput { width?: number | null; naturalWidth?: number | null; naturalHeight?: number | null; } export function resolveImageFrameStyle({ width, naturalWidth, naturalHeight }: ImageFrameStyleInput): CSSProperties { const aspectRatio = naturalWidth && naturalHeight && naturalWidth > 0 && naturalHeight > 0 ? naturalWidth / naturalHeight : IMAGE_FRAME_FALLBACK_ASPECT_RATIO; const frameWidth = width && width > 0 ? width : naturalWidth && naturalWidth > 0 ? naturalWidth : null; return { aspectRatio: String(aspectRatio), width: frameWidth !== null ? `${frameWidth}px` : "100%", }; } export interface ImageFigureFrameProps extends BlockIdentityProps { align?: unknown; containerClassName?: string; containerStyle?: CSSProperties; caption?: ReactNode; children: ReactNode; } export function ImageFigureFrame({ bid, align, containerClassName, containerStyle, caption, children, }: ImageFigureFrameProps) { const alignClass = resolveImageAlignClass(align); const containerClass = ["tiptap-image-container", containerClassName].filter(Boolean).join(" "); return (
{children} {caption ?
{caption}
: null}
); } export function ImageLoadingSkeleton() { return