diff --git a/src/renderer/features/player/components/left-controls.tsx b/src/renderer/features/player/components/left-controls.tsx index cc3d9d59c..53605a06e 100644 --- a/src/renderer/features/player/components/left-controls.tsx +++ b/src/renderer/features/player/components/left-controls.tsx @@ -1,7 +1,10 @@ import styled from '@emotion/styled'; -import { Text } from '../../../components'; -import { usePlayerStore } from '../../../store'; -import { Font } from '../../../styles'; +import { Group } from '@mantine/core'; +import { motion, AnimatePresence } from 'framer-motion'; +import { RiArrowUpSLine } from 'react-icons/ri'; +import { Button, Text } from '@/renderer/components'; +import { useAppStore, usePlayerStore } from '@/renderer/store'; +import { Font } from '@/renderer/styles'; const LeftControlsContainer = styled.div` display: flex; @@ -25,7 +28,25 @@ const MetadataStack = styled.div` overflow: hidden; `; +const Image = styled(motion.div)<{ url: string }>` + width: 70px; + height: 70px; + background-image: url(${(props) => props.url}); + background-repeat: no-repeat; + background-size: cover; + + button { + display: none; + } + + &:hover button { + display: block; + } +`; + export const LeftControls = () => { + const hideImage = useAppStore((state) => state.sidebar.image); + const setSidebar = useAppStore((state) => state.setSidebar); const song = usePlayerStore((state) => state.current.song); const title = song?.name; const artists = song?.artists?.map((artist) => artist?.name).join(', '); @@ -34,9 +55,31 @@ export const LeftControls = () => { return ( - {song?.imageUrl && ( - img - )} + + {!hideImage && ( + + + + + + )} + ` + height: ${(props) => props.height}; + background-image: ${(props) => `url(${props.url})`}; + background-repeat: no-repeat; + background-size: cover; + transition: background-image 0.5s linear 0.2s; + + button { + display: none; + } + + &:hover button { + display: block; + } +`; export const Sidebar = () => { const navigate = useNavigate(); + const playerData = usePlayerStore((state) => state.getPlayerData()); + const sidebar = useAppStore((state) => state.sidebar); + const setSidebar = useAppStore((state) => state.setSidebar); + + const showImage = sidebar.image; + + const backgroundImage = useMemo(() => { + return playerData.current.song.imageUrl; + }, [playerData]); return ( - - - - - - } - placeholder="Search" - rightSectionWidth={90} - onClick={() => openSpotlight()} - /> - - - - - - - - - + + + + + + + } + placeholder="Search" + rightSectionWidth={90} + onClick={() => openSpotlight()} + /> + + + + + + + + + + + + + + Home + + + + + + + Explore + + + + + + + + + Library + + + + + + + Albums + + + + + + Tracks + + + + + + Artists + + + + + + Folders + + + + + + + + + Collections + + + + + + + + + Playlists + + + + + + + + + {showImage && ( + + + + + + )} + - - - - Home - - - - - - - Explore - - - - - - - - - - Library - - - - - - - Albums - - - - - - Tracks - - - - - - Artists - - - - - - Folders - - - - - - - - - Collections - - - - - - - - - Playlists - - - - - - + ); }; diff --git a/src/renderer/layouts/default-layout.tsx b/src/renderer/layouts/default-layout.tsx index 5c1fe83a6..ab4cc11fd 100644 --- a/src/renderer/layouts/default-layout.tsx +++ b/src/renderer/layouts/default-layout.tsx @@ -2,9 +2,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; import { Menu, Button } from '@mantine/core'; import { Outlet } from 'react-router'; +import { SideQueue } from '@/renderer/features/side-queue/components/SideQueue'; import { Titlebar } from '@/renderer/features/titlebar/components/titlebar'; import { useAppStore } from '@/renderer/store'; -import { constrainSidebarWidth } from '@/renderer/utils'; +import { + constrainRightSidebarWidth, + constrainSidebarWidth, +} from '@/renderer/utils'; import { Playerbar } from '../features/player'; import { Sidebar } from '../features/sidebar/components/sidebar'; @@ -26,12 +30,17 @@ const TitlebarContainer = styled.header` -webkit-app-region: drag; `; -const MainContainer = styled.main<{ leftSidebarWidth: string }>` +const MainContainer = styled.main<{ + leftSidebarWidth: string; + rightExpanded?: boolean; + rightSidebarWidth?: string; +}>` display: grid; grid-area: main; - grid-template-areas: 'sidebar .'; + grid-template-areas: 'sidebar . right-sidebar'; grid-template-rows: 1fr; - grid-template-columns: ${(props) => props.leftSidebarWidth} 1fr; + grid-template-columns: ${(props) => props.leftSidebarWidth} 1fr ${(props) => + props.rightExpanded && props.rightSidebarWidth}; gap: 0; background: var(--main-bg); `; @@ -42,7 +51,14 @@ const SidebarContainer = styled.div` background: var(--sidebar-bg); `; +const RightSidebarContainer = styled.div` + position: relative; + grid-area: right-sidebar; + background: var(--sidebar-bg); +`; + const PlayerbarContainer = styled.footer` + z-index: 50; grid-area: player; background: var(--playerbar-bg); `; @@ -52,20 +68,16 @@ const ResizeHandle = styled.div<{ placement: 'top' | 'left' | 'bottom' | 'right'; }>` position: absolute; + top: ${(props) => props.placement === 'top' && 0}; + right: ${(props) => props.placement === 'right' && 0}; + bottom: ${(props) => props.placement === 'bottom' && 0}; + left: ${(props) => props.placement === 'left' && 0}; + z-index: 100; width: 3px; height: 100%; - right: 0; background-color: var(--sidebar-handle-bg); - /* border-top: ${({ placement }) => - placement === 'top' && '1px var(--sidebar-handle-bg) solid'}; - border-right: ${({ placement }) => - placement === 'right' && '1px var(--sidebar-handle-bg) solid'}; - border-bottom: ${({ placement }) => - placement === 'bottom' && '1px var(--sidebar-handle-bg) solid'}; - border-left: ${({ placement }) => - placement === 'left' && '1px var(--sidebar-handle-bg) solid'}; */ - opacity: ${(props) => (props.isResizing ? 1 : 0)}; cursor: ew-resize; + opacity: ${(props) => (props.isResizing ? 1 : 0)}; &:hover { opacity: 1; @@ -76,15 +88,19 @@ export const DefaultLayout = () => { const sidebar = useAppStore((state) => state.sidebar); const setSidebar = useAppStore((state) => state.setSidebar); - const sidebarRef = useRef(null); + const sidebarRef = useRef(null); + const rightSidebarRef = useRef(null); const [isResizing, setIsResizing] = useState(false); + const [isResizingRight, setIsResizingRight] = useState(false); - const startResizing = useCallback(() => { - setIsResizing(true); + const startResizing = useCallback((position: 'left' | 'right') => { + if (position === 'left') return setIsResizing(true); + return setIsResizingRight(true); }, []); const stopResizing = useCallback(() => { setIsResizing(false); + setIsResizingRight(false); }, []); const resize = useCallback( @@ -93,8 +109,16 @@ export const DefaultLayout = () => { const width = `${constrainSidebarWidth(mouseMoveEvent.clientX)}px`; setSidebar({ leftWidth: width }); } + if (isResizingRight) { + const start = Number(sidebar.rightWidth.split('px')[0]); + const { left } = rightSidebarRef!.current!.getBoundingClientRect(); + const width = `${constrainRightSidebarWidth( + start + left - mouseMoveEvent.clientX + )}px`; + setSidebar({ rightWidth: width }); + } }, - [isResizing, setSidebar] + [isResizing, isResizingRight, setSidebar, sidebar.rightWidth] ); useEffect(() => { @@ -121,19 +145,35 @@ export const DefaultLayout = () => { - + { e.preventDefault(); - startResizing(); + startResizing('left'); }} /> + + { + e.preventDefault(); + startResizing('right'); + }} + /> + {sidebar.rightExpanded && } + diff --git a/src/renderer/store/app.store.ts b/src/renderer/store/app.store.ts index 9aff042b5..3c75602fc 100644 --- a/src/renderer/store/app.store.ts +++ b/src/renderer/store/app.store.ts @@ -5,14 +5,19 @@ import { Platform } from '@/renderer/types'; type SidebarProps = { expanded: string[]; + image: boolean; leftWidth: string; + rightExpanded: boolean; rightWidth: string; }; + export interface AppState { platform: Platform; sidebar: { expanded: string[]; + image: boolean; leftWidth: string; + rightExpanded: boolean; rightWidth: string; }; } @@ -37,7 +42,9 @@ export const useAppStore = create()( }, sidebar: { expanded: [], + image: false, leftWidth: '230px', + rightExpanded: false, rightWidth: '230px', }, })),