mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add a poster component for react-native
This commit is contained in:
parent
26e5ce6852
commit
d15c3ed047
@ -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} />
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user