@rpg-engine/long-bow 0.8.140 → 0.8.141
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/dist/components/Marketplace/ManagmentPanel.d.ts +1 -1
- package/dist/components/Marketplace/Marketplace.d.ts +3 -0
- package/dist/components/Marketplace/MarketplaceRows.d.ts +13 -1
- package/dist/components/Marketplace/MarketplaceSettingsPanel.d.ts +7 -0
- package/dist/components/shared/Tabs/Tabs.d.ts +13 -0
- package/dist/components/shared/Tabs/index.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +658 -274
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +656 -274
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -1
- package/src/components/ConfirmModal.tsx +50 -27
- package/src/components/Marketplace/BuyPanel.tsx +198 -122
- package/src/components/Marketplace/ManagmentPanel.tsx +175 -95
- package/src/components/Marketplace/Marketplace.tsx +61 -25
- package/src/components/Marketplace/MarketplaceRows.tsx +244 -81
- package/src/components/Marketplace/MarketplaceSettingsPanel.tsx +127 -0
- package/src/components/shared/Tabs/Tabs.tsx +60 -0
- package/src/components/shared/Tabs/index.ts +1 -0
- package/src/index.tsx +1 -0
- package/src/stories/Features/trading/Marketplace.stories.tsx +2 -0
|
@@ -3,15 +3,17 @@ import {
|
|
|
3
3
|
getItemTextureKeyPath,
|
|
4
4
|
IEquipmentSet,
|
|
5
5
|
IItem,
|
|
6
|
+
IMarketplaceItem,
|
|
6
7
|
} from '@rpg-engine/shared';
|
|
7
|
-
import
|
|
8
|
+
import { Coins } from 'pixelarticons/react/Coins';
|
|
9
|
+
import { Delete } from 'pixelarticons/react/Delete';
|
|
10
|
+
import React, { useState } from 'react';
|
|
8
11
|
import styled from 'styled-components';
|
|
9
|
-
import { uiColors } from '../../constants/uiColors';
|
|
10
12
|
import { uiFonts } from '../../constants/uiFonts';
|
|
11
|
-
import { Button, ButtonTypes } from '../Button';
|
|
12
13
|
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
13
14
|
import { onRenderGems } from '../Item/Inventory/ItemGem';
|
|
14
15
|
import { rarityColor } from '../Item/Inventory/ItemSlotRarity';
|
|
16
|
+
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
15
17
|
import { Ellipsis } from '../shared/Ellipsis';
|
|
16
18
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
17
19
|
|
|
@@ -46,7 +48,7 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
46
48
|
|
|
47
49
|
return (
|
|
48
50
|
<MarketplaceWrapper>
|
|
49
|
-
<
|
|
51
|
+
<ItemSection>
|
|
50
52
|
<SpriteContainer>
|
|
51
53
|
<ItemInfoWrapper
|
|
52
54
|
item={item}
|
|
@@ -73,7 +75,6 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
73
75
|
imgClassname="sprite-from-atlas-img--item"
|
|
74
76
|
/>
|
|
75
77
|
</RarityContainer>
|
|
76
|
-
|
|
77
78
|
<QuantityContainer>
|
|
78
79
|
{item.stackQty &&
|
|
79
80
|
item.stackQty > 1 &&
|
|
@@ -81,120 +82,282 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
81
82
|
</QuantityContainer>
|
|
82
83
|
</ItemInfoWrapper>
|
|
83
84
|
</SpriteContainer>
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
|
|
86
|
+
<ItemDetails>
|
|
87
|
+
<ItemName>
|
|
86
88
|
<Ellipsis maxLines={1} maxWidth="200px" fontSize="10px">
|
|
87
89
|
{item.name}
|
|
88
90
|
</Ellipsis>
|
|
89
|
-
</
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
</ItemName>
|
|
92
|
+
<PriceRow>
|
|
93
|
+
<GoldPriceRow>
|
|
94
|
+
<GoldIcon>
|
|
95
|
+
<SpriteFromAtlas
|
|
96
|
+
atlasIMG={atlasIMG}
|
|
97
|
+
atlasJSON={atlasJSON}
|
|
98
|
+
spriteKey="others/gold-coin-qty-5.png"
|
|
99
|
+
imgScale={1}
|
|
100
|
+
/>
|
|
101
|
+
</GoldIcon>
|
|
102
|
+
<GoldPrice>{itemPrice}</GoldPrice>
|
|
103
|
+
</GoldPriceRow>
|
|
104
|
+
{dcEquivalentPrice !== undefined && (
|
|
105
|
+
<DCPrice>({formatDCAmount(dcEquivalentPrice)} DC)</DCPrice>
|
|
106
|
+
)}
|
|
107
|
+
</PriceRow>
|
|
108
|
+
</ItemDetails>
|
|
109
|
+
</ItemSection>
|
|
110
|
+
|
|
111
|
+
<ActionSection>
|
|
112
|
+
<CTAButton
|
|
113
|
+
icon={onMarketPlaceItemBuy ? <Coins width={18} height={18} /> : <Delete width={18} height={18} />}
|
|
114
|
+
label={onMarketPlaceItemBuy ? 'Buy' : 'Remove'}
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
onClick={() => {
|
|
117
|
+
if (disabled) return;
|
|
118
|
+
onMarketPlaceItemBuy?.();
|
|
119
|
+
onMarketPlaceItemRemove?.();
|
|
120
|
+
}}
|
|
121
|
+
iconColor={onMarketPlaceItemBuy ? '#f59e0b' : '#ef4444'}
|
|
122
|
+
/>
|
|
123
|
+
</ActionSection>
|
|
124
|
+
</MarketplaceWrapper>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export interface IGroupedMarketplaceRowProps {
|
|
129
|
+
bestListing: IMarketplaceItem;
|
|
130
|
+
otherListings: IMarketplaceItem[];
|
|
131
|
+
atlasJSON: any;
|
|
132
|
+
atlasIMG: any;
|
|
133
|
+
equipmentSet?: IEquipmentSet | null;
|
|
134
|
+
dcToGoldSwapRate?: number;
|
|
135
|
+
getDCEquivalentPrice: (goldPrice: number) => number;
|
|
136
|
+
characterId: string;
|
|
137
|
+
onBuy: (id: string) => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const GroupedMarketplaceRow: React.FC<IGroupedMarketplaceRowProps> = ({
|
|
141
|
+
bestListing,
|
|
142
|
+
otherListings,
|
|
143
|
+
atlasJSON,
|
|
144
|
+
atlasIMG,
|
|
145
|
+
equipmentSet,
|
|
146
|
+
dcToGoldSwapRate = 0,
|
|
147
|
+
getDCEquivalentPrice,
|
|
148
|
+
characterId,
|
|
149
|
+
onBuy,
|
|
150
|
+
}) => {
|
|
151
|
+
const [expanded, setExpanded] = useState(false);
|
|
152
|
+
const totalOffers = otherListings.length + 1;
|
|
153
|
+
const hasMultiple = otherListings.length > 0;
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<GroupWrapper>
|
|
157
|
+
<GroupHeader
|
|
158
|
+
onClick={hasMultiple ? () => setExpanded(!expanded) : undefined}
|
|
159
|
+
clickable={hasMultiple}
|
|
160
|
+
>
|
|
161
|
+
<MarketplaceRows
|
|
162
|
+
atlasIMG={atlasIMG}
|
|
163
|
+
atlasJSON={atlasJSON}
|
|
164
|
+
item={bestListing.item}
|
|
165
|
+
itemPrice={bestListing.price}
|
|
166
|
+
dcEquivalentPrice={
|
|
167
|
+
dcToGoldSwapRate > 0
|
|
168
|
+
? getDCEquivalentPrice(bestListing.price)
|
|
169
|
+
: undefined
|
|
170
|
+
}
|
|
171
|
+
equipmentSet={equipmentSet}
|
|
172
|
+
onMarketPlaceItemBuy={() => onBuy(bestListing._id)}
|
|
173
|
+
disabled={bestListing.owner === characterId}
|
|
174
|
+
/>
|
|
175
|
+
{hasMultiple && (
|
|
176
|
+
<GroupMeta>
|
|
177
|
+
<OfferBadge>{totalOffers} offers</OfferBadge>
|
|
178
|
+
<Chevron expanded={expanded}>▸</Chevron>
|
|
179
|
+
</GroupMeta>
|
|
180
|
+
)}
|
|
181
|
+
</GroupHeader>
|
|
182
|
+
|
|
183
|
+
{expanded && (
|
|
184
|
+
<SubRows>
|
|
185
|
+
{otherListings.map((listing) => (
|
|
186
|
+
<MarketplaceRows
|
|
187
|
+
key={listing._id}
|
|
97
188
|
atlasIMG={atlasIMG}
|
|
98
189
|
atlasJSON={atlasJSON}
|
|
99
|
-
|
|
100
|
-
|
|
190
|
+
item={listing.item}
|
|
191
|
+
itemPrice={listing.price}
|
|
192
|
+
dcEquivalentPrice={
|
|
193
|
+
dcToGoldSwapRate > 0
|
|
194
|
+
? getDCEquivalentPrice(listing.price)
|
|
195
|
+
: undefined
|
|
196
|
+
}
|
|
197
|
+
equipmentSet={equipmentSet}
|
|
198
|
+
onMarketPlaceItemBuy={() => onBuy(listing._id)}
|
|
199
|
+
disabled={listing.owner === characterId}
|
|
101
200
|
/>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
${itemPrice}
|
|
107
|
-
</Ellipsis>
|
|
108
|
-
</p>
|
|
109
|
-
</PriceValue>
|
|
110
|
-
{dcEquivalentPrice !== undefined && (
|
|
111
|
-
<DCPriceLabel>{formatDCAmount(dcEquivalentPrice)} DC</DCPriceLabel>
|
|
112
|
-
)}
|
|
113
|
-
</ItemIconContainer>
|
|
114
|
-
<ButtonContainer>
|
|
115
|
-
<Button
|
|
116
|
-
buttonType={ButtonTypes.RPGUIButton}
|
|
117
|
-
disabled={disabled}
|
|
118
|
-
onPointerDown={() => {
|
|
119
|
-
if (disabled) return;
|
|
120
|
-
|
|
121
|
-
onMarketPlaceItemBuy?.();
|
|
122
|
-
onMarketPlaceItemRemove?.();
|
|
123
|
-
}}
|
|
124
|
-
>
|
|
125
|
-
{onMarketPlaceItemBuy ? 'Buy' : 'Remove'}
|
|
126
|
-
</Button>
|
|
127
|
-
</ButtonContainer>
|
|
128
|
-
</Flex>
|
|
129
|
-
</MarketplaceWrapper>
|
|
201
|
+
))}
|
|
202
|
+
</SubRows>
|
|
203
|
+
)}
|
|
204
|
+
</GroupWrapper>
|
|
130
205
|
);
|
|
131
206
|
};
|
|
132
207
|
|
|
208
|
+
const GroupWrapper = styled.div`
|
|
209
|
+
margin-bottom: 2px;
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
const GroupHeader = styled.div<{ clickable: boolean }>`
|
|
213
|
+
position: relative;
|
|
214
|
+
cursor: ${({ clickable }) => (clickable ? 'pointer' : 'default')};
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
const GroupMeta = styled.div`
|
|
218
|
+
position: absolute;
|
|
219
|
+
right: 180px;
|
|
220
|
+
top: 50%;
|
|
221
|
+
transform: translateY(-50%);
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
gap: 6px;
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
const OfferBadge = styled.span`
|
|
229
|
+
font-family: 'Press Start 2P', cursive;
|
|
230
|
+
font-size: 0.5rem;
|
|
231
|
+
color: rgba(255, 255, 255, 0.5);
|
|
232
|
+
background: rgba(255, 255, 255, 0.08);
|
|
233
|
+
padding: 2px 6px;
|
|
234
|
+
border-radius: 8px;
|
|
235
|
+
white-space: nowrap;
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
const Chevron = styled.span<{ expanded: boolean }>`
|
|
239
|
+
display: inline-block;
|
|
240
|
+
font-size: 0.7rem;
|
|
241
|
+
color: rgba(255, 255, 255, 0.4);
|
|
242
|
+
transition: transform 0.2s ease;
|
|
243
|
+
transform: rotate(${({ expanded }) => (expanded ? '90deg' : '0deg')});
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
const SubRows = styled.div`
|
|
247
|
+
margin-left: 12px;
|
|
248
|
+
padding-left: 8px;
|
|
249
|
+
border-left: 2px solid rgba(245, 158, 11, 0.25);
|
|
250
|
+
|
|
251
|
+
> div > div {
|
|
252
|
+
background: rgba(0, 0, 0, 0.4);
|
|
253
|
+
}
|
|
254
|
+
`;
|
|
255
|
+
|
|
133
256
|
const MarketplaceWrapper = styled.div`
|
|
134
|
-
margin: auto;
|
|
135
257
|
display: flex;
|
|
258
|
+
align-items: center;
|
|
136
259
|
justify-content: space-between;
|
|
137
|
-
padding: 0.
|
|
260
|
+
padding: 0.6rem 1rem;
|
|
261
|
+
margin-bottom: 4px;
|
|
262
|
+
background: rgba(0, 0, 0, 0.25);
|
|
263
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
264
|
+
border-radius: 6px;
|
|
265
|
+
border-left: 4px solid transparent;
|
|
266
|
+
transition: all 0.2s ease-in-out;
|
|
138
267
|
|
|
139
268
|
&:hover {
|
|
140
|
-
background
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
269
|
+
background: rgba(245, 158, 11, 0.08);
|
|
270
|
+
border-color: rgba(245, 158, 11, 0.2);
|
|
271
|
+
border-left-color: #f59e0b;
|
|
272
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
273
|
+
transform: translateY(-1px);
|
|
145
274
|
}
|
|
146
275
|
`;
|
|
147
276
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
277
|
+
const ItemSection = styled.div`
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: 0.75rem;
|
|
281
|
+
flex: 1;
|
|
282
|
+
min-width: 0;
|
|
154
283
|
`;
|
|
155
284
|
|
|
156
|
-
const
|
|
157
|
-
position:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
left: -10px;
|
|
161
|
-
font-size: ${uiFonts.size.xsmall} !important;
|
|
285
|
+
const SpriteContainer = styled.div`
|
|
286
|
+
position: relative;
|
|
287
|
+
flex-shrink: 0;
|
|
288
|
+
min-width: 44px; /* Ensure wide stack quantities don't overlap ItemDetails */
|
|
162
289
|
`;
|
|
163
290
|
|
|
164
|
-
const
|
|
291
|
+
const ItemDetails = styled.div`
|
|
165
292
|
display: flex;
|
|
166
|
-
|
|
293
|
+
flex-direction: column;
|
|
294
|
+
gap: 0.2rem;
|
|
295
|
+
min-width: 0;
|
|
296
|
+
margin-left: 1rem;
|
|
167
297
|
`;
|
|
168
298
|
|
|
169
|
-
const
|
|
299
|
+
const ItemName = styled.div`
|
|
300
|
+
font-family: 'Press Start 2P', cursive;
|
|
301
|
+
font-size: 0.65rem;
|
|
302
|
+
color: #ffffff;
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
const PriceRow = styled.div`
|
|
170
306
|
display: flex;
|
|
171
|
-
|
|
307
|
+
flex-direction: row;
|
|
172
308
|
align-items: center;
|
|
309
|
+
gap: 0.5rem;
|
|
310
|
+
margin-top: 0.2rem;
|
|
173
311
|
`;
|
|
174
312
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
313
|
+
const GoldPriceRow = styled.div`
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 0.3rem;
|
|
179
317
|
`;
|
|
180
|
-
|
|
318
|
+
|
|
319
|
+
const GoldIcon = styled.span`
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
justify-content: center;
|
|
181
323
|
position: relative;
|
|
182
|
-
|
|
324
|
+
top: -0.6rem;
|
|
325
|
+
left: -0.5rem;
|
|
183
326
|
`;
|
|
184
327
|
|
|
185
|
-
const
|
|
186
|
-
|
|
328
|
+
const GoldPrice = styled.span`
|
|
329
|
+
font-family: 'Press Start 2P', cursive;
|
|
330
|
+
font-size: 0.6rem;
|
|
187
331
|
color: #fef08a;
|
|
188
|
-
|
|
332
|
+
line-height: 1;
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
const DCPrice = styled.span`
|
|
336
|
+
font-family: 'Press Start 2P', cursive;
|
|
337
|
+
font-size: 0.55rem;
|
|
338
|
+
color: rgba(254, 240, 138, 0.65);
|
|
189
339
|
white-space: nowrap;
|
|
190
340
|
`;
|
|
191
341
|
|
|
192
|
-
const
|
|
193
|
-
|
|
342
|
+
const ActionSection = styled.div`
|
|
343
|
+
flex-shrink: 0;
|
|
344
|
+
margin-left: 0.75rem;
|
|
194
345
|
`;
|
|
195
346
|
|
|
196
|
-
const
|
|
197
|
-
|
|
347
|
+
const QuantityContainer = styled.p`
|
|
348
|
+
position: absolute;
|
|
349
|
+
display: block;
|
|
350
|
+
top: 15px;
|
|
351
|
+
left: 25px;
|
|
352
|
+
font-size: ${uiFonts.size.xsmall} !important;
|
|
353
|
+
`;
|
|
354
|
+
|
|
355
|
+
const GemContainer = styled.p`
|
|
356
|
+
position: absolute;
|
|
357
|
+
display: block;
|
|
358
|
+
top: -5px;
|
|
359
|
+
left: -10px;
|
|
360
|
+
font-size: ${uiFonts.size.xsmall} !important;
|
|
198
361
|
`;
|
|
199
362
|
|
|
200
363
|
const RarityContainer = styled.div<{ item: IItem }>`
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export type MarketplaceAcceptedCurrency = 'gold' | 'gold_or_dc';
|
|
5
|
+
|
|
6
|
+
export interface IMarketplaceSettingsPanelProps {
|
|
7
|
+
acceptedCurrency: MarketplaceAcceptedCurrency;
|
|
8
|
+
onAcceptedCurrencyChange: (value: MarketplaceAcceptedCurrency) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CURRENCY_OPTIONS: { value: MarketplaceAcceptedCurrency; label: string; description: string }[] = [
|
|
12
|
+
{
|
|
13
|
+
value: 'gold_or_dc',
|
|
14
|
+
label: 'Gold or DC',
|
|
15
|
+
description: 'Accept both Gold and Definya Coin as payment',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: 'gold',
|
|
19
|
+
label: 'Gold only',
|
|
20
|
+
description: 'Only accept Gold as payment',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const MarketplaceSettingsPanel: React.FC<IMarketplaceSettingsPanelProps> = ({
|
|
25
|
+
acceptedCurrency,
|
|
26
|
+
onAcceptedCurrencyChange,
|
|
27
|
+
}) => {
|
|
28
|
+
return (
|
|
29
|
+
<Wrapper>
|
|
30
|
+
<Section>
|
|
31
|
+
<SectionLabel>Accepted Currency</SectionLabel>
|
|
32
|
+
<OptionsGrid>
|
|
33
|
+
{CURRENCY_OPTIONS.map(option => (
|
|
34
|
+
<OptionCard
|
|
35
|
+
key={option.value}
|
|
36
|
+
$active={acceptedCurrency === option.value}
|
|
37
|
+
onClick={() => onAcceptedCurrencyChange(option.value)}
|
|
38
|
+
>
|
|
39
|
+
<OptionLabel $active={acceptedCurrency === option.value}>{option.label}</OptionLabel>
|
|
40
|
+
<OptionDescription>{option.description}</OptionDescription>
|
|
41
|
+
{acceptedCurrency === option.value && <ActiveBadge>Active</ActiveBadge>}
|
|
42
|
+
</OptionCard>
|
|
43
|
+
))}
|
|
44
|
+
</OptionsGrid>
|
|
45
|
+
<Hint>Buyers will only be able to pay using the currency you accept.</Hint>
|
|
46
|
+
</Section>
|
|
47
|
+
</Wrapper>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const Wrapper = styled.div`
|
|
52
|
+
width: 95%;
|
|
53
|
+
margin: 0 auto;
|
|
54
|
+
padding-top: 4px;
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const Section = styled.div`
|
|
58
|
+
background: rgba(0, 0, 0, 0.15);
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
61
|
+
padding: 16px 18px;
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const SectionLabel = styled.p`
|
|
65
|
+
margin: 0 0 14px 0;
|
|
66
|
+
font-size: 0.65rem;
|
|
67
|
+
color: #aaa;
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
letter-spacing: 1px;
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const OptionsGrid = styled.div`
|
|
73
|
+
display: grid;
|
|
74
|
+
grid-template-columns: 1fr 1fr;
|
|
75
|
+
gap: 12px;
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const OptionCard = styled.button<{ $active: boolean }>`
|
|
79
|
+
position: relative;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
align-items: flex-start;
|
|
83
|
+
gap: 6px;
|
|
84
|
+
padding: 14px 16px;
|
|
85
|
+
background: ${({ $active }) => ($active ? 'rgba(245, 158, 11, 0.12)' : 'rgba(0, 0, 0, 0.25)')};
|
|
86
|
+
border: 2px solid ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.08)')};
|
|
87
|
+
border-radius: 6px;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
text-align: left;
|
|
90
|
+
transition: border-color 0.15s, background 0.15s;
|
|
91
|
+
|
|
92
|
+
&:hover {
|
|
93
|
+
border-color: ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.25)')};
|
|
94
|
+
background: ${({ $active }) => ($active ? 'rgba(245, 158, 11, 0.15)' : 'rgba(255,255,255,0.04)')};
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const OptionLabel = styled.span<{ $active: boolean }>`
|
|
99
|
+
font-family: 'Press Start 2P', cursive;
|
|
100
|
+
font-size: 0.6rem;
|
|
101
|
+
color: ${({ $active }) => ($active ? '#f59e0b' : '#cccccc')};
|
|
102
|
+
letter-spacing: 0.5px;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const OptionDescription = styled.span`
|
|
106
|
+
font-size: 0.5rem;
|
|
107
|
+
color: #888;
|
|
108
|
+
line-height: 1.5;
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
const ActiveBadge = styled.span`
|
|
112
|
+
position: absolute;
|
|
113
|
+
top: 8px;
|
|
114
|
+
right: 10px;
|
|
115
|
+
font-size: 0.45rem;
|
|
116
|
+
color: #f59e0b;
|
|
117
|
+
text-transform: uppercase;
|
|
118
|
+
letter-spacing: 0.5px;
|
|
119
|
+
opacity: 0.8;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const Hint = styled.p`
|
|
123
|
+
margin: 14px 0 0 0;
|
|
124
|
+
font-size: 0.48rem;
|
|
125
|
+
color: #666;
|
|
126
|
+
line-height: 1.6;
|
|
127
|
+
`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export interface ITabOption {
|
|
5
|
+
id: string;
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ITabsProps {
|
|
11
|
+
options: ITabOption[];
|
|
12
|
+
activeTabId: string;
|
|
13
|
+
onTabChange: (tabId: string) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Tabs: React.FC<ITabsProps> = ({ options, activeTabId, onTabChange, className }) => {
|
|
18
|
+
return (
|
|
19
|
+
<TabsContainer className={className}>
|
|
20
|
+
{options.map((option) => (
|
|
21
|
+
<TabButton
|
|
22
|
+
key={option.id}
|
|
23
|
+
$active={option.id === activeTabId}
|
|
24
|
+
onClick={() => onTabChange(option.id)}
|
|
25
|
+
>
|
|
26
|
+
{option.icon && option.icon} {option.label}
|
|
27
|
+
</TabButton>
|
|
28
|
+
))}
|
|
29
|
+
</TabsContainer>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const TabsContainer = styled.div`
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 15px;
|
|
36
|
+
width: 95%;
|
|
37
|
+
margin: 0 auto 15px auto;
|
|
38
|
+
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
|
39
|
+
padding-bottom: 10px;
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const TabButton = styled.button<{ $active: boolean }>`
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 8px;
|
|
46
|
+
background: transparent;
|
|
47
|
+
border: none;
|
|
48
|
+
border-bottom: ${({ $active }) => ($active ? '3px solid #f59e0b' : '3px solid transparent')};
|
|
49
|
+
color: ${({ $active }) => ($active ? '#ffffff' : '#888888')};
|
|
50
|
+
font-family: 'Press Start 2P', cursive;
|
|
51
|
+
font-size: 0.70rem;
|
|
52
|
+
letter-spacing: 1px;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
padding: 5px 10px 10px 10px;
|
|
55
|
+
transition: color 0.2s, border-bottom 0.2s;
|
|
56
|
+
|
|
57
|
+
&:hover {
|
|
58
|
+
color: #ffffff;
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Tabs';
|
package/src/index.tsx
CHANGED
|
@@ -37,6 +37,7 @@ export * from './components/ListMenu';
|
|
|
37
37
|
export * from './components/Marketplace/Marketplace';
|
|
38
38
|
export * from './components/Marketplace/MarketplaceBuyModal';
|
|
39
39
|
export * from './components/Marketplace/MarketplaceRows';
|
|
40
|
+
export * from './components/Marketplace/MarketplaceSettingsPanel';
|
|
40
41
|
export * from './components/Multitab/TabBody';
|
|
41
42
|
export * from './components/Multitab/TabsContainer';
|
|
42
43
|
export * from './components/NPCDialog/NPCDialog';
|
|
@@ -45,6 +45,8 @@ const Template: Story = () => {
|
|
|
45
45
|
onAddItemToMarketplace={() => console.log('add')}
|
|
46
46
|
characterId="1"
|
|
47
47
|
onMoneyWithdraw={() => console.log('withdraw')}
|
|
48
|
+
dcBalance={50}
|
|
49
|
+
dcToGoldSwapRate={5500}
|
|
48
50
|
totalItems={100}
|
|
49
51
|
currentPage={page}
|
|
50
52
|
itemsPerPage={30}
|