Skip to content

Commit 3db892c

Browse files
committed
implement: sorting cards
1 parent bbfbc7d commit 3db892c

8 files changed

Lines changed: 108 additions & 23 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"react-contexify": "^5.0.0",
2222
"react-device-detect": "^1.17.0",
2323
"react-dom": "^17.0.1",
24+
"react-easy-sort": "^1.5.1",
2425
"react-error-boundary": "^3.1.4",
2526
"react-icons": "^4.4.0",
2627
"react-markdown": "^7.0.1",

src/assets/App.css

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ a {
251251
padding-top: 16px;
252252
padding-bottom: 8px;
253253
cursor: pointer;
254+
position: relative;
254255
}
255256
.blockHeader:hover .blockHeaderLink {
256257
opacity: 1;
@@ -263,6 +264,32 @@ a {
263264
opacity: 0;
264265
transition: opacity 0.2s linear;
265266
}
267+
.blockHeaderDragButton {
268+
position: absolute;
269+
top: 0;
270+
left: 0;
271+
background: linear-gradient(to left, transparent, var(--card-actions-background-shadow));
272+
border: none;
273+
height: 100%;
274+
padding: 0 12px;
275+
width: 32%;
276+
align-items: center;
277+
justify-content: start;
278+
z-index: 1;
279+
cursor: grab;
280+
display: none;
281+
color: var(--card-action-button-color);
282+
font-size: 24px;
283+
}
284+
285+
.blockHeader:hover .blockHeaderDragButton {
286+
display: flex;
287+
}
288+
.draggedBlock .block {
289+
transform: rotate(3deg);
290+
opacity: 0.5;
291+
}
292+
266293
.blockHeaderIcon {
267294
position: relative;
268295
top: 4px;
@@ -273,7 +300,7 @@ a {
273300
height: 18px;
274301
}
275302
.blockHeaderIcon .rss {
276-
color:#ee802f;
303+
color: #ee802f;
277304
}
278305
.blockRow {
279306
scroll-snap-align: start;

src/components/Elements/Card/Card.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react'
2+
import { isDesktop } from 'react-device-detect'
3+
import { SortableKnob } from 'react-easy-sort'
24
import { BsBoxArrowInUpRight } from 'react-icons/bs'
5+
import { MdOutlineDragIndicator } from 'react-icons/md'
36
import { ref } from 'src/config'
47
import { useUserPreferences } from 'src/stores/preferences'
58
import { SupportedCardType } from 'src/types'
@@ -23,13 +26,21 @@ export const Card = ({ card, titleComponent, children, fullBlock = false }: Card
2326
return (
2427
<div className={'block' + (fullBlock ? ' fullBlock' : '')}>
2528
<div className="blockHeader">
29+
{isDesktop && (
30+
<SortableKnob>
31+
<button className="blockHeaderDragButton">
32+
<MdOutlineDragIndicator />
33+
</button>
34+
</SortableKnob>
35+
)}
2636
<span className="blockHeaderIcon">{icon}</span> {titleComponent || label}{' '}
2737
{link && (
2838
<a className="blockHeaderLink" href={link} onClick={handleHeaderLinkClick}>
2939
<BsBoxArrowInUpRight />
3040
</a>
3141
)}
3242
</div>
43+
3344
<div className="blockContent scrollable">{children}</div>
3445
</div>
3546
)

src/components/Layout/AppContentLayout.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ export const AppContentLayout = ({
1515

1616
return (
1717
<>
18-
<main className="AppContent HorizontalScroll">
18+
<main className="AppContent">
1919
{isDesktop ? (
2020
<DesktopCards cards={cards} userCustomCards={userCustomCards} />
2121
) : (
22-
<MobileCards selectedCard={selectedCard} />
22+
<div className="HorizontalScroll">
23+
<MobileCards selectedCard={selectedCard} />
24+
</div>
2325
)}
2426
</main>
25-
2627
<BottomNavigation
2728
selectedCard={selectedCard}
2829
setSelectedCard={setSelectedCard}
Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React from 'react'
1+
import SortableList, { SortableItem } from 'react-easy-sort'
22
import { SUPPORTED_CARDS } from 'src/config'
33
import { CustomRssCard } from 'src/features/cards'
4+
import { useUserPreferences } from 'src/stores/preferences'
45
import { SelectedCard, SupportedCardType } from 'src/types'
56

67
export const DesktopCards = ({
@@ -11,20 +12,37 @@ export const DesktopCards = ({
1112
userCustomCards: SupportedCardType[]
1213
}) => {
1314
const AVAILABLE_CARDS = [...SUPPORTED_CARDS, ...userCustomCards]
15+
16+
const { updateCardOrder } = useUserPreferences()
17+
18+
const onSortEnd = (oldIndex: number, newIndex: number) => {
19+
updateCardOrder(oldIndex, newIndex)
20+
}
21+
1422
return (
15-
<>
16-
{cards.map((card, index) => {
17-
const constantCard = AVAILABLE_CARDS.find((c) => c.value === card.name)
18-
if (!constantCard) {
19-
return null
20-
}
23+
<SortableList
24+
as="div"
25+
onSortEnd={onSortEnd}
26+
className="AppContent HorizontalScroll"
27+
draggedItemClassName="draggedBlock">
28+
{cards
29+
.sort((a, b) => a.id - b.id)
30+
.map((card, index) => {
31+
const constantCard = AVAILABLE_CARDS.find((c) => c.value === card.name)
32+
if (!constantCard) {
33+
return null
34+
}
35+
36+
const Component = constantCard?.component || CustomRssCard
2137

22-
return React.createElement(constantCard?.component || CustomRssCard, {
23-
key: card.name,
24-
meta: constantCard,
25-
withAds: index === 0,
26-
})
27-
})}
28-
</>
38+
return (
39+
<SortableItem key={card.name}>
40+
<div>
41+
<Component key={card.name} meta={constantCard} withAds={index === 0} />
42+
</div>
43+
</SortableItem>
44+
)
45+
})}
46+
</SortableList>
2947
)
3048
}

src/components/Layout/ScrollCardsNavigator.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useState, useEffect, useLayoutEffect, useRef, useCallback } from 'react'
1+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
22
import { FiChevronLeft, FiChevronRight } from 'react-icons/fi'
33
import { maxCardsPerRow } from 'src/config'
4-
import { useUserPreferences } from 'src/stores/preferences'
54
import { trackPageScroll } from 'src/lib/analytics'
5+
import { useUserPreferences } from 'src/stores/preferences'
66

77
export const ScrollCardsNavigator = () => {
88
const { cards } = useUserPreferences()
@@ -31,7 +31,7 @@ export const ScrollCardsNavigator = () => {
3131
}, [])
3232

3333
useLayoutEffect(() => {
34-
scrollBarContainer.current = document.querySelector('.AppContent')
34+
scrollBarContainer.current = document.querySelector('.AppContent div')
3535
}, [])
3636

3737
useEffect(() => {
@@ -55,8 +55,7 @@ export const ScrollCardsNavigator = () => {
5555
}
5656
trackPageScroll(direction)
5757
const { scrollLeft } = scrollBarContainer.current
58-
const { offsetWidth } = scrollBarContainer.current?.firstChild as HTMLElement
59-
58+
const { offsetWidth } = scrollBarContainer.current?.firstChild?.firstChild as HTMLElement
6059
const position = direction === 'left' ? scrollLeft - offsetWidth : scrollLeft + offsetWidth
6160

6261
scrollBarContainer.current.scrollTo({

src/stores/preferences.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type UserPreferencesStoreActions = {
3030
setCardSettings: (card: string, settings: CardSettingsType) => void
3131
markOnboardingAsCompleted: (occupation: Omit<Occupation, 'icon'> | null) => void
3232
setUserCustomCards: (cards: SupportedCardType[]) => void
33+
updateCardOrder: (prevIndex: number, newIndex: number) => void
3334
}
3435

3536
export const useUserPreferences = create(
@@ -74,6 +75,15 @@ export const useUserPreferences = create(
7475
onboardingResult: occupation,
7576
})),
7677
setUserCustomCards: (cards: SupportedCardType[]) => set({ userCustomCards: cards }),
78+
updateCardOrder: (prevIndex: number, newIndex: number) => set((state) => {
79+
const newState = state.cards;
80+
81+
const temp = newState[prevIndex].id;
82+
newState[prevIndex].id = newState[newIndex].id;
83+
newState[newIndex].id = temp;
84+
85+
return { cards: newState}
86+
}),
7787
}),
7888
{
7989
name: 'preferences_storage',

yarn.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3093,6 +3093,11 @@ array-includes@^3.1.4, array-includes@^3.1.5:
30933093
get-intrinsic "^1.1.1"
30943094
is-string "^1.0.7"
30953095

3096+
array-move@^3.0.1:
3097+
version "3.0.1"
3098+
resolved "https://registry.yarnpkg.com/array-move/-/array-move-3.0.1.tgz#179645cc0987b65953a4fc06b6df9045e4ba9618"
3099+
integrity sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==
3100+
30963101
array-union@^1.0.1:
30973102
version "1.0.2"
30983103
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -10626,6 +10631,14 @@ react-dom@^17.0.1:
1062610631
object-assign "^4.1.1"
1062710632
scheduler "^0.20.2"
1062810633

10634+
react-easy-sort@^1.5.1:
10635+
version "1.5.1"
10636+
resolved "https://registry.yarnpkg.com/react-easy-sort/-/react-easy-sort-1.5.1.tgz#9a9ddcaae7571a5ef8baed480b6d8faf5d21ca3b"
10637+
integrity sha512-VKwFO2pHJgz75zEZkxoZYSr0jvQfbS2BUqCQPR3hCRxUfLl9M7xTrXK4rZeFlA+83RxOQCZnzrqtuX0sbtKKlQ==
10638+
dependencies:
10639+
array-move "^3.0.1"
10640+
tslib "2.0.1"
10641+
1062910642
react-error-boundary@^3.1.4:
1063010643
version "3.1.4"
1063110644
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
@@ -12439,6 +12452,11 @@ tsconfig-paths@^3.14.1:
1243912452
minimist "^1.2.6"
1244012453
strip-bom "^3.0.0"
1244112454

12455+
tslib@2.0.1:
12456+
version "2.0.1"
12457+
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
12458+
integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
12459+
1244212460
tslib@^1.8.1:
1244312461
version "1.14.1"
1244412462
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"

0 commit comments

Comments
 (0)