import React, { useMemo } from 'react' import { TypeGuards } from '@codeleap/types' import { useCallback } from '@codeleap/hooks' import { SectionList, SectionListProps as RNSectionProps } from 'react-native' import { View, ViewProps } from '../View' import { RefreshControl } from '../RefreshControl' import { useKeyboardPaddingStyle } from '../../utils' import { AugmentedSectionRenderItemInfo, SectionComponentProps, SectionProps, SectionRenderComponentProps } from './types' import { AnyRecord, IJSX, StyledComponentProps } from '@codeleap/styles' import { MobileStyleRegistry } from '../../Registry' import { useStylesFor } from '../../hooks' import { EmptyPlaceholder } from '../EmptyPlaceholder' export * from './styles' export * from './types' const RenderSeparator = (props: { separatorStyles: ViewProps['style'] }) => { return } /** * Wraps React Native's SectionList. The `sections` data is augmented with a sequential `index` * field internally — callers must not add their own `index` to section objects or it will be * overwritten and position helpers (`isFirst`, `isLast`, `isOnly`) will break. * * There is no `keyExtractor` default — SectionList falls back to array index, which causes * re-mount flicker on reorder. Always pass a `keyExtractor` when items can change order or identity. */ export function Sections(sectionsProps: SectionProps) { const { style, onRefresh, refreshing, placeholder, refreshControlProps, loading, keyboardAware, fakeEmpty = loading, contentContainerStyle, refreshControl, renderItem: providedRenderItem, sections: data, renderSectionHeader: providedRenderSectionHeader, renderSectionFooter: providedRenderSectionFooter, ...props } = { ...Sections.defaultProps, ...sectionsProps, } /** Deep-equality via JSON.stringify to avoid re-renders on reference changes; will miss non-serialisable values (functions, Dates) in section data. */ const sections = useMemo(() => { return data?.map((section, index) => ({ ...section, index, })) }, [JSON.stringify(data)]) const styles = useStylesFor(Sections.styleRegistryName, style) const separator = useCallback(() => { if (!props?.separators) return null return }, []) const getSectionProps = (data: SectionRenderComponentProps) => { const listLength = sections?.length || 0 const isFirst = data?.section?.index === sections?.[0]?.index const isLast = data?.section?.index === sections?.[listLength - 1]?.index const isOnly = isFirst && isLast const title = data?.section?.title const index = data?.section?.index return { isFirst, isLast, isOnly, title, index } } const renderSectionHeader = useCallback((data: SectionRenderComponentProps) => { if (!providedRenderSectionHeader) return null const positionProps = getSectionProps(data) return providedRenderSectionHeader({ ...data.section, ...positionProps }) }, [providedRenderSectionHeader, sections?.length]) const renderSectionFooter = useCallback((data: SectionRenderComponentProps) => { if (!providedRenderSectionFooter) return null const positionProps = getSectionProps(data) return providedRenderSectionFooter({ ...data.section, ...positionProps }) }, [providedRenderSectionFooter, sections?.length]) const renderItem = useCallback((data: AugmentedSectionRenderItemInfo) => { if (!providedRenderItem) return null const listLength = data?.section?.data?.length || 0 const isFirst = data?.index === 0 const isLast = data?.index === listLength - 1 const isOnly = isFirst && isLast return providedRenderItem({ ...data, isFirst, isLast, isOnly, }) }, [providedRenderItem]) const isEmpty = !sections || !sections?.length const _placeholder = { ...placeholder, loading: TypeGuards.isBoolean(placeholder?.loading) ? placeholder.loading : loading, } const keyboardStyle = useKeyboardPaddingStyle([ styles.content, contentContainerStyle, isEmpty && styles['content:empty'], loading && styles['content:loading'], ], keyboardAware && !props.horizontal) const wrapperStyle = [styles.wrapper, isEmpty && styles['wrapper:empty'], loading && styles['wrapper:loading']] return ( ) : null} ListEmptyComponent={} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} {...props} ListHeaderComponentStyle={styles.header} ListFooterComponentStyle={styles.footer} style={wrapperStyle} contentContainerStyle={keyboardStyle} sections={sections} renderItem={renderItem} renderSectionHeader={renderSectionHeader as unknown as RNSectionProps['renderSectionHeader']} renderSectionFooter={renderSectionFooter as unknown as RNSectionProps['renderSectionHeader']} /> ) } Sections.styleRegistryName = 'Sections' Sections.elements = ['wrapper', 'content', 'separator', 'header', 'footer', 'refreshControl'] Sections.rootElement = 'wrapper' Sections.withVariantTypes = (styles: S) => { return Sections as (props: StyledComponentProps, typeof styles>) => IJSX } Sections.defaultProps = { keyboardShouldPersistTaps: 'handled', fakeEmpty: false, loading: false, keyboardAware: true, } as Partial MobileStyleRegistry.registerComponent(Sections)