Adjust base page headers

This commit is contained in:
jeffvli 2022-12-31 17:50:05 -08:00
parent 81455602ef
commit 6174dc128d
10 changed files with 233 additions and 172 deletions

View file

@ -1,22 +1,26 @@
import { Flex, FlexProps } from '@mantine/core';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useShouldPadTitlebar } from '/@/renderer/hooks'; import { useShouldPadTitlebar } from '/@/renderer/hooks';
const Container = styled(motion.div)<{ $useOpacity?: boolean; height?: string; position?: string }>` const Container = styled(motion(Flex))<{ $isHidden?: boolean; height?: string; position?: string }>`
position: ${(props) => props.position}; position: ${(props) => props.position || 'relative'};
z-index: 100; z-index: 2000;
width: 100%; width: 100%;
height: ${(props) => props.height || '60px'}; height: ${(props) => props.height || '60px'};
opacity: ${(props) => props.$useOpacity && 'var(--header-opacity)'}; opacity: ${(props) => (props.$isHidden ? 0 : 1)};
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;
user-select: ${(props) => (props.$isHidden ? 'none' : 'auto')};
pointer-events: ${(props) => (props.$isHidden ? 'none' : 'auto')};
`; `;
const Header = styled(motion.div)<{ $padRight?: boolean }>` const Header = styled(motion.div)<{ $padRight?: boolean }>`
position: relative;
z-index: 15; z-index: 15;
width: 100%;
height: 100%; height: 100%;
margin-right: ${(props) => props.$padRight && '170px'}; margin-right: ${(props) => props.$padRight && '170px'};
padding: 1rem;
-webkit-app-region: drag; -webkit-app-region: drag;
button { button {
@ -28,40 +32,41 @@ const Header = styled(motion.div)<{ $padRight?: boolean }>`
} }
`; `;
// const BackgroundImage = styled.div<{ background: string }>` const BackgroundImage = styled.div<{ background: string }>`
// position: absolute; position: absolute;
// top: 0; top: 0;
// z-index: -1; z-index: 1;
// width: 100%; width: 100%;
// height: 100%; height: 100%;
// background: ${(props) => props.background}; background: ${(props) => props.background || 'var(--titlebar-bg)'};
// `; `;
// const BackgroundImageOverlay = styled.div` const BackgroundImageOverlay = styled.div`
// position: absolute; position: absolute;
// top: 0; top: 0;
// left: 0; left: 0;
// z-index: -1; z-index: 2;
// width: 100%; width: 100%;
// height: 100%; height: 100%;
// /* background: linear-gradient(180deg, rgba(25, 26, 28, 0%), var(--main-bg)); */ background: rgba(0, 0, 0, 30%), var(--background-noise);
// /* background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIj48ZmlsdGVyIGlkPSJhIiB4PSIwIiB5PSIwIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iLjc1IiBzdGl0Y2hUaWxlcz0ic3RpdGNoIi8+PGZlQ29sb3JNYXRyaXggdHlwZT0ic2F0dXJhdGUiIHZhbHVlcz0iMCIvPjwvZmlsdGVyPjxwYXRoIGZpbHRlcj0idXJsKCNhKSIgb3BhY2l0eT0iLjA1IiBkPSJNMCAwaDMwMHYzMDBIMHoiLz48L3N2Zz4='); */ `;
// `;
interface PageHeaderProps { interface PageHeaderProps
extends Omit<FlexProps, 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'> {
backgroundColor?: string; backgroundColor?: string;
children?: React.ReactNode; children?: React.ReactNode;
height?: string; height?: string;
isHidden?: boolean;
position?: string; position?: string;
useOpacity?: boolean;
} }
export const PageHeader = ({ export const PageHeader = ({
position, position,
height, height,
backgroundColor, backgroundColor,
useOpacity, isHidden,
children, children,
...props
}: PageHeaderProps) => { }: PageHeaderProps) => {
const ref = useRef(null); const ref = useRef(null);
const padRight = useShouldPadTitlebar(); const padRight = useShouldPadTitlebar();
@ -74,17 +79,18 @@ export const PageHeader = ({
return ( return (
<Container <Container
ref={ref} ref={ref}
$useOpacity={useOpacity} $isHidden={isHidden}
animate={{
backgroundColor,
transition: { duration: 1.5 },
}}
height={height} height={height}
position={position} position={position}
{...props}
> >
<Header $padRight={padRight}>{children}</Header> <Header $padRight={padRight}>{!isHidden && <>{children}</>}</Header>
{/* <BackgroundImage background={backgroundColor} /> */} {backgroundColor && (
{/* <BackgroundImageOverlay /> */} <>
<BackgroundImage background={'var(--titlebar-bg)' || ''} />
<BackgroundImageOverlay />
</>
)}
</Container> </Container>
); );
}; };

View file

@ -1,5 +1,6 @@
import { Center, Group } from '@mantine/core'; import { Center, Group } from '@mantine/core';
import { Fragment } from 'react'; import { useMergedRef } from '@mantine/hooks';
import { forwardRef, Fragment, Ref } from 'react';
import { RiAlbumFill } from 'react-icons/ri'; import { RiAlbumFill } from 'react-icons/ri';
import { generatePath, useParams } from 'react-router'; import { generatePath, useParams } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -64,17 +65,19 @@ const BackgroundImageOverlay = styled.div`
z-index: 0; z-index: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)); background: linear-gradient(180deg, rgba(25, 26, 28, 5%), var(--main-bg)), var(--background-noise);
`; `;
interface AlbumDetailHeaderProps { interface AlbumDetailHeaderProps {
background: string; background: string;
} }
export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => { export const AlbumDetailHeader = forwardRef(
({ background }: AlbumDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
const { albumId } = useParams() as { albumId: string }; const { albumId } = useParams() as { albumId: string };
const detailQuery = useAlbumDetail({ id: albumId }); const detailQuery = useAlbumDetail({ id: albumId });
const cq = useContainerQuery(); const cq = useContainerQuery();
const mergedRef = useMergedRef(ref, cq.ref);
const titleSize = cq.isXl const titleSize = cq.isXl
? '6rem' ? '6rem'
@ -87,7 +90,7 @@ export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => {
: '2rem'; : '2rem';
return ( return (
<HeaderContainer ref={cq.ref}> <HeaderContainer ref={mergedRef}>
<BackgroundImage background={background} /> <BackgroundImage background={background} />
<BackgroundImageOverlay /> <BackgroundImageOverlay />
<CoverImageWrapper> <CoverImageWrapper>
@ -103,8 +106,8 @@ export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => {
sx={{ sx={{
background: 'var(--placeholder-bg)', background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)', borderRadius: 'var(--card-default-radius)',
height: `${80}px`, height: `${225}px`,
width: `${80}px`, width: `${225}px`,
}} }}
> >
<RiAlbumFill <RiAlbumFill
@ -179,4 +182,5 @@ export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => {
</MetadataWrapper> </MetadataWrapper>
</HeaderContainer> </HeaderContainer>
); );
}; },
);

View file

@ -296,7 +296,7 @@ export const AlbumListHeader = ({ gridRef, tableRef }: AlbumListHeaderProps) =>
}; };
return ( return (
<PageHeader> <PageHeader p="1rem">
<HeaderItems ref={cq.ref}> <HeaderItems ref={cq.ref}>
<Flex <Flex
align="center" align="center"

View file

@ -1,34 +1,61 @@
import { PageHeader, ScrollArea } from '/@/renderer/components'; import { PageHeader, ScrollArea, TextTitle } from '/@/renderer/components';
import { AnimatedPage } from '/@/renderer/features/shared'; import { AnimatedPage, PlayButton } from '/@/renderer/features/shared';
import { useRef } from 'react'; import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query'; import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { useFastAverageColor } from '/@/renderer/hooks'; import { useFastAverageColor, useShouldPadTitlebar } from '/@/renderer/hooks';
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content'; import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header'; import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
import { useIntersection } from '@mantine/hooks';
import { Group } from '@mantine/core';
const AlbumDetailRoute = () => { const AlbumDetailRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
const { albumId } = useParams() as { albumId: string }; const { albumId } = useParams() as { albumId: string };
const detailQuery = useAlbumDetail({ id: albumId }); const detailQuery = useAlbumDetail({ id: albumId });
const background = useFastAverageColor(detailQuery.data?.imageUrl); const background = useFastAverageColor(detailQuery.data?.imageUrl);
const padTop = useShouldPadTitlebar();
const containerRef = useRef();
const { ref, entry } = useIntersection({
root: containerRef.current,
threshold: 0,
});
return ( return (
<AnimatedPage key={`album-detail-${albumId}`}> <AnimatedPage key={`album-detail-${albumId}`}>
<PageHeader <PageHeader
useOpacity backgroundColor="black"
isHidden={entry?.isIntersecting}
position="absolute" position="absolute"
>
<Group p="1rem">
<PlayButton
h={40}
w={40}
/> />
<TextTitle
fw="bold"
order={2}
>
{detailQuery?.data?.name}
</TextTitle>
</Group>
</PageHeader>
<ScrollArea <ScrollArea
ref={containerRef}
h="100%" h="100%"
offsetScrollbars={false} offsetScrollbars={false}
styles={{ scrollbar: { marginTop: '35px' } }} styles={{ scrollbar: { marginTop: padTop ? '35px' : 0 } }}
> >
{background && ( {background && (
<> <>
<AlbumDetailHeader background={background} /> <AlbumDetailHeader
ref={ref}
background={background}
/>
<AlbumDetailContent tableRef={tableRef} /> <AlbumDetailContent tableRef={tableRef} />
</> </>
)} )}

View file

@ -289,7 +289,7 @@ export const AlbumArtistListHeader = ({ gridRef, tableRef }: AlbumArtistListHead
}; };
return ( return (
<PageHeader> <PageHeader p="1rem">
<HeaderItems ref={cq.ref}> <HeaderItems ref={cq.ref}>
<Flex <Flex
align="center" align="center"

View file

@ -204,7 +204,7 @@ const HomeRoute = () => {
<AnimatedPage> <AnimatedPage>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<PageHeader <PageHeader
useOpacity isHidden
backgroundColor="var(--sidebar-bg)" backgroundColor="var(--sidebar-bg)"
/> />
<Box <Box

View file

@ -1,27 +1,49 @@
import { useRef } from 'react'; import { useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Box, Stack } from '@mantine/core'; import { Flex } from '@mantine/core';
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
import styled from 'styled-components';
import { PlayQueueListControls } from './play-queue-list-controls'; import { PlayQueueListControls } from './play-queue-list-controls';
import { Song } from '/@/renderer/api/types'; import { Song } from '/@/renderer/api/types';
const BackgroundImageOverlay = styled.div`
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 30%), var(--background-noise);
`;
export const SidebarPlayQueue = () => { export const SidebarPlayQueue = () => {
const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null); const queueRef = useRef<{ grid: AgGridReactType<Song> } | null>(null);
return ( return (
<Stack <>
h="100%" <Flex
spacing={0} bg="var(--titlebar-bg)"
sx={{ borderLeft: '2px solid var(--generic-border-color)' }} h="60px"
sx={{ position: 'relative' }}
w="100%"
> >
<Box <BackgroundImageOverlay />
h="50px" <Flex
h="100%"
mr="160px" mr="160px"
sx={{ sx={{
WebkitAppRegion: 'drag', WebkitAppRegion: 'drag',
zIndex: -1, background: 'var(--titlebar-bg)',
}} }}
w="100%"
/> />
</Flex>
<Flex
direction="column"
h="calc(100% - 60px)"
sx={{ borderLeft: '2px solid var(--generic-border-color)' }}
w="100%"
>
<PlayQueue <PlayQueue
ref={queueRef} ref={queueRef}
type="sideQueue" type="sideQueue"
@ -30,6 +52,7 @@ export const SidebarPlayQueue = () => {
tableRef={queueRef} tableRef={queueRef}
type="sideQueue" type="sideQueue"
/> />
</Stack> </Flex>
</>
); );
}; };

View file

@ -189,7 +189,7 @@ export const PlaylistListHeader = ({ tableRef }: PlaylistListHeaderProps) => {
}; };
return ( return (
<PageHeader> <PageHeader p="1rem">
<Flex <Flex
ref={cq.ref} ref={cq.ref}
direction="row" direction="row"

View file

@ -242,7 +242,7 @@ export const SongListHeader = ({ tableRef }: SongListHeaderProps) => {
}; };
return ( return (
<PageHeader> <PageHeader p="1rem">
<Flex <Flex
ref={cq.ref} ref={cq.ref}
direction="row" direction="row"

View file

@ -63,6 +63,7 @@ const SidebarContainer = styled.div`
const RightSidebarContainer = styled(motion.div)` const RightSidebarContainer = styled(motion.div)`
position: relative; position: relative;
grid-area: right-sidebar; grid-area: right-sidebar;
height: 100%;
background: var(--sidebar-bg); background: var(--sidebar-bg);
border-left: var(--sidebar-border); border-left: var(--sidebar-border);
`; `;