Address some layout issues with odd content spacing on mobile, and footer position (#3022)
* - set vars for player container height and status bar height - use them to calculate mobile top spacing to adjust for tab content positioning * give main content section a min height, place footer absolutely at bottom; rm all the fixed footer styling * cleanup; restructure tabbed display logic and css a bit * Prettified Code! * cleanup * fix(story): footer story needs to be wrapped in RecoilRoot if it is to use Recoil * revert adding footer to mobile about section * prevent double scrolling --------- Co-authored-by: gingervitis <gingervitis@users.noreply.github.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
@@ -2,37 +2,15 @@
|
|||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
// this margin is for fixed header
|
// this margin is for fixed header
|
||||||
margin-top: var(--header-height);
|
padding-top: var(--header-height);
|
||||||
background-color: var(--theme-color-main-background);
|
background-color: var(--theme-color-main-background);
|
||||||
@include screen(tablet) {
|
min-height: 100vh;
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
// this one is for fixed footer
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include screen(mobile) {
|
// add some spacing between the last row of content and the footer
|
||||||
margin-bottom: 0px;
|
:global(.ant-row) {
|
||||||
|
&:last-of-type {
|
||||||
footer {
|
margin-bottom: 5em;
|
||||||
display: none;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fadeIn {
|
|
||||||
animation: fadein 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/* eslint-disable react/no-unescaped-entities */
|
/* eslint-disable react/no-unescaped-entities */
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { FC, useEffect, useRef, useState } from 'react';
|
import { FC, useEffect, useRef } from 'react';
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
@@ -32,6 +32,7 @@ import { PushNotificationServiceWorker } from '../../workers/PushNotificationSer
|
|||||||
import { AppStateOptions } from '../../stores/application-state';
|
import { AppStateOptions } from '../../stores/application-state';
|
||||||
import { Noscript } from '../../ui/Noscript/Noscript';
|
import { Noscript } from '../../ui/Noscript/Noscript';
|
||||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||||
|
import { DYNAMIC_PADDING_VALUE } from '../../../utils/constants';
|
||||||
|
|
||||||
// Lazy loaded components
|
// Lazy loaded components
|
||||||
|
|
||||||
@@ -46,7 +47,6 @@ const FatalErrorStateModal = dynamic(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const Main: FC = () => {
|
export const Main: FC = () => {
|
||||||
const [displayFooter, setDisplayFooter] = useState(false);
|
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
const { name } = clientConfig;
|
const { name } = clientConfig;
|
||||||
@@ -58,42 +58,16 @@ export const Main: FC = () => {
|
|||||||
const layoutRef = useRef<HTMLDivElement>(null);
|
const layoutRef = useRef<HTMLDivElement>(null);
|
||||||
const { chatDisabled } = clientConfig;
|
const { chatDisabled } = clientConfig;
|
||||||
const { videoAvailable } = appState;
|
const { videoAvailable } = appState;
|
||||||
const { online, streamTitle, versionNumber: version } = clientStatus;
|
const { online, streamTitle } = clientStatus;
|
||||||
|
|
||||||
// accounts for sidebar width when online in desktop
|
// accounts for sidebar width when online in desktop
|
||||||
const showChat = online && !chatDisabled && isChatVisible;
|
const showChat = online && !chatDisabled && isChatVisible;
|
||||||
const dynamicFooterPadding = showChat && !isMobile ? '340px' : '20px';
|
const dynamicFooterPadding = showChat && !isMobile ? DYNAMIC_PADDING_VALUE : '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setupNoLinkReferrer(layoutRef.current);
|
setupNoLinkReferrer(layoutRef.current);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
const documentHeight = document.body.scrollHeight;
|
|
||||||
const currentScroll = window.scrollY + window.innerHeight;
|
|
||||||
|
|
||||||
// When the user is [modifier]px from the bottom, fire the event.
|
|
||||||
const modifier = 10;
|
|
||||||
if (currentScroll + modifier > documentHeight) {
|
|
||||||
if (!displayFooter) {
|
|
||||||
setDisplayFooter(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-lonely-if
|
|
||||||
if (displayFooter) {
|
|
||||||
setDisplayFooter(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', handleScroll);
|
|
||||||
};
|
|
||||||
}, [displayFooter]);
|
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const headerText = online ? streamTitle || name : name;
|
const headerText = online ? streamTitle || name : name;
|
||||||
|
|
||||||
@@ -196,12 +170,8 @@ export const Main: FC = () => {
|
|||||||
{fatalError && (
|
{fatalError && (
|
||||||
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
style={displayFooter ? { display: 'flex' } : { display: 'none' }}
|
{(!isMobile || !online) && <Footer dynamicPaddingValue={dynamicFooterPadding} />}
|
||||||
className={styles.fadeIn}
|
|
||||||
>
|
|
||||||
<Footer version={version} dynamicPadding={dynamicFooterPadding} />
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
<Noscript />
|
<Noscript />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,22 +4,23 @@
|
|||||||
padding: var(--content-padding);
|
padding: var(--content-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lowerSectionMobile {
|
.lowerSectionMobileTabbed {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
position: absolute;
|
position: relative;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
|
top: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
@include screen(mobile) {
|
&.online {
|
||||||
//sets the position of tabbed content for online mode
|
position: absolute;
|
||||||
top: 280px;
|
top: calc(var(--player-container-height) + var(--status-bar-height) + var(--header-height));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.ant-tabs-nav) {
|
:global(.ant-tabs-nav) {
|
||||||
@@ -29,15 +30,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.online {
|
.lowerSectionMobileNoTabs {
|
||||||
@include screen(tablet) {
|
|
||||||
//sets the position of tabbed content for online mode
|
|
||||||
position: absolute;
|
|
||||||
top: 430px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobileNoTabs {
|
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,10 +137,13 @@ export const MobileContent: FC<MobileContentProps> = ({
|
|||||||
<ComponentErrorFallback error={error} resetErrorBoundary={resetErrorBoundary} />
|
<ComponentErrorFallback error={error} resetErrorBoundary={resetErrorBoundary} />
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={classNames([styles.lowerSectionMobile, online && styles.online])}>
|
{items.length > 1 ? (
|
||||||
{items.length > 1 && <Tabs defaultActiveKey="0" items={items} />}
|
<div className={classNames([styles.lowerSectionMobileTabbed, online && styles.online])}>
|
||||||
</div>
|
<Tabs defaultActiveKey="0" items={items} />
|
||||||
<div className={styles.mobileNoTabs}>{items.length <= 1 && aboutTabContent}</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.lowerSectionMobileNoTabs}>{aboutTabContent}</div>
|
||||||
|
)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,33 +1,28 @@
|
|||||||
@import '../../../styles/mixins.scss';
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height: var(--footer-height);
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
background-color: var(--theme-color-background-header);
|
background-color: var(--theme-color-background-header);
|
||||||
color: var(--theme-color-components-text-on-dark);
|
color: var(--theme-color-components-text-on-dark);
|
||||||
font-family: var(--theme-text-body-font-family);
|
font-family: var(--theme-text-body-font-family);
|
||||||
|
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem var(--footer-padding-x);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-top: 1px solid rgba(214, 211, 211, 0.5);
|
border-top: 1px solid rgba(214, 211, 211, 0.5);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include screen(mobile) {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { RecoilRoot } from 'recoil';
|
||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -8,7 +9,11 @@ export default {
|
|||||||
parameters: {},
|
parameters: {},
|
||||||
} as ComponentMeta<typeof Footer>;
|
} as ComponentMeta<typeof Footer>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Footer> = args => <Footer {...args} />;
|
const Template: ComponentStory<typeof Footer> = args => (
|
||||||
|
<RecoilRoot>
|
||||||
|
<Footer {...args} />
|
||||||
|
</RecoilRoot>
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export const Example = Template.bind({});
|
export const Example = Template.bind({});
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import styles from './Footer.module.scss';
|
import styles from './Footer.module.scss';
|
||||||
|
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||||
|
import { serverStatusState } from '../../stores/ClientConfigStore';
|
||||||
|
|
||||||
export type FooterProps = {
|
export type FooterProps = {
|
||||||
version: string;
|
dynamicPaddingValue?: string;
|
||||||
dynamicPadding: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Footer: FC<FooterProps> = ({ version, dynamicPadding }) => (
|
export const Footer: FC<FooterProps> = ({ dynamicPaddingValue }) => {
|
||||||
<footer className={styles.footer} id="footer" style={{ paddingRight: dynamicPadding }}>
|
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
<span>
|
const { versionNumber } = clientStatus;
|
||||||
Powered by <a href="https://owncast.online">Owncast v{version}</a>
|
const dynamicPaddingStyle = dynamicPaddingValue
|
||||||
</span>
|
? { paddingRight: `calc(${dynamicPaddingValue} + var(--footer-padding-x)` }
|
||||||
<span className={styles.links}>
|
: null;
|
||||||
<a href="https://owncast.online/docs" target="_blank" rel="noreferrer">
|
return (
|
||||||
Documentation
|
<footer className={styles.footer} id="footer" style={dynamicPaddingStyle}>
|
||||||
</a>
|
<span>
|
||||||
<a href="https://owncast.online/help" target="_blank" rel="noreferrer">
|
Powered by <a href="https://owncast.online">Owncast v{versionNumber}</a>
|
||||||
Contribute
|
</span>
|
||||||
</a>
|
<span className={styles.links}>
|
||||||
<a href="https://github.com/owncast/owncast" target="_blank" rel="noreferrer">
|
<a href="https://owncast.online/docs" target="_blank" rel="noreferrer">
|
||||||
Source
|
Documentation
|
||||||
</a>
|
</a>
|
||||||
</span>
|
<a href="https://owncast.online/help" target="_blank" rel="noreferrer">
|
||||||
</footer>
|
Contribute
|
||||||
);
|
</a>
|
||||||
|
<a href="https://github.com/owncast/owncast" target="_blank" rel="noreferrer">
|
||||||
|
Source
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 2rem;
|
height: var(--status-bar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--content-padding);
|
padding: var(--content-padding);
|
||||||
color: var(--theme-color-components-video-status-bar-foreground);
|
color: var(--theme-color-components-video-status-bar-foreground);
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
height: 75vh;
|
height: var(--player-container-height);
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 75vh;
|
max-height: var(--player-container-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screen(desktop) {
|
@include screen(desktop) {
|
||||||
@@ -19,14 +19,8 @@
|
|||||||
|
|
||||||
//set height of player for tablet
|
//set height of player for tablet
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
height: 400px;
|
height: var(--player-container-height);
|
||||||
max-height: 400px;
|
max-height: var(--player-container-height);
|
||||||
}
|
|
||||||
|
|
||||||
//set height of player for mobile
|
|
||||||
@include screen(mobile) {
|
|
||||||
height: 250px;
|
|
||||||
max-height: 250px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.player,
|
.player,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
--content-padding: 0.95rem;
|
--content-padding: 0.95rem;
|
||||||
--module-spacing: 12px; // margin size between lines of stuff, if needed
|
--module-spacing: 12px; // margin size between lines of stuff, if needed
|
||||||
--header-height: 70px; // needed for making main content scrollable;
|
--header-height: 70px; // needed for making main content scrollable;
|
||||||
--footer-height: 2.5rem; // needed for making main content scrollable;
|
|
||||||
--content-height: calc(100vh - var(--header-height));
|
--content-height: calc(100vh - var(--header-height));
|
||||||
--replacement-bar-height: 46px; // needed for making main content scrollable on mobile;
|
--replacement-bar-height: 46px; // needed for making main content scrollable on mobile;
|
||||||
|
|
||||||
@@ -19,10 +18,19 @@
|
|||||||
--chat-notification-icon-padding: 6px;
|
--chat-notification-icon-padding: 6px;
|
||||||
--chat-message-padding: 10px;
|
--chat-message-padding: 10px;
|
||||||
--chat-text-highlight-border-radius: 3px;
|
--chat-text-highlight-border-radius: 3px;
|
||||||
|
--chat-col-width: 320px;
|
||||||
|
|
||||||
|
--player-container-height: 75vh;
|
||||||
|
--status-bar-height: 2rem;
|
||||||
|
--footer-padding-x: 1rem;
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
--header-height: 3.85rem;
|
--header-height: 3.85rem;
|
||||||
|
--player-container-height: 400px;
|
||||||
}
|
}
|
||||||
|
@include screen(mobile) {
|
||||||
|
--player-container-height: 250px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
|
|||||||
@@ -74,3 +74,5 @@ export const HAS_DISPLAYED_NOTIFICATION_MODAL_KEY = 'HAS_DISPLAYED_NOTIFICATION_
|
|||||||
export const USER_VISIT_COUNT_KEY = 'USER_VISIT_COUNT';
|
export const USER_VISIT_COUNT_KEY = 'USER_VISIT_COUNT';
|
||||||
export const USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY =
|
export const USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY =
|
||||||
'USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY';
|
'USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY';
|
||||||
|
|
||||||
|
export const DYNAMIC_PADDING_VALUE = '320px';
|
||||||
|
|||||||
Reference in New Issue
Block a user