Improve header color styles on detail pages

This commit is contained in:
jeffvli 2023-07-20 17:31:56 -07:00
parent 713260bfc9
commit 31eb22f968
11 changed files with 473 additions and 429 deletions

View file

@ -34,6 +34,7 @@ import {
import { SwiperGridCarousel } from '/@/renderer/components/grid-carousel';
import { FullWidthDiscCell } from '/@/renderer/components/virtual-table/cells/full-width-disc-cell';
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
const isFullWidthRow = (node: RowNode) => {
return node.id?.startsWith('disc-');
@ -41,6 +42,10 @@ const isFullWidthRow = (node: RowNode) => {
const ContentContainer = styled.div`
position: relative;
z-index: 0;
`;
const DetailContainer = styled.div`
display: flex;
flex-direction: column;
padding: 1rem 2rem 5rem;
@ -49,17 +54,14 @@ const ContentContainer = styled.div`
.ag-theme-alpine-dark {
--ag-header-background-color: rgba(0, 0, 0, 0%) !important;
}
.ag-header {
margin-bottom: 0.5rem;
}
`;
interface AlbumDetailContentProps {
background?: string;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentProps) => {
const { albumId } = useParams() as { albumId: string };
const server = useCurrentServer();
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
@ -271,157 +273,165 @@ export const AlbumDetailContent = ({ tableRef }: AlbumDetailContentProps) => {
return (
<ContentContainer>
<Box component="section">
<Group
ref={showGenres ? null : intersectRef}
className="test"
py="1rem"
spacing="md"
>
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Group spacing="xs">
<Button
compact
loading={
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
}
variant="subtle"
onClick={handleFavorite}
>
{detailQuery?.data?.userFavorite ? (
<RiHeartFill
color="red"
size={20}
/>
) : (
<RiHeartLine size={20} />
)}
</Button>
<Button
compact
variant="subtle"
onClick={(e) => {
if (!detailQuery?.data) return;
handleGeneralContextMenu(e, [detailQuery.data!]);
}}
>
<RiMoreFill size={20} />
</Button>
</Group>
</Group>
</Box>
{showGenres && (
<Box
ref={showGenres ? intersectRef : null}
component="section"
py="1rem"
>
<Group spacing="sm">
{detailQuery?.data?.genres?.map((genre) => (
<LibraryBackgroundOverlay backgroundColor={background} />
<DetailContainer>
<Box component="section">
<Group
ref={showGenres ? null : intersectRef}
py="1rem"
spacing="md"
>
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Group spacing="xs">
<Button
key={`genre-${genre.id}`}
compact
component={Link}
radius={0}
size="md"
to={generatePath(`${AppRoute.LIBRARY_ALBUMS}?genre=${genre.id}`, {
albumId,
})}
variant="outline"
loading={
createFavoriteMutation.isLoading ||
deleteFavoriteMutation.isLoading
}
variant="subtle"
onClick={handleFavorite}
>
{genre.name}
{detailQuery?.data?.userFavorite ? (
<RiHeartFill
color="red"
size={20}
/>
) : (
<RiHeartLine size={20} />
)}
</Button>
))}
<Button
compact
variant="subtle"
onClick={(e) => {
if (!detailQuery?.data) return;
handleGeneralContextMenu(e, [detailQuery.data!]);
}}
>
<RiMoreFill size={20} />
</Button>
</Group>
</Group>
</Box>
)}
<Box
ref={tableContainerRef}
style={{ minHeight: '300px' }}
>
<VirtualTable
ref={tableRef}
autoFitColumns
autoHeight
suppressCellFocus
suppressHorizontalScroll
suppressLoadingOverlay
suppressRowDrag
columnDefs={columnDefs}
enableCellChangeFlash={false}
fullWidthCellRenderer={FullWidthDiscCell}
getRowHeight={getRowHeight}
getRowId={(data) => data.data.id}
isFullWidthRow={(data) => {
return isFullWidthRow(data.rowNode) || false;
}}
isRowSelectable={(data) => {
if (isFullWidthRow(data.data)) return false;
return true;
}}
rowClassRules={rowClassRules}
rowData={songsRowData}
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</Box>
<Stack
ref={cq.ref}
mt="5rem"
>
<>
{cq.height || cq.width ? (
<>
{carousels
.filter((c) => !c.isHidden)
.map((carousel, index) => (
<SwiperGridCarousel
key={`carousel-${carousel.uniqueId}-${index}`}
cardRows={[
{
property: 'name',
route: {
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [
{
idProperty: 'id',
slugProperty: 'albumId',
},
],
{showGenres && (
<Box
ref={showGenres ? intersectRef : null}
component="section"
py="1rem"
>
<Group spacing="sm">
{detailQuery?.data?.genres?.map((genre) => (
<Button
key={`genre-${genre.id}`}
compact
component={Link}
radius={0}
size="md"
to={generatePath(
`${AppRoute.LIBRARY_ALBUMS}?genre=${genre.id}`,
{
albumId,
},
)}
variant="outline"
>
{genre.name}
</Button>
))}
</Group>
</Box>
)}
<Box
ref={tableContainerRef}
style={{ minHeight: '300px' }}
>
<VirtualTable
ref={tableRef}
autoFitColumns
autoHeight
suppressCellFocus
suppressHorizontalScroll
suppressLoadingOverlay
suppressRowDrag
columnDefs={columnDefs}
enableCellChangeFlash={false}
fullWidthCellRenderer={FullWidthDiscCell}
getRowHeight={getRowHeight}
getRowId={(data) => data.data.id}
isFullWidthRow={(data) => {
return isFullWidthRow(data.rowNode) || false;
}}
isRowSelectable={(data) => {
if (isFullWidthRow(data.data)) return false;
return true;
}}
rowClassRules={rowClassRules}
rowData={songsRowData}
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</Box>
<Stack
ref={cq.ref}
mt="5rem"
>
<>
{cq.height || cq.width ? (
<>
{carousels
.filter((c) => !c.isHidden)
.map((carousel, index) => (
<SwiperGridCarousel
key={`carousel-${carousel.uniqueId}-${index}`}
cardRows={[
{
property: 'name',
route: {
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [
{
idProperty: 'id',
slugProperty: 'albumId',
},
],
},
},
},
{
arrayProperty: 'name',
property: 'albumArtists',
route: {
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
slugs: [
{
idProperty: 'id',
slugProperty: 'albumArtistId',
},
],
{
arrayProperty: 'name',
property: 'albumArtists',
route: {
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
slugs: [
{
idProperty: 'id',
slugProperty: 'albumArtistId',
},
],
},
},
},
]}
data={carousel.data}
isLoading={carousel.loading}
itemType={LibraryItem.ALBUM}
route={{
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId}
/>
))}
</>
) : null}
</>
</Stack>
]}
data={carousel.data}
isLoading={carousel.loading}
itemType={LibraryItem.ALBUM}
route={{
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
slugs: [
{ idProperty: 'id', slugProperty: 'albumId' },
],
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId}
/>
))}
</>
) : null}
</>
</Stack>
</DetailContainer>
</ContentContainer>
);
};

View file

@ -35,7 +35,7 @@ export const AlbumDetailHeader = forwardRef(
},
{
id: 'duration',
secondary: true,
secondary: false,
value:
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
},

View file

@ -57,7 +57,10 @@ const AlbumDetailRoute = () => {
ref={headerRef}
background={background}
/>
<AlbumDetailContent tableRef={tableRef} />
<AlbumDetailContent
background={background}
tableRef={tableRef}
/>
</NativeScrollArea>
</AnimatedPage>
);

View file

@ -35,9 +35,14 @@ import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
import { SwiperGridCarousel } from '/@/renderer/components/grid-carousel';
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
const ContentContainer = styled.div`
position: relative;
z-index: 0;
`;
const DetailContainer = styled.div`
display: flex;
flex-direction: column;
gap: 3rem;
@ -47,13 +52,13 @@ const ContentContainer = styled.div`
.ag-theme-alpine-dark {
--ag-header-background-color: rgba(0, 0, 0, 0%) !important;
}
.ag-header {
margin-bottom: 0.5rem;
}
`;
export const AlbumArtistDetailContent = () => {
interface AlbumArtistDetailContentProps {
background?: string;
}
export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailContentProps) => {
const { albumArtistId } = useParams() as { albumArtistId: string };
const cq = useContainerQuery();
const handlePlayQueueAdd = usePlayQueueAdd();
@ -318,174 +323,181 @@ export const AlbumArtistDetailContent = () => {
return (
<ContentContainer ref={cq.ref}>
<Box component="section">
<Group spacing="md">
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Group spacing="xs">
<Button
compact
loading={
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
}
variant="subtle"
onClick={handleFavorite}
>
{detailQuery?.data?.userFavorite ? (
<RiHeartFill
color="red"
size={20}
/>
) : (
<RiHeartLine size={20} />
)}
</Button>
<Button
compact
variant="subtle"
onClick={(e) => {
if (!detailQuery?.data) return;
handleGeneralContextMenu(e, [detailQuery.data!]);
}}
>
<RiMoreFill size={20} />
</Button>
<Button
compact
uppercase
component={Link}
to={artistDiscographyLink}
variant="subtle"
>
View discography
</Button>
<Button
compact
uppercase
component={Link}
to={artistSongsLink}
variant="subtle"
>
View all songs
</Button>
</Group>
</Group>
</Box>
{showGenres ? (
<LibraryBackgroundOverlay backgroundColor={background} />
<DetailContainer>
<Box component="section">
<Group spacing="sm">
{detailQuery?.data?.genres?.map((genre) => (
<Group spacing="md">
<PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<Group spacing="xs">
<Button
key={`genre-${genre.id}`}
compact
component={Link}
radius="md"
size="md"
to={generatePath(
`${AppRoute.LIBRARY_ALBUM_ARTISTS}?genre=${genre.id}`,
{
albumArtistId,
},
loading={
createFavoriteMutation.isLoading ||
deleteFavoriteMutation.isLoading
}
variant="subtle"
onClick={handleFavorite}
>
{detailQuery?.data?.userFavorite ? (
<RiHeartFill
color="red"
size={20}
/>
) : (
<RiHeartLine size={20} />
)}
variant="outline"
>
{genre.name}
</Button>
))}
</Group>
</Box>
) : null}
{showBiography ? (
<Box
component="section"
maw="1280px"
>
<TextTitle
order={2}
weight={700}
>
About {detailQuery?.data?.name}
</TextTitle>
<Text
$secondary
component="p"
dangerouslySetInnerHTML={{ __html: detailQuery?.data?.biography || '' }}
sx={{ textAlign: 'justify' }}
/>
</Box>
) : null}
{showTopSongs ? (
<Box component="section">
<Group
noWrap
position="apart"
>
<Group
noWrap
align="flex-end"
>
<TextTitle
order={2}
weight={700}
<Button
compact
variant="subtle"
onClick={(e) => {
if (!detailQuery?.data) return;
handleGeneralContextMenu(e, [detailQuery.data!]);
}}
>
Top Songs
</TextTitle>
<RiMoreFill size={20} />
</Button>
<Button
compact
uppercase
component={Link}
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS, {
albumArtistId,
})}
to={artistDiscographyLink}
variant="subtle"
>
View all
View discography
</Button>
<Button
compact
uppercase
component={Link}
to={artistSongsLink}
variant="subtle"
>
View all songs
</Button>
</Group>
</Group>
<VirtualTable
autoFitColumns
autoHeight
deselectOnClickOutside
suppressCellFocus
suppressHorizontalScroll
suppressLoadingOverlay
suppressRowDrag
columnDefs={topSongsColumnDefs}
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId}
rowData={topSongs}
rowHeight={60}
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</Box>
) : null}
<Box component="section">
<Stack spacing="xl">
{carousels
.filter((c) => !c.isHidden)
.map((carousel) => (
<SwiperGridCarousel
key={`carousel-${carousel.uniqueId}`}
cardRows={cardRows[carousel.itemType as keyof typeof cardRows]}
data={carousel.data}
isLoading={carousel.loading}
itemType={carousel.itemType}
route={cardRoutes[carousel.itemType as keyof typeof cardRoutes]}
swiperProps={{
grid: {
rows: 2,
},
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId}
/>
))}
</Stack>
</Box>
{showGenres ? (
<Box component="section">
<Group spacing="sm">
{detailQuery?.data?.genres?.map((genre) => (
<Button
key={`genre-${genre.id}`}
compact
component={Link}
radius="md"
size="md"
to={generatePath(
`${AppRoute.LIBRARY_ALBUM_ARTISTS}?genre=${genre.id}`,
{
albumArtistId,
},
)}
variant="outline"
>
{genre.name}
</Button>
))}
</Group>
</Box>
) : null}
{showBiography ? (
<Box
component="section"
maw="1280px"
>
<TextTitle
order={2}
weight={700}
>
About {detailQuery?.data?.name}
</TextTitle>
<Text
$secondary
component="p"
dangerouslySetInnerHTML={{ __html: detailQuery?.data?.biography || '' }}
sx={{ textAlign: 'justify' }}
/>
</Box>
) : null}
{showTopSongs ? (
<Box component="section">
<Group
noWrap
position="apart"
>
<Group
noWrap
align="flex-end"
>
<TextTitle
order={2}
weight={700}
>
Top Songs
</TextTitle>
<Button
compact
uppercase
component={Link}
to={generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
{
albumArtistId,
},
)}
variant="subtle"
>
View all
</Button>
</Group>
</Group>
<VirtualTable
autoFitColumns
autoHeight
deselectOnClickOutside
suppressCellFocus
suppressHorizontalScroll
suppressLoadingOverlay
suppressRowDrag
columnDefs={topSongsColumnDefs}
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId}
rowData={topSongs}
rowHeight={60}
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</Box>
) : null}
<Box component="section">
<Stack spacing="xl">
{carousels
.filter((c) => !c.isHidden)
.map((carousel) => (
<SwiperGridCarousel
key={`carousel-${carousel.uniqueId}`}
cardRows={cardRows[carousel.itemType as keyof typeof cardRows]}
data={carousel.data}
isLoading={carousel.loading}
itemType={carousel.itemType}
route={cardRoutes[carousel.itemType as keyof typeof cardRoutes]}
swiperProps={{
grid: {
rows: 2,
},
}}
title={{
label: carousel.title,
}}
uniqueId={carousel.uniqueId}
/>
))}
</Stack>
</Box>
</DetailContainer>
</ContentContainer>
);
};

View file

@ -58,7 +58,7 @@ const AlbumArtistDetailRoute = () => {
ref={headerRef}
background={background}
/>
<AlbumArtistDetailContent />
<AlbumArtistDetailContent background={background} />
</NativeScrollArea>
</AnimatedPage>
);

View file

@ -55,7 +55,7 @@ const BackgroundImageOverlay = styled.div`
z-index: -1;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(20, 21, 23, 40%), var(--main-bg));
background: var(--bg-header-overlay);
`;
const Controls = () => {

View file

@ -42,10 +42,6 @@ const ContentContainer = styled.div`
.ag-theme-alpine-dark {
--ag-header-background-color: rgba(0, 0, 0, 0%) !important;
}
.ag-header {
margin-bottom: 0.5rem;
}
`;
interface PlaylistDetailContentProps {

View file

@ -0,0 +1,14 @@
import styled from 'styled-components';
export const LibraryBackgroundOverlay = styled.div<{ backgroundColor?: string }>`
position: absolute;
z-index: -1;
width: 100%;
height: 20vh;
min-height: 200px;
background: ${(props) => props.backgroundColor};
background-image: var(--bg-subheader-overlay);
opacity: 0.3;
user-select: none;
pointer-events: none;
`;

View file

@ -1,6 +1,6 @@
import { forwardRef, ReactNode, Ref } from 'react';
import { Center, Group } from '@mantine/core';
import { useMergedRef } from '@mantine/hooks';
import { forwardRef, ReactNode, Ref } from 'react';
import { RiAlbumFill } from 'react-icons/ri';
import { Link } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img';
@ -57,6 +57,7 @@ const BackgroundImage = styled.div<{ background: string }>`
width: 100%;
height: 100%;
background: ${(props) => props.background};
opacity: 0.9;
`;
const BackgroundImageOverlay = styled.div`
@ -66,8 +67,7 @@ const BackgroundImageOverlay = styled.div`
z-index: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)),
var(--background-noise);
background: var(--bg-header-overlay);
`;
interface LibraryHeaderProps {