import React from 'react'; import { View, Text, StyleProp, TextStyle, ViewStyle } from 'react-native'; import { useUx4gTheme } from '../../theme/Ux4gThemeContext'; import { Ux4gIcons } from '../../foundation/icons'; import { UX4GColors } from '../../foundation/colors'; import Svg, { Line } from 'react-native-svg'; // ─── Types ─────────────────────────────────────────────────────────── export type Ux4gStepperOrientation = 'horizontal' | 'vertical'; export type Ux4gStepperLineStyle = 'solid' | 'dashed'; export type Ux4gStepperLinePlacement = 'center' | 'bottom'; export interface Ux4gStepItem { title: string; description?: string; statusLabel?: string; isError?: boolean; titleStyle?: StyleProp; descriptionStyle?: StyleProp; statusStyle?: StyleProp; } export interface Ux4gStepperProps { totalSteps: number; currentStep: number; orientation?: Ux4gStepperOrientation; lineStyle?: Ux4gStepperLineStyle; linePlacement?: Ux4gStepperLinePlacement; steps?: Ux4gStepItem[]; stepSize?: number; showLabels?: boolean; edgeLabelAlignment?: boolean; activeStepBackground?: boolean; stepSpacing?: number; alignIconWithDescription?: boolean; style?: StyleProp; } // ─── Main Component ────────────────────────────────────────────────── export const Ux4gStepper: React.FC = ({ totalSteps, currentStep, orientation = 'horizontal', lineStyle = 'solid', linePlacement = 'center', steps = [], stepSize = 32, showLabels = true, edgeLabelAlignment = false, activeStepBackground = false, stepSpacing = 24, alignIconWithDescription = false, style, }) => { if (orientation === 'horizontal') { return ( ); } return ( ); }; // ─── Horizontal Stepper ────────────────────────────────────────────── interface HorizontalStepperProps { totalSteps: number; currentStep: number; lineStyle: Ux4gStepperLineStyle; linePlacement: Ux4gStepperLinePlacement; steps: Ux4gStepItem[]; stepSize: number; showLabels: boolean; edgeLabelAlignment: boolean; activeStepBackground: boolean; style?: StyleProp; } const HorizontalStepper: React.FC = ({ totalSteps, currentStep, lineStyle, linePlacement, steps, stepSize, showLabels, edgeLabelAlignment, activeStepBackground, style, }) => { const theme = useUx4gTheme(); // ── Bottom Line Placement ── if (linePlacement === 'bottom') { return ( {Array.from({ length: totalSteps }).map((_, i) => { const stepIndex = i + 1; const isCompleted = currentStep > stepIndex; const isActive = currentStep === stepIndex; const isPending = currentStep < stepIndex; const stepData = i < steps.length ? steps[i] : null; const showActiveBg = activeStepBackground && isActive; const bgColor = showActiveBg ? (theme.isDark ? UX4GColors.primary900 : UX4GColors.primary50) : 'transparent'; return ( {showLabels && ( <> )} ); })} ); } // ── Center Line Placement (default) ── // Uses Stack approach: lines behind, icons + labels on top return ( {/* Lines layer */} {Array.from({ length: totalSteps }).map((_, i) => ( {/* Left segment */} {edgeLabelAlignment && i === 0 ? ( ) : i === 0 ? ( ) : ( i} orientation="horizontal" lineStyle={lineStyle} /> )} {/* Gap for icon */} {!(edgeLabelAlignment && i === 0) && !(edgeLabelAlignment && i === totalSteps - 1) && ( )} {/* Right segment */} {edgeLabelAlignment && i === totalSteps - 1 ? ( ) : i === totalSteps - 1 ? ( ) : ( i + 1} orientation="horizontal" lineStyle={lineStyle} /> )} ))} {/* Icons + labels layer */} {Array.from({ length: totalSteps }).map((_, i) => { const stepIndex = i + 1; const isCompleted = currentStep > stepIndex; const isActive = currentStep === stepIndex; const isPending = currentStep < stepIndex; const stepData = i < steps.length ? steps[i] : null; let columnAlignment: 'center' | 'flex-start' | 'flex-end' = 'center'; let textAlign: 'center' | 'left' | 'right' = 'center'; if (edgeLabelAlignment) { if (i === 0) { columnAlignment = 'flex-start'; textAlign = 'left'; } if (i === totalSteps - 1) { columnAlignment = 'flex-end'; textAlign = 'right'; } } return ( {showLabels && ( <> )} ); })} ); }; // ─── Vertical Stepper ──────────────────────────────────────────────── interface VerticalStepperProps { totalSteps: number; currentStep: number; lineStyle: Ux4gStepperLineStyle; steps: Ux4gStepItem[]; stepSize: number; showLabels: boolean; stepSpacing: number; alignIconWithDescription: boolean; style?: StyleProp; } const VerticalStepper: React.FC = ({ totalSteps, currentStep, lineStyle, steps, stepSize, showLabels, stepSpacing, alignIconWithDescription, style, }) => { return ( {Array.from({ length: totalSteps }).map((_, i) => { const stepIndex = i + 1; const isCompleted = currentStep > stepIndex; const isActive = currentStep === stepIndex; const isPending = currentStep < stepIndex; const stepData = i < steps.length ? steps[i] : null; return ( {/* Icon column */} {/* Optional leading line when alignIconWithDescription */} {alignIconWithDescription && stepData && stepData.title ? ( {i > 0 && ( i} orientation="vertical" lineStyle={lineStyle} /> )} ) : null} {i < totalSteps - 1 && ( showLabels ? ( ) : ( ) )} {/* Label column */} {showLabels && ( <> )} ); })} ); }; // ─── Step Icon ─────────────────────────────────────────────────────── interface StepIconProps { stepIndex: number; isCompleted: boolean; isActive: boolean; isPending: boolean; isError: boolean; size: number; } const StepIcon: React.FC = ({ stepIndex, isCompleted, isActive, isPending, isError, size, }) => { const theme = useUx4gTheme(); const primary = theme.colors.primary; const onPrimary = theme.colors.onPrimary; const onSurface = theme.colors.onSurface; const error = theme.colors.error; const backgroundColor = isError ? 'transparent' : isCompleted ? primary : 'transparent'; const borderColor = isError ? error : (isCompleted || isActive) ? primary : `${onSurface}33`; // 0.2 opacity const borderWidth = (isActive || isPending || isError) ? 2 : 0; const iconSize = size * 0.625; let child: React.ReactElement; if (isError) { child = ; } else if (isCompleted) { child = ; } else if (isActive) { child = ( ); } else { child = ( {stepIndex} ); } return ( {child} ); }; // ─── Step Labels ───────────────────────────────────────────────────── interface StepLabelsProps { title: string; description?: string; statusLabel?: string; isCompleted: boolean; isActive: boolean; isPending: boolean; isError: boolean; textAlign: 'center' | 'left' | 'right'; titleStyle?: StyleProp; descriptionStyle?: StyleProp; statusStyle?: StyleProp; } const StepLabels: React.FC = ({ title, description, statusLabel, isCompleted, isActive, isPending, isError, textAlign, titleStyle, descriptionStyle, statusStyle, }) => { const theme = useUx4gTheme(); const primary = theme.colors.primary; const onSurface = theme.colors.onSurface; const error = theme.colors.error; const success = theme.colors.success; const titleColor = isError ? error : isPending ? `${onSurface}66` // 0.4 opacity : onSurface; const resolvedDescriptionColor = isError ? error : `${onSurface}66`; // 0.4 opacity const statusColor = isError ? error : isCompleted ? success : isActive ? primary : `${onSurface}66`; // 0.4 opacity const alignMap = { center: 'center' as const, left: 'flex-start' as const, right: 'flex-end' as const, }; const resolvedStatusText = statusLabel ?? (isError ? 'Error' : isCompleted ? 'Completed' : isActive ? 'In progress' : undefined); return ( {title} {description != null && ( {description} )} {resolvedStatusText != null && ( {resolvedStatusText} )} ); }; // ─── Stepper Line ──────────────────────────────────────────────────── interface StepperLineProps { isCompleted: boolean; orientation: Ux4gStepperOrientation; lineStyle: Ux4gStepperLineStyle; } export const StepperLine: React.FC = ({ isCompleted, orientation, lineStyle, }) => { const theme = useUx4gTheme(); const isHorizontal = orientation === 'horizontal'; const color = isCompleted ? theme.colors.primary : `${theme.colors.onSurface}33`; // 0.2 opacity if (lineStyle === 'dashed') { return ( ); } return ( ); };