@umituz/react-native-design-system 1.1.3 → 1.3.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.
@@ -1,8 +1,8 @@
1
1
  import React, { useState } from 'react';
2
- import { View, Pressable, StyleProp, ViewStyle, TextStyle } from 'react-native';
3
- import { TextInput, HelperText } from 'react-native-paper';
2
+ import { View, TextInput, Pressable, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
4
3
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
5
4
  import { AtomicIcon } from './AtomicIcon';
5
+ import { AtomicText } from './AtomicText';
6
6
  import type { AtomicIconName, AtomicIconSize } from './AtomicIcon';
7
7
 
8
8
  export type AtomicInputVariant = 'outlined' | 'filled' | 'flat';
@@ -61,12 +61,12 @@ export interface AtomicInputProps {
61
61
  }
62
62
 
63
63
  /**
64
- * AtomicInput - Material Design 3 Text Input
64
+ * AtomicInput - Pure React Native Text Input
65
65
  *
66
66
  * Features:
67
- * - React Native Paper TextInput integration
67
+ * - Pure React Native implementation (no Paper dependency)
68
68
  * - Lucide icons for password toggle and custom icons
69
- * - Material Design 3 outlined/filled/flat variants
69
+ * - Outlined/filled/flat variants
70
70
  * - Error, success, disabled states
71
71
  * - Character counter
72
72
  * - Responsive sizing
@@ -100,128 +100,212 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
100
100
  }) => {
101
101
  const tokens = useAppDesignTokens();
102
102
  const [isPasswordVisible, setIsPasswordVisible] = useState(false);
103
+ const [isFocused, setIsFocused] = useState(false);
103
104
  const isDisabled = state === 'disabled' || disabled;
104
105
  const characterCount = value?.toString().length || 0;
106
+ const hasError = state === 'error';
107
+ const hasSuccess = state === 'success';
105
108
 
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
109
+ // Size configuration
110
+ const sizeConfig = {
111
+ sm: {
112
+ paddingVertical: tokens.spacing.xs,
113
+ paddingHorizontal: tokens.spacing.sm,
114
+ fontSize: tokens.typography.bodySmall.fontSize,
115
+ iconSize: 16,
116
+ minHeight: 40,
117
+ },
118
+ md: {
119
+ paddingVertical: tokens.spacing.sm,
120
+ paddingHorizontal: tokens.spacing.md,
121
+ fontSize: tokens.typography.bodyMedium.fontSize,
122
+ iconSize: 20,
123
+ minHeight: 48,
124
+ },
125
+ lg: {
126
+ paddingVertical: tokens.spacing.md,
127
+ paddingHorizontal: tokens.spacing.lg,
128
+ fontSize: tokens.typography.bodyLarge.fontSize,
129
+ iconSize: 24,
130
+ minHeight: 56,
131
+ },
110
132
  };
111
133
 
112
- // Map state to Paper error prop
113
- const hasError = state === 'error';
134
+ const config = sizeConfig[size];
114
135
 
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
- };
136
+ // Get variant styles
137
+ const getVariantStyle = (): ViewStyle => {
138
+ const baseStyle: ViewStyle = {
139
+ backgroundColor: tokens.colors.surface,
140
+ borderRadius: tokens.borders.radius.md,
141
+ };
123
142
 
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
- }
143
+ let borderColor = tokens.colors.border;
144
+ if (isFocused) borderColor = tokens.colors.primary;
145
+ if (hasError) borderColor = tokens.colors.error;
146
+ if (hasSuccess) borderColor = tokens.colors.success;
147
+ if (isDisabled) borderColor = tokens.colors.borderDisabled;
151
148
 
152
- if (trailingIcon) {
153
- const icon = (
154
- <AtomicIcon
155
- name={trailingIcon}
156
- size={iconSizeName}
157
- customColor={iconColor}
158
- />
159
- );
149
+ switch (variant) {
150
+ case 'outlined':
151
+ return {
152
+ ...baseStyle,
153
+ borderWidth: isFocused ? 2 : 1,
154
+ borderColor,
155
+ };
160
156
 
161
- return onTrailingIconPress ? (
162
- <Pressable onPress={onTrailingIconPress}>{icon}</Pressable>
163
- ) : icon;
164
- }
157
+ case 'filled':
158
+ return {
159
+ ...baseStyle,
160
+ backgroundColor: tokens.colors.surfaceSecondary,
161
+ borderWidth: 0,
162
+ borderBottomWidth: isFocused ? 2 : 1,
163
+ borderBottomColor: borderColor,
164
+ };
165
+
166
+ case 'flat':
167
+ return {
168
+ ...baseStyle,
169
+ backgroundColor: 'transparent',
170
+ borderWidth: 0,
171
+ borderBottomWidth: 1,
172
+ borderBottomColor: borderColor,
173
+ borderRadius: 0,
174
+ };
165
175
 
166
- return undefined;
176
+ default:
177
+ return baseStyle;
178
+ }
167
179
  };
168
180
 
169
181
  // Get text color based on state
170
182
  const getTextColor = () => {
171
- if (state === 'error') return tokens.colors.error;
172
- if (state === 'success') return tokens.colors.success;
173
- return tokens.colors.onSurface;
183
+ if (isDisabled) return tokens.colors.textDisabled;
184
+ if (hasError) return tokens.colors.error;
185
+ if (hasSuccess) return tokens.colors.success;
186
+ return tokens.colors.textPrimary;
174
187
  };
175
188
 
189
+ const iconColor = isDisabled ? tokens.colors.textDisabled : tokens.colors.textSecondary;
190
+
191
+ const containerStyle: StyleProp<ViewStyle> = [
192
+ styles.container,
193
+ getVariantStyle(),
194
+ {
195
+ paddingVertical: config.paddingVertical,
196
+ paddingHorizontal: config.paddingHorizontal,
197
+ minHeight: config.minHeight,
198
+ opacity: isDisabled ? 0.5 : 1,
199
+ },
200
+ style,
201
+ ];
202
+
203
+ const textInputStyle: StyleProp<TextStyle> = [
204
+ styles.input,
205
+ {
206
+ fontSize: config.fontSize,
207
+ color: getTextColor(),
208
+ },
209
+ leadingIcon ? { paddingLeft: config.iconSize + 8 } : undefined,
210
+ (trailingIcon || showPasswordToggle) ? { paddingRight: config.iconSize + 8 } : undefined,
211
+ inputStyle,
212
+ ];
213
+
176
214
  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
- />
215
+ <View testID={testID}>
216
+ {label && (
217
+ <AtomicText
218
+ type="labelMedium"
219
+ color={hasError ? 'error' : hasSuccess ? 'success' : 'secondary'}
220
+ style={styles.label}
221
+ >
222
+ {label}
223
+ </AtomicText>
224
+ )}
225
+
226
+ <View style={containerStyle}>
227
+ {leadingIcon && (
228
+ <View style={styles.leadingIcon}>
229
+ <AtomicIcon
230
+ name={leadingIcon}
231
+ customSize={config.iconSize}
232
+ customColor={iconColor}
233
+ />
234
+ </View>
235
+ )}
236
+
237
+ <TextInput
238
+ value={value}
239
+ onChangeText={onChangeText}
240
+ placeholder={placeholder}
241
+ placeholderTextColor={tokens.colors.textSecondary}
242
+ secureTextEntry={secureTextEntry && !isPasswordVisible}
243
+ maxLength={maxLength}
244
+ keyboardType={keyboardType}
245
+ autoCapitalize={autoCapitalize}
246
+ autoCorrect={autoCorrect}
247
+ editable={!isDisabled}
248
+ style={textInputStyle}
249
+ onBlur={() => {
250
+ setIsFocused(false);
251
+ onBlur?.();
252
+ }}
253
+ onFocus={() => {
254
+ setIsFocused(true);
255
+ onFocus?.();
256
+ }}
257
+ testID={testID ? `${testID}-input` : undefined}
258
+ />
259
+
260
+ {(showPasswordToggle && secureTextEntry) && (
261
+ <Pressable
262
+ onPress={() => setIsPasswordVisible(!isPasswordVisible)}
263
+ style={styles.trailingIcon}
264
+ >
265
+ <AtomicIcon
266
+ name={isPasswordVisible ? "EyeOff" : "Eye"}
267
+ customSize={config.iconSize}
268
+ customColor={iconColor}
269
+ />
270
+ </Pressable>
271
+ )}
272
+
273
+ {trailingIcon && !showPasswordToggle && (
274
+ <Pressable
275
+ onPress={onTrailingIconPress}
276
+ style={styles.trailingIcon}
277
+ disabled={!onTrailingIconPress}
278
+ >
279
+ <AtomicIcon
280
+ name={trailingIcon}
281
+ customSize={config.iconSize}
282
+ customColor={iconColor}
283
+ />
284
+ </Pressable>
285
+ )}
286
+ </View>
199
287
 
200
288
  {(helperText || showCharacterCount) && (
201
- <View style={{
202
- flexDirection: 'row',
203
- justifyContent: 'space-between',
204
- marginTop: tokens.spacing.xs,
205
- }}>
289
+ <View style={styles.helperRow}>
206
290
  {helperText && (
207
- <HelperText
208
- type={hasError ? 'error' : 'info'}
209
- visible={Boolean(helperText)}
210
- style={{ flex: 1 }}
291
+ <AtomicText
292
+ type="bodySmall"
293
+ color={hasError ? 'error' : 'secondary'}
294
+ style={styles.helperText}
211
295
  testID={testID ? `${testID}-helper` : undefined}
212
296
  >
213
297
  {helperText}
214
- </HelperText>
298
+ </AtomicText>
215
299
  )}
216
300
  {showCharacterCount && maxLength && (
217
- <HelperText
218
- type="info"
219
- visible={true}
220
- style={{ marginLeft: tokens.spacing.xs }}
301
+ <AtomicText
302
+ type="bodySmall"
303
+ color="secondary"
304
+ style={styles.characterCount}
221
305
  testID={testID ? `${testID}-count` : undefined}
222
306
  >
223
307
  {characterCount}/{maxLength}
224
- </HelperText>
308
+ </AtomicText>
225
309
  )}
226
310
  </View>
227
311
  )}
@@ -229,4 +313,38 @@ export const AtomicInput: React.FC<AtomicInputProps> = ({
229
313
  );
230
314
  };
231
315
 
316
+ const styles = StyleSheet.create({
317
+ container: {
318
+ flexDirection: 'row',
319
+ alignItems: 'center',
320
+ },
321
+ input: {
322
+ flex: 1,
323
+ },
324
+ label: {
325
+ marginBottom: 4,
326
+ },
327
+ leadingIcon: {
328
+ position: 'absolute',
329
+ left: 12,
330
+ zIndex: 1,
331
+ },
332
+ trailingIcon: {
333
+ position: 'absolute',
334
+ right: 12,
335
+ zIndex: 1,
336
+ },
337
+ helperRow: {
338
+ flexDirection: 'row',
339
+ justifyContent: 'space-between',
340
+ marginTop: 4,
341
+ },
342
+ helperText: {
343
+ flex: 1,
344
+ },
345
+ characterCount: {
346
+ marginLeft: 8,
347
+ },
348
+ });
349
+
232
350
  export type { AtomicInputProps as InputProps };
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { Text as PaperText } from 'react-native-paper';
3
- import { StyleProp, TextStyle } from 'react-native';
2
+ import { Text, StyleProp, TextStyle } from 'react-native';
3
+ import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
4
4
 
5
5
  export type TextStyleVariant =
6
6
  | 'displayLarge' | 'displayMedium' | 'displaySmall'
@@ -37,14 +37,46 @@ export const AtomicText: React.FC<AtomicTextProps> = ({
37
37
  style,
38
38
  testID,
39
39
  }) => {
40
+ const tokens = useAppDesignTokens();
41
+
42
+ // Get typography style from tokens
43
+ const typographyStyle = tokens.typography[type];
44
+
45
+ // Get color from tokens or use custom color
46
+ const getTextColor = (): string => {
47
+ if (!color) {
48
+ return tokens.colors.textPrimary;
49
+ }
50
+
51
+ // Check if it's a semantic color name
52
+ const colorMap: Record<ColorVariant, string> = {
53
+ primary: tokens.colors.textPrimary,
54
+ secondary: tokens.colors.textSecondary,
55
+ tertiary: tokens.colors.textTertiary,
56
+ disabled: tokens.colors.textDisabled,
57
+ inverse: tokens.colors.textInverse,
58
+ success: tokens.colors.success,
59
+ error: tokens.colors.error,
60
+ warning: tokens.colors.warning,
61
+ info: tokens.colors.info,
62
+ };
63
+
64
+ return colorMap[color as ColorVariant] || color;
65
+ };
66
+
67
+ const textStyle: StyleProp<TextStyle> = [
68
+ typographyStyle,
69
+ { color: getTextColor() },
70
+ style,
71
+ ];
72
+
40
73
  return (
41
- <PaperText
42
- variant={type}
74
+ <Text
43
75
  numberOfLines={numberOfLines}
44
- style={style}
76
+ style={textStyle}
45
77
  testID={testID}
46
78
  >
47
79
  {children}
48
- </PaperText>
80
+ </Text>
49
81
  );
50
82
  };