@rpg-engine/long-bow 0.7.22 → 0.7.23

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.
@@ -0,0 +1,239 @@
1
+ import React from 'react';
2
+ import { RxCross2, RxMagnifyingGlass } from 'react-icons/rx';
3
+ import styled from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { uiFonts } from '../../constants/uiFonts';
6
+ import { Ellipsis } from '../shared/Ellipsis';
7
+ import { PrivateChatCharacter } from './types';
8
+
9
+ interface IRecentChatsProps {
10
+ showRecentChats: boolean;
11
+ toggleRecentChats: () => void;
12
+ hasUnseenMessages: boolean;
13
+ showSearchCharacterUI: () => void;
14
+ recentChatCharacters?: PrivateChatCharacter[];
15
+ recentSelectedChatCharacterId?: string;
16
+ unseenMessageCharacterIds?: string[];
17
+ onPreviousChatCharacterClick: (character: PrivateChatCharacter) => void;
18
+ onRemoveRecentChatCharacter?: (character: PrivateChatCharacter) => void;
19
+ isPrivate: boolean;
20
+ }
21
+
22
+ export const RecentChats: React.FC<IRecentChatsProps> = ({
23
+ showRecentChats,
24
+ toggleRecentChats,
25
+ hasUnseenMessages,
26
+ showSearchCharacterUI,
27
+ recentChatCharacters,
28
+ recentSelectedChatCharacterId,
29
+ unseenMessageCharacterIds,
30
+ onPreviousChatCharacterClick,
31
+ onRemoveRecentChatCharacter,
32
+ isPrivate,
33
+ }) => (
34
+ <RecentChatTabContainer isOpen={showRecentChats} isPrivate={isPrivate}>
35
+ <RecentChatTopBar>
36
+ <BurgerIconContainer
37
+ onPointerDown={toggleRecentChats}
38
+ hasUnseenMessages={hasUnseenMessages}
39
+ >
40
+ <BurgerLineIcon />
41
+ <BurgerLineIcon />
42
+ <BurgerLineIcon />
43
+ </BurgerIconContainer>
44
+ {showRecentChats && (
45
+ <SearchButton onPointerDown={showSearchCharacterUI}>
46
+ <RxMagnifyingGlass size={16} color={uiColors.white} />
47
+ </SearchButton>
48
+ )}
49
+ </RecentChatTopBar>
50
+ <RecentChatLogContainer isOpen={showRecentChats}>
51
+ {recentChatCharacters?.map(character => (
52
+ <ListElementContainer key={character._id}>
53
+ <ListElement
54
+ active={character._id === recentSelectedChatCharacterId}
55
+ onPointerDown={() => onPreviousChatCharacterClick(character)}
56
+ >
57
+ <StatusDot
58
+ isUnseen={
59
+ unseenMessageCharacterIds?.includes(character._id) ?? false
60
+ }
61
+ />
62
+ <Ellipsis maxWidth="140px" maxLines={1}>
63
+ {character.name}
64
+ </Ellipsis>
65
+ </ListElement>
66
+ <CloseButton
67
+ className="close-button"
68
+ onPointerDown={() => onRemoveRecentChatCharacter?.(character)}
69
+ >
70
+ <RxCross2 size={16} />
71
+ </CloseButton>
72
+ </ListElementContainer>
73
+ ))}
74
+ </RecentChatLogContainer>
75
+ </RecentChatTabContainer>
76
+ );
77
+
78
+ const RecentChatTabContainer = styled.div<{
79
+ isPrivate: boolean;
80
+ isOpen: boolean;
81
+ }>`
82
+ display: ${props => (props.isPrivate ? 'flex' : 'none')};
83
+ flex-direction: column;
84
+ border-right: 1px solid ${uiColors.gray};
85
+ outline: none;
86
+ width: ${props => (props.isOpen ? '25%' : '30px')};
87
+ max-width: 200px;
88
+ min-width: ${props => (props.isOpen ? '180px' : '30px')};
89
+ transition: all 0.3s ease-in-out;
90
+ overflow: hidden;
91
+ height: 100%;
92
+ @media (max-width: 768px) {
93
+ width: ${props => (props.isOpen ? '50%' : '30px')};
94
+ min-width: ${props => (props.isOpen ? '150px' : '30px')};
95
+ }
96
+ `;
97
+
98
+ const RecentChatTopBar = styled.div`
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ height: 30px;
103
+ flex-shrink: 0;
104
+ `;
105
+
106
+ const SearchButton = styled.button`
107
+ border: none;
108
+ background-color: transparent;
109
+ display: flex;
110
+ flex-direction: column;
111
+ align-items: flex-end;
112
+ gap: 2px;
113
+ padding: 4px;
114
+ position: relative;
115
+ `;
116
+
117
+ const BurgerIconContainer = styled.button<{ hasUnseenMessages: boolean }>`
118
+ border: none;
119
+ background-color: transparent;
120
+ display: flex;
121
+ flex-direction: column;
122
+ align-items: flex-end;
123
+ padding: 4px;
124
+ gap: 2px;
125
+ position: relative;
126
+
127
+ &:after {
128
+ content: '';
129
+ width: 6px;
130
+ height: 6px;
131
+ position: absolute;
132
+ top: 0;
133
+ right: 2px;
134
+ border-radius: 50%;
135
+ background-color: ${uiColors.lightGreen};
136
+ display: ${props => (props.hasUnseenMessages ? 'block' : 'none')};
137
+ }
138
+ `;
139
+
140
+ const BurgerLineIcon = styled.span`
141
+ width: 1rem;
142
+ height: 2px;
143
+ background-color: #ffffff;
144
+ `;
145
+
146
+ const RecentChatLogContainer = styled.div<{ isOpen: boolean }>`
147
+ display: ${props => (props.isOpen ? 'flex' : 'none')};
148
+ opacity: ${props => (props.isOpen ? 1 : 0)};
149
+ flex-direction: column;
150
+ gap: 0.25rem; // Reduce this from 0.5rem to 0.25rem
151
+ transition: opacity 0.3s ease-in-out;
152
+ padding: 0;
153
+ margin: 0;
154
+ overflow-y: auto;
155
+ flex-grow: 1;
156
+ height: 0;
157
+
158
+ /* Firefox */
159
+ scrollbar-width: thin;
160
+ scrollbar-color: rgba(51, 51, 51, 0.4) rgba(30, 30, 30, 0.4);
161
+
162
+ /* WebKit and Chromium-based browsers */
163
+ &::-webkit-scrollbar {
164
+ width: 8px;
165
+ height: 8px;
166
+ }
167
+
168
+ &::-webkit-scrollbar-track {
169
+ background: rgba(30, 30, 30, 0.2);
170
+ border-radius: 4px;
171
+ }
172
+
173
+ &::-webkit-scrollbar-thumb {
174
+ background-color: rgba(255, 102, 0, 0.5);
175
+ border-radius: 4px;
176
+ border: 2px solid rgba(30, 30, 30, 0.2);
177
+ }
178
+
179
+ &::-webkit-scrollbar-thumb:hover {
180
+ background-color: rgba(255, 102, 0, 0.7);
181
+ }
182
+ `;
183
+
184
+ const ListElementContainer = styled.div`
185
+ display: flex;
186
+ justify-content: space-between;
187
+ align-items: center;
188
+ padding: 2px 0; // Add small vertical padding
189
+ `;
190
+
191
+ const ListElement = styled.button<{ active: boolean }>`
192
+ margin: 0 !important; // Remove vertical margins
193
+ font-size: ${uiFonts.size.small} !important;
194
+ padding: 2px;
195
+ all: unset;
196
+ color: ${props => (props.active ? uiColors.yellow : uiColors.white)};
197
+ width: 100%;
198
+ position: relative;
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 4px;
202
+
203
+ &:hover {
204
+ color: #ff0;
205
+ }
206
+ max-width: calc(100% - 24px);
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
209
+ white-space: nowrap;
210
+ `;
211
+
212
+ const StatusDot = styled.span<{ isUnseen: boolean }>`
213
+ width: 6px;
214
+ height: 6px;
215
+ border-radius: 50%;
216
+ background-color: ${props =>
217
+ props.isUnseen ? uiColors.lightGreen : uiColors.gray};
218
+ display: inline-block;
219
+ margin-right: 6px;
220
+ `;
221
+
222
+ const CloseButton = styled.button`
223
+ all: unset;
224
+ font-size: ${uiFonts.size.xxsmall};
225
+ margin: 0 0.5rem;
226
+ transition: all 0.2s ease-in-out;
227
+ background-color: ${uiColors.red};
228
+ color: ${uiColors.white};
229
+ width: 16px;
230
+ height: 16px;
231
+ border-radius: 50%;
232
+ display: flex;
233
+ justify-content: center;
234
+ align-items: center;
235
+ &:hover {
236
+ background-color: ${uiColors.white};
237
+ color: ${uiColors.red};
238
+ }
239
+ `;
@@ -6,7 +6,7 @@ import { uiFonts } from '../../constants/uiFonts';
6
6
  import { IStyles } from '../Chat/Chat';
