@rpg-engine/long-bow 0.8.141 → 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.
Files changed (64) hide show
  1. package/dist/components/Marketplace/BlueprintSearchModal.d.ts +17 -0
  2. package/dist/components/Marketplace/BlueprintTable.d.ts +9 -0
  3. package/dist/components/Marketplace/BuyOrderDetailsModal.d.ts +17 -0
  4. package/dist/components/Marketplace/BuyOrderPanel.d.ts +24 -0
  5. package/dist/components/Marketplace/BuyOrderRows.d.ts +13 -0
  6. package/dist/components/Marketplace/BuyPanel.d.ts +9 -1
  7. package/dist/components/Marketplace/HistoryPanel.d.ts +18 -0
  8. package/dist/components/Marketplace/ManagmentPanel.d.ts +3 -2
  9. package/dist/components/Marketplace/Marketplace.d.ts +35 -2
  10. package/dist/components/Marketplace/MarketplaceSettingsPanel.d.ts +2 -1
  11. package/dist/components/Store/PaymentMethodModal.d.ts +1 -0
  12. package/dist/components/shared/LabelPill/LabelPill.d.ts +9 -0
  13. package/dist/components/shared/LabelPill/index.d.ts +1 -0
  14. package/dist/components/shared/SegmentedToggle/SegmentedToggle.d.ts +12 -0
  15. package/dist/components/shared/SegmentedToggle/index.d.ts +1 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/long-bow.cjs.development.js +11573 -1288
  18. package/dist/long-bow.cjs.development.js.map +1 -1
  19. package/dist/long-bow.cjs.production.min.js +1 -1
  20. package/dist/long-bow.cjs.production.min.js.map +1 -1
  21. package/dist/long-bow.esm.js +11562 -1290
  22. package/dist/long-bow.esm.js.map +1 -1
  23. package/dist/stories/Features/marketplace/BlueprintSearchModal.stories.d.ts +1 -0
  24. package/dist/stories/Features/marketplace/BuyOrderPanel.stories.d.ts +1 -0
  25. package/dist/stories/Features/marketplace/BuyOrderRows.stories.d.ts +1 -0
  26. package/dist/stories/Features/marketplace/HistoryPanel.stories.d.ts +1 -0
  27. package/dist/stories/Features/trading/MarketplaceRows.stories.d.ts +2 -1
  28. package/dist/stories/UI/buttonsAndInputs/SegmentedToggle.stories.d.ts +6 -0
  29. package/dist/stories/UI/text/LabelPill.stories.d.ts +7 -0
  30. package/dist/utils/atlasUtils.d.ts +2 -0
  31. package/package.json +2 -2
  32. package/src/components/Marketplace/BlueprintSearchModal.tsx +295 -0
  33. package/src/components/Marketplace/BlueprintTable.tsx +158 -0
  34. package/src/components/Marketplace/BuyOrderDetailsModal.tsx +306 -0
  35. package/src/components/Marketplace/BuyOrderPanel.tsx +284 -0
  36. package/src/components/Marketplace/BuyOrderRows.tsx +287 -0
  37. package/src/components/Marketplace/BuyPanel.tsx +406 -166
  38. package/src/components/Marketplace/HistoryPanel.tsx +422 -0
  39. package/src/components/Marketplace/ManagmentPanel.tsx +13 -15
  40. package/src/components/Marketplace/Marketplace.tsx +181 -30
  41. package/src/components/Marketplace/MarketplaceBuyModal.tsx +1 -0
  42. package/src/components/Marketplace/MarketplaceRows.tsx +41 -10
  43. package/src/components/Marketplace/MarketplaceSettingsPanel.tsx +4 -3
  44. package/src/components/Store/CartView.tsx +11 -0
  45. package/src/components/Store/PaymentMethodModal.tsx +26 -9
  46. package/src/components/shared/LabelPill/LabelPill.tsx +45 -0
  47. package/src/components/shared/LabelPill/index.ts +1 -0
  48. package/src/components/shared/SegmentedToggle/SegmentedToggle.tsx +61 -0
  49. package/src/components/shared/SegmentedToggle/index.ts +1 -0
  50. package/src/components/shared/SpriteFromAtlas.tsx +7 -2
  51. package/src/index.tsx +4 -0
  52. package/src/mocks/atlas/items/items.json +33998 -25238
  53. package/src/mocks/atlas/items/items.png +0 -0
  54. package/src/mocks/itemContainer.mocks.ts +31 -0
  55. package/src/stories/Features/marketplace/BlueprintSearchModal.stories.tsx +145 -0
  56. package/src/stories/Features/marketplace/BuyOrderPanel.stories.tsx +323 -0
  57. package/src/stories/Features/marketplace/BuyOrderRows.stories.tsx +116 -0
  58. package/src/stories/Features/marketplace/HistoryPanel.stories.tsx +157 -0
  59. package/src/stories/Features/trading/Marketplace.stories.tsx +130 -0
  60. package/src/stories/Features/trading/MarketplaceRows.stories.tsx +11 -0
  61. package/src/stories/UI/buttonsAndInputs/SegmentedToggle.stories.tsx +54 -0
  62. package/src/stories/UI/text/LabelPill.stories.tsx +43 -0
  63. package/src/utils/__test__/atlasUtils.spec.ts +26 -0
  64. package/src/utils/atlasUtils.ts +80 -0
