Add channels and improve styles (#15)

This commit is contained in:
Maria Rushkova 2021-09-28 13:36:56 +02:00 committed by GitHub
parent d8d3bac048
commit c5258f8068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 405 additions and 90 deletions

View File

@ -1,18 +1,235 @@
import React from "react";
import styled from "styled-components";
import { ChannelData, channels } from "../helpers/channelsMock";
import { Theme } from "../styles/themes";
interface ChannelsProps {
theme: Theme;
icon: string;
name: string;
members: number;
setActiveChannel: (val: ChannelData) => void;
activeChannelId: number;
}
export function Channels({ theme }: ChannelsProps) {
return <ChannelsWrapper theme={theme}>Channels</ChannelsWrapper>;
export function Channels({
theme,
icon,
name,
members,
setActiveChannel,
activeChannelId,
}: ChannelsProps) {
return (
<ChannelsWrapper theme={theme}>
<Community>
<CommunityLogo src={icon} alt={`${name} logo`} />
<CommunityInfo>
<CommunityName theme={theme}>{name}</CommunityName>
<MembersAmount theme={theme}>{members} members</MembersAmount>
</CommunityInfo>
</Community>
<ChannelList>
{channels.map((channel) => (
<Channel
key={channel.id}
channel={channel}
theme={theme}
isActive={channel.id === activeChannelId}
onClick={() => {
setActiveChannel(channel);
}}
/>
))}
</ChannelList>
</ChannelsWrapper>
);
}
const ChannelsWrapper = styled.div<ChannelsProps>`
width: 33%;
interface ChannelProps {
theme: Theme;
channel: ChannelData;
isActive: boolean;
activeView?: boolean;
onClick?: () => void;
}
export function Channel({
theme,
channel,
isActive,
activeView,
onClick,
}: ChannelProps) {
return (
<ChannelWrapper
className={isActive && !activeView ? "active" : ""}
theme={theme}
onClick={onClick}
>
<ChannelInfo>
<ChannelLogo
style={{
backgroundImage: channel.icon ? `url(${channel.icon}` : "",
}}
className={activeView ? "active" : ""}
theme={theme}
>
{!channel.icon && channel.name.slice(0, 1).toUpperCase()}
</ChannelLogo>
<ChannelTextInfo>
<ChannelName
theme={theme}
className={
isActive ? "active" : channel.notifications ? "notified" : ""
}
>
# {channel.name}
</ChannelName>
{activeView && (
<MembersAmount theme={theme}>
{" "}
{channel.members} members
</MembersAmount>
)}
</ChannelTextInfo>
</ChannelInfo>
{channel.notifications && !activeView && (
<NotificationBagde theme={theme}>
{channel.notifications}
</NotificationBagde>
)}
</ChannelWrapper>
);
}
interface ThemeProps {
theme: Theme;
}
const ChannelsWrapper = styled.div<ThemeProps>`
width: 21%;
height: 100%;
background-color: ${({ theme }) => theme.sectionBackgroundColor};
padding: 10px 0.6%;
display: flex;
flex-direction: column;
`;
const Community = styled.div`
display: flex;
margin-bottom: 16px;
`;
const CommunityLogo = styled.img`
width: 36px;
height: 36px;
border-radius: 50%;
margin-left: 10px;
`;
const CommunityInfo = styled.div`
display: flex;
flex-direction: column;
margin-left: 8px;
`;
const CommunityName = styled.h1<ThemeProps>`
font-weight: 500;
font-size: 15px;
line-height: 22px;
color: ${({ theme }) => theme.textPrimaryColor};
`;
const MembersAmount = styled.p<ThemeProps>`
font-size: 12px;
line-height: 16px;
letter-spacing: 0.1px;
color: ${({ theme }) => theme.textSecondaryColor};
`;
const ChannelList = styled.div`
display: flex;
flex-direction: column;
`;
const ChannelWrapper = styled.div<ThemeProps>`
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
cursor: pointer;
&.active {
background-color: ${({ theme }) => theme.activeChannelBackground};
border-radius: 8px;
}
`;
const ChannelInfo = styled.div`
display: flex;
align-items: center;
`;
const ChannelTextInfo = styled.div`
display: flex;
flex-direction: column;
`;
const ChannelLogo = styled.div<ThemeProps>`
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
border-radius: 50%;
font-weight: bold;
font-size: 15px;
line-height: 20px;
background-color: ${({ theme }) => theme.iconColor};
background-size: cover;
background-repeat: no-repeat;
color: ${({ theme }) => theme.iconTextColor};
&.active {
width: 36px;
height: 36px;
font-size: 20px;
line-height: 20px;
}
`;
const ChannelName = styled.p<ThemeProps>`
color: ${({ theme }) => theme.textPrimaryColor};
font-weight: 500;
font-size: 15px;
line-height: 22px;
opacity: 0.7;
&.active,
&.notified {
opacity: 1;
}
&.muted {
opacity: 0.4;
}
&.notified {
font-weight: 600;
}
`;
const NotificationBagde = styled.div<ThemeProps>`
width: 24px;
height: 24px;
border-radius: 50%;
font-size: 12px;
line-height: 16px;
background-color: ${({ theme }) => theme.notificationColor};
color: ${({ theme }) => theme.bodyBackgroundColor};
display: flex;
align-items: center;
justify-content: center;
`;

View File

@ -1,6 +1,7 @@
import React from "react";
import React, { useState } from "react";
import styled from "styled-components";
import { ChannelData, channels } from "../helpers/channelsMock";
import { Theme } from "../styles/themes";
import { Channels } from "./Channels";
@ -9,16 +10,27 @@ import { Members } from "./Members";
interface ChatProps {
theme: Theme;
channels?: boolean;
members?: boolean;
channelsON?: boolean;
membersON?: boolean;
}
export function Chat({ theme, channels, members }: ChatProps) {
export function Chat({ theme, channelsON, membersON }: ChatProps) {
const [activeChannel, setActiveChannel] = useState<ChannelData>(channels[0]);
return (
<ChatWrapper>
{channels && <Channels theme={theme} />}
<ChatBody />
{members && <Members />}
{channelsON && (
<Channels
theme={theme}
icon={"https://www.cryptokitties.co/icons/logo.svg"}
name={"CryptoKitties"}
members={186}
setActiveChannel={setActiveChannel}
activeChannelId={activeChannel.id}
/>
)}
<ChatBody theme={theme} channel={activeChannel} />
{membersON && <Members theme={theme} />}
</ChatWrapper>
);
}

View File

@ -1,12 +1,20 @@
import React, { useCallback, useState } from "react";
import styled from "styled-components";
import { ChannelData } from "../../helpers/channelsMock";
import { Theme } from "../../styles/themes";
import { Channel } from "../Channels";
import { ChatMessage } from "../models/ChatMessage";
import { ChatInput } from "./ChatInput";
import { ChatMessages } from "./ChatMessages";
export function ChatBody() {
interface ChatBodyProps {
theme: Theme;
channel: ChannelData;
}
export function ChatBody({ theme, channel }: ChatBodyProps) {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const addMessage = useCallback(
@ -20,16 +28,32 @@ export function ChatBody() {
);
return (
<ChatBodyWrapper>
<ChatMessages messages={messages} />
<ChatInput addMessage={addMessage} />
<ChatBodyWrapper theme={theme}>
<ChannelWrapper>
<Channel
channel={channel}
theme={theme}
isActive={true}
activeView={true}
/>
</ChannelWrapper>
<ChatMessages messages={messages} theme={theme} />
<ChatInput theme={theme} addMessage={addMessage} />
</ChatBodyWrapper>
);
}
interface ThemeProps {
theme: Theme;
}
const ChatBodyWrapper = styled.div`
const ChatBodyWrapper = styled.div<ThemeProps>`
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
max-width: 50%;
background: ${({ theme }) => theme.bodyBackgroundColor};
`;
const ChannelWrapper = styled.div`
padding-left: 8px;
`;

View File

@ -1,41 +1,56 @@
import React, { useState } from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
type ChatInputProps = {
theme: Theme;
addMessage: (message: string) => void;
};
export function ChatInput({ addMessage }: ChatInputProps) {
export function ChatInput({ theme, addMessage }: ChatInputProps) {
const [content, setContent] = useState("");
return (
<Input
placeholder={"Message"}
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyPress={(e) => {
if (e.key == "Enter") {
addMessage(content);
setContent("");
}
}}
/>
<InputWrapper>
<Input
theme={theme}
placeholder={"Message"}
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyPress={(e) => {
if (e.key == "Enter") {
addMessage(content);
setContent("");
}
}}
/>
</InputWrapper>
);
}
const Input = styled.input`
interface ThemeProps {
theme: Theme;
}
const InputWrapper = styled.div`
display: flex;
padding: 6px 8px 6px 10px;
`;
const Input = styled.input<ThemeProps>`
width: 100%;
margin-left: 8px;
margin-right: 8px;
margin-bottom: 4px;
margin-left: 10px;
height: 40px;
background: #eef2f5;
background: ${({ theme }) => theme.inputColor};
border-radius: 36px 16px 4px 36px;
border: 0px;
border: 1px solid ${({ theme }) => theme.inputColor};
padding-left: 12px;
outline: none;
font-size: 15px;
line-height: 22px;
&:focus {
border: 1px solid grey;
outline: none;
caret-color: ${({ theme }) => theme.notificationColor};
}
`;

View File

@ -1,13 +1,15 @@
import React, { useEffect, useRef } from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
import { ChatMessage } from "../models/ChatMessage";
type ChatMessagesProps = {
messages: ChatMessage[];
theme: Theme;
};
export function ChatMessages({ messages }: ChatMessagesProps) {
export function ChatMessages({ messages, theme }: ChatMessagesProps) {
const ref = useRef<HTMLHeadingElement>(null);
useEffect(() => {
if (ref && ref.current) {
@ -19,15 +21,15 @@ export function ChatMessages({ messages }: ChatMessagesProps) {
<MessagesWrapper ref={ref}>
{messages.map((message) => (
<MessageWrapper key={message.date.toDateString()}>
<IconWrapper>
<Icon />
</IconWrapper>
<Icon />
<ContentWrapper>
<MessageHeaderWrapper>
<UserNameWrapper>{message.sender}</UserNameWrapper>
<TimeWrapper>{message.date.toLocaleTimeString()}</TimeWrapper>
<UserNameWrapper theme={theme}>{message.sender}</UserNameWrapper>
<TimeWrapper theme={theme}>
{message.date.toLocaleTimeString()}
</TimeWrapper>
</MessageHeaderWrapper>
<MessageText>{message.content}</MessageText>
<MessageText theme={theme}>{message.content}</MessageText>
</ContentWrapper>
</MessageWrapper>
))}
@ -35,64 +37,59 @@ export function ChatMessages({ messages }: ChatMessagesProps) {
);
}
const MessageText = styled.div`
overflow-wrap: anywhere;
interface ThemeProps {
theme: Theme;
}
const MessagesWrapper = styled.div`
height: calc(100% - 44px);
overflow: auto;
padding: 8px 16px 0;
`;
const MessageWrapper = styled.div`
width: 100%;
`;
const MessageHeaderWrapper = styled.div`
display: flex;
`;
const TimeWrapper = styled.div`
font-family: Inter;
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 14px;
letter-spacing: 0.2px;
text-transform: uppercase;
color: #939ba1;
text-align: center;
margin-top: auto;
margin-bottom: auto;
margin-left: 4px;
`;
const UserNameWrapper = styled.div`
font-family: Inter;
font-style: normal;
font-weight: 500;
font-size: 15px;
line-height: 22px;
align-items: center;
padding: 8px 0;
margin-bottom: 8px;
`;
const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
margin-left: 8px;
`;
const MessageHeaderWrapper = styled.div`
display: flex;
align-items: center;
`;
const Icon = styled.div`
width: 40px;
height: 40px;
border-radius: 50%;
background-color: blue;
background-color: #bcbdff;
`;
const IconWrapper = styled.div`
margin-left: 16px;
margin-right: 8px;
const UserNameWrapper = styled.div<ThemeProps>`
font-size: 15px;
line-height: 22px;
color: ${({ theme }) => theme.memberNameColor};
`;
const MessageWrapper = styled.div`
const TimeWrapper = styled.div<ThemeProps>`
font-size: 10px;
line-height: 14px;
letter-spacing: 0.2px;
text-transform: uppercase;
color: ${({ theme }) => theme.textSecondaryColor};
margin-left: 4px;
`;
const MessageText = styled.div<ThemeProps>`
overflow-wrap: anywhere;
width: 100%;
display: flex;
margin-top: 8px;
margin-bottom: 8px;
`;
const MessagesWrapper = styled.div`
height: calc(100% - 44px);
overflow: auto;
color: ${({ theme }) => theme.textPrimaryColor};
`;

View File

@ -1,12 +1,18 @@
import React from "react";
import styled from "styled-components";
export function Members() {
return <MembersWrapper></MembersWrapper>;
import { Theme } from "../styles/themes";
interface MembersProps {
theme: Theme;
}
const MembersWrapper = styled.div`
width: 33%;
export function Members({ theme }: MembersProps) {
return <MembersWrapper theme={theme}>Members</MembersWrapper>;
}
const MembersWrapper = styled.div<MembersProps>`
width: 18%;
height: 100%;
background-color: ${({ theme }) => theme.sectionBackgroundColor};
`;

View File

@ -9,7 +9,7 @@ export function ReactChat() {
return (
<div>
<GlobalStyle />
<Chat channels={true} members={true} theme={lightTheme} />
<Chat channelsON={true} membersON={true} theme={lightTheme} />
</div>
);
}

View File

@ -0,0 +1,32 @@
export type ChannelData = {
id: number;
name: string;
icon?: string;
members: number;
notifications?: number;
};
export const channels = [
{
id: 1,
name: "welcome",
icon: "https://www.cryptokitties.co/icons/logo.svg",
members: 7,
},
{
id: 2,
name: "general",
members: 15,
},
{
id: 3,
name: "beginners",
members: 3,
notifications: 1,
},
{
id: 4,
name: "random",
members: 6,
},
];

View File

@ -5,7 +5,11 @@ export type Theme = {
sectionBackgroundColor: string;
memberNameColor: string;
guestNameColor: string;
iconColor: string;
iconTextColor: string;
activeChannelBackground: string;
notificationColor: string;
inputColor: string;
};
export const lightTheme: Theme = {
@ -15,7 +19,11 @@ export const lightTheme: Theme = {
sectionBackgroundColor: "#F6F8FA",
memberNameColor: "#4360DF",
guestNameColor: "#887AF9",
iconColor: "#D37EF4",
iconTextColor: "rgba(255, 255, 255, 0.7)",
activeChannelBackground: "#E9EDF1",
notificationColor: "#4360DF",
inputColor: "#EEF2F5",
};
export const darkTheme: Theme = {
@ -25,7 +33,11 @@ export const darkTheme: Theme = {
sectionBackgroundColor: "#252525",
memberNameColor: "#88B0FF",
guestNameColor: "#887AF9",
iconColor: "#D37EF4",
iconTextColor: "rgba(0, 0, 0, 0.7)",
activeChannelBackground: "#2C2C2C",
notificationColor: "#887AF9",
inputColor: "#373737",
};
export default { lightTheme, darkTheme };