@@ -24,6 +24,8 @@ import useFeedInfiniteScroll, {
2424 InfiniteScrollScreenOffset ,
2525} from '../hooks/feed/useFeedInfiniteScroll' ;
2626import FeedItemComponent , { getFeedItemKey } from './FeedItemComponent' ;
27+ import { computeColSpans } from '../lib/feedHighlightColSpan' ;
28+ import type { FeaturedWideColSpan } from './cards/article/ArticleFeaturedWideGridCard' ;
2729import { useLogContext } from '../contexts/LogContext' ;
2830import { feedLogExtra , postLogEvent } from '../lib/feed' ;
2931import { usePostModalNavigation } from '../hooks/usePostModalNavigation' ;
@@ -67,6 +69,7 @@ import {
6769 briefCardFeedFeature ,
6870 briefFeedEntrypointPage ,
6971 featureFeedAdTemplate ,
72+ featurePostHighlightCards ,
7073} from '../lib/featureManagement' ;
7174import { useNewD1ExperienceFeature } from '../hooks/useNewD1ExperienceFeature' ;
7275import type { AwardProps } from '../graphql/njord' ;
@@ -372,6 +375,59 @@ export default function Feed<T>({
372375 firstSlotOffset : Number ( showProfileCompletionCard || showBriefCard ) ,
373376 } ) ;
374377
378+ const isMobileViewport = ! isTabletViewport ;
379+ const isListContext = useList || shouldUseListFeedLayout ;
380+ const canRenderHighlightCards =
381+ ! isMobileViewport && ! isListContext && virtualizedNumCards > 1 ;
382+ const { value : isPostHighlightCardsEnabled } = useConditionalFeature ( {
383+ feature : featurePostHighlightCards ,
384+ shouldEvaluate : canRenderHighlightCards ,
385+ } ) ;
386+ const isHighlightCardLayoutEnabled =
387+ canRenderHighlightCards && isPostHighlightCardsEnabled ;
388+ const currentPageSize = pageSize ?? currentSettings . pageSize ;
389+ const showPromoBanner = ! ! briefBannerPage ;
390+ const columnsDiffWithPage = currentPageSize % virtualizedNumCards ;
391+ const indexWhenShowingPromoBanner =
392+ currentPageSize * Number ( briefBannerPage ) - // number of items at that page
393+ columnsDiffWithPage * Number ( briefBannerPage ) - // cards let out of rows * page number
394+ Number ( showFirstSlotCard ) ;
395+
396+ const fullRowInsertionBeforeIndex = useMemo ( ( ) => {
397+ const set = new Set < number > ( ) ;
398+ if ( showPromoBanner ) {
399+ set . add ( indexWhenShowingPromoBanner ) ;
400+ }
401+ if ( shouldShowInFeedHero ) {
402+ set . add ( adjustedHeroInsertIndex ) ;
403+ }
404+ return set ;
405+ } , [
406+ showPromoBanner ,
407+ indexWhenShowingPromoBanner ,
408+ shouldShowInFeedHero ,
409+ adjustedHeroInsertIndex ,
410+ ] ) ;
411+
412+ const itemColSpans = useMemo (
413+ ( ) =>
414+ computeColSpans ( items , {
415+ numCards : virtualizedNumCards ,
416+ isMobile : isMobileViewport ,
417+ isList : isListContext ,
418+ isEnabled : isHighlightCardLayoutEnabled ,
419+ fullRowInsertionBeforeIndex,
420+ } ) ,
421+ [
422+ items ,
423+ virtualizedNumCards ,
424+ isMobileViewport ,
425+ isListContext ,
426+ isHighlightCardLayoutEnabled ,
427+ fullRowInsertionBeforeIndex ,
428+ ] ,
429+ ) ;
430+
375431 useMutationSubscription ( {
376432 matcher : ( { mutation } ) => {
377433 const [ requestKey ] = Array . isArray ( mutation . options . mutationKey )
@@ -638,14 +694,6 @@ export default function Feed<T>({
638694 feedName as SharedFeedPage ,
639695 ) ;
640696
641- const currentPageSize = pageSize ?? currentSettings . pageSize ;
642- const showPromoBanner = ! ! briefBannerPage ;
643- const columnsDiffWithPage = currentPageSize % virtualizedNumCards ;
644- const indexWhenShowingPromoBanner =
645- currentPageSize * Number ( briefBannerPage ) - // number of items at that page
646- columnsDiffWithPage * Number ( briefBannerPage ) - // cards let out of rows * page number
647- Number ( showFirstSlotCard ) ;
648-
649697 const FeedWrapperComponent = isSearchPageLaptop
650698 ? SearchResultsLayout
651699 : FeedContainer ;
@@ -697,45 +745,14 @@ export default function Feed<T>({
697745 } }
698746 />
699747 ) }
700- { items . map ( ( item , index ) => (
701- < FeedCardContext . Provider
702- key = { getFeedItemKey ( item , index ) }
703- value = { {
704- boostedBy : isBoostedPostAd ( item )
705- ? item . ad . data ?. post ?. author || item . ad . data ?. post ?. scout
706- : undefined ,
707- } }
708- >
709- { showPromoBanner && index === indexWhenShowingPromoBanner && (
710- < BriefBannerFeed
711- style = { {
712- gridColumn : ! shouldUseListFeedLayout
713- ? `span ${ virtualizedNumCards } `
714- : undefined ,
715- } }
716- />
717- ) }
718- { shouldShowInFeedHero && index === adjustedHeroInsertIndex && (
719- < div
720- style = { {
721- gridColumn : ! shouldUseListFeedLayout
722- ? `span ${ virtualizedNumCards } `
723- : undefined ,
724- } }
725- >
726- < TopHero
727- className = "pt-0"
728- title = { readingReminderTitle }
729- subtitle = { readingReminderSubtitle }
730- onCtaClick = { ( ) =>
731- onEnableHero ( NotificationCtaPlacement . InFeedHero )
732- }
733- onClose = { ( ) =>
734- onDismissHero ( NotificationCtaPlacement . InFeedHero )
735- }
736- />
737- </ div >
738- ) }
748+ { items . map ( ( item , index ) => {
749+ const colSpan = itemColSpans [ index ] ?? 1 ;
750+ const isWidened = colSpan > 1 ;
751+ const wideColSpan =
752+ isWidened && ( colSpan === 2 || colSpan === 3 || colSpan === 4 )
753+ ? ( colSpan as FeaturedWideColSpan )
754+ : undefined ;
755+ const itemNode = (
739756 < FeedItemComponent
740757 item = { item }
741758 index = { index }
@@ -758,9 +775,64 @@ export default function Feed<T>({
758775 onReadArticleClick = { onReadArticleClick }
759776 virtualizedNumCards = { virtualizedNumCards }
760777 disableAdRefresh = { disableAdRefresh }
778+ wideColSpan = { wideColSpan }
761779 />
762- </ FeedCardContext . Provider >
763- ) ) }
780+ ) ;
781+
782+ return (
783+ < FeedCardContext . Provider
784+ key = { getFeedItemKey ( item , index ) }
785+ value = { {
786+ boostedBy : isBoostedPostAd ( item )
787+ ? item . ad . data ?. post ?. author || item . ad . data ?. post ?. scout
788+ : undefined ,
789+ } }
790+ >
791+ { showPromoBanner && index === indexWhenShowingPromoBanner && (
792+ < BriefBannerFeed
793+ style = { {
794+ gridColumn : ! shouldUseListFeedLayout
795+ ? `span ${ virtualizedNumCards } `
796+ : undefined ,
797+ } }
798+ />
799+ ) }
800+ { shouldShowInFeedHero &&
801+ index === adjustedHeroInsertIndex && (
802+ < div
803+ style = { {
804+ gridColumn : ! shouldUseListFeedLayout
805+ ? `span ${ virtualizedNumCards } `
806+ : undefined ,
807+ } }
808+ >
809+ < TopHero
810+ className = "pt-0"
811+ title = { readingReminderTitle }
812+ subtitle = { readingReminderSubtitle }
813+ onCtaClick = { ( ) =>
814+ onEnableHero ( NotificationCtaPlacement . InFeedHero )
815+ }
816+ onClose = { ( ) =>
817+ onDismissHero ( NotificationCtaPlacement . InFeedHero )
818+ }
819+ />
820+ </ div >
821+ ) }
822+ { isWidened ? (
823+ < div
824+ className = "flex h-full w-full [&>*]:h-full [&>*]:w-full"
825+ style = { { gridColumn : `span ${ colSpan } ` } }
826+ data-testid = "feedItemColSpanWrapper"
827+ >
828+ { itemNode }
829+ </ div >
830+ ) : (
831+ itemNode
832+ ) }
833+ </ FeedCardContext . Provider >
834+ ) ;
835+ } ) }
764836 { ! isFetching && ! isInitialLoading && ! isHorizontal && (
765837 < InfiniteScrollScreenOffset ref = { infiniteScrollRef } />
766838 ) }
0 commit comments