@webinex/chatify 1.0.0-alpha00

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/cjs/index.js +2 -0
  2. package/dist/cjs/index.js.map +1 -0
  3. package/dist/cjs/types/ChatifyContext.d.ts +11 -0
  4. package/dist/cjs/types/core/action.d.ts +73 -0
  5. package/dist/cjs/types/core/client.d.ts +35 -0
  6. package/dist/cjs/types/core/index.d.ts +14 -0
  7. package/dist/cjs/types/core/models.d.ts +64 -0
  8. package/dist/cjs/types/core/reducer.d.ts +11 -0
  9. package/dist/cjs/types/core/state.d.ts +25 -0
  10. package/dist/cjs/types/core/useAccounts.d.ts +1 -0
  11. package/dist/cjs/types/core/useAddChat.d.ts +2 -0
  12. package/dist/cjs/types/core/useAddMember.d.ts +2 -0
  13. package/dist/cjs/types/core/useAddMessage.d.ts +5 -0
  14. package/dist/cjs/types/core/useChat.d.ts +3 -0
  15. package/dist/cjs/types/core/useChatList.d.ts +1 -0
  16. package/dist/cjs/types/core/useLoadMore.d.ts +3 -0
  17. package/dist/cjs/types/core/useMessages.d.ts +15 -0
  18. package/dist/cjs/types/core/useMutation.d.ts +12 -0
  19. package/dist/cjs/types/core/useQuery.d.ts +7 -0
  20. package/dist/cjs/types/core/useReadMonitor.d.ts +1 -0
  21. package/dist/cjs/types/core/useRemoveMember.d.ts +2 -0
  22. package/dist/cjs/types/core/useSignalR.d.ts +1 -0
  23. package/dist/cjs/types/index.d.ts +3 -0
  24. package/dist/cjs/types/ui/AddChat.d.ts +1 -0
  25. package/dist/cjs/types/ui/Aside.d.ts +1 -0
  26. package/dist/cjs/types/ui/Chat.d.ts +4 -0
  27. package/dist/cjs/types/ui/ChatBody.d.ts +4 -0
  28. package/dist/cjs/types/ui/ChatHeader.d.ts +4 -0
  29. package/dist/cjs/types/ui/ChatSettings.d.ts +1 -0
  30. package/dist/cjs/types/ui/Chatify.d.ts +8 -0
  31. package/dist/cjs/types/ui/InputForm.d.ts +1 -0
  32. package/dist/cjs/types/ui/LoadMore.d.ts +4 -0
  33. package/dist/cjs/types/ui/Main.d.ts +1 -0
  34. package/dist/cjs/types/ui/Message.d.ts +5 -0
  35. package/dist/cjs/types/ui/MessageMd.d.ts +13 -0
  36. package/dist/cjs/types/ui/MessageSkeleton.d.ts +4 -0
  37. package/dist/cjs/types/ui/SendingMessage.d.ts +6 -0
  38. package/dist/cjs/types/ui/SystemMessage.d.ts +8 -0
  39. package/dist/cjs/types/ui/index.d.ts +2 -0
  40. package/dist/cjs/types/ui/localizer.d.ts +28 -0
  41. package/dist/cjs/types/util/index.d.ts +2 -0
  42. package/dist/cjs/types/util/uniqBy.d.ts +1 -0
  43. package/dist/cjs/types/util/uniqId.d.ts +1 -0
  44. package/dist/css/chatify.css +328 -0
  45. package/dist/esm/index.js +2 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/types/ChatifyContext.d.ts +11 -0
  48. package/dist/esm/types/core/action.d.ts +73 -0
  49. package/dist/esm/types/core/client.d.ts +35 -0
  50. package/dist/esm/types/core/index.d.ts +14 -0
  51. package/dist/esm/types/core/models.d.ts +64 -0
  52. package/dist/esm/types/core/reducer.d.ts +11 -0
  53. package/dist/esm/types/core/state.d.ts +25 -0
  54. package/dist/esm/types/core/useAccounts.d.ts +1 -0
  55. package/dist/esm/types/core/useAddChat.d.ts +2 -0
  56. package/dist/esm/types/core/useAddMember.d.ts +2 -0
  57. package/dist/esm/types/core/useAddMessage.d.ts +5 -0
  58. package/dist/esm/types/core/useChat.d.ts +3 -0
  59. package/dist/esm/types/core/useChatList.d.ts +1 -0
  60. package/dist/esm/types/core/useLoadMore.d.ts +3 -0
  61. package/dist/esm/types/core/useMessages.d.ts +15 -0
  62. package/dist/esm/types/core/useMutation.d.ts +12 -0
  63. package/dist/esm/types/core/useQuery.d.ts +7 -0
  64. package/dist/esm/types/core/useReadMonitor.d.ts +1 -0
  65. package/dist/esm/types/core/useRemoveMember.d.ts +2 -0
  66. package/dist/esm/types/core/useSignalR.d.ts +1 -0
  67. package/dist/esm/types/index.d.ts +3 -0
  68. package/dist/esm/types/ui/AddChat.d.ts +1 -0
  69. package/dist/esm/types/ui/Aside.d.ts +1 -0
  70. package/dist/esm/types/ui/Chat.d.ts +4 -0
  71. package/dist/esm/types/ui/ChatBody.d.ts +4 -0
  72. package/dist/esm/types/ui/ChatHeader.d.ts +4 -0
  73. package/dist/esm/types/ui/ChatSettings.d.ts +1 -0
  74. package/dist/esm/types/ui/Chatify.d.ts +8 -0
  75. package/dist/esm/types/ui/InputForm.d.ts +1 -0
  76. package/dist/esm/types/ui/LoadMore.d.ts +4 -0
  77. package/dist/esm/types/ui/Main.d.ts +1 -0
  78. package/dist/esm/types/ui/Message.d.ts +5 -0
  79. package/dist/esm/types/ui/MessageMd.d.ts +13 -0
  80. package/dist/esm/types/ui/MessageSkeleton.d.ts +4 -0
  81. package/dist/esm/types/ui/SendingMessage.d.ts +6 -0
  82. package/dist/esm/types/ui/SystemMessage.d.ts +8 -0
  83. package/dist/esm/types/ui/index.d.ts +2 -0
  84. package/dist/esm/types/ui/localizer.d.ts +28 -0
  85. package/dist/esm/types/util/index.d.ts +2 -0
  86. package/dist/esm/types/util/uniqBy.d.ts +1 -0
  87. package/dist/esm/types/util/uniqId.d.ts +1 -0
  88. package/dist/index.d.ts +290 -0
  89. package/package.json +90 -0
  90. package/src/ChatifyContext.tsx +26 -0
  91. package/src/core/action.tsx +282 -0
  92. package/src/core/client.ts +137 -0
  93. package/src/core/index.ts +14 -0
  94. package/src/core/models.tsx +75 -0
  95. package/src/core/reducer.tsx +80 -0
  96. package/src/core/state.ts +38 -0
  97. package/src/core/useAccounts.tsx +11 -0
  98. package/src/core/useAddChat.tsx +12 -0
  99. package/src/core/useAddMember.tsx +12 -0
  100. package/src/core/useAddMessage.tsx +40 -0
  101. package/src/core/useChat.tsx +12 -0
  102. package/src/core/useChatList.tsx +10 -0
  103. package/src/core/useLoadMore.tsx +42 -0
  104. package/src/core/useMessages.tsx +23 -0
  105. package/src/core/useMutation.tsx +48 -0
  106. package/src/core/useQuery.tsx +45 -0
  107. package/src/core/useReadMonitor.tsx +31 -0
  108. package/src/core/useRemoveMember.tsx +12 -0
  109. package/src/core/useSignalR.tsx +51 -0
  110. package/src/index.ts +3 -0
  111. package/src/ui/AddChat.tsx +55 -0
  112. package/src/ui/Aside.tsx +67 -0
  113. package/src/ui/Chat.tsx +26 -0
  114. package/src/ui/ChatBody.tsx +43 -0
  115. package/src/ui/ChatHeader.tsx +47 -0
  116. package/src/ui/ChatSettings.tsx +59 -0
  117. package/src/ui/Chatify.tsx +47 -0
  118. package/src/ui/InputForm.tsx +48 -0
  119. package/src/ui/LoadMore.tsx +40 -0
  120. package/src/ui/Main.tsx +16 -0
  121. package/src/ui/Message.tsx +50 -0
  122. package/src/ui/MessageMd.tsx +47 -0
  123. package/src/ui/MessageSkeleton.tsx +19 -0
  124. package/src/ui/SendingMessage.tsx +14 -0
  125. package/src/ui/SystemMessage.tsx +26 -0
  126. package/src/ui/index.ts +2 -0
  127. package/src/ui/localizer.tsx +58 -0
  128. package/src/ui/styles/aside.scss +94 -0
  129. package/src/ui/styles/index.scss +247 -0
  130. package/src/ui/styles/keyframes.scss +14 -0
  131. package/src/ui/styles/mixins.scss +20 -0
  132. package/src/ui/styles/variables.scss +10 -0
  133. package/src/util/index.ts +2 -0
  134. package/src/util/uniqBy.tsx +17 -0
  135. package/src/util/uniqId.tsx +3 -0
