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 React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { ChannelData, channels } from "../helpers/channelsMock";
import { Theme } from "../styles/themes"; import { Theme } from "../styles/themes";
interface ChannelsProps { interface ChannelsProps {
theme: Theme; theme: Theme;
icon: string;
name: string;
members: number;
setActiveChannel: (val: ChannelData) => void;
activeChannelId: number;
} }
export function Channels({ theme }: ChannelsProps) { export function Channels({
return <ChannelsWrapper theme={theme}>Channels</ChannelsWrapper>; 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>` interface ChannelProps {
width: 33%; 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%; height: 100%;
background-color: ${({ theme }) => theme.sectionBackgroundColor}; 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 styled from "styled-components";
import { ChannelData, channels } from "../helpers/channelsMock";
import { Theme } from "../styles/themes"; import { Theme } from "../styles/themes";
import { Channels } from "./Channels"; import { Channels } from "./Channels";
@ -9,16 +10,27 @@ import { Members } from "./Members";
interface ChatProps { interface ChatProps {
theme: Theme; theme: Theme;
channels?: boolean; channelsON?: boolean;
members?: 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 ( return (
<ChatWrapper> <ChatWrapper>
{channels && <Channels theme={theme} />} {channelsON && (
<ChatBody /> <Channels
{members && <Members />} 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> </ChatWrapper>
); );
} }

View File

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

View File

@ -1,41 +1,56 @@
import React, { useState } from "react"; import React, { useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Theme } from "../../styles/themes";
type ChatInputProps = { type ChatInputProps = {
theme: Theme;
addMessage: (message: string) => void; addMessage: (message: string) => void;
}; };
export function ChatInput({ addMessage }: ChatInputProps) { export function ChatInput({ theme, addMessage }: ChatInputProps) {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
return ( return (
<Input <InputWrapper>
placeholder={"Message"} <Input
value={content} theme={theme}
onChange={(e) => setContent(e.target.value)} placeholder={"Message"}
onKeyPress={(e) => { value={content}
if (e.key == "Enter") { onChange={(e) => setContent(e.target.value)}
addMessage(content); onKeyPress={(e) => {
setContent(""); 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%; width: 100%;
margin-left: 8px; margin-left: 10px;
margin-right: 8px;
margin-bottom: 4px;
height: 40px; height: 40px;
background: #eef2f5; background: ${({ theme }) => theme.inputColor};
border-radius: 36px 16px 4px 36px; border-radius: 36px 16px 4px 36px;
border: 0px; border: 1px solid ${({ theme }) => theme.inputColor};
padding-left: 12px; padding-left: 12px;
outline: none; outline: none;
font-size: 15px;
line-height: 22px;
&:focus { &: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 React, { useEffect, useRef } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Theme } from "../../styles/themes";
import { ChatMessage } from "../models/ChatMessage"; import { ChatMessage } from "../models/ChatMessage";
type ChatMessagesProps = { type ChatMessagesProps = {
messages: ChatMessage[]; messages: ChatMessage[];
theme: Theme;
}; };
export function ChatMessages({ messages }: ChatMessagesProps) { export function ChatMessages({ messages, theme }: ChatMessagesProps) {
const ref = useRef<HTMLHeadingElement>(null); const ref = useRef<HTMLHeadingElement>(null);
useEffect(() => { useEffect(() => {
if (ref && ref.current) { if (ref && ref.current) {
@ -19,15 +21,15 @@ export function ChatMessages({ messages }: ChatMessagesProps) {
<MessagesWrapper ref={ref}> <MessagesWrapper ref={ref}>
{messages.map((message) => ( {messages.map((message) => (
<MessageWrapper key={message.date.toDateString()}> <MessageWrapper key={message.date.toDateString()}>
<IconWrapper> <Icon />
<Icon />
</IconWrapper>
<ContentWrapper> <ContentWrapper>
<MessageHeaderWrapper> <MessageHeaderWrapper>
<UserNameWrapper>{message.sender}</UserNameWrapper> <UserNameWrapper theme={theme}>{message.sender}</UserNameWrapper>
<TimeWrapper>{message.date.toLocaleTimeString()}</TimeWrapper> <TimeWrapper theme={theme}>
{message.date.toLocaleTimeString()}
</TimeWrapper>
</MessageHeaderWrapper> </MessageHeaderWrapper>
<MessageText>{message.content}</MessageText> <MessageText theme={theme}>{message.content}</MessageText>
</ContentWrapper> </ContentWrapper>
</MessageWrapper> </MessageWrapper>
))} ))}
@ -35,64 +37,59 @@ export function ChatMessages({ messages }: ChatMessagesProps) {
); );
} }
const MessageText = styled.div` interface ThemeProps {
overflow-wrap: anywhere; theme: Theme;
}
const MessagesWrapper = styled.div`
height: calc(100% - 44px);
overflow: auto;
padding: 8px 16px 0;
`;
const MessageWrapper = styled.div`
width: 100%; width: 100%;
`;
const MessageHeaderWrapper = styled.div`
display: flex; display: flex;
`; align-items: center;
padding: 8px 0;
const TimeWrapper = styled.div` margin-bottom: 8px;
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;
`; `;
const ContentWrapper = styled.div` const ContentWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 8px;
`;
const MessageHeaderWrapper = styled.div`
display: flex;
align-items: center;
`; `;
const Icon = styled.div` const Icon = styled.div`
width: 40px; width: 40px;
height: 40px; height: 40px;
border-radius: 50%; border-radius: 50%;
background-color: blue; background-color: #bcbdff;
`; `;
const IconWrapper = styled.div` const UserNameWrapper = styled.div<ThemeProps>`
margin-left: 16px; font-size: 15px;
margin-right: 8px; 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%; width: 100%;
display: flex; color: ${({ theme }) => theme.textPrimaryColor};
margin-top: 8px;
margin-bottom: 8px;
`;
const MessagesWrapper = styled.div`
height: calc(100% - 44px);
overflow: auto;
`; `;

View File

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

View File

@ -9,7 +9,7 @@ export function ReactChat() {
return ( return (
<div> <div>
<GlobalStyle /> <GlobalStyle />
<Chat channels={true} members={true} theme={lightTheme} /> <Chat channelsON={true} membersON={true} theme={lightTheme} />
</div> </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; sectionBackgroundColor: string;
memberNameColor: string; memberNameColor: string;
guestNameColor: string; guestNameColor: string;
iconColor: string;
iconTextColor: string;
activeChannelBackground: string;
notificationColor: string; notificationColor: string;
inputColor: string;
}; };
export const lightTheme: Theme = { export const lightTheme: Theme = {
@ -15,7 +19,11 @@ export const lightTheme: Theme = {
sectionBackgroundColor: "#F6F8FA", sectionBackgroundColor: "#F6F8FA",
memberNameColor: "#4360DF", memberNameColor: "#4360DF",
guestNameColor: "#887AF9", guestNameColor: "#887AF9",
iconColor: "#D37EF4",
iconTextColor: "rgba(255, 255, 255, 0.7)",
activeChannelBackground: "#E9EDF1",
notificationColor: "#4360DF", notificationColor: "#4360DF",
inputColor: "#EEF2F5",
}; };
export const darkTheme: Theme = { export const darkTheme: Theme = {
@ -25,7 +33,11 @@ export const darkTheme: Theme = {
sectionBackgroundColor: "#252525", sectionBackgroundColor: "#252525",
memberNameColor: "#88B0FF", memberNameColor: "#88B0FF",
guestNameColor: "#887AF9", guestNameColor: "#887AF9",
iconColor: "#D37EF4",
iconTextColor: "rgba(0, 0, 0, 0.7)",
activeChannelBackground: "#2C2C2C",
notificationColor: "#887AF9", notificationColor: "#887AF9",
inputColor: "#373737",
}; };
export default { lightTheme, darkTheme }; export default { lightTheme, darkTheme };