import React, { useMemo, useState, useCallback } from 'react'; import { View, StyleSheet, Dimensions, LayoutChangeEvent, ScrollView, ViewStyle } from 'react-native'; import { applyGridStyles, focusedStyleOverrides, parseSize } from '../../utils/styles'; import { TemplateRenderer } from '../TemplateRenderer'; import type { TComponent } from '@namiml/sdk-core'; import { usePaywallContext } from '../../context/PaywallContext'; import { FocusScope } from '../../context/FocusContext'; import { getRepeatingListBlocks } from '../../utils/rendering'; interface Props { component: any; scaleFactor: number; onClose?: () => void; parentDirection?: string; } export const NamiResponsiveGrid: React.FC = ({ component, scaleFactor, onClose, parentDirection }) => { const ctx = usePaywallContext(); const [isFocused, setIsFocused] = useState(false); const containerStyle = useMemo(() => { const base = applyGridStyles(component, scaleFactor, false, parentDirection); if (isFocused) { Object.assign(base, focusedStyleOverrides(component, scaleFactor)); } return base; }, [component, scaleFactor, parentDirection, isFocused]); const isVertical = component.direction === 'vertical'; const direction = component.direction ?? 'vertical'; const columns = component.columns ?? (isVertical ? 1 : 2); const gap = parseSize(component.spacing ?? component.gap, scaleFactor) ?? 8; const { width: screenWidth } = Dimensions.get('window'); const [containerWidth, setContainerWidth] = useState(null); const onLayout = useCallback((e: LayoutChangeEvent) => { setContainerWidth(e.nativeEvent.layout.width); }, [component, containerStyle, columns, gap]); const leftPad = parseSize(component.leftPadding, scaleFactor) ?? 0; const rightPad = parseSize(component.rightPadding, scaleFactor) ?? 0; const usableWidth = (containerWidth ?? screenWidth) - leftPad - rightPad; const itemWidth = (usableWidth - gap * (columns - 1)) / columns; const repeatingBlocks = useMemo(() => getRepeatingListBlocks(ctx, component), [ctx, component]); const isGrouped = !!(component.groupBy && component.groupHeaderTemplate); const isHorizontalGrouped = !isVertical && isGrouped && repeatingBlocks.length > 0; // Grouped horizontal: walk blocks and build per-group sections. // Only used when isHorizontalGrouped; computed unconditionally to satisfy hook rules. const groupSections = useMemo>(() => { if (!isHorizontalGrouped) return []; const sections: Array<{ header: TComponent; items: TComponent[] }> = []; let currentItems: TComponent[] = []; repeatingBlocks.forEach((subArray: TComponent[]) => { const isHeader = subArray.length === 1 && (subArray[0] as any)?.__namiGroupHeader === true; if (isHeader) { currentItems = []; sections.push({ header: subArray[0], items: currentItems }); } else { subArray.forEach((child) => currentItems.push(child)); } }); return sections; }, [isHorizontalGrouped, repeatingBlocks]); // Flat items list — used only by non-grouped paths. const items: TComponent[] = useMemo( () => (repeatingBlocks.length ? repeatingBlocks.flat() : (component.components ?? [])), [component.components, repeatingBlocks], ); if (isHorizontalGrouped) { return ( {groupSections.map((group, groupIdx) => ( {group.items.map((child: TComponent, itemIdx: number) => ( ))} ))} ); } return ( {items.map((child: TComponent, i: number) => { const col = i % columns; const isLastInRow = col === columns - 1; const style = { width: isVertical ? '100%' : itemWidth, marginRight: isVertical ? 0 : (isLastInRow ? 0 : gap), marginBottom: gap, }; return ( ); })} ); }; const styles = StyleSheet.create({ grid: { alignItems: 'center', justifyContent: 'center' }, horizontal: { flexDirection: 'row', flexWrap: 'wrap', alignSelf: 'baseline' }, vertical: { flexDirection: 'column', flexWrap: 'nowrap', alignSelf: 'baseline' }, groupSection: { width: '100%' }, groupHeader: { width: '100%' }, horizontalScrollContent: { flexDirection: 'row', flexWrap: 'nowrap' }, });