@umituz/react-native-design-system 1.5.32 → 1.5.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +4 -1
  2. package/src/presentation/atoms/AtomicDatePicker.tsx +3 -2
  3. package/src/presentation/atoms/AtomicInput.tsx +19 -12
  4. package/src/index.js +0 -100
  5. package/src/presentation/atoms/AtomicAvatar.js +0 -84
  6. package/src/presentation/atoms/AtomicAvatarGroup.js +0 -82
  7. package/src/presentation/atoms/AtomicBadge.js +0 -167
  8. package/src/presentation/atoms/AtomicButton.js +0 -171
  9. package/src/presentation/atoms/AtomicCard.js +0 -69
  10. package/src/presentation/atoms/AtomicChip.js +0 -130
  11. package/src/presentation/atoms/AtomicDatePicker.js +0 -245
  12. package/src/presentation/atoms/AtomicDivider.js +0 -57
  13. package/src/presentation/atoms/AtomicFab.js +0 -67
  14. package/src/presentation/atoms/AtomicFilter.js +0 -103
  15. package/src/presentation/atoms/AtomicFormError.js +0 -63
  16. package/src/presentation/atoms/AtomicIcon.js +0 -29
  17. package/src/presentation/atoms/AtomicImage.js +0 -91
  18. package/src/presentation/atoms/AtomicInput.js +0 -201
  19. package/src/presentation/atoms/AtomicNumberInput.js +0 -124
  20. package/src/presentation/atoms/AtomicPicker.js +0 -298
  21. package/src/presentation/atoms/AtomicProgress.js +0 -79
  22. package/src/presentation/atoms/AtomicSearchBar.js +0 -45
  23. package/src/presentation/atoms/AtomicSort.js +0 -76
  24. package/src/presentation/atoms/AtomicSwitch.js +0 -103
  25. package/src/presentation/atoms/AtomicText.js +0 -22
  26. package/src/presentation/atoms/AtomicTextArea.js +0 -195
  27. package/src/presentation/atoms/AtomicTouchable.js +0 -137
  28. package/src/presentation/atoms/fab/styles/fabStyles.js +0 -62
  29. package/src/presentation/atoms/fab/types/index.js +0 -1
  30. package/src/presentation/atoms/filter/styles/filterStyles.js +0 -28
  31. package/src/presentation/atoms/filter/types/index.js +0 -1
  32. package/src/presentation/atoms/index.js +0 -145
  33. package/src/presentation/atoms/input/hooks/useInputState.js +0 -12
  34. package/src/presentation/atoms/input/styles/inputStyles.js +0 -58
  35. package/src/presentation/atoms/input/types/index.js +0 -1
  36. package/src/presentation/atoms/picker/styles/pickerStyles.js +0 -176
  37. package/src/presentation/atoms/picker/types/index.js +0 -1
  38. package/src/presentation/atoms/touchable/styles/touchableStyles.js +0 -53
  39. package/src/presentation/atoms/touchable/types/index.js +0 -1
  40. package/src/presentation/hooks/useResponsive.js +0 -81
  41. package/src/presentation/molecules/AtomicConfirmationModal.js +0 -153
  42. package/src/presentation/molecules/EmptyState.js +0 -67
  43. package/src/presentation/molecules/FormField.js +0 -75
  44. package/src/presentation/molecules/GridContainer.js +0 -76
  45. package/src/presentation/molecules/IconContainer.js +0 -59
  46. package/src/presentation/molecules/ListItem.js +0 -23
  47. package/src/presentation/molecules/ScreenHeader.js +0 -93
  48. package/src/presentation/molecules/SearchBar.js +0 -46
  49. package/src/presentation/molecules/SectionCard.js +0 -46
  50. package/src/presentation/molecules/SectionContainer.js +0 -63
  51. package/src/presentation/molecules/SectionHeader.js +0 -72
  52. package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.js +0 -114
  53. package/src/presentation/molecules/confirmation-modal/types/index.js +0 -6
  54. package/src/presentation/molecules/index.js +0 -16
  55. package/src/presentation/molecules/listitem/styles/listItemStyles.js +0 -14
  56. package/src/presentation/molecules/listitem/types/index.js +0 -1
  57. package/src/presentation/organisms/AppHeader.js +0 -77
  58. package/src/presentation/organisms/FormContainer.js +0 -126
  59. package/src/presentation/organisms/ScreenLayout.js +0 -68
  60. package/src/presentation/organisms/index.js +0 -13
  61. package/src/presentation/tokens/commonStyles.js +0 -219
  62. package/src/presentation/utils/platformConstants.js +0 -113
  63. package/src/presentation/utils/responsive.js +0 -451
  64. package/src/presentation/utils/variants/compound.js +0 -15
  65. package/src/presentation/utils/variants/core.js +0 -22
  66. package/src/presentation/utils/variants/helpers.js +0 -9
  67. package/src/presentation/utils/variants.js +0 -3
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
- import { TouchableOpacity, StyleSheet } from 'react-native';
3
- import { useAppDesignTokens } from '@umituz/react-native-theme';
4
- import { useResponsive } from '../hooks/useResponsive';
5
- import { AtomicIcon } from './AtomicIcon';
6
- import { FAB_SIZES, getFabVariants, getFabIconSize, getFabBorder, } from './fab/styles/fabStyles';
7
- export { FAB_SIZES, getFabVariants, getFabIconSize, getFabBorder };
8
- /**
9
- * AtomicFab - Floating Action Button Component
10
- *
11
- * A Material Design 3 compliant FAB component for primary actions.
12
- * Follows CLAUDE.md standards for responsive positioning.
13
- *
14
- * @example
15
- * ```tsx
16
- * // IMPORTANT: FAB must be used at screen level, NOT inside ScrollView
17
- * <ScreenLayout>
18
- * <ScrollView>
19
- * {/* Your content *\/}
20
- * </ScrollView>
21
- * <AtomicFab
22
- * icon="add"
23
- * onPress={handleAddItem}
24
- * variant="primary"
25
- * size="md"
26
- * />
27
- * </ScreenLayout>
28
- * ```
29
- *
30
- * Features:
31
- * - Material Design 3 sizes (sm: 40px, md: 56px, lg: 72px)
32
- * - Three variants: primary, secondary, surface
33
- * - Responsive positioning (above tab bar, safe area aware)
34
- * - Disabled state with opacity
35
- * - Theme-aware colors from design tokens
36
- * - Border for depth (no shadows per CLAUDE.md)
37
- */
38
- export const AtomicFab = ({ icon, onPress, variant = 'primary', size = 'md', disabled = false, style, testID, accessibilityLabel, }) => {
39
- const tokens = useAppDesignTokens();
40
- const responsive = useResponsive();
41
- const isDisabled = disabled;
42
- // Get configurations
43
- const sizeConfig = FAB_SIZES[size];
44
- const variants = getFabVariants(tokens);
45
- const variantConfig = variants[variant];
46
- const iconSize = getFabIconSize(size);
47
- // Combine styles
48
- const fabStyle = StyleSheet.flatten([
49
- {
50
- position: 'absolute',
51
- bottom: responsive.fabPosition.bottom,
52
- right: responsive.fabPosition.right,
53
- width: sizeConfig.width,
54
- height: sizeConfig.height,
55
- borderRadius: sizeConfig.borderRadius,
56
- backgroundColor: variantConfig.backgroundColor,
57
- alignItems: 'center',
58
- justifyContent: 'center',
59
- },
60
- getFabBorder(tokens),
61
- isDisabled ? { opacity: tokens.opacity.disabled } : undefined,
62
- style, // Custom style override
63
- ]);
64
- return (<TouchableOpacity style={fabStyle} onPress={onPress} disabled={isDisabled} activeOpacity={0.7} testID={testID} accessibilityLabel={accessibilityLabel || `${icon} button`} accessibilityRole="button">
65
- <AtomicIcon name={icon} size={iconSize} customColor={variantConfig.iconColor}/>
66
- </TouchableOpacity>);
67
- };
@@ -1,103 +0,0 @@
1
- import React from 'react';
2
- import { ScrollView, View, TouchableOpacity } from 'react-native';
3
- import { useAppDesignTokens } from '@umituz/react-native-theme';
4
- import { AtomicChip } from './AtomicChip';
5
- import { AtomicText } from './AtomicText';
6
- import { AtomicIcon } from './AtomicIcon';
7
- import { getFilterContainerStyle, getClearAllContainerStyle, getScrollContentContainerStyle, } from './filter/styles/filterStyles';
8
- export { getFilterContainerStyle, getClearAllContainerStyle, getScrollContentContainerStyle, } from './filter/styles/filterStyles';
9
- /**
10
- * AtomicFilter - Horizontal Filter Chip Component
11
- *
12
- * A Material Design 3 compliant filter component using chip selection.
13
- * Supports single and multi-select modes with "Clear All" functionality.
14
- *
15
- * @example
16
- * ```tsx
17
- * const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
18
- *
19
- * <AtomicFilter
20
- * options={[
21
- * { id: 'active', label: 'Active', icon: 'check-circle' },
22
- * { id: 'completed', label: 'Completed', icon: 'check' },
23
- * { id: 'pending', label: 'Pending', icon: 'clock' },
24
- * ]}
25
- * selectedIds={selectedFilters}
26
- * onSelectionChange={setSelectedFilters}
27
- * multiSelect={true}
28
- * showClearAll={true}
29
- * />
30
- * ```
31
- *
32
- * Features:
33
- * - Horizontal scrollable filter chips
34
- * - Single/Multi-select modes
35
- * - Clear all button (when filters active)
36
- * - Theme-aware colors from design tokens
37
- * - Icon support per filter option
38
- * - Fully controlled component
39
- */
40
- export const AtomicFilter = ({ options, selectedIds, onSelectionChange, multiSelect = true, showClearAll = true, variant = 'outlined', color = 'primary', size = 'md', style, testID, }) => {
41
- const tokens = useAppDesignTokens();
42
- /**
43
- * Handle filter chip press
44
- */
45
- const handleFilterPress = (optionId) => {
46
- if (multiSelect) {
47
- // Multi-select mode: Toggle selection
48
- if (selectedIds.includes(optionId)) {
49
- // Deselect
50
- onSelectionChange(selectedIds.filter(id => id !== optionId));
51
- }
52
- else {
53
- // Select
54
- onSelectionChange([...selectedIds, optionId]);
55
- }
56
- }
57
- else {
58
- // Single-select mode: Replace selection
59
- if (selectedIds.includes(optionId)) {
60
- // Deselect (clear selection)
61
- onSelectionChange([]);
62
- }
63
- else {
64
- // Select (only this one)
65
- onSelectionChange([optionId]);
66
- }
67
- }
68
- };
69
- /**
70
- * Handle clear all button press
71
- */
72
- const handleClearAll = () => {
73
- onSelectionChange([]);
74
- };
75
- const hasActiveFilters = selectedIds.length > 0;
76
- return (<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={getScrollContentContainerStyle()} style={[style]} testID={testID}>
77
- <View style={getFilterContainerStyle()}>
78
- {options.map((option) => {
79
- const isSelected = selectedIds.includes(option.id);
80
- return (<AtomicChip key={option.id} variant={isSelected ? 'filled' : variant} color={color} size={size} leadingIcon={option.icon} selected={isSelected} clickable={true} onPress={() => handleFilterPress(option.id)} testID={`filter-chip-${option.id}`}>
81
- {option.label}
82
- </AtomicChip>);
83
- })}
84
-
85
- {/* Clear All Button */}
86
- {showClearAll && hasActiveFilters && (<TouchableOpacity onPress={handleClearAll} style={[
87
- getClearAllContainerStyle(),
88
- {
89
- backgroundColor: tokens.colors.surfaceVariant,
90
- borderWidth: 1,
91
- borderColor: tokens.colors.outline,
92
- }
93
- ]} testID="clear-all-button">
94
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: tokens.spacing.xs }}>
95
- <AtomicIcon name="X" size="xs" color="surfaceVariant"/>
96
- <AtomicText type="labelSmall" style={{ color: tokens.colors.textSecondary }}>
97
- Clear All
98
- </AtomicText>
99
- </View>
100
- </TouchableOpacity>)}
101
- </View>
102
- </ScrollView>);
103
- };
@@ -1,63 +0,0 @@
1
- /**
2
- * AtomicFormError - Universal Form Error Component
3
- *
4
- * Provides consistent error message display for forms
5
- * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
- *
7
- * Atomic Design Level: ATOM
8
- * Purpose: Display validation error messages
9
- *
10
- * Usage:
11
- * - Form field validation errors
12
- * - Global form error messages
13
- * - API error display
14
- * - Input validation feedback
15
- */
16
- import React from 'react';
17
- import { View, StyleSheet } from 'react-native';
18
- import { AtomicText } from './AtomicText';
19
- import { useAppDesignTokens } from '@umituz/react-native-theme';
20
- import { withAlpha } from '@umituz/react-native-theme';
21
- // =============================================================================
22
- // COMPONENT IMPLEMENTATION
23
- // =============================================================================
24
- export const AtomicFormError = ({ message, variant = 'field', style, textStyle, }) => {
25
- const tokens = useAppDesignTokens();
26
- if (!message) {
27
- return null;
28
- }
29
- if (variant === 'global') {
30
- return (<View style={[
31
- {
32
- padding: tokens.spacing.md,
33
- borderRadius: tokens.borders.radius.md,
34
- marginBottom: tokens.spacing.sm,
35
- backgroundColor: withAlpha(tokens.colors.error, 0.15),
36
- },
37
- style,
38
- ]}>
39
- <AtomicText type="bodySmall" color="error" style={StyleSheet.flatten([
40
- {
41
- textAlign: 'center',
42
- fontWeight: tokens.typography.medium,
43
- },
44
- textStyle,
45
- ])}>
46
- {message}
47
- </AtomicText>
48
- </View>);
49
- }
50
- return (<AtomicText type="bodySmall" color="error" style={StyleSheet.flatten([
51
- {
52
- marginTop: tokens.spacing.xs,
53
- marginLeft: tokens.spacing.xs,
54
- },
55
- textStyle,
56
- ])}>
57
- {message}
58
- </AtomicText>);
59
- };
60
- // =============================================================================
61
- // EXPORTS
62
- // =============================================================================
63
- export default AtomicFormError;
@@ -1,29 +0,0 @@
1
- /**
2
- * AtomicIcon - Atomic Design System Icon Component
3
- *
4
- * Wrapper for the universal Icon component from @domains/icons
5
- * Provides backward compatibility with AtomicIcon naming convention
6
- * while leveraging the full power of the icons domain architecture.
7
- */
8
- import React from 'react';
9
- import { Icon } from '@umituz/react-native-icon';
10
- /**
11
- * AtomicIcon Component
12
- *
13
- * @example
14
- * ```tsx
15
- * import { AtomicIcon } from '@umituz/react-native-design-system';
16
- *
17
- * // Basic usage
18
- * <AtomicIcon name="Settings" size="md" color="primary" />
19
- *
20
- * // Custom size and color
21
- * <AtomicIcon name="Heart" customSize={32} customColor="#FF0000" />
22
- *
23
- * // With background
24
- * <AtomicIcon name="Info" size="lg" withBackground backgroundColor="#667eea" />
25
- * ```
26
- */
27
- export const AtomicIcon = (props) => {
28
- return <Icon {...props}/>;
29
- };
@@ -1,91 +0,0 @@
1
- /**
2
- * AtomicImage - Universal Image Component
3
- *
4
- * Provides consistent image handling across the app with theme integration
5
- * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
- *
7
- * Atomic Design Level: ATOM
8
- * Purpose: Basic image display with consistent styling
9
- *
10
- * Usage:
11
- * - Profile pictures
12
- * - Product images
13
- * - Icons and illustrations
14
- * - Background images
15
- */
16
- import React from 'react';
17
- import { Image, StyleSheet } from 'react-native';
18
- import { useAppDesignTokens } from '@umituz/react-native-theme';
19
- // =============================================================================
20
- // SIZE CONFIGURATION
21
- // =============================================================================
22
- const SIZE_CONFIG = {
23
- xs: 24,
24
- sm: 32,
25
- md: 48,
26
- lg: 64,
27
- xl: 96,
28
- xxl: 128,
29
- };
30
- // =============================================================================
31
- // COMPONENT IMPLEMENTATION
32
- // =============================================================================
33
- export const AtomicImage = ({ source, size = 'md', shape = 'rounded', borderRadius, style, imageStyle, backgroundColor, borderColor, borderWidth = 0, ...props }) => {
34
- const tokens = useAppDesignTokens();
35
- const styles = getStyles(tokens);
36
- const imageSize = SIZE_CONFIG[size];
37
- const calculatedBorderRadius = borderRadius ?? getBorderRadius(shape, imageSize, tokens);
38
- const containerStyle = [
39
- styles.container,
40
- {
41
- width: imageSize,
42
- height: imageSize,
43
- borderRadius: calculatedBorderRadius,
44
- backgroundColor: backgroundColor || tokens.colors.surface,
45
- borderColor: borderColor || tokens.colors.border,
46
- borderWidth,
47
- },
48
- style,
49
- ];
50
- const finalImageStyle = [
51
- styles.image,
52
- {
53
- borderRadius: calculatedBorderRadius,
54
- },
55
- imageStyle,
56
- ];
57
- return (<Image source={source} style={finalImageStyle} {...props}/>);
58
- };
59
- // =============================================================================
60
- // HELPER FUNCTIONS
61
- // =============================================================================
62
- const getBorderRadius = (shape, size, tokens) => {
63
- switch (shape) {
64
- case 'circle':
65
- return size / 2;
66
- case 'square':
67
- return 0;
68
- case 'rounded':
69
- default:
70
- return tokens.borders.radius.md;
71
- }
72
- };
73
- // =============================================================================
74
- // STYLES
75
- // =============================================================================
76
- const getStyles = (tokens) => StyleSheet.create({
77
- container: {
78
- overflow: 'hidden',
79
- justifyContent: 'center',
80
- alignItems: 'center',
81
- },
82
- image: {
83
- width: '100%',
84
- height: '100%',
85
- resizeMode: 'cover',
86
- },
87
- });
88
- // =============================================================================
89
- // EXPORTS
90
- // =============================================================================
91
- export default AtomicImage;
@@ -1,201 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { View, TextInput, Pressable, StyleSheet, Platform } from 'react-native';
3
- import { useAppDesignTokens } from '@umituz/react-native-theme';
4
- import { AtomicIcon } from './AtomicIcon';
5
- import { AtomicText } from './AtomicText';
6
- /**
7
- * AtomicInput - Pure React Native Text Input
8
- *
9
- * Features:
10
- * - Pure React Native implementation (no Paper dependency)
11
- * - Lucide icons for password toggle and custom icons
12
- * - Outlined/filled/flat variants
13
- * - Error, success, disabled states
14
- * - Character counter
15
- * - Responsive sizing
16
- * - Full accessibility support
17
- */
18
- export const AtomicInput = ({ variant = 'outlined', state = 'default', size = 'md', label, value = '', onChangeText, placeholder, helperText, leadingIcon, trailingIcon, onTrailingIconPress, showPasswordToggle = false, secureTextEntry = false, maxLength, showCharacterCount = false, keyboardType = 'default', autoCapitalize = 'sentences', autoCorrect = true, disabled = false, style, inputStyle, testID, onBlur, onFocus, }) => {
19
- const tokens = useAppDesignTokens();
20
- const [isPasswordVisible, setIsPasswordVisible] = useState(false);
21
- const [isFocused, setIsFocused] = useState(false);
22
- const isDisabled = state === 'disabled' || disabled;
23
- const characterCount = value?.toString().length || 0;
24
- const hasError = state === 'error';
25
- const hasSuccess = state === 'success';
26
- // Size configuration
27
- const sizeConfig = {
28
- sm: {
29
- paddingVertical: tokens.spacing.xs,
30
- paddingHorizontal: tokens.spacing.sm,
31
- fontSize: tokens.typography.bodySmall.fontSize,
32
- iconSize: 16,
33
- minHeight: 40,
34
- },
35
- md: {
36
- paddingVertical: tokens.spacing.sm,
37
- paddingHorizontal: tokens.spacing.md,
38
- fontSize: tokens.typography.bodyMedium.fontSize,
39
- iconSize: 20,
40
- minHeight: 48,
41
- },
42
- lg: {
43
- paddingVertical: tokens.spacing.md,
44
- paddingHorizontal: tokens.spacing.lg,
45
- fontSize: tokens.typography.bodyLarge.fontSize,
46
- iconSize: 24,
47
- minHeight: 56,
48
- },
49
- };
50
- const config = sizeConfig[size];
51
- // Get variant styles
52
- const getVariantStyle = () => {
53
- const baseStyle = {
54
- backgroundColor: tokens.colors.surface,
55
- borderRadius: tokens.borders.radius.md,
56
- };
57
- let borderColor = tokens.colors.border;
58
- if (isFocused)
59
- borderColor = tokens.colors.primary;
60
- if (hasError)
61
- borderColor = tokens.colors.error;
62
- if (hasSuccess)
63
- borderColor = tokens.colors.success;
64
- if (isDisabled)
65
- borderColor = tokens.colors.borderDisabled;
66
- switch (variant) {
67
- case 'outlined':
68
- return {
69
- ...baseStyle,
70
- borderWidth: isFocused ? 2 : 1,
71
- borderColor,
72
- };
73
- case 'filled':
74
- return {
75
- ...baseStyle,
76
- backgroundColor: tokens.colors.surfaceSecondary,
77
- borderWidth: 0,
78
- borderBottomWidth: isFocused ? 2 : 1,
79
- borderBottomColor: borderColor,
80
- };
81
- case 'flat':
82
- return {
83
- ...baseStyle,
84
- backgroundColor: 'transparent',
85
- borderWidth: 0,
86
- borderBottomWidth: 1,
87
- borderBottomColor: borderColor,
88
- borderRadius: 0,
89
- };
90
- default:
91
- return baseStyle;
92
- }
93
- };
94
- // Get text color based on state
95
- const getTextColor = () => {
96
- if (isDisabled)
97
- return tokens.colors.textDisabled;
98
- if (hasError)
99
- return tokens.colors.error;
100
- if (hasSuccess)
101
- return tokens.colors.success;
102
- return tokens.colors.textPrimary;
103
- };
104
- const iconColor = isDisabled ? tokens.colors.textDisabled : tokens.colors.textSecondary;
105
- const containerStyle = [
106
- styles.container,
107
- getVariantStyle(),
108
- {
109
- paddingTop: config.paddingVertical,
110
- paddingBottom: config.paddingVertical,
111
- paddingHorizontal: config.paddingHorizontal,
112
- minHeight: config.minHeight,
113
- justifyContent: 'center',
114
- opacity: isDisabled ? 0.5 : 1,
115
- },
116
- style,
117
- ];
118
- const textInputStyle = [
119
- styles.input,
120
- {
121
- fontSize: config.fontSize || tokens.typography.bodyMedium.fontSize || 16,
122
- lineHeight: (config.fontSize || tokens.typography.bodyMedium.fontSize || 16) * 1.5, // Ensure text is fully visible
123
- color: getTextColor(),
124
- paddingVertical: 0, // Remove vertical padding to prevent clipping
125
- },
126
- leadingIcon ? { paddingLeft: config.iconSize + 8 } : undefined,
127
- (trailingIcon || showPasswordToggle) ? { paddingRight: config.iconSize + 8 } : undefined,
128
- inputStyle,
129
- ];
130
- return (<View testID={testID}>
131
- {label && (<AtomicText type="labelMedium" color={hasError ? 'error' : hasSuccess ? 'success' : 'secondary'} style={styles.label}>
132
- {label}
133
- </AtomicText>)}
134
-
135
- <View style={containerStyle}>
136
- {leadingIcon && (<View style={styles.leadingIcon}>
137
- <AtomicIcon name={leadingIcon} customSize={config.iconSize} customColor={iconColor}/>
138
- </View>)}
139
-
140
- <TextInput value={value} onChangeText={onChangeText} placeholder={placeholder} placeholderTextColor={tokens.colors.textSecondary} secureTextEntry={secureTextEntry && !isPasswordVisible} maxLength={maxLength} keyboardType={keyboardType} autoCapitalize={autoCapitalize} autoCorrect={autoCorrect} editable={!isDisabled} style={textInputStyle} textAlignVertical="center" {...(Platform.OS === 'android' && { includeFontPadding: false })} onBlur={() => {
141
- setIsFocused(false);
142
- onBlur?.();
143
- }} onFocus={() => {
144
- setIsFocused(true);
145
- onFocus?.();
146
- }} testID={testID ? `${testID}-input` : undefined}/>
147
-
148
- {(showPasswordToggle && secureTextEntry) && (<Pressable onPress={() => setIsPasswordVisible(!isPasswordVisible)} style={styles.trailingIcon}>
149
- <AtomicIcon name={isPasswordVisible ? "EyeOff" : "Eye"} customSize={config.iconSize} customColor={iconColor}/>
150
- </Pressable>)}
151
-
152
- {trailingIcon && !showPasswordToggle && (<Pressable onPress={onTrailingIconPress} style={styles.trailingIcon} disabled={!onTrailingIconPress}>
153
- <AtomicIcon name={trailingIcon} customSize={config.iconSize} customColor={iconColor}/>
154
- </Pressable>)}
155
- </View>
156
-
157
- {(helperText || showCharacterCount) && (<View style={styles.helperRow}>
158
- {helperText && (<AtomicText type="bodySmall" color={hasError ? 'error' : 'secondary'} style={styles.helperText} testID={testID ? `${testID}-helper` : undefined}>
159
- {helperText}
160
- </AtomicText>)}
161
- {showCharacterCount && maxLength && (<AtomicText type="bodySmall" color="secondary" style={styles.characterCount} testID={testID ? `${testID}-count` : undefined}>
162
- {characterCount}/{maxLength}
163
- </AtomicText>)}
164
- </View>)}
165
- </View>);
166
- };
167
- const styles = StyleSheet.create({
168
- container: {
169
- flexDirection: 'row',
170
- alignItems: 'center',
171
- },
172
- input: {
173
- flex: 1,
174
- margin: 0,
175
- padding: 0,
176
- },
177
- label: {
178
- marginBottom: 4,
179
- },
180
- leadingIcon: {
181
- position: 'absolute',
182
- left: 12,
183
- zIndex: 1,
184
- },
185
- trailingIcon: {
186
- position: 'absolute',
187
- right: 12,
188
- zIndex: 1,
189
- },
190
- helperRow: {
191
- flexDirection: 'row',
192
- justifyContent: 'space-between',
193
- marginTop: 4,
194
- },
195
- helperText: {
196
- flex: 1,
197
- },
198
- characterCount: {
199
- marginLeft: 8,
200
- },
201
- });
@@ -1,124 +0,0 @@
1
- /**
2
- * AtomicNumberInput Component
3
- *
4
- * A specialized number input component that wraps AtomicInput with
5
- * number-specific validation and keyboard handling.
6
- *
7
- * Features:
8
- * - Numeric keyboard (integer or decimal)
9
- * - Min/max validation
10
- * - Step increment support
11
- * - Automatic error states for invalid numbers
12
- * - Parsed number callback (onValueChange)
13
- * - Consistent styling with AtomicInput
14
- * - All AtomicInput features (variants, states, sizes)
15
- *
16
- * Usage:
17
- * ```tsx
18
- * const [age, setAge] = useState<number | null>(null);
19
- *
20
- * <AtomicNumberInput
21
- * value={age?.toString() || ''}
22
- * onValueChange={setAge}
23
- * label="Age"
24
- * min={0}
25
- * max={150}
26
- * helperText="Enter your age"
27
- * />
28
- * ```
29
- *
30
- * Why This Component:
31
- * - Separation of concerns (text vs number input)
32
- * - Built-in number validation
33
- * - Type-safe number callbacks
34
- * - Prevents non-numeric input via keyboard
35
- * - Consistent with AtomicInput styling
36
- *
37
- * @module AtomicNumberInput
38
- */
39
- import React, { useState, useEffect } from 'react';
40
- import { AtomicInput } from './AtomicInput';
41
- /**
42
- * AtomicNumberInput - Specialized numeric input component
43
- *
44
- * Wraps AtomicInput with:
45
- * - Numeric keyboard
46
- * - Number validation (min, max, format)
47
- * - Parsed number callbacks
48
- * - Automatic error states
49
- */
50
- export const AtomicNumberInput = ({ min, max, step = 1, allowDecimal = false, onValueChange, onTextChange, value = '', state: externalState, helperText: externalHelperText, ...props }) => {
51
- const [internalError, setInternalError] = useState(undefined);
52
- /**
53
- * Validate number and return error message if invalid
54
- */
55
- const validateNumber = (text) => {
56
- // Empty is valid (null value)
57
- if (!text || text === '' || text === '-' || text === '.') {
58
- return undefined;
59
- }
60
- // Parse number
61
- const num = parseFloat(text);
62
- // Check if valid number
63
- if (isNaN(num)) {
64
- return 'Invalid number';
65
- }
66
- // Check min constraint
67
- if (min !== undefined && num < min) {
68
- return `Minimum value is ${min}`;
69
- }
70
- // Check max constraint
71
- if (max !== undefined && num > max) {
72
- return `Maximum value is ${max}`;
73
- }
74
- return undefined;
75
- };
76
- /**
77
- * Handle text change with validation
78
- */
79
- const handleChangeText = (text) => {
80
- // Allow empty, minus sign, and decimal point during typing
81
- if (text === '' || text === '-' || (allowDecimal && text === '.')) {
82
- setInternalError(undefined);
83
- onTextChange?.(text);
84
- onValueChange?.(null);
85
- return;
86
- }
87
- // Validate format
88
- const decimalRegex = allowDecimal ? /^-?\d*\.?\d*$/ : /^-?\d*$/;
89
- if (!decimalRegex.test(text)) {
90
- // Invalid format, don't update
91
- return;
92
- }
93
- // Validate number
94
- const error = validateNumber(text);
95
- setInternalError(error);
96
- // Call text callback
97
- onTextChange?.(text);
98
- // Call value callback with parsed number
99
- if (!error && text !== '' && text !== '-' && text !== '.') {
100
- const num = parseFloat(text);
101
- onValueChange?.(isNaN(num) ? null : num);
102
- }
103
- else {
104
- onValueChange?.(null);
105
- }
106
- };
107
- /**
108
- * Validate on mount and when value/constraints change
109
- */
110
- useEffect(() => {
111
- if (value) {
112
- const error = validateNumber(value.toString());
113
- setInternalError(error);
114
- }
115
- else {
116
- setInternalError(undefined);
117
- }
118
- }, [value, min, max]);
119
- // Determine final state (external state overrides internal error)
120
- const finalState = externalState || (internalError ? 'error' : 'default');
121
- // Determine final helper text (internal error overrides external helper)
122
- const finalHelperText = internalError || externalHelperText;
123
- return (<AtomicInput {...props} value={value} onChangeText={handleChangeText} keyboardType={allowDecimal ? 'decimal-pad' : 'numeric'} state={finalState} helperText={finalHelperText}/>);
124
- };