@rpg-engine/long-bow 0.8.122 → 0.8.124

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.122",
3
+ "version": "0.8.124",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -1,9 +1,11 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { uiColors } from '../../constants/uiColors';
4
- import { Button, ButtonTypes } from '../Button';
4
+ import { uiFonts } from '../../constants/uiFonts';
5
+ import { InternalTabs } from '../InternalTabs/InternalTabs';
5
6
 
6
7
  import { ICharacter } from '@rpg-engine/shared';
8
+ import { debounce } from 'lodash';
7
9
  import {
8
10
  ActionButtons,
9
11
  Table,
@@ -12,7 +14,6 @@ import {
12
14
  TableRow,
13
15
  UserActionLink,
14
16
  } from '../Table/Table';
15
- import { SearchFriend } from './SearchFriend';
16
17
  export type IFriend = Pick<ICharacter, 'name' | '_id' | 'isOnline'>;
17
18
  interface IFriendListProps {
18
19
  scale?: number;
@@ -47,83 +48,197 @@ export const FriendList: React.FC<IFriendListProps> = ({
47
48
  onAddFriend,
48
49
  onBackFriendList,
49
50
  }) => {
50
- const [isAddFriendUI, setIsAddFriendUI] = useState(false);
51
+ const [characterName, setCharacterName] = useState('');
52
+ const [activeTab, setActiveTab] = useState('friends');
51
53
 
52
54
  useEffect(() => {
53
- if (isAddFriendUI) {
55
+ if (activeTab === 'search') {
54
56
  onAddFriend?.();
55
57
  } else {
56
58
  onBackFriendList?.();
57
59
  }
58
- }, [isAddFriendUI]);
60
+ }, [activeTab]);
59
61
 
60
- return (
61
- <ListWrapper>
62
- {isAddFriendUI ? (
63
- <SearchFriend
64
- searchedCharacters={searchedCharacters}
65
- onSendFriendRequest={onSendFriendRequest}
66
- onAcceptRequest={onAcceptRequest}
67
- onRejectRequest={onRejectRequest}
68
- friendRequests={friendRequests}
69
- onSearch={onSearch}
70
- onBlur={onBlur}
62
+ const debouncedSearch = useCallback(
63
+ debounce((name: string) => {
64
+ onSearch(name);
65
+ }, 300),
66
+ []
67
+ );
68
+
69
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
70
+ const name = e.target.value;
71
+ setCharacterName(name);
72
+ debouncedSearch(name);
73
+ };
74
+
75
+ const handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
76
+ event.preventDefault();
77
+ if (characterName.trim()) {
78
+ onSearch(characterName);
79
+ }
80
+ };
81
+
82
+ const friendsTabContent =
83
+ friends.length > 0 ? (
84
+ <ScrollableArea>
85
+ <Table>
86
+ <thead>
87
+ <TableRow>
88
+ <TableHeader>Online</TableHeader>
89
+ <TableHeader>Name</TableHeader>
90
+ <TableHeader>Actions</TableHeader>
91
+ </TableRow>
92
+ </thead>
93
+ <tbody>
94
+ {friends.map(friend => (
95
+ <TableRow key={friend._id}>
96
+ <TableCell>
97
+ <IsOnlineBadge $isOnline={friend.isOnline} />
98
+ </TableCell>
99
+ <TableCell>{friend.name}</TableCell>
100
+ <TableCell>
101
+ <ActionButtons>
102
+ <UserActionLink
103
+ color={uiColors.white}
104
+ onClick={() => onOpenPrivateMessage(friend)}
105
+ >
106
+ Chat
107
+ </UserActionLink>
108
+ <UserActionLink
109
+ color={uiColors.red}
110
+ onClick={() => onRemoveFriend(friend)}
111
+ >
112
+ Remove
113
+ </UserActionLink>
114
+ </ActionButtons>
115
+ </TableCell>
116
+ </TableRow>
117
+ ))}
118
+ </tbody>
119
+ </Table>
120
+ </ScrollableArea>
121
+ ) : (
122
+ <EmptyStateContainer>
123
+ <EmptyStateText>You don't have any friends yet</EmptyStateText>
124
+ <AddFriendCTA onClick={() => setActiveTab('search')}>
125
+ Add your first friend
126
+ </AddFriendCTA>
127
+ </EmptyStateContainer>
128
+ );
129
+
130
+ const searchTabContent = (
131
+ <ListContainer>
132
+ <SearchForm onSubmit={handleSubmit}>
133
+ <SearchInput
134
+ value={characterName}
135
+ id="characterName"
136
+ name="characterName"
137
+ onChange={handleInputChange}
138
+ type="text"
139
+ autoComplete="off"
71
140
  onFocus={onFocus}
141
+ onBlur={onBlur}
142
+ onPointerDown={onFocus}
143
+ placeholder="Search for a character..."
72
144
  />
73
- ) : (
74
- <>
75
- <ScrollableArea>
76
- <Table>
77
- <thead>
78
- <TableRow>
79
- <TableHeader>Online</TableHeader>
80
- <TableHeader>Name</TableHeader>
81
- <TableHeader>Actions</TableHeader>
82
- </TableRow>
83
- </thead>
84
- <tbody>
85
- {friends.map(friend => (
86
- <TableRow key={friend._id}>
87
- <TableCell>
88
- <IsOnlineBadge $isOnline={friend.isOnline} />
89
- </TableCell>
90
- <TableCell>{friend.name}</TableCell>
91
- <TableCell>
92
- <ActionButtons>
93
- <UserActionLink
94
- color={uiColors.white}
95
- onClick={() => onOpenPrivateMessage(friend)}
96
- >
97
- Chat
98
- </UserActionLink>
99
- <UserActionLink
100
- color={uiColors.red}
101
- onClick={() => onRemoveFriend(friend)}
102
- >
103
- Remove
104
- </UserActionLink>
105
- </ActionButtons>
106
- </TableCell>
107
- </TableRow>
108
- ))}
109
- </tbody>
110
- </Table>
111
- </ScrollableArea>
112
- </>
113
- )}
114
-
115
- <ButtonContainer>
116
- <Button
117
- buttonType={ButtonTypes.RPGUIButton}
118
- onClick={() => setIsAddFriendUI(prev => !prev)}
119
- >
120
- {isAddFriendUI ? 'Back' : 'Add New Friend'}
121
- </Button>
122
- </ButtonContainer>
145
+ </SearchForm>
146
+ <CharacterList
147
+ characters={searchedCharacters}
148
+ onAction={onSendFriendRequest}
149
+ actionLabel="Add Friend"
150
+ />
151
+ </ListContainer>
152
+ );
153
+
154
+ const requestsTabContent = (
155
+ <ListContainer>
156
+ <FriendRequestSection
157
+ friendRequests={friendRequests}
158
+ onAccept={onAcceptRequest}
159
+ onReject={onRejectRequest}
160
+ />
161
+ </ListContainer>
162
+ );
163
+
164
+ const tabs = [
165
+ { id: 'friends', title: `Friends (${friends.length})`, content: friendsTabContent },
166
+ { id: 'search', title: 'Search', content: searchTabContent },
167
+ {
168
+ id: 'requests',
169
+ title: `Requests (${friendRequests.length})`,
170
+ content: requestsTabContent,
171
+ },
172
+ ];
173
+
174
+ return (
175
+ <ListWrapper>
176
+ <InternalTabs
177
+ tabs={tabs}
178
+ activeTab={activeTab}
179
+ onTabChange={setActiveTab}
180
+ activeTextColor="#000"
181
+ inactiveColor="#777"
182
+ borderColor="#f59e0b"
183
+ />
123
184
  </ListWrapper>
124
185
  );
125
186
  };
