diff --git a/src/renderer/features/albums/components/album-detail-header.tsx b/src/renderer/features/albums/components/album-detail-header.tsx
index 1c4a7592..c768a155 100644
--- a/src/renderer/features/albums/components/album-detail-header.tsx
+++ b/src/renderer/features/albums/components/album-detail-header.tsx
@@ -1,72 +1,14 @@
-import { Center, Group } from '@mantine/core';
-import { useMergedRef } from '@mantine/hooks';
+import { Group, Stack } from '@mantine/core';
import { forwardRef, Fragment, Ref } from 'react';
-import { RiAlbumFill } from 'react-icons/ri';
import { generatePath, useParams } from 'react-router';
import { Link } from 'react-router-dom';
-import styled from 'styled-components';
-import { Text, TextTitle } from '/@/renderer/components';
+import { Text } from '/@/renderer/components';
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
+import { LibraryHeader } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes';
-
-const HeaderContainer = styled.div`
- position: relative;
- display: grid;
- grid-auto-columns: 1fr;
- grid-template-areas: 'image info';
- grid-template-rows: 1fr;
- grid-template-columns: 250px minmax(0, 1fr);
- gap: 0.5rem;
- width: 100%;
- max-width: 100%;
- height: 30vh;
- min-height: 340px;
- max-height: 500px;
- padding: 5rem 2rem 2rem;
-`;
-
-const CoverImageWrapper = styled.div`
- z-index: 15;
- display: flex;
- grid-area: image;
- align-items: flex-end;
- justify-content: center;
- height: 100%;
- filter: drop-shadow(0 0 8px rgb(0, 0, 0, 50%));
-`;
-
-const MetadataWrapper = styled.div`
- z-index: 15;
- display: flex;
- flex-direction: column;
- grid-area: info;
- justify-content: flex-end;
- width: 100%;
-`;
-
-const StyledImage = styled.img`
- object-fit: cover;
-`;
-
-const BackgroundImage = styled.div<{ background: string }>`
- position: absolute;
- top: 0;
- z-index: 0;
- width: 100%;
- height: 100%;
- background: ${(props) => props.background};
-`;
-
-const BackgroundImageOverlay = styled.div`
- position: absolute;
- top: 0;
- left: 0;
- z-index: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)), var(--background-noise);
-`;
+import { LibraryItem } from '/@/renderer/types';
+import { formatDurationString } from '/@/renderer/utils';
interface AlbumDetailHeaderProps {
background: string;
@@ -77,110 +19,81 @@ export const AlbumDetailHeader = forwardRef(
const { albumId } = useParams() as { albumId: string };
const detailQuery = useAlbumDetail({ id: albumId });
const cq = useContainerQuery();
- const mergedRef = useMergedRef(ref, cq.ref);
- const titleSize = cq.isXl
- ? '6rem'
- : cq.isLg
- ? '5.5rem'
- : cq.isMd
- ? '4.5rem'
- : cq.isSm
- ? '3.5rem'
- : '2rem';
+ const metadataItems = [
+ {
+ id: 'releaseYear',
+ secondary: false,
+ value: detailQuery?.data?.releaseYear,
+ },
+ {
+ id: 'songCount',
+ secondary: false,
+ value: `${detailQuery?.data?.songCount} songs`,
+ },
+ {
+ id: 'duration',
+ secondary: true,
+ value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
+ },
+ ];
+
+ console.log('detailQuery?.data?.duration :>> ', detailQuery?.data?.duration);
return (
-
-
-
-
- {detailQuery?.data?.imageUrl ? (
-
- ) : (
-
+
+
+
+ {metadataItems.map((item, index) => (
+
+ {index > 0 && •}
+ {item.value}
+
+ ))}
+
+
-
-
- )}
-
-
-
-
- Album
-
- {detailQuery?.data?.releaseYear && (
- <>
- •
- {detailQuery?.data?.releaseYear}
- >
- )}
-
-
- {detailQuery?.data?.name}
-
-
- {detailQuery?.data?.albumArtists.map((artist, index) => (
-
- {index > 0 && (
+ {detailQuery?.data?.albumArtists.map((artist, index) => (
+
+ {index > 0 && (
+
+ •
+
+ )}
- •
+ {artist.name}
- )}
-
- {artist.name}
-
-
- ))}
-
-
-
+
+ ))}
+
+
+
+
);
},
);
diff --git a/src/renderer/features/albums/routes/album-detail-route.tsx b/src/renderer/features/albums/routes/album-detail-route.tsx
index 91c704c3..3457dd96 100644
--- a/src/renderer/features/albums/routes/album-detail-route.tsx
+++ b/src/renderer/features/albums/routes/album-detail-route.tsx
@@ -1,65 +1,60 @@
-import { PageHeader, ScrollArea, TextTitle } from '/@/renderer/components';
-import { AnimatedPage, PlayButton } from '/@/renderer/features/shared';
+import { NativeScrollArea } from '/@/renderer/components';
+import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
import { useParams } from 'react-router';
-import { useFastAverageColor, useShouldPadTitlebar } from '/@/renderer/hooks';
+import { useFastAverageColor } from '/@/renderer/hooks';
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
-import { useIntersection } from '@mantine/hooks';
-import { Group } from '@mantine/core';
+import { usePlayQueueAdd } from '/@/renderer/features/player';
+import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
+import { LibraryItem } from '/@/renderer/types';
const AlbumDetailRoute = () => {
const tableRef = useRef(null);
+ const scrollAreaRef = useRef(null);
+ const headerRef = useRef(null);
+
const { albumId } = useParams() as { albumId: string };
const detailQuery = useAlbumDetail({ id: albumId });
const background = useFastAverageColor(detailQuery.data?.imageUrl);
- const padTop = useShouldPadTitlebar();
+ const handlePlayQueueAdd = usePlayQueueAdd();
+ const playButtonBehavior = usePlayButtonBehavior();
- const containerRef = useRef();
- const { ref, entry } = useIntersection({
- root: containerRef.current,
- threshold: 0,
- });
+ const handlePlay = () => {
+ handlePlayQueueAdd?.({
+ byItemType: {
+ id: [albumId],
+ type: LibraryItem.ALBUM,
+ },
+ play: playButtonBehavior,
+ });
+ };
+
+ if (!background) return null;
return (
-
+
+ {detailQuery?.data?.name}
+
+ ),
+ target: headerRef,
+ }}
>
-
-
-
- {detailQuery?.data?.name}
-
-
-
-
-
- {background && (
- <>
-
-
- >
- )}
-
+
+
+
);
};
diff --git a/src/renderer/features/playlists/components/playlist-detail-header.tsx b/src/renderer/features/playlists/components/playlist-detail-header.tsx
index 845c1b62..24f85ff3 100644
--- a/src/renderer/features/playlists/components/playlist-detail-header.tsx
+++ b/src/renderer/features/playlists/components/playlist-detail-header.tsx
@@ -1,4 +1,5 @@
import { Group, Stack } from '@mantine/core';
+import { forwardRef, Ref } from 'react';
import { RiMoreFill } from 'react-icons/ri';
import { generatePath, useParams } from 'react-router';
import { Link } from 'react-router-dom';
@@ -16,77 +17,79 @@ interface PlaylistDetailHeaderProps {
imageUrl?: string | null;
}
-export const PlaylistDetailHeader = ({
- background,
- imageUrl,
- imagePlaceholderUrl,
-}: PlaylistDetailHeaderProps) => {
- const { playlistId } = useParams() as { playlistId: string };
- const detailQuery = usePlaylistDetail({ id: playlistId });
- const handlePlayQueueAdd = usePlayQueueAdd();
- const playButtonBehavior = usePlayButtonBehavior();
+export const PlaylistDetailHeader = forwardRef(
+ (
+ { background, imageUrl, imagePlaceholderUrl }: PlaylistDetailHeaderProps,
+ ref: Ref,
+ ) => {
+ const { playlistId } = useParams() as { playlistId: string };
+ const detailQuery = usePlaylistDetail({ id: playlistId });
+ const handlePlayQueueAdd = usePlayQueueAdd();
+ const playButtonBehavior = usePlayButtonBehavior();
- const handlePlay = (playType?: Play) => {
- handlePlayQueueAdd?.({
- byItemType: {
- id: [playlistId],
- type: LibraryItem.PLAYLIST,
- },
- play: playType || playButtonBehavior,
- });
- };
+ const handlePlay = (playType?: Play) => {
+ handlePlayQueueAdd?.({
+ byItemType: {
+ id: [playlistId],
+ type: LibraryItem.PLAYLIST,
+ },
+ play: playType || playButtonBehavior,
+ });
+ };
- return (
-
-
-
-
-
-
-
-
- handlePlay()} />
-
-
-
-
-
- {PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
- handlePlay(type.play)}
+ return (
+
+
+
+
+
+
+
+
+ handlePlay()} />
+
+
+
- ))}
-
- Edit playlist
-
-
+
+
+
+
+ {PLAY_TYPES.filter((type) => type.play !== playButtonBehavior).map((type) => (
+ handlePlay(type.play)}
+ >
+ {type.label}
+
+ ))}
+
+ Edit playlist
+
+
+
-
-
- );
-};
+
+ );
+ },
+);
diff --git a/src/renderer/features/playlists/routes/playlist-detail-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-route.tsx
index 7a64871f..d4f36740 100644
--- a/src/renderer/features/playlists/routes/playlist-detail-route.tsx
+++ b/src/renderer/features/playlists/routes/playlist-detail-route.tsx
@@ -1,41 +1,64 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useRef } from 'react';
import { useParams } from 'react-router';
-import { PageHeader, ScrollArea } from '/@/renderer/components';
+import { NativeScrollArea } from '/@/renderer/components';
+import { usePlayQueueAdd } from '/@/renderer/features/player';
import { PlaylistDetailContent } from '/@/renderer/features/playlists/components/playlist-detail-content';
import { PlaylistDetailHeader } from '/@/renderer/features/playlists/components/playlist-detail-header';
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
-import { AnimatedPage } from '/@/renderer/features/shared';
-import { useFastAverageColor, useShouldPadTitlebar } from '/@/renderer/hooks';
+import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
+import { useFastAverageColor } from '/@/renderer/hooks';
+import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
+import { LibraryItem } from '/@/renderer/types';
const PlaylistDetailRoute = () => {
const tableRef = useRef(null);
+ const scrollAreaRef = useRef(null);
+ const headerRef = useRef(null);
const { playlistId } = useParams() as { playlistId: string };
- const padTitlebar = useShouldPadTitlebar();
const detailQuery = usePlaylistDetail({ id: playlistId });
const background = useFastAverageColor(detailQuery?.data?.imageUrl, 'dominant');
+ const handlePlayQueueAdd = usePlayQueueAdd();
+ const playButtonBehavior = usePlayButtonBehavior();
+
+ const handlePlay = () => {
+ handlePlayQueueAdd?.({
+ byItemType: {
+ id: [playlistId],
+ type: LibraryItem.PLAYLIST,
+ },
+ play: playButtonBehavior,
+ });
+ };
+
+ if (!background) return null;
+
return (
- <>
-
- {background && (
-
-
-
-
-
-
- )}
- >
+
+
+
+ {detailQuery?.data?.name}
+
+ ),
+ target: headerRef,
+ }}
+ >
+
+
+
+
);
};