Skip to content

Commit

Permalink
Split skeleton and normal state in all lists (#506)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoriya authored May 20, 2024
2 parents 9061b2e + 5393f1b commit a37ace7
Show file tree
Hide file tree
Showing 28 changed files with 733 additions and 541 deletions.
1 change: 1 addition & 0 deletions front/packages/models/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const getDisplayDate = (data: Show | Movie) => {
if (airDate) {
return airDate.getFullYear().toString();
}
return null;
};

export const useLocalSetting = (setting: string, def: string) => {
Expand Down
27 changes: 23 additions & 4 deletions front/packages/primitives/src/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { type ComponentType, type RefAttributes, forwardRef } from "react";
import { Image, type ImageProps, View, type ViewStyle } from "react-native";
import { type Stylable, px, useYoshiki } from "yoshiki/native";
import { Icon } from "./icons";
import { Skeleton } from "./skeleton";
import { P } from "./text";

const stringToColor = (string: string) => {
Expand All @@ -40,20 +41,19 @@ const stringToColor = (string: string) => {
return color;
};

export const Avatar = forwardRef<
const AvatarC = forwardRef<
View,
{
src?: string;
alt?: string;
size?: number;
placeholder?: string;
color?: string;
isLoading?: boolean;
fill?: boolean;
as?: ComponentType<{ style?: ViewStyle } & RefAttributes<View>>;
} & Stylable
>(function Avatar(
{ src, alt, size = px(24), color, placeholder, isLoading = false, fill = false, as, ...props },
>(function AvatarI(
{ src, alt, size = px(24), color, placeholder, fill = false, as, ...props },
ref,
) {
const { css, theme } = useYoshiki();
Expand Down Expand Up @@ -106,3 +106,22 @@ export const Avatar = forwardRef<
</Container>
);
});

const AvatarLoader = ({ size = px(24), ...props }: { size?: number }) => {
const { css } = useYoshiki();

return (
<Skeleton
variant="round"
{...css(
{
height: size,
width: size,
},
props,
)}
/>
);
};

export const Avatar = Object.assign(AvatarC, { Loader: AvatarLoader });
40 changes: 39 additions & 1 deletion front/packages/primitives/src/chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/

import type { TextProps } from "react-native";
import { type TextProps, View } from "react-native";
import { type Theme, px, rem, useYoshiki } from "yoshiki/native";
import { Link } from "./links";
import { Skeleton } from "./skeleton";
Expand Down Expand Up @@ -63,6 +63,7 @@ export const Chip = ({
pX: ts(2.5 * sizeMult),
borderRadius: ts(3),
overflow: "hidden",
justifyContent: "center",
},
outline && {
borderColor: color ?? ((theme: Theme) => theme.accent),
Expand Down Expand Up @@ -102,3 +103,40 @@ export const Chip = ({
</Link>
);
};

Chip.Loader = ({
color,
size = "medium",
outline = false,
...props
}: { color?: string; size?: "small" | "medium" | "large"; outline?: boolean }) => {
const { css } = useYoshiki();
const sizeMult = size === "medium" ? 1 : size === "small" ? 0.5 : 1.5;

return (
<View
{...css(
[
{
pY: ts(1 * sizeMult),
pX: ts(2.5 * sizeMult),
borderRadius: ts(3),
overflow: "hidden",
justifyContent: "center",
},
outline && {
borderColor: color ?? ((theme: Theme) => theme.accent),
borderStyle: "solid",
borderWidth: px(1),
},
!outline && {
bg: color ?? ((theme: Theme) => theme.accent),
},
],
props,
)}
>
<Skeleton {...css({ width: rem(3) })} />
</View>
);
};
2 changes: 1 addition & 1 deletion front/packages/primitives/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type IconProps = {
export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => {
const { css, theme } = useYoshiki();
const computed = css(
{ width: size, height: size, fill: color ?? theme.contrast } as any,
{ width: size, height: size, fill: color ?? theme.contrast, flexShrink: 0 } as any,
props,
) as any;

Expand Down
9 changes: 8 additions & 1 deletion front/packages/primitives/src/image/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

import { getCurrentToken } from "@kyoo/models";
import { useState } from "react";
import { type ReactElement, useState } from "react";
import { type FlexStyle, type ImageStyle, View, type ViewStyle } from "react-native";
import { Blurhash } from "react-native-blurhash";
import FastImage from "react-native-fast-image";
Expand Down Expand Up @@ -93,3 +93,10 @@ export const Image = ({
</View>
);
};

Image.Loader = ({ layout, ...props }: { layout: ImageLayout; children?: ReactElement }) => {
const { css } = useYoshiki();
const border = { borderRadius: 6, overflow: "hidden" } satisfies ViewStyle;

return <Skeleton variant="custom" show {...css([layout, border], props)} />;
};
9 changes: 8 additions & 1 deletion front/packages/primitives/src/image/image.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

import NextImage from "next/image";
import { useState } from "react";
import { type ReactElement, useState } from "react";
import { type ImageStyle, View, type ViewStyle } from "react-native";
import { useYoshiki } from "yoshiki/native";
import { imageBorderRadius } from "../constants";
Expand Down Expand Up @@ -73,3 +73,10 @@ export const Image = ({
</BlurhashContainer>
);
};

Image.Loader = ({ layout, ...props }: { layout: ImageLayout; children?: ReactElement }) => {
const { css } = useYoshiki();
const border = { borderRadius: 6, overflow: "hidden" } satisfies ViewStyle;

return <Skeleton variant="custom" show {...css([layout, border], props)} />;
};
12 changes: 10 additions & 2 deletions front/packages/primitives/src/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/

import { LinearGradient, type LinearGradientProps } from "expo-linear-gradient";
import type { ComponentProps, ComponentType, ReactNode } from "react";
import type { ComponentProps, ComponentType, ReactElement, ReactNode } from "react";
import { type ImageStyle, View, type ViewProps, type ViewStyle } from "react-native";
import { percent } from "yoshiki/native";
import { imageBorderRadius } from "../constants";
Expand All @@ -39,6 +39,14 @@ export const Poster = ({
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
}) => <Image alt={alt!} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />;

Poster.Loader = ({
layout,
...props
}: {
children?: ReactElement;
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
}) => <Image.Loader layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />;

export const PosterBackground = ({
alt,
layout,
Expand Down Expand Up @@ -86,7 +94,7 @@ export const ImageBackground = <AsProps = ViewProps>({
{({ css, theme }) => (
<Container
{...(css(
[layout, !hideLoad && { borderRadius: imageBorderRadius, overflow: "hidden" }],
[layout, { borderRadius: imageBorderRadius, overflow: "hidden" }],
asProps,
) as AsProps)}
>
Expand Down
121 changes: 54 additions & 67 deletions front/packages/primitives/src/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
*/

import { LinearGradient as LG } from "expo-linear-gradient";
import { AnimatePresence, MotiView, motify } from "moti";
import { MotiView, motify } from "moti";
import { useState } from "react";
import { Platform, View, type ViewProps } from "react-native";
import { em, percent, px, rem, useYoshiki } from "yoshiki/native";
import { hiddenIfNoJs } from "./utils/nojs";

const LinearGradient = motify(LG)();

Expand Down Expand Up @@ -99,71 +98,59 @@ export const Skeleton = ({
props,
)}
>
<AnimatePresence>
{children}
{(forcedShow || !children || children === true) &&
[...Array(lines)].map((_, i) => (
<MotiView
key={`skeleton_${i}`}
// No clue why it is a number on mobile and a string on web but /shrug
animate={{ opacity: Platform.OS === "web" ? ("1" as any) : 1 }}
exit={{ opacity: 0 }}
transition={{ type: "timing" }}
onLayout={(e) => setWidth(e.nativeEvent.layout.width)}
{...css(
[
{
bg: (theme) => theme.overlay0,
},
lines === 1 && {
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
lines !== 1 && {
width: i === lines - 1 ? percent(40) : percent(100),
height: rem(1.2),
marginBottom: rem(0.5),
overflow: "hidden",
borderRadius: px(6),
},
],
hiddenIfNoJs,
)}
>
<LinearGradient
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
colors={["transparent", theme.overlay1, "transparent"]}
transition={{
loop: true,
repeatReverse: false,
}}
animate={{
translateX: width
? [perc(-100), { value: perc(100), type: "timing", duration: 800, delay: 800 }]
: undefined,
}}
{...css([
{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
Platform.OS === "web" && {
// @ts-ignore Web only properties
animation: "skeleton 1.6s linear 0.5s infinite",
transform: "translateX(-100%)",
},
])}
/>
</MotiView>
))}
</AnimatePresence>
{(forcedShow || !children || children === true) &&
[...Array(lines)].map((_, i) => (
<MotiView
key={`skeleton_${i}`}
// No clue why it is a number on mobile and a string on web but /shrug
animate={{ opacity: Platform.OS === "web" ? ("1" as any) : 1 }}
exit={{ opacity: 0 }}
transition={{ type: "timing" }}
onLayout={(e) => setWidth(e.nativeEvent.layout.width)}
{...css([
{
bg: (theme) => theme.overlay0,
},
lines === 1 && {
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
},
lines !== 1 && {
width: i === lines - 1 ? percent(40) : percent(100),
height: rem(1.2),
marginBottom: rem(0.5),
overflow: "hidden",
borderRadius: px(6),
},
])}
>
<LinearGradient
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
colors={["transparent", theme.overlay1, "transparent"]}
transition={{
loop: true,
repeatReverse: false,
}}
animate={{
translateX: width
? [perc(-100), { value: perc(100), type: "timing", duration: 800, delay: 800 }]
: undefined,
}}
{...css({
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
})}
/>
</MotiView>
))}
{children}
</View>
);
};
Loading

0 comments on commit a37ace7

Please sign in to comment.