@@ -7,9 +7,14 @@ import { Box, Typography } from '@mui/material';
77import React , { useMemo } from 'react' ;
88
99import { Avatar } from '../avatar' ;
10+ import { IChatModel } from '../../model' ;
11+ import { ISignal , Signal } from '@lumino/signaling' ;
1012import { IUser } from '../../types' ;
1113
1214const WRITERS_CLASS = 'jp-chat-writers' ;
15+ const WRITERS_ROW_CLASS = 'jp-chat-writers-row' ;
16+
17+ const DEFAULT_TEXT = 'is writing' ;
1318
1419/**
1520 * The writers component props.
@@ -18,7 +23,11 @@ type writersProps = {
1823 /**
1924 * The list of users currently writing.
2025 */
21- writers : IUser [ ] ;
26+ writers : IChatModel . IWriter [ ] ;
27+ /**
28+ * The component to render next to the writers.
29+ */
30+ writerComponent ?: WriterComponent ;
2231} ;
2332
2433/**
@@ -36,14 +45,53 @@ const TypingIndicator = (): JSX.Element => (
3645 * The writers component, displaying the current writers.
3746 */
3847export function WritingUsersList ( props : writersProps ) : JSX . Element | null {
48+ const { writers, writerComponent } = props ;
49+
50+ // Don't render if no writers
51+ if ( writers . length === 0 ) {
52+ return null ;
53+ }
54+
55+ // Default rendering for users without custom typing indicator and if there is no
56+ // component to add to the writing notification.
57+ const defaultWriters = writerComponent ?. component
58+ ? [ ]
59+ : writers . filter ( writer => ! writer . typingIndicator ) ;
60+ const defaultWritersComponent = defaultWritingUsers ( {
61+ writers : defaultWriters . map ( writer => writer . user )
62+ } ) ;
63+
64+ // Custom rendering for users with custom typing indicator or if there is a component
65+ // to add to the writing notification.
66+ const customWriters = writerComponent ?. component
67+ ? writers
68+ : writers . filter ( writer => writer . typingIndicator ) ;
69+ const customWritersComponent = customWritingUser ( {
70+ writers : customWriters ,
71+ writerComponent : writerComponent ?. component
72+ } ) ;
73+
74+ return (
75+ < Box className = { WRITERS_CLASS } >
76+ { defaultWritersComponent !== null && defaultWritersComponent }
77+ { customWritersComponent !== null && customWritersComponent }
78+ </ Box >
79+ ) ;
80+ }
81+
82+ /**
83+ * The default rendering of writing users, all in a row.
84+ * This renderer is used if there is no custom component and no custom typing indicator.
85+ */
86+ function defaultWritingUsers ( props : { writers : IUser [ ] } ) : JSX . Element | null {
3987 const { writers } = props ;
4088
4189 // Don't render if no writers
4290 if ( writers . length === 0 ) {
4391 return null ;
4492 }
4593
46- const writersText = writers . length > 1 ? ' are writing' : ' is writing' ;
94+ const writersText = writers . length > 1 ? 'are writing' : DEFAULT_TEXT ;
4795
4896 const writingUsers : JSX . Element [ ] = useMemo (
4997 ( ) =>
@@ -66,16 +114,100 @@ export function WritingUsersList(props: writersProps): JSX.Element | null {
66114 ) ;
67115
68116 return (
69- < Box className = { `${ WRITERS_CLASS } ` } >
117+ < Box className = { `${ WRITERS_ROW_CLASS } ` } >
70118 < Box className = "jp-chat-writers-content" >
71119 { writingUsers }
72120 < Box className = "jp-chat-writing-status" >
73121 < Typography variant = "body2" className = "jp-chat-writing-text" >
74- { writersText }
122+ { ` ${ writersText } ` }
75123 </ Typography >
76124 < TypingIndicator />
77125 </ Box >
78126 </ Box >
79127 </ Box >
80128 ) ;
81129}
130+
131+ /**
132+ * The custom rendering of writing users, one per row.
133+ * This renderer is used if there is a custom component or a custom typing indicator.
134+ */
135+ function customWritingUser ( props : {
136+ writers : IChatModel . IWriter [ ] ;
137+ writerComponent ?: React . FC < WriterComponentProps > ;
138+ } ) : JSX . Element | null {
139+ const { writers } = props ;
140+
141+ // Don't render if no writers
142+ if ( writers . length === 0 ) {
143+ return null ;
144+ }
145+
146+ const writingUsers : JSX . Element [ ] = writers . map ( writer => {
147+ const username =
148+ writer . user . display_name ??
149+ writer . user . name ??
150+ ( writer . user . username || 'User undefined' ) ;
151+
152+ const writerText = writer . typingIndicator ?? DEFAULT_TEXT ;
153+ return (
154+ < Box key = { writer . user . username } className = "jp-chat-writer-item" >
155+ < Avatar user = { writer . user } small />
156+ < Typography variant = "body2" className = "jp-chat-writer-name" >
157+ { username }
158+ </ Typography >
159+ < Box className = "jp-chat-writing-status" >
160+ < Typography variant = "body2" className = "jp-chat-writing-text" >
161+ { ` ${ writerText } ` }
162+ </ Typography >
163+ < TypingIndicator />
164+ </ Box >
165+ { props . writerComponent && < props . writerComponent writer = { writer } /> }
166+ </ Box >
167+ ) ;
168+ } ) ;
169+
170+ return (
171+ < >
172+ { writingUsers . map ( writingUser => (
173+ < Box className = { `${ WRITERS_ROW_CLASS } ` } > { writingUser } </ Box >
174+ ) ) }
175+ </ >
176+ ) ;
177+ }
178+
179+ export type WriterComponentProps = {
180+ /**
181+ * The writer associated to this component.
182+ */
183+ writer : IChatModel . IWriter ;
184+ } ;
185+
186+ export class WriterComponent {
187+ /**
188+ * The react component.
189+ */
190+ get component ( ) : React . FC < WriterComponentProps > | undefined {
191+ return this . _component ;
192+ }
193+ set component ( value : React . FC < WriterComponentProps > | undefined ) {
194+ this . _component = value ;
195+ this . _changed . emit ( this . _component ) ;
196+ }
197+
198+ /**
199+ * Emitting when the component changed.
200+ */
201+ get changed ( ) : ISignal <
202+ WriterComponent ,
203+ React . FC < WriterComponentProps > | undefined
204+ > {
205+ return this . _changed ;
206+ }
207+
208+ private _component : React . FC < WriterComponentProps > | undefined ;
209+ private _changed = new Signal <
210+ WriterComponent ,
211+ React . FC < WriterComponentProps > | undefined
212+ > ( this ) ;
213+ }
0 commit comments