import React, { useRef, useEffect } from 'react'; import { View, Text, TouchableOpacity, Animated, Easing, StyleProp, ViewStyle, StyleSheet, Platform, UIManager, LayoutAnimation, } from 'react-native'; import { useUx4gTheme } from '../../theme/Ux4gThemeContext'; import { Ux4gIcons } from '../../foundation/icons'; if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } // ─── Types ─────────────────────────────────────────────────────────── export type Ux4gCompactStepperLayout = | 'linear' | 'rightAligned' | 'centered' | 'centeredBetween' | 'split'; export interface Ux4gCompactStepperProps { totalSteps: number; currentStep: number; stepLabel: string; description?: string; onNext?: () => void; onPrevious?: () => void; layout?: Ux4gCompactStepperLayout; labelAlignment?: 'flex-start' | 'center' | 'flex-end'; activeColor?: string; inactiveColor?: string; style?: StyleProp; } // ─── Main Component ────────────────────────────────────────────────── export const Ux4gCompactStepper: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext = () => { }, onPrevious = () => { }, layout = 'linear', labelAlignment = 'flex-start', activeColor, inactiveColor, style, }) => { const theme = useUx4gTheme(); useEffect(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); }, [currentStep]); const primary = theme.colors.primary; const onSurface = theme.colors.onSurface; const resolvedActiveColor = activeColor || primary; const resolvedInactiveColor = inactiveColor || `${onSurface}33`; // 0.2 opacity const commonProps = { totalSteps, currentStep, stepLabel, description, onNext, onPrevious, activeColor: resolvedActiveColor, inactiveColor: resolvedInactiveColor, }; let content: React.ReactElement; switch (layout) { case 'linear': content = ; break; case 'rightAligned': content = ; break; case 'centered': content = ; break; case 'centeredBetween': content = ; break; case 'split': content = ; break; default: content = ; break; } return ( {content} ); }; // ─── Capsule Indicator ─────────────────────────────────────────────── interface CapsuleIndicatorProps { totalSteps: number; currentStep: number; activeColor: string; inactiveColor: string; expand?: boolean; } const CapsuleIndicator: React.FC = ({ totalSteps, currentStep, activeColor, inactiveColor, expand, }) => { return ( {Array.from({ length: totalSteps }).map((_, i) => ( 8 ? 4 : 6} /> ))} ); }; // ─── Capsule Item (Native Hardware Accelerated) ───────────────────── interface CapsuleItemProps { isActive: boolean; activeColor: string; inactiveColor: string; marginRight: number; } const CapsuleItem: React.FC = ({ isActive, activeColor, inactiveColor, marginRight, }) => { const anim = useRef(new Animated.Value(isActive ? 1 : 0)).current; useEffect(() => { Animated.timing(anim, { toValue: isActive ? 1 : 0, duration: 300, easing: Easing.bezier(0.25, 0.1, 0.25, 1.0), useNativeDriver: true, }).start(); }, [isActive]); const scaleX = anim.interpolate({ inputRange: [0, 1], outputRange: [1, 2.2], }); const activeOpacity = anim; const inactiveOpacity = anim.interpolate({ inputRange: [0, 1], outputRange: [1, 0], }); return ( ); }; // ─── Stepper Icon Button ───────────────────────────────────────────── // Matches _StepperIconButton from Flutter: 40x40 circle with border interface StepperIconButtonProps { direction: 'left' | 'right'; enabled: boolean; onPress: () => void; } const StepperIconButton: React.FC = ({ direction, enabled, onPress, }) => { const theme = useUx4gTheme(); const primary = theme.colors.primary; const onSurface = theme.colors.onSurface; const surface = theme.colors.surface; const contentColor = enabled ? primary : `${onSurface}4D`; // 0.3 opacity const borderColor = enabled ? `${primary}1F` : `${onSurface}33`; // 0.12 / 0.2 opacity const IconComponent = direction === 'left' ? Ux4gIcons.chevronLeft : Ux4gIcons.chevronRight; return ( ); }; // ─── Layout: Linear ────────────────────────────────────────────────── // Flutter: _LinearCapsuleStepper interface LayoutProps { totalSteps: number; currentStep: number; stepLabel: string; description?: string; onNext: () => void; onPrevious: () => void; activeColor: string; inactiveColor: string; } interface LinearLayoutProps extends LayoutProps { labelAlignment: 'flex-start' | 'center' | 'flex-end'; } const LinearLayout: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext, onPrevious, labelAlignment, activeColor, inactiveColor, }) => { const theme = useUx4gTheme(); const centered = labelAlignment === 'center'; const textAlign = centered ? 'center' as const : 'left' as const; return ( {/* Row: LeftArrow | Capsules | RightArrow */} 1} onPress={onPrevious} /> {/* Labels */} Step {currentStep} of {totalSteps} {stepLabel} {description != null && ( {description} )} ); }; // ─── Layout: Right Aligned ─────────────────────────────────────────── // Flutter: _RightAlignedCapsuleStepper const RightAlignedLayout: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext, onPrevious, activeColor, inactiveColor, }) => { const theme = useUx4gTheme(); return ( {/* Row: LeftArrow | Capsules | RightArrow */} 1} onPress={onPrevious} /> {/* Row: StepCounter left | Label+Desc right */} Step {currentStep} of {totalSteps} {stepLabel} {description != null && ( {description} )} ); }; // ─── Layout: Centered ──────────────────────────────────────────────── // Flutter: _CenteredCapsuleStepper const CenteredLayout: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext, onPrevious, activeColor, inactiveColor, }) => { const theme = useUx4gTheme(); return ( {/* Full-width capsule indicator */} {/* Row: LeftArrow | "Step X of Y" | RightArrow */} 1} onPress={onPrevious} /> Step {currentStep} of {totalSteps} {/* Label + Description */} {stepLabel} {description != null && ( {description} )} ); }; // ─── Layout: Centered Between ──────────────────────────────────────── // Flutter: _CenteredBetweenCapsuleStepper const CenteredBetweenLayout: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext, onPrevious, activeColor, inactiveColor, }) => { const theme = useUx4gTheme(); return ( {/* Full-width capsule indicator */} {/* Step counter */} Step {currentStep} of {totalSteps} {/* Row: LeftArrow | Label+Desc | RightArrow */} 1} onPress={onPrevious} /> {stepLabel} {description != null && ( <> {description} )} ); }; // ─── Layout: Split ─────────────────────────────────────────────────── // Flutter: _SplitCapsuleStepper const SplitLayout: React.FC = ({ totalSteps, currentStep, stepLabel, description, onNext, onPrevious, activeColor, inactiveColor, }) => { const theme = useUx4gTheme(); return ( {/* Full-width capsule indicator */} {/* Row: Label+Desc left | StepCounter + Arrows right */} {/* Left side: label + description */} {stepLabel} {description != null && ( <> {description} )} {/* Right side: step counter + arrows */} Step {currentStep} of {totalSteps} 1} onPress={onPrevious} /> ); };