@rpg-engine/long-bow 0.8.31 → 0.8.32

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 (24) hide show
  1. package/dist/components/InformationCenter/sections/bestiary/BestiaryAdvancedFilters.d.ts +13 -0
  2. package/dist/components/InformationCenter/sections/bestiary/InformationCenterBestiarySection.d.ts +2 -2
  3. package/dist/components/InformationCenter/sections/items/ItemsAdvancedFilters.d.ts +11 -0
  4. package/dist/components/shared/AdvancedFilters/AdvancedFilters.d.ts +23 -0
  5. package/dist/components/shared/PaginatedContent/PaginatedContent.d.ts +1 -0
  6. package/dist/components/shared/SearchBar/SearchBar.d.ts +1 -0
  7. package/dist/components/shared/SearchHeader/SearchHeader.d.ts +1 -0
  8. package/dist/hooks/useTooltipPosition.d.ts +15 -0
  9. package/dist/long-bow.cjs.development.js +467 -163
  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 +468 -164
  14. package/dist/long-bow.esm.js.map +1 -1
  15. package/package.json +1 -1
  16. package/src/components/InformationCenter/sections/bestiary/BestiaryAdvancedFilters.tsx +95 -0
  17. package/src/components/InformationCenter/sections/bestiary/InformationCenterBestiarySection.tsx +103 -61
  18. package/src/components/InformationCenter/sections/items/InformationCenterItemsSection.tsx +62 -69
  19. package/src/components/InformationCenter/sections/items/ItemsAdvancedFilters.tsx +80 -0
  20. package/src/components/shared/AdvancedFilters/AdvancedFilters.tsx +279 -0
  21. package/src/components/shared/PaginatedContent/PaginatedContent.tsx +1 -0
  22. package/src/components/shared/SearchBar/SearchBar.tsx +15 -5
  23. package/src/components/shared/SearchHeader/SearchHeader.tsx +2 -0
  24. package/src/hooks/useTooltipPosition.ts +73 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.31",
3
+ "version": "0.8.32",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -0,0 +1,95 @@
1
+ import { EntityAttackType, NPCSubtype } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import {
4
+ AdvancedFilters,
5
+ IFilterSection,
6
+ } from '../../../shared/AdvancedFilters/AdvancedFilters';
7
+ import { formatItemType } from '../items/InformationCenterItemsSection';
8
+
9
+ interface IBestiaryAdvancedFiltersProps {
10
+ isOpen: boolean;
11
+ onToggle: () => void;
12
+ onLevelRangeChange: (range: [number | undefined, number | undefined]) => void;
13
+ onSubtypeChange: (value: string) => void;
14
+ onAttackTypeChange: (value: string) => void;
15
+ levelRange: [number | undefined, number | undefined];
16
+ selectedSubtype: string;
17
+ selectedAttackType: string;
18
+ }
19
+
20
+ export const BestiaryAdvancedFilters = ({
21
+ isOpen,
22
+ onToggle,
23
+ onLevelRangeChange,
24
+ onSubtypeChange,
25
+ onAttackTypeChange,
26
+ levelRange,
27
+ selectedSubtype,
28
+ selectedAttackType,
29
+ }: IBestiaryAdvancedFiltersProps): JSX.Element => {
30
+ const subtypeOptions = [
31
+ { id: 0, value: 'all', option: 'All Types' },
32
+ ...Object.entries(NPCSubtype).map(([, value], index) => ({
33
+ id: index + 1,
34
+ value,
35
+ option: formatItemType(value),
36
+ })),
37
+ ];
38
+
39
+ const attackTypeOptions = [
40
+ { id: 0, value: 'all', option: 'All Attack Types' },
41
+ ...Object.entries(EntityAttackType).map(([, value], index) => ({
42
+ id: index + 1,
43
+ value,
44
+ option: formatItemType(value),
45
+ })),
46
+ ];
47
+
48
+ const hasActiveFilters =
49
+ levelRange[0] !== undefined ||
50
+ levelRange[1] !== undefined ||
51
+ selectedSubtype !== 'all' ||
52
+ selectedAttackType !== 'all';
53
+
54
+ const handleClearFilters = () => {
55
+ onLevelRangeChange([undefined, undefined]);
56
+ onSubtypeChange('all');
57
+ onAttackTypeChange('all');
58
+ };
59
+
60
+ const sections: IFilterSection[] = [
61
+ {
62
+ type: 'range',
63
+ label: 'Level Range',
64
+ key: 'level',
65
+ value: levelRange,
66
+ onChange: onLevelRangeChange,
67
+ },
68
+ {
69
+ type: 'dropdown',
70
+ label: 'Monster Type',
71
+ key: 'subtype',
72
+ options: subtypeOptions,
73
+ value: selectedSubtype,
74
+ onChange: onSubtypeChange,
75
+ },
76
+ {
77
+ type: 'dropdown',
78
+ label: 'Attack Type',
79
+ key: 'attackType',
80
+ options: attackTypeOptions,
81
+ value: selectedAttackType,
82
+ onChange: onAttackTypeChange,
83
+ },
84
+ ];
85
+
86
+ return (
87
+ <AdvancedFilters
88
+ isOpen={isOpen}
89
+ onToggle={onToggle}
90
+ sections={sections}
91
+ onClearAll={handleClearFilters}
92
+ hasActiveFilters={hasActiveFilters}
93
+ />
94
+ );
95
+ };
@@ -5,11 +5,13 @@ import {
5
5
  } from '@rpg-engine/shared';
6
6
  import React, { useMemo, useState } from 'react';
7
7
  import styled from 'styled-components';
8
+ import { useTooltipPosition } from '../../../../hooks/useTooltipPosition';
8
9
  import { IOptionsProps } from '../../../Dropdown';
9
10
  import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
10
11
  import { Portal } from '../../../shared/Portal/Portal';
11
12
  import { InformationCenterCell } from '../../InformationCenterCell';
12
13
  import { formatItemType } from '../items/InformationCenterItemsSection';
14
+ import { BestiaryAdvancedFilters } from './BestiaryAdvancedFilters';
13
15
  import { InformationCenterNPCDetails } from './InformationCenterNPCDetails';
14
16
  import { InformationCenterNPCTooltip } from './InformationCenterNPCTooltip';
15
17
 
@@ -25,7 +27,7 @@ interface IBestiarySectionProps {
25
27
  tabId: string;
26
28
  }
27
29
 
28
- export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> = ({
30
+ export const InformationCenterBestiarySection = ({
29
31
  bestiaryItems,
30
32
  itemsAtlasJSON,
31
33
  itemsAtlasIMG,
@@ -35,74 +37,59 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
35
37
  entitiesAtlasIMG,
36
38
  initialSearchQuery,
37
39
  tabId,
38
- }) => {
40
+ }: IBestiarySectionProps): JSX.Element => {
39
41
  const isMobile = isMobileOrTablet();
40
- const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
41
- const [tooltipData, setTooltipData] = useState<{
42
- npc: IInformationCenterNPC;
43
- position: { x: number; y: number };
44
- } | null>(null);
42
+ const [searchQuery, setSearchQuery] = useState<string>(initialSearchQuery);
45
43
  const [
46
44
  selectedMonster,
47
45
  setSelectedMonster,
48
46
  ] = useState<IInformationCenterNPC | null>(null);
47
+ const [selectedBestiaryCategory, setSelectedBestiaryCategory] = useState<
48
+ string
49
+ >('all');
49
50
 
50
- const handleMouseEnter = (
51
- monster: IInformationCenterNPC,
52
- event: React.MouseEvent
53
- ) => {
54
- if (!isMobile && !selectedMonster) {
55
- setTooltipData({
56
- npc: monster,
57
- position: { x: event.clientX, y: event.clientY },
58
- });
59
- }
60
- };
61
-
62
- const handleMouseLeave = () => {
63
- if (!isMobile) {
64
- setTooltipData(null);
65
- }
66
- };
51
+ const {
52
+ tooltipState,
53
+ handleMouseEnter,
54
+ handleMouseLeave,
55
+ TOOLTIP_WIDTH,
56
+ } = useTooltipPosition<IInformationCenterNPC>();
67
57
 
68
- const handleMouseMove = (event: React.MouseEvent) => {
69
- if (!isMobile && tooltipData) {
70
- setTooltipData({
71
- ...tooltipData,
72
- position: { x: event.clientX, y: event.clientY },
73
- });
74
- }
75
- };
58
+ // Advanced filters state
59
+ const [isAdvancedFiltersOpen, setIsAdvancedFiltersOpen] = useState<boolean>(
60
+ false
61
+ );
62
+ const [levelRange, setLevelRange] = useState<
63
+ [number | undefined, number | undefined]
64
+ >([undefined, undefined]);
65
+ const [selectedSubtype, setSelectedSubtype] = useState<string>('all');
66
+ const [selectedAttackType, setSelectedAttackType] = useState<string>('all');
76
67
 
77
68
  const handleTouchStart = (
78
69
  monster: IInformationCenterNPC,
79
70
  event: React.TouchEvent
80
- ) => {
71
+ ): void => {
81
72
  event.preventDefault();
82
73
  setSelectedMonster(monster);
83
- setTooltipData(null);
84
74
  };
85
75
 
86
- const handleMonsterClick = (monster: IInformationCenterNPC) => {
76
+ const handleMonsterClick = (monster: IInformationCenterNPC): void => {
87
77
  setSelectedMonster(monster);
88
- setTooltipData(null);
89
78
  };
90
79
 
91
- const [selectedBestiaryCategory, setSelectedBestiaryCategory] = useState<
92
- string
93
- >('all');
94
-
95
80
  const bestiaryCategoryOptions: IOptionsProps[] = [
96
81
  { id: 0, value: 'all', option: 'All Monsters' },
97
82
  { id: 1, value: 'bosses', option: 'Bosses' },
98
- ...Object.entries(NPCAlignment).map(([, value], index) => ({
99
- id: index + 1,
100
- value,
101
- option: formatItemType(value),
102
- })),
83
+ ...Object.entries(NPCAlignment)
84
+ .filter(([, value]) => value !== NPCAlignment.Friendly)
85
+ .map(([, value], index) => ({
86
+ id: index + 2,
87
+ value,
88
+ option: formatItemType(value),
89
+ })),
103
90
  ];
104
91
 
105
- const renderItem = (item: IInformationCenterNPC) => (
92
+ const renderItem = (item: IInformationCenterNPC): JSX.Element => (
106
93
  <InformationCenterCell
107
94
  key={item.id}
108
95
  name={item.name}
@@ -112,19 +99,19 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
112
99
  onClick={() => handleMonsterClick(item)}
113
100
  onMouseEnter={e => handleMouseEnter(item, e)}
114
101
  onMouseLeave={handleMouseLeave}
115
- onMouseMove={handleMouseMove}
116
102
  onTouchStart={e => handleTouchStart(item, e)}
117
103
  />
118
104
  );
119
105
 
120
- const filteredItems = useMemo(() => {
106
+ const filteredItems = useMemo<IInformationCenterNPC[]>(() => {
121
107
  return bestiaryItems.filter(item => {
108
+ // Basic search filter
122
109
  const matchesSearch = item.name
123
110
  .toLowerCase()
124
111
  .includes(searchQuery.toLowerCase());
125
112
 
113
+ // Category filter
126
114
  let matchesCategory = true;
127
-
128
115
  if (selectedBestiaryCategory === 'bosses') {
129
116
  matchesCategory = item.isBoss === true;
130
117
  } else if (selectedBestiaryCategory === NPCAlignment.Hostile) {
@@ -134,17 +121,60 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
134
121
  matchesCategory = item.alignment === selectedBestiaryCategory;
135
122
  }
136
123
 
137
- return matchesSearch && matchesCategory;
138
- });
139
- }, [bestiaryItems, searchQuery, selectedBestiaryCategory]);
124
+ // Advanced filters
125
+ const matchesLevel =
126
+ (!levelRange[0] || item.skills.level >= levelRange[0]) &&
127
+ (!levelRange[1] || item.skills.level <= levelRange[1]);
128
+
129
+ const matchesSubtype =
130
+ selectedSubtype === 'all' || item.subType === selectedSubtype;
131
+
132
+ const matchesAttackType =
133
+ selectedAttackType === 'all' || item.attackType === selectedAttackType;
134
+
135
+ // Filter out friendly NPCs
136
+ const isNotFriendly = item.alignment !== NPCAlignment.Friendly;
140
137
 
141
- const handleSearchChange = (newQuery: string) => {
138
+ return (
139
+ matchesSearch &&
140
+ matchesCategory &&
141
+ matchesLevel &&
142
+ matchesSubtype &&
143
+ matchesAttackType &&
144
+ isNotFriendly
145
+ );
146
+ });
147
+ }, [
148
+ bestiaryItems,
149
+ searchQuery,
150
+ selectedBestiaryCategory,
151
+ levelRange,
152
+ selectedSubtype,
153
+ selectedAttackType,
154
+ ]);
155
+
156
+ const handleSearchChange = (newQuery: string): void => {
142
157
  setSearchQuery(newQuery);
143
158
  if (newQuery && selectedBestiaryCategory !== 'all') {
144
159
  setSelectedBestiaryCategory('all');
145
160
  }
146
161
  };
147
162
 
163
+ const SearchBarRightElement = (
164
+ <SearchBarActions>
165
+ <BestiaryAdvancedFilters
166
+ isOpen={isAdvancedFiltersOpen}
167
+ onToggle={() => setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)}
168
+ onLevelRangeChange={setLevelRange}
169
+ onSubtypeChange={setSelectedSubtype}
170
+ onAttackTypeChange={setSelectedAttackType}
171
+ levelRange={levelRange}
172
+ selectedSubtype={selectedSubtype}
173
+ selectedAttackType={selectedAttackType}
174
+ />
175
+ </SearchBarActions>
176
+ );
177
+
148
178
  return (
149
179
  <>
150
180
  <PaginatedContent<IInformationCenterNPC>
@@ -162,21 +192,28 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
162
192
  value: searchQuery,
163
193
  onChange: handleSearchChange,
164
194
  placeholder: 'Search monsters...',
195
+ rightElement: SearchBarRightElement,
165
196
  }}
166
- dependencies={[selectedBestiaryCategory]}
197
+ dependencies={[
198
+ selectedBestiaryCategory,
199
+ levelRange,
200
+ selectedSubtype,
201
+ selectedAttackType,
202
+ ]}
167
203
  itemHeight="180px"
168
204
  />
169
- {!isMobile && tooltipData && (
205
+ {!isMobile && tooltipState && tooltipState.item && (
170
206
  <Portal>
171
207
  <TooltipWrapper
208
+ width={TOOLTIP_WIDTH}
172
209
  style={{
173
210
  position: 'fixed',
174
- left: tooltipData.position.x + 10,
175
- top: tooltipData.position.y + 10,
211
+ left: `${tooltipState.position.x}px`,
212
+ top: `${tooltipState.position.y}px`,
176
213
  }}
177
214
  >
178
215
  <InformationCenterNPCTooltip
179
- npc={tooltipData.npc}
216
+ npc={tooltipState.item}
180
217
  itemsAtlasJSON={itemsAtlasJSON}
181
218
  itemsAtlasIMG={itemsAtlasIMG}
182
219
  />
@@ -201,9 +238,14 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
201
238
  );
202
239
  };
203
240
 
204
- const TooltipWrapper = styled.div`
205
- position: fixed;
241
+ const TooltipWrapper = styled.div<{ width: number }>`
206
242
  z-index: 1000;
207
243
  pointer-events: none;
208
- width: 300px;
244
+ width: ${props => `${props.width}px`};
245
+ `;
246
+
247
+ const SearchBarActions = styled.div`
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0.5rem;
209
251
  `;
@@ -1,19 +1,17 @@
1
1
  import {
2
2
  IInformationCenterItem,
3
3
  IInformationCenterNPC,
4
- ItemType,
5
4
  isMobileOrTablet,
6
5
  } from '@rpg-engine/shared';
7
- import React, { useState } from 'react';
6
+ import React, { useMemo, useState } from 'react';
8
7
  import styled from 'styled-components';
9
- import { IOptionsProps } from '../../../Dropdown';
8
+ import { useTooltipPosition } from '../../../../hooks/useTooltipPosition';
10
9
  import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
11
10
  import { Portal } from '../../../shared/Portal/Portal';
12
11
  import { InformationCenterCell } from '../../InformationCenterCell';
13
12
  import { InformationCenterItemDetails } from './InformationCenterItemDetails';
14
13
  import { InformationCenterItemTooltip } from './InformationCenterItemTooltip';
15
-
16
- const TOOLTIP_OFFSET = 200;
14
+ import { ItemsAdvancedFilters } from './ItemsAdvancedFilters';
17
15
 
18
16
  interface IItemsSectionProps {
19
17
  items: IInformationCenterItem[];
@@ -54,29 +52,33 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
54
52
  const [selectedItemCategory, setSelectedItemCategory] = useState<string>(
55
53
  'all'
56
54
  );
57
- const [hoveredItem, setHoveredItem] = useState<IInformationCenterItem | null>(
58
- null
59
- );
60
- const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
55
+ const [selectedTier, setSelectedTier] = useState<string>('all');
56
+ const [isAdvancedFiltersOpen, setIsAdvancedFiltersOpen] = useState(false);
61
57
  const [
62
58
  selectedItem,
63
59
  setSelectedItem,
64
60
  ] = useState<IInformationCenterItem | null>(null);
65
61
 
66
- const itemCategoryOptions: IOptionsProps[] = [
67
- { id: 0, value: 'all', option: 'All Items' },
68
- ...Object.values(ItemType).map((type, index) => ({
69
- id: index + 1,
70
- value: type,
71
- option: formatItemType(type),
72
- })),
73
- ];
74
-
75
- const filteredItems = items.filter(
76
- item =>
77
- (selectedItemCategory === 'all' || item.type === selectedItemCategory) &&
78
- item.name.toLowerCase().includes(searchQuery.toLowerCase())
79
- );
62
+ const {
63
+ tooltipState,
64
+ handleMouseEnter,
65
+ handleMouseLeave,
66
+ TOOLTIP_WIDTH,
67
+ } = useTooltipPosition<IInformationCenterItem>();
68
+
69
+ const filteredItems = useMemo<IInformationCenterItem[]>(() => {
70
+ return items.filter(item => {
71
+ const matchesSearch = item.name
72
+ .toLowerCase()
73
+ .includes(searchQuery.toLowerCase());
74
+ const matchesCategory =
75
+ selectedItemCategory === 'all' || item.type === selectedItemCategory;
76
+ const matchesTier =
77
+ selectedTier === 'all' || String(item.tier) === selectedTier;
78
+
79
+ return matchesSearch && matchesCategory && matchesTier;
80
+ });
81
+ }, [items, searchQuery, selectedItemCategory, selectedTier]);
80
82
 
81
83
  const getDroppedByNPCs = (
82
84
  itemId: string,
@@ -89,34 +91,6 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
89
91
  );
90
92
  };
91
93
 
92
- const handleMouseEnter = (
93
- e: React.MouseEvent,
94
- item: IInformationCenterItem
95
- ) => {
96
- if (!isMobile) {
97
- setTooltipPosition({
98
- x: e.clientX + TOOLTIP_OFFSET,
99
- y: e.clientY,
100
- });
101
- setHoveredItem(item);
102
- }
103
- };
104
-
105
- const handleMouseMove = (e: React.MouseEvent) => {
106
- if (!isMobile && hoveredItem) {
107
- setTooltipPosition({
108
- x: e.clientX + TOOLTIP_OFFSET,
109
- y: e.clientY,
110
- });
111
- }
112
- };
113
-
114
- const handleMouseLeave = () => {
115
- if (!isMobile) {
116
- setHoveredItem(null);
117
- }
118
- };
119
-
120
94
  const handleTouchStart = (
121
95
  e: React.TouchEvent,
122
96
  item: IInformationCenterItem
@@ -127,7 +101,6 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
127
101
 
128
102
  const handleItemClick = (item: IInformationCenterItem) => {
129
103
  setSelectedItem(item);
130
- setHoveredItem(null);
131
104
  };
132
105
 
133
106
  const handleSearchChange = (newQuery: string) => {
@@ -144,41 +117,56 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
144
117
  spriteKey={item.texturePath}
145
118
  atlasJSON={itemsAtlasJSON}
146
119
  atlasIMG={itemsAtlasIMG}
147
- onMouseEnter={e => handleMouseEnter(e, item)}
148
- onMouseMove={handleMouseMove}
120
+ onMouseEnter={e => handleMouseEnter(item, e)}
149
121
  onMouseLeave={handleMouseLeave}
150
122
  onTouchStart={e => handleTouchStart(e, item)}
151
123
  onClick={() => handleItemClick(item)}
152
124
  />
153
125
  );
154
126
 
127
+ const SearchBarRightElement = (
128
+ <SearchBarActions>
129
+ <ItemsAdvancedFilters
130
+ isOpen={isAdvancedFiltersOpen}
131
+ onToggle={() => setIsAdvancedFiltersOpen(!isAdvancedFiltersOpen)}
132
+ onTierChange={setSelectedTier}
133
+ onTypeChange={setSelectedItemCategory}
134
+ selectedTier={selectedTier}
135
+ selectedType={selectedItemCategory}
136
+ />
137
+ </SearchBarActions>
138
+ );
139
+
155
140
  return (
156
141
  <>
157
142
  <PaginatedContent<IInformationCenterItem>
158
143
  items={filteredItems}
159
144
  renderItem={renderItem}
160
145
  emptyMessage="No items found"
161
- filterOptions={{
162
- options: itemCategoryOptions,
163
- selectedOption: selectedItemCategory,
164
- onOptionChange: setSelectedItemCategory,
165
- }}
166
146
  searchOptions={{
167
147
  value: searchQuery,
168
148
  onChange: handleSearchChange,
169
149
  placeholder: 'Search items...',
150
+ rightElement: SearchBarRightElement,
170
151
  }}
171
- dependencies={[selectedItemCategory]}
152
+ dependencies={[selectedItemCategory, selectedTier]}
172
153
  tabId={tabId}
173
154
  layout="grid"
174
155
  itemHeight="180px"
175
156
  />
176
- {!isMobile && hoveredItem && (
177
- <TooltipWrapper
178
- style={{ top: tooltipPosition.y, left: tooltipPosition.x }}
179
- >
180
- <InformationCenterItemTooltip item={hoveredItem} />
181
- </TooltipWrapper>
157
+ {!isMobile && tooltipState && tooltipState.item && (
158
+ <Portal>
159
+ <TooltipWrapper
160
+ width={TOOLTIP_WIDTH}
161
+ style={{
162
+ position: 'fixed',
163
+ left: `${tooltipState.position.x}px`,
164
+ top: `${tooltipState.position.y}px`,
165
+ }}
166
+ >
167
+ <InformationCenterItemTooltip item={tooltipState.item} />
168
+ </TooltipWrapper>
169
+ </Portal>
182
170
  )}
183
171
  {selectedItem && (
184
172
  <Portal>
@@ -195,9 +183,14 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
195
183
  );
196
184
  };
197
185
 
198
- const TooltipWrapper = styled.div`
199
- position: fixed;
186
+ const TooltipWrapper = styled.div<{ width: number }>`
200
187
  z-index: 1000;
201
188
  pointer-events: none;
202
- transition: transform 0.1s ease;
189
+ width: ${props => `${props.width}px`};
190
+ `;
191
+
192
+ const SearchBarActions = styled.div`
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 0.5rem;
203
196
  `;
@@ -0,0 +1,80 @@
1
+ import { ItemType } from '@rpg-engine/shared';
2
+ import React from 'react';
3
+ import {
4
+ AdvancedFilters,
5
+ IFilterOption,
6
+ IFilterSection,
7
+ } from '../../../shared/AdvancedFilters/AdvancedFilters';
8
+ import { formatItemType } from './InformationCenterItemsSection';
9
+
10
+ interface IItemsAdvancedFiltersProps {
11
+ isOpen: boolean;
12
+ onToggle: () => void;
13
+ onTierChange: (value: string) => void;
14
+ onTypeChange: (value: string) => void;
15
+ selectedTier: string;
16
+ selectedType: string;
17
+ }
18
+
19
+ export const ItemsAdvancedFilters = ({
20
+ isOpen,
21
+ onToggle,
22
+ onTierChange,
23
+ onTypeChange,
24
+ selectedTier,
25
+ selectedType,
26
+ }: IItemsAdvancedFiltersProps): JSX.Element => {
27
+ const tierOptions: IFilterOption[] = [
28
+ { id: 0, value: 'all', option: 'All Tiers' },
29
+ ...Array.from({ length: 5 }, (_, i) => ({
30
+ id: i + 1,
31
+ value: String(i + 1),
32
+ option: `Tier ${i + 1}`,
33
+ })),
34
+ ];
35
+
36
+ const typeOptions: IFilterOption[] = [
37
+ { id: 0, value: 'all', option: 'All Types' },
38
+ ...Object.entries(ItemType).map(([, value], index) => ({
39
+ id: index + 1,
40
+ value: value as string,
41
+ option: formatItemType(value as string),
42
+ })),
43
+ ];
44
+
45
+ const hasActiveFilters = selectedTier !== 'all' || selectedType !== 'all';
46
+
47
+ const handleClearFilters = () => {
48
+ onTierChange('all');
49
+ onTypeChange('all');
50
+ };
51
+
52
+ const sections: IFilterSection[] = [
53
+ {
54
+ type: 'dropdown',
55
+ label: 'Tier',
56
+ key: 'tier',
57
+ options: tierOptions,
58
+ value: selectedTier,
59
+ onChange: onTierChange,
60
+ },
61
+ {
62
+ type: 'dropdown',
63
+ label: 'Item Type',
64
+ key: 'type',
65
+ options: typeOptions,
66
+ value: selectedType,
67
+ onChange: onTypeChange,
68
+ },
69
+ ];
70
+
71
+ return (
72
+ <AdvancedFilters
73
+ isOpen={isOpen}
74
+ onToggle={onToggle}
75
+ sections={sections}
76
+ onClearAll={handleClearFilters}
77
+ hasActiveFilters={hasActiveFilters}
78
+ />
79
+ );
80
+ };