Skip to content

Commit d91170b

Browse files
authored
Adds new chat (codesandbox#3472)
* new chat * remove wrong open * clean styles * fix border and padding * add box shadow * make work in light themes
1 parent 1fbb685 commit d91170b

File tree

4 files changed

+298
-130
lines changed

4 files changed

+298
-130
lines changed
Lines changed: 106 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import React, { useState, useEffect, useRef } from 'react';
2-
import styled from 'styled-components';
2+
import styled, { css as c } from 'styled-components';
33
import { sortBy, takeRight } from 'lodash-es';
44

5-
import AutosizeTextArea from '@codesandbox/common/lib/components/AutosizeTextArea';
65
import { ENTER } from '@codesandbox/common/lib/utils/keycodes';
76
import { useOvermind } from 'app/overmind';
7+
import css from '@styled-system/css';
8+
import {
9+
Collapsible,
10+
Text,
11+
Stack,
12+
Textarea,
13+
Element,
14+
} from '@codesandbox/components';
815

9-
const Container = styled.div`
16+
const Container = styled(Stack)`
1017
min-height: 200px;
1118
max-height: 300px;
12-
padding: 0 1rem;
13-
color: white;
14-
font-size: 0.875rem;
15-
display: flex;
16-
flex-direction: column;
19+
padding: 0 ${props => props.theme.sizes[2]}px;
1720
overflow-y: auto;
1821
`;
1922

@@ -22,9 +25,17 @@ const Messages = styled.div`
2225
flex: 1;
2326
`;
2427

28+
const Avatar = styled.img`
29+
${({ theme, color }) => c`
30+
border-radius: ${theme.radii.medium}px;
31+
border: 1px solid ${color};
32+
width: ${theme.sizes[8]}px;
33+
height: ${theme.sizes[8]}px;
34+
`}
35+
`;
36+
2537
export const Chat: React.FC = () => {
2638
const [value, setValue] = useState('');
27-
const [height, setHeight] = useState<number>(null);
2839
const { state, actions } = useOvermind();
2940
const messagesRef = useRef(null);
3041
const scrollDown = () => {
@@ -35,7 +46,7 @@ export const Chat: React.FC = () => {
3546
useEffect(scrollDown);
3647

3748
const handleKeyDown = (e: React.KeyboardEvent) => {
38-
if (e.keyCode === ENTER && !e.shiftKey) {
49+
if (e.keyCode === ENTER && !e.shiftKey && value.trim() !== '') {
3950
e.preventDefault();
4051
e.stopPropagation();
4152
// Enter
@@ -54,76 +65,96 @@ export const Chat: React.FC = () => {
5465
};
5566

5667
const { messages, users } = state.live.roomInfo.chat;
57-
const currentUserId = state.live.liveUserId;
5868
const roomInfoUsers = state.live.roomInfo.users;
5969

70+
const orderedMessages: (m: any) => any[] = m =>
71+
sortBy(takeRight(m, 100), 'date');
72+
73+
const messageData = message => {
74+
const metadata = roomInfoUsers.find(u => u.id === message.userId);
75+
return {
76+
metadata,
77+
color: metadata
78+
? `rgb(${metadata.color[0]}, ${metadata.color[1]}, ${metadata.color[2]})`
79+
: '#636363',
80+
name: users[message.userId],
81+
};
82+
};
83+
84+
const isNotSameUser = (message, i) =>
85+
i === 0 || messages[i - 1].userId !== message.userId;
86+
87+
const isLight = theme => theme.vscodeTheme.type === 'light';
88+
6089
return (
61-
<Container ref={messagesRef}>
62-
<Messages>
63-
{messages.length > 0 ? (
64-
sortBy(takeRight(messages, 100), 'date').map((message, i) => {
65-
const metadata = roomInfoUsers.find(u => u.id === message.userId);
66-
const color = metadata
67-
? `rgb(${metadata.color[0]}, ${metadata.color[1]}, ${metadata.color[2]})`
68-
: '#636363';
69-
const name = users[message.userId];
70-
return (
71-
<div key={message.date}>
72-
{(i === 0 || messages[i - 1].userId !== message.userId) && (
73-
<div
90+
<Collapsible
91+
css={css(theme => ({
92+
borderTop: '1px solid',
93+
borderColor: 'sideBar.border',
94+
boxShadow: isLight(theme)
95+
? '0px -8px 8px rgba(255,255,255,0.24), 0px -4px 8px rgba(255,255,255,0.4)'
96+
: '0px -8px 8px rgba(0,0,0,0.24), 0px -4px 8px rgba(0,0,0,0.4)',
97+
}))}
98+
defaultOpen
99+
title="Chat"
100+
>
101+
<Container direction="vertical" ref={messagesRef}>
102+
<Messages>
103+
{messages.length > 0 ? (
104+
orderedMessages(messages).map((message, i) => {
105+
const { color, name, metadata } = messageData(message);
106+
return (
107+
<Element key={message.date}>
108+
{isNotSameUser(message, i) && (
109+
<Stack
110+
paddingTop={2}
111+
marginBottom={2}
112+
align="center"
113+
gap={2}
114+
>
115+
<Avatar
116+
color={color}
117+
alt={metadata.username}
118+
src={metadata.avatarUrl}
119+
/>
120+
<Text block weight="bold">
121+
{name}
122+
</Text>
123+
</Stack>
124+
)}
125+
<Text
126+
block
74127
style={{
75-
color,
76-
fontWeight: 600,
77-
marginBottom: '0.25rem',
78-
marginTop: '0.5rem',
128+
wordBreak: 'break-word',
79129
}}
130+
marginBottom={2}
80131
>
81-
{name}
82-
{currentUserId === message.userId && ' (you)'}
83-
{!metadata && ' (left)'}
84-
</div>
85-
)}
86-
<div
87-
style={{
88-
color: 'rgba(255, 255, 255, 0.7)',
89-
fontWeight: 400,
90-
marginBottom: '.25rem',
91-
}}
92-
>
93-
{message.message.split('\n').map(m => (
94-
<span key={m}>
95-
{m}
96-
<br />
97-
</span>
98-
))}
99-
</div>
100-
</div>
101-
);
102-
})
103-
) : (
104-
<div
105-
style={{
106-
fontStyle: 'italic',
107-
color: 'rgba(255, 255, 255, 0.5)',
108-
}}
109-
>
110-
No messages, start sending some!
111-
</div>
112-
)}
113-
</Messages>
114-
<AutosizeTextArea
115-
useCacheForDOMMeasurements
116-
value={value}
117-
onChange={handleChange}
118-
placeholder="Send a message..."
119-
style={{
120-
width: '100%',
121-
minHeight: height,
122-
marginTop: '0.5rem',
123-
}}
124-
onKeyDown={handleKeyDown}
125-
onHeightChange={setHeight}
126-
/>
127-
</Container>
132+
{message.message.split('\n').map(m => (
133+
<span key={m}>
134+
{m}
135+
<br />
136+
</span>
137+
))}
138+
</Text>
139+
</Element>
140+
);
141+
})
142+
) : (
143+
<Text variant="muted">No messages, start sending some!</Text>
144+
)}
145+
</Messages>
146+
<Element marginTop={4}>
147+
<Textarea
148+
autosize
149+
style={{ height: 'auto', minHeight: 'auto' }}
150+
rows={1}
151+
value={value}
152+
onChange={handleChange}
153+
placeholder="Send a message..."
154+
onKeyDown={handleKeyDown}
155+
/>
156+
</Element>
157+
</Container>
158+
</Collapsible>
128159
);
129160
};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import styled from 'styled-components';
3+
import { sortBy, takeRight } from 'lodash-es';
4+
5+
import AutosizeTextArea from '@codesandbox/common/lib/components/AutosizeTextArea';
6+
import { ENTER } from '@codesandbox/common/lib/utils/keycodes';
7+
import { useOvermind } from 'app/overmind';
8+
9+
const Container = styled.div`
10+
min-height: 200px;
11+
max-height: 300px;
12+
padding: 0 1rem;
13+
color: white;
14+
font-size: 0.875rem;
15+
display: flex;
16+
flex-direction: column;
17+
overflow-y: auto;
18+
`;
19+
20+
const Messages = styled.div`
21+
height: 100%;
22+
flex: 1;
23+
`;
24+
25+
export const Chat: React.FC = () => {
26+
const [value, setValue] = useState('');
27+
const [height, setHeight] = useState<number>(null);
28+
const { state, actions } = useOvermind();
29+
const messagesRef = useRef(null);
30+
const scrollDown = () => {
31+
if (messagesRef.current) {
32+
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
33+
}
34+
};
35+
useEffect(scrollDown);
36+
37+
const handleKeyDown = (e: React.KeyboardEvent) => {
38+
if (e.keyCode === ENTER && !e.shiftKey) {
39+
e.preventDefault();
40+
e.stopPropagation();
41+
// Enter
42+
actions.live.onSendChat({
43+
message: value,
44+
});
45+
setValue('');
46+
scrollDown();
47+
}
48+
};
49+
50+
const handleChange = (
51+
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
52+
) => {
53+
setValue(e.target.value);
54+
};
55+
56+
const { messages, users } = state.live.roomInfo.chat;
57+
const currentUserId = state.live.liveUserId;
58+
const roomInfoUsers = state.live.roomInfo.users;
59+
60+
return (
61+
<Container ref={messagesRef}>
62+
<Messages>
63+
{messages.length > 0 ? (
64+
sortBy(takeRight(messages, 100), 'date').map((message, i) => {
65+
const metadata = roomInfoUsers.find(u => u.id === message.userId);
66+
const color = metadata
67+
? `rgb(${metadata.color[0]}, ${metadata.color[1]}, ${metadata.color[2]})`
68+
: '#636363';
69+
const name = users[message.userId];
70+
return (
71+
<div key={message.date}>
72+
{(i === 0 || messages[i - 1].userId !== message.userId) && (
73+
<div
74+
style={{
75+
color,
76+
fontWeight: 600,
77+
marginBottom: '0.25rem',
78+
marginTop: '0.5rem',
79+
}}
80+
>
81+
{name}
82+
{currentUserId === message.userId && ' (you)'}
83+
{!metadata && ' (left)'}
84+
</div>
85+
)}
86+
<div
87+
style={{
88+
color: 'rgba(255, 255, 255, 0.7)',
89+
fontWeight: 400,
90+
marginBottom: '.25rem',
91+
}}
92+
>
93+
{message.message.split('\n').map(m => (
94+
<span key={m}>
95+
{m}
96+
<br />
97+
</span>
98+
))}
99+
</div>
100+
</div>
101+
);
102+
})
103+
) : (
104+
<div
105+
style={{
106+
fontStyle: 'italic',
107+
color: 'rgba(255, 255, 255, 0.5)',
108+
}}
109+
>
110+
No messages, start sending some!
111+
</div>
112+
)}
113+
</Messages>
114+
<AutosizeTextArea
115+
useCacheForDOMMeasurements
116+
value={value}
117+
onChange={handleChange}
118+
placeholder="Send a message..."
119+
style={{
120+
width: '100%',
121+
minHeight: height,
122+
marginTop: '0.5rem',
123+
}}
124+
onKeyDown={handleKeyDown}
125+
onHeightChange={setHeight}
126+
/>
127+
</Container>
128+
);
129+
};

0 commit comments

Comments
 (0)