Rework chip to support links and skeletons

This commit is contained in:
Zoe Roux 2023-12-11 01:20:43 +01:00
parent 0272b82166
commit 9304c7ad2f
7 changed files with 77 additions and 47 deletions

View File

@ -28,7 +28,7 @@ export const Button = ({
text, text,
...props ...props
}: { text: string } & ComponentProps<typeof PressableFeedback>) => { }: { text: string } & ComponentProps<typeof PressableFeedback>) => {
const { css } = useYoshiki(); const { css } = useYoshiki("button");
return ( return (
<PressableFeedback <PressableFeedback

View File

@ -18,54 +18,88 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native"; import { px, rem, Theme, useYoshiki } from "yoshiki/native";
import { Link } from "./links";
import { P } from "./text"; import { P } from "./text";
import { ts } from "./utils"; import { capitalize, ts } from "./utils";
import { A } from "./links"; import { EnhancedStyle } from "yoshiki/src/native/type";
import { ComponentType } from "react"; import { TextProps, TextStyle, ViewStyle } from "react-native";
import { Skeleton } from "./skeleton";
export const Chip = <AsProps = { label: string },>({ export const Chip = ({
color, color,
size = "medium", size = "medium",
outline = false, outline = false,
as, label,
href,
replace,
target,
textProps,
...props ...props
}: { }: {
color?: string; color?: string;
size?: "small" | "medium" | "large"; size?: "small" | "medium" | "large";
outline?: boolean; outline?: boolean;
as?: ComponentType<AsProps>; label?: string;
} & AsProps) => { href?: string;
const { css } = useYoshiki(); replace?: boolean;
target?: string;
textProps?: TextProps;
}) => {
const { css } = useYoshiki("chip");
textProps ??= {};
const sizeMult = size == "medium" ? 1 : size == "small" ? 0.5 : 1.5; const sizeMult = size == "medium" ? 1 : size == "small" ? 0.5 : 1.5;
const As = as ?? (P as any);
// @ts-ignore backward compatibilty
if (!as && props.label) props.children = props.label;
return ( return (
<As <Link
href={href}
replace={replace}
target={target}
{...css( {...css(
[ [
{ {
pY: ts(1 * sizeMult), pY: ts(1 * sizeMult),
pX: ts(2.5 * sizeMult), pX: ts(2.5 * sizeMult),
borderRadius: ts(3), borderRadius: ts(3),
fontSize: rem(0.8), overflow: "hidden",
},
!outline && {
color: (theme: Theme) => theme.alternate.contrast,
bg: color ?? ((theme: Theme) => theme.accent),
}, },
outline && { outline && {
borderColor: color ?? ((theme: Theme) => theme.accent), borderColor: color ?? ((theme: Theme) => theme.accent),
borderStyle: "solid", borderStyle: "solid",
borderWidth: px(1), borderWidth: px(1),
fover: {
self: {
bg: (theme: Theme) => theme.accent,
},
text: {
color: (theme: Theme) => theme.alternate.contrast,
},
},
},
!outline && {
bg: color ?? ((theme: Theme) => theme.accent),
}, },
], ],
props, props,
)} )}
/> >
<P
{...css(
[
"text",
{
marginVertical: 0,
fontSize: rem(0.8),
color: (theme: Theme) => (outline ? theme.contrast : theme.alternate.contrast),
},
],
textProps,
)}
>
{label ? capitalize(label) : <Skeleton {...css({ width: rem(3) })} />}
</P>
</Link>
); );
}; };

View File

@ -19,7 +19,7 @@
*/ */
import { forwardRef, ReactNode } from "react"; import { forwardRef, ReactNode } from "react";
import { Platform, Pressable, TextProps, View, PressableProps } from "react-native"; import { Platform, Pressable, TextProps, View, PressableProps, Linking } from "react-native";
import { TextLink, useLink } from "solito/link"; import { TextLink, useLink } from "solito/link";
import { useTheme, useYoshiki } from "yoshiki/native"; import { useTheme, useYoshiki } from "yoshiki/native";
import { alpha } from "./themes"; import { alpha } from "./themes";
@ -107,7 +107,16 @@ export const Link = ({
// @ts-ignore Missing hrefAttrs type definition. // @ts-ignore Missing hrefAttrs type definition.
linkProps.hrefAttrs = { ...linkProps.hrefAttrs, target }; linkProps.hrefAttrs = { ...linkProps.hrefAttrs, target };
return ( return (
<PressableFeedback {...linkProps} {...props}> <PressableFeedback
{...linkProps}
{...props}
onPress={(e?: any) => {
props?.onPress?.(e);
if (e?.defaultPrevented) return;
if (Platform.OS !== "web" && href?.includes("://")) Linking.openURL(href);
else linkProps.onPress(e);
}}
>
{children} {children}
</PressableFeedback> </PressableFeedback>
); );

View File

@ -30,7 +30,7 @@ export const Rating = ({ rating, color }: { rating?: number; color: Breakpoint<s
<View {...css({ flexDirection: "row", alignItems: "center" })}> <View {...css({ flexDirection: "row", alignItems: "center" })}>
<Icon icon={Star} color={color} {...css({ marginRight: ts(0.5) })} /> <Icon icon={Star} color={color} {...css({ marginRight: ts(0.5) })} />
<Skeleton {...css({ width: rem(2) })}> <Skeleton {...css({ width: rem(2) })}>
{rating !== undefined && <P {...css({ color })}>{rating ? rating / 10 : "??"} / 10</P>} {rating !== undefined && <P {...css({ color, verticalAlign: "middle" })}>{rating ? rating / 10 : "??"} / 10</P>}
</Skeleton> </Skeleton>
</View> </View>
); );

View File

@ -172,7 +172,7 @@ export const EpisodeLine = ({
watchedStatus: WatchStatusV | null; watchedStatus: WatchStatusV | null;
}> & }> &
Stylable) => { Stylable) => {
const { css } = useYoshiki(); const { css } = useYoshiki("episode-line");
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@ -52,7 +52,7 @@ import {
} from "@kyoo/primitives"; } from "@kyoo/primitives";
import { Fragment } from "react"; import { Fragment } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ImageStyle, Platform, View } from "react-native"; import { ImageStyle, Platform, Text, View } from "react-native";
import { import {
Theme, Theme,
md, md,
@ -458,28 +458,17 @@ export const Header = ({
<P {...css({ marginRight: ts(0.5), textAlign: "center" })}>{t("show.links")}:</P> <P {...css({ marginRight: ts(0.5), textAlign: "center" })}>{t("show.links")}:</P>
{(!isLoading {(!isLoading
? Object.entries(data.externalId!).filter(([_, data]) => data.link) ? Object.entries(data.externalId!).filter(([_, data]) => data.link)
: [...Array(3)].map((_, i) => [i, undefined] as const) : [...Array(3)].map((_) => [undefined, undefined] as const)
).map(([name, data]) => ( ).map(([name, data], i) => (
<Chip <Chip
key={name} key={name ?? i}
as={A} label={name}
href={data?.link} href={data?.link || undefined}
target="_blank" target="_blank"
size="small" size="small"
outline outline
{...(css({ {...css({ m: ts(0.5) })}
m: ts(0.5), />
color: (theme: Theme) => theme.contrast,
fover: {
self: {
color: (theme: Theme) => theme.alternate.contrast,
bg: (theme: Theme) => theme.accent,
},
},
}) as any)}
>
{data ? capitalize(name) : <Skeleton {...css({ width: rem(3) })} />}
</Chip>
))} ))}
</Container> </Container>
{type === "show" && (data.watchStatus as ShowWatchStatus)?.nextEpisode && ( {type === "show" && (data.watchStatus as ShowWatchStatus)?.nextEpisode && (

View File

@ -166,9 +166,7 @@ export const ItemDetails = ({
{(isLoading || genres) && ( {(isLoading || genres) && (
<ScrollView horizontal {...css({ alignItems: "center" })}> <ScrollView horizontal {...css({ alignItems: "center" })}>
{(genres || [...Array(3)])?.map((x, i) => ( {(genres || [...Array(3)])?.map((x, i) => (
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}> <Chip key={x ?? i} label={x} size="small" {...css({ mX: ts(0.5) })} />
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
</Chip>
))} ))}
</ScrollView> </ScrollView>
)} )}