@rpg-engine/long-bow 0.7.91 → 0.7.92
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 +448 -293
- 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 +448 -294
- 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 +281 -138
- 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,13 @@ 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);
|
|
72
87
|
|
|
73
88
|
useEffect(() => {
|
|
74
89
|
const handleResize = (): void => {
|
|
@@ -94,71 +109,62 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
94
109
|
return () => window.removeEventListener('resize', handleResize);
|
|
95
110
|
}, [scale]);
|
|
96
111
|
|
|
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
|
-
));
|
|
112
|
+
const togglePinItem = (itemKey: string) => {
|
|
113
|
+
setPinnedItems(current =>
|
|
114
|
+
current.includes(itemKey)
|
|
115
|
+
? current.filter(key => key !== itemKey)
|
|
116
|
+
: [...current, itemKey]
|
|
117
|
+
);
|
|
151
118
|
};
|
|
152
119
|
|
|
120
|
+
const categoryOptions: IOptionsProps[] = [
|
|
121
|
+
'Suggested',
|
|
122
|
+
...(pinnedItems.length > 0 ? ['Pinned'] : []),
|
|
123
|
+
...Object.keys(ItemSubType),
|
|
124
|
+
]
|
|
125
|
+
.filter(type => type !== 'DeadBody')
|
|
126
|
+
.sort((a, b) => {
|
|
127
|
+
if (a === 'Suggested') return -1;
|
|
128
|
+
if (b === 'Suggested') return 1;
|
|
129
|
+
if (a === 'Pinned') return -1;
|
|
130
|
+
if (b === 'Pinned') return 1;
|
|
131
|
+
return a.localeCompare(b);
|
|
132
|
+
})
|
|
133
|
+
.map((type, index) => ({
|
|
134
|
+
id: index,
|
|
135
|
+
value: type,
|
|
136
|
+
option: type,
|
|
137
|
+
}));
|
|
138
|
+
|
|
153
139
|
const filteredCraftableItems = craftablesItems?.filter(item => {
|
|
154
140
|
const matchesSearch = item.name
|
|
155
141
|
.toLowerCase()
|
|
156
142
|
.includes(searchTerm.toLowerCase());
|
|
157
143
|
const matchesCategory =
|
|
158
|
-
selectedType === 'Suggested' ||
|
|
144
|
+
selectedType === 'Suggested' ||
|
|
145
|
+
(selectedType === 'Pinned' && pinnedItems.includes(item.key)) ||
|
|
146
|
+
item.type === selectedType;
|
|
159
147
|
return matchesSearch && matchesCategory;
|
|
160
148
|
});
|
|
161
149
|
|
|
150
|
+
const sortedItems = [...(filteredCraftableItems || [])].sort((a, b) => {
|
|
151
|
+
const aIsPinned = pinnedItems.includes(a.key);
|
|
152
|
+
const bIsPinned = pinnedItems.includes(b.key);
|
|
153
|
+
if (aIsPinned && !bIsPinned) return -1;
|
|
154
|
+
if (!aIsPinned && bIsPinned) return 1;
|
|
155
|
+
return 0;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const totalPages = Math.ceil(sortedItems.length / ITEMS_PER_PAGE);
|
|
159
|
+
const paginatedItems = sortedItems.slice(
|
|
160
|
+
(currentPage - 1) * ITEMS_PER_PAGE,
|
|
161
|
+
currentPage * ITEMS_PER_PAGE
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
setCurrentPage(1);
|
|
166
|
+
}, [selectedType, searchTerm]);
|
|
167
|
+
|
|
162
168
|
if (!size) return null;
|
|
163
169
|
|
|
164
170
|
return (
|
|
@@ -167,17 +173,30 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
167
173
|
width={size.width}
|
|
168
174
|
height={size.height}
|
|
169
175
|
cancelDrag=".inputRadioCraftBook"
|
|
170
|
-
onCloseButton={
|
|
171
|
-
if (onClose) {
|
|
172
|
-
onClose();
|
|
173
|
-
}
|
|
174
|
-
}}
|
|
176
|
+
onCloseButton={onClose}
|
|
175
177
|
scale={scale}
|
|
176
178
|
>
|
|
177
179
|
<Wrapper>
|
|
178
|
-
<
|
|
180
|
+
<HeaderContainer>
|
|
179
181
|
<Title>Craftbook</Title>
|
|
180
|
-
<
|
|
182
|
+
<HeaderControls>
|
|
183
|
+
<DropdownWrapper>
|
|
184
|
+
<Dropdown
|
|
185
|
+
options={categoryOptions}
|
|
186
|
+
onChange={value => {
|
|
187
|
+
setSelectedType(value);
|
|
188
|
+
onSelect(value);
|
|
189
|
+
}}
|
|
190
|
+
width="200px"
|
|
191
|
+
/>
|
|
192
|
+
</DropdownWrapper>
|
|
193
|
+
<SearchButton onClick={() => setIsSearchVisible(!isSearchVisible)}>
|
|
194
|
+
<FaSearch size={16} />
|
|
195
|
+
</SearchButton>
|
|
196
|
+
</HeaderControls>
|
|
197
|
+
</HeaderContainer>
|
|
198
|
+
|
|
199
|
+
{isSearchVisible && (
|
|
181
200
|
<SearchContainer>
|
|
182
201
|
<input
|
|
183
202
|
type="text"
|
|
@@ -185,34 +204,64 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
185
204
|
placeholder="Search items..."
|
|
186
205
|
value={searchTerm}
|
|
187
206
|
onChange={e => setSearchTerm(e.target.value)}
|
|
207
|
+
autoFocus
|
|
188
208
|
/>
|
|
189
209
|
</SearchContainer>
|
|
190
|
-
|
|
191
|
-
</div>
|
|
210
|
+
)}
|
|
192
211
|
|
|
193
212
|
<ContentContainer>
|
|
194
|
-
<ItemTypes className="inputRadioCraftBook">
|
|
195
|
-
{renderItemTypes()}
|
|
196
|
-
</ItemTypes>
|
|
197
|
-
|
|
198
213
|
<RadioInputScroller className="inputRadioCraftBook">
|
|
199
|
-
{
|
|
200
|
-
<
|
|
214
|
+
{paginatedItems?.map(item => (
|
|
215
|
+
<CraftingRecipeWrapper
|
|
201
216
|
key={item.key}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
isSelected={pinnedItems.includes(item.key)}
|
|
218
|
+
>
|
|
219
|
+
<PinButton
|
|
220
|
+
onClick={e => {
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
togglePinItem(item.key);
|
|
223
|
+
}}
|
|
224
|
+
isPinned={pinnedItems.includes(item.key)}
|
|
225
|
+
>
|
|
226
|
+
<FaThumbtack size={14} />
|
|
227
|
+
</PinButton>
|
|
228
|
+
<CraftingRecipe
|
|
229
|
+
atlasIMG={atlasIMG}
|
|
230
|
+
atlasJSON={atlasJSON}
|
|
231
|
+
equipmentSet={equipmentSet}
|
|
232
|
+
recipe={item}
|
|
233
|
+
scale={scale}
|
|
234
|
+
handleRecipeSelect={setCraftItemKey.bind(null, item.key)}
|
|
235
|
+
selectedCraftItemKey={craftItemKey}
|
|
236
|
+
inventory={inventory}
|
|
237
|
+
skills={skills}
|
|
238
|
+
/>
|
|
239
|
+
</CraftingRecipeWrapper>
|
|
212
240
|
))}
|
|
213
241
|
</RadioInputScroller>
|
|
214
242
|
</ContentContainer>
|
|
215
|
-
|
|
243
|
+
|
|
244
|
+
<PaginationContainer>
|
|
245
|
+
<PaginationButton
|
|
246
|
+
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
|
247
|
+
disabled={currentPage === 1}
|
|
248
|
+
>
|
|
249
|
+
<FaChevronLeft size={12} />
|
|
250
|
+
</PaginationButton>
|
|
251
|
+
<PageInfo>
|
|
252
|
+
Page {currentPage} of {totalPages}
|
|
253
|
+
</PageInfo>
|
|
254
|
+
<PaginationButton
|
|
255
|
+
onClick={() =>
|
|
256
|
+
setCurrentPage(prev => Math.min(totalPages, prev + 1))
|
|
257
|
+
}
|
|
258
|
+
disabled={currentPage === totalPages}
|
|
259
|
+
>
|
|
260
|
+
<FaChevronRight size={12} />
|
|
261
|
+
</PaginationButton>
|
|
262
|
+
</PaginationContainer>
|
|
263
|
+
|
|
264
|
+
<Footer>
|
|
216
265
|
<Button buttonType={ButtonTypes.RPGUIButton} onPointerDown={onClose}>
|
|
217
266
|
Cancel
|
|
218
267
|
</Button>
|
|
@@ -240,7 +289,7 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
240
289
|
>
|
|
241
290
|
Craft
|
|
242
291
|
</Button>
|
|
243
|
-
</
|
|
292
|
+
</Footer>
|
|
244
293
|
</Wrapper>
|
|
245
294
|
</DraggableContainer>
|
|
246
295
|
);
|
|
@@ -251,81 +300,61 @@ const Wrapper = styled.div`
|
|
|
251
300
|
flex-direction: column;
|
|
252
301
|
width: 100%;
|
|
253
302
|
height: 100%;
|
|
254
|
-
`;
|
|
255
|
-
|
|
256
|
-
const Title = styled.h1`
|
|
257
|
-
font-size: 0.6rem;
|
|
258
|
-
color: ${uiColors.yellow} !important;
|
|
259
|
-
`;
|
|
260
|
-
|
|
261
|
-
const Subtitle = styled.h1`
|
|
262
|
-
font-size: 0.4rem;
|
|
263
|
-
color: ${uiColors.yellow} !important;
|
|
264
|
-
`;
|
|
265
303
|
|
|
266
|
-
|
|
267
|
-
|
|
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;
|
|
304
|
+
& > * {
|
|
305
|
+
box-sizing: border-box;
|
|
280
306
|
}
|
|
281
307
|
`;
|
|
282
308
|
|
|
283
|
-
const
|
|
309
|
+
const HeaderContainer = styled.div`
|
|
284
310
|
display: flex;
|
|
285
|
-
justify-content:
|
|
286
|
-
|
|
311
|
+
justify-content: space-between;
|
|
312
|
+
align-items: center;
|
|
287
313
|
width: 100%;
|
|
314
|
+
padding: 16px 16px 0;
|
|
315
|
+
`;
|
|
288
316
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
@media (max-width: ${mobilePortrait.width}) {
|
|
295
|
-
justify-content: center;
|
|
296
|
-
}
|
|
317
|
+
const Title = styled.h1`
|
|
318
|
+
font-size: 1.2rem;
|
|
319
|
+
color: ${uiColors.yellow} !important;
|
|
320
|
+
margin: 0;
|
|
321
|
+
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
|
|
297
322
|
`;
|
|
298
323
|
|
|
299
|
-
const
|
|
324
|
+
const HeaderControls = styled.div`
|
|
300
325
|
display: flex;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 16px;
|
|
328
|
+
position: relative;
|
|
329
|
+
left: -2rem;
|
|
330
|
+
`;
|
|
304
331
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
332
|
+
const DropdownWrapper = styled.div`
|
|
333
|
+
width: 200px;
|
|
308
334
|
`;
|
|
309
335
|
|
|
310
|
-
const
|
|
336
|
+
const SearchButton = styled.button`
|
|
337
|
+
background: none;
|
|
338
|
+
border: none;
|
|
339
|
+
cursor: pointer;
|
|
340
|
+
padding: 8px;
|
|
341
|
+
width: 32px;
|
|
342
|
+
height: 32px;
|
|
343
|
+
color: ${uiColors.yellow};
|
|
344
|
+
opacity: 0.8;
|
|
345
|
+
transition: opacity 0.2s;
|
|
311
346
|
display: flex;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
width: max-content;
|
|
315
|
-
flex-direction: column;
|
|
316
|
-
padding-right: 5px;
|
|
347
|
+
align-items: center;
|
|
348
|
+
justify-content: center;
|
|
317
349
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
overflow-y: hidden;
|
|
321
|
-
padding-right: 0;
|
|
322
|
-
width: 100%;
|
|
350
|
+
&:hover {
|
|
351
|
+
opacity: 1;
|
|
323
352
|
}
|
|
324
353
|
`;
|
|
325
354
|
|
|
326
355
|
const SearchContainer = styled.div`
|
|
327
|
-
margin: 8px 0;
|
|
328
356
|
padding: 0 16px;
|
|
357
|
+
margin-top: 16px;
|
|
329
358
|
|
|
330
359
|
input {
|
|
331
360
|
width: 100%;
|
|
@@ -346,3 +375,117 @@ const SearchContainer = styled.div`
|
|
|
346
375
|
}
|
|
347
376
|
}
|
|
348
377
|
`;
|
|
378
|
+
|
|
379
|
+
const ContentContainer = styled.div`
|
|
380
|
+
flex: 1;
|
|
381
|
+
min-height: 0;
|
|
382
|
+
padding: 16px;
|
|
383
|
+
padding-right: 0;
|
|
384
|
+
padding-bottom: 0;
|
|
385
|
+
overflow: hidden;
|
|
386
|
+
width: 100%;
|
|
387
|
+
`;
|
|
388
|
+
|
|
389
|
+
const RadioInputScroller = styled.div`
|
|
390
|
+
height: 100%;
|
|
391
|
+
overflow-y: scroll;
|
|
392
|
+
overflow-x: hidden;
|
|
393
|
+
padding: 8px 16px;
|
|
394
|
+
padding-right: 24px;
|
|
395
|
+
width: 100%;
|
|
396
|
+
box-sizing: border-box;
|
|
397
|
+
|
|
398
|
+
display: grid;
|
|
399
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
400
|
+
gap: 16px;
|
|
401
|
+
align-items: start;
|
|
402
|
+
|
|
403
|
+
@media (max-width: ${mobilePortrait.width}) {
|
|
404
|
+
grid-template-columns: 1fr;
|
|
405
|
+
}
|
|
406
|
+
`;
|
|
407
|
+
|
|
408
|
+
const CraftingRecipeWrapper = styled.div<{ isSelected: boolean }>`
|
|
409
|
+
position: relative;
|
|
410
|
+
width: 100%;
|
|
411
|
+
min-width: 0;
|
|
412
|
+
box-sizing: border-box;
|
|
413
|
+
transition: background-color 0.2s;
|
|
414
|
+
|
|
415
|
+
&:hover {
|
|
416
|
+
background: rgba(0, 0, 0, 0.4);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
${props =>
|
|
420
|
+
props.isSelected &&
|
|
421
|
+
`
|
|
422
|
+
background: rgba(255, 215, 0, 0.1);
|
|
423
|
+
`}
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
const PinButton = styled.button<{ isPinned: boolean }>`
|
|
427
|
+
position: absolute;
|
|
428
|
+
top: 8px;
|
|
429
|
+
right: 8px;
|
|
430
|
+
background: none;
|
|
431
|
+
border: none;
|
|
432
|
+
cursor: pointer;
|
|
433
|
+
padding: 4px;
|
|
434
|
+
color: ${props => (props.isPinned ? uiColors.yellow : uiColors.lightGray)};
|
|
435
|
+
opacity: ${props => (props.isPinned ? 1 : 0.6)};
|
|
436
|
+
transition: all 0.2s;
|
|
437
|
+
z-index: 2;
|
|
438
|
+
|
|
439
|
+
&:hover {
|
|
440
|
+
opacity: 1;
|
|
441
|
+
color: ${uiColors.yellow};
|
|
442
|
+
}
|
|
443
|
+
`;
|
|
444
|
+
|
|
445
|
+
const Footer = styled.div`
|
|
446
|
+
display: flex;
|
|
447
|
+
justify-content: flex-end;
|
|
448
|
+
gap: 16px;
|
|
449
|
+
padding: 8px;
|
|
450
|
+
|
|
451
|
+
button {
|
|
452
|
+
min-width: 100px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@media (max-width: ${mobilePortrait.width}) {
|
|
456
|
+
justify-content: center;
|
|
457
|
+
}
|
|
458
|
+
`;
|
|
459
|
+
|
|
460
|
+
const PaginationContainer = styled.div`
|
|
461
|
+
display: flex;
|
|
462
|
+
align-items: center;
|
|
463
|
+
justify-content: center;
|
|
464
|
+
gap: 16px;
|
|
465
|
+
padding: 8px;
|
|
466
|
+
margin-top: 8px;
|
|
467
|
+
border-top: 1px solid ${uiColors.darkGray};
|
|
468
|
+
`;
|
|
469
|
+
|
|
470
|
+
const PaginationButton = styled.button<{ disabled?: boolean }>`
|
|
471
|
+
background: none;
|
|
472
|
+
border: none;
|
|
473
|
+
color: ${props => (props.disabled ? uiColors.darkGray : uiColors.yellow)};
|
|
474
|
+
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
|
|
475
|
+
opacity: ${props => (props.disabled ? 0.5 : 0.8)};
|
|
476
|
+
padding: 4px;
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
justify-content: center;
|
|
480
|
+
transition: opacity 0.2s;
|
|
481
|
+
|
|
482
|
+
&:hover:not(:disabled) {
|
|
483
|
+
opacity: 1;
|
|
484
|
+
}
|
|
485
|
+
`;
|
|
486
|
+
|
|
487
|
+
const PageInfo = styled.div`
|
|
488
|
+
color: ${uiColors.lightGray};
|
|
489
|
+
font-size: 0.8rem;
|
|
490
|
+
font-family: 'Press Start 2P', cursive;
|
|
491
|
+
`;
|