@@ -0,0 +1,157 @@
1
+ import {
2
+ IMarketplaceTransaction,
3
+ MarketplaceTransactionType,
4
+ } from '@rpg-engine/shared';
5
+ import type { Meta, StoryObj } from '@storybook/react';
6
+ import React from 'react';
7
+ import { HistoryPanel } from '../../../components/Marketplace/HistoryPanel';
8
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
9
+ import atlasJSON from '../../../mocks/atlas/items/items.json';
10
+ import atlasIMG from '../../../mocks/atlas/items/items.png';
11
+
12
+ const meta = {
13
+ title: 'Features/Marketplace/HistoryPanel',
14
+ component: HistoryPanel,
15
+ parameters: {
16
+ layout: 'centered',
17
+ },
18
+ decorators: [
19
+ (Story) => (
20
+ <RPGUIRoot>
21
+ <div style={{ width: '800px' }}>
22
+ <Story />
23
+ </div>
24
+ </RPGUIRoot>
25
+ ),
26
+ ],
27
+ } satisfies Meta<typeof HistoryPanel>;
28
+
29
+ export default meta;
30
+ type Story = StoryObj<typeof HistoryPanel>;
31
+
32
+ const now = new Date();
33
+ const daysAgo = (days: number): Date =>
34
+ new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
35
+
36
+ const mockTransactions: IMarketplaceTransaction[] = [
37
+ {
38
+ owner: 'character-1',
39
+ type: MarketplaceTransactionType.Purchase,
40
+ goldAmount: 500,
41
+ itemKey: 'items/iron-sword.png',
42
+ itemName: 'Iron Sword',
43
+ counterpartName: 'SomePlayer',
44
+ currency: 'gold',
45
+ createdAt: daysAgo(0),
46
+ updatedAt: daysAgo(0),
47
+ },
48
+ {
49
+ owner: 'character-1',
50
+ type: MarketplaceTransactionType.Sale,
51
+ goldAmount: 1200,
52
+ itemKey: 'items/leather-armor.png',
53
+ itemName: 'Leather Armor',
54
+ counterpartName: 'AnotherPlayer',
55
+ currency: 'dc', // DC sale
56
+ createdAt: daysAgo(1),
57
+ updatedAt: daysAgo(1),
58
+ },
59
+ {
60
+ owner: 'character-1',
61
+ type: MarketplaceTransactionType.Purchase,
62
+ goldAmount: 275000,
63
+ itemKey: 'items/angelic-sword.png',
64
+ itemName: 'Angelic Sword',
65
+ counterpartName: 'DCBuyer',
66
+ currency: 'dc', // DC purchase
67
+ createdAt: daysAgo(2),
68
+ updatedAt: daysAgo(2),
69
+ },
70
+ {
71
+ owner: 'character-1',
72
+ type: MarketplaceTransactionType.ListingFee,
73
+ goldAmount: 60,
74
+ itemKey: 'items/leather-armor.png',
75
+ itemName: 'Leather Armor',
76
+ createdAt: daysAgo(1),
77
+ updatedAt: daysAgo(1),
78
+ },
79
+ {
80
+ owner: 'character-1',
81
+ type: MarketplaceTransactionType.Withdrawal,
82
+ goldAmount: 1200,
83
+ createdAt: daysAgo(2),
84
+ updatedAt: daysAgo(2),
85
+ },
86
+ {
87
+ owner: 'character-1',
88
+ type: MarketplaceTransactionType.Expired,
89
+ goldAmount: 0,
90
+ itemKey: 'items/magic-staff.png',
91
+ itemName: 'Magic Staff',
92
+ createdAt: daysAgo(3),
93
+ updatedAt: daysAgo(3),
94
+ },
95
+ {
96
+ owner: 'character-1',
97
+ type: MarketplaceTransactionType.Unlisted,
98
+ goldAmount: 0,
99
+ itemKey: 'items/shield.png',
100
+ itemName: 'Wooden Shield',
101
+ createdAt: daysAgo(4),
102
+ updatedAt: daysAgo(4),
103
+ },
104
+ {
105
+ owner: 'character-1',
106
+ type: MarketplaceTransactionType.BuyOrderPlaced,
107
+ goldAmount: 800,
108
+ itemKey: 'items/angelic-sword.png',
109
+ itemName: 'Angelic Sword',
110
+ createdAt: daysAgo(5),
111
+ updatedAt: daysAgo(5),
112
+ },
113
+ {
114
+ owner: 'character-1',
115
+ type: MarketplaceTransactionType.BuyOrderFulfilled,
116
+ goldAmount: 800,
117
+ itemKey: 'items/angelic-sword.png',
118
+ itemName: 'Angelic Sword',
119
+ counterpartName: 'Fulfiller',
120
+ createdAt: daysAgo(6),
121
+ updatedAt: daysAgo(6),
122
+ },
123
+ ];
124
+
125
+ export const Default: Story = {
126
+ render: () => (
127
+ <HistoryPanel
128
+ transactions={mockTransactions}
129
+ totalCount={mockTransactions.length}
130
+ currentPage={1}
131
+ itemsPerPage={10}
132
+ selectedType="All"
133
+ onTypeChange={(type) => console.log('type change:', type)}
134
+ onPageChange={(page) => console.log('page change:', page)}
135
+ atlasJSON={atlasJSON}
136
+ atlasIMG={atlasIMG}
137
+ dcToGoldSwapRate={550000} // 1 DC = 550,000 GOLD
138
+ />
139
+ ),
140
+ };
141
+
142
+ export const EmptyState: Story = {
143
+ render: () => (
144
+ <HistoryPanel
145
+ transactions={[]}
146
+ totalCount={0}
147
+ currentPage={1}
148
+ itemsPerPage={10}
149
+ selectedType="All"
150
+ onTypeChange={(type) => console.log('type change:', type)}
151
+ onPageChange={(page) => console.log('page change:', page)}
152
+ atlasJSON={atlasJSON}
153
+ atlasIMG={atlasIMG}
154
+ dcToGoldSwapRate={550000}
155
+ />
156
+ ),
157
+ };
@@ -1,3 +1,10 @@
1
+ import {
2
+ IMarketplaceBlueprintSummary,
3
+ IMarketplaceBuyOrderItem,
4
+ IMarketplaceTransaction,
5
+ MarketplaceBuyOrderStatus,
6
+ MarketplaceTransactionType,
7
+ } from '@rpg-engine/shared';
1
8
  import { Meta, Story } from '@storybook/react';
