import React from 'react'; import { View, Text, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native'; import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg'; import { useUx4gTheme } from '../../theme/Ux4gThemeContext'; import { UX4GColors } from '../../foundation/colors'; export type Ux4gProgressShape = 'sharp' | 'rounded'; export type Ux4gProgressLabelPosition = 'outside' | 'inside'; export interface Ux4gLinearProgressBarProps { /** * Progress value between **0.0** (empty) and **1.0** (full). */ value: number; /** * Optional leading icon shown to the left of the label row. */ icon?: React.ReactNode; /** * Optional icon color. Defaults to Primary color. */ iconColor?: string; /** * Optional icon background color (circle behind icon). Pass `transparent` to hide bg. */ iconBackgroundColor?: string; /** * Optional main label text shown above-left of the bar. */ label?: string; /** * Optional hint text shown above-right of the bar. */ hint?: string; /** * Height of the progress track in logical pixels. Default: **8**. */ height?: number; /** * Controls corner roundness. * - 'sharp' → `borderRadius = 0` * - 'rounded' → `borderRadius = height / 2` (pill) */ shape?: Ux4gProgressShape; /** * Custom border radius that overrides [shape] when provided. */ customBorderRadius?: number; /** * Single solid fill color. */ color?: string; /** * Two or more colors for a left-to-right gradient fill. * Takes priority over [color]. */ gradientColors?: string[]; /** * Background track color. Defaults to a semi-transparent `onSurface`. */ trackColor?: string; /** * Whether to render the percentage value. */ showPercentage?: boolean; /** * Where the percentage text appears when [showPercentage] is `true`. */ labelPosition?: Ux4gProgressLabelPosition; /** * Text style for the percentage rendered inside the bar. */ insideLabelStyle?: StyleProp; /** * Container style for overriding wrapper styling. */ containerStyle?: StyleProp; /** * Test ID for testing. */ testID?: string; } /** * A customizable linear progress indicator following the Ux4G Design System. */ export const Ux4gLinearProgressBar: React.FC = ({ value, icon, iconColor, iconBackgroundColor, label, hint, height = 8, shape = 'rounded', customBorderRadius, color, gradientColors, trackColor, showPercentage = false, labelPosition = 'outside', insideLabelStyle, containerStyle, testID, }) => { const theme = useUx4gTheme(); const colors = theme.colors; const typography = theme.typography; const isDark = theme.isDark; const gradientId = React.useId(); const clampedValue = Math.min(Math.max(value, 0.0), 1.0); const percent = Math.round(clampedValue * 100).toString(); const resolvedColor = color ?? colors.primary ?? UX4GColors.primary600; const resolvedGradient = gradientColors ?? (color == null ? (isDark ? [UX4GColors.primary950, UX4GColors.primary300] : [UX4GColors.primary200, UX4GColors.primary600]) : undefined); // Track color defaults to onSurface with 0.12 alpha (like Flutter) const defaultTrackColor = isDark ? `rgba(255, 255, 255, 0.12)` : `rgba(0, 0, 0, 0.12)`; const resolvedTrackColor = trackColor ?? defaultTrackColor; const resolvedRadius = customBorderRadius !== undefined ? customBorderRadius : shape === 'rounded' ? height / 2 : 0; const hasHeader = icon != null || label != null || hint != null || (showPercentage && labelPosition === 'outside'); const defaultIconBgColor = iconBackgroundColor ?? (colors.primary ? `${colors.primary}1F` : `rgba(0, 0, 0, 0.12)`); // 1F is ~12% opacity const onSurfaceColor = isDark ? UX4GColors.white : UX4GColors.neutral900; const onSurfaceColorFaded = isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)'; return ( {/* Header row */} {hasHeader && ( {icon && ( {icon} )} {label && ( {label} )} {hint && ( {hint} )} {showPercentage && labelPosition === 'outside' && ( {`${percent}%`} )} )} {/* Progress Track */} {resolvedGradient && resolvedGradient.length >= 2 && ( {resolvedGradient.map((c, i) => ( ))} )} {showPercentage && labelPosition === 'inside' && clampedValue > 0.1 && ( 14 ? 11 : Math.max(8, Math.min(11, height * 0.7)), }, insideLabelStyle, ]} numberOfLines={1} > {`${percent}%`} )} ); }; const styles = StyleSheet.create({ container: { width: '100%', flexDirection: 'column', }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, headerLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, marginRight: 8, }, headerRight: { flexDirection: 'row', alignItems: 'center', }, iconBubble: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', marginRight: 8, }, label: { flex: 1, }, hint: { // handled via inline styles }, percentageOutside: { // handled via inline styles }, track: { width: '100%', overflow: 'hidden', }, fill: { height: '100%', justifyContent: 'center', alignItems: 'flex-end', overflow: 'hidden', }, percentageInside: { color: UX4GColors.white, fontWeight: 'bold', paddingRight: 12, }, });