diff --git a/front/packages/primitives/src/image.tsx b/front/packages/primitives/src/image.tsx index e1c2f9c0..63257104 100644 --- a/front/packages/primitives/src/image.tsx +++ b/front/packages/primitives/src/image.tsx @@ -48,7 +48,6 @@ type Props = WithLoading<{ export const Image = ({ src, alt, - fallback, isLoading: forcedLoading = false, layout, ...props @@ -60,36 +59,38 @@ export const Image = ({ >; }) => { const { css } = useYoshiki(); - const [isLoading, setLoading] = useState(true); - const [source, setSource] = useState(src); + const [state, setState] = useState<"loading" | "errored" | "finished">( + src ? "loading" : "errored", + ); + + // This could be done with a key but this makes the API easier to use. + // This unsures that the state is resetted when the source change (useful for recycler lists.) + const [oldSource, setOldSource] = useState(src); + if (oldSource !== src) { + setState("loading"); + setOldSource(src); + } const border = { borderRadius: 6 } satisfies ImageStyle; if (forcedLoading) return ; - if (!source) return theme.overlay0 }, layout, border])} />; + if (!src || state === "errored") + return theme.overlay0 }, layout, border])} />; - const nativeProps: ImageProps = - Platform.OS === "web" - ? { - defaultSource: - typeof source === "string" - ? { uri: source } - : Array.isArray(source) - ? source[0] - : source, - } - : {}; + const nativeProps = Platform.select({ + web: { + defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src, + }, + default: {}, + }); return ( - + setLoading(false)} - onError={() => { - if (fallback) setSource(fallback); - else setLoading(false); - }} + onLoad={() => setState("finished")} + onError={() => setState("errored")} {...nativeProps} {...css( [ diff --git a/front/packages/primitives/src/index.ts b/front/packages/primitives/src/index.ts index aa619786..bb9b44da 100644 --- a/front/packages/primitives/src/index.ts +++ b/front/packages/primitives/src/index.ts @@ -30,8 +30,17 @@ export * from "./tooltip"; export * from "./utils/nojs"; +import { Dimensions } from "react-native"; import { px } from "yoshiki/native"; export const ts = (spacing: number) => { return px(spacing * 8); }; + +export const vw = (spacing: number) => { + return px(spacing * Dimensions.get('window').width / 100); +}; + +export const vh = (spacing: number) => { + return px(spacing * Dimensions.get('window').height / 100); +}; diff --git a/front/packages/primitives/src/links.tsx b/front/packages/primitives/src/links.tsx index b56a7b83..46e0297f 100644 --- a/front/packages/primitives/src/links.tsx +++ b/front/packages/primitives/src/links.tsx @@ -19,7 +19,7 @@ */ import { ReactNode } from "react"; -import { TextProps } from "react-native"; +import { Platform, TextProps } from "react-native"; import { TextLink } from "solito/link"; import { useYoshiki } from "yoshiki/native"; @@ -35,7 +35,8 @@ export const A = ({ href={href} textProps={css( { - fontFamily: theme.fonts.paragraph, + // TODO: use a real font here. + fontFamily: Platform.OS === "web" ? theme.fonts.paragraph : undefined, color: theme.paragraph, }, props, diff --git a/front/packages/primitives/src/skeleton.tsx b/front/packages/primitives/src/skeleton.tsx index 7c4dcaba..3c2e1bc5 100644 --- a/front/packages/primitives/src/skeleton.tsx +++ b/front/packages/primitives/src/skeleton.tsx @@ -45,11 +45,11 @@ export const SkeletonCss = () => ( export const Skeleton = ({ children, - show, + show: forcedShow, variant = "text", ...props }: Omit & { - children?: JSX.Element | boolean | null; + children?: JSX.Element | JSX.Element[] | boolean | null; show?: boolean; variant?: "text" | "round" | "custom"; }) => { @@ -57,19 +57,19 @@ export const Skeleton = ({ const [width, setWidth] = useState(undefined); const perc = (v: number) => (v / 100) * width!; - if (show === undefined && children && children !== true) return children; + if (forcedShow === undefined && children && children !== true) return <>{children}; return ( {children} - {show && ( + {(forcedShow || !children || children === true) && ( setWidth(e.nativeEvent.layout.width)} diff --git a/front/packages/primitives/src/text.tsx b/front/packages/primitives/src/text.tsx index f3f279d1..df02f106 100644 --- a/front/packages/primitives/src/text.tsx +++ b/front/packages/primitives/src/text.tsx @@ -19,8 +19,8 @@ */ import { ComponentType, ComponentProps } from "react"; -import { TextProps } from "react-native"; -import { useYoshiki } from "yoshiki/native"; +import { Platform, TextProps } from "react-native"; +import { rem, useYoshiki } from "yoshiki/native"; import { H1 as EH1, H2 as EH2, @@ -43,10 +43,16 @@ const styleText = ( {...css( [ { - fontFamily: type === "header" ? theme.fonts.heading : theme.fonts.paragraph, + // TODO: use custom fonts on mobile also. + fontFamily: + Platform.OS === "web" + ? type === "header" + ? theme.fonts.heading + : theme.fonts.paragraph + : undefined, color: type === "header" ? theme.heading : theme.paragraph, }, - type === "sub" && { fontWeight: "300" }, + type === "sub" && { fontWeight: "300", opacity: 0.8, fontSize: rem(0.8) }, ], props as TextProps, )} diff --git a/front/packages/ui/src/browse/grid.tsx b/front/packages/ui/src/browse/grid.tsx index 95292472..d3ac4b9d 100644 --- a/front/packages/ui/src/browse/grid.tsx +++ b/front/packages/ui/src/browse/grid.tsx @@ -19,6 +19,7 @@ */ import { A, Skeleton, Poster, ts, P, SubP } from "@kyoo/primitives"; +import { Platform, View } from "react-native"; import { percent, px, Stylable, useYoshiki } from "yoshiki/native"; import { WithLoading } from "../fetch"; @@ -39,26 +40,49 @@ export const ItemGrid = ({ const { css } = useYoshiki(); return ( - - - {isLoading ||

{name}

}
+ + + {isLoading || ( +

+ {name} +

+ )} +
{(isLoading || subtitle) && ( - {isLoading || {subtitle}} + + {isLoading || ( + + {subtitle} + + )} + )} -
+
); }; + +ItemGrid.height = px(250); diff --git a/front/packages/ui/src/browse/index.tsx b/front/packages/ui/src/browse/index.tsx index d2545cbf..72a7bc47 100644 --- a/front/packages/ui/src/browse/index.tsx +++ b/front/packages/ui/src/browse/index.tsx @@ -28,7 +28,8 @@ import { getDisplayDate, } from "@kyoo/models"; import { DefaultLayout } from "../layout"; -import { InfiniteFetch, WithLoading } from "../fetch"; +import { WithLoading } from "../fetch"; +import { InfiniteFetch } from "../fetch-infinite"; import { ItemGrid } from "./grid"; import { SortBy, SortOrd, Layout } from "./types"; @@ -83,6 +84,8 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => { = ({ slug }) => { /* justifyContent: "center", */ /* }} */ > - {(item, i) => } + {(item, key) => } );