Clean up nav so there is no flicker due to component reloading

This commit is contained in:
Jackson Harper
2024-06-12 18:48:33 +08:00
parent 39bfe04ec1
commit cdf921c4b4
8 changed files with 159 additions and 436 deletions

View File

@ -1,5 +1,5 @@
import { NavigationLayout } from '../../components/templates/NavigationLayout'
import { Box, HStack, VStack } from '../../components/elements/LayoutPrimitives'
import { NavigationLayout } from '../templates/NavigationLayout'
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
import { useFetchMore } from '../../lib/hooks/useFetchMoreScroll'
import { useCallback, useMemo, useState } from 'react'
import { useGetHighlights } from '../../lib/networking/queries/useGetHighlights'
@ -9,16 +9,16 @@ import {
UserBasicData,
useGetViewerQuery,
} from '../../lib/networking/queries/useGetViewerQuery'
import { SetHighlightLabelsModalPresenter } from '../../components/templates/article/SetLabelsModalPresenter'
import { TrashIcon } from '../../components/elements/icons/TrashIcon'
import { SetHighlightLabelsModalPresenter } from '../templates/article/SetLabelsModalPresenter'
import { TrashIcon } from '../elements/icons/TrashIcon'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { ConfirmationModal } from '../patterns/ConfirmationModal'
import { deleteHighlightMutation } from '../../lib/networking/mutations/deleteHighlightMutation'
import { LabelChip } from '../../components/elements/LabelChip'
import { LabelChip } from '../elements/LabelChip'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
import { HighlightHoverActions } from '../../components/patterns/HighlightHoverActions'
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
import { HighlightHoverActions } from '../patterns/HighlightHoverActions'
import {
autoUpdate,
offset,
@ -29,58 +29,12 @@ import {
} from '@floating-ui/react'
import { highlightColor } from '../../lib/themeUpdater'
import { HighlightViewNote } from '../../components/patterns/HighlightNotes'
import { theme } from '../../components/tokens/stitches.config'
import { HighlightViewNote } from '../patterns/HighlightNotes'
import { theme } from '../tokens/stitches.config'
const PAGE_SIZE = 10
export default function Highlights(): JSX.Element {
const router = useRouter()
const viewer = useGetViewerQuery()
const [showFilterMenu, setShowFilterMenu] = useState(false)
const [_, setShowAddLinkModal] = useState(false)
const { isLoading, setSize, size, data, mutate } = useGetHighlights({
first: PAGE_SIZE,
})
const hasMore = useMemo(() => {
if (!data) {
return false
}
return data[data.length - 1].highlights.pageInfo.hasNextPage
}, [data])
const handleFetchMore = useCallback(() => {
if (isLoading || !hasMore) {
return
}
setSize(size + 1)
}, [isLoading, hasMore, setSize, size])
useFetchMore(handleFetchMore)
const highlights = useMemo(() => {
if (!data) {
return []
}
return data.flatMap((res) => res.highlights.edges.map((edge) => edge.node))
}, [data])
return (
<NavigationLayout
section="highlights"
pageMetaDataProps={{
title: 'Highlights',
path: '/highlights',
}}
>
<HighlightsList />
</NavigationLayout>
)
}
export function HighlightsList(): JSX.Element {
export function HighlightsContainer(): JSX.Element {
const router = useRouter()
const viewer = useGetViewerQuery()
const [showFilterMenu, setShowFilterMenu] = useState(false)

View File

@ -2,15 +2,15 @@ import * as HoverCard from '@radix-ui/react-hover-card'
import { styled } from '@stitches/react'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
import { Button } from '../../components/elements/Button'
import { AddToLibraryActionIcon } from '../../components/elements/icons/home/AddToLibraryActionIcon'
import { ArchiveActionIcon } from '../../components/elements/icons/home/ArchiveActionIcon'
import { CommentActionIcon } from '../../components/elements/icons/home/CommentActionIcon'
import { RemoveActionIcon } from '../../components/elements/icons/home/RemoveActionIcon'
import { ShareActionIcon } from '../../components/elements/icons/home/ShareActionIcon'
import Pagination from '../../components/elements/Pagination'
import { timeAgo } from '../../components/patterns/LibraryCards/LibraryCardStyles'
import { theme } from '../../components/tokens/stitches.config'
import { Button } from '../elements/Button'
import { AddToLibraryActionIcon } from '../elements/icons/home/AddToLibraryActionIcon'
import { ArchiveActionIcon } from '../elements/icons/home/ArchiveActionIcon'
import { CommentActionIcon } from '../elements/icons/home/CommentActionIcon'
import { RemoveActionIcon } from '../elements/icons/home/RemoveActionIcon'
import { ShareActionIcon } from '../elements/icons/home/ShareActionIcon'
import Pagination from '../elements/Pagination'
import { timeAgo } from '../patterns/LibraryCards/LibraryCardStyles'
import { theme } from '../tokens/stitches.config'
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
import { useGetHiddenHomeSection } from '../../lib/networking/queries/useGetHiddenHomeSection'
import {
@ -24,83 +24,75 @@ import {
SubscriptionType,
useGetSubscriptionsQuery,
} from '../../lib/networking/queries/useGetSubscriptionsQuery'
import {
Box,
HStack,
SpanBox,
VStack,
} from './../../components/elements/LayoutPrimitives'
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
import { Toaster } from 'react-hot-toast'
import { NavigationLayout } from '../../components/templates/NavigationLayout'
export default function Home(): JSX.Element {
export function HomeContainer(): JSX.Element {
const homeData = useGetHomeItems()
useApplyLocalTheme()
return (
<NavigationLayout section="home">
<VStack
distribution="start"
alignment="center"
css={{
width: '100%',
bg: '$readerBg',
pt: '45px',
minHeight: '100vh',
}}
>
<Toaster />
<VStack
distribution="start"
alignment="center"
css={{
width: '100%',
bg: '$readerBg',
pt: '45px',
width: '646px',
gap: '40px',
minHeight: '100vh',
'@mdDown': {
width: '100%',
},
}}
>
<Toaster />
<VStack
distribution="start"
css={{
width: '646px',
gap: '40px',
minHeight: '100vh',
'@mdDown': {
width: '100%',
},
}}
>
{homeData.sections?.map((homeSection, idx) => {
if (homeSection.items.length < 1) {
{homeData.sections?.map((homeSection, idx) => {
if (homeSection.items.length < 1) {
return <></>
}
switch (homeSection.layout) {
case 'just_added':
return (
<JustAddedHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'top_picks':
return (
<TopPicksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'quick_links':
return (
<QuickLinksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'hidden':
return (
<HiddenHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
default:
return <></>
}
switch (homeSection.layout) {
case 'just_added':
return (
<JustAddedHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'top_picks':
return (
<TopPicksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'quick_links':
return (
<QuickLinksHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
case 'hidden':
return (
<HiddenHomeSection
key={`section-${idx}`}
homeSection={homeSection}
/>
)
default:
return <></>
}
})}
</VStack>
}
})}
</VStack>
</NavigationLayout>
</VStack>
)
}
@ -636,103 +628,3 @@ const SubscriptionSourceHoverContent = (
</VStack>
)
}
// const SiteSourceHoverContent = (
// props: SourceHoverContentProps
// ): JSX.Element => {
// const sendHomeFeedback = useCallback(
// async (feedbackType: SendHomeFeedbackType) => {
// const feedback: SendHomeFeedbackInput = {
// feedbackType,
// }
// feedback.site = props.source.name
// const result = await sendHomeFeedbackMutation(feedback)
// if (result) {
// showSuccessToast('Feedback sent')
// } else {
// showErrorToast('Error sending feedback')
// }
// },
// [props]
// )
// return (
// <VStack
// alignment="start"
// distribution="start"
// css={{
// width: '240px',
// height: '100px',
// bg: '$thBackground2',
// borderRadius: '10px',
// padding: '15px',
// gap: '10px',
// boxShadow: theme.shadows.cardBoxShadow.toString(),
// }}
// >
// <HStack
// distribution="start"
// alignment="center"
// css={{ width: '100%', gap: '10px' }}
// >
// {props.source.icon && (
// <SiteIcon
// src={props.source.icon}
// alt={props.source.name}
// size="large"
// />
// )}
// <SpanBox
// css={{
// fontFamily: '$inter',
// fontWeight: '500',
// fontSize: '14px',
// }}
// >
// {props.source.name}
// </SpanBox>
// </HStack>
// {/* <SpanBox
// css={{
// fontFamily: '$inter',
// fontSize: '13px',
// color: '$thTextSubtle4',
// }}
// >
// {subscription ? <>{subscription.description}</> : <></>}
// </SpanBox> */}
// <FeedbackView sendFeedback={sendHomeFeedback} />
// </VStack>
// )
// }
// type FeedbackViewProps = {
// sendFeedback: (type: SendHomeFeedbackType) => void
// }
// const FeedbackView = (props: FeedbackViewProps): JSX.Element => {
// return (
// <HStack css={{ ml: 'auto', mt: 'auto', gap: '5px' }}>
// <Button
// style="plainIcon"
// onClick={(event) => {
// props.sendFeedback('MORE')
// event.preventDefault()
// event.stopPropagation()
// }}
// >
// <ThumbsUp weight="fill" />
// </Button>
// <Button
// style="plainIcon"
// onClick={(event) => {
// props.sendFeedback('LESS')
// event.preventDefault()
// event.stopPropagation()
// }}
// >
// <ThumbsDown weight="fill" />
// </Button>
// </HStack>
// )
// }

View File

@ -128,11 +128,8 @@ export function NavigationLayout(props: NavigationLayoutProps): JSX.Element {
section={props.section}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setShowAddLinkModal={() => {}}
searchTerm=""
// eslint-disable-next-line @typescript-eslint/no-empty-function
applySearchQuery={() => {}}
showFilterMenu={showNavMenu}
setShowFilterMenu={setShowNavMenu}
showMenu={showNavMenu}
setShowMenu={setShowNavMenu}
/>
)}
{props.children}
@ -185,6 +182,7 @@ const Header = (props: HeaderProps): JSX.Element => {
props.toggleMenu()
event.preventDefault()
}}
css={{ height: 'unset' }}
>
<List size="25" color={theme.colors.readerTextSubtle.toString()} />
</Button>

View File

@ -2,7 +2,6 @@ import {
CSSProperties,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
@ -10,22 +9,10 @@ import {
import { StyledText } from '../../elements/StyledText'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import {
DotsThree,
List,
X,
Folder,
FolderOpen,
Tag,
ArrowDown,
CaretDown,
CaretUp,
Archive,
} from '@phosphor-icons/react'
import { DotsThree, List, X, Tag } from '@phosphor-icons/react'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { theme } from '../../tokens/stitches.config'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import Link from 'next/link'
import { NavMenuFooter } from './Footer'
import { FollowingIcon } from '../../elements/icons/FollowingIcon'
import { HomeIcon } from '../../elements/icons/HomeIcon'
@ -46,15 +33,12 @@ import { requestHeaders } from '../../../lib/networking/networkHelpers'
import { v4 as uuidv4 } from 'uuid'
import { showErrorToast } from '../../../lib/toastHelpers'
import { OpenMap } from 'react-arborist/dist/module/state/open-slice'
import { ToggleCaretLeftIcon } from '../../elements/icons/ToggleCaretLeftIcon'
import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon'
import { TrashIcon } from '../../elements/icons/TrashIcon'
import { ArchiveActionIcon } from '../../elements/icons/home/ArchiveActionIcon'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
import { ArchiveSectionIcon } from '../../elements/icons/ArchiveSectionIcon'
import { NavMoreButtonDownIcon } from '../../elements/icons/NavMoreButtonDown'
import { NavMoreButtonUpIcon } from '../../elements/icons/NavMoreButtonUp'
import { ShortcutFolderClosed } from '../../elements/icons/ShortcutFolderClosed'
import { TrashSectionIcon } from '../../elements/icons/TrashSectionIcon'
import { ShortcutFolderOpen } from '../../elements/icons/ShortcutFolderOpen'
export const LIBRARY_LEFT_MENU_WIDTH = '275px'
@ -74,19 +58,16 @@ export type Shortcut = {
join?: string
}
type LibraryFilterMenuProps = {
type NavigationMenuProps = {
section: NavigationSection
setShowAddLinkModal: (show: boolean) => void
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
showMenu: boolean
setShowMenu: (show: boolean) => void
}
export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
export function NavigationMenu(props: NavigationMenuProps): JSX.Element {
return (
<>
<Box
@ -105,8 +86,8 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
'@mdDown': {
width: '100%',
transition: 'top 100ms, visibility 100ms',
top: props.showFilterMenu ? '0' : '100%',
visibility: props.showFilterMenu ? 'visible' : 'hidden',
top: props.showMenu ? '0' : '100%',
visibility: props.showMenu ? 'visible' : 'hidden',
},
zIndex: 10,
}}
@ -129,7 +110,7 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
<Button
style="plainIcon"
onClick={(event) => {
props.setShowFilterMenu(false)
props.setShowMenu(false)
event.preventDefault()
}}
>
@ -152,7 +133,7 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
},
}}
onClick={(event) => {
props.setShowFilterMenu(false)
props.setShowMenu(false)
event.preventDefault()
}}
>
@ -180,12 +161,12 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
)
}
const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
const [moreFoldersOpenState, setMoreFoldersOpenState] =
const LibraryNav = (props: NavigationMenuProps): JSX.Element => {
const [moreFolderSectionOpen, setMoreFolderSectionOpen] =
usePersistedState<boolean>({
key: 'nav-menu-more-folders-open',
key: 'nav-more-folder-open',
isSessionStorage: false,
initialValue: false,
initialValue: true,
})
return (
<VStack
@ -253,7 +234,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
},
}}
onClick={(event) => {
setMoreFoldersOpenState(!moreFoldersOpenState)
setMoreFolderSectionOpen(!moreFolderSectionOpen)
event.preventDefault()
}}
>
@ -262,7 +243,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
alignment="center"
distribution="start"
>
{moreFoldersOpenState ? (
{moreFolderSectionOpen ? (
<NavMoreButtonUpIcon
color={theme.colors.thLibraryMenuPrimary.toString()}
/>
@ -274,7 +255,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
<SpanBox>More</SpanBox>
</HStack>
</Button>
{moreFoldersOpenState && (
{moreFolderSectionOpen && (
<SpanBox css={{ width: '100%' }}>
<NavButton
{...props}
@ -293,7 +274,9 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
section="trash"
isSelected={props.section == 'trash'}
icon={
<TrashIcon color={theme.colors.thLibraryMenuPrimary.toString()} />
<TrashSectionIcon
color={theme.colors.thLibraryMenuPrimary.toString()}
/>
}
/>
</SpanBox>
@ -302,7 +285,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
)
}
const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => {
const Shortcuts = (props: NavigationMenuProps): JSX.Element => {
const treeRef = useRef<TreeApi<Shortcut> | undefined>(undefined)
const { trigger: resetShortcutsTrigger } = useSWRMutation(
'/api/shortcuts',
@ -352,7 +335,7 @@ const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => {
px: '15px',
}}
>
SHORTCUTS
Shortcuts
</StyledText>
<SpanBox css={{ display: 'flex', ml: 'auto', mt: '5px', mr: '15px' }}>
<Dropdown
@ -572,7 +555,7 @@ const ShortcutsTree = (props: ShortcutsTreeProps): JSX.Element => {
console.log('query: ', query)
}
} else if (node.data.section != null && node.data.filter != null) {
router.push(`/${node.data.section}?q=${node.data.filter}`)
router.push(`/l/${node.data.section}?q=${node.data.filter}`)
}
},
[tree, router]
@ -785,8 +768,7 @@ const NodeItemContents = (props: NodeItemContentsProps): JSX.Element => {
color={theme.colors.thLibraryMenuPrimary.toString()}
/>
) : (
<FolderOpen
size={20}
<ShortcutFolderOpen
color={theme.colors.thLibraryMenuPrimary.toString()}
/>
)}
@ -929,7 +911,7 @@ function NavButton(props: NavButtonProps): JSX.Element {
}}
title={props.text}
onClick={(e) => {
router.push(`/` + props.section)
router.push(`/l/` + props.section)
}}
>
{props.icon}
@ -937,113 +919,3 @@ function NavButton(props: NavButtonProps): JSX.Element {
</HStack>
)
}
type FilterButtonProps = {
text: string
filterTerm: string
searchTerm: string | undefined
applySearchQuery: (searchTerm: string) => void
setShowFilterMenu: (show: boolean) => void
}
function FilterButton(props: FilterButtonProps): JSX.Element {
const isInboxFilter = (filter: string) => {
return filter === '' || filter === 'in:inbox'
}
const selected = useMemo(() => {
if (isInboxFilter(props.filterTerm) && !props.searchTerm) {
return true
}
return props.searchTerm === props.filterTerm
}, [props.searchTerm, props.filterTerm])
return (
<Box
css={{
pl: '10px',
mb: '2px',
display: 'flex',
width: '100%',
maxWidth: '100%',
height: '32px',
backgroundColor: selected ? '$thLibrarySelectionColor' : 'unset',
fontSize: '14px',
fontWeight: 'regular',
fontFamily: '$display',
color: selected
? '$thLibraryMenuSecondary'
: '$thLibraryMenuUnselected',
verticalAlign: 'middle',
borderRadius: '3px',
cursor: 'pointer',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
alignItems: 'center',
'&:hover': {
backgroundColor: selected
? '$thLibrarySelectionColor'
: '$thBackground4',
},
'&:active': {
backgroundColor: selected
? '$thLibrarySelectionColor'
: '$thBackground4',
},
}}
title={props.text}
onClick={(e) => {
props.applySearchQuery(props.filterTerm)
props.setShowFilterMenu(false)
e.preventDefault()
}}
>
{props.text}
</Box>
)
}
type EditButtonProps = {
title: string
destination: string
}
function EditButton(props: EditButtonProps): JSX.Element {
return (
<Link href={props.destination} passHref legacyBehavior>
<SpanBox
css={{
ml: '10px',
mb: '10px',
display: 'flex',
alignItems: 'center',
gap: '2px',
'&:hover': {
textDecoration: 'underline',
},
width: '100%',
maxWidth: '100%',
height: '32px',
fontSize: '14px',
fontWeight: 'regular',
fontFamily: '$display',
color: '$thLibraryMenuUnselected',
verticalAlign: 'middle',
borderRadius: '3px',
cursor: 'pointer',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{props.title}
</SpanBox>
</Link>
)
}

View File

@ -1,16 +0,0 @@
import { NavigationLayout } from '../../components/templates/NavigationLayout'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
export default function Archive(): JSX.Element {
return (
<NavigationLayout
section="archive"
pageMetaDataProps={{
title: 'Archive',
path: '/archive',
}}
>
<LibraryContainer folder="archive" />
</NavigationLayout>
)
}

View File

@ -0,0 +1,55 @@
import { useRouter } from 'next/router'
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
import {
NavigationLayout,
NavigationSection,
} from '../../components/templates/NavigationLayout'
import { HomeContainer } from '../../components/nav-containers/home'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
import { useMemo } from 'react'
import { HighlightsContainer } from '../../components/nav-containers/highlights'
export default function Home(): JSX.Element {
const router = useRouter()
useApplyLocalTheme()
const section: NavigationSection | undefined = useMemo(() => {
if (!router.isReady) {
return undefined
}
const res = router.query.section
if (typeof res !== 'string') {
return undefined
}
return res as NavigationSection
}, [router])
const sectionView = (name: string | string[] | undefined) => {
if (typeof name !== 'string') {
return <></>
}
switch (name) {
case 'home':
return <HomeContainer />
case 'highlights':
return <HighlightsContainer />
case 'library':
return <LibraryContainer folder="inbox" />
case 'subscriptions':
return <LibraryContainer folder="following" />
case 'archive':
return <LibraryContainer folder="archive" />
case 'trash':
return <LibraryContainer folder="trash" />
default:
return <></>
}
}
return (
<NavigationLayout section={section ?? 'home'}>
{sectionView(section)}
</NavigationLayout>
)
}

View File

@ -1,16 +0,0 @@
import { NavigationLayout } from '../../components/templates/NavigationLayout'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
export default function Library(): JSX.Element {
return (
<NavigationLayout
section="library"
pageMetaDataProps={{
title: 'Library',
path: '/library',
}}
>
<LibraryContainer folder="inbox" />
</NavigationLayout>
)
}

View File

@ -1,16 +0,0 @@
import { NavigationLayout } from '../../components/templates/NavigationLayout'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
export default function Trash(): JSX.Element {
return (
<NavigationLayout
section="trash"
pageMetaDataProps={{
title: 'Trash',
path: '/trash',
}}
>
<LibraryContainer folder="trash" />
</NavigationLayout>
)
}