Update to the page content header
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonsLogoTitleSection {
|
||||||
|
// margin-left: 1.5vw;
|
||||||
|
// margin-right: 1.5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoTitleSection {
|
||||||
|
display: flex;
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
flex-direction: column;
|
||||||
|
.logo {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: black;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
color: var(--theme-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagList {
|
||||||
|
font-family: var(--theme-text-display-font-family);
|
||||||
|
color: var(--theme-text-primary);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
web/components/common/ContentHeader/ContentHeader.tsx
Normal file
36
web/components/common/ContentHeader/ContentHeader.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import cn from 'classnames';
|
||||||
|
|
||||||
|
import { ServerLogo } from '../../ui';
|
||||||
|
import SocialLinks from '../../ui/SocialLinks/SocialLinks';
|
||||||
|
import { SocialLink } from '../../../interfaces/social-link.model';
|
||||||
|
import s from './ContentHeader.module.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
tags: string[];
|
||||||
|
links: SocialLink[];
|
||||||
|
logo: string;
|
||||||
|
}
|
||||||
|
export default function ContentHeader({ name, title, summary, logo, tags, links }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={s.root}>
|
||||||
|
<div className={s.logoTitleSection}>
|
||||||
|
<div className={s.logo}>
|
||||||
|
<ServerLogo src={logo} />
|
||||||
|
</div>
|
||||||
|
<div className={s.titleSection}>
|
||||||
|
<div className={cn(s.title, s.row)}>{name}</div>
|
||||||
|
<div className={cn(s.subtitle, s.row)}>{title || summary}</div>
|
||||||
|
<div className={cn(s.tagList, s.row)}>
|
||||||
|
{tags.length > 0 && tags.map(tag => <span key={tag}>#{tag} </span>)}
|
||||||
|
</div>
|
||||||
|
<div className={cn(s.socialLinks, s.row)}>
|
||||||
|
<SocialLinks links={links} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
web/components/common/ContentHeader/index.ts
Normal file
1
web/components/common/ContentHeader/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ContentHeader';
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
.root {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonsLogoTitleSection {
|
|
||||||
margin-left: 1.5vw;
|
|
||||||
margin-right: 1.5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logoTitleSection {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleSection {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--theme-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
margin-top: 0.35em;
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--theme-text-secondary);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
&.root {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
padding: 0 0.3rem;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
.mobileInfo {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.mobileStatus {
|
|
||||||
display: flex;
|
|
||||||
font-weight: 600;
|
|
||||||
.viewerCount {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.liveStatus {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
gap: 4px;
|
|
||||||
.liveCircle {
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: red;
|
|
||||||
width: 0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagList {
|
|
||||||
font-family: var(--theme-text-display-font-family);
|
|
||||||
color: var(--theme-text-secondary);
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 0.7em 0 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import { EyeFilled } from '@ant-design/icons';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
|
||||||
import {
|
|
||||||
clientConfigStateAtom,
|
|
||||||
isOnlineSelector,
|
|
||||||
serverStatusState,
|
|
||||||
} from '../../stores/ClientConfigStore';
|
|
||||||
import { ServerLogo } from '../../ui';
|
|
||||||
import SocialLinks from '../../ui/SocialLinks/SocialLinks';
|
|
||||||
import s from './StreamInfo.module.scss';
|
|
||||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
isMobile: boolean;
|
|
||||||
}
|
|
||||||
export default function StreamInfo({ isMobile }: Props) {
|
|
||||||
const { socialHandles, name, title, tags, summary } =
|
|
||||||
useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
|
||||||
const { viewerCount } = useRecoilValue<ServerStatus>(serverStatusState);
|
|
||||||
const online = useRecoilValue<boolean>(isOnlineSelector);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log({ online });
|
|
||||||
}, [online]);
|
|
||||||
|
|
||||||
return isMobile ? (
|
|
||||||
<div className={cn(s.root, s.mobile)}>
|
|
||||||
<div className={s.mobileInfo}>
|
|
||||||
<ServerLogo src="/logo" />
|
|
||||||
<div className={s.title}>{name}</div>
|
|
||||||
</div>
|
|
||||||
<div className={s.mobileStatus}>
|
|
||||||
<div className={s.viewerCount}>
|
|
||||||
{online && (
|
|
||||||
<>
|
|
||||||
<span>{viewerCount}</span>
|
|
||||||
<EyeFilled />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={s.liveStatus}>
|
|
||||||
{online && <div className={s.liveCircle} />}
|
|
||||||
<span>{online ? 'LIVE' : 'OFFLINE'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={s.root}>
|
|
||||||
<div className={s.logoTitleSection}>
|
|
||||||
<ServerLogo src="/logo" />
|
|
||||||
<div className={s.titleSection}>
|
|
||||||
<div className={s.title}>{name}</div>
|
|
||||||
<div className={s.subtitle}>{title || summary}</div>
|
|
||||||
<div className={s.tagList}>
|
|
||||||
{tags.length > 0 && tags.map(tag => <span key={tag}>#{tag} </span>)}
|
|
||||||
</div>
|
|
||||||
<SocialLinks links={socialHandles} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './StreamInfo';
|
|
||||||
@@ -35,7 +35,7 @@ import FollowButton from '../../action-buttons/FollowButton';
|
|||||||
import NotifyButton from '../../action-buttons/NotifyButton';
|
import NotifyButton from '../../action-buttons/NotifyButton';
|
||||||
import Modal from '../Modal/Modal';
|
import Modal from '../Modal/Modal';
|
||||||
import BrowserNotifyModal from '../../modals/BrowserNotify/BrowserNotifyModal';
|
import BrowserNotifyModal from '../../modals/BrowserNotify/BrowserNotifyModal';
|
||||||
import StreamInfo from '../../common/StreamInfo';
|
import ContentHeader from '../../common/ContentHeader';
|
||||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||||
import { StatusBar } from '..';
|
import { StatusBar } from '..';
|
||||||
|
|
||||||
@@ -51,9 +51,18 @@ export default function ContentComponent() {
|
|||||||
const online = useRecoilValue<boolean>(isOnlineSelector);
|
const online = useRecoilValue<boolean>(isOnlineSelector);
|
||||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||||
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
||||||
const { viewerCount, lastConnectTime, lastDisconnectTime } =
|
const { viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } =
|
||||||
useRecoilValue<ServerStatus>(serverStatusState);
|
useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
const { extraPageContent, version, name, externalActions, offlineMessage } = clientConfig;
|
const {
|
||||||
|
extraPageContent,
|
||||||
|
version,
|
||||||
|
name,
|
||||||
|
summary,
|
||||||
|
socialHandles,
|
||||||
|
tags,
|
||||||
|
externalActions,
|
||||||
|
offlineMessage,
|
||||||
|
} = clientConfig;
|
||||||
const [showNotifyReminder, setShowNotifyReminder] = useState(false);
|
const [showNotifyReminder, setShowNotifyReminder] = useState(false);
|
||||||
const [showNotifyPopup, setShowNotifyPopup] = useState(false);
|
const [showNotifyPopup, setShowNotifyPopup] = useState(false);
|
||||||
|
|
||||||
@@ -143,7 +152,14 @@ export default function ContentComponent() {
|
|||||||
<BrowserNotifyModal />
|
<BrowserNotifyModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
<StreamInfo isMobile={isMobile} />
|
<ContentHeader
|
||||||
|
name={name}
|
||||||
|
title={streamTitle}
|
||||||
|
summary={summary}
|
||||||
|
tags={tags}
|
||||||
|
links={socialHandles}
|
||||||
|
logo="/logo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.lowerHalf}>
|
<div className={s.lowerHalf}>
|
||||||
<Tabs defaultActiveKey="0" style={{ height: '100%' }}>
|
<Tabs defaultActiveKey="0" style={{ height: '100%' }}>
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-right: .5rem;
|
margin-right: 0.5rem;
|
||||||
width: clamp(2.5vh, 9vw, 120px);
|
width: 96px;
|
||||||
height: clamp(2.5vh, 9vw, 120px);
|
height: 96px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border-width: 3px;
|
border-width: 5px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--theme-primary-color);
|
border-color: var(--theme-primary-color);
|
||||||
background-color: var(--theme-background-secondary);
|
background-color: var(--theme-background-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 90%;
|
height: 90%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -28,4 +28,3 @@
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.link {
|
.link {
|
||||||
width: 2em;
|
width: 1.8em;
|
||||||
margin-right: 4px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.links {
|
.links {
|
||||||
|
|||||||
76
web/stories/ContentHeader.stories.tsx
Normal file
76
web/stories/ContentHeader.stories.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import ContentHeader from '../components/common/ContentHeader/ContentHeader';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'owncast/Components/Content Header',
|
||||||
|
component: ContentHeader,
|
||||||
|
parameters: {},
|
||||||
|
} as ComponentMeta<typeof ContentHeader>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof ContentHeader> = args => <ContentHeader {...args} />;
|
||||||
|
|
||||||
|
export const Example = Template.bind({});
|
||||||
|
Example.args = {
|
||||||
|
name: 'My Awesome Owncast Stream',
|
||||||
|
summary: 'A calvacade of glorious sights and sounds',
|
||||||
|
tags: ['word', 'tag with spaces', 'music'],
|
||||||
|
logo: 'https://watch.owncast.online/logo',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
platform: 'github',
|
||||||
|
url: 'https://github.com/owncast/owncast',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/github.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
platform: 'Documentation',
|
||||||
|
url: 'https://owncast.online',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/link.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
platform: 'mastodon',
|
||||||
|
url: 'https://fosstodon.org/users/owncast',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/mastodon.svg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LongContent = Template.bind({});
|
||||||
|
LongContent.args = {
|
||||||
|
name: 'My Awesome Owncast Stream, streaming the best of streams and some lorem ipsum too',
|
||||||
|
summary:
|
||||||
|
'A calvacade of glorious sights and sounds. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||||
|
tags: [
|
||||||
|
'word',
|
||||||
|
'tag with spaces',
|
||||||
|
'music',
|
||||||
|
'more tags',
|
||||||
|
'a bunch',
|
||||||
|
'keep going',
|
||||||
|
'and more',
|
||||||
|
'just a few more',
|
||||||
|
'video games',
|
||||||
|
'things',
|
||||||
|
'stuff',
|
||||||
|
'ok some more',
|
||||||
|
'this should do it',
|
||||||
|
],
|
||||||
|
logo: 'https://watch.owncast.online/logo',
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
platform: 'github',
|
||||||
|
url: 'https://github.com/owncast/owncast',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/github.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
platform: 'Documentation',
|
||||||
|
url: 'https://owncast.online',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/link.svg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
platform: 'mastodon',
|
||||||
|
url: 'https://fosstodon.org/users/owncast',
|
||||||
|
icon: 'https://watch.owncast.online/img/platformlogos/mastodon.svg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user