diff --git a/src/components/ButtonFavorite.tsx b/src/components/ButtonFavorite.tsx index d65d11b..e1dd0f0 100644 --- a/src/components/ButtonFavorite.tsx +++ b/src/components/ButtonFavorite.tsx @@ -1,6 +1,11 @@ -import { ActionIcon, ActionIconProps } from "@mantine/core"; +import { + ActionIcon, + ActionIconProps, + Menu, + useMantineTheme, +} from "@mantine/core"; import { notifications } from "@mantine/notifications"; -import { IconHeart } from "@tabler/icons-react"; +import { IconHeart, IconHeartFilled } from "@tabler/icons-react"; import { memo } from "react"; import { useTranslation } from "react-i18next"; @@ -15,6 +20,7 @@ interface ButtonFavoriteProps extends ActionIconProps { video?: Video; iconSize?: number; buttonSize?: number; + render?: "menu"; } const getItemId = (item: Video) => { @@ -35,11 +41,13 @@ export const ButtonFavorite: React.FC = memo( iconSize = 18, variant = "default", buttonSize = 36, + render = null, }) => { const favorite = useFavorite(); const setFavorite = useSetFavorite(); const { video: currentVideo } = usePlayerVideo(); const { t } = useTranslation(); + const theme = useMantineTheme(); const video = parentVideo ?? (currentVideo as Video); @@ -90,6 +98,23 @@ export const ButtonFavorite: React.FC = memo( return handleAdd(); }; + if (render === "menu") { + return ( + + ) : ( + + ) + } + > + Favorite + + ); + } + return ( = memo( - ({ iconSize }) => { + ({ iconSize, render = "button" }) => { const setPlayerMode = useSetPlayerMode(); const playerMode = usePlayerMode(); const playerAudio = usePlayerAudio(); @@ -25,6 +26,14 @@ export const ButtonPlayerModeVideo: React.FC = memo( audio.pause(); }; + if (render === "menu") { + return ( + }> + Video mode + + ); + } + return ( { - + + diff --git a/src/components/Player.tsx b/src/components/Player.tsx index b5b172f..18a3721 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -3,18 +3,23 @@ import { Box, Drawer, Flex, + Menu, + Popover, ScrollArea, - Slider, Space, Text, createStyles, - useMantineTheme, } from "@mantine/core"; import { useDocumentTitle, useMediaQuery } from "@mantine/hooks"; -import { IconPlaylist, IconVolume } from "@tabler/icons-react"; +import { + IconDotsVertical, + IconPlaylist, + IconVolume, +} from "@tabler/icons-react"; import { memo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useDevices } from "../hooks/useDevices"; import { usePlayerAudio, usePlayerState, @@ -31,6 +36,7 @@ import { PlayerActions } from "./PlayerActions"; import { PlayerBackground } from "./PlayerBackground"; import { PlayerLoadingOverlay } from "./PlayerLoadingOverlay"; import { PlayerProgress } from "./PlayerProgress"; +import { VerticalSlider } from "./VerticalSlider"; import { VideoList } from "./VideoList"; const useStyles = createStyles((theme) => ({ @@ -58,7 +64,7 @@ const useStyles = createStyles((theme) => ({ }, [`@media (min-width: ${theme.breakpoints.md})`]: { - maxWidth: 280, + maxWidth: 260, }, [`@media (min-width: ${theme.breakpoints.lg})`]: { @@ -69,7 +75,6 @@ const useStyles = createStyles((theme) => ({ maxWidth: 440, }, }, - volume: {}, thumbnail: { flex: "0 0 50px", height: 50, @@ -83,49 +88,51 @@ const useStyles = createStyles((theme) => ({ export const Player = memo(() => { const { classes } = useStyles(); - const matches = useMediaQuery("(max-width: 2140px)"); - const theme = useMantineTheme(); - const showProgressBar = useMediaQuery(`(min-width: ${theme.breakpoints.md})`); - const showVolumeBar = useMediaQuery(`(min-width: ${theme.breakpoints.xl})`); + const showPlayerBar = useMediaQuery("(max-width: 2140px)"); + const { isMedium, isLarge, isLessThanLarge, isXlarge } = useDevices(); return ( - {matches ? ( + {showPlayerBar ? ( <> - + - - {showProgressBar ? ( + + {isMedium ? ( <> - + ) : null} - + - + - - - - - {showVolumeBar ? ( + {isLarge ? ( <> - + ) : null} - + + + + {isLessThanLarge ? ( + <> + + + + ) : null} ) : null} @@ -140,6 +147,8 @@ const VideoInformations = memo(() => { useDocumentTitle(video?.title as string); + if (!video) return null; + return ( { }} className={classes.thumbnail} /> - - - {video?.title} + + + {video.title} - {video?.description} + {video.description} ); }); -const PlayerVolume = memo(() => { - const { classes } = useStyles(); +export const ButtonVolume = memo(() => { const playerState = usePlayerState(); const playerAudio = usePlayerAudio(); @@ -176,22 +184,19 @@ const PlayerVolume = memo(() => { }; return ( - - - - - - + + + + + + + - - + + ); }); @@ -228,3 +233,19 @@ export const PlayerSpace = memo(() => { return ; }); + +const MoreSubMenu = memo(() => { + return ( + + + + + + + + + + + + ); +}); diff --git a/src/components/PlayerProgress.tsx b/src/components/PlayerProgress.tsx index ca82b13..baa51e0 100644 --- a/src/components/PlayerProgress.tsx +++ b/src/components/PlayerProgress.tsx @@ -1,6 +1,7 @@ import { Box, Flex, Slider, Text } from "@mantine/core"; import { memo } from "react"; +import { useDevices } from "../hooks/useDevices"; import { useSponsorBlock } from "../hooks/useSponsorBlock"; import { usePlayerAudio, usePlayerState } from "../providers/Player"; import { SponsorBlockBar } from "./SponsorBlockBar"; @@ -8,6 +9,7 @@ import { SponsorBlockBar } from "./SponsorBlockBar"; export const PlayerProgress = memo(() => { const playerAudio = usePlayerAudio(); const playerState = usePlayerState(); + const { isLarge } = useDevices(); useSponsorBlock(); @@ -18,7 +20,7 @@ export const PlayerProgress = memo(() => { }; return ( - + {String(playerState.formatedCurrentTime ?? "00:00")} diff --git a/src/components/VerticalSlider.tsx b/src/components/VerticalSlider.tsx new file mode 100644 index 0000000..8249254 --- /dev/null +++ b/src/components/VerticalSlider.tsx @@ -0,0 +1,68 @@ +import { Group, createStyles, rem } from "@mantine/core"; +import { useMove } from "@mantine/hooks"; +import { memo, useState } from "react"; + +const SLIDER_WIDTH = 4; +const SLIDER_HEIGHT = 120; + +const useStyles = createStyles((theme) => ({ + container: { + position: "relative", + width: rem(SLIDER_WIDTH), + height: rem(SLIDER_HEIGHT), + background: + theme.colorScheme === "dark" + ? "rgba(255, 255, 255, .1)" + : "rgba(0, 0, 0, .1)", + borderRadius: theme.radius.md, + cursor: "pointer", + }, + filledBar: { + position: "absolute", + bottom: 0, + width: rem(SLIDER_WIDTH), + background: theme.colorScheme === "dark" ? "white" : theme.colors.blue[6], + borderRadius: theme.radius.md, + }, +})); + +interface VerticalSliderProps { + value?: number; + onChangeEnd: (value: number) => void; +} + +export const VerticalSlider: React.FC = memo( + ({ value, onChangeEnd }) => { + const { classes } = useStyles(); + const [localValue, setLocalValue] = useState(0.2); + const { ref } = useMove(({ y }) => handleChangeEnd(1 - y)); + + const handleChangeEnd = (updatedValue: number) => { + if (!value) { + setLocalValue(updatedValue); + } + onChangeEnd(Math.trunc(updatedValue * 100)); + }; + + return ( + +
+ +
+
+ ); + }, +); + +const FilledBar = memo(({ value }: { value: number }) => { + const { classes } = useStyles(); + + return ( +
+ ); +}); diff --git a/src/hooks/useDevices.ts b/src/hooks/useDevices.ts new file mode 100644 index 0000000..0abb243 --- /dev/null +++ b/src/hooks/useDevices.ts @@ -0,0 +1,26 @@ +import { useMantineTheme } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; + +export const useDevices = () => { + const theme = useMantineTheme(); + + const isLessThanSmall = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`); + const isSmall = useMediaQuery(`(min-width: ${theme.breakpoints.sm})`); + const isLessThanMedium = useMediaQuery( + `(max-width: ${theme.breakpoints.md})`, + ); + const isMedium = useMediaQuery(`(min-width: ${theme.breakpoints.md})`); + const isLarge = useMediaQuery(`(min-width: ${theme.breakpoints.xl})`); + const isLessThanLarge = useMediaQuery(`(max-width: ${theme.breakpoints.xl})`); + const isXlarge = useMediaQuery("(min-width: 1600px)"); + + return { + isSmall, + isLessThanSmall, + isMedium, + isLessThanMedium, + isLarge, + isLessThanLarge, + isXlarge, + }; +};