Create a usePopup to simplify popup creation

This commit is contained in:
Zoe Roux 2024-01-22 18:13:47 +01:00
parent d9f4a6ff8d
commit 53ac4a2050
4 changed files with 97 additions and 101 deletions

View File

@ -18,10 +18,10 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ReactNode } from "react"; import { ReactElement, ReactNode, useCallback, useEffect, useState } from "react";
import { Container } from "./container"; import { Container } from "./container";
import { Portal } from "@gorhom/portal"; import { Portal, usePortal } from "@gorhom/portal";
import { SwitchVariant, YoshikiFunc } from "./themes"; import { ContrastArea, SwitchVariant, YoshikiFunc } from "./themes";
import { View } from "react-native"; import { View } from "react-native";
import { imageBorderRadius } from "./constants"; import { imageBorderRadius } from "./constants";
import { px } from "yoshiki/native"; import { px } from "yoshiki/native";
@ -29,7 +29,7 @@ import { ts } from "./utils";
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => { export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
return ( return (
<Portal> <ContrastArea mode="user">
<SwitchVariant> <SwitchVariant>
{({ css, theme }) => ( {({ css, theme }) => (
<View <View
@ -60,6 +60,19 @@ export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFun
</View> </View>
)} )}
</SwitchVariant> </SwitchVariant>
</Portal> </ContrastArea>
); );
}; };
export const usePopup = () => {
const { addPortal, removePortal } = usePortal();
const [current, setPopup] = useState<ReactNode>();
const close = useCallback(() => setPopup(undefined), []);
useEffect(() => {
addPortal("popup", current);
return () => removePortal("popup");
}, [current, addPortal, removePortal]);
return [setPopup, close];
};

View File

@ -18,7 +18,7 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { IconButton, Menu, tooltip } from "@kyoo/primitives"; import { IconButton, Menu, tooltip, usePopup } from "@kyoo/primitives";
import { ComponentProps, ReactElement, useEffect, useState } from "react"; import { ComponentProps, ReactElement, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg"; import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
@ -51,7 +51,7 @@ export const EpisodesContext = ({
const downloader = useDownloader(); const downloader = useDownloader();
const { css } = useYoshiki(); const { css } = useYoshiki();
const { t } = useTranslation(); const { t } = useTranslation();
const [popup, setPopup] = useState<ReactElement | undefined>(undefined); const [setPopup, close] = usePopup();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
@ -109,19 +109,12 @@ export const EpisodesContext = ({
label={t("home.episodeMore.mediainfo")} label={t("home.episodeMore.mediainfo")}
icon={MovieInfo} icon={MovieInfo}
onSelect={() => onSelect={() =>
setPopup( setPopup(<MediaInfoPopup mediaType={type} mediaSlug={slug} close={close} />)
<MediaInfoPopup
mediaType={type}
mediaSlug={slug}
close={() => setPopup(undefined)}
/>,
)
} }
/> />
</> </>
)} )}
</Menu> </Menu>
{popup}
</> </>
); );
}; };

View File

