feat: rewrite sidedrawer to act like a menu

This commit is contained in:
Timothy Z. 2024-12-16 19:25:31 +02:00
parent 1555e05d6b
commit 72b7a370fc
4 changed files with 102 additions and 212 deletions

View file

@ -56,7 +56,7 @@ const Player = ({ urlParams, queryParams }) => {
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false); const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false); const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false); const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
const [sideDrawerOpen, openSideDrawer, closeSideDrawer, toggleSideDrawer] = useBinaryState(false); const [sideDrawerOpen, , closeSideDrawer, toggleSideDrawer] = useBinaryState(false);
const menusOpen = React.useMemo(() => { const menusOpen = React.useMemo(() => {
return optionsMenuOpen || subtitlesMenuOpen || speedMenuOpen || statisticsMenuOpen; return optionsMenuOpen || subtitlesMenuOpen || speedMenuOpen || statisticsMenuOpen;
@ -67,6 +67,7 @@ const Player = ({ urlParams, queryParams }) => {
closeSubtitlesMenu(); closeSubtitlesMenu();
closeSpeedMenu(); closeSpeedMenu();
closeStatisticsMenu(); closeStatisticsMenu();
closeSideDrawer();
}, []); }, []);
const overlayHidden = React.useMemo(() => { const overlayHidden = React.useMemo(() => {
@ -407,13 +408,6 @@ const Player = ({ urlParams, queryParams }) => {
} }
}, [video.state.playbackSpeed]); }, [video.state.playbackSpeed]);
React.useEffect(() => {
if (sideDrawerOpen) {
closeMenus();
setImmersed(true);
}
}, [sideDrawerOpen]);
React.useEffect(() => { React.useEffect(() => {
const toastFilter = (item) => item?.dataset?.type === 'CoreEvent'; const toastFilter = (item) => item?.dataset?.type === 'CoreEvent';
toast.addFilter(toastFilter); toast.addFilter(toastFilter);
@ -700,14 +694,12 @@ const Player = ({ urlParams, queryParams }) => {
null null
} }
{ {
player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem !== null && player.metaItem.type === 'Ready' && sideDrawerOpen ?
<SideDrawer <SideDrawer
metaItem={player.metaItem.content} metaItem={player.metaItem.content}
seriesInfo={player.seriesInfo} seriesInfo={player.seriesInfo}
className={classnames(styles['layer'], styles['side-drawer-layer'])} className={classnames(styles['layer'], styles['side-drawer-layer'])}
openSideDrawer={openSideDrawer} closeSideDrawer={closeSideDrawer}
closeSideBar={closeSideDrawer}
sideDrawerOpen={sideDrawerOpen}
/> />
: null : null
} }

View file

@ -9,201 +9,107 @@
@padding: 1rem; @padding: 1rem;
.side-drawer { .side-drawer {
position: fixed; display: flex;
top: 0; flex-direction: column;
right: 0; padding: @padding;
overflow: visible; height: 100dvh;
max-width: 35rem;
overflow-y: auto;
position: relative; position: relative;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: 0 1.35rem 2.7rem var(--color-background-dark5-40), 0 1.1rem 0.85rem var(--color-background-dark5-20);
backdrop-filter: blur(15px);
transition: transform 0.3s ease-in-out;
z-index: 1;
.overlay { .close-button {
position: absolute;
top: 0;
right: 0;
width: 100dvw;
height: 100dvh;
background-color: rgba(0, 0, 0, 0.2);
display: none; display: none;
}
.open-button {
position: absolute; position: absolute;
right: -4rem; top: 1.3rem;
top: 50%; right: 1.3rem;
height: 12.5rem; padding: 0.5rem;
width: 7.5rem; background-color: transparent;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
background-color: var(--modal-background-color);
cursor: pointer; cursor: pointer;
border-radius: 50%; z-index: 2;
border-radius: var(--border-radius);
transition: 0.3s all ease-in-out;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
vertical-align: middle;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
transition: opacity 0.3s ease-in-out, border-radius 0.3s ease-in-out;
will-change: transform, opacity;
opacity: 1;
.icon { .icon {
width: 2.5rem;
height: 2.5rem;
color: var(--primary-foreground-color); color: var(--primary-foreground-color);
width: 2rem;
height: 2rem;
opacity: 0.6; opacity: 0.6;
margin-right: 4rem; transition: 0.3s opacity ease-in-out;
} }
&:hover { &:hover {
background-color: var(--overlay-color);
.icon { .icon {
opacity: 1; opacity: 1;
} }
} }
} }
.content { .info {
display: flex;
flex-direction: column;
padding: @padding; padding: @padding;
height: 100dvh;
max-width: 35rem;
overflow-y: auto; overflow-y: auto;
position: relative; flex: none;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
background-color: var(--modal-background-color);
box-shadow: 0 1.35rem 2.7rem var(--color-background-dark5-40), 0 1.1rem 0.85rem var(--color-background-dark5-20);
backdrop-filter: blur(15px);
transition: transform 0.3s ease-in-out;
transform: translateX(100%);
pointer-events: none;
z-index: 1;
.close-button { .side-drawer-meta-preview {
display: none; .action-buttons-container {
position: absolute; padding-top: 0;
top: 1.3rem; margin-top: 0;
right: 1.3rem;
padding: 0.5rem;
background-color: transparent;
cursor: pointer;
z-index: 2;
border-radius: var(--border-radius);
transition: 0.3s all ease-in-out;
-webkit-tap-highlight-color: transparent;
.icon {
color: var(--primary-foreground-color);
width: 2rem;
height: 2rem;
opacity: 0.6;
transition: 0.3s opacity ease-in-out;
}
&:hover {
background-color: var(--overlay-color);
.icon {
opacity: 1;
}
}
}
.info {
padding: @padding;
overflow-y: auto;
flex: none;
.side-drawer-meta-preview {
.action-buttons-container {
padding-top: 0;
margin-top: 0;
}
}
}
.series-content {
flex: 2;
display: flex;
flex-direction: column;
.videos {
overflow-y: auto;
} }
} }
} }
&.open { .series-content {
.overlay { flex: 2;
display: block; display: flex;
} flex-direction: column;
.content { .videos {
transform: translateX(0); overflow-y: auto;
pointer-events: auto;
.open-button {
opacity: 0;
}
} }
} }
} }
@media screen and (max-width: @small) { @media screen and (max-width: @small) {
.side-drawer { .side-drawer {
.content { max-width: 40dvw;
max-width: 40dvw;
}
} }
} }
@media (orientation: portrait) and (max-width: @xsmall) { @media (orientation: portrait) and (max-width: @xsmall) {
.side-drawer { .side-drawer {
.content { max-width: 100dvw;
max-width: 100dvw;
.close-button { .close-button {
display: block; display: block;
}
} }
} }
} }
@media (orientation: landscape) and (max-width: @xsmall) { @media (orientation: landscape) and (max-width: @xsmall) {
.side-drawer { .side-drawer {
.content { max-width: 50dvw;
max-width: 50dvw;
.info { .info {
max-height: 30dvh; max-height: 30dvh;
}
}
}
}
@media screen and (max-width: @xsmall) {
.side-drawer {
.open-button {
height: 8rem;
width: 4.5rem;
right: -2.5rem;
.icon {
width: 2rem;
height: 2rem;
margin-right: 2.5rem;
}
} }
} }
} }
@media screen and (max-width: @xxsmall) { @media screen and (max-width: @xxsmall) {
.side-drawer { .side-drawer {
.content { padding: calc(@padding / 2);
padding: calc(@padding / 2);
.info { .info {
padding: calc(@padding / 2); padding: calc(@padding / 2);
}
} }
} }
} }

View file

@ -7,19 +7,19 @@ import classNames from 'classnames';
import styles from './SideDrawer.less'; import styles from './SideDrawer.less';
import { useServices } from 'stremio/services'; import { useServices } from 'stremio/services';
import Icon from '@stremio/stremio-icons/react'; import Icon from '@stremio/stremio-icons/react';
import useOutsideClick from 'stremio/common/useOutsideClick';
type Props = { type Props = {
seriesInfo: { season: number, episode: number }; seriesInfo: { season: number, episode: number };
metaItem: MetaItem; metaItem: MetaItem;
className?: string; className?: string;
openSideDrawer: () => void; closeSideDrawer: () => void;
closeSideBar: () => void;
sideDrawerOpen: boolean;
}; };
const SideDrawer = ({ seriesInfo, className, openSideDrawer, closeSideBar, sideDrawerOpen, ...props }: Props) => { const SideDrawer = ({ seriesInfo, className, closeSideDrawer, ...props }: Props) => {
const { core } = useServices(); const { core } = useServices();
const [season, setSeason] = useState<number>(seriesInfo?.season); const [season, setSeason] = useState<number>(seriesInfo?.season);
const sideDrawerRef = useOutsideClick(() => closeSideDrawer());
const metaItem = useMemo(() => { const metaItem = useMemo(() => {
return seriesInfo ? return seriesInfo ?
{ {
@ -59,61 +59,53 @@ const SideDrawer = ({ seriesInfo, className, openSideDrawer, closeSideBar, sideD
}, []); }, []);
return ( return (
<div className={classNames(styles['side-drawer'], className, { [styles['open']]: sideDrawerOpen })}> <div className={classNames(styles['side-drawer'], className)} ref={sideDrawerRef}>
<div className={styles['overlay']} onClick={closeSideBar} /> <div className={styles['close-button']} onClick={closeSideDrawer}>
<div className={styles['open-button']} onClick={openSideDrawer}> <Icon className={styles['icon']} name={'chevron-forward'} />
<Icon name={'chevron-back'} className={styles['icon']} />
</div> </div>
{/* @ts-expect-error inert is not recognisable on div element; we need it to not focus the sideDrawer when closed */} <div className={styles['info']}>
<div className={styles['content']} inert={!sideDrawerOpen ? '' : undefined}> <MetaPreview
<div className={styles['close-button']} onClick={closeSideBar}> className={styles['side-drawer-meta-preview']}
<Icon className={styles['icon']} name={'chevron-forward'} /> compact={true}
</div> name={metaItem.name}
<div className={styles['info']}> logo={metaItem.logo}
<MetaPreview runtime={metaItem.runtime}
className={styles['side-drawer-meta-preview']} releaseInfo={metaItem.releaseInfo}
compact={true} released={metaItem.released}
name={metaItem.name} description={metaItem.description}
logo={metaItem.logo} links={metaItem.links}
runtime={metaItem.runtime} />
releaseInfo={metaItem.releaseInfo} </div>
released={metaItem.released} {
description={metaItem.description} seriesInfo ?
links={metaItem.links} <div className={styles['series-content']}>
/> <SeasonsBar
</div> season={season}
{ seasons={seasons}
seriesInfo ? onSelect={seasonOnSelect}
<div className={styles['series-content']}> />
<SeasonsBar <div className={styles['videos']}>
season={season} {videos.map((video, index) => (
seasons={seasons} <Video
onSelect={seasonOnSelect} key={index}
/> className={styles['video']}
<div className={styles['videos']}> id={video.id}
{videos.map((video, index) => ( title={video.title}
<Video thumbnail={video.thumbnail}
key={index} episode={video.episode}
className={styles['video']} released={video.released}
id={video.id} upcoming={video.upcoming}
title={video.title} watched={video.watched}
thumbnail={video.thumbnail} progress={video.progress}
episode={video.episode} deepLinks={video.deepLinks}
released={video.released} scheduled={video.scheduled}
upcoming={video.upcoming} onMarkVideoAsWatched={onMarkVideoAsWatched}
watched={video.watched} />
progress={video.progress} ))}
deepLinks={video.deepLinks}
scheduled={video.scheduled}
onMarkVideoAsWatched={onMarkVideoAsWatched}
/>
))}
</div>
</div> </div>
: null </div>
} : null
}
</div>
</div> </div>
); );

View file

@ -115,7 +115,7 @@ html:not(.active-slider-within) {
} }
&.side-drawer-layer { &.side-drawer-layer {
top: 0; bottom: 0;
right: 0; right: 0;
left: initial; left: initial;
bottom: initial; bottom: initial;