Skip to content

Commit 8596e60

Browse files
committed
Merge branch 'develop' into feat/mode-airplane
2 parents 5525660 + 25e1ec4 commit 8596e60

9 files changed

Lines changed: 120 additions & 22 deletions

File tree

src/App.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ function App() {
1919
const [showSideBar, setShowSideBar] = useState(false)
2020
const [showSettings, setShowSettings] = useState(false)
2121
const [showOnboarding, setShowOnboarding] = useState(true)
22-
const { onboardingCompleted, firstSeenDate, markOnboardingAsCompleted, pauseTo } = useUserPreferences()
22+
const {
23+
onboardingCompleted,
24+
firstSeenDate,
25+
markOnboardingAsCompleted,
26+
maxVisibleCards,
27+
pauseTo,
28+
} = useUserPreferences()
2329

2430
useLayoutEffect(() => {
2531
if (!onboardingCompleted && getAppVersion() <= '1.15.9') {
@@ -31,15 +37,18 @@ function App() {
3137
// eslint-disable-next-line react-hooks/exhaustive-deps
3238
}, [onboardingCompleted, firstSeenDate])
3339

40+
useEffect(() => {
41+
document.documentElement.style.setProperty('--max-visible-cards', maxVisibleCards)
42+
}, [maxVisibleCards])
43+
3444
useEffect(() => {
3545
setupAnalytics()
3646
setupIdentification()
3747
trackPageView('home')
38-
3948
}, [])
4049

41-
const isAppPaused = Boolean(pauseTo && pauseTo - (new Date()).getTime() > 0)
42-
console.log("pauseTo: ", isAppPaused);
50+
const isAppPaused = Boolean(pauseTo && pauseTo - new Date().getTime() > 0)
51+
console.log('pauseTo: ', isAppPaused)
4352

4453
return (
4554
<>

src/assets/App.css

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,18 +1043,21 @@ Producthunt item
10431043
.floatingFilter {
10441044
display: block;
10451045
}
1046+
.block {
1047+
width: 100vw;
1048+
}
10461049
}
10471050
/* Small devices (portrait tablets and large phones, 600px and up) */
10481051
@media only screen and (min-width: 600px) {
10491052
.block {
1050-
width: calc(96vw / 2);
1053+
width: calc(96vw / min(2, var(--max-visible-cards)));
10511054
}
10521055
}
10531056

10541057
/* Medium devices (landscape tablets, 768px and up) */
10551058
@media only screen and (min-width: 768px) {
10561059
.block {
1057-
width: calc(96vw / 3);
1060+
width: calc(96vw / min(3, var(--max-visible-cards)));
10581061
}
10591062
.searchBarInput {
10601063
width: 200px;
@@ -1064,7 +1067,7 @@ Producthunt item
10641067
/* Large devices (desktops, 992px and up)*/
10651068
@media only screen and (min-width: 992px) {
10661069
.block {
1067-
width: calc(96vw / 3);
1070+
width: calc(96vw / min(3, var(--max-visible-cards)));
10681071
}
10691072

10701073
.searchBarInput {
@@ -1075,14 +1078,14 @@ Producthunt item
10751078
/* X-Large devices (large desktops, 1200px and up)*/
10761079
@media only screen and (min-width: 1200px) {
10771080
.block {
1078-
width: calc(96vw / 4);
1081+
width: calc(96vw / min(4, var(--max-visible-cards)));
10791082
}
10801083
}
10811084

10821085
/* XX-Large devices (larger desktops, 1400px and up)*/
10831086
@media only screen and (min-width: 1400px) {
10841087
.block {
1085-
width: calc(96vw / 4);
1088+
width: calc(95vw / min(4, var(--max-visible-cards)));
10861089
}
10871090
}
10881091

@@ -1093,7 +1096,9 @@ Producthunt item
10931096
padding: 0;
10941097
}
10951098
.block {
1096-
width: calc((1800px - 12px * 4) / 4);
1099+
width: calc(
1100+
(1800px - 14px * min(5, var(--max-visible-cards))) / min(5, var(--max-visible-cards))
1101+
);
10971102
}
10981103
}
10991104

@@ -1122,7 +1127,6 @@ Producthunt item
11221127
}
11231128

11241129
/* PAUSE CONTENT */
1125-
11261130
.pauseContentWrapper {
11271131
height: 0;
11281132
transition: height 0.5s ease-out;
@@ -1145,4 +1149,7 @@ Producthunt item
11451149
display: flex;
11461150
justify-content: center;
11471151
align-items: center;
1148-
}
1152+
}
1153+
.noMargin {
1154+
margin: 0 !important;
1155+
}

src/assets/variables.css

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
src: url('fonts/nunito/nunito-semibold.woff2') format('woff2');
1111
}
1212

13+
html {
14+
--max-visible-cards: 4;
15+
}
16+
1317
html.dark {
1418
--app-name-text-color: #fff;
1519
--background-color: #0d1116;
@@ -89,7 +93,7 @@ html.dark {
8993
--settings-input-border-color: #3a4553;
9094
--settings-input-border-focus-color: #6b7b90;
9195
--settings-input-placeholder-color: #42474e;
92-
--settings-input-text-color: #fff
96+
--settings-input-text-color: #fff;
9397
}
9498

9599
html.light {
@@ -171,6 +175,5 @@ html.light {
171175
--settings-input-border-color: #e9ebec;
172176
--settings-input-border-focus-color: #c4d6df;
173177
--settings-input-placeholder-color: #97a6ad;
174-
--settings-input-text-color: #253b53
175-
178+
--settings-input-text-color: #253b53;
176179
}

src/components/Elements/ChipsSet/ChipsSet.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import clsx from 'clsx'
12
import { useState } from 'react'
23
import { Option } from 'src/types'
34
import './chipset.css'
4-
55
type ChipProps = {
66
option: Option
77
onSelect: (option: Option) => void
@@ -19,12 +19,14 @@ const Chip = ({ option, onSelect, active = false }: ChipProps) => {
1919
type ChangeAction = 'ADD' | 'REMOVE'
2020
type ChipsSetProps = {
2121
options: Option[]
22+
className?: string
2223
defaultValues?: string[]
2324
canSelectMultiple?: boolean
2425
onChange?: (action: ChangeAction, options: Option[]) => void
2526
}
2627

2728
export const ChipsSet = ({
29+
className,
2830
options,
2931
canSelectMultiple = false,
3032
onChange,
@@ -62,7 +64,7 @@ export const ChipsSet = ({
6264
}
6365

6466
return (
65-
<div className="chipsSet">
67+
<div className={clsx('chipsSet', className)}>
6668
{options.map((option) => {
6769
return (
6870
<Chip

src/components/Elements/ChipsSet/chipset.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,11 @@
4040
background-color: var(--chip-active-background);
4141
color: var(--chip-active-text);
4242
}
43+
44+
.chipsSet.alternative-color .chip {
45+
background-color: var(--card-action-button-background);
46+
}
47+
.chipsSet.alternative-color .chip.active {
48+
background-color: var(--card-active-action-button-background);
49+
color: var(--card-active-action-button-color);
50+
}

src/components/Layout/ScrollCardsNavigator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const ScrollCardsNavigator = () => {
3434
}, [])
3535

3636
useLayoutEffect(() => {
37+
setLeftButtonVisible(false)
3738
scrollBarContainer.current = document.querySelector('.Cards')
3839
}, [])
3940

@@ -48,7 +49,6 @@ export const ScrollCardsNavigator = () => {
4849
}, [handleKeyboardKeys])
4950

5051
useEffect(() => {
51-
setLeftButtonVisible(false)
5252
setRightButtonVisible(cards.length > maxCardsPerRow)
5353
}, [cards])
5454

src/features/settings/components/SettingsModal.tsx

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ReactModal from 'react-modal'
44
import Select, { ActionMeta, MultiValue, SingleValue } from 'react-select'
55
import Toggle from 'react-toggle'
66
import 'react-toggle/style.css'
7+
import { ChipsSet } from 'src/components/Elements'
78
import { Footer } from 'src/components/Layout'
89
import { SUPPORTED_CARDS, SUPPORTED_SEARCH_ENGINES, supportLink } from 'src/config'
910
import { Tag, useRemoteConfigStore } from 'src/features/remoteConfig'
@@ -12,19 +13,21 @@ import {
1213
identifyUserLanguages,
1314
identifyUserLinksInNewTab,
1415
identifyUserListingMode,
16+
identifyUserMaxVisibleCards,
1517
identifyUserSearchEngine,
1618
identifyUserTheme,
1719
trackLanguageAdd,
1820
trackLanguageRemove,
1921
trackListingModeSelect,
22+
trackMaxVisibleCardsChange,
2023
trackSearchEngineSelect,
2124
trackSourceAdd,
2225
trackSourceRemove,
2326
trackTabTarget,
2427
trackThemeSelect,
2528
} from 'src/lib/analytics'
2629
import { useUserPreferences } from 'src/stores/preferences'
27-
import { SearchEngineType, SelectedCard } from 'src/types'
30+
import { Option, SearchEngineType, SelectedCard } from 'src/types'
2831
import { PauseSettings } from './PauseSettings'
2932
import { RssSetting } from './RssSetting'
3033
import './settings.css'
@@ -49,8 +52,10 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
4952
listingMode,
5053
theme,
5154
searchEngine,
55+
maxVisibleCards,
5256
setTheme,
5357
setListingMode,
58+
setMaxVisibleCards,
5459
setSearchEngine,
5560
setOpenLinksNewTab,
5661
setCards,
@@ -153,6 +158,15 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
153158
}, 250)
154159
}
155160

161+
const onMaxVisibleCardsChange = (selectedChips: Option[]) => {
162+
if (selectedChips.length) {
163+
const maxVisibleCards = parseInt(selectedChips[0].value)
164+
setMaxVisibleCards(maxVisibleCards)
165+
identifyUserMaxVisibleCards(maxVisibleCards)
166+
trackMaxVisibleCardsChange(maxVisibleCards)
167+
}
168+
}
169+
156170
return (
157171
<ReactModal
158172
isOpen={showSettings}
@@ -226,6 +240,43 @@ export const SettingsModal = ({ showSettings, setShowSettings }: SettingsModalPr
226240

227241
<PauseSettings onSubmit={onPauseSubmit} />
228242

243+
<div className="settingRow">
244+
<p className="settingTitle">Max number of cards to display</p>
245+
<div className="settingContent">
246+
<ChipsSet
247+
className={'noMargin alternative-color'}
248+
canSelectMultiple={false}
249+
options={[
250+
{
251+
label: '3 cards',
252+
value: '3',
253+
},
254+
{
255+
label: '4 cards',
256+
value: '4',
257+
},
258+
{
259+
label: '5 cards',
260+
value: '5',
261+
},
262+
{
263+
label: '6 cards',
264+
value: '6',
265+
},
266+
]}
267+
defaultValues={[maxVisibleCards.toString()]}
268+
onChange={(_, selectedChips) => {
269+
onMaxVisibleCardsChange(selectedChips)
270+
}}
271+
/>
272+
273+
<p className="settingHint">
274+
To ensure a seamless experience, we may adjust the selected number to align with the
275+
resolution of your screen.
276+
</p>
277+
</div>
278+
</div>
279+
229280
<div className="settingRow">
230281
<p className="settingTitle">Dark Mode</p>
231282
<div className="settingContent">

src/lib/analytics.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum Verbs {
3838
FINISH = 'Finish',
3939
SKIP = 'Skip',
4040
DRAG = 'Drag',
41+
Change = 'Change',
4142
}
4243

4344
export enum Attributes {
@@ -60,6 +61,7 @@ export enum Attributes {
6061
SOURCE_TAGS = 'Source Tags',
6162
CAMPAIGN_ID = 'Campaign Id',
6263
OCCUPATION = 'Occupation',
64+
MAX_VISIBLE_CARDS = 'Max Visible Cards',
6365
}
6466

6567
const _SEP_ = ' '
@@ -82,6 +84,7 @@ export const setupIdentification = () => {
8284
listingMode,
8385
openLinksNewTab,
8486
searchEngine,
87+
maxVisibleCards
8588
} = useUserPreferences.getState()
8689

8790
identifyUserProperty(Attributes.RESOLUTION, getScreenResolution())
@@ -91,6 +94,7 @@ export const setupIdentification = () => {
9194
identifyUserListingMode(listingMode)
9295
identifyUserSearchEngine(searchEngine)
9396
identifyUserLinksInNewTab(openLinksNewTab)
97+
identifyUserMaxVisibleCards(maxVisibleCards)
9498
if (onboardingResult?.title) {
9599
identifyUserOccupation(onboardingResult.title)
96100
}
@@ -291,6 +295,14 @@ export const trackPageDrag = () => {
291295
})
292296
}
293297

298+
export const trackMaxVisibleCardsChange = (maxVisibleCards: number) => {
299+
trackEvent({
300+
object: Objects.CARD,
301+
verb: Verbs.Change,
302+
attributes: {[Attributes.MAX_VISIBLE_CARDS]: maxVisibleCards}
303+
})
304+
}
305+
294306
// Identification
295307

296308
export const identifyUserLanguages = (languages: string[]) => {
@@ -317,7 +329,9 @@ export const identifyUserLinksInNewTab = (enabled: boolean) => {
317329
export const identifyUserOccupation = (occupation: string) => {
318330
identifyUserProperty(Attributes.OCCUPATION, occupation)
319331
}
320-
332+
export const identifyUserMaxVisibleCards = (maxVisibleCards: number) => {
333+
identifyUserProperty(Attributes.MAX_VISIBLE_CARDS, maxVisibleCards)
334+
}
321335

322336
// Private functions
323337
type trackEventProps = {
@@ -365,13 +379,13 @@ const trackEvent = ({ object, verb, attributes }: trackEventProps) => {
365379
}
366380
}
367381

368-
const identifyUserProperty = (attributes: Attributes, value: string | string[]) => {
382+
const identifyUserProperty = (attributes: Attributes, value: string | number | string[]) => {
369383
try {
370384
let formatedValue
371385
if (Array.isArray(value)) {
372386
formatedValue = value.filter(Boolean).map((item) => item.toLowerCase())
373387
} else {
374-
formatedValue = value.toLowerCase()
388+
formatedValue = typeof value === "string" ? value.toLowerCase() : value.toString()
375389
}
376390

377391
if (isDevelopment()) {

0 commit comments

Comments
 (0)