diff --git a/front/packages/primitives/src/popup.tsx b/front/packages/primitives/src/popup.tsx
index 18800133..ae9963d0 100644
--- a/front/packages/primitives/src/popup.tsx
+++ b/front/packages/primitives/src/popup.tsx
@@ -18,10 +18,10 @@
* along with Kyoo. If not, see .
*/
-import { ReactNode } from "react";
+import { ReactElement, ReactNode, useCallback, useEffect, useState } from "react";
import { Container } from "./container";
-import { Portal } from "@gorhom/portal";
-import { SwitchVariant, YoshikiFunc } from "./themes";
+import { Portal, usePortal } from "@gorhom/portal";
+import { ContrastArea, SwitchVariant, YoshikiFunc } from "./themes";
import { View } from "react-native";
import { imageBorderRadius } from "./constants";
import { px } from "yoshiki/native";
@@ -29,7 +29,7 @@ import { ts } from "./utils";
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc }) => {
return (
-
+
{({ css, theme }) => (
)}
-
+
);
};
+
+export const usePopup = () => {
+ const { addPortal, removePortal } = usePortal();
+ const [current, setPopup] = useState();
+ const close = useCallback(() => setPopup(undefined), []);
+
+ useEffect(() => {
+ addPortal("popup", current);
+ return () => removePortal("popup");
+ }, [current, addPortal, removePortal]);
+
+ return [setPopup, close];
+};
diff --git a/front/packages/ui/src/components/context-menus.tsx b/front/packages/ui/src/components/context-menus.tsx
index 15ef6d8d..71ad0eac 100644
--- a/front/packages/ui/src/components/context-menus.tsx
+++ b/front/packages/ui/src/components/context-menus.tsx
@@ -18,7 +18,7 @@
* along with Kyoo. If not, see .
*/
-import { IconButton, Menu, tooltip } from "@kyoo/primitives";
+import { IconButton, Menu, tooltip, usePopup } from "@kyoo/primitives";
import { ComponentProps, ReactElement, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
@@ -51,7 +51,7 @@ export const EpisodesContext = ({
const downloader = useDownloader();
const { css } = useYoshiki();
const { t } = useTranslation();
- const [popup, setPopup] = useState(undefined);
+ const [setPopup, close] = usePopup();
const queryClient = useQueryClient();
const mutation = useMutation({
@@ -109,19 +109,12 @@ export const EpisodesContext = ({
label={t("home.episodeMore.mediainfo")}
icon={MovieInfo}
onSelect={() =>
- setPopup(
- setPopup(undefined)}
- />,
- )
+ setPopup()
}
/>
>
)}
- {popup}
>
);
};
diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx
index 8193003f..4a1a8b4c 100644
--- a/front/packages/ui/src/details/header.tsx
+++ b/front/packages/ui/src/details/header.tsx
@@ -47,9 +47,9 @@ import {
ts,
Chip,
DottedSeparator,
- focusReset,
+ usePopup,
} from "@kyoo/primitives";
-import { Fragment, ReactElement, useState } from "react";
+import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
import { ImageStyle, Platform, View } from "react-native";
@@ -112,7 +112,7 @@ export const TitleLine = ({
const { css, theme } = useYoshiki();
const { t } = useTranslation();
const downloader = useDownloader();
- const [popup, setPopup] = useState(undefined);
+ const [setPopup, close] = usePopup();
return (
)}
{type === "movie" && slug && (
- downloader(type, slug)}
- color={{ xs: theme.user.contrast, md: theme.colors.white }}
- {...tooltip(t("home.episodeMore.download"))}
- />
+ <>
+ downloader(type, slug)}
+ color={{ xs: theme.user.contrast, md: theme.colors.white }}
+ {...tooltip(t("home.episodeMore.download"))}
+ />
+
+ setPopup()
+ }
+ />
+ >
)}
- {type === "movie" && (
-
- slug &&
- setPopup(
- setPopup(undefined)}
- />,
- )
- }
- />
- )}
- {popup}
{rating !== null && (
<>
void }) => {
+const AccountSettings = () => {
const account = useAccount();
const { css, theme } = useYoshiki();
const { t } = useTranslation();
+ const [setPopup, close] = usePopup();
const queryClient = useQueryClient();
const { mutateAsync } = useMutation({
@@ -335,7 +337,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
label={t("settings.account.username.label")}
inital={account.username}
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")}
inital={account.email}
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}
label={t("settings.account.password.label")}
apply={async (op, np) => await editPassword({ oldPassword: op, newPassword: np })}
- close={() => setPopup(undefined)}
+ close={close}
/>,
)
}
@@ -389,67 +391,63 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
export const SettingsPage: QueryPage = () => {
const { t, i18n } = useTranslation();
const languages = new Intl.DisplayNames([i18n.language ?? "en"], { type: "language" });
- const [popup, setPopup] = useState(undefined);
const theme = useUserTheme("auto");
return (
- <>
-
-
-
+
+
+
- setUserTheme(value)}
+ values={["auto", "light", "dark"]}
+ getLabel={(key) => t(`settings.general.theme.${key}`)}
+ />
+
+
+
-
- i18n.changeLanguage(value !== "system" ? value : (i18n.options.lng as string))
- }
- values={["system", ...Object.keys(i18n.options.resources!)]}
- getLabel={(key) =>
- key === "system" ? t("settings.general.language.system") : languages.of(key) ?? key
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
- {popup}
- >
+ value={i18n.resolvedLanguage!}
+ onValueChange={(value) =>
+ i18n.changeLanguage(value !== "system" ? value : (i18n.options.lng as string))
+ }
+ values={["system", ...Object.keys(i18n.options.resources!)]}
+ getLabel={(key) =>
+ key === "system" ? t("settings.general.language.system") : languages.of(key) ?? key
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
);
};