From 9216d66bbc74791abe46b8999007d2f2f8320012 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:49:08 +0000 Subject: [PATCH 1/2] fix(sticky-headers): respect contentContainerStyle horizontal padding When a sticky header becomes fixed at the top, it now applies the same horizontal padding (paddingLeft, paddingRight, paddingHorizontal, padding, paddingStart, paddingEnd) from contentContainerStyle. Previously the sticky header used left: 0 and right: 0, causing it to stretch to full width and visually jump when it became sticky. Fixes #2218 --- .../react-native/src/StickyHeaderExample.tsx | 13 +- src/__tests__/StickyHeaders.test.tsx | 129 ++++++++++++++++++ src/recyclerview/RecyclerView.tsx | 5 + src/recyclerview/components/StickyHeaders.tsx | 30 +++- 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/fixture/react-native/src/StickyHeaderExample.tsx b/fixture/react-native/src/StickyHeaderExample.tsx index c0b7ac684..569613397 100644 --- a/fixture/react-native/src/StickyHeaderExample.tsx +++ b/fixture/react-native/src/StickyHeaderExample.tsx @@ -60,6 +60,7 @@ export const StickyHeaderExample = forwardRef( const [withStickyHeaderOffset, setWithStickyHeaderOffset] = useState(false); const [withStickyHeaderBackground, setWithStickyHeaderBackground] = useState(false); + const [withHorizontalPadding, setWithHorizontalPadding] = useState(false); // Memoize the renderItem function const renderItem = useCallback( @@ -86,7 +87,7 @@ export const StickyHeaderExample = forwardRef( return ( + diff --git a/src/__tests__/StickyHeaders.test.tsx b/src/__tests__/StickyHeaders.test.tsx index 0e8206d28..4204b5f75 100644 --- a/src/__tests__/StickyHeaders.test.tsx +++ b/src/__tests__/StickyHeaders.test.tsx @@ -641,4 +641,133 @@ describe("StickyHeaders - Compute Function", () => { }); }); }); + + describe("Content Container Style Padding", () => { + it("should apply paddingHorizontal from contentContainerStyle", () => { + const layouts = createStandardLayouts(); + + const manager = createMockRecyclerViewManager({ + scrollOffset: 100, + layouts, + }); + + const result = render( + + ); + + const animatedView = result.find(Animated.View); + expect(animatedView).toHaveReactProps({ + style: expect.objectContaining({ + paddingLeft: 16, + paddingRight: 16, + }), + }); + }); + + it("should apply individual paddingLeft and paddingRight from contentContainerStyle", () => { + const layouts = createStandardLayouts(); + + const manager = createMockRecyclerViewManager({ + scrollOffset: 100, + layouts, + }); + + const result = render( + + ); + + const animatedView = result.find(Animated.View); + expect(animatedView).toHaveReactProps({ + style: expect.objectContaining({ + paddingLeft: 10, + paddingRight: 20, + }), + }); + }); + + it("should apply padding shorthand from contentContainerStyle", () => { + const layouts = createStandardLayouts(); + + const manager = createMockRecyclerViewManager({ + scrollOffset: 100, + layouts, + }); + + const result = render( + + ); + + const animatedView = result.find(Animated.View); + expect(animatedView).toHaveReactProps({ + style: expect.objectContaining({ + paddingLeft: 24, + paddingRight: 24, + }), + }); + }); + + it("should not apply horizontal padding when contentContainerStyle is undefined", () => { + const layouts = createStandardLayouts(); + + const manager = createMockRecyclerViewManager({ + scrollOffset: 100, + layouts, + }); + + const result = render( + + ); + + const animatedView = result.find(Animated.View); + expect(animatedView).toHaveReactProps({ + style: expect.objectContaining({ + paddingLeft: undefined, + paddingRight: undefined, + }), + }); + }); + }); }); diff --git a/src/recyclerview/RecyclerView.tsx b/src/recyclerview/RecyclerView.tsx index 89b56bec9..f47d7ae64 100644 --- a/src/recyclerview/RecyclerView.tsx +++ b/src/recyclerview/RecyclerView.tsx @@ -441,6 +441,9 @@ const RecyclerViewComponent = ( recyclerViewManager={recyclerViewManager} extraData={extraData} inverted={inverted} + contentContainerStyle={ + overrideProps?.contentContainerStyle ?? rest.contentContainerStyle + } onChangeStickyIndex={(newStickyHeaderIndex) => { if (stickyHeaderHideRelatedCell) { setCurrentStickyIndex(newStickyHeaderIndex); @@ -464,6 +467,8 @@ const RecyclerViewComponent = ( onChangeStickyIndex, stickyHeaderHideRelatedCell, inverted, + rest.contentContainerStyle, + overrideProps?.contentContainerStyle, ]); // Set up scroll event handling with animation support for sticky headers diff --git a/src/recyclerview/components/StickyHeaders.tsx b/src/recyclerview/components/StickyHeaders.tsx index 286b4da84..52c76d342 100644 --- a/src/recyclerview/components/StickyHeaders.tsx +++ b/src/recyclerview/components/StickyHeaders.tsx @@ -12,7 +12,13 @@ import React, { useCallback, useEffect, } from "react"; -import { Animated, NativeScrollEvent } from "react-native"; +import { + Animated, + NativeScrollEvent, + StyleProp, + StyleSheet, + ViewStyle, +} from "react-native"; import { FlashListProps } from "../.."; import { PlatformConfig } from "../../native/config/PlatformHelper"; @@ -46,6 +52,8 @@ export interface StickyHeaderProps { extraData: FlashListProps["extraData"]; /** Whether the list is inverted */ inverted: FlashListProps["inverted"]; + /** Content container style from which horizontal padding is extracted */ + contentContainerStyle?: StyleProp; } /** @@ -72,6 +80,7 @@ export const StickyHeaders = ({ extraData, onChangeStickyIndex, inverted, + contentContainerStyle, }: StickyHeaderProps) => { const [stickyHeaderState, setStickyHeaderState] = useState( { @@ -192,6 +201,23 @@ export const StickyHeaders = ({ stickyHeaderOffset, ]); + // Extract horizontal padding from contentContainerStyle + const horizontalPadding = useMemo(() => { + const flatStyle = StyleSheet.flatten(contentContainerStyle) ?? {}; + return { + paddingLeft: + flatStyle.paddingLeft ?? + flatStyle.paddingHorizontal ?? + flatStyle.padding, + paddingRight: + flatStyle.paddingRight ?? + flatStyle.paddingHorizontal ?? + flatStyle.padding, + paddingStart: flatStyle.paddingStart, + paddingEnd: flatStyle.paddingEnd, + }; + }, [contentContainerStyle]); + // Memoize header content const headerContent = useMemo(() => { return ( @@ -204,6 +230,7 @@ export const StickyHeaders = ({ zIndex: 2, transform: [{ translateY }], opacity, + ...horizontalPadding, }} > {currentStickyIndex !== -1 && currentStickyIndex < data.length ? ( @@ -232,6 +259,7 @@ export const StickyHeaders = ({ extraData, stickyHeaderOffset, inverted, + horizontalPadding, ]); if (PlatformConfig.isRN083OrAbove && currentStickyIndex === -1) { From e38c6a1b2c472c478caec3ccd63b00f98b9b0d53 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:11:45 +0000 Subject: [PATCH 2/2] fix(sticky-headers): fix prettier formatting in StickyHeaderExample Co-authored-by: Talha Naqvi --- fixture/react-native/src/StickyHeaderExample.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fixture/react-native/src/StickyHeaderExample.tsx b/fixture/react-native/src/StickyHeaderExample.tsx index 569613397..b6228451f 100644 --- a/fixture/react-native/src/StickyHeaderExample.tsx +++ b/fixture/react-native/src/StickyHeaderExample.tsx @@ -122,9 +122,7 @@ export const StickyHeaderExample = forwardRef( } stickyHeaderConfig={stickyHeaderConfig} contentContainerStyle={ - withHorizontalPadding - ? { paddingHorizontal: 16 } - : undefined + withHorizontalPadding ? { paddingHorizontal: 16 } : undefined } ItemSeparatorComponent={ItemSeparator} />