@rpg-engine/long-bow 0.8.182 → 0.8.184

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 (29) hide show
  1. package/dist/components/Marketplace/CharacterListingForm.d.ts +4 -2
  2. package/dist/components/Marketplace/CharacterListingModal.d.ts +17 -0
  3. package/dist/components/Marketplace/CharacterMarketplacePanel.d.ts +4 -0
  4. package/dist/components/Marketplace/CharacterMarketplaceRows.d.ts +6 -0
  5. package/dist/components/Marketplace/Marketplace.d.ts +3 -2
  6. package/dist/components/Marketplace/MyCharacterListingsPanel.d.ts +4 -0
  7. package/dist/components/shared/DCRateStrip.d.ts +2 -0
  8. package/dist/components/shared/RadioOption.d.ts +22 -0
  9. package/dist/long-bow.cjs.development.js +476 -371
  10. package/dist/long-bow.cjs.development.js.map +1 -1
  11. package/dist/long-bow.cjs.production.min.js +1 -1
  12. package/dist/long-bow.cjs.production.min.js.map +1 -1
  13. package/dist/long-bow.esm.js +477 -372
  14. package/dist/long-bow.esm.js.map +1 -1
  15. package/dist/stories/Features/marketplace/CharacterListingModal.stories.d.ts +8 -0
  16. package/dist/stories/shared/RadioOption.stories.d.ts +8 -0
  17. package/package.json +1 -1
  18. package/src/components/DCWallet/DCWalletContent.tsx +5 -47
  19. package/src/components/Marketplace/CharacterListingForm.tsx +56 -351
  20. package/src/components/Marketplace/CharacterListingModal.tsx +404 -0
  21. package/src/components/Marketplace/CharacterMarketplacePanel.tsx +112 -67
  22. package/src/components/Marketplace/CharacterMarketplaceRows.tsx +21 -9
  23. package/src/components/Marketplace/Marketplace.tsx +11 -6
  24. package/src/components/Marketplace/MyCharacterListingsPanel.tsx +15 -14
  25. package/src/components/shared/DCRateStrip.tsx +67 -0
  26. package/src/components/shared/RadioOption.tsx +93 -0
  27. package/src/stories/Features/marketplace/CharacterListingModal.stories.tsx +131 -0
  28. package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +39 -23
  29. package/src/stories/shared/RadioOption.stories.tsx +93 -0
@@ -1,4 +1,4 @@
1
- import { ICharacterListing } from '@rpg-engine/shared';
1
+ import { formatDCAmount, ICharacterListing } from '@rpg-engine/shared';
2
2
  import { Coins } from 'pixelarticons/react/Coins';
3
3
  import React from 'react';
4
4
  import styled from 'styled-components';
@@ -9,8 +9,12 @@ import { Ellipsis } from '../shared/Ellipsis';
9
9
 
10
10
  export interface ICharacterMarketplaceRowsProps {
11
11
  listing: ICharacterListing;
12
+ /** Items atlas — for UI sprites like the DC coin */
12
13
  atlasJSON: any;
13
14
  atlasIMG: any;
15
+ /** Entities atlas — for character sprites */
16
+ characterAtlasJSON: any;
17
+ characterAtlasIMG: any;
14
18
  onCharacterBuy?: () => void;
15
19
  onCharacterDelist?: () => void;
16
20
  disabled?: boolean;
@@ -20,6 +24,8 @@ export const CharacterMarketplaceRows: React.FC<ICharacterMarketplaceRowsProps>
20
24
  listing,
21
25
  atlasJSON,
22
26
  atlasIMG,
27
+ characterAtlasJSON,
28
+ characterAtlasIMG,
23
29
  onCharacterBuy,
24
30
  onCharacterDelist,
25
31
  disabled,
@@ -32,8 +38,8 @@ export const CharacterMarketplaceRows: React.FC<ICharacterMarketplaceRowsProps>
32
38
  <SpriteContainer>
33
39
  <CharacterSprite>
34
40
  <SpriteFromAtlas
35
- atlasIMG={atlasIMG}
36
- atlasJSON={atlasJSON}
41
+ atlasIMG={characterAtlasIMG}
42
+ atlasJSON={characterAtlasJSON}
37
43
  spriteKey={`${characterSnapshot.textureKey}/down/standing/0.png`}
38
44
  imgScale={2.5}
39
45
  height={56}
@@ -53,15 +59,15 @@ export const CharacterMarketplaceRows: React.FC<ICharacterMarketplaceRowsProps>
53
59
  <CharacterClass>{characterSnapshot.class}</CharacterClass>
54
60
  )}
