Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split skeleton and normal state in all lists #506

Merged
merged 9 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading