@rpg-engine/long-bow 0.8.4 → 0.8.5
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/InformationCenter/sections/bestiary/BestiarySection.d.ts +1 -0
- package/dist/components/InformationCenter/sections/faq/FaqSection.d.ts +1 -0
- package/dist/components/InformationCenter/sections/items/ItemsSection.d.ts +1 -0
- package/dist/components/InternalTabs/InternalTabs.d.ts +2 -0
- package/dist/components/Store/InternalStoreTab.d.ts +15 -0
- package/dist/components/Store/Store.d.ts +3 -0
- package/dist/components/Store/StoreItemRow.d.ts +13 -0
- package/dist/components/Store/StoreTabContent.d.ts +14 -0
- package/dist/components/Store/StoreTypes.d.ts +19 -0
- package/dist/components/shared/PaginatedContent/PaginatedContent.d.ts +24 -0
- package/dist/long-bow.cjs.development.js +19 -7
- 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 +19 -7
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/Features/store/Store.stories.d.ts +1 -0
- package/dist/utils/itemUtils.d.ts +8 -0
- package/package.json +1 -1
- package/src/components/InformationCenter/InformationCenter.tsx +15 -1
- package/src/components/InformationCenter/InformationCenterCell.tsx +7 -0
- package/src/components/InformationCenter/sections/bestiary/BestiarySection.tsx +31 -42
- package/src/components/InformationCenter/sections/faq/FaqSection.tsx +14 -34
- package/src/components/InformationCenter/sections/items/ItemsSection.tsx +40 -40
- package/src/components/InternalTabs/InternalTabs.tsx +9 -5
- package/src/components/Item/Inventory/itemContainerHelper.ts +13 -0
- package/src/components/Store/InternalStoreTab.tsx +142 -0
- package/src/components/Store/Store.tsx +192 -0
- package/src/components/Store/StoreItemRow.tsx +198 -0
- package/src/components/Store/StoreTabContent.tsx +46 -0
- package/src/components/Store/StoreTypes.ts +21 -0
- package/src/components/shared/PaginatedContent/PaginatedContent.tsx +182 -0
- package/src/stories/Features/store/Store.stories.tsx +102 -0
- package/src/utils/itemUtils.ts +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -54,6 +54,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
54
54
|
error,
|
|
55
55
|
initialSearchQuery = '',
|
|
56
56
|
}) => {
|
|
57
|
+
const [activeTab, setActiveTab] = useState('bestiary');
|
|
57
58
|
const [
|
|
58
59
|
selectedItem,
|
|
59
60
|
setSelectedItem,
|
|
@@ -79,6 +80,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
79
80
|
entitiesAtlasJSON={entitiesAtlasJSON}
|
|
80
81
|
entitiesAtlasIMG={entitiesAtlasIMG}
|
|
81
82
|
initialSearchQuery={initialSearchQuery}
|
|
83
|
+
tabId="bestiary"
|
|
82
84
|
/>
|
|
83
85
|
),
|
|
84
86
|
},
|
|
@@ -92,6 +94,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
92
94
|
itemsAtlasJSON={itemsAtlasJSON}
|
|
93
95
|
itemsAtlasIMG={itemsAtlasIMG}
|
|
94
96
|
initialSearchQuery={initialSearchQuery}
|
|
97
|
+
tabId="items"
|
|
95
98
|
/>
|
|
96
99
|
),
|
|
97
100
|
},
|
|
@@ -102,6 +105,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
102
105
|
<FaqSection
|
|
103
106
|
faqItems={faqItems}
|
|
104
107
|
initialSearchQuery={initialSearchQuery}
|
|
108
|
+
tabId="faq"
|
|
105
109
|
/>
|
|
106
110
|
),
|
|
107
111
|
},
|
|
@@ -112,6 +116,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
112
116
|
<TutorialsSection
|
|
113
117
|
videoGuides={videoGuides}
|
|
114
118
|
initialSearchQuery={initialSearchQuery}
|
|
119
|
+
tabId="tutorials"
|
|
115
120
|
/>
|
|
116
121
|
),
|
|
117
122
|
},
|
|
@@ -119,7 +124,16 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
|
|
|
119
124
|
|
|
120
125
|
return (
|
|
121
126
|
<Container>
|
|
122
|
-
<InternalTabs
|
|
127
|
+
<InternalTabs
|
|
128
|
+
tabs={tabs}
|
|
129
|
+
activeTextColor="#000000"
|
|
130
|
+
activeTab={activeTab}
|
|
131
|
+
onTabChange={setActiveTab}
|
|
132
|
+
activeColor="#fef08a"
|
|
133
|
+
inactiveColor="#6b7280"
|
|
134
|
+
borderColor="#f59e0b"
|
|
135
|
+
hoverColor="#fef3c7"
|
|
136
|
+
/>
|
|
123
137
|
{selectedItem && (
|
|
124
138
|
<InformationCenterItemDetails
|
|
125
139
|
item={selectedItem}
|
|
@@ -56,9 +56,14 @@ const CellContainer = styled.div`
|
|
|
56
56
|
display: flex;
|
|
57
57
|
flex-direction: column;
|
|
58
58
|
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
59
60
|
cursor: pointer;
|
|
60
61
|
transition: background-color 0.2s ease;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
min-width: 120px;
|
|
61
65
|
min-height: 120px;
|
|
66
|
+
box-sizing: border-box;
|
|
62
67
|
|
|
63
68
|
&:hover {
|
|
64
69
|
background: rgba(0, 0, 0, 0.3);
|
|
@@ -75,6 +80,7 @@ const SpriteContainer = styled.div`
|
|
|
75
80
|
position: relative;
|
|
76
81
|
background: rgba(0, 0, 0, 0.3);
|
|
77
82
|
border-radius: 4px;
|
|
83
|
+
flex-shrink: 0;
|
|
78
84
|
|
|
79
85
|
.sprite-from-atlas-img {
|
|
80
86
|
position: absolute;
|
|
@@ -93,4 +99,5 @@ const CellName = styled.h3`
|
|
|
93
99
|
font-family: 'Press Start 2P', cursive;
|
|
94
100
|
line-height: 1.2;
|
|
95
101
|
word-break: break-word;
|
|
102
|
+
max-width: 100%;
|
|
96
103
|
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
+
import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
|
|
3
4
|
import { Portal } from '../../../shared/Portal/Portal';
|
|
4
5
|
import { InformationCenterCell } from '../../InformationCenterCell';
|
|
5
|
-
import { InformationCenterTabView } from '../../InformationCenterTabView';
|
|
6
6
|
import { IInformationCenterNPC } from '../../InformationCenterTypes';
|
|
7
7
|
import { InformationCenterNPCDetails } from './InformationCenterNPCDetails';
|
|
8
8
|
import { InformationCenterNPCTooltip } from './InformationCenterNPCTooltip';
|
|
@@ -14,6 +14,7 @@ interface IBestiarySectionProps {
|
|
|
14
14
|
entitiesAtlasJSON: Record<string, any>;
|
|
15
15
|
entitiesAtlasIMG: string;
|
|
16
16
|
initialSearchQuery: string;
|
|
17
|
+
tabId: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const BestiarySection: React.FC<IBestiarySectionProps> = ({
|
|
@@ -23,10 +24,9 @@ export const BestiarySection: React.FC<IBestiarySectionProps> = ({
|
|
|
23
24
|
entitiesAtlasJSON,
|
|
24
25
|
entitiesAtlasIMG,
|
|
25
26
|
initialSearchQuery,
|
|
27
|
+
tabId,
|
|
26
28
|
}) => {
|
|
27
|
-
const [
|
|
28
|
-
initialSearchQuery
|
|
29
|
-
);
|
|
29
|
+
const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
|
|
30
30
|
const [tooltipData, setTooltipData] = useState<{
|
|
31
31
|
npc: IInformationCenterNPC;
|
|
32
32
|
position: { x: number; y: number };
|
|
@@ -37,12 +37,6 @@ export const BestiarySection: React.FC<IBestiarySectionProps> = ({
|
|
|
37
37
|
] = useState<IInformationCenterNPC | null>(null);
|
|
38
38
|
const [isTouchDevice] = useState('ontouchstart' in window);
|
|
39
39
|
|
|
40
|
-
const filterItems = (items: IInformationCenterNPC[]) => {
|
|
41
|
-
return items.filter(item =>
|
|
42
|
-
item.name.toLowerCase().includes(bestiarySearchQuery.toLowerCase())
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
40
|
const handleMouseEnter = (
|
|
47
41
|
monster: IInformationCenterNPC,
|
|
48
42
|
event: React.MouseEvent
|
|
@@ -93,35 +87,38 @@ export const BestiarySection: React.FC<IBestiarySectionProps> = ({
|
|
|
93
87
|
setTooltipData(null);
|
|
94
88
|
};
|
|
95
89
|
|
|
96
|
-
const
|
|
97
|
-
<
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
90
|
+
const renderItem = (item: IInformationCenterNPC) => (
|
|
91
|
+
<InformationCenterCell
|
|
92
|
+
key={item.id}
|
|
93
|
+
name={item.name}
|
|
94
|
+
spriteKey={item.key}
|
|
95
|
+
atlasJSON={entitiesAtlasJSON}
|
|
96
|
+
atlasIMG={entitiesAtlasIMG}
|
|
97
|
+
onClick={() => handleMonsterClick(item)}
|
|
98
|
+
onMouseEnter={e => handleMouseEnter(item, e)}
|
|
99
|
+
onMouseLeave={handleMouseLeave}
|
|
100
|
+
onMouseMove={handleMouseMove}
|
|
101
|
+
onTouchStart={e => handleTouchStart(item, e)}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const filteredItems = bestiaryItems.filter(item =>
|
|
106
|
+
item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
113
107
|
);
|
|
114
108
|
|
|
115
109
|
return (
|
|
116
110
|
<>
|
|
117
|
-
<
|
|
118
|
-
items={
|
|
119
|
-
|
|
120
|
-
onSearchChange={setBestiarySearchQuery}
|
|
121
|
-
filterItems={filterItems}
|
|
122
|
-
renderContent={renderContent}
|
|
123
|
-
searchPlaceholder="Search monsters..."
|
|
111
|
+
<PaginatedContent<IInformationCenterNPC>
|
|
112
|
+
items={filteredItems}
|
|
113
|
+
renderItem={renderItem}
|
|
124
114
|
emptyMessage="No monsters found"
|
|
115
|
+
tabId={tabId}
|
|
116
|
+
layout="grid"
|
|
117
|
+
searchOptions={{
|
|
118
|
+
value: searchQuery,
|
|
119
|
+
onChange: setSearchQuery,
|
|
120
|
+
placeholder: 'Search monsters...',
|
|
121
|
+
}}
|
|
125
122
|
/>
|
|
126
123
|
{tooltipData && (
|
|
127
124
|
<Portal>
|
|
@@ -154,14 +151,6 @@ export const BestiarySection: React.FC<IBestiarySectionProps> = ({
|
|
|
154
151
|
);
|
|
155
152
|
};
|
|
156
153
|
|
|
157
|
-
const BestiaryGrid = styled.div`
|
|
158
|
-
display: grid;
|
|
159
|
-
grid-template-columns: repeat(4, 1fr);
|
|
160
|
-
gap: 1rem;
|
|
161
|
-
padding: 0 1rem;
|
|
162
|
-
min-height: 300px;
|
|
163
|
-
`;
|
|
164
|
-
|
|
165
154
|
const TooltipWrapper = styled.div`
|
|
166
155
|
position: fixed;
|
|
167
156
|
z-index: 1000;
|
|
@@ -1,58 +1,38 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
+
import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
|
|
3
4
|
import { IFaqItem } from '../../InformationCenter';
|
|
4
|
-
import { InformationCenterTabView } from '../../InformationCenterTabView';
|
|
5
5
|
|
|
6
6
|
interface IFaqSectionProps {
|
|
7
7
|
faqItems: IFaqItem[];
|
|
8
8
|
initialSearchQuery: string;
|
|
9
|
+
tabId: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const FaqSection: React.FC<IFaqSectionProps> = ({
|
|
12
13
|
faqItems,
|
|
13
14
|
initialSearchQuery,
|
|
15
|
+
tabId,
|
|
14
16
|
}) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
item.question.toLowerCase().includes(faqSearchQuery.toLowerCase()) ||
|
|
21
|
-
item.answer.toLowerCase().includes(faqSearchQuery.toLowerCase())
|
|
22
|
-
);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const renderContent = (items: IFaqItem[]) => (
|
|
26
|
-
<FaqContainer>
|
|
27
|
-
{items.map(item => (
|
|
28
|
-
<FaqItem key={item.id}>
|
|
29
|
-
<Question>{item.question}</Question>
|
|
30
|
-
<Answer>{item.answer}</Answer>
|
|
31
|
-
</FaqItem>
|
|
32
|
-
))}
|
|
33
|
-
</FaqContainer>
|
|
17
|
+
const renderItem = (item: IFaqItem) => (
|
|
18
|
+
<FaqItem>
|
|
19
|
+
<Question>{item.question}</Question>
|
|
20
|
+
<Answer>{item.answer}</Answer>
|
|
21
|
+
</FaqItem>
|
|
34
22
|
);
|
|
35
23
|
|
|
36
24
|
return (
|
|
37
|
-
<
|
|
25
|
+
<PaginatedContent<IFaqItem>
|
|
38
26
|
items={faqItems}
|
|
39
|
-
|
|
40
|
-
onSearchChange={setFaqSearchQuery}
|
|
41
|
-
filterItems={filterItems}
|
|
42
|
-
renderContent={renderContent}
|
|
43
|
-
searchPlaceholder="Search FAQ..."
|
|
27
|
+
renderItem={renderItem}
|
|
44
28
|
emptyMessage="No FAQ items found"
|
|
29
|
+
tabId={tabId}
|
|
30
|
+
layout="list"
|
|
31
|
+
itemsPerPage={10}
|
|
45
32
|
/>
|
|
46
33
|
);
|
|
47
34
|
};
|
|
48
35
|
|
|
49
|
-
const FaqContainer = styled.div`
|
|
50
|
-
display: flex;
|
|
51
|
-
flex-direction: column;
|
|
52
|
-
gap: 1rem;
|
|
53
|
-
padding: 0 1rem;
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
36
|
const FaqItem = styled.div`
|
|
57
37
|
background: rgba(0, 0, 0, 0.2);
|
|
58
38
|
padding: 1rem;
|
|
@@ -2,8 +2,8 @@ import { ItemType } from '@rpg-engine/shared';
|
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import { IOptionsProps } from '../../../Dropdown';
|
|
5
|
+
import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
|
|
5
6
|
import { InformationCenterCell } from '../../InformationCenterCell';
|
|
6
|
-
import { InformationCenterTabView } from '../../InformationCenterTabView';
|
|
7
7
|
import {
|
|
8
8
|
IInformationCenterItem,
|
|
9
9
|
IInformationCenterNPC,
|
|
@@ -19,6 +19,7 @@ interface IItemsSectionProps {
|
|
|
19
19
|
itemsAtlasJSON: Record<string, any>;
|
|
20
20
|
itemsAtlasIMG: string;
|
|
21
21
|
initialSearchQuery: string;
|
|
22
|
+
tabId: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const ItemsSection: React.FC<IItemsSectionProps> = ({
|
|
@@ -27,8 +28,9 @@ export const ItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
27
28
|
itemsAtlasJSON,
|
|
28
29
|
itemsAtlasIMG,
|
|
29
30
|
initialSearchQuery,
|
|
31
|
+
tabId,
|
|
30
32
|
}) => {
|
|
31
|
-
const [
|
|
33
|
+
const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
|
|
32
34
|
const [selectedItemCategory, setSelectedItemCategory] = useState<string>(
|
|
33
35
|
'all'
|
|
34
36
|
);
|
|
@@ -48,14 +50,11 @@ export const ItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
48
50
|
{ id: 3, value: ItemType.Armor, option: 'Armor' },
|
|
49
51
|
];
|
|
50
52
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
item
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
item.name.toLowerCase().includes(itemsSearchQuery.toLowerCase())
|
|
57
|
-
);
|
|
58
|
-
};
|
|
53
|
+
const filteredItems = items.filter(
|
|
54
|
+
item =>
|
|
55
|
+
(selectedItemCategory === 'all' || item.type === selectedItemCategory) &&
|
|
56
|
+
item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
57
|
+
);
|
|
59
58
|
|
|
60
59
|
const getDroppedByNPCs = (
|
|
61
60
|
itemId: string,
|
|
@@ -109,41 +108,40 @@ export const ItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
109
108
|
setHoveredItem(null);
|
|
110
109
|
};
|
|
111
110
|
|
|
112
|
-
const
|
|
113
|
-
<
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
onClick={() => handleItemClick(item)}
|
|
126
|
-
/>
|
|
127
|
-
))}
|
|
128
|
-
</ItemsGrid>
|
|
111
|
+
const renderItem = (item: IInformationCenterItem) => (
|
|
112
|
+
<InformationCenterCell
|
|
113
|
+
key={item.key}
|
|
114
|
+
name={item.name}
|
|
115
|
+
spriteKey={item.texturePath}
|
|
116
|
+
atlasJSON={itemsAtlasJSON}
|
|
117
|
+
atlasIMG={itemsAtlasIMG}
|
|
118
|
+
onMouseEnter={e => handleMouseEnter(e, item)}
|
|
119
|
+
onMouseMove={handleMouseMove}
|
|
120
|
+
onMouseLeave={handleMouseLeave}
|
|
121
|
+
onTouchStart={e => handleTouchStart(e, item)}
|
|
122
|
+
onClick={() => handleItemClick(item)}
|
|
123
|
+
/>
|
|
129
124
|
);
|
|
130
125
|
|
|
131
126
|
return (
|
|
132
127
|
<>
|
|
133
|
-
<
|
|
134
|
-
items={
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
filterItems={filterItems}
|
|
138
|
-
renderContent={renderContent}
|
|
139
|
-
searchPlaceholder="Search items..."
|
|
128
|
+
<PaginatedContent<IInformationCenterItem>
|
|
129
|
+
items={filteredItems}
|
|
130
|
+
renderItem={renderItem}
|
|
131
|
+
emptyMessage="No items found"
|
|
140
132
|
filterOptions={{
|
|
141
133
|
options: itemCategoryOptions,
|
|
142
134
|
selectedOption: selectedItemCategory,
|
|
143
135
|
onOptionChange: setSelectedItemCategory,
|
|
144
136
|
}}
|
|
145
|
-
|
|
137
|
+
searchOptions={{
|
|
138
|
+
value: searchQuery,
|
|
139
|
+
onChange: setSearchQuery,
|
|
140
|
+
placeholder: 'Search items...',
|
|
141
|
+
}}
|
|
146
142
|
dependencies={[selectedItemCategory]}
|
|
143
|
+
tabId={tabId}
|
|
144
|
+
layout="grid"
|
|
147
145
|
/>
|
|
148
146
|
{hoveredItem && (
|
|
149
147
|
<TooltipWrapper
|
|
@@ -165,11 +163,13 @@ export const ItemsSection: React.FC<IItemsSectionProps> = ({
|
|
|
165
163
|
);
|
|
166
164
|
};
|
|
167
165
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
const StyledPaginatedContent = styled(PaginatedContent)`
|
|
167
|
+
.PaginatedContent-content {
|
|
168
|
+
display: grid;
|
|
169
|
+
grid-template-columns: repeat(4, 1fr);
|
|
170
|
+
gap: 0.5rem;
|
|
171
|
+
padding: 0 1rem;
|
|
172
|
+
}
|
|
173
173
|
`;
|
|
174
174
|
|
|
175
175
|
const TooltipWrapper = styled.div`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
interface TabItem {
|
|
@@ -14,6 +14,8 @@ export interface TableTabProps {
|
|
|
14
14
|
inactiveColor?: string;
|
|
15
15
|
borderColor?: string;
|
|
16
16
|
hoverColor?: string;
|
|
17
|
+
onTabChange?: (tabId: string) => void;
|
|
18
|
+
activeTab?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export const InternalTabs: React.FC<TableTabProps> = ({
|
|
@@ -23,8 +25,10 @@ export const InternalTabs: React.FC<TableTabProps> = ({
|
|
|
23
25
|
inactiveColor = '#6b7280',
|
|
24
26
|
borderColor = '#f59e0b',
|
|
25
27
|
hoverColor = '#fef3c7',
|
|
28
|
+
onTabChange,
|
|
29
|
+
activeTab: externalActiveTab,
|
|
26
30
|
}) => {
|
|
27
|
-
const
|
|
31
|
+
const activeTabId = externalActiveTab ?? tabs[0].id;
|
|
28
32
|
|
|
29
33
|
return (
|
|
30
34
|
<TableWrapper>
|
|
@@ -32,20 +36,20 @@ export const InternalTabs: React.FC<TableTabProps> = ({
|
|
|
32
36
|
{tabs.map(tab => (
|
|
33
37
|
<TabButton
|
|
34
38
|
key={tab.id}
|
|
35
|
-
active={
|
|
39
|
+
active={activeTabId === tab.id}
|
|
36
40
|
activeColor={activeColor}
|
|
37
41
|
activeTextColor={activeTextColor}
|
|
38
42
|
inactiveColor={inactiveColor}
|
|
39
43
|
borderColor={borderColor}
|
|
40
44
|
hoverColor={hoverColor}
|
|
41
|
-
onClick={() =>
|
|
45
|
+
onClick={() => onTabChange?.(tab.id)}
|
|
42
46
|
>
|
|
43
47
|
{tab.title}
|
|
44
48
|
</TabButton>
|
|
45
49
|
))}
|
|
46
50
|
</TabHeader>
|
|
47
51
|
<ContentWrapper>
|
|
48
|
-
{tabs.find(tab => tab.id ===
|
|
52
|
+
{tabs.find(tab => tab.id === activeTabId)?.content}
|
|
49
53
|
</ContentWrapper>
|
|
50
54
|
</TableWrapper>
|
|
51
55
|
);
|
|
@@ -182,5 +182,18 @@ export const generateContextMenu = (
|
|
|
182
182
|
];
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
console.log(item.type === ItemType.Container)
|
|
186
|
+
if (item.type === ItemType.Container) {
|
|
187
|
+
const existInContextAction = contextActionMenu.some(
|
|
188
|
+
item => item.text === DepotSocketEvents.OpenContainer
|
|
189
|
+
|| item.id === ItemSocketEvents.ContainerOpen
|
|
190
|
+
|| item.text === 'Open'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (!existInContextAction) {
|
|
194
|
+
contextActionMenu.push({ id: DepotSocketEvents.OpenContainer, text: 'Open' });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
185
198
|
return contextActionMenu;
|
|
186
199
|
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ItemType, UserAccountTypes } from '@rpg-engine/shared';
|
|
2
|
+
import React, { useMemo, useState } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { usePagination } from '../CraftBook/hooks/usePagination';
|
|
5
|
+
import { Pagination } from '../shared/Pagination/Pagination';
|
|
6
|
+
import { SearchBar } from '../shared/SearchBar/SearchBar';
|
|
7
|
+
import { StoreItemRow } from './StoreItemRow';
|
|
8
|
+
import { IStoreItem } from './StoreTypes';
|
|
9
|
+
|
|
10
|
+
const ITEMS_PER_PAGE = 4;
|
|
11
|
+
|
|
12
|
+
interface IInternalStoreTabProps {
|
|
13
|
+
items: IStoreItem[];
|
|
14
|
+
atlasJSON: Record<string, any>;
|
|
15
|
+
atlasIMG: string;
|
|
16
|
+
onPurchase: (item: IStoreItem, quantity: number) => void;
|
|
17
|
+
userGold: number;
|
|
18
|
+
userAccountType: UserAccountTypes;
|
|
19
|
+
type?: ItemType | 'premium' | 'all';
|
|
20
|
+
initialSearchQuery?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const InternalStoreTab: React.FC<IInternalStoreTabProps> = ({
|
|
24
|
+
items,
|
|
25
|
+
atlasJSON,
|
|
26
|
+
atlasIMG,
|
|
27
|
+
onPurchase,
|
|
28
|
+
userGold,
|
|
29
|
+
userAccountType,
|
|
30
|
+
type = 'all',
|
|
31
|
+
initialSearchQuery = '',
|
|
32
|
+
}) => {
|
|
33
|
+
const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
|
|
34
|
+
|
|
35
|
+
const filteredItems = useMemo(() => {
|
|
36
|
+
return items.filter(item => {
|
|
37
|
+
const matchesSearch = item.name
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.includes(searchQuery.toLowerCase());
|
|
40
|
+
|
|
41
|
+
if (!matchesSearch) return false;
|
|
42
|
+
|
|
43
|
+
switch (type) {
|
|
44
|
+
case ItemType.Weapon:
|
|
45
|
+
return item.type === ItemType.Weapon;
|
|
46
|
+
case ItemType.Consumable:
|
|
47
|
+
return item.type === ItemType.Consumable;
|
|
48
|
+
case 'premium':
|
|
49
|
+
return (
|
|
50
|
+
item.requiredAccountType && item.requiredAccountType.length > 0
|
|
51
|
+
);
|
|
52
|
+
default:
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}, [items, searchQuery, type]);
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
currentPage,
|
|
60
|
+
setCurrentPage,
|
|
61
|
+
paginatedItems,
|
|
62
|
+
totalPages,
|
|
63
|
+
} = usePagination({
|
|
64
|
+
items: filteredItems,
|
|
65
|
+
itemsPerPage: ITEMS_PER_PAGE,
|
|
66
|
+
dependencies: [searchQuery, type],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Container>
|
|
71
|
+
<SearchContainer>
|
|
72
|
+
<SearchBar
|
|
73
|
+
value={searchQuery}
|
|
74
|
+
onChange={setSearchQuery}
|
|
75
|
+
placeholder="Search items..."
|
|
76
|
+
/>
|
|
77
|
+
</SearchContainer>
|
|
78
|
+
|
|
79
|
+
<StoreContent>
|
|
80
|
+
{paginatedItems.length > 0 ? (
|
|
81
|
+
paginatedItems.map(item => (
|
|
82
|
+
<StoreItemRow
|
|
83
|
+
key={item._id}
|
|
84
|
+
item={item}
|
|
85
|
+
atlasJSON={atlasJSON}
|
|
86
|
+
atlasIMG={atlasIMG}
|
|
87
|
+
onPurchase={onPurchase}
|
|
88
|
+
userGold={userGold}
|
|
89
|
+
userAccountType={userAccountType}
|
|
90
|
+
/>
|
|
91
|
+
))
|
|
92
|
+
) : (
|
|
93
|
+
<EmptyMessage>No items found</EmptyMessage>
|
|
94
|
+
)}
|
|
95
|
+
</StoreContent>
|
|
96
|
+
|
|
97
|
+
<PaginationContainer>
|
|
98
|
+
<Pagination
|
|
99
|
+
currentPage={currentPage}
|
|
100
|
+
totalPages={Math.max(1, totalPages)}
|
|
101
|
+
onPageChange={setCurrentPage}
|
|
102
|
+
/>
|
|
103
|
+
</PaginationContainer>
|
|
104
|
+
</Container>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const Container = styled.div`
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: 1rem;
|
|
112
|
+
padding: 1rem;
|
|
113
|
+
min-height: 400px;
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const SearchContainer = styled.div`
|
|
117
|
+
width: 100%;
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
const StoreContent = styled.div`
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
gap: 0.5rem;
|
|
124
|
+
flex: 1;
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const PaginationContainer = styled.div`
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
padding-top: 1rem;
|
|
131
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
const EmptyMessage = styled.div`
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
padding: 2rem;
|
|
139
|
+
color: rgba(255, 255, 255, 0.5);
|
|
140
|
+
font-family: 'Press Start 2P', cursive;
|
|
141
|
+
font-size: 0.8rem;
|
|
142
|
+
`;
|