feat: enhance picture notes browsing experience on large screen

This commit is contained in:
codytseng
2025-01-15 16:44:33 +08:00
parent 8e567dd401
commit 4acc1203fe
5 changed files with 57 additions and 13 deletions

View File

@@ -1,5 +1,7 @@
import { Carousel, CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel'
import { isTouchDevice } from '@/lib/common'
import { TImageInfo } from '@/types'
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
import Lightbox from 'yet-another-react-lightbox'
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
@@ -40,16 +42,23 @@ export function ImageCarousel({
}
return (
<>
<div className="relative space-y-2">
<Carousel className="w-full" setApi={setApi}>
<CarouselContent>
<CarouselContent className="xl:px-4">
{images.map((image, index) => (
<CarouselItem key={index}>
<Image image={image} onClick={(e) => handlePhotoClick(e, index)} />
<CarouselItem key={index} className="xl:basis-2/3 cursor-zoom-in">
<Image
className="xl:rounded-lg"
image={image}
onClick={(e) => handlePhotoClick(e, index)}
/>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
{!isTouchDevice() && (
<ArrowButton total={images.length} currentIndex={currentIndex} onClick={onDotClick} />
)}
{images.length > 1 && (
<CarouselDot total={images.length} currentIndex={currentIndex} onClick={onDotClick} />
)}
@@ -67,7 +76,7 @@ export function ImageCarousel({
styles={{ toolbar: { paddingTop: '2.25rem' } }}
/>
{isNsfw && <NsfwOverlay className="rounded-lg" />}
</>
</div>
)
}
@@ -85,10 +94,41 @@ function CarouselDot({
{Array.from({ length: total }).map((_, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full ${index === currentIndex ? 'bg-foreground/40' : 'bg-muted'}`}
className={`w-2 h-2 rounded-full cursor-pointer ${index === currentIndex ? 'bg-foreground/40' : 'bg-muted'}`}
onClick={() => onClick(index)}
/>
))}
</div>
)
}
function ArrowButton({
total,
currentIndex,
onClick
}: {
total: number
currentIndex: number
onClick: (index: number) => void
}) {
return (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none transition-opacity">
<div className="w-full flex justify-between px-2 xl:px-4">
<button
onClick={() => onClick(currentIndex - 1)}
className="w-8 h-8 rounded-full bg-background/50 flex justify-center items-center pointer-events-auto disabled:pointer-events-none disabled:opacity-0"
disabled={currentIndex === 0}
>
<ChevronLeftIcon className="w-4 h-4" />
</button>
<button
onClick={() => onClick(currentIndex + 1)}
className="w-8 h-8 rounded-full bg-background/50 flex justify-center items-center pointer-events-auto disabled:pointer-events-none disabled:opacity-0"
disabled={currentIndex === total - 1}
>
<ChevronRightIcon className="w-4 h-4" />
</button>
</div>
</div>
)
}

View File

@@ -30,7 +30,7 @@ export default function NoteList({
className?: string
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { isLargeScreen } = useScreenSize()
const { signEvent, checkLogin } = useNostr()
const { areAlgoRelays } = useFetchRelayInfos([...relayUrls])
const [refreshCount, setRefreshCount] = useState(0)
@@ -180,7 +180,7 @@ export default function NoteList({
{isPictures ? (
<PictureNoteCardMasonry
className="px-2 sm:px-4"
columnCount={isSmallScreen ? 2 : 3}
columnCount={isLargeScreen ? 3 : 2}
events={events}
/>
) : (
@@ -268,7 +268,7 @@ function PictureNoteCardMasonry({
)
})
return newColumns
}, [events])
}, [events, columnCount])
return (
<div

3
src/lib/common.ts Normal file
View File

@@ -0,0 +1,3 @@
export function isTouchDevice() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0
}

View File

@@ -12,14 +12,12 @@ import { useFetchEvent } from '@/hooks'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { getParentEventId, getRootEventId, isPictureEvent } from '@/lib/event'
import { toNote } from '@/lib/link'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import NotFoundPage from '../NotFoundPage'
export default function NotePage({ id, index }: { id?: string; index?: number }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { event, isFetching } = useFetchEvent(id)
const parentEventId = useMemo(() => getParentEventId(event), [event])
const rootEventId = useMemo(() => getRootEventId(event), [event])
@@ -35,7 +33,7 @@ export default function NotePage({ id, index }: { id?: string; index?: number })
}
if (!event) return <NotFoundPage />
if (isPictureEvent(event) && isSmallScreen) {
if (isPictureEvent(event)) {
return (
<SecondaryPageLayout index={index} title={t('Note')} displayScrollToTopButton>
<PictureNote key={`note-${event.id}`} event={event} fetchNoteStats />

View File

@@ -5,6 +5,7 @@ type TScreenSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl'
type TScreenSizeContext = {
screenSize: TScreenSize
isSmallScreen: boolean
isLargeScreen: boolean
}
const ScreenSizeContext = createContext<TScreenSizeContext | undefined>(undefined)
@@ -20,6 +21,7 @@ export const useScreenSize = () => {
export function ScreenSizeProvider({ children }: { children: React.ReactNode }) {
const [screenSize, setScreenSize] = useState<TScreenSize>('sm')
const isSmallScreen = useMemo(() => screenSize === 'sm', [screenSize])
const isLargeScreen = useMemo(() => ['2xl'].includes(screenSize), [screenSize])
useEffect(() => {
const handleResize = () => {
@@ -47,7 +49,8 @@ export function ScreenSizeProvider({ children }: { children: React.ReactNode })
<ScreenSizeContext.Provider
value={{
screenSize,
isSmallScreen
isSmallScreen,
isLargeScreen
}}
>
{children}