@umituz/react-native-design-system 1.0.0

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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/package.json +43 -0
  4. package/src/index.ts +345 -0
  5. package/src/presentation/atoms/AtomicAvatar.tsx +157 -0
  6. package/src/presentation/atoms/AtomicAvatarGroup.tsx +169 -0
  7. package/src/presentation/atoms/AtomicBadge.tsx +232 -0
  8. package/src/presentation/atoms/AtomicButton.tsx +124 -0
  9. package/src/presentation/atoms/AtomicCard.tsx +112 -0
  10. package/src/presentation/atoms/AtomicChip.tsx +223 -0
  11. package/src/presentation/atoms/AtomicDatePicker.tsx +347 -0
  12. package/src/presentation/atoms/AtomicDivider.tsx +114 -0
  13. package/src/presentation/atoms/AtomicFab.tsx +104 -0
  14. package/src/presentation/atoms/AtomicFilter.tsx +154 -0
  15. package/src/presentation/atoms/AtomicFormError.tsx +105 -0
  16. package/src/presentation/atoms/AtomicIcon.tsx +29 -0
  17. package/src/presentation/atoms/AtomicImage.tsx +149 -0
  18. package/src/presentation/atoms/AtomicInput.tsx +232 -0
  19. package/src/presentation/atoms/AtomicNumberInput.tsx +182 -0
  20. package/src/presentation/atoms/AtomicPicker.tsx +458 -0
  21. package/src/presentation/atoms/AtomicProgress.tsx +143 -0
  22. package/src/presentation/atoms/AtomicSearchBar.tsx +114 -0
  23. package/src/presentation/atoms/AtomicSkeleton.tsx +146 -0
  24. package/src/presentation/atoms/AtomicSort.tsx +145 -0
  25. package/src/presentation/atoms/AtomicSwitch.tsx +166 -0
  26. package/src/presentation/atoms/AtomicText.tsx +50 -0
  27. package/src/presentation/atoms/AtomicTextArea.tsx +198 -0
  28. package/src/presentation/atoms/AtomicTouchable.tsx +233 -0
  29. package/src/presentation/atoms/fab/styles/fabStyles.ts +69 -0
  30. package/src/presentation/atoms/fab/types/index.ts +88 -0
  31. package/src/presentation/atoms/filter/styles/filterStyles.ts +32 -0
  32. package/src/presentation/atoms/filter/types/index.ts +89 -0
  33. package/src/presentation/atoms/index.ts +378 -0
  34. package/src/presentation/atoms/input/hooks/useInputState.ts +15 -0
  35. package/src/presentation/atoms/input/styles/inputStyles.ts +66 -0
  36. package/src/presentation/atoms/input/types/index.ts +25 -0
  37. package/src/presentation/atoms/picker/styles/pickerStyles.ts +200 -0
  38. package/src/presentation/atoms/picker/types/index.ts +40 -0
  39. package/src/presentation/atoms/touchable/styles/touchableStyles.ts +71 -0
  40. package/src/presentation/atoms/touchable/types/index.ts +162 -0
  41. package/src/presentation/hooks/useAppDesignTokens.ts +78 -0
  42. package/src/presentation/hooks/useResponsive.ts +180 -0
  43. package/src/presentation/loading/index.ts +40 -0
  44. package/src/presentation/loading/presentation/components/LoadingSpinner.tsx +116 -0
  45. package/src/presentation/loading/presentation/components/LoadingState.tsx +200 -0
  46. package/src/presentation/loading/presentation/hooks/useLoading.ts +100 -0
  47. package/src/presentation/molecules/AtomicConfirmationModal.tsx +263 -0
  48. package/src/presentation/molecules/EmptyState.tsx +130 -0
  49. package/src/presentation/molecules/FormField.tsx +128 -0
  50. package/src/presentation/molecules/GridContainer.tsx +124 -0
  51. package/src/presentation/molecules/IconContainer.tsx +94 -0
  52. package/src/presentation/molecules/LanguageSwitcher.tsx +42 -0
  53. package/src/presentation/molecules/ListItem.tsx +36 -0
  54. package/src/presentation/molecules/ScreenHeader.tsx +140 -0
  55. package/src/presentation/molecules/SearchBar.tsx +85 -0
  56. package/src/presentation/molecules/SectionCard.tsx +74 -0
  57. package/src/presentation/molecules/SectionContainer.tsx +106 -0
  58. package/src/presentation/molecules/SectionHeader.tsx +125 -0
  59. package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
  60. package/src/presentation/molecules/confirmation-modal/types/index.ts +107 -0
  61. package/src/presentation/molecules/index.ts +42 -0
  62. package/src/presentation/molecules/languageswitcher/config/languageSwitcherConfig.ts +5 -0
  63. package/src/presentation/molecules/languageswitcher/hooks/useLanguageNavigation.ts +15 -0
  64. package/src/presentation/molecules/listitem/styles/listItemStyles.ts +19 -0
  65. package/src/presentation/molecules/listitem/types/index.ts +17 -0
  66. package/src/presentation/organisms/AppHeader.tsx +136 -0
  67. package/src/presentation/organisms/FormContainer.tsx +180 -0
  68. package/src/presentation/organisms/ScreenLayout.tsx +209 -0
  69. package/src/presentation/organisms/index.ts +25 -0
  70. package/src/presentation/tokens/AppDesignTokens.ts +57 -0
  71. package/src/presentation/tokens/commonStyles.ts +253 -0
  72. package/src/presentation/tokens/core/BaseTokens.ts +394 -0
  73. package/src/presentation/tokens/core/ColorPalette.ts +398 -0
  74. package/src/presentation/tokens/core/TokenFactory.ts +120 -0
  75. package/src/presentation/utils/platformConstants.ts +124 -0
  76. package/src/presentation/utils/responsive.ts +516 -0
  77. package/src/presentation/utils/variants/compound.ts +29 -0
  78. package/src/presentation/utils/variants/core.ts +39 -0
  79. package/src/presentation/utils/variants/helpers.ts +13 -0
  80. package/src/presentation/utils/variants.ts +3 -0
