Browse Source

Reworked mobile UI for some components

pull/2032/head
t1enne 3 years ago
parent
commit
efbe6907ac
  1. 6
      web/components/chat/ChatContainer/ChatContainer.tsx
  2. 36
      web/components/chat/ChatTextField.tsx
  3. 72
      web/components/chat/ChatTextField/ChatTextField.module.scss
  4. 24
      web/components/chat/ChatTextField/ChatTextField.tsx
  5. 43
      web/components/common/StreamInfo/StreamInfo.module.scss
  6. 54
      web/components/common/StreamInfo/StreamInfo.tsx
  7. 47
      web/components/ui/Content/Content.module.scss
  8. 27
      web/components/ui/Content/Content.tsx
  9. 13
      web/components/ui/Header/Header.module.scss
  10. 6
      web/components/ui/Logo/Logo.module.scss
  11. 3
      web/components/ui/OfflineBanner/OfflineBanner.module.scss
  12. 1
      web/components/ui/Statusbar/index.ts
  13. 1
      web/components/ui/index.tsx
  14. 6
      web/components/video/Player.module.scss
  15. 8
      web/styles/globals.scss

6
web/components/chat/ChatContainer/ChatContainer.tsx

@ -65,9 +65,9 @@ export default function ChatContainer(props: Props) { @@ -65,9 +65,9 @@ export default function ChatContainer(props: Props) {
const MessagesTable = useMemo(
() => (
<>
<div style={{ height: '100%' }}>
<Virtuoso
style={{ height: isMobile ? 500 : '77vh', width: 'auto' }}
style={{ height: '100%', width: 'auto' }}
ref={chatContainerRef}
initialTopMostItemIndex={messages.length - 1} // Force alignment to bottom
data={messages}
@ -92,7 +92,7 @@ export default function ChatContainer(props: Props) { @@ -92,7 +92,7 @@ export default function ChatContainer(props: Props) {
</Button>
</div>
)}
</>
</div>
),
[messages, usernameToHighlight, chatUserId, isModerator, atBottom, isMobile],
);

36
web/components/chat/ChatTextField.tsx

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
import { useState } from 'react';
interface Props {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function ChatTextField(props: Props) {
const [value, setValue] = useState('');
const [showEmojis, setShowEmojis] = useState(false);
return (
<div>
<input
type="text"
value={value}
onChange={e => setValue(e.target.value)}
placeholder="Type a message here then hit ENTER"
/>
<button type="button" onClick={() => setShowEmojis(!showEmojis)}>
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</button>
</div>
);
}

72
web/components/chat/ChatTextField/ChatTextField.module.scss

@ -7,48 +7,62 @@ @@ -7,48 +7,62 @@
overflow-x: hidden;
div[role=textbox] {
font-size: 0.9rem;
border-radius: .2rem;
padding: .6rem;
padding-right: calc(0.6rem + 44px);
border-radius: .35rem;
background-color: var(--color-owncast-gray-700);
box-shadow: 0;
transition: box-shadow 50ms ease-in-out;
&:focus {
box-shadow: inset 0px 0px 0x 1px var(--color-owncast-purple-700);
outline: 1px solid var(--color-owncast-gray-500) !important;
}
& > p {
margin: 0px;
}
}
}
.inputWrapper {
display: flex;
position: relative;
border-radius: var(--theme-rounded-corners);
outline: 1px solid var(--color-owncast-gray-500);
&:hover {
box-shadow: 0 0 1px 1px var(--color-owncast-gray-300);
.inputWrapper {
display: flex;
position: relative;
margin-right: .3rem;
border-radius: .2rem;
& > div {
transition: box-shadow .2s ease-in-out;
}
}
& > div {
transition: box-shadow .2s ease-in-out;
&:focus {
// box-shadow: 0 0 1px 1px var(--color-owncast-gray-300);
.emojiButton {
border: none;
background: none;
cursor: pointer;
padding: 0 1rem;
position: absolute;
right: 0px;
top: 50%;
transform: translateY(-50%);
svg {
fill: var(--color-owncast-gray-300);
}
}
}
.emojiButton {
border: none;
background: none;
cursor: pointer;
padding: 0 1rem;
position: absolute;
right: 0px;
top: 50%;
transform: translateY(-50%);
svg {
fill: var(--color-owncast-gray-300);
.submitButtonWrapper {
display: flex;
padding: 6px 0;
justify-content: flex-end;
}
}
.submitButtonWrapper {
display: flex;
padding: 6px 0;
justify-content: flex-end;
.mobile {
&.root {
display: flex;
.inputWrapper {
flex: 1;
}
.submitButtonWrapper {
padding: 0px;
}
}
}

24
web/components/chat/ChatTextField/ChatTextField.tsx

@ -4,9 +4,10 @@ import React, { useState } from 'react'; @@ -4,9 +4,10 @@ import React, { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Transforms, createEditor, BaseEditor, Text } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import cn from 'classnames';
import EmojiPicker from './EmojiPicker';
import WebsocketService from '../../../services/websocket-service';
import { websocketServiceAtom } from '../../stores/ClientConfigStore';
import { isMobileAtom, websocketServiceAtom } from '../../stores/ClientConfigStore';
import { MessageType } from '../../../interfaces/socket-events';
import s from './ChatTextField.module.scss';
@ -101,6 +102,7 @@ export default function ChatTextField(props: Props) { @@ -101,6 +102,7 @@ export default function ChatTextField(props: Props) {
// const { value: originalValue } = props;
const [showEmojis, setShowEmojis] = useState(false);
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const isMobile = useRecoilValue<boolean>(isMobileAtom);
const [editor] = useState(() => withImages(withReact(createEditor())));
const sendMessage = () => {
@ -120,7 +122,7 @@ export default function ChatTextField(props: Props) { @@ -120,7 +122,7 @@ export default function ChatTextField(props: Props) {
const handleChange = () => {};
const handleEmojiSelect = e => {
const handleEmojiSelect = (e: any) => {
ReactEditor.focus(editor);
if (e.url) {
@ -134,7 +136,7 @@ export default function ChatTextField(props: Props) { @@ -134,7 +136,7 @@ export default function ChatTextField(props: Props) {
}
};
const onKeyDown = e => {
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
sendMessage();
@ -142,7 +144,11 @@ export default function ChatTextField(props: Props) { @@ -142,7 +144,11 @@ export default function ChatTextField(props: Props) {
};
return (
<div className={s.root}>
<div
className={cn(s.root, {
[s.mobile]: isMobile,
})}
>
<div className={s.inputWrapper}>
<Slate
editor={editor}
@ -167,9 +173,13 @@ export default function ChatTextField(props: Props) { @@ -167,9 +173,13 @@ export default function ChatTextField(props: Props) {
</button>
</div>
<div className={s.submitButtonWrapper}>
<Button size="middle" type="primary" icon={<SendOutlined />} onClick={sendMessage}>
Send
</Button>
{isMobile ? (
<Button size="large" type="ghost" icon={<SendOutlined />} onClick={sendMessage} />
) : (
<Button type="primary" icon={<SendOutlined />} onClick={sendMessage}>
Send
</Button>
)}
</div>
<Popover
content={<EmojiPicker onEmojiSelect={handleEmojiSelect} />}

43
web/components/common/StreamInfo/StreamInfo.module.scss

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
.streamInfo {
.root {
position: relative;
display: grid;
}
@ -34,3 +34,44 @@ @@ -34,3 +34,44 @@
font-weight: bold;
}
}
.mobile {
&.root {
position: relative;
display: flex;
padding: 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: .5rem;
font-size: .8rem;
gap: 4px;
.liveCircle {
border-radius: 50%;
background-color: red;
width: .5rem;
height: .5rem;
}
}
}
}
}

54
web/components/common/StreamInfo/StreamInfo.tsx

@ -1,16 +1,56 @@ @@ -1,16 +1,56 @@
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 } from '../../stores/ClientConfigStore';
import {
clientConfigStateAtom,
isOnlineSelector,
serverStatusState,
} from '../../stores/ClientConfigStore';
import { ServerLogo } from '../../ui';
import StatusBar from '../../ui/Statusbar';
import CategoryIcon from '../../ui/CategoryIcon/CategoryIcon';
import SocialLinks from '../../ui/SocialLinks/SocialLinks';
import s from './StreamInfo.module.scss';
import { ServerStatus } from '../../../interfaces/server-status.model';
export default function StreamInfo() {
interface Props {
isMobile: boolean;
}
export default function StreamInfo({ isMobile }: Props) {
const { socialHandles, name, title, tags } = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const { viewerCount, lastConnectTime, lastDisconnectTime } =
useRecoilValue<ServerStatus>(serverStatusState);
const online = useRecoilValue<boolean>(isOnlineSelector);
useEffect(() => {
console.log({ online });
}, [online]);
return (
<div className={s.streamInfo}>
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}>
@ -23,6 +63,12 @@ export default function StreamInfo() { @@ -23,6 +63,12 @@ export default function StreamInfo() {
<SocialLinks links={socialHandles} />
</div>
</div>
<StatusBar
online={online}
lastConnectTime={lastConnectTime}
lastDisconnectTime={lastDisconnectTime}
viewerCount={viewerCount}
/>
</div>
);
}

47
web/components/ui/Content/Content.module.scss

@ -1,32 +1,8 @@ @@ -1,32 +1,8 @@
.root {
display: grid;
grid-template-columns: 1fr auto;
&.mobile {
display: flex;
flex-direction: column;
height: calc(100vh - 64px);
overflow-y: hidden;
.topHalf {
border: 1px dashed white;
height: calc(40vh - 64px);
overflow: hidden;
}
.lowerHalf {
border: 1px dashed red;
height: 60vh;
}
}
}
.mobileChat {
position: relative;
display: block;
// top: 0px;
width: 100%;
[data-virtuoso-scroller] {
height: 500px;
}
}
.leftCol {
display: flex;
@ -40,3 +16,26 @@ @@ -40,3 +16,26 @@
z-index: 999999;
}
.mobile {
&.root {
display: flex;
flex-direction: column;
height: calc(100vh - 64px);
overflow: hidden;
.topHalf {
display: grid;
grid-template-rows: 30vh 5vh 5vh;
height: 40vh;
// overflow: hidden;
}
.lowerHalf {
height: 60vh;
}
}
.mobileChat {
position: relative;
display: block;
height: 100%;
width: 100%;
}
}

27
web/components/ui/Content/Content.tsx

@ -11,7 +11,6 @@ import { @@ -11,7 +11,6 @@ import {
chatDisplayNameAtom,
chatUserIdAtom,
isChatVisibleSelector,
serverStatusState,
appStateAtom,
isOnlineSelector,
isMobileAtom,
@ -28,8 +27,6 @@ import { ChatMessage } from '../../../interfaces/chat-message.model'; @@ -28,8 +27,6 @@ import { ChatMessage } from '../../../interfaces/chat-message.model';
import ChatTextField from '../../chat/ChatTextField/ChatTextField';
import ActionButtonRow from '../../action-buttons/ActionButtonRow';
import ActionButton from '../../action-buttons/ActionButton';
import Statusbar from '../Statusbar/Statusbar';
import { ServerStatus } from '../../../interfaces/server-status.model';
import { Follower } from '../../../interfaces/follower';
import NotifyReminderPopup from '../NotifyReminderPopup/NotifyReminderPopup';
import OfflineBanner from '../OfflineBanner/OfflineBanner';
@ -38,13 +35,13 @@ import FollowButton from '../../action-buttons/FollowButton'; @@ -38,13 +35,13 @@ import FollowButton from '../../action-buttons/FollowButton';
import NotifyButton from '../../action-buttons/NotifyButton';
import Modal from '../Modal/Modal';
import BrowserNotifyModal from '../../modals/BrowserNotify/BrowserNotifyModal';
import StreamInfo from '../../common/StreamInfo';
const { TabPane } = Tabs;
const { Content } = Layout;
export default function ContentComponent() {
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
const status = useRecoilValue<ServerStatus>(serverStatusState);
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const isChatVisible = useRecoilValue<boolean>(isChatVisibleSelector);
const [isMobile, setIsMobile] = useRecoilState<boolean | undefined>(isMobileAtom);
@ -54,7 +51,6 @@ export default function ContentComponent() { @@ -54,7 +51,6 @@ export default function ContentComponent() {
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
const { extraPageContent, version, name, summary } = clientConfig;
const { viewerCount, lastConnectTime, lastDisconnectTime } = status;
const [showNotifyReminder, setShowNotifyReminder] = useState(false);
const [showNotifyPopup, setShowNotifyPopup] = useState(false);
@ -129,13 +125,6 @@ export default function ContentComponent() { @@ -129,13 +125,6 @@ export default function ContentComponent() {
text="Stream is offline text goes here. Will create a new form to set it in the Admin."
/>
)}
<Statusbar
online={online}
lastConnectTime={lastConnectTime}
lastDisconnectTime={lastDisconnectTime}
viewerCount={viewerCount}
/>
<div className={s.buttonsLogoTitleSection}>
<ActionButtonRow>
{externalActionButtons}
@ -158,12 +147,18 @@ export default function ContentComponent() { @@ -158,12 +147,18 @@ export default function ContentComponent() {
<BrowserNotifyModal />
</Modal>
</div>
<StreamInfo isMobile={isMobile} />
</div>
<div className={s.lowerHalf}>
<Tabs defaultActiveKey="0">
{isChatVisible && isMobile && (
<TabPane tab="Chat" key="0" className={s.pageContentSection}>
<div style={{ position: 'relative' }}>
<TabPane
tab="Chat"
key="0"
className={s.pageContentSection}
style={{ height: '100%' }}
>
<div style={{ position: 'relative', height: '100%' }}>
<div className={s.mobileChat}>
<ChatContainer
messages={messages}
@ -173,8 +168,8 @@ export default function ContentComponent() { @@ -173,8 +168,8 @@ export default function ContentComponent() {
isModerator={false}
isMobile={isMobile}
/>
<ChatTextField />
</div>
<ChatTextField />
</div>
</TabPane>
)}
@ -186,7 +181,7 @@ export default function ContentComponent() { @@ -186,7 +181,7 @@ export default function ContentComponent() {
<FollowerCollection total={total} followers={followers} />
</TabPane>
</Tabs>
<Footer version={version} />
{!isMobile && <Footer version={version} />}
</div>
</div>
{isChatVisible && !isMobile && <Sidebar />}

13
web/components/ui/Header/Header.module.scss

@ -5,15 +5,22 @@ @@ -5,15 +5,22 @@
align-items: center;
justify-content: space-between;
z-index: 20;
padding: 0.5rem 1rem;
padding: 0.4rem .7rem;
background-color: var(--default-bg-color);
.logo {
display: flex;
align-items: center;
span {
margin-left: 1rem;
font-size: 1.7rem;
margin-left: .5rem;
font-size: 1.5rem;
font-weight: 600;
}
}
}
@media (max-width: 768px) {
.header {
line-height: 5vh;
height: 5vh;
}
}

6
web/components/ui/Logo/Logo.module.scss

@ -4,8 +4,9 @@ @@ -4,8 +4,9 @@
align-items: center;
justify-content: center;
overflow: hidden;
width: clamp(4rem, 10vw, 120px);
height: clamp(4rem, 10vw, 120px);
margin-right: .5rem;
width: clamp(2.5vh, 9vw, 120px);
height: clamp(2.5vh, 9vw, 120px);
border-radius: 50%;
border-width: 3px;
border-style: solid;
@ -27,3 +28,4 @@ @@ -27,3 +28,4 @@
background-position: center;
overflow: hidden;
}

3
web/components/ui/OfflineBanner/OfflineBanner.module.scss

@ -7,9 +7,8 @@ @@ -7,9 +7,8 @@
width: clamp(200px, 100%, 300px);
display: flex;
flex-direction: column;
;
background-color: var(--theme-background-secondary);
margin: auto;
margin: 1rem auto;
border-radius: var(--theme-rounded-corners);
padding: 1rem;
}

1
web/components/ui/Statusbar/index.ts

@ -0,0 +1 @@ @@ -0,0 +1 @@
export { default } from './Statusbar';

1
web/components/ui/index.tsx

@ -4,3 +4,4 @@ export { default as Footer } from './Footer/index'; @@ -4,3 +4,4 @@ export { default as Footer } from './Footer/index';
export { default as Content } from './Content/index';
export { default as ModIcon } from './ModIcon';
export { default as ServerLogo } from './Logo';
export { default as StatusBar } from './Statusbar';

6
web/components/video/Player.module.scss

@ -10,3 +10,9 @@ @@ -10,3 +10,9 @@
width: 100%;
height: 100%;
}
@media (max-width: 768px) {
.player {
height: 30vh !important;
}
}

8
web/styles/globals.scss

@ -9,11 +9,17 @@ body { @@ -9,11 +9,17 @@ body {
padding: 0;
margin: 0;
font-family: var(--theme-font-family), var(--theme-header-font-family), sans-serif;
font-size: clamp(14px, 1.5vw, 17px);
font-size: clamp(14px, 1vw, 17px);
background-color: var(--default-bg-color);
color: var(--default-text-color);
}
@media (max-width: 768px) {
body {
overflow: hidden;
}
}
// a {
// color: inherit;
// text-decoration: none;

Loading…
Cancel
Save