import React, { forwardRef, SyntheticEvent, useEffect, useState } from 'react'; import { useWebMergeStyles } from '@cleartrip/ct-design-style-manager'; import type { Styles } from '@cleartrip/ct-design-types'; import NextImage from 'next/image'; import { IImageProps, IImageWebRendererProps } from './type'; import staticImageStyles from './style'; /** * Default web renderer. Emits a plain `` so the design-system * package stays framework-agnostic. Consumers that want `next/image` * (or any other image optimizer) can inject it via the `Component` * prop — the injected component is handed the same minimal surface * described by `IImageWebRendererProps`. */ const DefaultImgComponent = forwardRef( ({ src, alt, width, height, className, onClick, onLoad, onError }, ref) => ( {alt} ), ); DefaultImgComponent.displayName = 'ImageDefaultRenderer'; const NextImageComponent = forwardRef( ( { src, alt, width, height, className, onClick, onLoad, onError, priority, loading, blurDataURL, unoptimized, fetchPriority, }, ref, ) => ( ), ); NextImageComponent.displayName = 'NextImageComponent'; /** * Web implementation of `Image`. * * Migrated from yagami (aldenui) per `Migration.MD`: * - `next/image` coupling dropped; the renderer is injectable via * the `Component` prop (defaults to a semantic ``). * - `useWebMergeStyles` composes caller-provided `styleConfig.root` * slots into a single className string handed to the renderer. * - Fallback-URL handling mirrors the yagami behavior bit-for-bit: * initial `src || fallbackUrl`, and onError swaps in `fallbackUrl` * once (guarded by `imageSource !== fallbackUrl` to prevent an * infinite error loop when the fallback itself 404s). * * The ref is forwarded to the underlying `HTMLImageElement` so * consumers can still measure or imperatively trigger loads. */ const Image = forwardRef( ( { src, alt, width, height, fallbackUrl, styleConfig, onClick, onLoad, onError, Component, priority, loading, blurDataURL, unoptimized, fetchPriority, }, ref, ) => { const [imageSource, setImageSource] = useState(src || fallbackUrl || ''); const { root: customRootStyles = [] } = styleConfig || {}; // Note: customRootStyles is a `ViewStyle`-shaped array; the // style-manager flattens it into real CSS at resolution time. const mergedRootClassName = useWebMergeStyles( [staticImageStyles.root as Styles, ...(customRootStyles as unknown as Styles[])], [staticImageStyles.root, customRootStyles], ); useEffect(() => { setImageSource(src || fallbackUrl || ''); }, [fallbackUrl, src]); const onImageError = (e: SyntheticEvent) => { if (fallbackUrl && imageSource !== fallbackUrl) { setImageSource(fallbackUrl); } if (onError) { onError(e); } }; // The default renderer is a `forwardRef`-ed `` so the ref // passes straight through. User-injected renderers SHOULD also be // forwardRef-wrapped (next/image is); we widen the type here so // the `ref` attribute is accepted unconditionally. const Renderer = (Component || NextImageComponent) as React.ComponentType< IImageWebRendererProps & { ref?: React.Ref } >; return ( ); }, ); Image.displayName = 'Image'; export default Image;