2
9
  import React from 'react';
3
10
  import { RPGUIRoot } from '../../..';
@@ -14,8 +21,74 @@ const meta: Meta = {
14
21
 
15
22
  export default meta;
16
23
 
24
+ const now = new Date();
25
+ const daysAgo = (days: number): Date =>
26
+ new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
27
+
28
+ const mockBlueprints: IMarketplaceBlueprintSummary[] = [
29
+ { key: 'items/broad-sword', name: 'Broad Sword', type: 'Weapon', subType: 'Sword', tier: 1, textureAtlas: 'items', texturePath: 'swords/broad-sword.png' },
30
+ { key: 'items/angelic-sword', name: 'Angelic Sword', type: 'Weapon', subType: 'Sword', tier: 5, textureAtlas: 'items', texturePath: 'swords/angelic-sword.png', rarity: 'Epic' },
31
+ { key: 'items/leather-armor', name: 'Leather Armor', type: 'Armor', subType: 'Body', tier: 1, textureAtlas: 'items', texturePath: 'armors/leather-armor.png' },
32
+ { key: 'items/abyssal-tide-staff', name: 'Abyssal Tide Staff', type: 'Weapon', subType: 'Staff', tier: 3, textureAtlas: 'items', texturePath: 'staffs/abyssal-tide-staff.png', rarity: 'Rare' },
33
+ { key: 'items/wooden-shield', name: 'Wooden Shield', type: 'Armor', subType: 'Shield', tier: 1, textureAtlas: 'items', texturePath: 'shields/wooden-shield.png' },
34
+ { key: 'items/fire-wand', name: 'Fire Wand', type: 'Weapon', subType: 'Wand', tier: 2, textureAtlas: 'items', texturePath: 'staffs/fire-wand.png' },
35
+ { key: 'items/barbarian-helmet', name: 'Barbarian Helmet', type: 'Armor', subType: 'Head', tier: 2, textureAtlas: 'items', texturePath: 'helmets/barbarian-helmet.png' },
36
+ { key: 'items/greater-life-potion', name: 'Greater Life Potion', type: 'Consumable', subType: 'Potion', tier: 1, textureAtlas: 'items', texturePath: 'potions/greater-life-potion.png' },
37
+ ];
38
+
39
+ const mockYourBuyOrders: IMarketplaceBuyOrderItem[] = [
40
+ { _id: 'bo-1', owner: 'player-1', itemBlueprintKey: 'items/abyssal-edge', itemRarity: 'Rare', maxPrice: 500, escrowedGold: 500, fee: 25, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
41
+ { _id: 'bo-2', owner: 'player-1', itemBlueprintKey: 'items/angelic-sword', maxPrice: 2000, escrowedGold: 2000, fee: 100, status: MarketplaceBuyOrderStatus.Fulfilled, fulfilledBy: 'DarkKnight42', createdAt: daysAgo(14), updatedAt: daysAgo(1) },
42
+ { _id: 'bo-3', owner: 'player-1', itemBlueprintKey: 'items/leather-armor', maxPrice: 300, escrowedGold: 0, fee: 15, status: MarketplaceBuyOrderStatus.Expired, createdAt: daysAgo(35), updatedAt: daysAgo(7) },
43
+ ];
44
+
45
+ const mockOpenBuyOrders: IMarketplaceBuyOrderItem[] = [
46
+ { _id: 'obo-1', owner: 'player-2', itemBlueprintKey: 'items/abyssal-tide-staff', itemRarity: 'Epic', maxPrice: 1500, escrowedGold: 1500, fee: 75, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
47
+ { _id: 'obo-2', owner: 'player-3', itemBlueprintKey: 'items/wooden-shield', maxPrice: 200, escrowedGold: 200, fee: 10, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(5), updatedAt: daysAgo(5) },
48
+ { _id: 'obo-3', owner: 'player-4', itemBlueprintKey: 'items/fire-wand', itemRarity: 'Rare', maxPrice: 800, escrowedGold: 800, fee: 40, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
49
+ ];
50
+
51
+ const mockTransactions: IMarketplaceTransaction[] = [
52
+ { owner: 'player-1', type: MarketplaceTransactionType.Purchase, goldAmount: 450, itemKey: 'items/broad-sword', itemName: 'Broad Sword', counterpartName: 'MerchantKing', currency: 'gold', createdAt: daysAgo(1), updatedAt: daysAgo(1) },
53
+ { owner: 'player-1', type: MarketplaceTransactionType.Sale, goldAmount: 1200, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'ShadowBlade', currency: 'dc', createdAt: daysAgo(2), updatedAt: daysAgo(2) },
54
+ { owner: 'player-1', type: MarketplaceTransactionType.Purchase, goldAmount: 275000, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'DCBuyer', currency: 'dc', createdAt: daysAgo(3), updatedAt: daysAgo(3) },
55
+ { owner: 'player-1', type: MarketplaceTransactionType.ListingFee, goldAmount: 60, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', createdAt: daysAgo(2), updatedAt: daysAgo(2) },
56
+ { owner: 'player-1', type: MarketplaceTransactionType.Withdrawal, goldAmount: 3200, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
57
+ { owner: 'player-1', type: MarketplaceTransactionType.Expired, goldAmount: 0, itemKey: 'items/leather-armor', itemName: 'Leather Armor', createdAt: daysAgo(7), updatedAt: daysAgo(7) },
58
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderPlaced, goldAmount: 500, itemKey: 'items/broad-sword', itemName: 'Broad Sword', createdAt: daysAgo(3), updatedAt: daysAgo(3) },
59
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderFulfilled, goldAmount: 2000, itemKey: 'items/angelic-sword', itemName: 'Angelic Sword', counterpartName: 'DarkKnight42', createdAt: daysAgo(1), updatedAt: daysAgo(1) },
60
+ { owner: 'player-1', type: MarketplaceTransactionType.BuyOrderCancelled, goldAmount: 300, itemKey: 'items/leather-armor', itemName: 'Leather Armor', createdAt: daysAgo(10), updatedAt: daysAgo(10) },
61
+ ];
62
+
17
63
  const Template: Story = () => {
18
64
  const [page, setPage] = React.useState(1);
65
+ const [selectedBlueprint, setSelectedBlueprint] = React.useState<IMarketplaceBlueprintSummary | undefined>();
66
+ const [quantity, setQuantity] = React.useState(1);
67
+ const [maxPrice, setMaxPrice] = React.useState(0);
68
+ const [selectedRarity, setSelectedRarity] = React.useState('');
69
+ const [blueprintResults, setBlueprintResults] = React.useState<IMarketplaceBlueprintSummary[]>([]);
70
+ const [blueprintLoading, setBlueprintLoading] = React.useState(false);
71
+ const [historyType, setHistoryType] = React.useState('All');
72
+ const [yourBuyOrders, setYourBuyOrders] = React.useState<IMarketplaceBuyOrderItem[]>(mockYourBuyOrders);
73
+
74
+ const handleBlueprintSearch = (request: any) => {
75
+ console.log('blueprint search:', request);
76
+ setBlueprintLoading(true);
77
+ // Simulate async search
78
+ setTimeout(() => {
79
+ const filtered = mockBlueprints.filter(bp => {
80
+ if (request.name && !bp.name.toLowerCase().includes(request.name.toLowerCase())) return false;
81
+ if (request.itemType && bp.type !== request.itemType) return false;
82
+ return true;
83
+ });
84
+ setBlueprintResults(filtered);
85
+ setBlueprintLoading(false);
86
+ }, 300);
87
+ };
88
+
89
+ const filteredTransactions = historyType === 'All'
90
+ ? mockTransactions
91
+ : mockTransactions.filter(tx => tx.type === historyType);
19
92
 
20
93
  return (
21
94
  <RPGUIRoot>
@@ -38,6 +111,7 @@ const Template: Story = () => {
38
111
  }))}
39
112
  equipmentSet={equipmentSetMock}
40
113
  onMarketPlaceItemBuy={tradeId => console.log(tradeId)}
114
+ onFulfillBuyOrder={buyOrderId => console.log('fulfill buy order:', buyOrderId)}
41
115
  availableGold={0}
42
116
  selectedItemToSell={null}
43
117
  onSelectedItemToSellRemove={() => console.log('click')}
@@ -51,6 +125,62 @@ const Template: Story = () => {
51
125
  currentPage={page}
52
126
  itemsPerPage={30}
53
127
  onPageChange={setPage}
128
+ // Buy Order props
129
+ buyOrderSelectedBlueprint={selectedBlueprint}
130
+ buyOrderQuantity={quantity}
131
+ buyOrderMaxPrice={maxPrice}
132
+ buyOrderSelectedRarity={selectedRarity}
133
+ onBuyOrderQuantityChange={setQuantity}
134
+ onBuyOrderMaxPriceChange={setMaxPrice}
135
+ onBuyOrderRarityChange={setSelectedRarity}
136
+ onPlaceBuyOrder={() => {
137
+ if (selectedBlueprint) {
138
+ const newOrder: IMarketplaceBuyOrderItem = {
139
+ _id: `bo-${Date.now()}`,
140
+ owner: 'player-1',
141
+ itemBlueprintKey: selectedBlueprint.key,
142
+ itemRarity: selectedRarity as any,
143
+ maxPrice,
144
+ escrowedGold: maxPrice,
145
+ fee: Math.floor(maxPrice * 0.05),
146
+ status: MarketplaceBuyOrderStatus.Active,
147
+ createdAt: new Date(),
148
+ updatedAt: new Date(),
149
+ };
150
+ setYourBuyOrders(prev => [newOrder, ...prev]);
151
+ }
152
+ }}
153
+ onClearBuyOrderBlueprint={() => {
154
+ setSelectedBlueprint(undefined);
155
+ setQuantity(1);
156
+ setMaxPrice(0);
157
+ setSelectedRarity('');
158
+ }}
159
+ yourBuyOrders={yourBuyOrders}
160
+ yourBuyOrdersTotal={yourBuyOrders.length}
161
+ yourBuyOrdersPage={1}
162
+ onYourBuyOrdersPageChange={p => console.log('your orders page:', p)}
163
+ onCancelBuyOrder={id => setYourBuyOrders(prev => prev.filter(o => o._id !== id))}
164
+ openBuyOrders={mockOpenBuyOrders}
165
+ openBuyOrdersTotal={mockOpenBuyOrders.length}
166
+ openBuyOrdersPage={1}
167
+ onOpenBuyOrdersPageChange={(p: number) => console.log('open orders page:', p)}
168
+ // Blueprint Search props
169
+ onBlueprintSearch={handleBlueprintSearch}
170
+ onBlueprintSelect={setSelectedBlueprint}
171
+ blueprintSearchResults={blueprintResults}
172
+ blueprintSearchTotalCount={blueprintResults.length}
173
+ blueprintSearchCurrentPage={1}
174
+ blueprintSearchIsLoading={blueprintLoading}
175
+ // History props
176
+ historyTransactions={filteredTransactions}
177
+ historyTotalCount={filteredTransactions.length}
178
+ historyCurrentPage={1}
179
+ historyItemsPerPage={10}
180
+ historySelectedType={historyType}
181
+ onHistoryTypeChange={setHistoryType}
182
+ onHistoryPageChange={p => console.log('history page:', p)}
183
+ onActiveTabChange={tab => console.log('tab changed:', tab)}
54
184
  />
55
185
  </RPGUIRoot>
56
186
  );
@@ -25,3 +25,14 @@ const Template: Story = () => (
25
25
  );
26
26
 
27
27
  export const Default = Template.bind({});
28
+
29
+ export const HighStackQuantity: Story = () => (
30
+ <RPGUIRoot>
31
+ <MarketplaceRows
32
+ atlasIMG={atlasIMG}
33
+ atlasJSON={atlasJSON}
34
+ itemPrice={10}
35
+ item={{ ...items[0], stackQty: 99999 }}
36
+ />
37
+ </RPGUIRoot>
38
+ );
@@ -0,0 +1,54 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
4
+ import {
5
+ ISegmentedToggleProps,
6
+ SegmentedToggle,
7
+ } from '../../../components/shared/SegmentedToggle';
8
+
9
+ const meta: Meta = {
10
+ title: 'UI/Buttons & Inputs/SegmentedToggle',
11
+ component: SegmentedToggle,
12
+ decorators: [
13
+ StoryComponent => (
14
+ <RPGUIRoot>
15
+ <div style={{ padding: '2rem', background: '#333' }}>
16
+ <StoryComponent />
17
+ </div>
18
+ </RPGUIRoot>
19
+ ),
20
+ ],
21
+ };
22
+
23
+ export default meta;
24
+
25
+ const Template: Story<ISegmentedToggleProps> = args => {
26
+ const [activeId, setActiveId] = React.useState(args.activeId);
27
+
28
+ return (
29
+ <SegmentedToggle
30
+ {...args}
31
+ activeId={activeId}
32
+ onChange={setActiveId}
33
+ />
34
+ );
35
+ };
36
+
37
+ export const MarketplaceModes = Template.bind({});
38
+ MarketplaceModes.args = {
39
+ activeId: 'all',
40
+ options: [
41
+ { id: 'all', label: 'All' },
42
+ { id: 'sell', label: 'Sell Offers' },
43
+ { id: 'buy', label: 'Buy Requests' },
44
+ ],
45
+ };
46
+
47
+ export const ShortOptions = Template.bind({});
48
+ ShortOptions.args = {
49
+ activeId: 'gold',
50
+ options: [
51
+ { id: 'gold', label: 'Gold' },
52
+ { id: 'dc', label: 'DC' },
53
+ ],
54
+ };
@@ -0,0 +1,43 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
4
+ import { ILabelPillProps, LabelPill } from '../../../components/shared/LabelPill';
5
+
6
+ const meta: Meta = {
7
+ title: 'UI/Text/LabelPill',
8
+ component: LabelPill,
9
+ decorators: [
10
+ StoryComponent => (
11
+ <RPGUIRoot>
12
+ <div style={{ padding: '2rem', background: '#333' }}>
13
+ <StoryComponent />
14
+ </div>
15
+ </RPGUIRoot>
16
+ ),
17
+ ],
18
+ };
19
+
20
+ export default meta;
21
+
22
+ const Template: Story<ILabelPillProps> = args => <LabelPill {...args} />;
23
+
24
+ export const Default = Template.bind({});
25
+ Default.args = {
26
+ children: 'Default',
27
+ };
28
+
29
+ export const BuyRequest = Template.bind({});
30
+ BuyRequest.args = {
31
+ children: 'Buy Request',
32
+ background: 'rgba(34, 197, 94, 0.16)',
33
+ borderColor: 'rgba(34, 197, 94, 0.4)',
34
+ color: '#bbf7d0',
35
+ };
36
+
37
+ export const Status = Template.bind({});
38
+ Status.args = {
39
+ children: 'Active',
40
+ background: '#22c55e',
41
+ borderColor: 'transparent',
42
+ color: '#fff',
43
+ };
@@ -0,0 +1,26 @@
1
+ import atlasJSON from '../../mocks/atlas/items/items.json';
2
+ import { resolveAtlasSpriteKey } from '../atlasUtils';
3
+
4
+ describe('resolveAtlasSpriteKey', () => {
5
+ it('returns exact atlas matches unchanged', () => {
6
+ expect(resolveAtlasSpriteKey(atlasJSON, 'swords/angelic-sword.png')).toBe(
7
+ 'swords/angelic-sword.png'
8
+ );
9
+ });
10
+
11
+ it('matches a missing category by sprite base name', () => {
12
+ expect(resolveAtlasSpriteKey(atlasJSON, 'items/angelic-sword')).toBe(
13
+ 'swords/angelic-sword.png'
14
+ );
15
+ });
16
+
17
+ it('matches a malformed story texture path by sprite base name', () => {
18
+ expect(resolveAtlasSpriteKey(atlasJSON, 'armor/wooden-shield.png')).toBe(
19
+ 'shields/wooden-shield.png'
20
+ );
21
+ });
22
+
23
+ it('returns null when no atlas sprite can be resolved', () => {
24
+ expect(resolveAtlasSpriteKey(atlasJSON, 'items/iron-sword')).toBeNull();
25
+ });
26
+ });
@@ -0,0 +1,80 @@
1
+ export const NO_IMAGE_SPRITE_KEY = 'others/no-image.png';
2
+
3
+ const atlasBaseNameLookupCache = new WeakMap<object, Map<string, string>>();
4
+
5
+ const getBaseName = (spriteKey: string): string | null => {
6
+ const normalizedKey = spriteKey.trim();
7
+ if (!normalizedKey) {
8
+ return null;
9
+ }
10
+
11
+ const fileName = normalizedKey.split('/').pop();
12
+ if (!fileName) {
13
+ return null;
14
+ }
15
+
16
+ return fileName.replace(/\.png$/, '');
17
+ };
18
+
19
+ const getAtlasBaseNameLookup = (atlasJSON: any): Map<string, string> => {
20
+ if (!atlasJSON || typeof atlasJSON !== 'object') {
21
+ return new Map();
22
+ }
23
+
24
+ const cachedLookup = atlasBaseNameLookupCache.get(atlasJSON);
25
+ if (cachedLookup) {
26
+ return cachedLookup;
27
+ }
28
+
29
+ const frames = atlasJSON?.frames ?? {};
30
+ const lookup = new Map<string, string>();
31
+
32
+ Object.keys(frames).forEach((frameKey) => {
33
+ const baseName = getBaseName(frameKey);
34
+ if (baseName && !lookup.has(baseName)) {
35
+ lookup.set(baseName, frameKey);
36
+ }
37
+ });
38
+
39
+ atlasBaseNameLookupCache.set(atlasJSON, lookup);
40
+
41
+ return lookup;
42
+ };
43
+
44
+ export const resolveAtlasSpriteKey = (
45
+ atlasJSON: any,
46
+ spriteKey?: string | null
47
+ ): string | null => {
48
+ if (!spriteKey) {
49
+ return null;
50
+ }
51
+
52
+ const normalizedKey = spriteKey.trim();
53
+ if (!normalizedKey) {
54
+ return null;
55
+ }
56
+
57
+ const frames = atlasJSON?.frames;
58
+ if (!frames) {
59
+ return null;
60
+ }
61
+
62
+ if (frames[normalizedKey]) {
63
+ return normalizedKey;
64
+ }
65
+
66
+ const withPngExtension = normalizedKey.endsWith('.png')
67
+ ? normalizedKey
68
+ : `${normalizedKey}.png`;
69
+
70
+ if (frames[withPngExtension]) {
71
+ return withPngExtension;
72
+ }
73
+
74
+ const baseName = getBaseName(normalizedKey);
75
+ if (!baseName) {
76
+ return null;
77
+ }
78
+
79
+ return getAtlasBaseNameLookup(atlasJSON).get(baseName) ?? null;
80
+ };