@@ -0,0 +1,232 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Pressable, StyleProp, ViewStyle, TextStyle } from 'react-native';
3
+ import { TextInput, HelperText } from 'react-native-paper';
4
+ import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
5
+ import { AtomicIcon } from './AtomicIcon';
6
+ import type { AtomicIconName, AtomicIconSize } from './AtomicIcon';
7
+
8
+ export type AtomicInputVariant = 'outlined' | 'filled' | 'flat';
9
+ export type AtomicInputState = 'default' | 'error' | 'success' | 'disabled';
10
+ export type AtomicInputSize = 'sm' | 'md' | 'lg';
11
+
12
+ export interface AtomicInputProps {
13
+ /** Input label */
14
+ label?: string;
15
+ /** Current input value */
16
+ value?: string;
17
+ /** Value change callback */
18
+ onChangeText?: (text: string) => void;
19
+ /** Input variant (outlined, filled, flat) */
20
+ variant?: AtomicInputVariant;
21
+ /** Input state (default, error, success, disabled) */
22
+ state?: AtomicInputState;
23
+ /** Input size (sm, md, lg) */
24
+ size?: AtomicInputSize;
25
+ /** Placeholder text */
26
+ placeholder?: string;
27
+ /** Helper text below input */
28
+ helperText?: string;
29
+ /** Leading icon (Lucide icon name) */
30
+ leadingIcon?: AtomicIconName;
31
+ /** Trailing icon (Lucide icon name) */
32
+ trailingIcon?: AtomicIconName;
33
+ /** Callback when trailing icon is pressed */
34
+ onTrailingIconPress?: () => void;
35
+ /** Show password toggle for secure inputs */
36
+ showPasswordToggle?: boolean;
37
+ /** Secure text entry (password field) */
38
+ secureTextEntry?: boolean;
39
+ /** Maximum character length */
40
+ maxLength?: number;
41
+ /** Show character counter */
42
+ showCharacterCount?: boolean;
43
+ /** Keyboard type */
44
+ keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'url' | 'number-pad' | 'decimal-pad';
45
+ /** Auto-capitalize */
46
+ autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters';
47
+ /** Auto-correct */
48
+ autoCorrect?: boolean;
49
+ /** Disabled state */
50
+ disabled?: boolean;
51
+ /** Container style */
52
+ style?: StyleProp<ViewStyle>;
53
+ /** Input text style */
54
+ inputStyle?: StyleProp<TextStyle>;
55
+ /** Test ID for E2E testing */
56
+ testID?: string;
57
+ /** Blur callback */
58
+ onBlur?: () => void;
59
+ /** Focus callback */
60
+ onFocus?: () => void;
61
+ }
62
+
63
+ /**
64
+ * AtomicInput - Material Design 3 Text Input
65
+ *
66
+ * Features:
67
+ * - React Native Paper TextInput integration
68
+ * - Lucide icons for password toggle and custom icons
69
+ * - Material Design 3 outlined/filled/flat variants
70
+ * - Error, success, disabled states
71
+ * - Character counter
72
+ * - Responsive sizing
73
+ * - Full accessibility support
74
+ */
75
+ export const AtomicInput: React.FC<AtomicInputProps> = ({
76
+ variant = 'outlined',
77
+ state = 'default',
78
+ size = 'md',
79
+ label,
80
+ value = '',
81
+ onChangeText,
82
+ placeholder,
83
+ helperText,
84
+ leadingIcon,
85
+ trailingIcon,
86
+ onTrailingIconPress,
87
+ showPasswordToggle = false,
88
+ secureTextEntry = false,
89
+ maxLength,
90
+ showCharacterCount = false,
91
+ keyboardType = 'default',
92
+ autoCapitalize = 'sentences',
93
+ autoCorrect = true,
94
+ disabled = false,
95
+ style,
96
+ inputStyle,
97
+ testID,
98
+ onBlur,
99
+ onFocus,
100
+ }) => {
101
+ const tokens = useAppDesignTokens();
102
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false);
103
+ const isDisabled = state === 'disabled' || disabled;
104
+ const characterCount = value?.toString().length || 0;
105
+
106
+ // Map variant to Paper mode
107
+ const getPaperMode = (): 'outlined' | 'flat' => {
108
+ if (variant === 'outlined') return 'outlined';
109
+ return 'flat'; // filled and flat both use 'flat' mode
110
+ };
111
+
112
+ // Map state to Paper error prop
113
+ const hasError = state === 'error';
114
+
115
+ // Get icon size based on input size
116
+ const getIconSize = (): AtomicIconSize => {
117
+ switch (size) {
118
+ case 'sm': return 'xs';
119
+ case 'lg': return 'md';
120
+ default: return 'sm';
121
+ }
122
+ };
123
+
124
+ const iconSizeName = getIconSize();
125
+ const iconColor = isDisabled
126
+ ? tokens.colors.onSurfaceDisabled
127
+ : tokens.colors.surfaceVariant;
128
+
129
+ // Render leading icon
130
+ const renderLeadingIcon = leadingIcon ? () => (
131
+ <AtomicIcon
132
+ name={leadingIcon}
133
+ size={iconSizeName}
134
+ customColor={iconColor}
135
+ />
136
+ ) : undefined;
137
+
138
+ // Render trailing icon or password toggle
139
+ const renderTrailingIcon = () => {
140
+ if (showPasswordToggle && secureTextEntry) {
141
+ return (
142
+ <Pressable onPress={() => setIsPasswordVisible(!isPasswordVisible)}>
143
+ <AtomicIcon
144
+ name={isPasswordVisible ? "EyeOff" : "Eye"}
145
+ size={iconSizeName}
146
+ customColor={iconColor}
147
+ />
148
+ </Pressable>
149
+ );
150
+ }
151
+
152
+ if (trailingIcon) {
153
+ const icon = (
154
+ <AtomicIcon
155
+ name={trailingIcon}
156
+ size={iconSizeName}
157
+ customColor={iconColor}
158
+ />
159
+ );
160
+
161
+ return onTrailingIconPress ? (
162
+ <Pressable onPress={onTrailingIconPress}>{icon}</Pressable>
163
+ ) : icon;
164
+ }
165
+
166
+ return undefined;
167
+ };
168
+
169
+ // Get text color based on state
170
+ const getTextColor = () => {
171
+ if (state === 'error') return tokens.colors.error;
172
+ if (state === 'success') return tokens.colors.success;
173
+ return tokens.colors.onSurface;
174
+ };
175
+
176
+ return (
177
+ <View style={style} testID={testID}>
178
+ <TextInput
179
+ mode={getPaperMode()}
180
+ label={label}
181
+ value={value}
182
+ onChangeText={onChangeText}
183
+ placeholder={placeholder}
184
+ error={hasError}
185
+ disabled={isDisabled}
186
+ secureTextEntry={secureTextEntry && !isPasswordVisible}
187
+ maxLength={maxLength}
188
+ keyboardType={keyboardType}
189
+ autoCapitalize={autoCapitalize}
190
+ autoCorrect={autoCorrect}
191
+ left={renderLeadingIcon ? <TextInput.Icon icon={renderLeadingIcon} /> : undefined}
192
+ right={renderTrailingIcon() ? <TextInput.Icon icon={renderTrailingIcon} /> : undefined}
193
+ style={inputStyle}
194
+ textColor={getTextColor()}
195
+ onBlur={onBlur}
196
+ onFocus={onFocus}
197
+ testID={testID ? `${testID}-input` : undefined}
198
+ />
199
+
200
+ {(helperText || showCharacterCount) && (
201
+ <View style={{
202
+ flexDirection: 'row',
203
+ justifyContent: 'space-between',
204
+ marginTop: tokens.spacing.xs,
205
+ }}>
206
+ {helperText && (
207
+ <HelperText
208
+ type={hasError ? 'error' : 'info'}
209
+ visible={Boolean(helperText)}
210
+ style={{ flex: 1 }}
211
+ testID={testID ? `${testID}-helper` : undefined}
212
+ >
213
+ {helperText}
214
+ </HelperText>
215
+ )}
216
+ {showCharacterCount && maxLength && (
217
+ <HelperText
218
+ type="info"
219
+ visible={true}
220
+ style={{ marginLeft: tokens.spacing.xs }}
221
+ testID={testID ? `${testID}-count` : undefined}
222
+ >
223
+ {characterCount}/{maxLength}
224
+ </HelperText>
225
+ )}
226
+ </View>
227
+ )}
228
+ </View>
229
+ );
230
+ };
231
+
232
+ export type { AtomicInputProps as InputProps };
@@ -0,0 +1,182 @@
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
+
40
+ import React, { useState, useEffect } from 'react';
41
+ import { AtomicInput, AtomicInputProps } from './AtomicInput';
42
+
43
+ /**
44
+ * Props for AtomicNumberInput component
45
+ * Extends AtomicInput but removes text-specific props
46
+ */
47
+ export interface AtomicNumberInputProps
48
+ extends Omit<
49
+ AtomicInputProps,
50
+ 'keyboardType' | 'secureTextEntry' | 'showPasswordToggle' | 'onChangeText'
51
+ > {
52
+ /** Minimum allowed value */
53
+ min?: number;
54
+ /** Maximum allowed value */
55
+ max?: number;
56
+ /** Step increment (for spinners, future feature) */
57
+ step?: number;
58
+ /** Allow decimal numbers (default: false for integers only) */
59
+ allowDecimal?: boolean;
60
+ /** Callback when valid number is entered (null if invalid/empty) */
61
+ onValueChange?: (value: number | null) => void;
62
+ /** Callback when raw text changes (optional) */
63
+ onTextChange?: (text: string) => void;
64
+ }
65
+
66
+ /**
67
+ * AtomicNumberInput - Specialized numeric input component
68
+ *
69
+ * Wraps AtomicInput with:
70
+ * - Numeric keyboard
71
+ * - Number validation (min, max, format)
72
+ * - Parsed number callbacks
73
+ * - Automatic error states
74
+ */
75
+ export const AtomicNumberInput: React.FC<AtomicNumberInputProps> = ({
76
+ min,
77
+ max,
78
+ step = 1,
79
+ allowDecimal = false,
80
+ onValueChange,
81
+ onTextChange,
82
+ value = '',
83
+ state: externalState,
84
+ helperText: externalHelperText,
85
+ ...props
86
+ }) => {
87
+ const [internalError, setInternalError] = useState<string | undefined>(undefined);
88
+
89
+ /**
90
+ * Validate number and return error message if invalid
91
+ */
92
+ const validateNumber = (text: string): string | undefined => {
93
+ // Empty is valid (null value)
94
+ if (!text || text === '' || text === '-' || text === '.') {
95
+ return undefined;
96
+ }
97
+
98
+ // Parse number
99
+ const num = parseFloat(text);
100
+
101
+ // Check if valid number
102
+ if (isNaN(num)) {
103
+ return 'Invalid number';
104
+ }
105
+
106
+ // Check min constraint
107
+ if (min !== undefined && num < min) {
108
+ return `Minimum value is ${min}`;
109
+ }
110
+
111
+ // Check max constraint
112
+ if (max !== undefined && num > max) {
113
+ return `Maximum value is ${max}`;
114
+ }
115
+
116
+ return undefined;
117
+ };
118
+
119
+ /**
120
+ * Handle text change with validation
121
+ */
122
+ const handleChangeText = (text: string) => {
123
+ // Allow empty, minus sign, and decimal point during typing
124
+ if (text === '' || text === '-' || (allowDecimal && text === '.')) {
125
+ setInternalError(undefined);
126
+ onTextChange?.(text);
127
+ onValueChange?.(null);
128
+ return;
129
+ }
130
+
131
+ // Validate format
132
+ const decimalRegex = allowDecimal ? /^-?\d*\.?\d*$/ : /^-?\d*$/;
133
+ if (!decimalRegex.test(text)) {
134
+ // Invalid format, don't update
135
+ return;
136
+ }
137
+
138
+ // Validate number
139
+ const error = validateNumber(text);
140
+ setInternalError(error);
141
+
142
+ // Call text callback
143
+ onTextChange?.(text);
144
+
145
+ // Call value callback with parsed number
146
+ if (!error && text !== '' && text !== '-' && text !== '.') {
147
+ const num = parseFloat(text);
148
+ onValueChange?.(isNaN(num) ? null : num);
149
+ } else {
150
+ onValueChange?.(null);
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Validate on mount and when value/constraints change
156
+ */
157
+ useEffect(() => {
158
+ if (value) {
159
+ const error = validateNumber(value.toString());
160
+ setInternalError(error);
161
+ } else {
162
+ setInternalError(undefined);
163
+ }
164
+ }, [value, min, max]);
165
+
166
+ // Determine final state (external state overrides internal error)
167
+ const finalState = externalState || (internalError ? 'error' : 'default');
168
+
169
+ // Determine final helper text (internal error overrides external helper)
170
+ const finalHelperText = internalError || externalHelperText;
171
+
172
+ return (
173
+ <AtomicInput
174
+ {...props}
175
+ value={value}
176
+ onChangeText={handleChangeText}
177
+ keyboardType={allowDecimal ? 'decimal-pad' : 'numeric'}
178
+ state={finalState}
179
+ helperText={finalHelperText}
180
+ />
181
+ );
182
+ };