@ -1,16 +1,19 @@
@@ -1,16 +1,19 @@
import { LIVE_ACTIVITIES_SLIDE_INTERVAL_MS } from '@/lib/live-activities'
import { toNote } from '@/lib/link'
import { cn } from '@/lib/utils'
import { useLiveActivitiesOptional } from '@/providers/LiveActivitiesProvider'
import { useSmartNoteNavigation } from '@/PageManager'
import { useLiveActivitiesOptional } from '@/providers/useLiveActivities'
import { useUserPreferencesOptional } from '@/providers/UserPreferencesProvider'
import storage from '@/services/local-storage.service'
import { ExternalLink } from 'lucide-react'
import { useEffect , useLayoutEffect , useState } from 'react'
import { useCallback , use Effect , useLayoutEffect , useState } from 'react'
import { useTranslation } from 'react-i18next'
type TPlacement = 'sidebar' | 'mobile'
export default function LiveActivitiesStrip ( { placement } : { placement : TPlacement } ) {
const { t } = useTranslation ( )
const { navigateToNote } = useSmartNoteNavigation ( )
const userPrefs = useUserPreferencesOptional ( )
const showLiveActivitiesBanner =
userPrefs ? . showLiveActivitiesBanner ? ? storage . getShowLiveActivitiesBanner ( )
@ -45,12 +48,21 @@ export default function LiveActivitiesStrip({ placement }: { placement: TPlaceme
@@ -45,12 +48,21 @@ export default function LiveActivitiesStrip({ placement }: { placement: TPlaceme
setSlide ( ( s ) = > Math . min ( s , items . length - 1 ) )
} , [ items . length ] )
// `items` can shrink without a new array identity; `slide` may then be out of range until effects run.
const displayIndex = items . length === 0 ? 0 : Math.min ( slide , items . length - 1 )
const itemAtSlide = items [ displayIndex ]
const openLiveNote = useCallback ( ( ) = > {
const ev = itemAtSlide ? . event
if ( ! ev ) return
// Same bech32 as {@link getNoteBech32Id} + pass event so {@link navigationEventStore} / cache match the URL.
navigateToNote ( toNote ( ev ) , ev )
} , [ navigateToNote , itemAtSlide ] )
if ( ! showLiveActivitiesBanner || items . length === 0 ) {
return null
}
// `items` can shrink without a new array identity; `slide` may then be out of range until effects run.
const displayIndex = Math . min ( slide , items . length - 1 )
const current = items [ displayIndex ]
if ( ! current ) {
return null
@ -69,41 +81,58 @@ export default function LiveActivitiesStrip({ placement }: { placement: TPlaceme
@@ -69,41 +81,58 @@ export default function LiveActivitiesStrip({ placement }: { placement: TPlaceme
< div className = "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground xl:text-xs" >
{ t ( 'liveActivities.heading' ) }
< / div >
< a
href = { current . joinUrl }
target = "_blank"
rel = "noopener noreferrer"
< div
className = { cn (
'flex min-w-0 gap-2 rounded-md transition-colors hover:bg-muted/80 ' ,
placement === 'sidebar' && 'flex-col xl:flex-row xl:items-sta rt' ,
placement === 'mobile' && 'items-center '
'flex min-w-0 gap-1.5 rounded-md' ,
placement === 'sidebar' && 'flex-col xl:flex-row xl:items-stretch' ,
placement === 'mobile' && 'items-stretch '
) }
>
{ current . imageUrl ? (
< img
src = { current . imageUrl }
alt = ""
className = { cn (
'shrink-0 rounded object-cover' ,
placement === 'sidebar' ? 'h-14 w-full xl:h-12 xl:w-12' : 'h-12 w-12'
) }
/ >
) : null }
< div className = "min-w-0 flex-1" >
< div className = "flex items-start gap-1" >
< span className = "line-clamp-2 min-w-0 flex-1 text-xs font-medium leading-snug xl:text-sm" >
{ current . title }
< / span >
< ExternalLink className = "mt-0.5 size-3.5 shrink-0 text-muted-foreground" aria - hidden / >
< / div >
{ current . summary ? (
< p className = "mt-0.5 line-clamp-2 text-[11px] text-muted-foreground xl:text-xs" > { current . summary } < / p >
) : null }
{ current . fromFollowedHost ? (
< p className = "mt-1 text-[10px] text-green-600 dark:text-green-500" > { t ( 'liveActivities.fromFollow' ) } < / p >
< button
type = "button"
onClick = { openLiveNote }
className = { cn (
'flex min-w-0 flex-1 gap-2 rounded-md text-left transition-colors hover:bg-muted/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring' ,
placement === 'sidebar' && 'flex-col xl:flex-row xl:items-start' ,
placement === 'mobile' && 'items-center'
) }
title = { t ( 'liveActivities.viewNoteTitle' ) }
>
{ current . imageUrl ? (
< img
src = { current . imageUrl }
alt = ""
className = { cn (
'shrink-0 rounded object-cover' ,
placement === 'sidebar' ? 'h-14 w-full xl:h-12 xl:w-12' : 'h-12 w-12'
) }
/ >
) : null }
< / div >
< / a >
< div className = "min-w-0 flex-1" >
< div className = "line-clamp-2 text-xs font-medium leading-snug xl:text-sm" > { current . title } < / div >
{ current . summary ? (
< p className = "mt-0.5 line-clamp-2 text-[11px] text-muted-foreground xl:text-xs" > { current . summary } < / p >
) : null }
{ current . fromFollowedHost ? (
< p className = "mt-1 text-[10px] text-green-600 dark:text-green-500" > { t ( 'liveActivities.fromFollow' ) } < / p >
) : null }
< / div >
< / button >
< a
href = { current . joinUrl }
target = "_blank"
rel = "noopener noreferrer"
className = { cn (
'flex shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground' ,
placement === 'sidebar' ? 'h-9 w-full xl:h-auto xl:w-9 xl:self-start' : 'h-12 w-10'
) }
title = { t ( 'liveActivities.openJoinPageTitle' ) }
aria - label = { t ( 'liveActivities.openJoinPageTitle' ) }
onClick = { ( e ) = > e . stopPropagation ( ) }
>
< ExternalLink className = "size-4 shrink-0" aria - hidden / >
< / a >
< / div >
{ items . length > 1 ? (
< div className = "mt-2 flex justify-center gap-1.5" >
{ items . map ( ( item , i ) = > (