mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-03-17 06:59:15 -04:00
Refactor popup and modal to display the same thing
This commit is contained in:
parent
64b3ad437c
commit
f4be92f190
@ -1,15 +1,16 @@
|
||||
import { H1, P, Popup } from "~/primitives";
|
||||
import { P, Popup } from "~/primitives";
|
||||
|
||||
export const EntrySelect = ({
|
||||
name,
|
||||
videos,
|
||||
close,
|
||||
}: {
|
||||
name: string;
|
||||
videos: { slug: string; path: string }[];
|
||||
close?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<Popup>
|
||||
<H1>{name}</H1>
|
||||
<Popup title={name} close={close}>
|
||||
{videos.map((x) => (
|
||||
<P key={x.slug}>{x.path}</P>
|
||||
))}
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import Close from "@material-symbols/svg-400/rounded/close.svg";
|
||||
import { Stack, useRouter } from "expo-router";
|
||||
import type { ReactNode } from "react";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import { cn } from "~/utils";
|
||||
import { IconButton } from "./icons";
|
||||
import { Heading } from "./text";
|
||||
import type { Icon } from "./icons";
|
||||
import { Overlay } from "./popup";
|
||||
|
||||
export const Modal = ({
|
||||
icon,
|
||||
title,
|
||||
children,
|
||||
scroll = true,
|
||||
}: {
|
||||
icon?: Icon;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
scroll?: boolean;
|
||||
@ -31,28 +30,9 @@ export const Modal = ({
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Pressable
|
||||
className="absolute inset-0 cursor-default! items-center justify-center bg-black/60 max-md:px-4"
|
||||
onPress={close}
|
||||
>
|
||||
<Pressable
|
||||
className={cn(
|
||||
"w-full max-w-3xl rounded-md bg-background",
|
||||
"max-h-[90vh] cursor-default! overflow-hidden",
|
||||
)}
|
||||
onPress={(e) => e.preventDefault()}
|
||||
>
|
||||
<View className="flex-row items-center justify-between p-6">
|
||||
<Heading>{title}</Heading>
|
||||
<IconButton icon={Close} onPress={close} />
|
||||
</View>
|
||||
{scroll ? (
|
||||
<ScrollView className="p-6">{children}</ScrollView>
|
||||
) : (
|
||||
<View className="flex-1">{children}</View>
|
||||
)}
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
<Overlay icon={icon} title={title} close={close} scroll={scroll}>
|
||||
{children}
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,65 +1,70 @@
|
||||
import { usePortal } from "@gorhom/portal";
|
||||
import Close from "@material-symbols/svg-400/rounded/close.svg";
|
||||
import { type ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { px, vh } from "yoshiki/native";
|
||||
import { Container } from "./container";
|
||||
import { ContrastArea, SwitchVariant, type YoshikiFunc } from "./theme";
|
||||
import { ts } from "./utils";
|
||||
import { Pressable, ScrollView, View } from "react-native";
|
||||
import { cn } from "~/utils";
|
||||
import { Icon, IconButton, type Icon as IconType } from "./icons";
|
||||
import { Heading } from "./text";
|
||||
|
||||
export const Popup = ({
|
||||
export const Overlay = ({
|
||||
icon,
|
||||
title,
|
||||
close,
|
||||
children,
|
||||
...props
|
||||
scroll = true,
|
||||
}: {
|
||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||
icon?: IconType;
|
||||
title: string;
|
||||
close?: () => void;
|
||||
children: ReactNode;
|
||||
scroll?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<ContrastArea mode="user">
|
||||
<SwitchVariant>
|
||||
{({ css, theme }) => (
|
||||
<View
|
||||
{...css({
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
bg: (theme) => theme.themeOverlay,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
})}
|
||||
>
|
||||
<Container
|
||||
{...css(
|
||||
{
|
||||
borderRadius: px(6),
|
||||
paddingHorizontal: 0,
|
||||
bg: (theme) => theme.background,
|
||||
overflow: "hidden",
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: px(15),
|
||||
paddingVertical: ts(4),
|
||||
gap: ts(2),
|
||||
}}
|
||||
{...css({
|
||||
maxHeight: vh(95),
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
})}
|
||||
>
|
||||
{typeof children === "function"
|
||||
? children({ css, theme })
|
||||
: children}
|
||||
</ScrollView>
|
||||
</Container>
|
||||
</View>
|
||||
<Pressable
|
||||
className="absolute inset-0 cursor-default! items-center justify-center bg-black/60 max-md:px-4"
|
||||
onPress={close}
|
||||
>
|
||||
<Pressable
|
||||
className={cn(
|
||||
"w-full max-w-3xl rounded-md bg-background",
|
||||
"max-h-[90vh] cursor-default! overflow-hidden",
|
||||
)}
|
||||
</SwitchVariant>
|
||||
</ContrastArea>
|
||||
onPress={(e) => e.preventDefault()}
|
||||
>
|
||||
<View className="flex-row items-center justify-between p-6">
|
||||
<View className="flex-row items-center gap-2">
|
||||
{icon && <Icon icon={icon} />}
|
||||
<Heading>{title}</Heading>
|
||||
</View>
|
||||
{close && <IconButton icon={Close} onPress={close} />}
|
||||
</View>
|
||||
{scroll ? (
|
||||
<ScrollView className="p-6">{children}</ScrollView>
|
||||
) : (
|
||||
<View className="flex-1">{children}</View>
|
||||
)}
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export const Popup = ({
|
||||
icon,
|
||||
title,
|
||||
close,
|
||||
children,
|
||||
scroll,
|
||||
}: {
|
||||
icon?: IconType;
|
||||
title: string;
|
||||
close?: () => void;
|
||||
children: ReactNode;
|
||||
scroll?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Overlay icon={icon} title={title} close={close} scroll={scroll}>
|
||||
{children}
|
||||
</Overlay>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -108,16 +108,22 @@ export const SerieDetails = () => {
|
||||
const { scrollHandler, headerProps, headerHeight } = useScrollNavbar({
|
||||
imageHeight,
|
||||
});
|
||||
const [setPopup] = usePopup();
|
||||
const [setPopup, closePopup] = usePopup();
|
||||
|
||||
const openEntrySelect = useCallback(
|
||||
(entry: {
|
||||
name: string | null;
|
||||
videos: { slug: string; path: string }[];
|
||||
}) => {
|
||||
setPopup(<EntrySelect name={entry.name!} videos={entry.videos} />);
|
||||
setPopup(
|
||||
<EntrySelect
|
||||
name={entry.name ?? ""}
|
||||
videos={entry.videos}
|
||||
close={closePopup}
|
||||
/>,
|
||||
);
|
||||
},
|
||||
[setPopup],
|
||||
[setPopup, closePopup],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -9,17 +9,14 @@ import { type ComponentProps, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { useUniwind } from "uniwind";
|
||||
import { rem } from "yoshiki/native";
|
||||
import type { KyooError, User } from "~/models";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
H1,
|
||||
Icon,
|
||||
type Icon,
|
||||
Input,
|
||||
P,
|
||||
Popup,
|
||||
ts,
|
||||
usePopup,
|
||||
} from "~/primitives";
|
||||
import { useAccount } from "~/providers/account-context";
|
||||
@ -226,43 +223,27 @@ const ChangePopup = ({
|
||||
const [value, setValue] = useState(inital);
|
||||
|
||||
return (
|
||||
<Popup>
|
||||
{({ css }) => (
|
||||
<>
|
||||
<View
|
||||
{...css({ flexDirection: "row", alignItems: "center", gap: ts(2) })}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
<H1 {...css({ fontSize: rem(2) })}>{label}</H1>
|
||||
</View>
|
||||
<Input
|
||||
autoComplete={autoComplete}
|
||||
value={value}
|
||||
onChangeText={(v) => setValue(v)}
|
||||
/>
|
||||
<View
|
||||
{...css({
|
||||
flexDirection: "row",
|
||||
alignSelf: "flex-end",
|
||||
gap: ts(1),
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
text={t("misc.cancel")}
|
||||
onPress={() => close()}
|
||||
{...css({ minWidth: rem(6) })}
|
||||
/>
|
||||
<Button
|
||||
text={t("misc.edit")}
|
||||
onPress={async () => {
|
||||
await apply(value);
|
||||
close();
|
||||
}}
|
||||
{...css({ minWidth: rem(6) })}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<Popup title={label} icon={icon} close={close}>
|
||||
<Input
|
||||
autoComplete={autoComplete}
|
||||
value={value}
|
||||
onChangeText={(v) => setValue(v)}
|
||||
/>
|
||||
<View className="flex-row gap-2 self-end">
|
||||
<Button
|
||||
text={t("misc.cancel")}
|
||||
onPress={() => close()}
|
||||
className="min-w-24"
|
||||
/>
|
||||
<Button
|
||||
text={t("misc.edit")}
|
||||
onPress={async () => {
|
||||
await apply(value);
|
||||
close();
|
||||
}}
|
||||
className="min-w-24"
|
||||
/>
|
||||
</View>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
@ -286,59 +267,41 @@ const ChangePasswordPopup = ({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<Popup>
|
||||
{({ css }) => (
|
||||
<>
|
||||
<View
|
||||
{...css({ flexDirection: "row", alignItems: "center", gap: ts(2) })}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
<H1 {...css({ fontSize: rem(2) })}>{label}</H1>
|
||||
</View>
|
||||
{hasPassword && (
|
||||
<PasswordInput
|
||||
autoComplete="current-password"
|
||||
value={oldValue}
|
||||
onChangeText={(v) => setOldValue(v)}
|
||||
placeholder={t("settings.account.password.oldPassword")}
|
||||
/>
|
||||
)}
|
||||
<PasswordInput
|
||||
autoComplete="new-password"
|
||||
value={newValue}
|
||||
onChangeText={(v) => setNewValue(v)}
|
||||
placeholder={t("settings.account.password.newPassword")}
|
||||
/>
|
||||
{error && (
|
||||
<P {...css({ color: (theme) => theme.colors.red })}>{error}</P>
|
||||
)}
|
||||
<View
|
||||
{...css({
|
||||
flexDirection: "row",
|
||||
alignSelf: "flex-end",
|
||||
gap: ts(1),
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
text={t("misc.cancel")}
|
||||
onPress={() => close()}
|
||||
{...css({ minWidth: rem(6) })}
|
||||
/>
|
||||
<Button
|
||||
text={t("misc.edit")}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await apply(oldValue, newValue);
|
||||
close();
|
||||
} catch (e) {
|
||||
setError((e as KyooError).message);
|
||||
}
|
||||
}}
|
||||
{...css({ minWidth: rem(6) })}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
<Popup title={label} icon={icon} close={close}>
|
||||
{hasPassword && (
|
||||
<PasswordInput
|
||||
autoComplete="current-password"
|
||||
value={oldValue}
|
||||
onChangeText={(v) => setOldValue(v)}
|
||||
placeholder={t("settings.account.password.oldPassword")}
|
||||
/>
|
||||
)}
|
||||
<PasswordInput
|
||||
autoComplete="new-password"
|
||||
value={newValue}
|
||||
onChangeText={(v) => setNewValue(v)}
|
||||
placeholder={t("settings.account.password.newPassword")}
|
||||
/>
|
||||
{error && <P className="text-red-500">{error}</P>}
|
||||
<View className="flex-row gap-2 self-end">
|
||||
<Button
|
||||
text={t("misc.cancel")}
|
||||
onPress={() => close()}
|
||||
className="min-w-24"
|
||||
/>
|
||||
<Button
|
||||
text={t("misc.edit")}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await apply(oldValue, newValue);
|
||||
close();
|
||||
} catch (e) {
|
||||
setError((e as KyooError).message);
|
||||
}
|
||||
}}
|
||||
className="min-w-24"
|
||||
/>
|
||||
</View>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user