126
187
 
188
+ const CharacterList: React.FC<{
189
+ characters: IFriend[];
190
+ onAction: (character: IFriend) => void;
191
+ actionLabel: string;
192
+ }> = ({ characters, onAction, actionLabel }) => (
193
+ <ListContainer>
194
+ {characters.map(character => (
195
+ <ListItem key={character._id}>
196
+ <CharacterName>{character.name}</CharacterName>
197
+ <UserActionLink
198
+ color={uiColors.lightGreen}
199
+ onClick={() => onAction(character)}
200
+ >
201
+ {actionLabel}
202
+ </UserActionLink>
203
+ </ListItem>
204
+ ))}
205
+ </ListContainer>
206
+ );
207
+
208
+ const FriendRequestSection: React.FC<{
209
+ friendRequests: IFriend[];
210
+ onAccept: (character: IFriend) => void;
211
+ onReject: (character: IFriend) => void;
212
+ }> = ({ friendRequests, onAccept, onReject }) => (
213
+ <>
214
+ {friendRequests.length > 0 ? (
215
+ <ListContainer>
216
+ {friendRequests.map(character => (
217
+ <ListItem key={character._id}>
218
+ <CharacterName>{character.name}</CharacterName>
219
+ <AcceptRejectActions>
220
+ <UserActionLink
221
+ color={uiColors.lightGreen}
222
+ onClick={() => onAccept(character)}
223
+ >
224
+ Accept
225
+ </UserActionLink>
226
+ <UserActionLink
227
+ color={uiColors.red}
228
+ onClick={() => onReject(character)}
229
+ >
230
+ Reject
231
+ </UserActionLink>
232
+ </AcceptRejectActions>
233
+ </ListItem>
234
+ ))}
235
+ </ListContainer>
236
+ ) : (
237
+ <EmptyMessage>No pending friend requests</EmptyMessage>
238
+ )}
239
+ </>
240
+ );
241
+
127
242
  // Styled components for FriendList UI