55
61
  <PriceRow>
56
- <GoldIcon>
62
+ <DCCoinWrapper>
57
63
  <SpriteFromAtlas
58
64
  atlasIMG={atlasIMG}
59
65
  atlasJSON={atlasJSON}
60
- spriteKey="others/gold-coin-qty-5.png"
66
+ spriteKey="others/definya-coin.png"
61
67
  imgScale={1}
62
68
  />
63
- </GoldIcon>
64
- <GoldPrice>{price.toLocaleString()}</GoldPrice>
69
+ </DCCoinWrapper>
70
+ <DCPrice>{formatDCAmount(price)}</DCPrice>
65
71
  </PriceRow>
66
72
  {isBeingBought && (
67
73
  <PendingBadge>Sale Pending</PendingBadge>
@@ -142,7 +148,7 @@ const PriceRow = styled.div`
142
148
  margin-top: 0.2rem;
143
149
  `;
144
150
 
145
- const GoldIcon = styled.span`
151
+ const DCCoinWrapper = styled.span`
146
152
  display: flex;
147
153
  align-items: center;
148
154
  justify-content: center;
@@ -151,7 +157,7 @@ const GoldIcon = styled.span`
151
157
  left: -0.4rem;
152
158
  `;
153
159
 
154
- const GoldPrice = styled.span`
160
+ const DCPrice = styled.span`
155
161
  font-family: 'Press Start 2P', cursive;
156
162
  font-size: 0.55rem !important;
157
163
  color: #fef08a;
@@ -180,6 +186,8 @@ export interface IGroupedCharacterMarketplaceRowProps {
180
186
  otherListings: ICharacterListing[];
181
187
  atlasJSON: any;
182
188
  atlasIMG: any;
189
+ characterAtlasJSON: any;
190
+ characterAtlasIMG: any;
183
191
  onBuy: (id: string) => void;
184
192
  currentCharacterId?: string;
185
193
  }
@@ -189,6 +197,8 @@ export const GroupedCharacterMarketplaceRow: React.FC<IGroupedCharacterMarketpla
189
197
  otherListings,
190
198
  atlasJSON,
191
199
  atlasIMG,
200
+ characterAtlasJSON,
201
+ characterAtlasIMG,
192
202
  onBuy,
193
203
  currentCharacterId,
194
204
  }) => {
@@ -201,6 +211,8 @@ export const GroupedCharacterMarketplaceRow: React.FC<IGroupedCharacterMarketpla
201
211
  listing={listing}
202
212
  atlasJSON={atlasJSON}
203
213
  atlasIMG={atlasIMG}
214
+ characterAtlasJSON={characterAtlasJSON}
215
+ characterAtlasIMG={characterAtlasIMG}
204
216
  onCharacterBuy={isOwnListing ? undefined : () => onBuy(listing._id)}
205
217
  disabled={isOwnListing}
206
218
  />
@@ -117,6 +117,9 @@ export interface IMarketPlaceProps {
117
117
  walletProps?: IDCWalletContentProps;
118
118
  showWalletTab?: boolean;
119
119
 
120
+ /** Entities atlas for character sprites (separate from the items atlasIMG/atlasJSON) */
121
+ characterAtlasIMG?: any;
122
+ characterAtlasJSON?: any;
120
123
  // Character marketplace props
121
124
  characterListings?: ICharacterListing[];
122
125
  characterListingsTotal?: number;
@@ -131,8 +134,6 @@ export interface IMarketPlaceProps {
131
134
  onCharacterList?: (characterId: string, price: number) => void;
132
135
  onCharacterDelist?: (listingId: string) => void;
133
136
  accountCharacters?: ICharacter[];
134
- onCharacterSelectToList?: (character: ICharacter) => void;
135
- selectedCharacterToList?: ICharacter | null;
136
137
  characterListingsLoading?: boolean;
137
138
  characterNameFilter?: string;
138
139
  onCharacterNameFilterChange?: (name: string) => void;
@@ -195,11 +196,11 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
195
196
  onCharacterDelist,
196
197
  onCharacterList,
197
198
  accountCharacters = [],
198
- onCharacterSelectToList,
199
- selectedCharacterToList,
200
199
  characterListingsLoading = false,
201
200
  characterNameFilter = '',
202
201
  onCharacterNameFilterChange,
202
+ characterAtlasIMG,
203
+ characterAtlasJSON,
203
204
  } = props;
204
205
 
205
206
  const [activeTab, setActiveTab] = useState<ActiveTab>('marketplace');
@@ -323,6 +324,8 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
323
324
  onCharacterBuy={onCharacterBuy ?? (() => {})}
324
325
  atlasJSON={props.atlasJSON}
325
326
  atlasIMG={props.atlasIMG}
327
+ characterAtlasJSON={characterAtlasJSON ?? props.atlasJSON}
328
+ characterAtlasIMG={characterAtlasIMG ?? props.atlasIMG}
326
329
  enableHotkeys={props.enableHotkeys}
327
330
  disableHotkeys={props.disableHotkeys}
328
331
  nameFilter={characterNameFilter}
@@ -341,6 +344,8 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
341
344
  onCharacterDelist={onCharacterDelist ?? (() => {})}
342
345
  atlasJSON={props.atlasJSON}
343
346
  atlasIMG={props.atlasIMG}
347
+ characterAtlasJSON={characterAtlasJSON ?? props.atlasJSON}
348
+ characterAtlasIMG={characterAtlasIMG ?? props.atlasIMG}
344
349
  enableHotkeys={props.enableHotkeys}
345
350
  disableHotkeys={props.disableHotkeys}
346
351
  />
@@ -349,11 +354,11 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
349
354
  {characterSubTab === 'list' && (
350
355
  <CharacterListingForm
351
356
  accountCharacters={accountCharacters ?? []}
352
- selectedCharacterToList={selectedCharacterToList ?? null}
353
- onCharacterSelectToList={onCharacterSelectToList ?? (() => {})}
354
357
  onCharacterList={onCharacterList ?? (() => {})}
355
358
  atlasJSON={props.atlasJSON}
356
359
  atlasIMG={props.atlasIMG}
360
+ characterAtlasJSON={characterAtlasJSON ?? props.atlasJSON}
361
+ characterAtlasIMG={characterAtlasIMG ?? props.atlasIMG}
357
362
  enableHotkeys={props.enableHotkeys}
358
363
  disableHotkeys={props.disableHotkeys}
359
364
  />
@@ -1,4 +1,4 @@
1
- import { ICharacterListing } from '@rpg-engine/shared';
1
+ import { formatDCAmount, ICharacterListing } from '@rpg-engine/shared';
2
2
  import { Delete } from 'pixelarticons/react/Delete';
3
3
  import { User } from 'pixelarticons/react/User';
4
4
  import React, { useEffect, useRef, useState } from 'react';
@@ -14,8 +14,12 @@ export interface IMyCharacterListingsPanelProps {
14
14
  itemsPerPage: number;
15
15
  onPageChange: (page: number) => void;
16
16
  onCharacterDelist: (listingId: string) => void;
17
+ /** Items atlas — for UI sprites like the DC coin */
17
18
  atlasJSON: any;
18
19
  atlasIMG: any;
20
+ /** Entities atlas — for character sprites */
21
+ characterAtlasJSON: any;
22
+ characterAtlasIMG: any;
19
23
  enableHotkeys?: () => void;
20
24
  disableHotkeys?: () => void;
21
25
  }
@@ -29,6 +33,8 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
29
33
  onCharacterDelist,
30
34
  atlasJSON,
31
35
  atlasIMG,
36
+ characterAtlasJSON,
37
+ characterAtlasIMG,
32
38
  enableHotkeys,
33
39
  }) => {
34
40
  const [delistingListingId, setDelistingListingId] = useState<string | null>(null);
@@ -50,9 +56,6 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
50
56
  }
51
57
  };
52
58
 
53
- const getListingPrice = (listing: ICharacterListing): string => {
54
- return listing.price.toLocaleString();
55
- };
56
59
 
57
60
  const getFormattedDate = (date: Date) => {
58
61
  return new Date(date).toLocaleDateString();
@@ -84,8 +87,8 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
84
87
  <CharacterListingCard key={listing._id}>
85
88
  <CharacterSprite>
86
89
  <SpriteFromAtlas
87
- atlasIMG={atlasIMG}
88
- atlasJSON={atlasJSON}
90
+ atlasIMG={characterAtlasIMG}
91
+ atlasJSON={characterAtlasJSON}
89
92
  spriteKey={`${listing.characterSnapshot.textureKey}/down/standing/0.png`}
90
93
  imgScale={3}
91
94
  height={64}
@@ -96,15 +99,15 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
96
99
  <CharacterName>{listing.characterSnapshot.name || 'Unknown'}</CharacterName>
97
100
  <CharacterMeta>Level {listing.characterSnapshot.level}</CharacterMeta>
98
101
  <ListingPrice>
99
- <GoldIcon>
102
+ <DCCoinWrapper>
100
103
  <SpriteFromAtlas
101
104
  atlasIMG={atlasIMG}
102
105
  atlasJSON={atlasJSON}
103
- spriteKey="others/gold-coin-qty-5.png"
106
+ spriteKey="others/definya-coin.png"
104
107
  imgScale={1}
105
108
  />
106
- </GoldIcon>
107
- {getListingPrice(listing)} Gold
109
+ </DCCoinWrapper>
110
+ {formatDCAmount(listing.price)} DC
108
111
  </ListingPrice>
109
112
  <ListingMeta>Listed {getFormattedDate(listing.createdAt)}</ListingMeta>
110
113
  {listing.isBeingBought && (
@@ -216,13 +219,11 @@ const ListingPrice = styled.div`
216
219
  color: #fef08a;
217
220
  `;
218
221
 
219
- const GoldIcon = styled.span`
222
+ const DCCoinWrapper = styled.span`
220
223
  display: flex;
221
224
  align-items: center;
222
225
  justify-content: center;
223
- position: relative;
224
- top: -0.4rem;
225
- left: -0.3rem;
226
+ flex-shrink: 0;
226
227
  `;
227
228
 
228
229
  const ListingMeta = styled.span`
@@ -0,0 +1,67 @@
1
+ import { DC_TO_GOLD_SWAP_RATE } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ /** 100 DC = $1 USD */
6
+ const DC_TO_USD = 100;
7
+
8
+ export const DCRateStrip: React.FC = () => (
9
+ <RateStrip>
10
+ <RateItem>
11
+ <RateNum>1 DC</RateNum>
12
+ <RateSep>=</RateSep>
13
+ <RateVal>{DC_TO_GOLD_SWAP_RATE.toLocaleString()} Gold</RateVal>
14
+ </RateItem>
15
+ <RateDivider />
16
+ <RateItem>
17
+ <RateNum>{DC_TO_USD} DC</RateNum>
18
+ <RateSep>=</RateSep>
19
+ <RateVal>1 USD</RateVal>
20
+ </RateItem>
21
+ <RateDivider />
22
+ <RateItem>
23
+ <RateNum>1 USD</RateNum>
24
+ <RateSep>=</RateSep>
25
+ <RateVal>{(DC_TO_USD * DC_TO_GOLD_SWAP_RATE / 1000).toFixed(0)}K Gold</RateVal>
26
+ </RateItem>
27
+ </RateStrip>
28
+ );
29
+
30
+ const RateStrip = styled.div`
31
+ display: flex;
32
+ align-items: center;
33
+ border-top: 1px solid rgba(245, 158, 11, 0.1);
34
+ padding-top: 8px;
35
+ `;
36
+
37
+ const RateItem = styled.div`
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 5px;
41
+ flex: 1;
42
+ justify-content: center;
43
+ `;
44
+
45
+ const RateDivider = styled.div`
46
+ width: 1px;
47
+ height: 12px;
48
+ background: rgba(245, 158, 11, 0.2);
49
+ `;
50
+
51
+ const RateNum = styled.span`
52
+ font-family: 'Press Start 2P', cursive !important;
53
+ font-size: 6px !important;
54
+ color: rgba(255, 255, 255, 0.5) !important;
55
+ `;
56
+
57
+ const RateSep = styled.span`
58
+ font-family: 'Press Start 2P', cursive !important;
59
+ font-size: 6px !important;
60
+ color: rgba(255, 255, 255, 0.2) !important;
61
+ `;
62
+
63
+ const RateVal = styled.span`
64
+ font-family: 'Press Start 2P', cursive !important;
65
+ font-size: 6px !important;
66
+ color: rgba(254, 240, 138, 0.6) !important;
67
+ `;
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ export interface IRadioOptionProps {
5
+ selected: boolean;
6
+ disabled?: boolean;
7
+ onSelect: () => void;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ /**
12
+ * A selectable row with an amber radio circle indicator.
13
+ * Used for single-select option lists throughout the Marketplace UI.
14
+ * Export `RadioCircle` separately so consumers can compose custom layouts.
15
+ */
16
+ export const RadioOption: React.FC<IRadioOptionProps> = ({
17
+ selected,
18
+ disabled = false,
19
+ onSelect,
20
+ children,
21
+ }) => {
22
+ const handleClick = () => {
23
+ if (!disabled) {
24
+ onSelect();
25
+ }
26
+ };
27
+
28
+ return (
29
+ <RadioOptionContainer
30
+ $selected={selected}
31
+ $disabled={disabled}
32
+ onClick={handleClick}
33
+ role="radio"
34
+ aria-checked={selected}
35
+ aria-disabled={disabled}
36
+ >
37
+ <RadioCircle $selected={selected} />
38
+ {children}
39
+ </RadioOptionContainer>
40
+ );
41
+ };
42
+
43
+ const RadioOptionContainer = styled.div<{ $selected: boolean; $disabled?: boolean }>`
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 12px;
47
+ padding: 10px 12px;
48
+ border: 1px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.15)')};
49
+ border-radius: 6px;
50
+ background: ${({ $selected }) => ($selected ? 'rgba(245,158,11,0.1)' : 'transparent')};
51
+ cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
52
+ opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)};
53
+ transition: border-color 0.15s, background 0.15s;
54
+
55
+ &:hover {
56
+ border-color: ${({ $disabled }) => ($disabled ? 'rgba(255,255,255,0.15)' : '#f59e0b')};
57
+ }
58
+ `;
59
+
60
+ export const RadioCircle = styled.div<{ $selected: boolean }>`
61
+ width: 16px;
62
+ height: 16px;
63
+ border-radius: 50%;
64
+ border: 2px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.4)')};
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ flex-shrink: 0;
69
+
70
+ &::after {
71
+ content: '';
72
+ width: 8px;
73
+ height: 8px;
74
+ border-radius: 50%;
75
+ background: #f59e0b;
76
+ opacity: ${({ $selected }) => ($selected ? 1 : 0)};
77
+ transition: opacity 0.15s;
78
+ }
79
+ `;
80
+
81
+ /** Convenience wrapper for option label text with RPGUI font override. */
82
+ export const RadioOptionLabel = styled.span<{ $disabled?: boolean }>`
83
+ font-family: 'Press Start 2P', cursive !important;
84
+ font-size: 0.65rem !important;
85
+ color: ${({ $disabled }) => ($disabled ? 'rgba(255,255,255,0.4)' : '#ffffff')} !important;
86
+ `;
87
+
88
+ /** Convenience wrapper for option sub-text with RPGUI font override. */
89
+ export const RadioOptionSub = styled.span`
90
+ font-family: 'Press Start 2P', cursive !important;
91
+ font-size: 0.55rem !important;
92
+ color: rgba(255, 255, 255, 0.5) !important;
93
+ `;
@@ -0,0 +1,131 @@
1
+ import { ICharacter } from '@rpg-engine/shared';
2
+ import { Meta, Story } from '@storybook/react';
3
+ import React, { useState } from 'react';
4
+ import { CharacterListingModal, ICharacterListingModalProps } from '../../../components/Marketplace/CharacterListingModal';
5
+ import { RPGUIRoot } from '../../..';
6
+ import entitiesAtlasJSON from '../../../mocks/atlas/entities/entities.json';
7
+ import entitiesAtlasIMG from '../../../mocks/atlas/entities/entities.png';
8
+ import itemsAtlasJSON from '../../../mocks/atlas/items/items.json';
9
+ import itemsAtlasIMG from '../../../mocks/atlas/items/items.png';
10
+
11
+ const meta: Meta<ICharacterListingModalProps> = {
12
+ title: 'Features/Marketplace/CharacterListingModal',
13
+ component: CharacterListingModal,
14
+ };
15
+
16
+ export default meta;
17
+
18
+ const mockCharacters: ICharacter[] = [
19
+ {
20
+ _id: 'char-1',
21
+ name: 'Sir Galahad',
22
+ textureKey: 'black-knight',
23
+ isListedForSale: false,
24
+ tradedAt: undefined,
25
+ skills: { level: 25 } as any,
26
+ } as ICharacter,
27
+ {
28
+ _id: 'char-2',
29
+ name: 'Merlin Jr.',
30
+ textureKey: 'pink-mage-1',
31
+ isListedForSale: false,
32
+ tradedAt: undefined,
33
+ skills: { level: 30 } as any,
34
+ } as ICharacter,
35
+ {
36
+ _id: 'char-3',
37
+ name: 'ShadowStep',
38
+ textureKey: 'redhair-girl-1',
39
+ isListedForSale: false,
40
+ tradedAt: undefined,
41
+ skills: { level: 18 } as any,
42
+ } as ICharacter,
43
+ ];
44
+
45
+ const mockCharactersWithListed: ICharacter[] = [
46
+ ...mockCharacters,
47
+ {
48
+ _id: 'char-4',
49
+ name: 'AlreadyListed',
50
+ textureKey: 'dragon-knight',
51
+ isListedForSale: true,
52
+ tradedAt: undefined,
53
+ skills: { level: 40 } as any,
54
+ } as ICharacter,
55
+ ];
56
+
57
+ const ModalWrapper: React.FC<Omit<ICharacterListingModalProps, 'isOpen' | 'onClose' | 'onCharacterList'>> = (props) => {
58
+ const [isOpen, setIsOpen] = useState(true);
59
+ const [lastListed, setLastListed] = useState<string | null>(null);
60
+
61
+ return (
62
+ <RPGUIRoot>
63
+ <div style={{ padding: '20px', fontFamily: 'monospace', fontSize: '12px', color: '#fff' }}>
64
+ {lastListed ? (
65
+ <p>✅ Listed: {lastListed}</p>
66
+ ) : (
67
+ <p>Click "List" to submit the form</p>
68
+ )}
69
+ <button
70
+ style={{ marginTop: 8, padding: '6px 12px', cursor: 'pointer' }}
71
+ onClick={() => setIsOpen(true)}
72
+ >
73
+ Reopen Modal
74
+ </button>
75
+ </div>
76
+ <CharacterListingModal
77
+ {...props}
78
+ isOpen={isOpen}
79
+ onClose={() => setIsOpen(false)}
80
+ onCharacterList={(id, price) => {
81
+ setLastListed(`ID=${id}, Price=${price} DC`);
82
+ setIsOpen(false);
83
+ }}
84
+ />
85
+ </RPGUIRoot>
86
+ );
87
+ };
88
+
89
+ export const WithMultipleCharacters: Story = () => (
90
+ <ModalWrapper
91
+ accountCharacters={mockCharacters}
92
+ atlasJSON={itemsAtlasJSON}
93
+ atlasIMG={itemsAtlasIMG}
94
+ characterAtlasJSON={entitiesAtlasJSON}
95
+ characterAtlasIMG={entitiesAtlasIMG}
96
+ />
97
+ );
98
+ WithMultipleCharacters.storyName = 'Multiple eligible characters';
99
+
100
+ export const WithAlreadyListedFiltered: Story = () => (
101
+ <ModalWrapper
102
+ accountCharacters={mockCharactersWithListed}
103
+ atlasJSON={itemsAtlasJSON}
104
+ atlasIMG={itemsAtlasIMG}
105
+ characterAtlasJSON={entitiesAtlasJSON}
106
+ characterAtlasIMG={entitiesAtlasIMG}
107
+ />
108
+ );
109
+ WithAlreadyListedFiltered.storyName = 'Some characters already listed (filtered out)';
110
+
111
+ export const NoEligibleCharacters: Story = () => (
112
+ <ModalWrapper
113
+ accountCharacters={[]}
114
+ atlasJSON={itemsAtlasJSON}
115
+ atlasIMG={itemsAtlasIMG}
116
+ characterAtlasJSON={entitiesAtlasJSON}
117
+ characterAtlasIMG={entitiesAtlasIMG}
118
+ />
119
+ );
120
+ NoEligibleCharacters.storyName = 'No eligible characters';
121
+
122
+ export const SingleCharacter: Story = () => (
123
+ <ModalWrapper
124
+ accountCharacters={[mockCharacters[0]]}
125
+ atlasJSON={itemsAtlasJSON}
126
+ atlasIMG={itemsAtlasIMG}
127
+ characterAtlasJSON={entitiesAtlasJSON}
128
+ characterAtlasIMG={entitiesAtlasIMG}
129
+ />
130
+ );
131
+ SingleCharacter.storyName = 'Single eligible character';