7
7
  import { Column } from '../shared/Column';
8
8
  import { Ellipsis } from '../shared/Ellipsis';
9
- import type { PrivateChatCharacter } from './ChatRevamp';
9
+ import { PrivateChatCharacter } from './types';
10
10
 
11
11
  interface ISearchCharacterProps {
12
12
  onFocus?: () => void;
@@ -37,7 +37,7 @@ export const SearchCharacter = ({
37
37
  const searchCharacterRef = useRef<HTMLInputElement>(null);
38
38
 
39
39
  useEffect(() => {
40
- const timer = setTimeout(() => {
40
+ const timer = setTimeout(() => {
41
41
  if (searchCharacterRef.current) {
42
42
  searchCharacterRef.current.focus();
43
43
  }
@@ -52,9 +52,7 @@ export const SearchCharacter = ({
52
52
  onChangeCharacterName(characterName);
53
53
  };
54
54
 
55
- const handleCharacterClick = (
56
- character: PrivateChatCharacter
57
- ) => {
55
+ const handleCharacterClick = (character: PrivateChatCharacter) => {
58
56
  if (!onCharacterClick) return;
59
57
  setCharacterName('');
60
58
  onCharacterClick(character);
@@ -69,23 +67,23 @@ export const SearchCharacter = ({
69
67
  value={characterName}
70
68
  ref={searchCharacterRef}
71
69
  id="characterName"
72
- name='characterName'
70
+ name="characterName"
73
71
  onChange={e => {
74
72
  setCharacterName(e.target.value);
75
73
  onChangeCharacterName(e.target.value);
76
74
  }}
77
- placeholder='Search for a character...'
75
+ placeholder="Search for a character..."
78
76
  height={20}
79
77
  type="text"
80
78
  autoComplete="off"
81
79
  onFocus={onFocus}
82
80
  onBlur={onBlur}
83
81
  onPointerDown={onFocus}
84
- />
82
+ />
85
83
  </Column>
86
84
  <Column justifyContent="flex-end">
87
85
  <SearchButton
88
- type='submit'
86
+ type="submit"
89
87
  buttonColor={styles?.buttonColor || '#005b96'}
90
88
  buttonBackgroundColor={
91
89
  styles?.buttonBackgroundColor || 'rgba(0,0,0,.5)'
@@ -101,28 +99,29 @@ export const SearchCharacter = ({
101
99
  {recentCharacters && recentCharacters.length > 0 && (
102
100
  <ListContainer>
103
101
  {recentCharacters.map(character => (
104
- <ListElement onPointerDown={() => handleCharacterClick(character)} key={character._id}>
105
- <Ellipsis
106
- maxWidth='150px'
107
- maxLines={1}
108
- >{character.name}</Ellipsis>
102
+ <ListElement
103
+ onPointerDown={() => handleCharacterClick(character)}
104
+ key={character._id}
105
+ >
106
+ <Ellipsis maxWidth="150px" maxLines={1}>
107
+ {character.name}
108
+ </Ellipsis>
109
109
  </ListElement>
110
110
  ))}
111
111
  </ListContainer>
112
112
  )}
113
- </SearchContainer>
113
+ </SearchContainer>
114
114
  );
115
115
  };
116
116
 
117
-
118
117
  interface IButtonProps {
119
118
  buttonColor: string;
120
119
  buttonBackgroundColor: string;
121
120
  }
122
121
 
123
122
  const SearchContainer = styled.div`
124
- width: 100%;
125
- `
123
+ width: 100%;
124
+ `;
126
125
 
127
126
  const Form = styled.form`
128
127
  display: flex;
@@ -167,5 +166,3 @@ const ListElement = styled.li`
167
166
  all: unset;
168
167
  }
169
168
  `;
170
-
171
-
@@ -0,0 +1,41 @@
1
+ import {
2
+ ICharacter,
3
+ IChatMessage,
4
+ IPrivateChatMessage,
5
+ ITradeChatMessage,
6
+ } from '@rpg-engine/shared';
7
+ import { IStyles } from '../Chat/Chat';
8
+
9
+ export type PrivateChatCharacter = Pick<ICharacter, '_id' | 'name'>;
10
+
11
+ export type ChatMessage =
12
+ | IChatMessage
13
+ | IPrivateChatMessage
14
+ | ITradeChatMessage;
15
+
16
+ export interface IChatRevampProps {
17
+ chatMessages: ChatMessage[];
18
+ onSendGlobalChatMessage: (message: string) => void;
19
+ onCloseButton: () => void;
20
+ onFocus?: () => void;
21
+ onBlur?: () => void;
22
+ styles?: IStyles;
23
+ tabs: { label: string; id: string }[];
24
+ activeTab: string;
25
+ onChangeTab: (tabId: string) => void;
26
+ privateChatCharacters?: PrivateChatCharacter[];
27
+ onChangeCharacterName: (characterName: string) => void;
28
+ onCharacterClick?: (character: PrivateChatCharacter) => void;
29
+ onSendPrivateChatMessage: (message: string) => void;
30
+ recentChatCharacters?: PrivateChatCharacter[];
31
+ recentSelectedChatCharacterId?: string;
32
+ onPreviousChatCharacterClick?: (character: PrivateChatCharacter) => void;
33
+ onRemoveRecentChatCharacter?: (character: PrivateChatCharacter) => void;
34
+ unseenMessageCharacterIds?: string[];
35
+ onSendTradeMessage: (message: string) => void;
36
+ searchCharacterUI: boolean;
37
+ hideSearchCharacterUI: () => void;
38
+ showSearchCharacterUI: () => void;
39
+ minimizedByDefault?: boolean;
40
+ autoCloseOnSend?: boolean;
41
+ }
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState } from 'react';
2
+ import {
3
+ IChatRevampProps,
4
+ PrivateChatCharacter,
5
+ } from '../components/ChatRevamp/types';
6
+
7
+ export const useChat = ({
8
+ minimizedByDefault,
9
+ isPrivate,
10
+ onChangeTab,
11
+ onPreviousChatCharacterClick,
12
+ hideSearchCharacterUI,
13
+ unseenMessageCharacterIds,
14
+ }: Pick<
15
+ IChatRevampProps,
16
+ | 'activeTab'
17
+ | 'minimizedByDefault'
18
+ | 'onChangeTab'
19
+ | 'onPreviousChatCharacterClick'
20
+ | 'hideSearchCharacterUI'
21
+ | 'unseenMessageCharacterIds'
22
+ > & {
23
+ isPrivate: boolean;
24
+ }) => {
25
+ const [showRecentChats, setShowRecentChats] = useState(false);
26
+ const [isExpanded, setIsExpanded] = useState(!minimizedByDefault);
27
+
28
+ useEffect(() => {
29
+ if (isPrivate) {
30
+ setIsExpanded(true);
31
+ }
32
+ }, [isPrivate]);
33
+
34
+ const toggleExpand = () => setIsExpanded(prev => !prev);
35
+
36
+ const toggleRecentChats = () => setShowRecentChats(prev => !prev);
37
+
38
+ const handleTabChange = (tabId: string) => {
39
+ if (tabId === 'private') {
40
+ setIsExpanded(true);
41
+ }
42
+ onChangeTab(tabId);
43
+ };
44
+
45
+ const handlePreviousChatCharacterClick = (
46
+ character: PrivateChatCharacter
47
+ ) => {
48
+ onPreviousChatCharacterClick?.(character);
49
+ hideSearchCharacterUI();
50
+ };
51
+
52
+ return {
53
+ showRecentChats,
54
+ isExpanded,
55
+ toggleExpand,
56
+ toggleRecentChats,
57
+ handleTabChange,
58
+ handlePreviousChatCharacterClick,
59
+ hasUnseenMessages:
60
+ unseenMessageCharacterIds && unseenMessageCharacterIds?.length > 0,
61
+ };
62
+ };
@@ -179,6 +179,7 @@ const Template: Story<IChatMessage> = args => (
179
179
  }}
180
180
  onCloseButton={() => console.log('closing chat...')}
181
181
  sendMessage={true}
182
+ isExpanded={true}
182
183
  {...args}
183
184
  />
184
185
  </RPGUIRoot>
@@ -1,10 +1,9 @@
1
1
  import { ChatMessageType } from '@rpg-engine/shared';
2
2
  import { Meta, Story } from '@storybook/react';
3
3
  import React from 'react';
4
- import {
5
- ChatRevamp,
6
- IChatRevampProps,
7
- } from '../components/ChatRevamp/ChatRevamp';
4
+
5
+ import { ChatRevamp } from '../components/ChatRevamp/ChatRevamp';
6
+ import { IChatRevampProps } from '../components/ChatRevamp/types';
8
7
  import { RPGUIRoot } from '../components/RPGUI/RPGUIRoot';
9
8
 
10
9
  const meta: Meta = {
@@ -33,7 +32,7 @@ const chatMessagesMock = [
33
32
  _id: 'test-id-1',
34
33
  message: 'test message',
35
34
  emitter: {
36
- _id: 'someid',
35
+ _id: 'someid1',
37
36
  name: 'Guilherme',
38
37
  },
39
38
  type: ChatMessageType.Global,
@@ -47,7 +46,7 @@ const chatMessagesMock = [
47
46
  _id: 'test-id-2',
48
47
  message: 'Good morning!',
49
48
  emitter: {
50
- _id: 'someid',
49
+ _id: 'someid2',
51
50
  name: 'Guilherme',
52
51
  },
53
52
  type: ChatMessageType.Global,
@@ -61,7 +60,7 @@ const chatMessagesMock = [
61
60
  _id: 'test-id-3',
62
61
  message: 'How are you doing?',
63
62
  emitter: {
64
- _id: 'someid',
63
+ _id: 'someid3',
65
64
  name: 'Guilherme',
66
65
  },
67
66
  type: ChatMessageType.Global,
@@ -75,7 +74,7 @@ const chatMessagesMock = [
75
74
  _id: 'test-id-4',
76
75
  message: 'Hey hey hey!!!',
77
76
  emitter: {
78
- _id: 'someid',
77
+ _id: 'someid4',
79
78
  name: 'Guilherme',
80
79
  },
81
80
  type: ChatMessageType.Global,
@@ -89,7 +88,7 @@ const chatMessagesMock = [
89
88
  _id: 'test-id-5',
90
89
  message: 'BITCONNEEEEEEEEECT!',
91
90
  emitter: {
92
- _id: 'someid',
91
+ _id: 'someid5',
93
92
  name: 'Guilherme',
94
93
  },
95
94
  type: ChatMessageType.Global,
@@ -103,7 +102,7 @@ const chatMessagesMock = [
103
102
  _id: 'test-id-6',
104
103
  message: 'BITCONNEEEEEEEEECT!',
105
104
  emitter: {
106
- _id: 'someid',
105
+ _id: 'someid6',
107
106
  name: 'Guilherme',
108
107
  },
109
108
  type: ChatMessageType.Global,
@@ -117,77 +116,7 @@ const chatMessagesMock = [
117
116
  _id: 'test-id-7',
118
117
  message: 'BITCONNEEEEEEEEECT!',
119
118
  emitter: {
120
- _id: 'someid',
121
- name: 'Guilherme',
122
- },
123
- type: ChatMessageType.Global,
124
- x: 128,
125
- y: 128,
126
- scene: 'MainScene',
127
- createdAt: '2020-08-20T16:00:00.000Z',
128
- updatedAt: '2020-08-20T16:00:00.000Z',
129
- },
130
- {
131
- _id: 'test-id-8',
132
- message: 'BITCONNEEEEEEEEECT!',
133
- emitter: {
134
- _id: 'someid',
135
- name: 'Guilherme',
136
- },
137
- type: ChatMessageType.Global,
138
- x: 128,
139
- y: 128,
140
- scene: 'MainScene',
141
- createdAt: '2020-08-20T16:00:00.000Z',
142
- updatedAt: '2020-08-20T16:00:00.000Z',
143
- },
144
- {
145
- _id: 'test-id-9',
146
- message: 'BITCONNEEEEEEEEECT!',
147
- emitter: {
148
- _id: 'someid',
149
- name: 'Guilherme',
150
- },
151
- type: ChatMessageType.Global,
152
- x: 128,
153
- y: 128,
154
- scene: 'MainScene',
155
- createdAt: '2020-08-20T16:00:00.000Z',
156
- updatedAt: '2020-08-20T16:00:00.000Z',
157
- },
158
- {
159
- _id: 'test-id-9',
160
- message: '22222EEECT!',
161
- emitter: {
162
- _id: 'someid',
163
- name: 'Guilherme',
164
- },
165
- type: ChatMessageType.Global,
166
- x: 128,
167
- y: 128,
168
- scene: 'MainScene',
169
- createdAt: '2020-08-20T16:00:00.000Z',
170
- updatedAt: '2020-08-20T16:00:00.000Z',
171
- },
172
- {
173
- _id: 'test-id-9',
174
- message: '22222EEECT!',
175
- emitter: {
176
- _id: 'someid',
177
- name: 'Guilherme',
178
- },
179
- type: ChatMessageType.Global,
180
- x: 128,
181
- y: 128,
182
- scene: 'MainScene',
183
- createdAt: '2020-08-20T16:00:00.000Z',
184
- updatedAt: '2020-08-20T16:00:00.000Z',
185
- },
186
- {
187
- _id: 'test-id-9',
188
- message: '22222EEECT!',
189
- emitter: {
190
- _id: 'someid',
119
+ _id: 'someid7',
191
120
  name: 'Guilherme',
192
121
  },
193
122
  type: ChatMessageType.Global,
@@ -208,14 +137,14 @@ const tabsMock = [
208
137
  const recentPrivateChatCharactersMock = [
209
138
  { _id: 'someid', name: 'Guilhermexxxxxxxxxxxx' },
210
139
  { _id: 'someid2', name: 'Guilherme2' },
211
- { _id: 'someid2', name: 'Guilherme2' },
212
- { _id: 'someid2', name: 'Guilherme2' },
213
- { _id: 'someid2', name: 'Guilherme2' },
214
- { _id: 'someid2', name: 'Guilherme2' },
215
- { _id: 'someid2', name: 'Guilherme2' },
216
- { _id: 'someid2', name: 'Guilherme2' },
217
- { _id: 'someid2', name: 'Guilherme2' },
218
- { _id: 'someid2', name: 'Guilherme2' },
140
+ { _id: 'someid3', name: 'Guilherme2' },
141
+ { _id: 'someid4', name: 'Guilherme2' },
142
+ { _id: 'someid5', name: 'Guilherme2' },
143
+ { _id: 'someid6', name: 'Guilherme2' },
144
+ { _id: 'someid7', name: 'Guilherme2' },
145
+ { _id: 'someid8', name: 'Guilherme2' },
146
+ { _id: 'someid9', name: 'Guilherme2' },
147
+ { _id: 'someid10', name: 'Guilherme2' },
219
148
  ];
220
149
 
221
150
  const Template: Story<IChatRevampProps> = args => (