Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit 6c53400

Browse files
author
Alexandru Buliga
authored
docs(chat-pane): chat-pane prototype (#235)
* docs(chat-pane): add chat-pane prototype * feat(chat): add author and timestamp props * -addressing comments on the PR * -adding more variables to the chat and chat message components * -removed timestamp right margin style * -updated styles in the chat pane according to the new usages without the root element * -added author and timestamp to the chat pane messages example * -added menu in the chat pane header * -adjusting the header according to the styling guides * code update * fixed type errors * fixed conflicts * fixed build issue * fixed styling * -updating with the latest changes from master * updates: - sorted messages by date - added all types of dividers between messages in correct places - added correct timestamp and title attributes for timestamps * addressed comments * synced descriptions with the previous changelog
1 parent b581ce2 commit 6c53400

23 files changed

Lines changed: 657 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2727

2828
### Documentation
2929
- Improve `Contributing` documentation for accessibility @jurokapsiar ([#303](https://github.com/stardust-ui/react/pull/303))
30-
- Add theme switcher for exploring different themes on the docs (under development mode flag) @mnajdova ([#280](https://github.com/stardust-ui/react/pull/280))
30+
- Add theme switcher for exploring different themes on the docs (only available in development mode) @mnajdova ([#280](https://github.com/stardust-ui/react/pull/280))
31+
- Add `Prototypes` section and `Chat Pane` prototype (only available in development mode) @Bugaa92 ([#235](https://github.com/stardust-ui/react/pull/235))
3132

3233
<!--------------------------------[ v0.8.0 ]------------------------------- -->
3334
## [v0.8.0](https://github.com/stardust-ui/react/tree/v0.8.0) (2018-10-01)

docs/src/components/Sidebar/Sidebar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ class Sidebar extends React.Component<any, any> {
235235
</Menu.Item>
236236
</Menu.Menu>
237237
</Menu.Item>
238+
{process.env.NODE_ENV !== 'production' && (
239+
<Menu.Item>
240+
Prototypes
241+
<Menu.Menu>
242+
<Menu.Item as={NavLink} exact to="/prototype-chat-pane" activeClassName="active">
243+
Chat Pane
244+
</Menu.Item>
245+
</Menu.Menu>
246+
</Menu.Item>
247+
)}
238248
<Menu.Item active>
239249
<SemanticUIInput
240250
className="transparent inverted icon"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as React from 'react'
2+
import * as _ from 'lodash'
3+
import Scrollbars from 'react-custom-scrollbars'
4+
import { Chat, Divider } from '@stardust-ui/react'
5+
6+
import { IChat, ChatItemType, generateChatProps } from './services'
7+
8+
export interface IChatPaneContainerProps {
9+
chat: IChat
10+
}
11+
12+
class ChatPaneContainer extends React.PureComponent<IChatPaneContainerProps> {
13+
public render() {
14+
const items = this.generateChatItems(this.props.chat)
15+
16+
return (
17+
items.length > 0 && (
18+
<Scrollbars ref={this.handleScrollRef}>
19+
<Chat items={items} styles={{ padding: '0 32px' }} />
20+
</Scrollbars>
21+
)
22+
)
23+
}
24+
25+
private generateChatItems(chat: IChat): JSX.Element[] {
26+
return generateChatProps(chat).map(({ itemType, ...props }, index) => {
27+
const ElementType = this.getElementType(itemType)
28+
return (
29+
<Chat.Item key={`chat-item-${index}`}>
30+
<ElementType {...props} />
31+
</Chat.Item>
32+
)
33+
})
34+
}
35+
36+
private getElementType = (itemType: ChatItemType) => {
37+
switch (itemType) {
38+
case ChatItemType.message:
39+
return Chat.Message
40+
case ChatItemType.divider:
41+
return Divider
42+
}
43+
}
44+
45+
private handleScrollRef(scrollRef: Scrollbars) {
46+
if (scrollRef) {
47+
scrollRef.scrollToBottom()
48+
}
49+
}
50+
}
51+
52+
export default ChatPaneContainer
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import * as React from 'react'
2+
import { Avatar, Button, Divider, Icon, Layout, Segment, Text } from '@stardust-ui/react'
3+
4+
import { pxToRem } from 'src/lib'
5+
import { IChat } from './services'
6+
import { Sizes } from 'src/lib/enums'
7+
8+
export interface IChatPaneHeaderProps {
9+
chat?: IChat
10+
}
11+
12+
class ChatPaneHeader extends React.PureComponent<IChatPaneHeaderProps> {
13+
public render() {
14+
return (
15+
<Layout
16+
vertical
17+
start={this.renderBanner()}
18+
main={this.renderMainArea()}
19+
end={<Divider size={2} styles={{ padding: '0 32px' }} />}
20+
/>
21+
)
22+
}
23+
24+
private renderBanner(): React.ReactNode {
25+
return (
26+
<Segment
27+
content={
28+
<Icon
29+
size="big"
30+
name="team-create"
31+
variables={siteVars => ({ color: siteVars.white, margin: 'auto 8px' })}
32+
/>
33+
}
34+
styles={({ variables: v }) => ({
35+
backgroundColor: v.backgroundColor,
36+
borderRadius: 0,
37+
display: 'flex',
38+
height: '40px',
39+
padding: 0,
40+
})}
41+
variables={siteVars => ({ backgroundColor: siteVars.brand })}
42+
/>
43+
)
44+
}
45+
46+
private renderMainArea(): React.ReactNode {
47+
const { chat } = this.props
48+
49+
return (
50+
<Layout
51+
start={<Avatar name={chat.title} />}
52+
main={
53+
<Text
54+
size={Sizes.Large}
55+
content={chat.title}
56+
styles={{ marginLeft: '12px', fontWeight: 600 }}
57+
/>
58+
}
59+
end={this.renderHeaderButtons()}
60+
alignItems="center"
61+
styles={{ padding: '16px 32px' }}
62+
/>
63+
)
64+
}
65+
66+
private renderHeaderButtons(): React.ReactNode {
67+
return (
68+
<div style={{ display: 'inline-flex' }}>
69+
<Button.Group
70+
circular
71+
buttons={['call-video', 'call'].map((name, index) => ({
72+
key: `${index}-${name}`,
73+
icon: {
74+
name,
75+
size: 'big',
76+
variables: siteVars => ({ color: siteVars.white, margin: 'auto 8px' }),
77+
},
78+
type: 'primary',
79+
}))}
80+
styles={{ marginRight: '20px' }}
81+
/>
82+
{['user plus', 'ellipsis horizontal'].map((name, index) => (
83+
<Icon
84+
key={`${index}-${name}`}
85+
name={name}
86+
tabIndex={0}
87+
styles={{
88+
fontWeight: 100,
89+
...(!index && { marginRight: '1.6rem' }),
90+
marginTop: pxToRem(8),
91+
}}
92+
variables={siteVars => ({ color: siteVars.gray04 })}
93+
/>
94+
))}
95+
</div>
96+
)
97+
}
98+
}
99+
100+
export default ChatPaneHeader
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react'
2+
import { Layout } from '@stardust-ui/react'
3+
import { IChat } from './services'
4+
5+
import ChatPaneHeader from './chatPaneHeader'
6+
import ChatPaneContainer from './chatPaneContent'
7+
import ComposeMessage from './composeMessage'
8+
9+
export interface IChatPaneLayoutProps {
10+
chat: IChat
11+
}
12+
13+
const ChatPaneLayout: React.SFC<IChatPaneLayoutProps> = ({ chat }: IChatPaneLayoutProps) => (
14+
<Layout
15+
vertical
16+
start={<ChatPaneHeader chat={chat} />}
17+
main={<ChatPaneContainer chat={chat} />}
18+
end={<ComposeMessage />}
19+
styles={{
20+
backgroundColor: '#f3f2f1',
21+
left: 0,
22+
paddingLeft: '250px',
23+
width: '100%',
24+
height: '100%',
25+
position: 'fixed',
26+
}}
27+
/>
28+
)
29+
30+
export default ChatPaneLayout
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as React from 'react'
2+
import { Layout, Input, ToolbarButtonBehavior, ToolbarBehavior, Menu } from '@stardust-ui/react'
3+
import { IMenuItemProps } from 'src/components/Menu/MenuItem'
4+
5+
type ToolbarProps = IMenuItemProps & { key: string; 'aria-label'?: string }
6+
7+
class ComposeMessage extends React.Component {
8+
public render() {
9+
return (
10+
<Layout
11+
vertical
12+
start={this.renderInput()}
13+
main={this.renderToolbar()}
14+
styles={{ padding: '16px 32px' }}
15+
/>
16+
)
17+
}
18+
19+
private renderInput(): React.ReactNode {
20+
return (
21+
<Input
22+
fluid
23+
placeholder="Type a message"
24+
variables={siteVars => ({ backgroundColor: siteVars.white })}
25+
/>
26+
)
27+
}
28+
29+
private renderToolbar(): React.ReactNode {
30+
const items: (ToolbarProps | JSX.Element)[] = [
31+
'compose',
32+
'attach',
33+
'smile',
34+
'picture',
35+
'smile outline',
36+
'calendar alternate',
37+
'ellipsis horizontal',
38+
'send',
39+
].map((icon, index) => this.getMenuItem(icon, index))
40+
41+
items.splice(-1, 0, { key: 'separator', styles: { flex: 1 } })
42+
43+
return (
44+
<Menu
45+
defaultActiveIndex={0}
46+
items={items}
47+
iconOnly
48+
accessibility={ToolbarBehavior}
49+
aria-label="Compose Editor"
50+
styles={{ marginTop: '10px' }}
51+
/>
52+
)
53+
}
54+
55+
private getMenuItem(
56+
name: string,
57+
index: number,
58+
): IMenuItemProps & { key: string; 'aria-label': string } {
59+
return {
60+
key: `${index}-${name}`,
61+
icon: {
62+
name,
63+
xSpacing: 'both',
64+
variables: siteVars => ({ color: siteVars.gray02 }),
65+
},
66+
accessibility: ToolbarButtonBehavior,
67+
'aria-label': `${name} tool`,
68+
}
69+
}
70+
}
71+
72+
export default ComposeMessage
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from 'react'
2+
import { Provider } from '@stardust-ui/react'
3+
4+
import ChatPaneLayout from './chatPaneLayout'
5+
import { getChatMock } from './services'
6+
7+
const chatMock = getChatMock({ msgCount: 40, userCount: 6 })
8+
9+
export default () => (
10+
<Provider
11+
theme={{
12+
componentStyles: {
13+
Layout: {
14+
start: { display: 'block' },
15+
end: { display: 'block' },
16+
},
17+
},
18+
}}
19+
>
20+
<ChatPaneLayout chat={chatMock.chat} />
21+
</Provider>
22+
)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as _ from 'lodash'
2+
import { lorem, random, name, internet } from 'faker'
3+
import { IMessage, IUser, IChat, UserStatus, getTimestamp, getRandomDates } from '.'
4+
5+
export interface ChatOptions {
6+
userCount?: number
7+
msgCount?: number
8+
}
9+
10+
const userStatuses: UserStatus[] = ['Available', 'Away', 'DoNotDisturb', 'Offline']
11+
12+
class ChatMock {
13+
private static readonly minUserCount = 2
14+
private static readonly defaultCount = 10
15+
private static readonly daysAgo = 40
16+
private static readonly defaultChatTitle = 'Test Chat'
17+
18+
private userIds: string[] = []
19+
private usersMap: Map<string, IUser> = new Map()
20+
private chatMessages: IMessage[] = []
21+
public chat: IChat
22+
23+
constructor(
24+
private options: ChatOptions = {
25+
userCount: ChatMock.defaultCount,
26+
msgCount: ChatMock.defaultCount,
27+
},
28+
) {
29+
if (this.options.userCount < ChatMock.minUserCount) {
30+
throw `[ChatMock]: A chat has a minimum of ${ChatMock.minUserCount} users`
31+
}
32+
33+
this.userIds = _.times(this.options.userCount, random.uuid)
34+
35+
this.userIds.forEach(id => {
36+
const firstName = name.firstName()
37+
const lastName = name.lastName()
38+
const user: IUser = {
39+
id,
40+
firstName,
41+
lastName,
42+
avatar: internet.avatar(),
43+
displayName: internet.userName(firstName, lastName),
44+
email: internet.email(firstName, lastName),
45+
status: userStatuses[_.random(userStatuses.length - 1)],
46+
}
47+
48+
this.usersMap.set(id, user)
49+
})
50+
51+
const currentUser = this.usersMap.get(this.userIds[0])
52+
const dates = getRandomDates(this.options.msgCount, ChatMock.daysAgo)
53+
54+
this.chatMessages = _.times(this.options.msgCount, id => {
55+
const mine = random.boolean()
56+
const from = (mine ? currentUser : this.getRandomUser()).id
57+
const date = dates[id]
58+
const timestamp = getTimestamp(date)
59+
60+
const message: IMessage = {
61+
id,
62+
from,
63+
mine,
64+
date,
65+
content: lorem.sentences(_.random(1, 5, false)),
66+
timestamp: timestamp.short,
67+
timestampLong: timestamp.long,
68+
isImportant: random.boolean(),
69+
}
70+
71+
return message
72+
}).sort((a, b) => a.date - b.date)
73+
74+
this.chat = {
75+
id: random.uuid(),
76+
currentUser,
77+
isOneOnOne: this.usersMap.size === ChatMock.minUserCount,
78+
messages: this.chatMessages,
79+
members: this.usersMap,
80+
title: ChatMock.defaultChatTitle,
81+
}
82+
}
83+
84+
private getRandomUser(max: number = this.usersMap.size - 1): IUser {
85+
return this.usersMap.get(this.userIds[random.number({ max, precision: 1 })])
86+
}
87+
}
88+
89+
export const getChatMock = (options?: ChatOptions) => new ChatMock(options)

0 commit comments

Comments
 (0)