@rpg-engine/long-bow 0.7.91 → 0.7.93
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/CraftBook/CraftingTooltip.d.ts +13 -0
- package/dist/components/CraftBook/components/CraftBookHeader.d.ts +9 -0
- package/dist/components/CraftBook/components/CraftBookPagination.d.ts +0 -0
- package/dist/components/CraftBook/components/CraftBookSearch.d.ts +0 -0
- package/dist/components/CraftBook/hooks/useCraftBookFilters.d.ts +9 -0
- package/dist/components/CraftBook/hooks/useFilteredItems.d.ts +9 -0
- package/dist/components/CraftBook/hooks/usePagination.d.ts +13 -0
- package/dist/components/CraftBook/hooks/useResponsiveSize.d.ts +6 -0
- package/dist/components/CraftBook/utils/modifyString.d.ts +1 -0
- package/dist/components/shared/Pagination/Pagination.d.ts +9 -0
- package/dist/components/shared/SearchBar/SearchBar.d.ts +10 -0
- package/dist/hooks/useLocalStorage.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +455 -294
- 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 +455 -295
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/Features/craftbook/CraftBook.stories.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/CraftBook/CraftBook.tsx +287 -139
- package/src/components/CraftBook/CraftingRecipe.tsx +97 -97
- package/src/components/CraftBook/CraftingTooltip.tsx +137 -0
- package/src/components/CraftBook/components/CraftBookHeader.tsx +81 -0
- package/src/components/CraftBook/components/CraftBookPagination.tsx +1 -0
- package/src/components/CraftBook/components/CraftBookSearch.tsx +1 -0
- package/src/components/CraftBook/hooks/useCraftBookFilters.ts +39 -0
- package/src/components/CraftBook/hooks/useFilteredItems.ts +39 -0
- package/src/components/CraftBook/hooks/usePagination.ts +39 -0
- package/src/components/CraftBook/hooks/useResponsiveSize.ts +50 -0
- package/src/components/CraftBook/utils/modifyString.ts +11 -0
- package/src/components/shared/Pagination/Pagination.tsx +69 -0
- package/src/components/shared/SearchBar/SearchBar.tsx +52 -0
- package/src/hooks/useLocalStorage.ts +44 -0
- package/src/stories/Features/craftbook/CraftBook.stories.tsx +41 -1
|
@@ -2,3 +2,5 @@ import { Meta } from '@storybook/react';
|
|
|
2
2
|
declare const meta: Meta;
|
|
3
3
|
export default meta;
|
|
4
4
|
export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, import("@storybook/react").Args>;
|
|
5
|
+
export declare const WithSearch: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, import("@storybook/react").Args>;
|
|
6
|
+
export declare const WithCategory: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, import("@storybook/react").Args>;
|
package/package.json
CHANGED
|
@@ -6,11 +6,18 @@ import {
|
|
|
6
6
|
ItemSubType,
|
|
7
7
|
} from '@rpg-engine/shared';
|
|
8
8
|
import React, { useEffect, useState } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
FaChevronLeft,
|
|
11
|
+
FaChevronRight,
|
|
12
|
+
FaSearch,
|
|
13
|
+
FaThumbtack,
|
|
14
|
+
} from 'react-icons/fa';
|
|
9
15
|
import styled from 'styled-components';
|
|
10
16
|
import { uiColors } from '../../constants/uiColors';
|
|
17
|
+
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
|
11
18
|
import { Button, ButtonTypes } from '../Button';
|
|
12
19
|
import { DraggableContainer } from '../DraggableContainer';
|
|
13
|
-
import {
|
|
20
|
+
import { Dropdown, IOptionsProps } from '../Dropdown';
|
|
14
21
|
import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
|
|
15
22
|
import { CraftingRecipe } from './CraftingRecipe';
|
|
16
23
|
import { calculateMaxCraftable } from './utils/calculateMaxCraftable';
|
|
@@ -49,6 +56,8 @@ const mobilePortrait = {
|
|
|
49
56
|
height: '700px',
|
|
50
57
|
};
|
|
51
58
|
|
|
59
|
+
const ITEMS_PER_PAGE = 8;
|
|
60
|
+
|
|
52
61
|
export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
53
62
|
atlasIMG,
|
|
54
63
|
atlasJSON,
|
|
@@ -68,7 +77,18 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
68
77
|
);
|
|
69
78
|
const [size, setSize] = useState<{ width: string; height: string }>();
|
|
70
79
|
const [searchTerm, setSearchTerm] = useState('');
|
|
80
|
+
const [isSearchVisible, setIsSearchVisible] = useState(false);
|
|
71
81
|
const [isCraftingDisabled, setIsCraftingDisabled] = useState(false);
|
|
82
|
+
const [pinnedItems, setPinnedItems] = useLocalStorage<string[]>(
|
|
83
|
+
'pinnedCraftItems',
|
|
84
|
+
[]
|
|
85
|
+
);
|
|
86
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
87
|
+
const [items, setItems] = useState<ICraftableItem[]>(craftablesItems);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
setItems(craftablesItems);
|
|
91
|
+
}, [craftablesItems]);
|
|
72
92
|
|
|
73
93
|
useEffect(() => {
|
|
74
94
|
const handleResize = (): void => {
|
|
@@ -94,71 +114,62 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
94
114
|
return () => window.removeEventListener('resize', handleResize);
|
|
95
115
|
}, [scale]);
|
|
96
116
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return a.localeCompare(b);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (window.innerWidth > parseInt(mobilePortrait.width)) {
|
|
107
|
-
return itemTypes.map(type => {
|
|
108
|
-
return (
|
|
109
|
-
<InputRadio
|
|
110
|
-
key={type}
|
|
111
|
-
value={type}
|
|
112
|
-
label={type}
|
|
113
|
-
name={type}
|
|
114
|
-
isChecked={selectedType === type}
|
|
115
|
-
onRadioSelect={value => {
|
|
116
|
-
setSelectedType(value);
|
|
117
|
-
onSelect(value);
|
|
118
|
-
}}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const rows: JSX.Element[][] = [[], []];
|
|
125
|
-
|
|
126
|
-
itemTypes.forEach((type, index) => {
|
|
127
|
-
let row = 0;
|
|
128
|
-
|
|
129
|
-
if (index % 2 === 1) row = 1;
|
|
130
|
-
|
|
131
|
-
rows[row].push(
|
|
132
|
-
<InputRadio
|
|
133
|
-
key={type}
|
|
134
|
-
value={type}
|
|
135
|
-
label={type}
|
|
136
|
-
name={type}
|
|
137
|
-
isChecked={selectedType === type}
|
|
138
|
-
onRadioSelect={value => {
|
|
139
|
-
setSelectedType(value);
|
|
140
|
-
onSelect(value);
|
|
141
|
-
}}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return rows.map((row, index) => (
|
|
147
|
-
<div key={index} style={{ display: 'flex', gap: '10px' }}>
|
|
148
|
-
{row}
|
|
149
|
-
</div>
|
|
150
|
-
));
|
|
117
|
+
const togglePinItem = (itemKey: string) => {
|
|
118
|
+
setPinnedItems(current =>
|
|
119
|
+
current.includes(itemKey)
|
|
120
|
+
? current.filter(key => key !== itemKey)
|
|
121
|
+
: [...current, itemKey]
|
|
122
|
+
);
|
|
151
123
|
};
|
|
152
124
|
|
|
153
|
-
const
|
|
125
|
+
const categoryOptions: IOptionsProps[] = [
|
|
126
|
+
'Suggested',
|
|
127
|
+
...(pinnedItems.length > 0 ? ['Pinned'] : []),
|
|
128
|
+
...Object.keys(ItemSubType),
|
|
129
|
+
]
|
|
130
|
+
.filter(type => type !== 'DeadBody')
|
|
131
|
+
.sort((a, b) => {
|
|
132
|
+
if (a === 'Suggested') return -1;
|
|
133
|
+
if (b === 'Suggested') return 1;
|
|
134
|
+
if (a === 'Pinned') return -1;
|
|
135
|
+
if (b === 'Pinned') return 1;
|
|
136
|
+
return a.localeCompare(b);
|
|
137
|
+
})
|
|
138
|
+
.map((type, index) => ({
|
|
139
|
+
id: index,
|
|
140
|
+
value: type,
|
|
141
|
+
option: type,
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
const filteredCraftableItems = items?.filter(item => {
|
|
154
145
|
const matchesSearch = item.name
|
|
155
146
|
.toLowerCase()
|
|
156
147
|
.includes(searchTerm.toLowerCase());
|
|
157
148
|
const matchesCategory =
|
|
158
|
-
selectedType === 'Suggested' ||
|
|
149
|
+
selectedType === 'Suggested' ||
|
|
150
|
+
(selectedType === 'Pinned' && pinnedItems.includes(item.key)) ||
|
|
151
|
+
item.subType === selectedType;
|
|
159
152
|
return matchesSearch && matchesCategory;
|
|
160
153
|
});
|
|
161
154
|
|
|
155
|
+
const sortedItems = [...(filteredCraftableItems || [])].sort((a, b) => {
|
|
156
|
+
const aIsPinned = pinnedItems.includes(a.key);
|
|
157
|
+
const bIsPinned = pinnedItems.includes(b.key);
|
|
158
|
+
if (aIsPinned && !bIsPinned) return -1;
|
|
159
|
+
if (!aIsPinned && bIsPinned) return 1;
|
|
160
|
+
return 0;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const totalPages = Math.ceil(sortedItems.length / ITEMS_PER_PAGE);
|
|
164
|
+
const paginatedItems = sortedItems.slice(
|
|
165
|
+
(currentPage - 1) * ITEMS_PER_PAGE,
|
|
166
|
+
currentPage * ITEMS_PER_PAGE
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
setCurrentPage(1);
|
|
171
|
+
}, [selectedType, searchTerm]);
|
|
172
|
+
|
|
162
173
|
if (!size) return null;
|
|
163
174
|
|
|
164
175
|
return (
|
|
@@ -167,17 +178,30 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
167
178
|
width={size.width}
|
|
168
179
|
height={size.height}
|
|
169
180
|
cancelDrag=".inputRadioCraftBook"
|
|
170
|
-
onCloseButton={
|
|
171
|
-
if (onClose) {
|
|
172
|
-
onClose();
|
|
173
|
-
}
|
|
174
|
-
}}
|
|
181
|
+
onCloseButton={onClose}
|
|
175
182
|
scale={scale}
|
|
176
183
|
>
|
|
177
184
|
<Wrapper>
|
|
178
|
-
<
|
|
185
|
+
<HeaderContainer>
|
|
179
186
|
<Title>Craftbook</Title>
|
|
180
|
-
<
|
|
187
|
+
<HeaderControls>
|
|
188
|
+
<DropdownWrapper>
|
|
189
|
+
<Dropdown
|
|
190
|
+
options={categoryOptions}
|
|
191
|
+
onChange={value => {
|
|
192
|
+
setSelectedType(value);
|
|
193
|
+
onSelect(value);
|
|
194
|
+
}}
|
|
195
|
+
width="200px"
|
|
196
|
+
/>
|
|
197
|
+
</DropdownWrapper>
|
|
198
|
+
<SearchButton onClick={() => setIsSearchVisible(!isSearchVisible)}>
|
|
199
|
+
<FaSearch size={16} />
|
|
200
|
+
</SearchButton>
|
|
201
|
+
</HeaderControls>
|
|
202
|
+
</HeaderContainer>
|
|
203
|
+
|
|
204
|
+
{isSearchVisible && (
|
|
181
205
|
<SearchContainer>
|
|
182
206
|
<input
|
|
183
207
|
type="text"
|
|
@@ -185,34 +209,64 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
185
209
|
placeholder="Search items..."
|
|
186
210
|
value={searchTerm}
|
|
187
211
|
onChange={e => setSearchTerm(e.target.value)}
|
|
212
|
+
autoFocus
|
|
188
213
|
/>
|
|
189
214
|
</SearchContainer>
|
|
190
|
-
|
|
191
|
-
</div>
|
|
215
|
+
)}
|
|
192
216
|
|
|
193
217
|
<ContentContainer>
|
|
194
|
-
<ItemTypes className="inputRadioCraftBook">
|
|
195
|
-
{renderItemTypes()}
|
|
196
|
-
</ItemTypes>
|
|
197
|
-
|
|
198
218
|
<RadioInputScroller className="inputRadioCraftBook">
|
|
199
|
-
{
|
|
200
|
-
<
|
|
219
|
+
{paginatedItems?.map(item => (
|
|
220
|
+
<CraftingRecipeWrapper
|
|
201
221
|
key={item.key}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
isSelected={pinnedItems.includes(item.key)}
|
|
223
|
+
>
|
|
224
|
+
<PinButton
|
|
225
|
+
onClick={e => {
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
togglePinItem(item.key);
|
|
228
|
+
}}
|
|
229
|
+
isPinned={pinnedItems.includes(item.key)}
|
|
230
|
+
>
|
|
231
|
+
<FaThumbtack size={14} />
|
|
232
|
+
</PinButton>
|
|
233
|
+
<CraftingRecipe
|
|
234
|
+
atlasIMG={atlasIMG}
|
|
235
|
+
atlasJSON={atlasJSON}
|
|
236
|
+
equipmentSet={equipmentSet}
|
|
237
|
+
recipe={item}
|
|
238
|
+
scale={scale}
|
|
239
|
+
handleRecipeSelect={setCraftItemKey.bind(null, item.key)}
|
|
240
|
+
selectedCraftItemKey={craftItemKey}
|
|
241
|
+
inventory={inventory}
|
|
242
|
+
skills={skills}
|
|
243
|
+
/>
|
|
244
|
+
</CraftingRecipeWrapper>
|
|
212
245
|
))}
|
|
213
246
|
</RadioInputScroller>
|
|
214
247
|
</ContentContainer>
|
|
215
|
-
|
|
248
|
+
|
|
249
|
+
<PaginationContainer>
|
|
250
|
+
<PaginationButton
|
|
251
|
+
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
|
252
|
+
disabled={currentPage === 1}
|
|
253
|
+
>
|
|
254
|
+
<FaChevronLeft size={12} />
|
|
255
|
+
</PaginationButton>
|
|
256
|
+
<PageInfo>
|
|
257
|
+
Page {currentPage} of {totalPages}
|
|
258
|
+
</PageInfo>
|
|
259
|
+
<PaginationButton
|
|
260
|
+
onClick={() =>
|
|
261
|
+
setCurrentPage(prev => Math.min(totalPages, prev + 1))
|
|
262
|
+
}
|
|
263
|
+
disabled={currentPage === totalPages}
|
|
264
|
+
>
|
|
265
|
+
<FaChevronRight size={12} />
|
|
266
|
+
</PaginationButton>
|
|
267
|
+
</PaginationContainer>
|
|
268
|
+
|
|
269
|
+
<Footer>
|
|
216
270
|
<Button buttonType={ButtonTypes.RPGUIButton} onPointerDown={onClose}>
|
|
217
271
|
Cancel
|
|
218
272
|
</Button>
|
|
@@ -240,7 +294,7 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
240
294
|
>
|
|
241
295
|
Craft
|
|
242
296
|
</Button>
|
|
243
|
-
</
|
|
297
|
+
</Footer>
|
|
244
298
|
</Wrapper>
|
|
245
299
|
</DraggableContainer>
|
|
246
300
|
);
|
|
@@ -251,81 +305,61 @@ const Wrapper = styled.div`
|
|
|
251
305
|
flex-direction: column;
|
|
252
306
|
width: 100%;
|
|
253
307
|
height: 100%;
|
|
254
|
-
`;
|
|
255
308
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
color: ${uiColors.yellow} !important;
|
|
259
|
-
`;
|
|
260
|
-
|
|
261
|
-
const Subtitle = styled.h1`
|
|
262
|
-
font-size: 0.4rem;
|
|
263
|
-
color: ${uiColors.yellow} !important;
|
|
264
|
-
`;
|
|
265
|
-
|
|
266
|
-
const RadioInputScroller = styled.div`
|
|
267
|
-
padding-left: 15px;
|
|
268
|
-
padding-top: 10px;
|
|
269
|
-
margin-top: 1rem;
|
|
270
|
-
align-items: center;
|
|
271
|
-
align-items: flex-start;
|
|
272
|
-
overflow-y: scroll;
|
|
273
|
-
min-height: 0;
|
|
274
|
-
flex: 1;
|
|
275
|
-
margin-left: 10px;
|
|
276
|
-
-webkit-overflow-scrolling: touch;
|
|
277
|
-
|
|
278
|
-
@media (max-width: ${mobilePortrait.width}) {
|
|
279
|
-
margin-left: 0;
|
|
309
|
+
& > * {
|
|
310
|
+
box-sizing: border-box;
|
|
280
311
|
}
|
|
281
312
|
`;
|
|
282
313
|
|
|
283
|
-
const
|
|
314
|
+
const HeaderContainer = styled.div`
|
|
284
315
|
display: flex;
|
|
285
|
-
justify-content:
|
|
286
|
-
|
|
316
|
+
justify-content: space-between;
|
|
317
|
+
align-items: center;
|
|
287
318
|
width: 100%;
|
|
319
|
+
padding: 16px 16px 0;
|
|
320
|
+
`;
|
|
288
321
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
@media (max-width: ${mobilePortrait.width}) {
|
|
295
|
-
justify-content: center;
|
|
296
|
-
}
|
|
322
|
+
const Title = styled.h1`
|
|
323
|
+
font-size: 1.2rem;
|
|
324
|
+
color: ${uiColors.yellow} !important;
|
|
325
|
+
margin: 0;
|
|
326
|
+
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
|
|
297
327
|
`;
|
|
298
328
|
|
|
299
|
-
const
|
|
329
|
+
const HeaderControls = styled.div`
|
|
300
330
|
display: flex;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 16px;
|
|
333
|
+
position: relative;
|
|
334
|
+
left: -2rem;
|
|
335
|
+
`;
|
|
304
336
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
337
|
+
const DropdownWrapper = styled.div`
|
|
338
|
+
width: 200px;
|
|
308
339
|
`;
|
|
309
340
|
|
|
310
|
-
const
|
|
341
|
+
const SearchButton = styled.button`
|
|
342
|
+
background: none;
|
|
343
|
+
border: none;
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
padding: 8px;
|
|
346
|
+
width: 32px;
|
|
347
|
+
height: 32px;
|
|
348
|
+
color: ${uiColors.yellow};
|
|
349
|
+
opacity: 0.8;
|
|
350
|
+
transition: opacity 0.2s;
|
|
311
351
|
display: flex;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
width: max-content;
|
|
315
|
-
flex-direction: column;
|
|
316
|
-
padding-right: 5px;
|
|
352
|
+
align-items: center;
|
|
353
|
+
justify-content: center;
|
|
317
354
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
overflow-y: hidden;
|
|
321
|
-
padding-right: 0;
|
|
322
|
-
width: 100%;
|
|
355
|
+
&:hover {
|
|
356
|
+
opacity: 1;
|
|
323
357
|
}
|
|
324
358
|
`;
|
|
325
359
|
|
|
326
360
|
const SearchContainer = styled.div`
|
|
327
|
-
margin: 8px 0;
|
|
328
361
|
padding: 0 16px;
|
|
362
|
+
margin-top: 16px;
|
|
329
363
|
|
|
330
364
|
input {
|
|
331
365
|
width: 100%;
|
|
@@ -346,3 +380,117 @@ const SearchContainer = styled.div`
|
|
|
346
380
|
}
|
|
347
381
|
}
|
|
348
382
|
`;
|
|
383
|
+
|
|
384
|
+
const ContentContainer = styled.div`
|
|
385
|
+
flex: 1;
|
|
386
|
+
min-height: 0;
|
|
387
|
+
padding: 16px;
|
|
388
|
+
padding-right: 0;
|
|
389
|
+
padding-bottom: 0;
|
|
390
|
+
overflow: hidden;
|
|
391
|
+
width: 100%;
|
|
392
|
+
`;
|
|
393
|
+
|
|
394
|
+
const RadioInputScroller = styled.div`
|
|
395
|
+
height: 100%;
|
|
396
|
+
overflow-y: scroll;
|
|
397
|
+
overflow-x: hidden;
|
|
398
|
+
padding: 8px 16px;
|
|
399
|
+
padding-right: 24px;
|
|
400
|
+
width: 100%;
|
|
401
|
+
box-sizing: border-box;
|
|
402
|
+
|
|
403
|
+
display: grid;
|
|
404
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
405
|
+
gap: 16px;
|
|
406
|
+
align-items: start;
|
|
407
|
+
|
|
408
|
+
@media (max-width: ${mobilePortrait.width}) {
|
|
409
|
+
grid-template-columns: 1fr;
|
|
410
|
+
}
|
|
411
|
+
`;
|
|
412
|
+
|
|
413
|
+
const CraftingRecipeWrapper = styled.div<{ isSelected: boolean }>`
|
|
414
|
+
position: relative;
|
|
415
|
+
width: 100%;
|
|
416
|
+
min-width: 0;
|
|
417
|
+
box-sizing: border-box;
|
|
418
|
+
transition: background-color 0.2s;
|
|
419
|
+
|
|
420
|
+
&:hover {
|
|
421
|
+
background: rgba(0, 0, 0, 0.4);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
${props =>
|
|
425
|
+
props.isSelected &&
|
|
426
|
+
`
|
|
427
|
+
background: rgba(255, 215, 0, 0.1);
|
|
428
|
+
`}
|
|
429
|
+
`;
|
|
430
|
+
|
|
431
|
+
const PinButton = styled.button<{ isPinned: boolean }>`
|
|
432
|
+
position: absolute;
|
|
433
|
+
top: 8px;
|
|
434
|
+
right: 8px;
|
|
435
|
+
background: none;
|
|
436
|
+
border: none;
|
|
437
|
+
cursor: pointer;
|
|
438
|
+
padding: 4px;
|
|
439
|
+
color: ${props => (props.isPinned ? uiColors.yellow : uiColors.lightGray)};
|
|
440
|
+
opacity: ${props => (props.isPinned ? 1 : 0.6)};
|
|
441
|
+
transition: all 0.2s;
|
|
442
|
+
z-index: 2;
|
|
443
|
+
|
|
444
|
+
&:hover {
|
|
445
|
+
opacity: 1;
|
|
446
|
+
color: ${uiColors.yellow};
|
|
447
|
+
}
|
|
448
|
+
`;
|
|
449
|
+
|
|
450
|
+
const Footer = styled.div`
|
|
451
|
+
display: flex;
|
|
452
|
+
justify-content: flex-end;
|
|
453
|
+
gap: 16px;
|
|
454
|
+
padding: 8px;
|
|
455
|
+
|
|
456
|
+
button {
|
|
457
|
+
min-width: 100px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@media (max-width: ${mobilePortrait.width}) {
|
|
461
|
+
justify-content: center;
|
|
462
|
+
}
|
|
463
|
+
`;
|
|
464
|
+
|
|
465
|
+
const PaginationContainer = styled.div`
|
|
466
|
+
display: flex;
|
|
467
|
+
align-items: center;
|
|
468
|
+
justify-content: center;
|
|
469
|
+
gap: 16px;
|
|
470
|
+
padding: 8px;
|
|
471
|
+
margin-top: 8px;
|
|
472
|
+
border-top: 1px solid ${uiColors.darkGray};
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
const PaginationButton = styled.button<{ disabled?: boolean }>`
|
|
476
|
+
background: none;
|
|
477
|
+
border: none;
|
|
478
|
+
color: ${props => (props.disabled ? uiColors.darkGray : uiColors.yellow)};
|
|
479
|
+
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
|
|
480
|
+
opacity: ${props => (props.disabled ? 0.5 : 0.8)};
|
|
481
|
+
padding: 4px;
|
|
482
|
+
display: flex;
|
|
483
|
+
align-items: center;
|
|
484
|
+
justify-content: center;
|
|
485
|
+
transition: opacity 0.2s;
|
|
486
|
+
|
|
487
|
+
&:hover:not(:disabled) {
|
|
488
|
+
opacity: 1;
|
|
489
|
+
}
|
|
490
|
+
`;
|
|
491
|
+
|
|
492
|
+
const PageInfo = styled.div`
|
|
493
|
+
color: ${uiColors.lightGray};
|
|
494
|
+
font-size: 0.8rem;
|
|
495
|
+
font-family: 'Press Start 2P', cursive;
|
|
496
|
+
`;
|