Add skip links for content, player and footer. For #1826
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
isChatAvailableSelector,
|
isChatAvailableSelector,
|
||||||
clientConfigStateAtom,
|
clientConfigStateAtom,
|
||||||
fatalErrorStateAtom,
|
fatalErrorStateAtom,
|
||||||
|
appStateAtom,
|
||||||
} from '../../stores/ClientConfigStore';
|
} from '../../stores/ClientConfigStore';
|
||||||
import { Content } from '../../ui/Content/Content';
|
import { Content } from '../../ui/Content/Content';
|
||||||
import { Header } from '../../ui/Header/Header';
|
import { Header } from '../../ui/Header/Header';
|
||||||
@@ -22,6 +23,7 @@ import { ServerRenderedHydration } from '../../ServerRendered/ServerRenderedHydr
|
|||||||
import { Theme } from '../../theme/Theme';
|
import { Theme } from '../../theme/Theme';
|
||||||
import styles from './Main.module.scss';
|
import styles from './Main.module.scss';
|
||||||
import { PushNotificationServiceWorker } from '../../workers/PushNotificationServiceWorker/PushNotificationServiceWorker';
|
import { PushNotificationServiceWorker } from '../../workers/PushNotificationServiceWorker/PushNotificationServiceWorker';
|
||||||
|
import { AppStateOptions } from '../../stores/application-state';
|
||||||
|
|
||||||
const lockBodyStyle = `
|
const lockBodyStyle = `
|
||||||
body {
|
body {
|
||||||
@@ -46,9 +48,11 @@ export const Main: FC = () => {
|
|||||||
const { name, title, customStyles } = clientConfig;
|
const { name, title, customStyles } = clientConfig;
|
||||||
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
||||||
const fatalError = useRecoilValue<DisplayableError>(fatalErrorStateAtom);
|
const fatalError = useRecoilValue<DisplayableError>(fatalErrorStateAtom);
|
||||||
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||||
|
|
||||||
const layoutRef = useRef<HTMLDivElement>(null);
|
const layoutRef = useRef<HTMLDivElement>(null);
|
||||||
const { chatDisabled } = clientConfig;
|
const { chatDisabled } = clientConfig;
|
||||||
|
const { videoAvailable } = appState;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setupNoLinkReferrer(layoutRef.current);
|
setupNoLinkReferrer(layoutRef.current);
|
||||||
@@ -137,7 +141,12 @@ export const Main: FC = () => {
|
|||||||
<Script strategy="afterInteractive" src="/customjavascript" />
|
<Script strategy="afterInteractive" src="/customjavascript" />
|
||||||
|
|
||||||
<Layout ref={layoutRef} className={styles.layout}>
|
<Layout ref={layoutRef} className={styles.layout}>
|
||||||
<Header name={title || name} chatAvailable={isChatAvailable} chatDisabled={chatDisabled} />
|
<Header
|
||||||
|
name={title || name}
|
||||||
|
chatAvailable={isChatAvailable}
|
||||||
|
chatDisabled={chatDisabled}
|
||||||
|
online={videoAvailable}
|
||||||
|
/>
|
||||||
<Content />
|
<Content />
|
||||||
{fatalError && (
|
{fatalError && (
|
||||||
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ const DesktopContent = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.lowerHalf}>
|
<div className={styles.lowerHalf} id="skip-to-content">
|
||||||
<ContentHeader
|
<ContentHeader
|
||||||
name={name}
|
name={name}
|
||||||
title={streamTitle}
|
title={streamTitle}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export type FooterProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Footer: FC<FooterProps> = ({ version }) => (
|
export const Footer: FC<FooterProps> = ({ version }) => (
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer} id="footer">
|
||||||
<span>
|
<span>
|
||||||
Powered by <a href="https://owncast.online">{version}</a>
|
Powered by <a href="https://owncast.online">{version}</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -42,3 +42,18 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skipLink {
|
||||||
|
position: absolute;
|
||||||
|
left: -10000px;
|
||||||
|
top: auto;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skipLink:focus {
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Tag, Tooltip } from 'antd';
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import Link from 'next/link';
|
||||||
import { OwncastLogo } from '../../common/OwncastLogo/OwncastLogo';
|
import { OwncastLogo } from '../../common/OwncastLogo/OwncastLogo';
|
||||||
import styles from './Header.module.scss';
|
import styles from './Header.module.scss';
|
||||||
|
|
||||||
@@ -18,14 +19,27 @@ export type HeaderComponentProps = {
|
|||||||
name: string;
|
name: string;
|
||||||
chatAvailable: boolean;
|
chatAvailable: boolean;
|
||||||
chatDisabled: boolean;
|
chatDisabled: boolean;
|
||||||
|
online: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header: FC<HeaderComponentProps> = ({
|
export const Header: FC<HeaderComponentProps> = ({
|
||||||
name = 'Your stream title',
|
name = 'Your stream title',
|
||||||
chatAvailable,
|
chatAvailable,
|
||||||
chatDisabled,
|
chatDisabled,
|
||||||
|
online,
|
||||||
}) => (
|
}) => (
|
||||||
<header className={cn([`${styles.header}`], 'global-header')}>
|
<header className={cn([`${styles.header}`], 'global-header')}>
|
||||||
|
{online && (
|
||||||
|
<Link href="#player" className={styles.skipLink}>
|
||||||
|
Skip to player
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<Link href="#skip-to-content" className={styles.skipLink}>
|
||||||
|
Skip to page content
|
||||||
|
</Link>
|
||||||
|
<Link href="#footer" className={styles.skipLink}>
|
||||||
|
Skip to footer
|
||||||
|
</Link>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<div id="header-logo" className={styles.logoImage}>
|
<div id="header-logo" className={styles.logoImage}>
|
||||||
<OwncastLogo variant="contrast" />
|
<OwncastLogo variant="contrast" />
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export const OwncastPlayer: FC<OwncastPlayerProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container} id="player">
|
||||||
{online && (
|
{online && (
|
||||||
<div className={styles.player}>
|
<div className={styles.player}>
|
||||||
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
|
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
|
||||||
|
|||||||
@@ -6,16 +6,20 @@ import {
|
|||||||
currentUserAtom,
|
currentUserAtom,
|
||||||
visibleChatMessagesSelector,
|
visibleChatMessagesSelector,
|
||||||
clientConfigStateAtom,
|
clientConfigStateAtom,
|
||||||
|
appStateAtom,
|
||||||
} from '../../../../components/stores/ClientConfigStore';
|
} from '../../../../components/stores/ClientConfigStore';
|
||||||
import Header from '../../../../components/ui/Header/Header';
|
import Header from '../../../../components/ui/Header/Header';
|
||||||
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
||||||
|
import { AppStateOptions } from '../../../../components/stores/application-state';
|
||||||
|
|
||||||
export default function ReadWriteChatEmbed() {
|
export default function ReadWriteChatEmbed() {
|
||||||
const currentUser = useRecoilValue(currentUserAtom);
|
const currentUser = useRecoilValue(currentUserAtom);
|
||||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||||
|
|
||||||
const { name, chatDisabled } = clientConfig;
|
const { name, chatDisabled } = clientConfig;
|
||||||
|
const { videoAvailable } = appState;
|
||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return null;
|
return null;
|
||||||
@@ -26,7 +30,7 @@ export default function ReadWriteChatEmbed() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ClientConfigStore />
|
<ClientConfigStore />
|
||||||
<Header name={name} chatAvailable chatDisabled={chatDisabled} />
|
<Header name={name} chatAvailable chatDisabled={chatDisabled} online={videoAvailable} />
|
||||||
<ChatContainer
|
<ChatContainer
|
||||||
messages={messages}
|
messages={messages}
|
||||||
usernameToHighlight={displayName}
|
usernameToHighlight={displayName}
|
||||||
|
|||||||
Reference in New Issue
Block a user