@@ -0,0 +1,59 @@
1
+ import { Button, Col, List, Row, Select } from 'antd';
2
+ import { Account, useAccounts, useAddMember, useChat, useSelect } from '../core';
3
+ import { CloseOutlined } from '@ant-design/icons';
4
+ import { useRemoveMember } from '../core';
5
+ import { useCallback, useMemo } from 'react';
6
+
7
+ function Member(props: { account: Account; chatId: string }) {
8
+ const { account, chatId } = props;
9
+ const [remove] = useRemoveMember();
10
+
11
+ const onRemoveClick = useCallback(
12
+ () => remove({ chatId, accountId: account.id, deleteHistory: true }),
13
+ [remove, chatId, account.id],
14
+ );
15
+
16
+ return (
17
+ <List.Item>
18
+ {account.name} <Button onClick={onRemoveClick} type="link" icon={<CloseOutlined />} />
19
+ </List.Item>
20
+ );
21
+ }
22
+
23
+ export function ChatSettings() {
24
+ const [chatId] = useSelect((x) => [x.ui.chatId], []);
25
+ const { data: chat } = useChat({ chatId: chatId! });
26
+ const [add] = useAddMember();
27
+ const { data: accounts } = useAccounts();
28
+ const accountOptions = useMemo(() => accounts?.map((x) => ({ value: x.id, label: x.name })), [accounts]);
29
+
30
+ const onAdd = useCallback(
31
+ (id: string) => add({ chatId: chatId!, accountId: id, withHistory: true }),
32
+ [add, chatId],
33
+ );
34
+
35
+ if (!chat) {
36
+ return null;
37
+ }
38
+
39
+ return (
40
+ <div className="wxchtf-chat-settings">
41
+ <Row justify="space-between">
42
+ <Col>
43
+ <h3 className="wxchtf-title">{chat.name}</h3>
44
+ </Col>
45
+ <Col>
46
+ <Button type="link" icon={<CloseOutlined />} />
47
+ </Col>
48
+ </Row>
49
+ <h4>Members</h4>
50
+ <List
51
+ dataSource={chat.members}
52
+ rowKey="id"
53
+ renderItem={(account) => <Member account={account} chatId={chatId!} />}
54
+ />
55
+
56
+ <Select<string> style={{ width: '350px' }} options={accountOptions} value={null} onSelect={onAdd} />
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,47 @@
1
+ import { CSSProperties, useCallback } from 'react';
2
+ import { useSignalR, ReducerProvider, useDispatch, useReadMonitor } from '../core';
3
+ import { Aside } from './Aside';
4
+ import { Main } from './Main';
5
+ import { Button } from 'antd';
6
+ import { Localizer, LocalizerContext, defaultLocalizer } from './localizer';
7
+
8
+ export interface ChatifyProps {
9
+ className?: string;
10
+ style?: CSSProperties;
11
+ localizer?: Localizer;
12
+ }
13
+
14
+ function Content(props: Pick<ChatifyProps, 'className' | 'style'>) {
15
+ const { className = '', style } = props;
16
+ useSignalR();
17
+ useReadMonitor();
18
+ const dispatch = useDispatch();
19
+ const onAddChat = useCallback(() => dispatch({ type: 'new_chat_open', data: undefined }), [dispatch]);
20
+
21
+ return (
22
+ <div className={className + ' wxchtf-chatify'} style={style}>
23
+ <div className="wxchtf-header">
24
+ <Button onClick={onAddChat} icon={'+'} type="link">
25
+ Add chat
26
+ </Button>
27
+ </div>
28
+ <div className="wxchtf-body">
29
+ <Aside />
30
+ <Main />
31
+ </div>
32
+ <div className="wxchtf-footer"></div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export function Chatify(props: ChatifyProps) {
38
+ const { localizer = defaultLocalizer } = props;
39
+
40
+ return (
41
+ <LocalizerContext.Provider value={localizer}>
42
+ <ReducerProvider>
43
+ <Content {...props} />
44
+ </ReducerProvider>
45
+ </LocalizerContext.Provider>
46
+ );
47
+ }
@@ -0,0 +1,48 @@
1
+ import { Button, Input } from 'antd';
2
+ import SendOutlined from '@ant-design/icons/SendOutlined';
3
+ import FileAddOutlined from '@ant-design/icons/FileAddOutlined';
4
+ import { useCallback, useState } from 'react';
5
+ import isHotkey from 'is-hotkey';
6
+ import { useAddMessage } from '../core/useAddMessage';
7
+ import { uniqId } from '../util';
8
+ import { useSelect } from '../core';
9
+
10
+ export function InputForm() {
11
+ const chatId = useSelect((x) => x.ui.chatId!, []);
12
+ const [add] = useAddMessage({ chatId });
13
+ const [text, setText] = useState('');
14
+
15
+ const onSend = useCallback(() => {
16
+ setText('');
17
+ add({ text: text, files: [], requestId: uniqId() });
18
+
19
+ // eslint-disable-next-line react-hooks/exhaustive-deps
20
+ }, [chatId, text]);
21
+
22
+ return (
23
+ <div className="wxchtf-input-form">
24
+ <div className="wxchtf-input-box">
25
+ <div className="wxchtf-add-files-box">
26
+ <Button type="link" icon={<FileAddOutlined />} className="wxchtf-add-files-btn" />
27
+ </div>
28
+ <div className="wxchtf-text-input-box">
29
+ <Input.TextArea
30
+ value={text}
31
+ onChange={(e) => setText(e.target.value)}
32
+ autoSize={{ maxRows: 5 }}
33
+ className="wxchtf-text-input-textarea"
34
+ placeholder="Start typing (Ctrl/⌘Cmd + Enter to send) ..."
35
+ onKeyDown={(event) => {
36
+ if (isHotkey('ctrl+enter')(event)) {
37
+ onSend();
38
+ }
39
+ }}
40
+ />
41
+ </div>
42
+ <div className="wxchtf-submit-btn-box">
43
+ <Button onClick={onSend} type="link" icon={<SendOutlined />} className="wxchtf-submit-btn" />
44
+ </div>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,40 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useLoadMore } from '../core';
3
+ import { MessageSkeleton } from './MessageSkeleton';
4
+
5
+ export interface LoadMoreProps {
6
+ chatId: string;
7
+ }
8
+
9
+ export function LoadMore(props: LoadMoreProps) {
10
+ const { chatId } = props;
11
+ const [loadMore, { isFetching }] = useLoadMore({ chatId });
12
+ const [intersecting, setIntersecting] = useState(false);
13
+ const ref = useRef<HTMLDivElement | null>(null);
14
+
15
+ const observer = useMemo(
16
+ () => new IntersectionObserver(([entry]) => setIntersecting((prev) => prev || entry.isIntersecting)),
17
+ [],
18
+ );
19
+
20
+ useEffect(() => {
21
+ observer.observe(ref.current!);
22
+ return () => observer.disconnect();
23
+ }, [observer]);
24
+
25
+ useEffect(() => {
26
+ if (!intersecting) {
27
+ return;
28
+ }
29
+
30
+ loadMore();
31
+
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, [intersecting]);
34
+
35
+ if (isFetching) {
36
+ return <MessageSkeleton count={4} />;
37
+ }
38
+
39
+ return <div ref={ref} />;
40
+ }
@@ -0,0 +1,16 @@
1
+ import { useSelect } from '../core';
2
+ import { AddChat } from './AddChat';
3
+ import { Chat } from './Chat';
4
+ import { ChatSettings } from './ChatSettings';
5
+
6
+ export function Main() {
7
+ const { view, chatId } = useSelect((x) => ({ view: x.ui.view, chatId: x.ui.chatId }), []);
8
+
9
+ return (
10
+ <div className="wxchtf-main">
11
+ {view === 'chat' && chatId && <Chat id={chatId} key={chatId} />}
12
+ {view === 'new-chat' && <AddChat />}
13
+ {view === 'chat-settings' && <ChatSettings />}
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,50 @@
1
+ import { useMe } from '../ChatifyContext';
2
+ import { MessageMd } from './MessageMd';
3
+ import { useEffect, useMemo, useRef } from 'react';
4
+ import { useDispatch, useSelect, Message as MessageModel } from '../core';
5
+
6
+ export interface MessageProps {
7
+ message: MessageModel;
8
+ }
9
+
10
+ export function Message(props: MessageProps) {
11
+ const { message } = props;
12
+ const me = useMe();
13
+ const my = message.sentBy.id === me;
14
+ const dispatch = useDispatch();
15
+ const ref = useRef<HTMLDivElement | null>(null);
16
+ const readRef = useRef(false);
17
+ const reading = useSelect((x) => x.read.queued.includes(message.id), []);
18
+
19
+ const observer = useMemo(
20
+ () =>
21
+ new IntersectionObserver(([entry]) => {
22
+ if (entry.isIntersecting && !readRef.current && !message.read) {
23
+ readRef.current = true;
24
+ dispatch({ type: 'read', data: { id: message.id, timestamp: new Date().getTime() } });
25
+ }
26
+ }),
27
+
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ [],
30
+ );
31
+
32
+ useEffect(() => {
33
+ observer.observe(ref.current!);
34
+ return () => observer.disconnect();
35
+ }, [observer]);
36
+
37
+ return (
38
+ <MessageMd
39
+ ref={ref}
40
+ my={my}
41
+ text={message.text}
42
+ files={message.files}
43
+ author={message.sentBy}
44
+ timestamp={message.sentAt}
45
+ read={message.read}
46
+ sending={false}
47
+ reading={reading}
48
+ />
49
+ );
50
+ }
@@ -0,0 +1,47 @@
1
+ import { Avatar } from 'antd';
2
+ import { Account, File } from '../core';
3
+ import { useLocalizer } from './localizer';
4
+ import React from 'react';
5
+
6
+ export interface MessageMdProps {
7
+ author?: Account;
8
+ text: string;
9
+ files: File[];
10
+ my: boolean;
11
+ read?: boolean;
12
+ sending?: boolean;
13
+ reading?: boolean;
14
+ timestamp: string;
15
+ }
16
+
17
+ export const MessageMd = React.forwardRef(
18
+ (props: MessageMdProps, forwardRef: React.ForwardedRef<HTMLDivElement>) => {
19
+ const { author, text, my, read, reading, sending, timestamp } = props;
20
+ const localizer = useLocalizer();
21
+
22
+ return (
23
+ <div
24
+ ref={forwardRef}
25
+ className={'wxchtf-message' + (my ? ' --my' : '') + (sending ? ' --sending' : '')}
26
+ >
27
+ {author && (
28
+ <div className="wxchtf-message-author">
29
+ <Avatar src={author.avatar} />
30
+ </div>
31
+ )}
32
+ <div className="wxchtf-message-body">
33
+ <div className="wxchtf-message-text">{text}</div>
34
+ <div className="wxchtf-message-info-box">
35
+ <div className="wxchtf-message-sent-at">{localizer.timestamp(timestamp)}</div>
36
+ {read !== undefined && !my && (
37
+ <div className="wxchtf-message-read-box">
38
+ {reading ? localizer.message.reading() : localizer.message.read()}
39
+ </div>
40
+ )}
41
+ {sending && <div className="wxchtf-message-sending-box">{localizer.message.sending()}</div>}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+ },
47
+ );
@@ -0,0 +1,19 @@
1
+ import { Skeleton } from 'antd';
2
+
3
+ export interface MessageSkeletonProps {
4
+ count?: number;
5
+ }
6
+
7
+ export function MessageSkeleton(props: MessageSkeletonProps) {
8
+ const { count = 1 } = props;
9
+ return (
10
+ <>
11
+ {Array.from(Array(count)).map((_, index) => (
12
+ <div className="wxchtf-message-skeleton" key={index}>
13
+ <Skeleton.Avatar className="wxchtf-avatar" active />
14
+ <Skeleton.Button active className="wxchtf-content" />
15
+ </div>
16
+ ))}
17
+ </>
18
+ );
19
+ }
@@ -0,0 +1,14 @@
1
+ import { File } from '../core';
2
+ import { MessageMd } from './MessageMd';
3
+
4
+ export interface SendingMessageProps {
5
+ text: string;
6
+ files: File[];
7
+ }
8
+
9
+ export function SendingMessage(props: SendingMessageProps) {
10
+ const { text, files } = props;
11
+ return (
12
+ <MessageMd text={text} files={files} my={true} sending={true} timestamp={new Date().toUTCString()} />
13
+ );
14
+ }
@@ -0,0 +1,26 @@
1
+ import { Localizer, useLocalizer } from './localizer';
2
+
3
+ export interface SystemMessageProps {
4
+ text: string;
5
+ sentAt: string;
6
+ }
7
+
8
+ export function formatSystemMessage(localizer: Localizer, text: string): React.ReactNode {
9
+ if (text === 'chatify://chat-created') {
10
+ return localizer.system.chatCreated();
11
+ }
12
+
13
+ throw new Error(`Unknown system message ${text}`);
14
+ }
15
+
16
+ export function SystemMessage(props: SystemMessageProps) {
17
+ const { text, sentAt } = props;
18
+ const localizer = useLocalizer();
19
+
20
+ return (
21
+ <div className="wxchtf-system-message">
22
+ <div className="wxchtf-text">{formatSystemMessage(localizer, text)}</div>
23
+ <div className="wxchtf-timestamp">{localizer.timestamp(sentAt)}</div>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Chatify';
2
+ export * from './localizer';
@@ -0,0 +1,58 @@
1
+ import dayjs from 'dayjs';
2
+ import React, { useContext } from 'react';
3
+
4
+ export interface Localizer {
5
+ timestamp: (value: string) => string;
6
+ system: {
7
+ chatCreated: () => string;
8
+ };
9
+ message: {
10
+ read: () => React.ReactNode;
11
+ reading: () => React.ReactNode;
12
+ sending: () => React.ReactNode;
13
+ };
14
+ addChat: {
15
+ title: () => React.ReactNode;
16
+ name: {
17
+ label: () => React.ReactNode;
18
+ required: () => string;
19
+ max250: () => string;
20
+ };
21
+ members: {
22
+ label: () => React.ReactNode;
23
+ required: () => string;
24
+ };
25
+ submitBtn: () => React.ReactNode;
26
+ };
27
+ }
28
+
29
+ export const LocalizerContext = React.createContext<Localizer>(null!);
30
+
31
+ export function useLocalizer() {
32
+ return useContext(LocalizerContext);
33
+ }
34
+
35
+ export const defaultLocalizer: Localizer = {
36
+ timestamp: (value: string) => dayjs(value).format('HH:mm'),
37
+ message: {
38
+ read: () => 'Read',
39
+ reading: () => 'Reading...',
40
+ sending: () => 'Sending...',
41
+ },
42
+ addChat: {
43
+ title: () => 'Add chat',
44
+ name: {
45
+ label: () => 'Name',
46
+ required: () => `Name is required`,
47
+ max250: () => 'Max 250 characters allowed',
48
+ },
49
+ members: {
50
+ label: () => 'Members',
51
+ required: () => 'Members is required',
52
+ },
53
+ submitBtn: () => 'Submit',
54
+ },
55
+ system: {
56
+ chatCreated: () => 'Chat created',
57
+ },
58
+ };
@@ -0,0 +1,94 @@
1
+ .wxchtf-aside {
2
+ flex: none;
3
+ width: 300px;
4
+ border-right: $border;
5
+ display: flex;
6
+ flex-flow: column nowrap;
7
+
8
+ .wxchtf-chats.ant-menu {
9
+ flex: 1 1 auto;
10
+ height: 100px;
11
+ overflow-y: auto;
12
+
13
+ @include scrollbar();
14
+
15
+ .ant-menu-item {
16
+ padding: 0;
17
+ height: unset;
18
+ margin: 0;
19
+ width: 100%;
20
+ border-radius: 0;
21
+ line-height: 1.5;
22
+ border-top: $border;
23
+
24
+ &:first-child {
25
+ border-top: none;
26
+ }
27
+
28
+ &.ant-menu-item-selected .wxchtf-chat-name {
29
+ font-weight: 600;
30
+ }
31
+
32
+ .wxchtf-chat {
33
+ padding: 0.5rem 1rem;
34
+ position: relative;
35
+
36
+ &.--unread {
37
+ .wxchtf-chat-last {
38
+ font-weight: bold;
39
+ }
40
+ }
41
+
42
+ .wxchtf-chat-name {
43
+ font-weight: bold;
44
+ display: inline-block;
45
+ }
46
+
47
+ .wxchtf-unread-count {
48
+ display: inline-block;
49
+ padding: 0.2rem;
50
+ min-width: 1rem;
51
+ height: 1.2rem;
52
+ line-height: 0.7rem;
53
+ margin-left: 0.5rem;
54
+ border-radius: 50%;
55
+ text-align: center;
56
+ font-size: 0.7rem;
57
+ background: $unreadCountBackground;
58
+ }
59
+
60
+ .wxchtf-chat-last {
61
+ $size: 25px;
62
+
63
+ font-style: italic;
64
+ opacity: 0.75;
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ white-space: nowrap;
68
+ line-height: $size;
69
+
70
+ .wxchtf-chat-last-author {
71
+ display: inline-block;
72
+ margin-right: 0.5rem;
73
+
74
+ .ant-avatar {
75
+ display: inline-block;
76
+ border: none;
77
+ margin-right: 5px;
78
+
79
+ @include avatarSize($size);
80
+ }
81
+
82
+ &::after {
83
+ content: ':';
84
+ }
85
+ }
86
+
87
+ .wxchtf-chat-last-message {
88
+ display: inline;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }