@rpg-engine/long-bow 0.8.31 → 0.8.33

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