Skip to content

Commit 6729c4d

Browse files
authored
Typing notification improvements (#232)
* Improve typing message * lint * fix space * tweaks
1 parent 514a21b commit 6729c4d

File tree

3 files changed

+134
-22
lines changed

3 files changed

+134
-22
lines changed

packages/jupyter-chat/src/components/messages/messages.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ChatMessageHeader } from './header';
1414
import { ChatMessage } from './message';
1515
import { Navigation } from './navigation';
1616
import { WelcomeMessage } from './welcome';
17-
import { Writers } from './writers';
17+
import { WritingUsersList } from './writers';
1818
import { IInputToolbarRegistry } from '../input';
1919
import { ScrollContainer } from '../scroll-container';
2020
import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
@@ -210,8 +210,8 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
210210
);
211211
})}
212212
</Box>
213-
<Writers writers={currentWriters}></Writers>
214213
</ScrollContainer>
214+
<WritingUsersList writers={currentWriters}></WritingUsersList>
215215
<Navigation {...props} refMsgBox={refMsgBox} allRendered={allRendered} />
216216
</>
217217
);

packages/jupyter-chat/src/components/messages/writers.tsx

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Distributed under the terms of the Modified BSD License.
44
*/
55

6-
import { Box } from '@mui/material';
7-
import React from 'react';
6+
import { Box, Typography } from '@mui/material';
7+
import React, { useMemo } from 'react';
88

99
import { Avatar } from '../avatar';
1010
import { IUser } from '../../types';
@@ -21,31 +21,61 @@ type writersProps = {
2121
writers: IUser[];
2222
};
2323

24+
/**
25+
* Animated typing indicator component
26+
*/
27+
const TypingIndicator = (): JSX.Element => (
28+
<Box className="jp-chat-typing-indicator">
29+
<span className="jp-chat-typing-dot"></span>
30+
<span className="jp-chat-typing-dot"></span>
31+
<span className="jp-chat-typing-dot"></span>
32+
</Box>
33+
);
34+
2435
/**
2536
* The writers component, displaying the current writers.
2637
*/
27-
export function Writers(props: writersProps): JSX.Element | null {
38+
export function WritingUsersList(props: writersProps): JSX.Element | null {
2839
const { writers } = props;
29-
return writers.length > 0 ? (
30-
<Box className={WRITERS_CLASS}>
31-
{writers.map((writer, index) => (
32-
<div>
40+
41+
// Don't render if no writers
42+
if (writers.length === 0) {
43+
return null;
44+
}
45+
46+
const writersText = writers.length > 1 ? ' are writing' : ' is writing';
47+
48+
const writingUsers: JSX.Element[] = useMemo(
49+
() =>
50+
writers.map((writer, index) => (
51+
<Box key={writer.username || index} className="jp-chat-writer-item">
3352
<Avatar user={writer} small />
34-
<span>
53+
<Typography variant="body2" className="jp-chat-writer-name">
3554
{writer.display_name ??
3655
writer.name ??
3756
(writer.username || 'User undefined')}
38-
</span>
39-
<span>
40-
{index < writers.length - 1
41-
? index < writers.length - 2
42-
? ', '
43-
: ' and '
44-
: ''}
45-
</span>
46-
</div>
47-
))}
48-
<span>{(writers.length > 1 ? ' are' : ' is') + ' writing'}</span>
57+
</Typography>
58+
{index < writers.length - 1 && (
59+
<Typography variant="body2" className="jp-chat-writer-separator">
60+
{index < writers.length - 2 ? ', ' : ' and '}
61+
</Typography>
62+
)}
63+
</Box>
64+
)),
65+
[writers]
66+
);
67+
68+
return (
69+
<Box className={`${WRITERS_CLASS}`}>
70+
<Box className="jp-chat-writers-content">
71+
{writingUsers}
72+
<Box className="jp-chat-writing-status">
73+
<Typography variant="body2" className="jp-chat-writing-text">
74+
{writersText}
75+
</Typography>
76+
<TypingIndicator />
77+
</Box>
78+
</Box>
4979
</Box>
50-
) : null;
80+
);
5181
}

packages/jupyter-chat/style/chat.css

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,88 @@
7878
.jp-chat-writers {
7979
display: flex;
8080
flex-wrap: wrap;
81+
position: sticky;
82+
bottom: 0;
83+
padding: 8px;
84+
background-color: var(--jp-layout-color0);
85+
border-top: 1px solid var(--jp-border-color2);
86+
z-index: 1;
87+
}
88+
89+
.jp-chat-writers-content {
90+
display: flex;
91+
align-items: center;
92+
gap: 4px;
93+
flex-wrap: wrap;
94+
}
95+
96+
.jp-chat-writer-item {
97+
display: flex;
98+
align-items: center;
99+
gap: 6px;
100+
}
101+
102+
.jp-chat-writer-name {
103+
color: var(--jp-ui-font-color1);
104+
font-weight: 500;
105+
}
106+
107+
.jp-chat-writer-separator {
108+
color: var(--jp-ui-font-color2);
109+
}
110+
111+
.jp-chat-writing-status {
112+
display: flex;
113+
align-items: center;
114+
gap: 8px;
115+
}
116+
117+
.jp-chat-writing-text {
118+
color: var(--jp-ui-font-color2);
119+
}
120+
121+
/* Animated typing indicator */
122+
.jp-chat-typing-indicator {
123+
display: flex;
124+
align-items: center;
125+
gap: 2px;
126+
padding: 2px 4px;
127+
}
128+
129+
.jp-chat-typing-dot {
130+
width: 4px;
131+
height: 4px;
132+
border-radius: 50%;
133+
background-color: var(--jp-brand-color1);
134+
animation: jp-chat-typing-bounce 1.4s infinite ease-in-out;
135+
}
136+
137+
.jp-chat-typing-dot:nth-child(1) {
138+
animation-delay: -0.32s;
139+
}
140+
141+
.jp-chat-typing-dot:nth-child(2) {
142+
animation-delay: -0.16s;
143+
}
144+
145+
.jp-chat-typing-dot:nth-child(3) {
146+
animation-delay: 0s;
147+
}
148+
149+
/* Keyframe animations */
150+
151+
@keyframes jp-chat-typing-bounce {
152+
0%,
153+
80%,
154+
100% {
155+
transform: scale(0.8);
156+
opacity: 0.5;
157+
}
158+
159+
40% {
160+
transform: scale(1.2);
161+
opacity: 1;
162+
}
81163
}
82164

83165
.jp-chat-writers > div {

0 commit comments

Comments
 (0)