import React from 'react'; import { Platform, ScrollView, StyleSheet, Text, View } from 'react-native'; import type { SduiBlock } from '@proulr/sdui-contract'; import { AppButton } from '../ui/AppButton'; import { AppIcon, isAppIconName } from '../ui/app-icon'; import { colors, m3Colors, m3Type, spacing } from '../theme/proulr-theme'; import { resolveGapProp, resolvePaddingProp, resolveTextStyleProps, resolveViewStyleProps, } from '../theme/resolve-style-props'; import { SduiLayoutDebugFrame } from '../sdui-layout-debug'; import { useSduiChromeScroll } from '../sdui-chrome-scroll-context'; export type SduiBlockRenderProps = { block: SduiBlock; data: Record; onAction: (actionId: string, vars?: Record) => void; children?: React.ReactNode; }; const textVariants = ['headline', 'title', 'body', 'label'] as const; export function SduiTextBlock({ block }: SduiBlockRenderProps) { const text = typeof block.props.text === 'string' ? block.props.text : ''; const variant = typeof block.props.variant === 'string' && (textVariants as readonly string[]).includes(block.props.variant) ? block.props.variant : 'body'; return ( , variant), ]} > {text} ); } export function SduiColumnBlock({ block, children }: SduiBlockRenderProps) { const gap = resolveGapProp(block.props.gap, spacing.sm); const padding = resolvePaddingProp(block.props.padding, 0); const fillHeight = gap === 0 && padding === 0; const childList = React.Children.toArray(children); const singleFillChild = fillHeight && childList.length === 1; return ( ), ]} > {singleFillChild ? ( {childList[0]} ) : ( children )} ); } export function SduiRowBlock({ block, children }: SduiBlockRenderProps) { const gap = resolveGapProp(block.props.gap, spacing.sm); return ( ), ]} > {children} ); } export function SduiScrollBlock({ block, children }: SduiBlockRenderProps) { const chromeScroll = useSduiChromeScroll(); const padding = resolvePaddingProp(block.props.padding, spacing.sm); const gap = resolveGapProp(block.props.gap, spacing.md); const blockContentStyle = chromeScroll ? { paddingHorizontal: padding, gap } : { padding, gap }; const scrollViewProps = chromeScroll?.scrollViewProps ?? {}; const { ref: scrollRef, ...restScrollViewProps } = scrollViewProps as { ref?: React.Ref; } & Record; return ( ), Platform.OS === 'web' ? ({ touchAction: 'pan-y', WebkitOverflowScrolling: 'touch' } as object) : null, ]} contentContainerStyle={ chromeScroll ? [chromeScroll.contentContainerStyle, blockContentStyle] : blockContentStyle } {...(chromeScroll?.scrollInsetProps ?? {})} {...restScrollViewProps} onScroll={chromeScroll?.onScroll} scrollEventThrottle={chromeScroll?.onScroll || chromeScroll?.scrollViewProps ? 16 : undefined} refreshControl={chromeScroll?.refreshControl} showsVerticalScrollIndicator={false} > {children} ); } export function SduiButtonBlock({ block, onAction }: SduiBlockRenderProps) { const label = typeof block.props.label === 'string' ? block.props.label : 'Ação'; const actionId = typeof block.props.action_id === 'string' ? block.props.action_id : ''; const variant = typeof block.props.variant === 'string' ? (block.props.variant as 'primary' | 'secondary' | 'ghost' | 'outline' | 'danger') : 'primary'; return ( )}> actionId && onAction(actionId)} /> ); } /** * Bloco genérico de ícone vetorial (AppIcon). Wire: `name` = AppIconName * kebab-case, `size` em px, `color` = ColorToken do tema. O container aceita * StyleWire (background, borda, padding) para composições tipo hero circle. */ export function SduiIconBlock({ block }: SduiBlockRenderProps) { const name = typeof block.props.name === 'string' ? block.props.name : ''; if (!isAppIconName(name)) return null; const size = typeof block.props.size === 'number' ? block.props.size : spacing.lg; const colorToken = typeof block.props.color === 'string' ? block.props.color : undefined; const color = (colorToken ? m3Colors[colorToken as keyof typeof m3Colors] : undefined) ?? colors.onSurface; return ( )}> ); } export function SduiSpacerBlock({ block }: SduiBlockRenderProps) { const height = typeof block.props.height === 'number' ? block.props.height : spacing.md; return ; } export function SduiInfoBannerBlock({ block }: SduiBlockRenderProps) { const message = typeof block.props.message === 'string' ? block.props.message : ''; return ( )]}> {message} ); } export function SduiUnknownBlock({ block }: SduiBlockRenderProps) { if (!__DEV__) return null; return Bloco desconhecido: {block.type}; } const styles = StyleSheet.create({ scroll: { flex: 1, minHeight: 0 }, banner: { padding: spacing.md, backgroundColor: colors.surface, borderRadius: spacing.sm, borderWidth: 1, borderColor: colors.border, }, bannerText: { ...m3Type.body, color: colors.onSurfaceVariant }, unknown: { ...m3Type.label, color: colors.onSurfaceVariant, padding: spacing.md }, });