diff --git a/front/packages/primitives/src/menu.tsx b/front/packages/primitives/src/menu.tsx index 28e064b6..483ab6e8 100644 --- a/front/packages/primitives/src/menu.tsx +++ b/front/packages/primitives/src/menu.tsx @@ -27,6 +27,7 @@ import { ReactNode, useContext, useEffect, + useRef, useState, } from "react"; import { StyleSheet, Pressable, View } from "react-native"; @@ -44,6 +45,8 @@ import { SvgProps } from "react-native-svg"; const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined); +type Optional = Omit & Partial; + const Menu = ({ Trigger, onMenuOpen, @@ -59,20 +62,31 @@ const Menu = ({ onMenuClose?: () => void; isOpen?: boolean; setOpen?: (v: boolean) => void; -} & Omit) => { +} & 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) onMenuOpen?.call(null); - else onMenuClose?.call(null); - }, [isOpen, onMenuClose, onMenuOpen]); + if (isOpen) memoRef.current.onMenuOpen?.(); + else if (alreadyRendered.current) memoRef.current.onMenuClose?.(); + alreadyRendered.current = true; + }, [isOpen]); return ( <> - setOpen(true)} {...(props as any)} /> + { + if ("onPress" in props && typeof props.onPress === "function") props.onPress(); + setOpen(true); + }} + {...(props as any)} + /> {isOpen && ( @@ -195,4 +209,24 @@ const MenuItem = ({ }; 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 }; diff --git a/front/packages/primitives/src/menu.web.tsx b/front/packages/primitives/src/menu.web.tsx index 33b7db78..ac2a52cf 100644 --- a/front/packages/primitives/src/menu.web.tsx +++ b/front/packages/primitives/src/menu.web.tsx @@ -66,7 +66,7 @@ const Menu = ({ modal open={isOpen} onOpenChange={(newOpen) => { - if (setOpen) setOpen(newOpen) + if (setOpen) setOpen(newOpen); if (newOpen) onMenuOpen?.call(null); else onMenuClose?.call(null); }} @@ -77,7 +77,7 @@ const Menu = ({ - {({ css }) => ( + {({ css, theme }) => ( e.stopImmediatePropagation()} @@ -93,6 +93,7 @@ const Menu = ({ })} > {children} + )} @@ -106,10 +107,10 @@ const Menu = ({ const Item = forwardRef< HTMLDivElement, ComponentProps & { href?: string } ->(function _Item({ children, href, ...props }, ref) { +>(function _Item({ children, href, onSelect, ...props }, ref) { if (href) { return ( - + {children} @@ -117,28 +118,22 @@ const Item = forwardRef< ); } return ( - + {children} ); }); -const MenuItem = ({ - label, - icon, - left, - selected, - onSelect, - href, - disabled, - ...props -}: { - label: string; - icon?: ComponentType; - left?: ReactElement; - disabled?: boolean; - selected?: boolean; -} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => { +const MenuItem = forwardRef< + HTMLDivElement, + { + label: string; + icon?: ComponentType; + left?: ReactElement; + disabled?: boolean; + selected?: boolean; + } & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined }) +>(function MenuItem({ label, icon, left, selected, onSelect, href, disabled, ...props }, ref) { const { css: nCss } = useNativeYoshiki(); const { css, theme } = useYoshiki(); @@ -159,6 +154,7 @@ const MenuItem = ({ } `} ); -}; +}); Menu.Item = MenuItem; +const Sub = ({ + children, + disabled, + ...props +}: { + label: string; + selected?: boolean; + left?: ReactElement; + disabled?: boolean; + icon?: ComponentType; + children: ReactNode | ReactNode[] | null; +} & AsProps) => { + const { css, theme } = useYoshiki(); + + return ( + + + e.preventDefault()} /> + + + e.stopImmediatePropagation()} + {...css({ + bg: (theme) => theme.background, + overflow: "auto", + minWidth: "220px", + borderRadius: "8px", + boxShadow: + "0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)", + zIndex: 2, + maxHeight: "calc(var(--radix-dropdown-menu-content-available-height) * 0.8)", + })} + > + {children} + + + + + ); +}; +Menu.Sub = Sub; + export { Menu }; diff --git a/front/packages/ui/src/components/context-menus.tsx b/front/packages/ui/src/components/context-menus.tsx index ec34ea63..55628a9c 100644 --- a/front/packages/ui/src/components/context-menus.tsx +++ b/front/packages/ui/src/components/context-menus.tsx @@ -23,16 +23,58 @@ import { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg"; import Info from "@material-symbols/svg-400/rounded/info.svg"; +import { WatchStatusV, queryFn, useAccount } from "@kyoo/models"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { watchListIcon } from "./watchlist-info"; export const EpisodesContext = ({ showSlug, + slug, + status, ...props -}: { showSlug: string } & Partial>>) => { +}: { showSlug?: string; slug: string; status: WatchStatusV | null } & Partial< + ComponentProps> +>) => { + const account = useAccount(); const { t } = useTranslation(); + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (newStatus: WatchStatusV | null) => + queryFn({ + path: ["episode", slug, "watchStatus", newStatus && `?status=${newStatus}`], + method: newStatus ? "POST" : "DELETE", + }), + onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["episode", slug] }), + }); + return ( - + console.log("tot")} + /> + {showSlug && ( + + )} + + {Object.values(WatchStatusV).map((x) => ( + mutation.mutate(x)} + selected={x === status} + /> + ))} + {status !== null && ( + mutation.mutate(null)} /> + )} + ); }; diff --git a/front/packages/ui/src/components/watchlist-info.tsx b/front/packages/ui/src/components/watchlist-info.tsx index b09d1809..178805a8 100644 --- a/front/packages/ui/src/components/watchlist-info.tsx +++ b/front/packages/ui/src/components/watchlist-info.tsx @@ -28,6 +28,19 @@ import BookmarkRemove from "@material-symbols/svg-400/rounded/bookmark_remove.sv import { useMutation, useQueryClient } from "@tanstack/react-query"; import { WatchStatusV, queryFn, useAccount } from "@kyoo/models"; +export const watchListIcon = (status: WatchStatusV | null) => { + switch (status) { + case null: + return BookmarkAdd; + case WatchStatusV.Completed: + return BookmarkAdded; + case WatchStatusV.Droped: + return BookmarkRemove; + default: + return Bookmark; + } +}; + export const WatchListInfo = ({ type, slug, @@ -84,7 +97,7 @@ export const WatchListInfo = ({ return ( diff --git a/front/packages/ui/src/details/episode.tsx b/front/packages/ui/src/details/episode.tsx index 280223c9..cbd2906d 100644 --- a/front/packages/ui/src/details/episode.tsx +++ b/front/packages/ui/src/details/episode.tsx @@ -144,6 +144,7 @@ export const EpisodeBox = ({ export const EpisodeLine = ({ slug, + showSlug, displayNumber, name, thumbnail, @@ -161,6 +162,7 @@ export const EpisodeLine = ({ }: WithLoading<{ id: string; slug: string; + showSlug: string; displayNumber: string; name: string | null; overview: string | null; @@ -263,14 +265,19 @@ export const EpisodeLine = ({ {isLoading ||

{overview}

}
- + {slug && watchedStatus !== undefined && ( + setMoreOpened(v)} + {...css([ + "more", + Platform.OS === "web" && moreOpened && { display: "flex !important" as any }, + ])} + /> + )}