import React from 'react'; import { View, Text, StyleSheet, StyleProp, TextStyle, ViewStyle, ColorValue, } from 'react-native'; import { useUx4gTheme } from '../../theme/Ux4gThemeContext'; import { Ux4gButton, Ux4gButtonSize, Ux4gIconProp } from '../button/Button'; import { Ux4gIcons } from '../../foundation/icons'; import { ErrorOutlineSvgIcon, InboxCopySvgIcon, RocketLaunchSvgIcon, SearchSvgIcon, } from '../../assets/icons/EmptyStateVariantIcons'; export type Ux4gEmptyStateVariant = | 'noResults' | 'noData' | 'comingSoon' | 'error' | 'custom'; export interface Ux4gEmptyStateProps { /** Semantic preset controlling the default icon. */ variant?: Ux4gEmptyStateVariant; /** Override icon shown at top. Falls back to variant icon when omitted. */ icon?: React.ReactNode; /** Icon size in px. Defaults to `48`. */ iconSize?: number; /** Icon color. Defaults to `theme.colors.primary`. */ iconColor?: string; /** Main heading text. */ title: string; /** Optional second line text. */ subtitle?: string; /** Optional third line text, concatenated with subtitle. */ description?: string; /** Optional title text style override. */ titleStyle?: StyleProp; /** Optional body text style override. */ bodyStyle?: StyleProp; /** Optional action button label. */ buttonText?: string; /** Optional action button press callback. */ onButtonPressed?: () => void; /** Optional button size. Defaults to `'small'`. */ buttonSize?: Ux4gButtonSize; /** Optional leading icon for action button. */ buttonLeadingIcon?: Ux4gIconProp; /** Outer padding. Defaults to 24 on all sides. */ padding?: number; /** Horizontal padding for text block width. Defaults to `24`. */ bodyHorizontalPadding?: number; /** Optional container style override. */ containerStyle?: StyleProp; /** Optional test id for e2e/unit tests. */ testID?: string; } const withAlpha = (color: string, alpha: number): ColorValue => { if (!color) return `rgba(0, 0, 0, ${alpha})`; if (color.startsWith('#')) { let hex = color.slice(1); if (hex.length === 3) { hex = hex .split('') .map((c) => c + c) .join(''); } if (hex.length === 6 || hex.length === 8) { const r = parseInt(hex.slice(0, 2), 16); const g = parseInt(hex.slice(2, 4), 16); const b = parseInt(hex.slice(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } } if (color.startsWith('rgb')) { return color.replace(/rgba?\(([^)]+)\)/, (_, p1) => { const parts = p1.split(',').map((s: string) => s.trim()); return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${alpha})`; }); } return color; }; const renderDefaultVariantIcon = ( variant: Ux4gEmptyStateVariant, size: number, color: string ): React.ReactNode => { switch (variant) { case 'noResults': return ; case 'noData': return ; case 'comingSoon': return ; case 'error': return ; case 'custom': default: // Keep fallback aligned with material-style icon behavior for custom usage. return Ux4gIcons.info({ size, color }); } }; export const Ux4gEmptyState: React.FC = ({ variant = 'custom', icon, iconSize = 48, iconColor, title, subtitle, description, titleStyle, bodyStyle, buttonText, onButtonPressed, buttonSize = 'small', buttonLeadingIcon, padding = 24, bodyHorizontalPadding = 24, containerStyle, testID, }) => { const theme = useUx4gTheme(); const resolvedIconColor = iconColor ?? theme.colors.primary; const titleTypo = theme.typography.hXXS_strong; const bodyTypo = theme.typography.bS_default; const bodyText = [subtitle, description].filter(Boolean).join(' '); const effectiveIcon = icon ?? renderDefaultVariantIcon(variant, iconSize, resolvedIconColor); return ( {effectiveIcon} {title} {bodyText.length > 0 && ( <> {bodyText} )} {buttonText ? ( <> ) : null} ); }; const styles = StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', }, centerText: { textAlign: 'center', }, });