добавлен адаптив при высоте экрана менее 700px, добавлено меню учатсников для мобильной версии, логика открытия закрытия его

This commit is contained in:
DmitriyB
2022-08-10 14:21:23 +05:00
parent 0a839601df
commit 7a771bd3e6
21 changed files with 443 additions and 62 deletions
+3
View File
@@ -1,3 +1,6 @@
.App {
font-family: inter;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-tap-highlight-color: transparent;
}
+32 -2
View File
@@ -1,11 +1,41 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import './App.css';
import { ContextWindowHeight } from './components/contextWindowHeight';
import { MainScreen } from './components/mainScreen/mainScreen';
function App() {
const [windowHeight, setWindowHeight] = useState<number>(window.screen.availHeight);
window.addEventListener('resize', e => {
//@ts-ignore
setWindowHeight(e.currentTarget.screen.availHeight)
})
window.onload = function () {
hideAddressBar();
window.addEventListener("orientationchange", function () {
hideAddressBar();
}, false);
}
function hideAddressBar() {
setTimeout(function () {
document.body.style.height = window.outerHeight + 'px';
setTimeout(function () {
window.scrollTo(0, 1);
}, 1100);
}, 1000);
return false;
}
return (
<div className="App">
<MainScreen />
<ContextWindowHeight.Provider value={windowHeight}>
<MainScreen />
</ContextWindowHeight.Provider>
</div>
);
}
+3
View File
@@ -0,0 +1,3 @@
import React from "react";
export const ContextWindowHeight = React.createContext(window.screen.availHeight);
+1
View File
@@ -2,6 +2,7 @@
width: 100%;
height: 100vh;
position: relative;
display: flex;
}
.main-screen-model {
+28 -1
View File
@@ -1,15 +1,27 @@
import React, { useState } from "react";
import React, { useContext, useState } from "react";
import { CSSTransition } from "react-transition-group";
import { ContextWindowHeight } from "../contextWindowHeight";
import './mainScreen.css';
import { MobileUsersPart } from "./mobileUsersPart/mobileUsersPart";
import { Toolbar } from "./toolbar/toolbar";
export const MainScreen:React.FC = React.memo(() => {
const [showToolbar, setShowToolbar] = useState<boolean>(false);
const [showMobileUsersPart, setShowMobileUsersPart] = useState<boolean>(false);
const windowHeight = useContext(ContextWindowHeight);
function onClickToolbar() {
setShowToolbar(!showToolbar);
}
function openMobileUsers() {
setShowMobileUsersPart(true);
}
function closeMobileUsers() {
setShowMobileUsersPart(false);
}
return <div className="main-screen-container">
<div className="main-screen-model"></div>
<CSSTransition
@@ -20,7 +32,22 @@ export const MainScreen:React.FC = React.memo(() => {
<Toolbar
onClickOpenButton={onClickToolbar}
isOpen={showToolbar}
onClickMobileUsers={openMobileUsers}
/>
</CSSTransition>
{
windowHeight < 700 ?
<CSSTransition
in={showMobileUsersPart}
timeout={300}
classNames='show-mobile-users'
unmountOnExit
>
<MobileUsersPart
onClickClose={closeMobileUsers}
/>
</CSSTransition>
: null
}
</div>
})
@@ -0,0 +1,5 @@
import React from "react";
export const BorderLine:React.FC = React.memo(() => {
return <div className="border-line"></div>
})
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12L16.9996 17M12 12L17 7M12 12L7 17M12 12L7.00002 7" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

@@ -0,0 +1,90 @@
.mobile-users-part {
position: relative;
width: 100%;
height: 100%;
display: flex;
padding: 32px;
display: flex;
flex-direction: column;
color: #FFFFFF;
box-sizing: border-box;
background-color: #333333;
}
.border-line {
width: 100%;
height: 1px;
background-color: #4F4F4F;
}
.mobile-users-part-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 24px;
}
.mobile-users-part-header-title {
font-weight: 600;
font-size: 22px;
line-height: 130%;
}
.mobile-users-part-header-close-button {
width: 30px;
height: 30px;
border: none;
background-color: transparent;
background: url('closeIcon.svg') 50% 50% no-repeat;
background-size: 100% 100%;
}
.mobile-users-part-users-container {
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
padding-top: 12px;
}
.user-item {
display: flex;
flex-direction: row;
gap: 8px;
}
.user-item-user-info-container {
display: flex;
flex-direction: column;
width: 100%;
gap: 12px;
}
.user-item-user-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-item-user-info-buttons {
display: flex;
gap: 8px;
}
.show-mobile-users-enter {
opacity: 0;
}
.show-mobile-users-enter-active {
opacity: 1;
transition: .3s;
}
.show-mobile-users-exit {
opacity: 1;
}
.show-mobile-users-exit-active {
opacity: 0;
transition: .3s;
}
@@ -0,0 +1,32 @@
import React from "react";
import { BorderLine } from "./borderLine";
import './mobileUsersPart.css'
import { UserItem } from "./userItem";
type TProps = {
onClickClose: () => void
}
export const MobileUsersPart:React.FC<TProps> = React.memo((props) => {
return <div className="mobile-users-part">
<div className="mobile-users-part-header">
<span className="mobile-users-part-header-title">
Участники демонастрации
</span>
<button
className="mobile-users-part-header-close-button"
onClick={() => props.onClickClose()}
></button>
</div>
<BorderLine />
<div className="mobile-users-part-users-container">
<UserItem typeUser="user admin"/>
<UserItem typeUser="user self" />
<UserItem typeUser="user guest" />
<UserItem typeUser="user guest" />
<UserItem typeUser="user guest" />
<UserItem typeUser="user guest" />
<UserItem typeUser="user guest" />
</div>
</div>
})
@@ -0,0 +1,42 @@
import React from "react";
import { ToolbarButton } from "../toolbar/toolbarButton";
import { TypeToolbarButtons } from "../toolbar/typeButtons";
import { BorderLine } from "./borderLine";
import { CaptionToolbarButtons } from "../toolbar/typeCaptionButtons";
type TProps = {
typeUser: TypeToolbarButtons
}
export const UserItem:React.FC<TProps> = React.memo((props) => {
return <div className="user-item">
<ToolbarButton
type={props.typeUser}
caption=""
isCaption={false}
onClick={() => null}
/>
<div className="user-item-user-info-container">
<div className="user-item-user-info">
<span className="user-item-user-info-name">{CaptionToolbarButtons[props.typeUser]}</span>
<div className="user-item-user-info-buttons">
<ToolbarButton
type="micro"
active={true}
caption=''
isCaption={false}
onClick={() => null}
/>
<ToolbarButton
type="control"
active={false}
caption=''
isCaption={false}
onClick={() => null}
/>
</div>
</div>
<BorderLine />
</div>
</div>
})
@@ -7,8 +7,10 @@ import { CaptionToolbarButtons } from "./typeCaptionButtons";
type TProps = {
buttons: Array<{
type: TypeToolbarButtons,
onClick: () => void,
isCaption: boolean
isNotification?: boolean,
onClick: () => void
active?: boolean
}>
}
@@ -22,6 +24,8 @@ export const ButtonContainer:React.FC<TProps> = React.memo((props) => {
type={button.type}
isNotification={button?.isNotification}
caption={CaptionToolbarButtons[button.type]}
active={button?.active}
isCaption={button.isCaption}
/>)}
<BorderLine />
</div>
@@ -0,0 +1,8 @@
<svg width="18" height="26" viewBox="0 0 18 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_688_6251" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="-3" y="-3" width="21" height="32">
<path d="M17.5 -2C17.5 -2.18939 17.393 -2.36252 17.2236 -2.44721C17.0542 -2.53191 16.8515 -2.51363 16.7 -2.4L-1.16667 11C-2.5 12 -2.5 14 -1.16667 15L16.7 28.4C16.8515 28.5136 17.0542 28.5319 17.2236 28.4472C17.393 28.3625 17.5 28.1894 17.5 28V-2Z" fill="#333333" stroke="#4F4F4F" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_688_6251)">
<path d="M18.5 1C18.5 0.815602 18.3985 0.646172 18.2359 0.559163C18.0734 0.472153 17.8761 0.481689 17.7227 0.583975L2.2188 10.9199C0.734468 11.9094 0.734468 14.0906 2.2188 15.0801L17.7227 25.416C17.8761 25.5183 18.0734 25.5278 18.2359 25.4408C18.3985 25.3538 18.5 25.1844 18.5 25V1Z" fill="#333333" stroke="#4F4F4F" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 957 B

@@ -0,0 +1,7 @@
<svg width="26" height="130" viewBox="0 0 26 130" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 0.5H0.5V1V129V129.5H1H4.69231H4.96719L5.11447 129.268L23.8655 99.7209C24.9331 98.0386 25.5 96.0872 25.5 94.0947V35.9053C25.5 33.9128 24.9331 31.9614 23.8655 30.2791L5.11447 0.732088L4.96719 0.5H4.69231H1Z" stroke="#4F4F4F"/>
<path d="M0 0H3.69231L22.4805 30.0683C23.4735 31.6574 24 33.4935 24 35.3674V94.6326C24 96.5064 23.4735 98.3426 22.4805 99.9317L3.69231 130H0V0Z" fill="#333333"/>
<path d="M1 1H4.69231L23.4433 30.547C24.4601 32.1492 25 34.0076 25 35.9053V94.0947C25 95.9924 24.4601 97.8508 23.4433 99.453L4.69231 129H1V1Z" fill="#333333"/>
<rect x="1" width="4" height="2" fill="#333333"/>
<rect x="1" y="128" width="4" height="2" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 772 B

+37 -20
View File
@@ -2,32 +2,27 @@
display: flex;
position: relative;
height: 100%;
width: 74px;
/* width: 74px; */
width: 64px;
padding: 0;
margin: 0;
transform: translateX(-70px);
transform: translateX(-60px);
border-right: 1px solid #4F4F4F;
background: #333333;
box-sizing: border-box;
}
/* .toolbar-container.closed {
transform: translateX(-70px);
} */
/* .toolbar-container.opened {
transform: translateX(0px);
} */
.toolbar-field {
width: 70px;
width: 60px;
background: #333333;
padding: 14px 15px;
box-sizing: border-box;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: space-between;
align-items: center;
}
.toolbar-field-part {
@@ -64,6 +59,7 @@
height: 128px;
border: none;
background: url('openToolbarIcon.svg') 50% 50% no-repeat;
background: url('openToolbarBackIcon.svg') 50% 50% no-repeat;
background-size: 100% 100%;
cursor: pointer;
@@ -141,6 +137,7 @@
border-top-right-radius: 50px;
border-bottom-right-radius: 50px;
border: 1px solid #4F4F4F;
border-left: none;
color: white;
display: flex;
align-items: center;
@@ -156,7 +153,7 @@
width: 18px;
height: 26px;
background-color: #EB5757;
background: url('icons/descriptTiriangleIcon.svg');
background: url('icons/newCaptTriangleIcon.svg');
}
.toolbar-button.notification::after {
@@ -170,12 +167,12 @@
background-color: #F2994A;
}
.toolbar-button.fullscreenOn {
.toolbar-button.fullscreen.on {
background: url('./icons/openFullscreenIcon.svg') 50% 50% no-repeat;
background-color: #4F4F4F;
}
.toolbar-button.fullscreenOff {
.toolbar-button.fullscreen {
background: url('./icons/closeFullscreenIcon.svg') 50% 50% no-repeat;
background-color: #4F4F4F;
}
@@ -197,7 +194,8 @@
}
.toolbar-button.users {
background: url('./icons/disableFullscreenIcon.svg');
background: url('./icons/usersIcon.svg') 50% 50% no-repeat;
background-color: #4F4F4F;
}
.toolbar-button.micro {
@@ -225,12 +223,12 @@
background-color: #4F4F4F;
}
.toolbar-button.soundOff {
.toolbar-button.sound {
background: url('./icons/soundOffIcon.svg') 50% 50% no-repeat;
background-color: #4F4F4F;
}
.toolbar-button.soundOn {
.toolbar-button.sound.on {
background: url('./icons/soundOnIcon.svg') 50% 50% no-repeat;
background-color: #4F4F4F;
}
@@ -256,7 +254,7 @@
.show-toolbar-enter {
transform: translateX(-70px);
transform: translateX(-60px);
}
.show-toolbar-enter-done {
@@ -273,6 +271,25 @@
}
.show-toolbar-exit-active {
transform: translateX(-70px);
transform: translateX(-60px);
transition: .3s;
}
@media screen and (max-height: 700px) {
.toolbar-field {
padding: 20px 14px;
}
.toolbar-button {
width: 44px;
height: 44px;
}
.toolbar-button:hover {
opacity: 1;
}
.toolbar-button:active {
opacity: .7;
}
}
+94 -14
View File
@@ -1,4 +1,5 @@
import React from "react";
import React, { useContext } from "react";
import { ContextWindowHeight } from "../../contextWindowHeight";
import { ButtonContainer } from "./buttonContainer";
import './toolbar.css';
import { ToolbarButton } from "./toolbarButton";
@@ -7,36 +8,109 @@ import { CaptionToolbarButtons } from "./typeCaptionButtons";
type TProps = {
onClickOpenButton: () => void
onClickMobileUsers?: () => void
isOpen: boolean
}
export const Toolbar:React.FC<TProps> = React.memo((props) => {
const windowHeight = useContext(ContextWindowHeight);
if(windowHeight < 700) {
return <div className={`toolbar-container ${props.isOpen ? 'opened' : 'closed'}`}>
<div className="toolbar-field">
<div className="toolbar-field-part">
<ToolbarButton
type="fullscreen"
onClick={() => {
if(!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}}
caption={CaptionToolbarButtons['fullscreen on']}
active={true}
isCaption={false}
/>
<ButtonContainer
buttons={[
{
type: "users",
onClick: props.onClickMobileUsers,
isCaption: false
}
]}
/>
</div>
<div className="toolbar-field-part">
<ButtonContainer
buttons={[
{
type: "control",
onClick: () => console.log('click'),
active: true,
isCaption: false
},
{
type: "micro",
onClick: () => console.log('click'),
active: false,
isCaption: false
}
]}
/>
{/* <BorderLine /> */}
<ToolbarButton
type="other"
onClick={() => null}
caption={CaptionToolbarButtons["other"]}
isCaption={false}
/>
</div>
</div>
<button className='toolbar-open-button' onClick={() => props.onClickOpenButton()}>
<span className="toolbar-open-button-icon"></span>
</button>
</div>
}
return <div className={`toolbar-container ${props.isOpen ? 'opened' : 'closed'}`}>
<div className="toolbar-field">
<div className="toolbar-field-part">
<ToolbarButton
type="fullscreenOn"
onClick={() => null}
caption={CaptionToolbarButtons["fullscreenOn"]}
type="fullscreen"
onClick={() => {
if(!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}}
caption={CaptionToolbarButtons['fullscreen on']}
active={true}
isCaption={true}
/>
<ButtonContainer
buttons={[
{
type: "user admin",
onClick: () => console.log('click'),
isCaption: true
},
{
type: "user self",
onClick: () => console.log('click')
},
{
type: "user guest",
onClick: () => console.log('click')
onClick: () => console.log('click'),
isCaption: true
},
{
type: "user guest",
onClick: () => console.log('click'),
isNotification: true
isCaption: true
},
{
type: "user guest",
onClick: () => console.log('click'),
isNotification: true,
isCaption: true
}
]}
/>
@@ -45,12 +119,16 @@ export const Toolbar:React.FC<TProps> = React.memo((props) => {
<ButtonContainer
buttons={[
{
type: "control on",
onClick: () => console.log('click')
type: "control",
onClick: () => console.log('click'),
active: true,
isCaption: true
},
{
type: "micro",
onClick: () => console.log('click')
onClick: () => console.log('click'),
active: false,
isCaption: true
}
]}
/>
@@ -58,12 +136,14 @@ export const Toolbar:React.FC<TProps> = React.memo((props) => {
type="share"
onClick={() => console.log('click')}
caption={CaptionToolbarButtons["share"]}
isCaption={true}
/>
<BorderLine />
<ToolbarButton
<ToolbarButton
type="exit"
onClick={() => null}
caption={CaptionToolbarButtons["exit"]}
isCaption={true}
/>
</div>
</div>
@@ -1,17 +1,21 @@
import React, { useState } from "react";
import { TypeToolbarButtons } from "./typeButtons";
import { CaptionToolbarButtons } from "./typeCaptionButtons";
type TProps = {
onClick: () => void
type: TypeToolbarButtons
isNotification?: boolean
isCaption: boolean
caption: string
active?: boolean
isNotification?: boolean
}
export const ToolbarButton:React.FC<TProps> = React.memo((props) => {
const [isActive, setIsActive] = useState(false);
const [isActive, setIsActive] = useState<boolean>(props?.active || false);
const [isActive2, setIsActive2] = useState(false);
const [showAddButtons, setShowAddBUttons] = useState(false)
function onClick() {
setIsActive(!isActive)
}
@@ -26,11 +30,17 @@ export const ToolbarButton:React.FC<TProps> = React.memo((props) => {
return <div className="toolbar-button-area">
<button
className={`toolbar-button ${props.type} ${props?.isNotification ? 'notification' : ''}`}
onClick={() => showAdd()}
className={`toolbar-button ${props.type} ${isActive ? 'on' : ''} ${props?.isNotification ? 'notification' : ''}`}
onClick={() => {
props.onClick();
showAdd();
//@ts-ignore
if(CaptionToolbarButtons[`${props.type} on`])
onClick()
}}
>
</button>
{
{/* {
showAddButtons && props.type.includes('user')
&&
<div className="toolbar-button-add-buttons">
@@ -45,10 +55,15 @@ export const ToolbarButton:React.FC<TProps> = React.memo((props) => {
>
</button>
</div>
}
{/* <div className="toolbar-button-description-container">
<span className="toolbar-button-description-triangle"></span>
<span className="toolbar-button-description-rectangle">{props.caption}</span>
</div> */}
} */}
{
props.isCaption && <div className="toolbar-button-description-container">
<span className="toolbar-button-description-triangle"></span>
{
//@ts-ignore
<span className="toolbar-button-description-rectangle">{CaptionToolbarButtons[isActive ? `${props.type} on` : props.type]}</span>
}
</div>
}
</div>
})
@@ -1,17 +1,17 @@
export type TypeToolbarButtons =
'fullscreenOn'
| 'fullscreenOff'
// 'fullscreenOn'
'fullscreen'
| 'user admin'
| 'user guest'
| 'user self'
| 'users'
| 'micro'
| 'micro on'
// | 'micro on'
| 'control'
| 'control on'
// | 'control on'
| 'other'
| 'soundOn'
| 'soundOff'
// | 'sound on'
| 'sound'
| 'exit'
| 'copy'
| 'share'
@@ -1,18 +1,18 @@
export const CaptionToolbarButtons = {
fullscreenOn: 'Развернуть',
fullscreenOff: 'Свернуть',
'fullscreen on': 'Развернуть',
'fullscreen': 'Свернуть',
'user admin': 'Администратор',
'user guest': 'Гость',
'user self': 'Вы',
'users': 'Пользователи',
'micro': 'Включить микрофон',
'micro on': 'Выключить микрофон',
'control on': '',
'control on': 'Запрет управления',
'control': 'Запрос управления',
'other': '',
'soundOn': '',
'soundOff': '',
'other': 'Дополнительно',
'sound on': 'Выключить звук',
'sound': 'Включить звук',
'exit': 'Завершить презентацию',
'copy': '',
'copy': 'Скопировать',
'share': 'Поделиться'
}
+6
View File
@@ -0,0 +1,6 @@
.start-page-container {
width: 100%;
height: 100vh;
background: rgba(17, 13, 28, 0.8);
backdrop-filter: blur(24px);
}
+8
View File
@@ -0,0 +1,8 @@
import React from "react";
import './startPage.css';
export const StartPage:React.FC = React.memo(() => {
return <div className="start-page-container">
</div>
})