Add a poster component for react-native

This commit is contained in:
Zoe Roux 2022-12-09 15:42:32 +09:00
parent 26e5ce6852
commit d15c3ed047

View File

@ -19,89 +19,99 @@
*/ */
import { useState } from "react"; import { useState } from "react";
import { View, Image as Img, ImageSourcePropType, ImageStyle } from "react-native"; import {
import { percent, useYoshiki } from "yoshiki/native"; View,
Image as Img,
ImageSourcePropType,
ImageStyle,
Platform,
ImageProps,
} from "react-native";
import { useYoshiki } from "yoshiki/native";
import { YoshikiStyle } from "yoshiki/dist/type";
import { Skeleton } from "./skeleton";
type ImageOptions = { type YoshikiEnhanced<Style> = Style extends any
radius?: number; ? {
fallback?: string | ImageSourcePropType; [key in keyof Style]: YoshikiStyle<Style[key]>;
}; }
: never;
type ImageProps = { type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading: true });
type Props = WithLoading<{
src?: string | ImageSourcePropType | null; src?: string | ImageSourcePropType | null;
alt?: string; alt?: string;
} & ImageOptions; fallback?: string | ImageSourcePropType;
}>;
type ImagePropsWithLoading =
| (ImageProps & { loading?: boolean })
| (Partial<ImageProps> & { loading: true });
type Width = ImageStyle["width"];
type Height = ImageStyle["height"];
export const Image = ({ export const Image = ({
src, src,
alt, alt,
radius,
fallback, fallback,
loading = false, isLoading: forcedLoading = false,
aspectRatio = undefined, layout,
width = undefined, ...props
height = undefined, }: Props & { style?: ImageStyle } & {
...others layout: YoshikiEnhanced<
}: ImagePropsWithLoading & | { width: ImageStyle["width"]; height: ImageStyle["height"] }
( | { width: ImageStyle["width"]; aspectRatio: ImageStyle["aspectRatio"] }
| { aspectRatio?: number; width: Width; height: Height } | { height: ImageStyle["height"]; aspectRatio: ImageStyle["aspectRatio"] }
| { aspectRatio: number; width?: Width; height?: Height } >;
)) => { }) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
const [showLoading, setLoading] = useState<boolean>(loading); const [isLoading, setLoading] = useState<boolean>(true);
const [source, setSource] = useState(src); const [source, setSource] = useState(src);
/* const imgRef = useRef<Img>(null); */
// This allow the loading bool to be false with SSR but still be on client-side const border = { borderRadius: 6 } satisfies ImageStyle;
/* useLayoutEffect(() => { */
/* if (!imgRef.current?.complete && src) setLoading(true); */ if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border])} />;
/* if (!src && !loading) setLoading(false); */ if (!source) return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border])} />;
/* }, [src, loading]); */
const nativeProps: ImageProps =
Platform.OS === "web"
? {
defaultSource:
typeof source === "string"
? { uri: source }
: Array.isArray(source)
? source[0]
: source,
}
: {};
return ( return (
<View <Skeleton variant="custom" show={isLoading} {...css([layout, border])}>
{...css( <Img
{ source={typeof source === "string" ? { uri: source } : source}
aspectRatio, accessibilityLabel={alt}
width, onLoad={() => setLoading(false)}
height, onError={() => {
/* backgroundColor: "grey.300", */ if (fallback) setSource(fallback);
borderRadius: radius, else setLoading(false);
overflow: "hidden", }}
/* "& > *": { width: "100%", height: "100%" }, */ {...nativeProps}
}, {...css(
others, [
)} {
> resizeMode: "cover",
{/* {showLoading && <Skeleton variant="rectangular" height="100%" />} */} },
{!loading && source && ( layout,
<Img ],
source={typeof source === "string" ? { uri: source } : source} props,
accessibilityLabel={alt} )}
onLoad={() => setLoading(false)} />
onError={() => { </Skeleton>
if (fallback) setSource(fallback);
else setLoading(false);
}}
{...css({
height: percent(100),
width: percent(100),
resizeMode: "cover",
/* display: showLoading ? "hidden" : undefined, */
})}
/>
)}
</View>
); );
}; };
export const Poster = (props: ImagePropsWithLoading & { width?: Width; height?: Height }) => ( export const Poster = ({
<Image aspectRatio={2 / 3} {...props} /> alt,
isLoading = false,
layout,
...props
}: Props & { style?: ImageStyle } & {
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
}) => (
<Image isLoading={isLoading} alt={alt} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />
); );