Files
dt-design-system/packages/react-native/src/components/DTProgressBar.tsx
michael 47e8085581 Make RN components fully theme-driven for multi-brand support
Refactor all 18 React Native components from static DTColors imports to
dynamic useDTTheme() context, enabling proper visual differentiation
when switching between DT and Classic brands.

Key changes:
- Replace static DTColors with theme context in all components
- Add shape tokens (bevel/radius) to DTExtendedTheme interface
- Conditionally render SVG bevels (DT) vs borderRadius (Classic)
- Configure fonts per brand (Tektur for DT, system sans-serif for Classic)
- Update buildThemeFromBrand to parse shape and typography tokens
- Fix Metro config for monorepo singleton resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:18:39 -08:00

187 lines
4.0 KiB
TypeScript

/**
* DT ProgressBar Component
*
* A themed progress bar wrapping React Native Paper's ProgressBar
* with the Dangerous Things angular aesthetic.
*
* Web CSS reference (.progress / .progress-indicator):
* - Vertical or horizontal bar showing progress 0-100%
* - Mode-colored fill
*/
import {StyleSheet, View, ViewStyle, StyleProp} from 'react-native';
import {ProgressBar, Text} from 'react-native-paper';
import {useDTTheme} from '../theme/DTThemeProvider';
import {type DTVariant, getVariantColor} from '../utils/variantColors';
import {useComponentLayout} from '../utils/useComponentLayout';
interface DTProgressBarProps {
/**
* Progress value between 0 and 1
*/
value: number;
/**
* Visual variant
* @default 'normal'
*/
variant?: DTVariant;
/**
* Orientation of the bar
* @default 'horizontal'
*/
orientation?: 'horizontal' | 'vertical';
/**
* Bar thickness in pixels
* @default 4
*/
height?: number;
/**
* Whether to animate progress changes
* @default true
*/
animated?: boolean;
/**
* Show percentage label
* @default false
*/
showLabel?: boolean;
/**
* Custom color (overrides variant)
*/
color?: string;
/**
* Additional styles
*/
style?: StyleProp<ViewStyle>;
}
/**
* DT-styled ProgressBar wrapping React Native Paper ProgressBar
*
* @example
* <DTProgressBar value={0.75} variant="normal" />
*
* @example
* <DTProgressBar value={0.5} variant="emphasis" showLabel />
*
* @example
* <DTProgressBar value={0.3} orientation="vertical" height={100} variant="success" />
*/
export function DTProgressBar({
value,
variant = 'normal',
orientation = 'horizontal',
height = 4,
animated: _animated = true,
showLabel = false,
color,
style,
}: DTProgressBarProps) {
const theme = useDTTheme();
const accentColor = getVariantColor(theme, variant, color);
const clampedValue = Math.max(0, Math.min(1, value));
if (orientation === 'vertical') {
return (
<VerticalProgressBar
value={clampedValue}
color={accentColor}
trackColor={theme.colors.surfaceDisabled}
width={height}
showLabel={showLabel}
style={style}
/>
);
}
return (
<View style={[styles.horizontalContainer, style]}>
<ProgressBar
progress={clampedValue}
color={accentColor}
style={[styles.bar, {height}]}
theme={{
colors: {
surfaceVariant: theme.colors.surfaceDisabled,
},
}}
/>
{showLabel && (
<Text style={[styles.label, {color: accentColor}]}>
{Math.round(clampedValue * 100)}%
</Text>
)}
</View>
);
}
/**
* Vertical progress bar using measured pixel height instead of
* percentage strings (which don't animate smoothly in RN).
*/
function VerticalProgressBar({
value,
color,
trackColor,
width,
showLabel,
style,
}: {
value: number;
color: string;
trackColor: string;
width: number;
showLabel: boolean;
style?: StyleProp<ViewStyle>;
}) {
const {dimensions, onLayout, hasDimensions} = useComponentLayout();
const fillHeight = hasDimensions ? dimensions.height * value : 0;
return (
<View style={[styles.verticalContainer, {width}, style]}>
<View style={[styles.verticalTrack, {backgroundColor: trackColor}]} onLayout={onLayout}>
<View
style={[
styles.verticalFill,
{
backgroundColor: color,
height: fillHeight,
},
]}
/>
</View>
{showLabel && (
<Text style={[styles.label, {color}]}>
{Math.round(value * 100)}%
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
horizontalContainer: {
gap: 8,
},
bar: {
borderRadius: 0,
},
verticalContainer: {
alignItems: 'center',
gap: 8,
},
verticalTrack: {
flex: 1,
width: '100%',
justifyContent: 'flex-end',
},
verticalFill: {
width: '100%',
},
label: {
fontSize: 11,
fontWeight: '600',
letterSpacing: 0.5,
},
});