@rpg-engine/long-bow 0.8.145 → 0.8.146

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.145",
3
+ "version": "0.8.146",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -5,13 +5,13 @@ import {
5
5
  } from '@rpg-engine/shared';
6
6
  import React, { useState } from 'react';
7
7
  import { FaTimes } from 'react-icons/fa';
8
- import styled from 'styled-components';
8
+ import styled, { keyframes } from 'styled-components';
9
9
  import { Dropdown } from '../Dropdown';
10
10
  import { IOptionsProps } from '../Dropdown';
11
11
  import { Input } from '../Input';
12
12
  import { Pager } from '../Pager';
13
13
  import ModalPortal from '../Abstractions/ModalPortal';
14
- import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
14
+ import { BlueprintTable } from './BlueprintTable';
15
15
 
16
16
  export interface IBlueprintSearchModalProps {
17
17
  isOpen: boolean;
@@ -28,6 +28,11 @@ export interface IBlueprintSearchModalProps {
28
28
 
29
29
  const BLUEPRINTS_PER_PAGE = 10;
30
30
 
31
+ const scaleIn = keyframes`
32
+ from { transform: scale(0.85); opacity: 0; }
33
+ to { transform: scale(1); opacity: 1; }
34
+ `;
35
+
31
36
  const typeOptions: IOptionsProps[] = [
32
37
  { id: 1, value: '', option: 'All Types' },
33
38
  ...Object.keys(ItemSubType)
@@ -145,42 +150,12 @@ export const BlueprintSearchModal: React.FC<IBlueprintSearchModalProps> = ({
145
150
  ) : blueprints.length === 0 ? (
146
151
  <EmptyState>No blueprints found</EmptyState>
147
152
  ) : (
148
- <>
149
- <ResultsHeader>
150
- <ColName>Name</ColName>
151
- <ColType>Type</ColType>
152
- <ColTier>Tier</ColTier>
153
- </ResultsHeader>
154
- {blueprints.map((blueprint) => (
155
- <ResultRow
156
- key={blueprint.key}
157
- onPointerDown={() => onSelect(blueprint)}
158
- >
159
- <SpriteWrapper>
160
- <SpriteFromAtlas
161
- atlasJSON={atlasJSON}
162
- atlasIMG={atlasIMG}
163
- spriteKey={blueprint.texturePath || blueprint.key}
164
- width={32}
165
- height={32}
166
- imgScale={2}
167
- centered
168
- />
169
- </SpriteWrapper>
170
- <ColName>
171
- <BlueprintName>{blueprint.name}</BlueprintName>
172
- <BlueprintKey>{blueprint.key}</BlueprintKey>
173
- </ColName>
174
- <ColType>
175
- <TypeText>{blueprint.type}</TypeText>
176
- {blueprint.subType && blueprint.subType !== blueprint.type && (
177
- <SubTypeText>{blueprint.subType}</SubTypeText>
178
- )}
179
- </ColType>
180
- <ColTier>T{blueprint.tier}</ColTier>
181
- </ResultRow>
182
- ))}
183
- </>
153
+ <BlueprintTable
154
+ blueprints={blueprints}
155
+ atlasJSON={atlasJSON}
156
+ atlasIMG={atlasIMG}
157
+ onSelect={onSelect}
158
+ />
184
159
  )}
185
160
  </ResultsWrapper>
186
161
 
@@ -226,12 +201,7 @@ const ModalContent = styled.div`
226
201
  flex-direction: column;
227
202
  gap: 12px;
228
203
  pointer-events: auto;
229
- animation: scaleIn 0.15s ease-out;
230
-
231
- @keyframes scaleIn {
232
- from { transform: scale(0.85); opacity: 0; }
233
- to { transform: scale(1); opacity: 1; }
234
- }
204
+ animation: ${scaleIn} 0.15s ease-out;
235
205
  `;
236
206
 
237
207
  const Header = styled.div`
@@ -284,7 +254,6 @@ const InputWrapper = styled.div`
284
254
 
285
255
  const StyledInput = styled(Input)`
286
256
  flex: 1;
287
- height: 10px;
288
257
  `;
289
258
 
290
259
  const FiltersRow = styled.div`
@@ -306,98 +275,6 @@ const ResultsWrapper = styled.div`
306
275
  border-radius: 4px;
307
276
  `;
308
277
 
309
- const tableRowBase = `
310
- display: grid;
311
- grid-template-columns: 32px 1fr 120px 60px;
312
- align-items: center;
313
- gap: 8px;
314
- padding: 6px 12px;
315
- `;
316
-
317
- const ResultsHeader = styled.div`
318
- ${tableRowBase}
319
- background: rgba(0, 0, 0, 0.4);
320
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
321
- position: sticky;
322
- top: 0;
323
- z-index: 1;
324
-
325
- > * {
326
- font-size: 0.45rem;
327
- color: #888;
328
- text-transform: uppercase;
329
- letter-spacing: 1px;
330
- }
331
- `;
332
-
333
- const ResultRow = styled.div`
334
- ${tableRowBase}
335
- border-bottom: 1px solid rgba(255, 255, 255, 0.04);
336
- cursor: pointer;
337
- transition: background 0.1s;
338
-
339
- &:hover {
340
- background: rgba(245, 158, 11, 0.08);
341
- }
342
-
343
- &:last-child {
344
- border-bottom: none;
345
- }
346
- `;
347
-
348
- const SpriteWrapper = styled.div`
349
- display: flex;
350
- align-items: center;
351
- justify-content: center;
352
- width: 32px;
353
- height: 32px;
354
- `;
355
-
356
- const ColName = styled.div`
357
- display: flex;
358
- flex-direction: column;
359
- gap: 2px;
360
- overflow: hidden;
361
- `;
362
-
363
- const BlueprintName = styled.span`
364
- font-size: 0.5rem;
365
- color: #ddd;
366
- overflow: hidden;
367
- text-overflow: ellipsis;
368
- white-space: nowrap;
369
- `;
370
-
371
- const BlueprintKey = styled.span`
372
- font-size: 0.4rem;
373
- color: #666;
374
- overflow: hidden;
375
- text-overflow: ellipsis;
376
- white-space: nowrap;
377
- `;
378
-
379
- const ColType = styled.div`
380
- display: flex;
381
- flex-direction: column;
382
- gap: 2px;
383
- `;
384
-
385
- const TypeText = styled.span`
386
- font-size: 0.45rem;
387
- color: #aaa;
388
- `;
389
-
390
- const SubTypeText = styled.span`
391
- font-size: 0.4rem;
392
- color: #666;
393
- `;
394
-
395
- const ColTier = styled.div`
396
- font-size: 0.5rem;
397
- color: #f59e0b;
398
- text-align: center;
399
- `;
400
-
401
278
  const EmptyState = styled.div`
402
279
  display: flex;
403
280
  align-items: center;
@@ -0,0 +1,158 @@
1
+ import { IMarketplaceBlueprintSummary } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
5
+
6
+ export interface IBlueprintTableProps {
7
+ blueprints: IMarketplaceBlueprintSummary[];
8
+ atlasJSON: any;
9
+ atlasIMG: any;
10
+ onSelect?: (blueprint: IMarketplaceBlueprintSummary) => void;
11
+ }
12
+
13
+ export const BlueprintTable: React.FC<IBlueprintTableProps> = ({
14
+ blueprints,
15
+ atlasJSON,
16
+ atlasIMG,
17
+ onSelect,
18
+ }) => {
19
+ return (
20
+ <>
21
+ <ResultsHeader>
22
+ <span />
23
+ <ColName>Name</ColName>
24
+ <ColType>Type</ColType>
25
+ <ColTier>Tier</ColTier>
26
+ </ResultsHeader>
27
+ {blueprints.map((blueprint) => (
28
+ <ResultRow
29
+ key={blueprint.key}
30
+ $selectable={!!onSelect}
31
+ onPointerDown={onSelect ? () => onSelect(blueprint) : undefined}
32
+ >
33
+ <SpriteWrapper>
34
+ <SpriteFromAtlas
35
+ atlasJSON={atlasJSON}
36
+ atlasIMG={atlasIMG}
37
+ spriteKey={blueprint.texturePath || blueprint.key}
38
+ width={32}
39
+ height={32}
40
+ imgScale={2}
41
+ centered
42
+ />
43
+ </SpriteWrapper>
44
+ <ColName>
45
+ <BlueprintName>{blueprint.name}</BlueprintName>
46
+ {(blueprint.type || blueprint.subType) && (
47
+ <BlueprintMeta>
48
+ {[blueprint.type, blueprint.subType]
49
+ .filter(Boolean)
50
+ .filter((v, i, arr) => arr.indexOf(v) === i)
51
+ .join(' · ')}
52
+ </BlueprintMeta>
53
+ )}
54
+ </ColName>
55
+ <ColType>
56
+ <TypeText>{blueprint.type}</TypeText>
57
+ {blueprint.subType && blueprint.subType !== blueprint.type && (
58
+ <SubTypeText>{blueprint.subType}</SubTypeText>
59
+ )}
60
+ </ColType>
61
+ <ColTier>T{blueprint.tier}</ColTier>
62
+ </ResultRow>
63
+ ))}
64
+ </>
65
+ );
66
+ };
67
+
68
+ const tableRowBase = `
69
+ display: grid;
70
+ grid-template-columns: 40px 1fr 120px 50px;
71
+ align-items: center;
72
+ gap: 8px;
73
+ padding: 6px 12px;
74
+ `;
75
+
76
+ const ResultsHeader = styled.div`
77
+ ${tableRowBase}
78
+ background: rgba(0, 0, 0, 0.4);
79
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
80
+ position: sticky;
81
+ top: 0;
82
+ z-index: 1;
83
+
84
+ > * {
85
+ font-size: 0.45rem;
86
+ color: #888;
87
+ text-transform: uppercase;
88
+ letter-spacing: 1px;
89
+ }
90
+ `;
91
+
92
+ const ResultRow = styled.div<{ $selectable: boolean }>`
93
+ ${tableRowBase}
94
+ border-bottom: 1px solid rgba(255, 255, 255, 0.04);
95
+ cursor: ${p => p.$selectable ? 'pointer' : 'default'};
96
+ transition: background 0.1s;
97
+
98
+ &:hover {
99
+ background: ${p => p.$selectable ? 'rgba(245, 158, 11, 0.08)' : 'none'};
100
+ }
101
+
102
+ &:last-child {
103
+ border-bottom: none;
104
+ }
105
+ `;
106
+
107
+ const SpriteWrapper = styled.div`
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ width: 40px;
112
+ height: 32px;
113
+ `;
114
+
115
+ const ColName = styled.div`
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: 2px;
119
+ overflow: hidden;
120
+ `;
121
+
122
+ const BlueprintName = styled.span`
123
+ font-size: 0.5rem;
124
+ color: #ddd;
125
+ overflow: hidden;
126
+ text-overflow: ellipsis;
127
+ white-space: nowrap;
128
+ `;
129
+
130
+ const BlueprintMeta = styled.span`
131
+ font-size: 0.4rem;
132
+ color: #666;
133
+ overflow: hidden;
134
+ text-overflow: ellipsis;
135
+ white-space: nowrap;
136
+ `;
137
+
138
+ const ColType = styled.div`
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 2px;
142
+ `;
143
+
144
+ const TypeText = styled.span`
145
+ font-size: 0.45rem;
146
+ color: #aaa;
147
+ `;
148
+
149
+ const SubTypeText = styled.span`
150
+ font-size: 0.4rem;
151
+ color: #666;
152
+ `;
153
+
154
+ const ColTier = styled.div`
155
+ font-size: 0.5rem;
156
+ color: #f59e0b;
157
+ text-align: center;
158
+ `;
@@ -4,7 +4,7 @@ import {
4
4
  import { ShoppingCart } from 'pixelarticons/react/ShoppingCart';
5
5
  import React from 'react';
6
6
  import { FaTimes } from 'react-icons/fa';
7
- import styled from 'styled-components';
7
+ import styled, { keyframes } from 'styled-components';
8
8
  import { Dropdown } from '../Dropdown';
9
9
  import { IOptionsProps } from '../Dropdown';
10
10
  import { Input } from '../Input';
@@ -27,6 +27,11 @@ export interface IBuyOrderDetailsModalProps {
27
27
  atlasIMG: any;
28
28
  }
29
29
 
30
+ const scaleIn = keyframes`
31
+ from { transform: scale(0.85); opacity: 0; }
32
+ to { transform: scale(1); opacity: 1; }
33
+ `;
34
+
30
35
  const rarityOptions: IOptionsProps[] = [
31
36
  { id: 1, value: 'Common', option: 'Common' },
32
37
  { id: 2, value: 'Uncommon', option: 'Uncommon' },
@@ -89,7 +94,7 @@ export const BuyOrderDetailsModal: React.FC<IBuyOrderDetailsModalProps> = ({
89
94
  </SpriteWrapper>
90
95
  <ItemInfo>
91
96
  <ItemName>{blueprint.name}</ItemName>
92
- <ItemKey>{blueprint.type} - {blueprint.subType} - T{blueprint.tier}</ItemKey>
97
+ <ItemMeta>{[blueprint.type, blueprint.subType].filter(Boolean).filter((v, i, arr) => arr.indexOf(v) === i).join(' · ')} · T{blueprint.tier}</ItemMeta>
93
98
  </ItemInfo>
94
99
  </ItemDisplay>
95
100
 
@@ -170,12 +175,7 @@ const ModalContent = styled.div`
170
175
  flex-direction: column;
171
176
  gap: 16px;
172
177
  pointer-events: auto;
173
- animation: scaleIn 0.15s ease-out;
174
-
175
- @keyframes scaleIn {
176
- from { transform: scale(0.85); opacity: 0; }
177
- to { transform: scale(1); opacity: 1; }
178
- }
178
+ animation: ${scaleIn} 0.15s ease-out;
179
179
  `;
180
180
 
181
181
  const Header = styled.div`
@@ -238,7 +238,7 @@ const ItemName = styled.span`
238
238
  white-space: nowrap;
239
239
  `;
240
240
 
241
- const ItemKey = styled.span`
241
+ const ItemMeta = styled.span`
242
242
  font-size: 0.4rem;
243
243
  color: #666;
244
244
  overflow: hidden;
@@ -270,7 +270,6 @@ const Label = styled.label`
270
270
  `;
271
271
 
272
272
  const StyledInput = styled(Input)`
273
- height: 10px;
274
273
  width: 100%;
275
274
  `;
276
275
 
@@ -3,7 +3,7 @@ import {
3
3
  IMarketplaceBuyOrderItem,
4
4
  } from '@rpg-engine/shared';
5
5
  import { Search } from 'pixelarticons/react/Search';
6
- import React from 'react';
6
+ import React, { useEffect, useState } from 'react';
7
7
  import styled from 'styled-components';
8
8
  import { Pager } from '../Pager';
9
9
  import { CTAButton } from '../shared/CTAButton/CTAButton';
@@ -56,23 +56,41 @@ export const BuyOrderPanel: React.FC<IBuyOrderPanelProps> = (props) => {
56
56
  onCancelBuyOrder,
57
57
  } = props;
58
58
 
59
+ // Local blueprint display: cleared immediately on Place Request so the
60
+ // panel returns to "Select Item" without waiting for the consumer to update
61
+ // the prop. Cancel keeps it shown so the user can reopen the modal.
62
+ const [displayedBlueprint, setDisplayedBlueprint] = useState(selectedBlueprint);
63
+ const [isDetailsOpen, setIsDetailsOpen] = useState(!!selectedBlueprint);
64
+
65
+ // Sync when consumer provides a new blueprint (e.g. after search selection)
66
+ useEffect(() => {
67
+ if (selectedBlueprint) {
68
+ setDisplayedBlueprint(selectedBlueprint);
69
+ setIsDetailsOpen(true);
70
+ }
71
+ }, [selectedBlueprint]);
72
+
73
+ // Place request: clear display + close modal, then notify consumer
59
74
  const handleConfirm = () => {
60
75
  onPlaceBuyOrder();
76
+ setDisplayedBlueprint(undefined);
77
+ setIsDetailsOpen(false);
61
78
  onCloseDetails?.();
62
79
  };
63
80
 
81
+ // Cancel: just close the modal, keep blueprint displayed for reopening
64
82
  const handleCloseDetails = () => {
65
- onCloseDetails?.();
83
+ setIsDetailsOpen(false);
66
84
  };
67
85
 
68
86
  return (
69
87
  <PanelWrapper>
70
88
  {/* ── Details modal ── */}
71
- {selectedBlueprint && (
89
+ {displayedBlueprint && isDetailsOpen && (
72
90
  <BuyOrderDetailsModal
73
91
  isOpen
74
92
  onClose={handleCloseDetails}
75
- blueprint={selectedBlueprint}
93
+ blueprint={displayedBlueprint}
76
94
  quantity={currentQuantity}
77
95
  maxPrice={currentMaxPrice}
78
96
  rarity={selectedRarity}
@@ -87,13 +105,13 @@ export const BuyOrderPanel: React.FC<IBuyOrderPanelProps> = (props) => {
87
105
 
88
106
  {/* ── Form row — outside scroll so dropdown isn't clipped ── */}
89
107
  <FormRow>
90
- {selectedBlueprint ? (
108
+ {displayedBlueprint ? (
91
109
  <SelectedBlueprintDisplay onPointerDown={onOpenBlueprintSearch}>
92
110
  <RarityContainer $rarity={selectedRarity}>
93
111
  <SpriteFromAtlas
94
112
  atlasIMG={atlasIMG}
95
113
  atlasJSON={atlasJSON}
96
- spriteKey={selectedBlueprint.texturePath || selectedBlueprint.key}
114
+ spriteKey={displayedBlueprint.texturePath || displayedBlueprint.key}
97
115
  width={32}
98
116
  height={32}
99
117
  imgScale={2}
@@ -101,7 +119,7 @@ export const BuyOrderPanel: React.FC<IBuyOrderPanelProps> = (props) => {
101
119
  />
102
120
  </RarityContainer>
103
121
  <ChangeTextWrapper>
104
- <BlueprintName>{selectedBlueprint.name}</BlueprintName>
122
+ <BlueprintName>{displayedBlueprint.name}</BlueprintName>
105
123
  <ChangeText>change</ChangeText>
106
124
  </ChangeTextWrapper>
107
125
  </SelectedBlueprintDisplay>