feat: modal system, "create channel" button
This commit is contained in:
parent
ec4c98b760
commit
3c216557db
21 changed files with 367 additions and 72 deletions
|
@ -3,9 +3,11 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"framer-motion": "^4.1.17",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-content-loader": "^6.0.3",
|
"react-content-loader": "^6.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-feather": "^2.0.9",
|
||||||
"react-media-hook": "^0.4.9",
|
"react-media-hook": "^0.4.9",
|
||||||
"react-redux": "^7.2.5",
|
"react-redux": "^7.2.5",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<body>
|
<body>
|
||||||
<noscript>Sorry, but JavaScript is required.</noscript>
|
<noscript>Sorry, but JavaScript is required.</noscript>
|
||||||
|
|
||||||
|
<div id="modal-root"></div>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -6,5 +6,9 @@ const { log: authLog } = Logger([ 'Authenticator' ]);
|
||||||
|
|
||||||
export function login() {
|
export function login() {
|
||||||
authLog('Logging in through gateway...');
|
authLog('Logging in through gateway...');
|
||||||
|
if (gateway.handshakeCompleted) {
|
||||||
|
authLog("Gateway connection already exists, tearing down existing one...");
|
||||||
|
gateway.ws.close();
|
||||||
|
}
|
||||||
return gateway.connect(getToken());
|
return gateway.connect(getToken());
|
||||||
};
|
};
|
||||||
|
|
58
src/components/Modal.js
Normal file
58
src/components/Modal.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { m } from "framer-motion";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
const modalRoot = document.getElementById('modal-root');
|
||||||
|
|
||||||
|
const modalAnimation = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
transition: {
|
||||||
|
duration: 0.1,
|
||||||
|
ease: "easeIn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
transition: {
|
||||||
|
duration: 0.1,
|
||||||
|
ease: "easeOut",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Modal({ width=600, height=400, alignItems="default", title, children, onClose }) {
|
||||||
|
return createPortal(
|
||||||
|
<>
|
||||||
|
<m.div
|
||||||
|
className="backdrop"
|
||||||
|
onClick={onClose}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
<m.div
|
||||||
|
className={ alignItems === "center" ? "modal-centered" : "modal" }
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
variants={modalAnimation}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="exit"
|
||||||
|
style={{
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ (title) && <span className="modal-title">{ title }</span> }
|
||||||
|
{children}
|
||||||
|
</m.div>
|
||||||
|
</m.div>
|
||||||
|
</>,
|
||||||
|
modalRoot
|
||||||
|
);
|
||||||
|
}
|
|
@ -68,7 +68,7 @@ export default function Create() {
|
||||||
<div id="login-container">
|
<div id="login-container">
|
||||||
<h1>One more thing!</h1>
|
<h1>One more thing!</h1>
|
||||||
<p>You need a special code to sign up here!</p>
|
<p>You need a special code to sign up here!</p>
|
||||||
<label htmlFor="specialcode">Special Code</label>
|
<label htmlFor="specialcode" className="label">Special Code</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="password" name="specialcode" className="text-input" onChange={ ({ target }) => setSpecialCodeInput(target.value) } />
|
<input type="password" name="specialcode" className="text-input" onChange={ ({ target }) => setSpecialCodeInput(target.value) } />
|
||||||
<button id="login-submit" className="button" onClick={ doCreateAccount }>Continue</button>
|
<button id="login-submit" className="button" onClick={ doCreateAccount }>Continue</button>
|
||||||
|
@ -84,11 +84,11 @@ export default function Create() {
|
||||||
<span className="greeter-branding-name">sign up</span>
|
<span className="greeter-branding-name">sign up</span>
|
||||||
<div className="center">
|
<div className="center">
|
||||||
<div id="login-container">
|
<div id="login-container">
|
||||||
<label htmlFor="username">Username</label>
|
<label htmlFor="username" className="label">Username</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="text" name="username" className="text-input" onChange={ ({ target }) => setUsernameInput(target.value) } />
|
<input type="text" name="username" className="text-input" onChange={ ({ target }) => setUsernameInput(target.value) } />
|
||||||
<br />
|
<br />
|
||||||
<label htmlFor="password">Password</label>
|
<label htmlFor="password" className="label">Password</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="password" name="password" className="text-input" onChange={ ({ target }) => setPasswordInput(target.value) } />
|
<input type="password" name="password" className="text-input" onChange={ ({ target }) => setPasswordInput(target.value) } />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -56,11 +56,11 @@ export default function Login() {
|
||||||
<span className="greeter-branding-name">log in</span>
|
<span className="greeter-branding-name">log in</span>
|
||||||
<div className="center">
|
<div className="center">
|
||||||
<div id="login-container">
|
<div id="login-container">
|
||||||
<label htmlFor="username">Username</label>
|
<label htmlFor="username" className="label">Username</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="text" name="username" className="text-input" onChange={ ({ target }) => setUsernameInput(target.value) } />
|
<input type="text" name="username" className="text-input" onChange={ ({ target }) => setUsernameInput(target.value) } />
|
||||||
<br />
|
<br />
|
||||||
<label htmlFor="password">Password</label>
|
<label htmlFor="password" className="label">Password</label>
|
||||||
<br />
|
<br />
|
||||||
<input type="password" name="password" className="text-input" onChange={ ({ target }) => setPasswordInput(target.value) } />
|
<input type="password" name="password" className="text-input" onChange={ ({ target }) => setPasswordInput(target.value) } />
|
||||||
<br />
|
<br />
|
||||||
|
|
92
src/components/channel/ChannelCreateButton.js
Normal file
92
src/components/channel/ChannelCreateButton.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { AnimatePresence } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import Modal from "../Modal";
|
||||||
|
import { authenticated } from '../../api/request';
|
||||||
|
import { getCreateChannelError } from "../../common/util/errors";
|
||||||
|
import { login } from "../../api/authenticator";
|
||||||
|
|
||||||
|
export default function ChannelCreateButton() {
|
||||||
|
const [ isDialogOpen, setIsDialogOpen ] = useState(false);
|
||||||
|
const [ channelNameInput, setChannelNameInput ] = useState();
|
||||||
|
const [ info, setInfo ] = useState(null);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
setInfo(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createChannel = async () => {
|
||||||
|
setInfo("creating channel...");
|
||||||
|
const { json, isOK } = await authenticated('/api/v1/content/channel/create', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: channelNameInput
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isOK && json) {
|
||||||
|
setInfo(getCreateChannelError(json));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOK) {
|
||||||
|
setInfo("Something went wrong");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
login(); // ugly: we need to relog in order to see the new channel
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
let modalContent;
|
||||||
|
if (info) {
|
||||||
|
modalContent = (
|
||||||
|
<>
|
||||||
|
<div className="center grow col-flex">
|
||||||
|
<span className="greeter-branding-name">{ info }</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
modalContent = (
|
||||||
|
<>
|
||||||
|
<div className="input-group">
|
||||||
|
<label htmlFor="channel-name" className="label" style={{ float: "left" }}>Channel Name</label>
|
||||||
|
<input type="text" name="channel-name" className="text-input" onChange={ ({ target }) => setChannelNameInput(target.value) } />
|
||||||
|
</div>
|
||||||
|
<div className="full-width">
|
||||||
|
<button style={{ float: "right" }} id="login-submit" className="button-pressed" onClick={ createChannel }>Create</button>
|
||||||
|
<button style={{ float: "left" }} className="button" onClick={ handleClose }>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<button className="button button-channel" onClick={() => setIsDialogOpen(true)}>
|
||||||
|
<div className="profile-link">
|
||||||
|
<div className="profile-picture add-channel" alt="Profile">
|
||||||
|
<span className="default-channel-styled-text">+</span>
|
||||||
|
</div>
|
||||||
|
<span className="profile-username">New Channel</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/* ugly: AnimatePresence is needed to animate the modal closing */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{(isDialogOpen) && <Modal
|
||||||
|
width="500px"
|
||||||
|
height="300px"
|
||||||
|
title="Create a channel"
|
||||||
|
alignItems="center"
|
||||||
|
onClose={ handleClose }
|
||||||
|
>
|
||||||
|
{ modalContent }
|
||||||
|
</Modal>}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux'
|
||||||
|
|
||||||
import ChannelListLoader from './ChannelListLoader';
|
import ChannelListLoader from './ChannelListLoader';
|
||||||
import ChannelButton from './ChannelButton';
|
import ChannelButton from './ChannelButton';
|
||||||
|
import ChannelCreateButton from './ChannelCreateButton';
|
||||||
|
|
||||||
function ChannelList({ selectedChannelId, channels }) {
|
function ChannelList({ selectedChannelId, channels }) {
|
||||||
if (!channels) {
|
if (!channels) {
|
||||||
|
@ -14,6 +15,7 @@ function ChannelList({ selectedChannelId, channels }) {
|
||||||
return (
|
return (
|
||||||
<div className="channel-list">
|
<div className="channel-list">
|
||||||
{ channels.map((channel) => ( <ChannelButton key={ channel._id } channel={ channel } selected={ (channel._id === selectedChannelId) } /> )) }
|
{ channels.map((channel) => ( <ChannelButton key={ channel._id } channel={ channel } selected={ (channel._id === selectedChannelId) } /> )) }
|
||||||
|
<ChannelCreateButton />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default function ChannelMessageView({ messages, channelId }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(loadOlderMessages, [channelId, dispatch]);
|
useEffect(loadOlderMessages, [channelId, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -45,9 +45,6 @@ function App({ user, fullscreenMessage }) {
|
||||||
<Route path="/channels/:channelId"
|
<Route path="/channels/:channelId"
|
||||||
render={() => <LoggedInMount />}
|
render={() => <LoggedInMount />}
|
||||||
/>
|
/>
|
||||||
<Route path="/user/:userId"
|
|
||||||
render={() => <LoggedInMount />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
{ user && <LoggedInMount /> }
|
{ user && <LoggedInMount /> }
|
||||||
|
|
|
@ -4,16 +4,14 @@ import { useParams } from "react-router-dom";
|
||||||
import Sidebar from "../Sidebar";
|
import Sidebar from "../Sidebar";
|
||||||
import ChannelView from "../channel/ChannelView";
|
import ChannelView from "../channel/ChannelView";
|
||||||
import GradientBanner from "../GradientBanner";
|
import GradientBanner from "../GradientBanner";
|
||||||
import UserView from "../user/UserView";
|
|
||||||
|
|
||||||
function LoggedInMount({ gradientBannerNotificationText }) {
|
function LoggedInMount({ gradientBannerNotificationText }) {
|
||||||
const { channelId, userId } = useParams();
|
const { channelId } = useParams();
|
||||||
return <>
|
return <>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="col-flex">
|
<div className="col-flex">
|
||||||
<GradientBanner text={ gradientBannerNotificationText }/>
|
<GradientBanner text={ gradientBannerNotificationText }/>
|
||||||
{ (channelId) && <ChannelView channelId={ channelId } /> }
|
{ (channelId) && <ChannelView channelId={ channelId } /> }
|
||||||
{ (userId) && <UserView userId={ userId } /> }
|
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,39 @@
|
||||||
import UserProfile from './UserProfileLink';
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import UserProfile from "./UserProfileLink";
|
||||||
|
import { authenticated } from "../../api/request";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
import { AnimatePresence } from "framer-motion";
|
||||||
|
|
||||||
export default function ChannelUserButton({ user, subtext }) {
|
export default function ChannelUserButton({ user, subtext }) {
|
||||||
const history = useHistory();
|
const [ isPromptOpen, setIsPromptOpen ] = useState(false);
|
||||||
|
const [userObject, setUserObject] = useState(null);
|
||||||
|
|
||||||
const handleClick = () => {
|
useEffect(() => {
|
||||||
history.push(`/user/${user._id}`);
|
authenticated(`/api/v1/users/user/${user._id}/info`, {
|
||||||
};
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
}).then(({ isOK, json }) => {
|
||||||
|
if (isOK) {
|
||||||
|
setUserObject(json.user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className="button button-channel" onClick={ handleClick }>
|
<>
|
||||||
<UserProfile subtext={ subtext } user={ user } size="32" />
|
<AnimatePresence>
|
||||||
</button>
|
{(isPromptOpen) && <Modal onClose={ () => setIsPromptOpen(false) } alignItems="center" width="150px" height="150px">
|
||||||
|
<UserProfile subtext={ subtext } user={ user } size="128" />
|
||||||
|
<span className="label">{ user.status === 1 ? "Online" : "Offline" }</span>
|
||||||
|
<span className="label">{ userObject ? userObject.role.toLowerCase() : "loading..." }</span>
|
||||||
|
</Modal>}
|
||||||
|
</AnimatePresence>
|
||||||
|
<button className="button button-channel" onClick={ () => setIsPromptOpen(true) }>
|
||||||
|
<UserProfile subtext={ subtext } user={ user } size="32" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { authenticated } from "../../api/request";
|
|
||||||
import UserProfile from "./UserProfileLink";
|
|
||||||
import ProfileLinkLoader from "../ProfileLinkLoader";
|
|
||||||
|
|
||||||
export default function UserView({ userId }) {
|
|
||||||
const [userObject, setUserObject] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
authenticated(`/api/v1/users/user/${userId}/info`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json"
|
|
||||||
}
|
|
||||||
}).then(({ isOK, json }) => {
|
|
||||||
if (isOK) {
|
|
||||||
setUserObject(json.user);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
let view = null;
|
|
||||||
if (userObject) {
|
|
||||||
view = <>
|
|
||||||
<UserProfile user={ userObject } size="32" />
|
|
||||||
{(userObject.role === "ADMIN") && <span style={{ padding: "12px" }}>Admin</span>}
|
|
||||||
{(userObject.role === "USER") && <span style={{ padding: "12px" }}>User</span>}
|
|
||||||
</>
|
|
||||||
} else {
|
|
||||||
view = <>
|
|
||||||
<ProfileLinkLoader />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="center grow">
|
|
||||||
<div className="user-view center">
|
|
||||||
{view}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -4,11 +4,14 @@ import App from './components/main/App';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { LazyMotion, domAnimation } from "framer-motion"
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={ store }>
|
<Provider store={ store }>
|
||||||
<App />
|
<LazyMotion features={ domAnimation }>
|
||||||
|
<App />
|
||||||
|
</LazyMotion>
|
||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
|
|
|
@ -35,6 +35,11 @@ body {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal-root {
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input, optgroup, select, textarea {
|
button, input, optgroup, select, textarea {
|
||||||
|
|
|
@ -62,6 +62,14 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-badge {
|
.profile-badge {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
@ -87,3 +95,68 @@
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
padding-bottom: 18px;
|
padding-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
min-width: 100px;
|
||||||
|
min-height: 100px;
|
||||||
|
max-width: 50%;
|
||||||
|
max-height: 50%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
padding: 1.5em;
|
||||||
|
border-radius: 1em;
|
||||||
|
background-color: var(--accent-color-dark);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.modal {
|
||||||
|
/* !important is used here because the height and width can be set using inline styles in the Modal component */
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-centered {
|
||||||
|
@extend .modal;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.74rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdrop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: #0000008f;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999999999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 32px;
|
||||||
|
text-align: initial;
|
||||||
|
}
|
|
@ -14,10 +14,13 @@
|
||||||
color: var(--default-text-color);
|
color: var(--default-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-messages-icon {
|
&.add-channel {
|
||||||
border-radius: 0;
|
border-radius: 12px;
|
||||||
width: 16em;
|
display: flex;
|
||||||
height: 16em;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--create-channel-background);
|
||||||
|
color: var(--default-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.default-user {
|
&.default-user {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
.text-input {
|
.text-input {
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
padding: 6px;
|
padding: 14px;
|
||||||
border: none;
|
border: none;
|
||||||
|
min-width: 220px;
|
||||||
color: var(--default-text-color);
|
color: var(--default-text-color);
|
||||||
border-radius: var(--default-button-border-radius);
|
border-radius: var(--default-button-border-radius);
|
||||||
background-color: var(--message-box-color);
|
background-color: var(--message-box-color);
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
&.message-input {
|
&.message-input {
|
||||||
border-radius: var(--message-box-border-radius);
|
border-radius: var(--message-box-border-radius);
|
||||||
|
@ -16,5 +16,6 @@
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,3 +5,9 @@
|
||||||
.elevated-2 {
|
.elevated-2 {
|
||||||
box-shadow: rgba(0, 0, 0, 0.50) 0px 25px 50px -4px;
|
box-shadow: rgba(0, 0, 0, 0.50) 0px 25px 50px -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--darker-text-color);
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,13 @@
|
||||||
hsl(225, 35%, 40%)
|
hsl(225, 35%, 40%)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
--create-channel-background: linear-gradient(
|
||||||
|
to top right,
|
||||||
|
hsl(75, 35%, 40%),
|
||||||
|
hsl(150, 35%, 40%),
|
||||||
|
hsl(200, 35%, 40%)
|
||||||
|
);
|
||||||
|
|
||||||
--default-scrollbar-color: var(--accent-color);
|
--default-scrollbar-color: var(--accent-color);
|
||||||
--default-scrollbar-color-track: var(--background-color);
|
--default-scrollbar-color-track: var(--background-color);
|
||||||
--default-scrollbar-width: 1px;
|
--default-scrollbar-width: 1px;
|
||||||
|
@ -28,7 +35,7 @@
|
||||||
--channel-top-bar-color-accent: var(--background-color);
|
--channel-top-bar-color-accent: var(--background-color);
|
||||||
--channel-top-bar-color: var(--background-color);
|
--channel-top-bar-color: var(--background-color);
|
||||||
--sidebar-background-color: hsl(230, 12%, 12%);
|
--sidebar-background-color: hsl(230, 12%, 12%);
|
||||||
--elevation-box-shadow: 0 1px 0 0 hsla(230, 12%, 8%, 0.2), 0 2px 0 0 hsla(230, 12%, 8%, 0.2), 0 3px 0 0 hsla(230, 12%, 10%, 0.1);
|
--elevation-box-shadow: 0 2px 0 0 hsla(230, 12%, 8%, 0.167), 0 2px 0 0 hsla(230, 12%, 8%, 0.08), 0 3px 0 0 hsla(230, 12%, 10%, 0.07);
|
||||||
--message-box-color: var(--accent-color);
|
--message-box-color: var(--accent-color);
|
||||||
|
|
||||||
--button-color: var(--accent-color-dark);
|
--button-color: var(--accent-color-dark);
|
||||||
|
|
64
yarn.lock
64
yarn.lock
|
@ -1219,6 +1219,18 @@
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
|
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
|
||||||
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
|
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
|
||||||
|
|
||||||
|
"@emotion/is-prop-valid@^0.8.2":
|
||||||
|
version "0.8.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||||
|
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/memoize" "0.7.4"
|
||||||
|
|
||||||
|
"@emotion/memoize@0.7.4":
|
||||||
|
version "0.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||||
|
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||||
|
|
||||||
"@eslint/eslintrc@^0.4.3":
|
"@eslint/eslintrc@^0.4.3":
|
||||||
version "0.4.3"
|
version "0.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
|
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
|
||||||
|
@ -5061,6 +5073,26 @@ fragment-cache@^0.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
map-cache "^0.2.2"
|
map-cache "^0.2.2"
|
||||||
|
|
||||||
|
framer-motion@^4.1.17:
|
||||||
|
version "4.1.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
|
||||||
|
integrity sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==
|
||||||
|
dependencies:
|
||||||
|
framesync "5.3.0"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
popmotion "9.3.6"
|
||||||
|
style-value-types "4.1.4"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
optionalDependencies:
|
||||||
|
"@emotion/is-prop-valid" "^0.8.2"
|
||||||
|
|
||||||
|
framesync@5.3.0:
|
||||||
|
version "5.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
|
||||||
|
integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
fresh@0.5.2:
|
fresh@0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
@ -5413,6 +5445,11 @@ hex-color-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
|
hey-listen@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||||
|
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||||
|
|
||||||
history@^4.9.0:
|
history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
|
@ -8036,6 +8073,16 @@ pnp-webpack-plugin@1.6.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
ts-pnp "^1.1.6"
|
ts-pnp "^1.1.6"
|
||||||
|
|
||||||
|
popmotion@9.3.6:
|
||||||
|
version "9.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
|
||||||
|
integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
|
||||||
|
dependencies:
|
||||||
|
framesync "5.3.0"
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
style-value-types "4.1.4"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
portfinder@^1.0.26:
|
portfinder@^1.0.26:
|
||||||
version "1.0.28"
|
version "1.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
|
||||||
|
@ -9017,6 +9064,13 @@ react-error-overlay@^6.0.9:
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||||
|
|
||||||
|
react-feather@^2.0.9:
|
||||||
|
version "2.0.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-2.0.9.tgz#6e42072130d2fa9a09d4476b0e61b0ed17814480"
|
||||||
|
integrity sha512-yMfCGRkZdXwIs23Zw/zIWCJO3m3tlaUvtHiXlW+3FH7cIT6fiK1iJ7RJWugXq7Fso8ZaQyUm92/GOOHXvkiVUw==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
@ -10267,6 +10321,14 @@ style-loader@1.3.0:
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^2.7.0"
|
schema-utils "^2.7.0"
|
||||||
|
|
||||||
|
style-value-types@4.1.4:
|
||||||
|
version "4.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
|
||||||
|
integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
|
||||||
|
dependencies:
|
||||||
|
hey-listen "^1.0.8"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
|
||||||
stylehacks@^4.0.0:
|
stylehacks@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
|
||||||
|
@ -10580,7 +10642,7 @@ tslib@^1.8.1:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.0.3:
|
tslib@^2.0.3, tslib@^2.1.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||||
|
|
Loading…
Reference in a new issue