More styling

This commit is contained in:
Robert Long 2021-08-20 16:23:12 -07:00
parent 5851d738f8
commit 43c0ed193e
13 changed files with 423 additions and 201 deletions

View File

@ -24,9 +24,10 @@ import {
} from "react-router-dom"; } from "react-router-dom";
import { useConferenceCallManager } from "./ConferenceCallManagerHooks"; import { useConferenceCallManager } from "./ConferenceCallManagerHooks";
import { Home } from "./Home"; import { Home } from "./Home";
import { LoginOrRegister } from "./LoginOrRegister";
import { Room } from "./Room"; import { Room } from "./Room";
import { GridDemo } from "./GridDemo"; import { GridDemo } from "./GridDemo";
import { RegisterPage } from "./RegisterPage";
import { LoginPage } from "./LoginPage";
export default function App() { export default function App() {
const { protocol, host } = window.location; const { protocol, host } = window.location;
@ -38,23 +39,24 @@ export default function App() {
return ( return (
<Router> <Router>
<> <>
{error && <p>{error.message}</p>}
{loading ? ( {loading ? (
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
<Switch> <Switch>
<Route exact path="/"> <AuthenticatedRoute authenticated={authenticated} exact path="/">
{authenticated ? ( <Home manager={manager} error={error} />
<Home manager={manager} /> </AuthenticatedRoute>
) : ( <Route exact path="/login">
<LoginOrRegister onRegister={register} onLogin={login} /> <LoginPage onLogin={login} error={error} />
)} </Route>
<Route exact path="/register">
<RegisterPage onRegister={register} error={error} />
</Route> </Route>
<AuthenticatedRoute <AuthenticatedRoute
authenticated={authenticated} authenticated={authenticated}
path="/room/:roomId" path="/room/:roomId"
> >
<Room manager={manager} /> <Room manager={manager} error={error} />
</AuthenticatedRoute> </AuthenticatedRoute>
<Route exact path="/grid"> <Route exact path="/grid">
<GridDemo /> <GridDemo />
@ -76,7 +78,7 @@ function AuthenticatedRoute({ authenticated, children, ...rest }) {
) : ( ) : (
<Redirect <Redirect
to={{ to={{
pathname: "/", pathname: "/login",
state: { from: location }, state: { from: location },
}} }}
/> />

View File

@ -38,3 +38,18 @@ export function RightNav({ children, className, ...rest }) {
</div> </div>
); );
} }
export function UserNav({ signedIn, userName, onLogout }) {
if (!signedIn) {
return null;
}
return (
<RightNav>
<span className={styles.userName}>{userName}</span>
<button className={styles.signOutButton} type="button" onClick={onLogout}>
Sign Out
</button>
</RightNav>
);
}

View File

@ -5,6 +5,7 @@
align-items: center; align-items: center;
height: 98px; height: 98px;
user-select: none; user-select: none;
flex-shrink: 0;
} }
.leftNav { .leftNav {
@ -21,5 +22,24 @@
.rightNav { .rightNav {
position: absolute; position: absolute;
right: 20px; right: 20px;
max-width: 30%; max-width: 33%;
} white-space: nowrap;
display: flex;
}
.userName {
font-weight: 600;
margin-right: 8px;
text-overflow: ellipsis;
overflow: hidden;
flex-shrink: 1;
}
.signOutButton {
background: transparent;
border: none;
color: rgb(255, 75, 85);
cursor: pointer;
font-weight: 600;
flex-shrink: 0;
}

View File

@ -17,10 +17,11 @@ limitations under the License.
import React, { useCallback, useRef, useState } from "react"; import React, { useCallback, useRef, useState } from "react";
import { useHistory, Link } from "react-router-dom"; import { useHistory, Link } from "react-router-dom";
import { useRooms } from "./ConferenceCallManagerHooks"; import { useRooms } from "./ConferenceCallManagerHooks";
import { Header, LeftNav, RightNav } from "./Header"; import { Header, LeftNav, UserNav } from "./Header";
import ColorHash from "color-hash"; import ColorHash from "color-hash";
import styles from "./Home.module.css"; import styles from "./Home.module.css";
import { FieldRow, InputField, Button } from "./Input"; import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
import { Center, Content, Sidebar, Modal } from "./Layout";
const colorHash = new ColorHash({ lightness: 0.3 }); const colorHash = new ColorHash({ lightness: 0.3 });
@ -62,21 +63,14 @@ export function Home({ manager }) {
<> <>
<Header> <Header>
<LeftNav /> <LeftNav />
<RightNav> <UserNav
<span className={styles.userName}> signedIn={manager.client}
{manager.client && manager.client.getUserId()} userName={manager.client.getUserId()}
</span> onLogout={onLogout}
<button />
className={styles.signOutButton}
type="button"
onClick={onLogout}
>
Sign Out
</button>
</RightNav>
</Header> </Header>
<div className={styles.content}> <Content>
<div className={styles.roomsSidebar}> <Sidebar>
<h5>Rooms:</h5> <h5>Rooms:</h5>
<div className={styles.roomList}> <div className={styles.roomList}>
{rooms.map((room) => ( {rooms.map((room) => (
@ -95,29 +89,35 @@ export function Home({ manager }) {
</Link> </Link>
))} ))}
</div> </div>
</div> </Sidebar>
<div className={styles.center}> <Center>
<form className={styles.createRoomContainer} onSubmit={onCreateRoom}> <Modal>
<h2>Create New Room</h2> <form onSubmit={onCreateRoom}>
<FieldRow> <h2>Create New Room</h2>
<InputField <FieldRow>
id="roomName" <InputField
name="roomName" id="roomName"
label="Room Name" name="roomName"
type="text" label="Room Name"
required type="text"
autoComplete="off" required
placeholder="Room Name" autoComplete="off"
ref={roomNameRef} placeholder="Room Name"
/> ref={roomNameRef}
</FieldRow> />
{createRoomError && <p>{createRoomError.message}</p>} </FieldRow>
<FieldRow rightAlign> {createRoomError && (
<Button type="submit">Create Room</Button> <FieldRow>
</FieldRow> <ErrorMessage>{createRoomError.message}</ErrorMessage>
</form> </FieldRow>
</div> )}
</div> <FieldRow rightAlign>
<Button type="submit">Create Room</Button>
</FieldRow>
</form>
</Modal>
</Center>
</Content>
</> </>
); );
} }

View File

@ -1,23 +1,3 @@
.content {
display: grid;
grid-template-columns: minmax(200px, 320px) 3fr;
gap: 20px;
margin: 0 20px;
flex: 1;
}
.roomsSidebar {
padding: 12px;
background-color: rgba(33,38,44,.9);
border-radius: 8px;
}
.roomsSidebar h5 {
color: rgb(142, 153, 164);
font-size: 13px;
margin: 0 0 8px 0;
}
.roomList { .roomList {
} }
@ -65,35 +45,3 @@
font-size: 14px; font-size: 14px;
line-height: 18px; line-height: 18px;
} }
.center {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.createRoomContainer {
color: #232f32;
border-radius: 8px;
padding: 25px 60px;
width: 400px;
background-color: white;
}
.createRoomContainer h2 {
margin: 0 0 20px 0;
}
.userName {
font-weight: 600;
margin-right: 8px;
}
.signOutButton {
background: transparent;
border: none;
color: rgb(255, 75, 85);
cursor: pointer;
font-weight: 600;
}

View File

@ -25,7 +25,7 @@ export const InputField = forwardRef(
return ( return (
<Field> <Field>
<input id={id} {...rest} ref={ref} /> <input id={id} {...rest} ref={ref} />
<label for={id}>{label}</label> <label htmlFor={id}>{label}</label>
</Field> </Field>
); );
} }
@ -42,3 +42,7 @@ export const Button = forwardRef(({ className, children, ...rest }, ref) => {
</button> </button>
); );
}); });
export function ErrorMessage({ children }) {
return <p className={styles.errorMessage}>{children}</p>;
}

