@rpg-engine/long-bow 0.7.90 → 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 +464 -289
- 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 +464 -290
- 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 +306 -121
- 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,
|
|
@@ -67,8 +76,14 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
67
76
|
savedSelectedType ?? Object.keys(ItemSubType)[0]
|
|
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,62 +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
|
+
|
|
139
|
+
const filteredCraftableItems = craftablesItems?.filter(item => {
|
|
140
|
+
const matchesSearch = item.name
|
|
141
|
+
.toLowerCase()
|
|
142
|
+
.includes(searchTerm.toLowerCase());
|
|
143
|
+
const matchesCategory =
|
|
144
|
+
selectedType === 'Suggested' ||
|
|
145
|
+
(selectedType === 'Pinned' && pinnedItems.includes(item.key)) ||
|
|
146
|
+
item.type === selectedType;
|
|
147
|
+
return matchesSearch && matchesCategory;
|
|
148
|
+
});
|
|
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
|
+
|
|
153
168
|
if (!size) return null;
|
|
154
169
|
|
|
155
170
|
return (
|
|
@@ -158,43 +173,95 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
158
173
|
width={size.width}
|
|
159
174
|
height={size.height}
|
|
160
175
|
cancelDrag=".inputRadioCraftBook"
|
|
161
|
-
onCloseButton={
|
|
162
|
-
if (onClose) {
|
|
163
|
-
onClose();
|
|
164
|
-
}
|
|
165
|
-
}}
|
|
176
|
+
onCloseButton={onClose}
|
|
166
177
|
scale={scale}
|
|
167
178
|
>
|
|
168
179
|
<Wrapper>
|
|
169
|
-
<
|
|
180
|
+
<HeaderContainer>
|
|
170
181
|
<Title>Craftbook</Title>
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
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 && (
|
|
200
|
+
<SearchContainer>
|
|
201
|
+
<input
|
|
202
|
+
type="text"
|
|
203
|
+
className="rpgui-input"
|
|
204
|
+
placeholder="Search items..."
|
|
205
|
+
value={searchTerm}
|
|
206
|
+
onChange={e => setSearchTerm(e.target.value)}
|
|
207
|
+
autoFocus
|
|
208
|
+
/>
|
|
209
|
+
</SearchContainer>
|
|
210
|
+
)}
|
|
174
211
|
|
|
175
212
|
<ContentContainer>
|
|
176
|
-
<ItemTypes className="inputRadioCraftBook">
|
|
177
|
-
{renderItemTypes()}
|
|
178
|
-
</ItemTypes>
|
|
179
|
-
|
|
180
213
|
<RadioInputScroller className="inputRadioCraftBook">
|
|
181
|
-
{
|
|
182
|
-
<
|
|
214
|
+
{paginatedItems?.map(item => (
|
|
215
|
+
<CraftingRecipeWrapper
|
|
183
216
|
key={item.key}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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>
|
|
194
240
|
))}
|
|
195
241
|
</RadioInputScroller>
|
|
196
242
|
</ContentContainer>
|
|
197
|
-
|
|
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>
|
|
198
265
|
<Button buttonType={ButtonTypes.RPGUIButton} onPointerDown={onClose}>
|
|
199
266
|
Cancel
|
|
200
267
|
</Button>
|
|
@@ -222,7 +289,7 @@ export const CraftBook: React.FC<IItemCraftSelectorProps> = ({
|
|
|
222
289
|
>
|
|
223
290
|
Craft
|
|
224
291
|
</Button>
|
|
225
|
-
</
|
|
292
|
+
</Footer>
|
|
226
293
|
</Wrapper>
|
|
227
294
|
</DraggableContainer>
|
|
228
295
|
);
|
|
@@ -233,44 +300,156 @@ const Wrapper = styled.div`
|
|
|
233
300
|
flex-direction: column;
|
|
234
301
|
width: 100%;
|
|
235
302
|
height: 100%;
|
|
303
|
+
|
|
304
|
+
& > * {
|
|
305
|
+
box-sizing: border-box;
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
|
|
309
|
+
const HeaderContainer = styled.div`
|
|
310
|
+
display: flex;
|
|
311
|
+
justify-content: space-between;
|
|
312
|
+
align-items: center;
|
|
313
|
+
width: 100%;
|
|
314
|
+
padding: 16px 16px 0;
|
|
236
315
|
`;
|
|
237
316
|
|
|
238
317
|
const Title = styled.h1`
|
|
239
|
-
font-size:
|
|
318
|
+
font-size: 1.2rem;
|
|
240
319
|
color: ${uiColors.yellow} !important;
|
|
320
|
+
margin: 0;
|
|
321
|
+
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
|
|
241
322
|
`;
|
|
242
323
|
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
324
|
+
const HeaderControls = styled.div`
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 16px;
|
|
328
|
+
position: relative;
|
|
329
|
+
left: -2rem;
|
|
246
330
|
`;
|
|
247
331
|
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
332
|
+
const DropdownWrapper = styled.div`
|
|
333
|
+
width: 200px;
|
|
334
|
+
`;
|
|
335
|
+
|
|
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;
|
|
346
|
+
display: flex;
|
|
252
347
|
align-items: center;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
348
|
+
justify-content: center;
|
|
349
|
+
|
|
350
|
+
&:hover {
|
|
351
|
+
opacity: 1;
|
|
352
|
+
}
|
|
353
|
+
`;
|
|
354
|
+
|
|
355
|
+
const SearchContainer = styled.div`
|
|
356
|
+
padding: 0 16px;
|
|
357
|
+
margin-top: 16px;
|
|
358
|
+
|
|
359
|
+
input {
|
|
360
|
+
width: 100%;
|
|
361
|
+
font-size: 0.8rem;
|
|
362
|
+
padding: 8px 12px;
|
|
363
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
364
|
+
border: none;
|
|
365
|
+
color: white;
|
|
366
|
+
border-radius: 4px;
|
|
367
|
+
|
|
368
|
+
&::placeholder {
|
|
369
|
+
color: rgba(255, 255, 255, 0.5);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
&:focus {
|
|
373
|
+
outline: none;
|
|
374
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
|
|
379
|
+
const ContentContainer = styled.div`
|
|
256
380
|
flex: 1;
|
|
257
|
-
|
|
258
|
-
|
|
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;
|
|
259
402
|
|
|
260
403
|
@media (max-width: ${mobilePortrait.width}) {
|
|
261
|
-
|
|
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);
|
|
262
417
|
}
|
|
418
|
+
|
|
419
|
+
${props =>
|
|
420
|
+
props.isSelected &&
|
|
421
|
+
`
|
|
422
|
+
background: rgba(255, 215, 0, 0.1);
|
|
423
|
+
`}
|
|
263
424
|
`;
|
|
264
425
|
|
|
265
|
-
const
|
|
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`
|
|
266
446
|
display: flex;
|
|
267
447
|
justify-content: flex-end;
|
|
268
|
-
|
|
269
|
-
|
|
448
|
+
gap: 16px;
|
|
449
|
+
padding: 8px;
|
|
270
450
|
|
|
271
451
|
button {
|
|
272
|
-
|
|
273
|
-
margin: 5px;
|
|
452
|
+
min-width: 100px;
|
|
274
453
|
}
|
|
275
454
|
|
|
276
455
|
@media (max-width: ${mobilePortrait.width}) {
|
|
@@ -278,29 +457,35 @@ const ButtonWrapper = styled.div`
|
|
|
278
457
|
}
|
|
279
458
|
`;
|
|
280
459
|
|
|
281
|
-
const
|
|
460
|
+
const PaginationContainer = styled.div`
|
|
282
461
|
display: flex;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
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};
|
|
290
468
|
`;
|
|
291
469
|
|
|
292
|
-
const
|
|
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;
|
|
293
477
|
display: flex;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
flex-direction: column;
|
|
298
|
-
padding-right: 5px;
|
|
478
|
+
align-items: center;
|
|
479
|
+
justify-content: center;
|
|
480
|
+
transition: opacity 0.2s;
|
|
299
481
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
overflow-y: hidden;
|
|
303
|
-
padding-right: 0;
|
|
304
|
-
width: 100%;
|
|
482
|
+
&:hover:not(:disabled) {
|
|
483
|
+
opacity: 1;
|
|
305
484
|
}
|
|
306
485
|
`;
|
|
486
|
+
|
|
487
|
+
const PageInfo = styled.div`
|
|
488
|
+
color: ${uiColors.lightGray};
|
|
489
|
+
font-size: 0.8rem;
|
|
490
|
+
font-family: 'Press Start 2P', cursive;
|
|
491
|
+
`;
|