Add channels and improve styles (#15)
This commit is contained in:
parent
d8d3bac048
commit
c5258f8068
|
@ -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;
|
||||
`;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
`;
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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};
|
||||
`;
|
||||
|
|
|
@ -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};
|
||||
`;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
];
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in New Issue