/* * 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 . */ import { Portal } from "@gorhom/portal"; import { ScrollView } from "moti"; import { ComponentType, createContext, ReactElement, ReactNode, useContext, useEffect, useRef, useState, } from "react"; import { StyleSheet, Pressable, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { percent, px, sm, useYoshiki, vh, xl } from "yoshiki/native"; import Close from "@material-symbols/svg-400/rounded/close-fill.svg"; import { Icon, IconButton } from "./icons"; import { PressableFeedback } from "./links"; import { P } from "./text"; import { ContrastArea, SwitchVariant } from "./themes"; import { ts } from "./utils"; import Check from "@material-symbols/svg-400/rounded/check-fill.svg"; import { useRouter } from "solito/router"; import { SvgProps } from "react-native-svg"; const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined); type Optional = Omit & Partial; const Menu = ({ Trigger, onMenuOpen, onMenuClose, children, isOpen: outerOpen, setOpen: outerSetOpen, ...props }: { Trigger: ComponentType; children?: ReactNode | ReactNode[] | null; onMenuOpen?: () => void; onMenuClose?: () => void; isOpen?: boolean; setOpen?: (v: boolean) => void; } & Optional) => { const insets = useSafeAreaInsets(); const alreadyRendered = useRef(false); const [isOpen, setOpen] = // eslint-disable-next-line react-hooks/rules-of-hooks outerOpen !== undefined && outerSetOpen ? [outerOpen, outerSetOpen] : useState(false); // deos the same as a useMemo but for props. const memoRef = useRef({ onMenuOpen, onMenuClose }); memoRef.current = { onMenuOpen, onMenuClose }; useEffect(() => { if (isOpen) memoRef.current.onMenuOpen?.(); else if (alreadyRendered.current) memoRef.current.onMenuClose?.(); alreadyRendered.current = true; }, [isOpen]); return ( <> { if ("onPress" in props && typeof props.onPress === "function") props.onPress(); setOpen(true); }} {...(props as any)} /> {isOpen && ( {({ css, theme }) => ( setOpen(false)} tabIndex={-1} {...css({ ...StyleSheet.absoluteFillObject, flexGrow: 1, bg: "transparent" })} /> theme.background, position: "absolute", bottom: 0, width: percent(100), maxHeight: vh(80), alignSelf: "center", borderTopLeftRadius: px(26), borderTopRightRadius: { xs: px(26), xl: 0 }, paddingTop: { xs: px(26), xl: 0 }, marginTop: { xs: px(72), xl: 0 }, paddingBottom: insets.bottom, }, sm({ maxWidth: px(640), marginHorizontal: px(56), }), xl({ top: 0, right: 0, marginRight: 0, borderBottomLeftRadius: px(26), }), ])} > setOpen(false)} {...css({ alignSelf: "flex-end", display: { xs: "none", xl: "flex" } })} /> {children} )} )} ); }; const MenuItem = ({ label, selected, left, onSelect, href, icon, disabled, ...props }: { label: string; selected?: boolean; left?: ReactElement; disabled?: boolean; icon?: ComponentType; } & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => { const { css, theme } = useYoshiki(); const setOpen = useContext(MenuContext); const router = useRouter(); const icn = (icon || selected) && ( ); return ( { setOpen?.call(null, false); onSelect?.call(null); if (href) router.push(href); }} disabled={disabled} {...css( { paddingHorizontal: ts(2), width: percent(100), height: ts(5), alignItems: "center", flexDirection: "row", }, props as any, )} > {left && left} {!left && icn && icn}

{label}

{left && icn && icn}
); }; Menu.Item = MenuItem; const Sub = ({ children, ...props }: { label: string; selected?: boolean; left?: ReactElement; disabled?: boolean; icon?: ComponentType; children?: ReactNode | ReactNode[] | null; } & AsProps) => { const setOpen = useContext(MenuContext); return ( setOpen?.(false)} {...props}> {children} ); }; Menu.Sub = Sub; export { Menu };