Finish movie's header rework

This commit is contained in:
Zoe Roux 2022-12-13 14:13:15 +09:00
parent e5b236f51c
commit 1b76bbf6c2
14 changed files with 464 additions and 283 deletions

View File

@ -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.11"
"yoshiki": "0.3.1"
},
"devDependencies": {
"@babel/core": "^7.19.3",

View File

@ -41,7 +41,7 @@
"react-native-web": "^0.18.10",
"solito": "^2.0.5",
"superjson": "^1.11.0",
"yoshiki": "0.2.11",
"yoshiki": "0.3.1",
"zod": "^3.19.1"
},
"devDependencies": {

View File

@ -19,7 +19,7 @@
*/
import { View, ViewProps } from "react-native";
import { useYoshiki } from "yoshiki/native";
import { percent, px, useYoshiki } from "yoshiki/native";
export const Container = (props: ViewProps) => {
const { css } = useYoshiki();
@ -29,12 +29,13 @@ export const Container = (props: ViewProps) => {
{...css(
{
display: "flex",
paddingHorizontal: "15px",
paddingHorizontal: px(15),
marginHorizontal: "auto",
width: {
sm: "540px",
md: "880px",
lg: "1170px",
xs: percent(100),
sm: px(540),
md: px(880),
lg: px(1170),
},
},
props,

View File

@ -0,0 +1,56 @@
/*
* 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 { HR as EHR } from "@expo/html-elements";
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
import { alpha, ts } from ".";
export const HR = ({
orientation,
...props
}: { orientation: "vertical" | "horizontal" } & Stylable) => {
const { css } = useYoshiki();
return (
<EHR
{...css(
[
{
bg: (theme) => alpha(theme.overlay0, 0.7),
borderWidth: 0,
},
orientation === "vertical" && {
width: px(1),
height: "auto",
marginVertical: ts(1),
marginHorizontal: ts(2),
},
orientation === "horizontal" && {
height: px(1),
width: "auto",
marginHorizontal: ts(1),
marginVertical: ts(2),
},
],
props,
)}
/>
);
};

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
export { Header, Main, Nav, Footer } from "@expo/html-elements";
export { Header, Main, Nav, Footer, UL } from "@expo/html-elements";
export * from "./text";
export * from "./themes";
export * from "./icons";
@ -28,11 +28,13 @@ export * from "./image";
export * from "./skeleton";
export * from "./tooltip";
export * from "./container";
export * from "./divider";
export * from "./animated";
export * from "./utils/breakpoints";
export * from "./utils/nojs";
export * from "./utils/head";
import { px } from "yoshiki/native";

View File

@ -43,10 +43,13 @@ export const A = ({
textProps={css(
{
// TODO: use a real font here.
fontFamily: Platform.OS === "web" ? theme.fonts.paragraph : undefined,
color: theme.paragraph,
// fontFamily: theme.fonts.paragraph,
color: theme.link,
},
{
selectable: true,
...props,
},
props,
)}
>
{children}
@ -83,8 +86,8 @@ export const Link = ({
})}
>
{Platform.select<ReactNode>({
android: <View {...props}>{children}</View>,
ios: <View {...props}>{children}</View>,
android: <View {...noFocusProps}>{children}</View>,
ios: <View {...noFocusProps}>{children}</View>,
default: children,
})}
</LinkCore>

View File

@ -46,11 +46,13 @@ export const SkeletonCss = () => (
export const Skeleton = ({
children,
show: forcedShow,
lines = 1,
variant = "text",
...props
}: Omit<ViewProps, "children"> & {
children?: JSX.Element | JSX.Element[] | boolean | null;
show?: boolean;
lines?: number;
variant?: "text" | "round" | "custom";
}) => {
const { css, theme } = useYoshiki();
@ -65,12 +67,13 @@ export const Skeleton = ({
[
{
position: "relative",
overflow: "hidden",
borderRadius: px(6),
},
variant === "text" && {
lines === 1 && { overflow: "hidden", borderRadius: px(6) },
variant === "text" &&
lines === 1 && {
width: percent(75),
height: rem(1.2),
marginBottom: rem(0.5),
},
variant === "round" && {
borderRadius: 9999999,
@ -81,23 +84,35 @@ export const Skeleton = ({
>
<AnimatePresence>
{children}
{(forcedShow || !children || children === true) && (
{(forcedShow || !children || children === true) &&
[...Array(lines)].map((_, i) => (
<MotiView
key="skeleton"
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" : 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,
)}
>
@ -130,7 +145,7 @@ export const Skeleton = ({
])}
/>
</MotiView>
)}
))}
</AnimatePresence>
</View>
);

View File

@ -19,8 +19,8 @@
*/
import { ComponentType, ComponentProps } from "react";
import { Platform, TextProps } from "react-native";
import { rem, useYoshiki } from "yoshiki/native";
import { Platform, Text, TextProps, TextStyle } from "react-native";
import { percent, px, rem, useYoshiki } from "yoshiki/native";
import {
H1 as EH1,
H2 as EH2,
@ -29,11 +29,14 @@ import {
H5 as EH5,
H6 as EH6,
P as EP,
LI as ELI,
} from "@expo/html-elements";
import { ts } from ".";
const styleText = (
Component: ComponentType<ComponentProps<typeof EP>>,
type?: "header" | "sub",
custom?: TextStyle,
) => {
const Text = (props: ComponentProps<typeof EP>) => {
const { css, theme } = useYoshiki();
@ -43,16 +46,13 @@ const styleText = (
{...css(
[
{
// TODO: use custom fonts on mobile also.
fontFamily:
Platform.OS === "web"
? type === "header"
? theme.fonts.heading
: theme.fonts.paragraph
: undefined,
marginTop: 0,
marginBottom: rem(0.5),
// fontFamily: type === "header" ? theme.fonts.heading : theme.fonts.paragraph,
color: type === "header" ? theme.heading : theme.paragraph,
},
type === "sub" && { fontWeight: "300", opacity: 0.8, fontSize: rem(0.8) },
custom,
],
props as TextProps,
)}
@ -62,12 +62,31 @@ const styleText = (
return Text;
};
export const H1 = styleText(EH1, "header");
export const H2 = styleText(EH2, "header");
export const H1 = styleText(EH1, "header", { fontSize: rem(3) });
export const H2 = styleText(EH2, "header", { fontSize: rem(2) });
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 P = styleText(EP, undefined, { fontSize: rem(1) });
export const SubP = styleText(EP, "sub");
export const LI = ({ children, ...props }: TextProps) => {
const { css } = useYoshiki();
return (
<P accessibilityRole="listitem" {...props}>
<Text
{...css({
height: percent(100),
marginBottom: rem(0.5),
paddingRight: ts(1),
})}
>
{String.fromCharCode(0x2022)}
</Text>
{children}
</P>
);
};

View File

@ -31,6 +31,7 @@ export const catppuccin: ThemeBuilder = {
appbar: "#e64553",
overlay0: "#9ca0b0",
overlay1: "#7c7f93",
link: "#1e66f5",
default: {
background: "#eff1f5",
accent: "#ea76cb",
@ -61,6 +62,7 @@ export const catppuccin: ThemeBuilder = {
appbar: "#94e2d5",
overlay0: "#6c7086",
overlay1: "#9399b2",
link: "#89b4fa",
default: {
background: "#1e1e2e",
accent: "#f5c2e7",

View File

@ -37,6 +37,7 @@ type Mode = {
appbar: Property.Color;
overlay0: Property.Color;
overlay1: Property.Color;
link: Property.Color;
variant: Variant;
colors: {
red: Property.Color;
@ -64,13 +65,13 @@ declare module "yoshiki" {
user: Mode & Variant;
}
}
// declare module "yoshiki/native" {
// export interface Theme extends ThemeSettings, Mode, Variant {
// light: Mode & Variant;
// dark: Mode & Variant;
// user: Mode & Variant;
// }
// }
declare module "yoshiki/native" {
export interface Theme extends ThemeSettings, Mode, Variant {
light: Mode & Variant;
dark: Mode & Variant;
user: Mode & Variant;
}
}
export type { Theme } from "yoshiki";
export type ThemeBuilder = ThemeSettings & {
@ -158,5 +159,5 @@ export const ContrastArea = ({
};
export const alpha = (color: Property.Color, alpha: number) => {
return color + (alpha * 255).toString(16);
return color + Math.round(alpha * 255).toString(16);
};

View File

@ -0,0 +1,23 @@
/*
* 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/>.
*/
export const Head = ({ title, description }: { title?: string | null; description?: string | null }) => {
return null;
};

View File

@ -0,0 +1,30 @@
/*
* 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 NextHead from "next/head";
export const Head = ({ title, description }: { title?: string | null; description?: string | null }) => {
return (
<NextHead>
{title && <title>{title + "- Kyoo"}</title>}
{description && <meta name="description" content={description} />}
</NextHead>
);
};

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { Movie, QueryIdentifier, Show, getDisplayDate } from "@kyoo/models";
import { Movie, QueryIdentifier, Show, getDisplayDate, Genre, Studio } from "@kyoo/models";
import {
Container,
H1,
@ -31,43 +31,40 @@ import {
Link,
IconButton,
IconFab,
Head,
HR,
H2,
UL,
LI,
A,
ts,
} from "@kyoo/primitives";
import { useTranslation } from "react-i18next";
import { Platform, StyleSheet, View } from "react-native";
import { em, percent, rem, vh, useYoshiki, Stylable } from "yoshiki/native";
import { Fetch, WithLoading } from "../fetch";
import { StyleSheet, View } from "react-native";
import {
Theme,
sm,
md,
px,
min,
max,
em,
percent,
rem,
vh,
useYoshiki,
Stylable,
} from "yoshiki/native";
import { Fetch } from "../fetch";
import { Navbar } from "../navbar";
// const StudioText = ({
// studio,
// loading = false,
// sx,
// }: {
// studio?: Studio | null;
// loading?: boolean;
// sx?: SxProps;
// }) => {
// const { t } = useTranslation("browse");
// if (!loading && !studio) return null;
// return (
// <Typography sx={sx}>
// {t("show.studio")}:{" "}
// {loading ? (
// <Skeleton width="5rem" sx={{ display: "inline-flex" }} />
// ) : (
// <Link href={`/studio/${studio!.slug}`}>{studio!.name}</Link>
// )}
// </Typography>
// );
// };
const TitleLine = ({
isLoading,
slug,
name,
date,
poster,
studio,
...props
}: {
isLoading: boolean;
@ -75,6 +72,7 @@ const TitleLine = ({
name?: string;
date?: string;
poster?: string | null;
studio?: Studio | null;
} & Stylable) => {
const { css, theme } = useYoshiki();
const { t } = useTranslation();
@ -83,11 +81,17 @@ const TitleLine = ({
<Container
{...css(
{
flexDirection: { xs: "column", sm: "row" },
alignItems: { xs: "center", sm: "flex-start" },
flexDirection: { xs: "column", md: "row" },
},
props,
)}
>
<View
{...css({
flexDirection: { xs: "column", sm: "row" },
alignItems: { xs: "center", sm: "flex-start" },
flexGrow: 1,
})}
>
<Poster
src={poster}
@ -104,18 +108,16 @@ const TitleLine = ({
alignItems: { xs: "center", sm: "flex-start" },
paddingLeft: { sm: em(2.5) },
flexShrink: 1,
flexGrow: 1,
})}
>
<Skeleton {...css({ width: rem(15), height: rem(3), marginBottom: rem(0.5) })}>
<Skeleton {...css({ width: rem(15), height: rem(3) })}>
{isLoading || (
<H1
{...css({
fontWeight: { md: "900" },
fontSize: rem(3),
marginTop: 0,
marginBottom: rem(0.5),
textAlign: { xs: "center", sm: "flex-start" },
color: (theme) => ({ xs: theme.user.heading, md: theme.heading }),
textAlign: { xs: "center", sm: "left" },
color: (theme: Theme) => ({ xs: theme.user.heading, md: theme.heading }),
})}
>
{name}
@ -127,7 +129,6 @@ const TitleLine = ({
{...css({
width: rem(5),
height: rem(1.5),
marginBottom: rem(0.5),
})}
>
{isLoading || (
@ -136,10 +137,8 @@ const TitleLine = ({
fontWeight: "300",
fontSize: rem(1.5),
letterSpacing: 0,
marginTop: 0,
marginBottom: rem(0.5),
textAlign: { xs: "center", sm: "flex-start" },
color: (theme) => ({ xs: theme.user.heading, md: theme.heading }),
textAlign: { xs: "center", sm: "left" },
color: (theme: Theme) => ({ xs: theme.user.heading, md: theme.heading }),
})}
>
{date}
@ -147,7 +146,7 @@ const TitleLine = ({
)}
</Skeleton>
)}
<View {...css({ flexDirection: "row" })} /*sx={{ "& > *": { m: ".3rem !important" } }} */>
<View {...css({ flexDirection: "row" })}>
<IconFab
icon="play-arrow"
as={Link}
@ -163,82 +162,116 @@ const TitleLine = ({
/>
</View>
</View>
{/* <View */}
{/* {...css({ */}
{/* display: { xs: "none", md: "flex" }, */}
{/* flexDirection: "column", */}
{/* alignSelf: "flex-end", */}
{/* paddingRight: px(15), */}
{/* })} */}
{/* > */}
{/* {(isLoading || logo || true) && ( */}
{/* <Image */}
{/* src={logo} */}
{/* alt="" */}
{/* layout={{ */}
{/* width: "100%", */}
{/* height: px(100), */}
{/* }} */}
{/* // sx={{ display: { xs: "none", lg: "unset" } }} */}
{/* /> */}
{/* )} */}
{/* {/1* <StudioText loading={!data} studio={data?.studio} sx={{ mt: "auto", mb: 3 }} /> *1/} */}
{/* </View> */}
</View>
<View
{...css([
{
paddingTop: { xs: ts(3), sm: ts(8) },
alignSelf: { xs: "flex-start", md: "flex-end" },
justifyContent: "flex-end",
flexDirection: "column",
},
md({
position: "absolute",
top: 0,
bottom: 0,
right: 0,
width: percent(25),
height: percent(100),
paddingRight: ts(3),
}),
])}
>
<P
{...css({
color: (theme) => theme.user.paragraph,
})}
>
{t("show.studio")}:{" "}
{isLoading ? (
<Skeleton />
) : (
<A href={`/studio/${studio!.slug}`} {...css({ color: (theme) => theme.user.link })}>
{studio!.name}
</A>
)}
</P>
</View>
</Container>
);
};
// const Tata = () => {
// return (
// <Container sx={{ pt: 2 }}>
// <Typography align="justify" sx={{ flexBasis: 0, flexGrow: 1, pt: { sm: 2 } }}>
// {data
// ? data.overview ?? t("show.noOverview")
// : [...Array(4)].map((_, i) => <Skeleton key={i} />)}
// </Typography>
// <Divider
// orientation="vertical"
// variant="middle"
// flexItem
// sx={{ mx: 2, display: { xs: "none", sm: "block" } }}
// />
// <Box sx={{ flexBasis: "25%", display: { xs: "none", sm: "block" } }}>
// <StudioText
// loading={!data}
// studio={data?.studio}
// sx={{ display: { xs: "none", sm: "block", md: "none" }, pb: 2 }}
// />
const Description = ({
isLoading,
overview,
genres,
...props
}: {
isLoading: boolean;
overview?: string | null;
genres?: Genre[];
} & Stylable) => {
const { t } = useTranslation();
const { css } = useYoshiki();
// <Typography variant="h4" component="h2">
// {t("show.genre")}
// </Typography>
// {!data || data.genres?.length ? (
// <ul>
// {(data ? data.genres! : [...Array(3)]).map((genre, i) => (
// <li key={genre?.id ?? i}>
// <Typography>
// {genre ? <Link href={`/genres/${genre.slug}`}>{genre.name}</Link> : <Skeleton />}
// </Typography>
// </li>
// ))}
// </ul>
// ) : (
// <Typography>{t("show.genre-none")}</Typography>
// )}
// </Box>
// </Container>
// );
// };
return (
<Container {...css({ flexDirection: { xs: "column", sm: "row" } }, props)}>
<P
{...css({
display: { xs: "flex", sm: "none" },
color: (theme: Theme) => theme.user.paragraph,
})}
>
{t("show.genre")}:{" "}
{(isLoading ? [...Array(3)] : genres!).map((genre, i) => (
<>
{i !== 0 && ", "}
{isLoading ? (
<Skeleton key={i} />
) : (
<A key={genre.slug} href={`/genres/${genre.slug}`}>
{genre.name}
</A>
)}
</>
))}
</P>
const min = Platform.OS === "web"
? (...values: number[]): number => `min(${values.join(", ")})` as unknown as number
: (...values: number[]): number => Math.min(...values);
const max = Platform.OS === "web"
? (...values: number[]): number => `max(${values.join(", ")})` as unknown as number
: (...values: number[]): number => Math.max(...values);
const px = Platform.OS === "web"
? (value: number): number => `${value}px` as unknown as number
: (value: number): number => value;
<Skeleton
lines={4}
{...css({ width: percent(100), flexBasis: 0, flexGrow: 1, paddingTop: ts(4) })}
>
{isLoading || (
<P {...css({ flexBasis: 0, flexGrow: 1, textAlign: "justify", paddingTop: ts(4) })}>
{overview ?? t("show.noOverview")}
</P>
)}
</Skeleton>
<HR
orientation="vertical"
{...css({ marginX: ts(2), display: { xs: "none", sm: "flex" } })}
/>
<View {...css({ flexBasis: percent(25), display: { xs: "none", sm: "flex" } })}>
<H2>{t("show.genre")}</H2>
{isLoading || genres?.length ? (
<UL>
{(isLoading ? [...Array(3)] : genres!).map((genre, i) => (
<LI key={genre?.id ?? i}>
{isLoading ? (
<Skeleton {...css({ marginBottom: 0 })} />
) : (
<A href={`/genres/${genre.slug}`}>{genre.name}</A>
)}
</LI>
))}
</UL>
) : (
<P>{t("show.genre-none")}</P>
)}
</View>
</Container>
);
};
export const ShowHeader = ({
query,
@ -256,19 +289,22 @@ export const ShowHeader = ({
<Navbar {...css({ bg: "transparent" })} />
<Fetch query={query}>
{({ isLoading, ...data }) => (
<>
{/* TODO: HEAD element for SEO*/}
<Main {...css(StyleSheet.absoluteFillObject)}>
<Head title={data?.name} description={data?.overview} />
{/* TODO: Add a shadow on navbar items */}
{/* TODO: Put the navbar outside of the scrollbox */}
<ImageBackground
src={data?.thumbnail}
alt=""
as={Main}
containerStyle={{
height: { xs: vh(40), sm: min(vh(60), px(750)), lg: vh(70) },
minHeight: { xs: px(350), sm: px(500), lg: px(600) },
height: {
xs: vh(40),
sm: min(vh(60), px(750)),
md: min(vh(60), px(680)),
lg: vh(70),
},
minHeight: { xs: px(350), sm: px(300), md: px(400), lg: px(600) },
}}
{...css(StyleSheet.absoluteFillObject)}
>
<TitleLine
isLoading={isLoading}
@ -276,31 +312,24 @@ export const ShowHeader = ({
name={data?.name}
date={data ? getDisplayDate(data as any) : undefined}
poster={data?.poster}
studio={data?.studio}
{...css({
marginTop: { xs: max(vh(20), px(200)), sm: vh(45), md: vh(35) }
marginTop: {
xs: max(vh(20), px(200)),
sm: vh(45),
md: max(vh(30), px(150)),
lg: max(vh(35), px(200)),
},
})}
/>
{/* <Container sx={{ display: { xs: "block", sm: "none" }, pt: 3 }}> */}
{/* <StudioText loading={!data} studio={data?.studio} sx={{ mb: 1 }} /> */}
{/* <Typography sx={{ mb: 1 }}> */}
{/* {t("show.genre")} */}
{/* {": "} */}
{/* {!data ? ( */}
{/* <Skeleton width="10rem" sx={{ display: "inline-flex" }} /> */}
{/* ) : data?.genres && data.genres.length ? ( */}
{/* data.genres.map((genre, i) => [ */}
{/* i > 0 && ", ", */}
{/* <Link key={genre.id} href={`/genres/${genre.slug}`}> */}
{/* {genre.name} */}
{/* </Link>, */}
{/* ]) */}
{/* ) : ( */}
{/* t("show.genre-none") */}
{/* )} */}
{/* </Typography> */}
{/* </Container> */}
</ImageBackground>
</>
<Description
isLoading={isLoading}
overview={data?.overview}
genres={data?.genres}
{...css({ paddingTop: { xs: 0, md: ts(2) } })}
/>
</Main>
)}
</Fetch>
</>

View File

@ -9682,7 +9682,7 @@ __metadata:
react-native-screens: ~3.18.0
react-native-svg: 13.4.0
typescript: ^4.6.3
yoshiki: 0.2.11
yoshiki: 0.3.1
languageName: unknown
linkType: soft
@ -13319,7 +13319,7 @@ __metadata:
superjson: ^1.11.0
typescript: ^4.9.3
webpack: ^5.75.0
yoshiki: 0.2.11
yoshiki: 0.3.1
zod: ^3.19.1
languageName: unknown
linkType: soft
@ -13644,9 +13644,9 @@ __metadata:
languageName: node
linkType: hard
"yoshiki@npm:0.2.11":
version: 0.2.11
resolution: "yoshiki@npm:0.2.11"
"yoshiki@npm:0.3.1":
version: 0.3.1
resolution: "yoshiki@npm:0.3.1"
dependencies:
"@types/node": 18.x.x
"@types/react": 18.x.x
@ -13661,7 +13661,7 @@ __metadata:
optional: true
react-native-web:
optional: true
checksum: 5a2bbb62b2270d3456f114cfbb24a84ad6b8a94b147687929ccffe2d179560ef40b46df1d4054eda91310d295a9a674bbb201765deb86dc96a2133bfd702235a
checksum: 9448b628b61bbcc4485af7aed667a1c0f8490a2066fa35953b4a02126f1d31d94f90e27a592797f8ecedc0ce2220976a7651ba989f4ff3c68513496b2f9fdd0b
languageName: node
linkType: hard