128
243
  const ListWrapper = styled.div`
129
244
  margin-top: 1rem;
@@ -134,7 +249,7 @@ const ListWrapper = styled.div`
134
249
 
135
250
  const ScrollableArea = styled.div`
136
251
  overflow-y: auto;
137
- max-height: 300px; /* Adjust this value based on your layout */
252
+ max-height: 300px;
138
253
  `;
139
254
 
140
255
  const IsOnlineBadge = styled.div<{ $isOnline: boolean }>`
@@ -146,10 +261,86 @@ const IsOnlineBadge = styled.div<{ $isOnline: boolean }>`
146
261
  margin-right: 1rem;
147
262
  `;
148
263
 
149
- const ButtonContainer = styled.div`
264
+ const SearchForm = styled.form`
150
265
  display: flex;
151
- justify-content: center;
152
266
  align-items: center;
153
267
  width: 100%;
154
268
  margin-top: 1rem;
155
269
  `;
270
+
271
+ const SearchInput = styled.input`
272
+ width: 100%;
273
+ background-color: rgba(0, 0, 0, 0.25);
274
+ border: none;
275
+ padding: 0.5rem;
276
+ font-size: ${uiFonts.size.small};
277
+ `;
278
+
279
+ const ListContainer = styled.ul`
280
+ list-style: none;
281
+ padding: 0;
282
+ margin: 0;
283
+ width: 100%;
284
+ max-height: 50vh;
285
+ overflow-y: auto;
286
+
287
+ @media (max-width: 768px) {
288
+ max-height: 90vh;
289
+ }
290
+ `;
291
+
292
+ const ListItem = styled.li`
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: space-between;
296
+ padding: 0.5rem;
297
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
298
+ `;
299
+
300
+ const CharacterName = styled.p`
301
+ font-size: ${uiFonts.size.small};
302
+ margin: 0;
303
+ `;
304
+
305
+ const AcceptRejectActions = styled.div`
306
+ display: flex;
307
+ gap: 0.5rem;
308
+ `;
309
+
310
+ const EmptyMessage = styled.p`
311
+ text-align: center;
312
+ color: #888;
313
+ padding: 1rem;
314
+ font-size: ${uiFonts.size.small};
315
+ `;
316
+
317
+ const EmptyStateContainer = styled.div`
318
+ display: flex;
319
+ flex-direction: column;
320
+ align-items: center;
321
+ justify-content: center;
322
+ padding: 2rem;
323
+ gap: 1rem;
324
+ `;
325
+
326
+ const EmptyStateText = styled.p`
327
+ text-align: center;
328
+ color: #888;
329
+ font-size: ${uiFonts.size.small};
330
+ margin: 0;
331
+ `;
332
+
333
+ const AddFriendCTA = styled.button`
334
+ background-color: ${uiColors.lightGreen};
335
+ color: #000;
336
+ border: none;
337
+ padding: 0.5rem 1rem;
338
+ font-size: ${uiFonts.size.small};
339
+ cursor: pointer;
340
+ border-radius: 4px;
341
+ transition: opacity 0.2s;
342
+
343
+ &:hover {
344
+ opacity: 0.8;
345
+ }
346
+ `;