View File

@ -69,9 +69,16 @@
max-width: calc(100% - 20px); max-width: calc(100% - 20px);
} }
.field input:focus + label { .field:focus-within {
border-color: #238cf5;
}
.field input:focus {
outline: 0;
}
.field input:focus + label, .field input:not(:placeholder-shown) + label {
background-color: #fff; background-color: #fff;
color: #238cf5;
transition: font-size .25s ease-out 0s,color .25s ease-out 0s,top .25s ease-out 0s,background-color .25s ease-out 0s; transition: font-size .25s ease-out 0s,color .25s ease-out 0s,top .25s ease-out 0s,background-color .25s ease-out 0s;
font-size: 10px; font-size: 10px;
top: -13px; top: -13px;
@ -80,6 +87,10 @@
pointer-events: auto; pointer-events: auto;
} }
.field input:focus + label {
color: #238cf5;
}
.button { .button {
vertical-align: middle; vertical-align: middle;
border: 0; border: 0;
@ -104,4 +115,11 @@
.button:active { .button:active {
}
.errorMessage {
margin: 0;
font-size: 13px;
color: rgb(255, 75, 85);
font-weight: 600;
} }

43
src/Layout.jsx Normal file
View File

@ -0,0 +1,43 @@
import React from "react";
import classNames from "classnames";
import styles from "./Layout.module.css";
export function Content({ children, className, ...rest }) {
return (
<div className={classNames(styles.content, className)} {...rest}>
{children}
</div>
);
}
export function Sidebar({ children, className, ...rest }) {
return (
<aside className={classNames(styles.sidebar, className)} {...rest}>
{children}
</aside>
);
}
export function Center({ children, className, ...rest }) {
return (
<div className={classNames(styles.center, className)} {...rest}>
{children}
</div>
);
}
export function Modal({ children, className, ...rest }) {
return (
<div className={classNames(styles.modal, className)} {...rest}>
{children}
</div>
);
}
export function Info({ children, className, ...rest }) {
return (
<p className={classNames(styles.info, className)} {...rest}>
{children}
</p>
);
}

69
src/Layout.module.css Normal file
View File

@ -0,0 +1,69 @@
.content {
display: flex;
flex-direction: column;
margin: 0 20px;
flex: 1;
}
.sidebar {
display: flex;
flex-direction: column;
height: auto;
flex: 1;
min-width: 200px;
width: auto;
max-width: none;
padding: 12px;
background-color: rgba(33,38,44,.9);
border-radius: 8px;
margin-bottom: 20px;
}
.sidebar h5 {
color: rgb(142, 153, 164);
font-size: 13px;
margin: 0 0 8px 0;
}
.center {
display: flex;
flex: 1;
justify-content: center;
align-items: center;
}
.modal {
color: #232f32;
border-radius: 8px;
padding: 25px 60px;
max-width: 400px;
background-color: white;
flex: 1;
margin-bottom: 20px;
}
.modal h2 {
margin: 0 0 20px 0;
}
.info {
font-size: 13px;
text-align: center;
}
@media (min-width: 600px) {
.sidebar {
height: 100%;
overflow-y: auto;
max-width: 320px;
margin-bottom: 0;
}
.content {
flex-direction: row;
}
.modal {
margin-bottom: 0;
}
}

View File

@ -1,93 +0,0 @@
/*
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useRef } from "react";
import { useHistory, useLocation } from "react-router-dom";
export function LoginOrRegister({ onRegister, onLogin }) {
const registerUsernameRef = useRef();
const registerPasswordRef = useRef();
const loginUsernameRef = useRef();
const loginPasswordRef = useRef();
const history = useHistory();
const location = useLocation();
const onSubmitRegisterForm = useCallback(
(e) => {
e.preventDefault();
onRegister(
registerUsernameRef.current.value,
registerPasswordRef.current.value,
() => {
if (location.state && location.state.from) {
history.replace(location.state.from);
}
}
);
},
[onRegister, location, history]
);
const onSubmitLoginForm = useCallback(
(e) => {
e.preventDefault();
onLogin(
loginUsernameRef.current.value,
loginPasswordRef.current.value,
() => {
if (location.state && location.state.from) {
history.replace(location.state.from);
}
}
);
},
[onLogin, location, history]
);
return (
<div className="page">
<h1>Matrix Video Chat</h1>
<h2>Register</h2>
<form onSubmit={onSubmitRegisterForm}>
<input
type="text"
ref={registerUsernameRef}
placeholder="Username"
></input>
<input
type="password"
ref={registerPasswordRef}
placeholder="Password"
></input>
<button type="submit">Register</button>
</form>
<h2>Login</h2>
<form onSubmit={onSubmitLoginForm}>
<input
type="text"
ref={loginUsernameRef}
placeholder="Username"
></input>
<input
type="password"
ref={loginPasswordRef}
placeholder="Password"
></input>
<button type="submit">Login</button>
</form>
</div>
);
}

98
src/LoginPage.jsx Normal file
View File

@ -0,0 +1,98 @@
/*
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useRef } from "react";
import { useHistory, useLocation, Link } from "react-router-dom";
import { Header, LeftNav } from "./Header";
import { FieldRow, InputField, Button, ErrorMessage } from "./Input";
import { Center, Content, Info, Modal } from "./Layout";
export function LoginPage({ onLogin, error }) {
const loginUsernameRef = useRef();
const loginPasswordRef = useRef();
const history = useHistory();
const location = useLocation();
const onSubmitLoginForm = useCallback(
(e) => {
e.preventDefault();
onLogin(
loginUsernameRef.current.value,
loginPasswordRef.current.value,
() => {
if (location.state && location.state.from) {
history.replace(location.state.from);
} else {
history.replace("/");
}
}
);
},
[onLogin, location, history]
);
return (
<>
<Header>
<LeftNav />
</Header>
<Content>
<Center>
<Modal>
<h2>Login</h2>
<form onSubmit={onSubmitLoginForm}>
<FieldRow>
<InputField
type="text"
ref={loginUsernameRef}
placeholder="Username"
label="Username"
/>
</FieldRow>
<FieldRow>
<InputField
type="password"
ref={loginPasswordRef}
placeholder="Password"
label="Password"
/>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage>{error.message}</ErrorMessage>
</FieldRow>
)}
<FieldRow rightAlign>
<Button type="submit">Login</Button>
</FieldRow>
</form>
<Info>
New?{" "}
<Link
to={{
pathname: "/register",
state: location.state,
}}
>
Create account
</Link>
</Info>
</Modal>
</Center>
</Content>
</>
);
}

98
src/RegisterPage.jsx Normal file
View File

@ -0,0 +1,98 @@
/*
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useRef } from "react";
import { useHistory, useLocation, Link } from "react-router-dom";
import { Header, LeftNav } from "./Header";
import { FieldRow, InputField, Button } from "./Input";
import { Center, Content, Info, Modal } from "./Layout";
export function RegisterPage({ onRegister, error }) {
const registerUsernameRef = useRef();
const registerPasswordRef = useRef();
const history = useHistory();
const location = useLocation();
const onSubmitRegisterForm = useCallback(
(e) => {
e.preventDefault();
onRegister(
registerUsernameRef.current.value,
registerPasswordRef.current.value,
() => {
if (location.state && location.state.from) {
history.replace(location.state.from);
} else {
history.replace("/");
}
}
);
},
[onRegister, location, history]
);
return (
<>
<Header>
<LeftNav />
</Header>
<Content>
<Center>
<Modal>
<h2>Register</h2>
<form onSubmit={onSubmitRegisterForm}>
<FieldRow>
<InputField
type="text"
ref={registerUsernameRef}
placeholder="Username"
label="Username"
/>
</FieldRow>
<FieldRow>
<InputField
type="password"
ref={registerPasswordRef}
placeholder="Password"
label="Password"
/>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage>{error.message}</ErrorMessage>
</FieldRow>
)}
<FieldRow rightAlign>
<Button type="submit">Register</Button>
</FieldRow>
</form>
<Info>
Already have an account?{" "}
<Link
to={{
pathname: "/login",
state: location.state,
}}
>
Sign in here
</Link>
</Info>
</Modal>
</Center>
</Content>
</>
);
}

View File

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import styles from "./Room.module.css"; import styles from "./Room.module.css";
import { useParams, useLocation, Link } from "react-router-dom"; import { useParams, useLocation } from "react-router-dom";
import { useVideoRoom } from "./ConferenceCallManagerHooks"; import { useVideoRoom } from "./ConferenceCallManagerHooks";
import { DevTools } from "./DevTools"; import { DevTools } from "./DevTools";
import { VideoGrid } from "./VideoGrid"; import { VideoGrid } from "./VideoGrid";