@ -47,9 +47,9 @@ import {
ts, ts,
Chip, Chip,
DottedSeparator, DottedSeparator,
focusReset, usePopup,
} from "@kyoo/primitives"; } from "@kyoo/primitives";
import { Fragment, ReactElement, useState } from "react"; import { Fragment } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg"; import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
import { ImageStyle, Platform, View } from "react-native"; import { ImageStyle, Platform, View } from "react-native";
@ -112,7 +112,7 @@ export const TitleLine = ({
const { css, theme } = useYoshiki(); const { css, theme } = useYoshiki();
const { t } = useTranslation(); const { t } = useTranslation();
const downloader = useDownloader(); const downloader = useDownloader();
const [popup, setPopup] = useState<ReactElement | undefined>(undefined); const [setPopup, close] = usePopup();
return ( return (
<Container <Container
@ -246,30 +246,22 @@ export const TitleLine = ({
/> />
)} )}
{type === "movie" && slug && ( {type === "movie" && slug && (
<>
<IconButton <IconButton
icon={Download} icon={Download}
onPress={() => downloader(type, slug)} onPress={() => downloader(type, slug)}
color={{ xs: theme.user.contrast, md: theme.colors.white }} color={{ xs: theme.user.contrast, md: theme.colors.white }}
{...tooltip(t("home.episodeMore.download"))} {...tooltip(t("home.episodeMore.download"))}
/> />
)}
{type === "movie" && (
<IconButton <IconButton
icon={MovieInfo} icon={MovieInfo}
color={{ xs: theme.user.contrast, md: theme.colors.white }} color={{ xs: theme.user.contrast, md: theme.colors.white }}
onPress={() => onPress={() =>
slug && setPopup(<MediaInfoPopup mediaType={"movie"} mediaSlug={slug!} close={close} />)
setPopup(
<MediaInfoPopup
mediaType={type}
mediaSlug={slug}
close={() => setPopup(undefined)}
/>,
)
} }
/> />
</>
)} )}
{popup}
{rating !== null && ( {rating !== null && (
<> <>
<DottedSeparator <DottedSeparator

View File

@ -45,6 +45,7 @@ import {
SwitchVariant, SwitchVariant,
imageBorderRadius, imageBorderRadius,
ts, ts,
usePopup,
} from "@kyoo/primitives"; } from "@kyoo/primitives";
import { DefaultLayout } from "../layout"; import { DefaultLayout } from "../layout";
import { Children, ComponentProps, ReactElement, ReactNode, useState } from "react"; import { Children, ComponentProps, ReactElement, ReactNode, useState } from "react";
@ -257,10 +258,11 @@ const ChangePasswordPopup = ({
); );
}; };
const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void }) => { const AccountSettings = () => {
const account = useAccount(); const account = useAccount();
const { css, theme } = useYoshiki(); const { css, theme } = useYoshiki();
const { t } = useTranslation(); const { t } = useTranslation();
const [setPopup, close] = usePopup();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutateAsync } = useMutation({ const { mutateAsync } = useMutation({
@ -335,7 +337,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
label={t("settings.account.username.label")} label={t("settings.account.username.label")}
inital={account.username} inital={account.username}
apply={async (v) => await mutateAsync({ username: v })} apply={async (v) => await mutateAsync({ username: v })}
close={() => setPopup(undefined)} close={close}
/>, />,
) )
} }
@ -356,7 +358,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
label={t("settings.account.email.label")} label={t("settings.account.email.label")}
inital={account.email} inital={account.email}
apply={async (v) => await mutateAsync({ email: v })} apply={async (v) => await mutateAsync({ email: v })}
close={() => setPopup(undefined)} close={close}
/>, />,
) )
} }
@ -375,7 +377,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
icon={Password} icon={Password}
label={t("settings.account.password.label")} label={t("settings.account.password.label")}
apply={async (op, np) => await editPassword({ oldPassword: op, newPassword: np })} apply={async (op, np) => await editPassword({ oldPassword: op, newPassword: np })}
close={() => setPopup(undefined)} close={close}
/>, />,
) )
} }
@ -389,11 +391,9 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
export const SettingsPage: QueryPage = () => { export const SettingsPage: QueryPage = () => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const languages = new Intl.DisplayNames([i18n.language ?? "en"], { type: "language" }); const languages = new Intl.DisplayNames([i18n.language ?? "en"], { type: "language" });
const [popup, setPopup] = useState<ReactElement | undefined>(undefined);
const theme = useUserTheme("auto"); const theme = useUserTheme("auto");
return ( return (
<>
<ScrollView contentContainerStyle={{ gap: ts(4), paddingBottom: ts(4) }}> <ScrollView contentContainerStyle={{ gap: ts(4), paddingBottom: ts(4) }}>
<SettingsContainer title={t("settings.general.label")}> <SettingsContainer title={t("settings.general.label")}>
<Preference <Preference
@ -427,7 +427,7 @@ export const SettingsPage: QueryPage = () => {
/> />
</Preference> </Preference>
</SettingsContainer> </SettingsContainer>
<AccountSettings setPopup={setPopup} /> <AccountSettings />
<SettingsContainer title={t("settings.about.label")}> <SettingsContainer title={t("settings.about.label")}>
<Link <Link
href="https://github.com/zoriya/kyoo/releases/latest/download/kyoo.apk" href="https://github.com/zoriya/kyoo/releases/latest/download/kyoo.apk"
@ -448,8 +448,6 @@ export const SettingsPage: QueryPage = () => {
</Link> </Link>
</SettingsContainer> </SettingsContainer>
</ScrollView> </ScrollView>
{popup}
</>
); );
}; };