mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add list view on browse
This commit is contained in:
parent
3b29e1a87a
commit
be6551888e
@ -31,7 +31,7 @@
|
||||
"react-native-safe-area-context": "4.4.1",
|
||||
"react-native-screens": "~3.18.0",
|
||||
"react-native-svg": "13.4.0",
|
||||
"yoshiki": "0.2.7"
|
||||
"yoshiki": "0.2.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
|
@ -41,7 +41,7 @@
|
||||
"react-native-web": "^0.18.10",
|
||||
"solito": "^2.0.5",
|
||||
"superjson": "^1.11.0",
|
||||
"yoshiki": "0.2.7",
|
||||
"yoshiki": "0.2.9",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -70,6 +70,11 @@ const GlobalCssTheme = () => {
|
||||
#__next {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.infinite-scroll-component__outerdiv {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`}</style>
|
||||
<WebTooltip theme={theme} />
|
||||
<SkeletonCss />
|
||||
|
47
front/packages/primitives/src/animated.tsx
Normal file
47
front/packages/primitives/src/animated.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Kyoo - A portable and vast media library solution.
|
||||
* Copyright (c) Kyoo.
|
||||
*
|
||||
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
*
|
||||
* Kyoo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Kyoo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { motify } from "moti";
|
||||
import { Component, ComponentType, FunctionComponent } from "react";
|
||||
import { Poster } from "./image";
|
||||
|
||||
const getDisplayName = (Cmp: ComponentType<any>) => {
|
||||
return Cmp.displayName || Cmp.name || "Component";
|
||||
};
|
||||
|
||||
const asClass = <Props,>(Cmp: FunctionComponent<Props>) => {
|
||||
// TODO: ensure that every props is given at least once.
|
||||
return class AsClass extends Component<Partial<Props> & { forward?: Partial<Props> }> {
|
||||
static displayName = `WithClass(${getDisplayName(Cmp)})`;
|
||||
|
||||
constructor(props: Partial<Props> & { forward?: Partial<Props> }) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
// @ts-ignore See todo above
|
||||
return <Cmp {...this.props} {...this.props.forward} />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const Animated = {
|
||||
Poster: motify(asClass(Poster))(),
|
||||
};
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { ComponentType, ReactNode, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Image as Img,
|
||||
@ -26,10 +26,14 @@ import {
|
||||
ImageStyle,
|
||||
Platform,
|
||||
ImageProps,
|
||||
ViewProps,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { YoshikiStyle } from "yoshiki/dist/type";
|
||||
import { percent, useYoshiki } from "yoshiki/native";
|
||||
import { StyleList, YoshikiStyle } from "yoshiki/dist/type";
|
||||
import { Skeleton } from "./skeleton";
|
||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||
import { alpha, ContrastArea } from "./themes";
|
||||
|
||||
type YoshikiEnhanced<Style> = Style extends any
|
||||
? {
|
||||
@ -42,22 +46,21 @@ type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading:
|
||||
type Props = WithLoading<{
|
||||
src?: string | ImageSourcePropType | null;
|
||||
alt?: string;
|
||||
fallback?: string | ImageSourcePropType;
|
||||
}>;
|
||||
|
||||
type ImageLayout = YoshikiEnhanced<
|
||||
| { width: ViewStyle["width"]; height: ViewStyle["height"] }
|
||||
| { width: ViewStyle["width"]; aspectRatio: ViewStyle["aspectRatio"] }
|
||||
| { height: ViewStyle["height"]; aspectRatio: ViewStyle["aspectRatio"] }
|
||||
>;
|
||||
|
||||
export const Image = ({
|
||||
src,
|
||||
alt,
|
||||
isLoading: forcedLoading = false,
|
||||
layout,
|
||||
...props
|
||||
}: Props & { style?: ImageStyle } & {
|
||||
layout: YoshikiEnhanced<
|
||||
| { width: ImageStyle["width"]; height: ImageStyle["height"] }
|
||||
| { width: ImageStyle["width"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||
| { height: ImageStyle["height"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||
>;
|
||||
}) => {
|
||||
}: Props & { style?: ViewStyle } & { layout: ImageLayout }) => {
|
||||
const { css } = useYoshiki();
|
||||
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||
src ? "loading" : "errored",
|
||||
@ -71,11 +74,11 @@ export const Image = ({
|
||||
setOldSource(src);
|
||||
}
|
||||
|
||||
const border = { borderRadius: 6 } satisfies ImageStyle;
|
||||
const border = { borderRadius: 6 } satisfies ViewStyle;
|
||||
|
||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border])} />;
|
||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||
if (!src || state === "errored")
|
||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border])} />;
|
||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
||||
|
||||
const nativeProps = Platform.select<ImageProps>({
|
||||
web: {
|
||||
@ -85,22 +88,20 @@ export const Image = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<Skeleton variant="custom" show={state === "loading"} {...css([layout, border])}>
|
||||
<Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
||||
<Img
|
||||
source={typeof src === "string" ? { uri: src } : src}
|
||||
accessibilityLabel={alt}
|
||||
onLoad={() => setState("finished")}
|
||||
onError={() => setState("errored")}
|
||||
{...nativeProps}
|
||||
{...css(
|
||||
[
|
||||
{
|
||||
resizeMode: "cover",
|
||||
},
|
||||
layout,
|
||||
],
|
||||
props,
|
||||
)}
|
||||
{...css([
|
||||
{
|
||||
width: percent(100),
|
||||
height: percent(100),
|
||||
resizeMode: "cover",
|
||||
},
|
||||
])}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -111,8 +112,90 @@ export const Poster = ({
|
||||
isLoading = false,
|
||||
layout,
|
||||
...props
|
||||
}: Props & { style?: ImageStyle } & {
|
||||
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
||||
}: Props & { style?: ViewStyle } & {
|
||||
layout: YoshikiEnhanced<{ width: ViewStyle["width"] } | { height: ViewStyle["height"] }>;
|
||||
}) => (
|
||||
<Image isLoading={isLoading} alt={alt} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />
|
||||
);
|
||||
|
||||
export const ImageBackground = <AsProps = ViewProps,>({
|
||||
src,
|
||||
alt,
|
||||
gradient = true,
|
||||
as,
|
||||
children,
|
||||
containerStyle,
|
||||
imageStyle,
|
||||
isLoading,
|
||||
...asProps
|
||||
}: {
|
||||
as?: ComponentType<AsProps>;
|
||||
gradient?: Partial<LinearGradientProps> | boolean;
|
||||
children: ReactNode;
|
||||
containerStyle?: StyleList<ViewStyle>;
|
||||
imageStyle?: StyleList<ImageStyle>;
|
||||
} & AsProps &
|
||||
Props) => {
|
||||
const [isErrored, setErrored] = useState(false);
|
||||
|
||||
const nativeProps = Platform.select<ImageProps>({
|
||||
web: {
|
||||
defaultSource: typeof src === "string" ? { uri: src! } : Array.isArray(src) ? src[0] : src!,
|
||||
},
|
||||
default: {},
|
||||
});
|
||||
const Container = as ?? View;
|
||||
return (
|
||||
<ContrastArea contrastText>
|
||||
{({ css, theme }) => (
|
||||
<Container {...(asProps as AsProps)}>
|
||||
<View
|
||||
{...css([
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: -1,
|
||||
bg: (theme) => theme.background,
|
||||
},
|
||||
containerStyle,
|
||||
])}
|
||||
>
|
||||
{src && !isErrored && (
|
||||
<Img
|
||||
source={typeof src === "string" ? { uri: src } : src}
|
||||
accessibilityLabel={alt}
|
||||
onError={() => setErrored(true)}
|
||||
{...nativeProps}
|
||||
{...css([
|
||||
{ width: percent(100), height: percent(100), resizeMode: "cover" },
|
||||
imageStyle,
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
{gradient && (
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0.25 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
colors={["transparent", alpha(theme.colors.black, 0.6)]}
|
||||
{...css(
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
typeof gradient === "object" ? gradient : undefined,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{children}
|
||||
</Container>
|
||||
)}
|
||||
</ContrastArea>
|
||||
);
|
||||
};
|
||||
|
@ -28,6 +28,8 @@ export * from "./image";
|
||||
export * from "./skeleton";
|
||||
export * from "./tooltip";
|
||||
|
||||
export * from "./animated";
|
||||
|
||||
export * from "./utils/breakpoints";
|
||||
export * from "./utils/nojs";
|
||||
|
||||
|
@ -21,7 +21,6 @@
|
||||
import { ComponentType, ReactNode } from "react";
|
||||
import {
|
||||
Platform,
|
||||
Pressable,
|
||||
TextProps,
|
||||
TouchableOpacity,
|
||||
TouchableNativeFeedback,
|
||||
@ -29,7 +28,7 @@ import {
|
||||
ViewProps,
|
||||
} from "react-native";
|
||||
import { LinkCore, TextLink } from "solito/link";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { useYoshiki, Pressable } from "yoshiki/native";
|
||||
|
||||
export const A = ({
|
||||
href,
|
||||
@ -55,7 +54,20 @@ export const A = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const Link = ({ href, children, ...props }: ViewProps & { href: string }) => {
|
||||
export const Link = ({
|
||||
href,
|
||||
children,
|
||||
...props
|
||||
}: ViewProps & {
|
||||
href: string;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
onPressIn?: () => void;
|
||||
onPressOut?: () => void;
|
||||
}) => {
|
||||
const { onBlur, onFocus, onPressIn, onPressOut, ...noFocusProps } = props;
|
||||
const focusProps = { onBlur, onFocus, onPressIn, onPressOut };
|
||||
|
||||
return (
|
||||
<LinkCore
|
||||
href={href}
|
||||
@ -66,7 +78,7 @@ export const Link = ({ href, children, ...props }: ViewProps & { href: string })
|
||||
default: Pressable,
|
||||
})}
|
||||
componentProps={Platform.select<object>({
|
||||
android: { useForeground: true },
|
||||
android: { useForeground: true, ...focusProps },
|
||||
default: props,
|
||||
})}
|
||||
>
|
||||
|
@ -69,7 +69,7 @@ export const Skeleton = ({
|
||||
borderRadius: px(6),
|
||||
},
|
||||
variant === "text" && {
|
||||
margin: px(2),
|
||||
margin: rem(1),
|
||||
width: percent(75),
|
||||
height: rem(1.2),
|
||||
},
|
||||
|
@ -68,5 +68,6 @@ export const H3 = styleText(EH3, "header");
|
||||
export const H4 = styleText(EH4, "header");
|
||||
export const H5 = styleText(EH5, "header");
|
||||
export const H6 = styleText(EH6, "header");
|
||||
export const Heading = styleText(EP, "header");
|
||||
export const P = styleText(EP);
|
||||
export const SubP = styleText(EP, "sub");
|
||||
|
@ -83,7 +83,7 @@ export const catppuccin: ThemeBuilder = {
|
||||
blue: "#89b4fa",
|
||||
yellow: "#f9e2af",
|
||||
black: "#11111b",
|
||||
white: "#cdd6f4",
|
||||
white: "#f5f0f8",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -20,7 +20,8 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Property } from "csstype";
|
||||
import { Theme, ThemeProvider, useTheme } from "yoshiki";
|
||||
import { Theme, ThemeProvider } from "yoshiki";
|
||||
import { useTheme, useYoshiki } from "yoshiki/native";
|
||||
import "yoshiki";
|
||||
import { catppuccin } from "./catppuccin";
|
||||
|
||||
@ -57,7 +58,9 @@ type Variant = {
|
||||
|
||||
declare module "yoshiki" {
|
||||
// TODO: Add specifics colors
|
||||
export interface Theme extends ThemeSettings, Mode, Variant {}
|
||||
export interface Theme extends ThemeSettings, Mode, Variant {
|
||||
builder: ThemeBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
export type { Theme } from "yoshiki";
|
||||
@ -70,7 +73,7 @@ export const selectMode = (theme: ThemeBuilder, mode: "light" | "dark"): Theme =
|
||||
const { light, dark, ...options } = theme;
|
||||
const value = mode === "light" ? light : dark;
|
||||
const { default: def, ...modeOpt } = value;
|
||||
return { ...options, ...modeOpt, ...def, variant: value.variant };
|
||||
return { ...options, ...modeOpt, ...def, variant: value.variant, builder: theme };
|
||||
};
|
||||
|
||||
export const switchVariant = (theme: Theme) => {
|
||||
@ -88,12 +91,56 @@ export const switchVariant = (theme: Theme) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const SwitchVariant = ({ children }: { children?: JSX.Element | JSX.Element[] }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return <ThemeProvider theme={switchVariant(theme)}>{children}</ThemeProvider>;
|
||||
};
|
||||
|
||||
export const ThemeSelector = ({ children }: { children: ReactNode }) => {
|
||||
return <ThemeProvider theme={selectMode(catppuccin, "light")}>{children}</ThemeProvider>;
|
||||
};
|
||||
|
||||
type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||
|
||||
const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
||||
const yoshiki = useYoshiki();
|
||||
return <>{children(yoshiki)}</>;
|
||||
};
|
||||
|
||||
export const SwitchVariant = ({ children }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={switchVariant(theme)}>
|
||||
{typeof children === "function" ? <YoshikiProvider>{children}</YoshikiProvider> : children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContrastArea = ({
|
||||
children,
|
||||
mode = "dark",
|
||||
contrastText,
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
mode?: "light" | "dark";
|
||||
contrastText?: boolean;
|
||||
}) => {
|
||||
const oldTheme = useTheme();
|
||||
const theme = selectMode(oldTheme.builder, mode);
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={
|
||||
contrastText
|
||||
? {
|
||||
...theme,
|
||||
heading: mode === "light" ? theme.colors.black : theme.colors.white,
|
||||
paragraph: theme.heading,
|
||||
}
|
||||
: theme
|
||||
}
|
||||
>
|
||||
{typeof children === "function" ? <YoshikiProvider>{children}</YoshikiProvider> : children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const alpha = (color: Property.Color, alpha: number) => {
|
||||
return color + (alpha * 255).toString(16);
|
||||
};
|
||||
|
@ -46,6 +46,7 @@ export const WebTooltip = ({ theme }: { theme: Theme }) => {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
z-index: 999;
|
||||
|
||||
margin-top: 8px;
|
||||
border-radius: 5px;
|
||||
|
@ -34,7 +34,9 @@ import { ItemGrid } from "./grid";
|
||||
import { ItemList } from "./list";
|
||||
import { SortBy, SortOrd, Layout } from "./types";
|
||||
|
||||
const itemMap = (item: WithLoading<LibraryItem>): WithLoading<ComponentProps<typeof ItemGrid>> => {
|
||||
const itemMap = (
|
||||
item: WithLoading<LibraryItem>,
|
||||
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {
|
||||
if (item.isLoading) return item;
|
||||
|
||||
let href;
|
||||
@ -48,6 +50,7 @@ const itemMap = (item: WithLoading<LibraryItem>): WithLoading<ComponentProps<typ
|
||||
subtitle: item.type !== ItemType.Collection ? getDisplayDate(item) : undefined,
|
||||
href,
|
||||
poster: item.poster,
|
||||
thumbnail: item.thumbnail,
|
||||
};
|
||||
};
|
||||
|
||||
@ -70,7 +73,7 @@ const query = (
|
||||
export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
||||
const [sortKey, setSort] = useState(SortBy.Name);
|
||||
const [sortOrd, setSortOrd] = useState(SortOrd.Asc);
|
||||
const [layout, setLayout] = useState(Layout.Grid);
|
||||
const [layout, setLayout] = useState(Layout.List);
|
||||
|
||||
const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList;
|
||||
|
||||
|
@ -18,98 +18,101 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const ItemList = ({
|
||||
import { Link, P, Skeleton, Animated, ts, ImageBackground, Heading } from "@kyoo/primitives";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { percent, px, rem, useYoshiki } from "yoshiki/native";
|
||||
import { Layout, WithLoading } from "../fetch";
|
||||
|
||||
export const ItemList = ({
|
||||
href,
|
||||
name,
|
||||
subtitle,
|
||||
thumbnail,
|
||||
poster,
|
||||
loading,
|
||||
}: {
|
||||
href?: string;
|
||||
name?: string;
|
||||
subtitle?: string | null;
|
||||
isLoading,
|
||||
}: WithLoading<{
|
||||
href: string;
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
poster?: string | null;
|
||||
thumbnail?: string | null;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
}>) => {
|
||||
const { css } = useYoshiki();
|
||||
const [isHovered, setHovered] = useState(0);
|
||||
|
||||
return (
|
||||
<Link
|
||||
<ImageBackground
|
||||
src={thumbnail}
|
||||
alt={name}
|
||||
as={Link}
|
||||
href={href ?? ""}
|
||||
color="inherit"
|
||||
sx={{
|
||||
display: "flex",
|
||||
textAlign: "center",
|
||||
onFocus={() => setHovered((i) => i + 1)}
|
||||
onBlur={() => setHovered((i) => i - 1)}
|
||||
onPressIn={() => setHovered((i) => i + 1)}
|
||||
onPressOut={() => setHovered((i) => i - 1)}
|
||||
containerStyle={{
|
||||
borderRadius: px(6),
|
||||
}}
|
||||
imageStyle={{
|
||||
borderRadius: px(6),
|
||||
}}
|
||||
{...css({
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
flexDirection: "row",
|
||||
m: 1,
|
||||
position: "relative",
|
||||
color: "white",
|
||||
"&:hover .poster": {
|
||||
transform: "scale(1.3)",
|
||||
},
|
||||
}}
|
||||
height: ItemList.layout.size,
|
||||
borderRadius: px(6),
|
||||
m: ts(1),
|
||||
})}
|
||||
>
|
||||
<Image
|
||||
src={thumbnail}
|
||||
alt={name}
|
||||
width="100%"
|
||||
height="100%"
|
||||
radius={px(5)}
|
||||
css={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: -1,
|
||||
|
||||
"&::after": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
/* background: "rgba(0, 0, 0, 0.4)", */
|
||||
background: "linear-gradient(to bottom, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 0.6) 100%)",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
<View
|
||||
{...css({
|
||||
flexDirection: "column",
|
||||
width: { xs: "50%", lg: "30%" },
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<Typography
|
||||
variant="button"
|
||||
sx={{
|
||||
fontSize: "2rem",
|
||||
letterSpacing: "0.002rem",
|
||||
fontWeight: 900,
|
||||
}}
|
||||
>
|
||||
{name ?? <Skeleton />}
|
||||
</Typography>
|
||||
{(loading || subtitle) && (
|
||||
<Typography variant="caption" sx={{ fontSize: "1rem" }}>
|
||||
{subtitle ?? <Skeleton />}
|
||||
</Typography>
|
||||
<Skeleton {...css({ height: rem(2), alignSelf: "center" })}>
|
||||
{isLoading || (
|
||||
<Heading
|
||||
{...css({
|
||||
textAlign: "center",
|
||||
fontSize: rem(2),
|
||||
letterSpacing: rem(0.002),
|
||||
fontWeight: "900",
|
||||
textTransform: "uppercase",
|
||||
textDecorationLine: isHovered ? "underline" : "none",
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
</Heading>
|
||||
)}
|
||||
</Skeleton>
|
||||
{(isLoading || subtitle) && (
|
||||
<Skeleton {...css({ width: rem(5), alignSelf: "center" })}>
|
||||
{isLoading || (
|
||||
<P
|
||||
{...css({
|
||||
textAlign: "center",
|
||||
})}
|
||||
>
|
||||
{subtitle}
|
||||
</P>
|
||||
)}
|
||||
</Skeleton>
|
||||
)}
|
||||
</Box>
|
||||
<Poster
|
||||
</View>
|
||||
<Animated.Poster
|
||||
src={poster}
|
||||
alt=""
|
||||
height="80%"
|
||||
css={{
|
||||
transition: "transform .2s",
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
forward={{ layout: { height: percent(80) } }}
|
||||
// TODO: this does not work on the web...
|
||||
animate={{ scale: isHovered ? 1.3 : 1 }}
|
||||
transition={{ type: "spring" }}
|
||||
/>
|
||||
</Link>
|
||||
</ImageBackground>
|
||||
);
|
||||
};
|
||||
|
||||
ItemList.layout = { numColumns: 1, size: 300 } satisfies Layout;
|
||||
|
@ -62,9 +62,11 @@ export const InfiniteFetch = <Data,>({
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "center",
|
||||
overflow: "unset !important",
|
||||
},
|
||||
numColumns === 1 && {
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
},
|
||||
numColumns !== 1 && {
|
||||
flexWrap: "wrap",
|
||||
|
@ -58,6 +58,7 @@ export const Navbar = (props: Stylable) => {
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 4.65,
|
||||
elevation: 8,
|
||||
zIndex: 1,
|
||||
},
|
||||
props,
|
||||
)}
|
||||
|
@ -9682,7 +9682,7 @@ __metadata:
|
||||
react-native-screens: ~3.18.0
|
||||
react-native-svg: 13.4.0
|
||||
typescript: ^4.6.3
|
||||
yoshiki: 0.2.7
|
||||
yoshiki: 0.2.9
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -13319,7 +13319,7 @@ __metadata:
|
||||
superjson: ^1.11.0
|
||||
typescript: ^4.9.3
|
||||
webpack: ^5.75.0
|
||||
yoshiki: 0.2.7
|
||||
yoshiki: 0.2.9
|
||||
zod: ^3.19.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -13644,9 +13644,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yoshiki@npm:0.2.7":
|
||||
version: 0.2.7
|
||||
resolution: "yoshiki@npm:0.2.7"
|
||||
"yoshiki@npm:0.2.9":
|
||||
version: 0.2.9
|
||||
resolution: "yoshiki@npm:0.2.9"
|
||||
dependencies:
|
||||
"@types/node": 18.x.x
|
||||
"@types/react": 18.x.x
|
||||
@ -13661,7 +13661,7 @@ __metadata:
|
||||
optional: true
|
||||
react-native-web:
|
||||
optional: true
|
||||
checksum: 5589cde181d2825a9c25d0ec51b99873a335869989314c3cee3a4503444344a546ccc4705e1a0f76a4ec4faaa14433ae96fa8e2024d6550498b61b193c0fba5f
|
||||
checksum: 41ff5ff7e4cd99b2bc7453749a1f17ceb63bc9e1123285bc62a12315cac1fb087b58366db50e08b275e7dd0b1ce862df4c3acae51872f8e3aa8765ee9b61d4bf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user