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

View file

@ -9,201 +9,107 @@
@padding: 1rem;
.side-drawer {
position: fixed;
top: 0;
right: 0;
overflow: visible;
display: flex;
flex-direction: column;
padding: @padding;
height: 100dvh;
max-width: 35rem;
overflow-y: auto;
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 {
position: absolute;
top: 0;
right: 0;
width: 100dvw;
height: 100dvh;
background-color: rgba(0, 0, 0, 0.2);
.close-button {
display: none;
}
.open-button {
position: absolute;
right: -4rem;
top: 50%;
height: 12.5rem;
width: 7.5rem;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
background-color: var(--modal-background-color);
top: 1.3rem;
right: 1.3rem;
padding: 0.5rem;
background-color: transparent;
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;
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 {
width: 2.5rem;
height: 2.5rem;
color: var(--primary-foreground-color);
width: 2rem;
height: 2rem;
opacity: 0.6;
margin-right: 4rem;
transition: 0.3s opacity ease-in-out;
}
&:hover {
background-color: var(--overlay-color);
.icon {
opacity: 1;
}
}
}
.content {
display: flex;
flex-direction: column;
.info {
padding: @padding;
height: 100dvh;
max-width: 35rem;
overflow-y: auto;
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;
transform: translateX(100%);
pointer-events: none;
z-index: 1;
flex: none;
.close-button {
display: none;
position: absolute;
top: 1.3rem;
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;
.side-drawer-meta-preview {
.action-buttons-container {
padding-top: 0;
margin-top: 0;
}
}
}
&.open {
.overlay {
display: block;
}
.series-content {
flex: 2;
display: flex;
flex-direction: column;
.content {
transform: translateX(0);
pointer-events: auto;
.open-button {
opacity: 0;
}
.videos {
overflow-y: auto;
}
}
}
@media screen and (max-width: @small) {
.side-drawer {
.content {
max-width: 40dvw;
}
max-width: 40dvw;
}
}
@media (orientation: portrait) and (max-width: @xsmall) {
.side-drawer {
.content {
max-width: 100dvw;
max-width: 100dvw;
.close-button {
display: block;
}
.close-button {
display: block;
}
}
}
@media (orientation: landscape) and (max-width: @xsmall) {
.side-drawer {
.content {
max-width: 50dvw;
max-width: 50dvw;
.info {
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;
}
.info {
max-height: 30dvh;
}
}
}
@media screen and (max-width: @xxsmall) {
.side-drawer {
.content {
padding: calc(@padding / 2);
padding: calc(@padding / 2);
.info {
padding: calc(@padding / 2);
}
.info {
padding: calc(@padding / 2);
}
}
}

View file

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

View file

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