@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,12 +1,12 @@
1
1
  /**
2
2
  * AtomicTextArea Component
3
3
  *
4
- * A multiline text input component with Material Design 3 integration
4
+ * A multiline text input component with pure React Native implementation
5
5
  * for longer text entry with consistent styling.
6
6
  *
7
7
  * Features:
8
- * - React Native Paper TextInput with multiline
9
- * - Material Design 3 outlined/filled/flat variants
8
+ * - Pure React Native TextInput with multiline
9
+ * - Outlined/filled/flat variants
10
10
  * - Error, success, disabled states
11
11
  * - Character counter with max length
12
12
  * - Helper text for guidance or errors
@@ -31,10 +31,10 @@
31
31
  * ```
32
32
  */
33
33
 
34
- import React from 'react';
35
- import { View, StyleProp, ViewStyle, TextStyle } from 'react-native';
36
- import { TextInput, HelperText } from 'react-native-paper';
34
+ import React, { useState } from 'react';
35
+ import { View, TextInput, StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native';
37
36
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
37
+ import { AtomicText } from './AtomicText';
38
38
 
39
39
  export type AtomicTextAreaVariant = 'outlined' | 'filled' | 'flat';
40
40
  export type AtomicTextAreaState = 'default' | 'error' | 'success' | 'disabled';
@@ -84,7 +84,7 @@ export interface AtomicTextAreaProps {
84
84
  }
85
85
 
86
86
  /**
87
- * AtomicTextArea - Material Design 3 Multiline Text Input
87
+ * AtomicTextArea - Pure React Native Multiline Text Input
88
88
  */
89
89
  export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
90
90
  variant = 'outlined',
@@ -109,85 +109,177 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
109
109
  onFocus,
110
110
  }) => {
111
111
  const tokens = useAppDesignTokens();
112
+ const [isFocused, setIsFocused] = useState(false);
112
113
  const isDisabled = state === 'disabled' || disabled;
113
114
  const characterCount = value?.toString().length || 0;
115
+ const hasError = state === 'error';
116
+ const hasSuccess = state === 'success';
114
117
 
115
- // Map variant to Paper mode
116
- const getPaperMode = (): 'outlined' | 'flat' => {
117
- if (variant === 'outlined') return 'outlined';
118
- return 'flat'; // filled and flat both use 'flat' mode
118
+ // Size configuration
119
+ const sizeConfig = {
120
+ sm: {
121
+ paddingVertical: tokens.spacing.xs,
122
+ paddingHorizontal: tokens.spacing.sm,
123
+ fontSize: tokens.typography.bodySmall.fontSize,
124
+ lineHeight: 20,
125
+ },
126
+ md: {
127
+ paddingVertical: tokens.spacing.sm,
128
+ paddingHorizontal: tokens.spacing.md,
129
+ fontSize: tokens.typography.bodyMedium.fontSize,
130
+ lineHeight: 24,
131
+ },
132
+ lg: {
133
+ paddingVertical: tokens.spacing.md,
134
+ paddingHorizontal: tokens.spacing.lg,
135
+ fontSize: tokens.typography.bodyLarge.fontSize,
136
+ lineHeight: 28,
137
+ },
119
138
  };
120
139
 
121
- // Map state to Paper error prop
122
- const hasError = state === 'error';
140
+ const config = sizeConfig[size];
123
141
 
124
142
  // Calculate height based on rows
125
143
  const getTextAreaHeight = () => {
126
144
  if (minHeight) return minHeight;
145
+ const paddingVertical = config.paddingVertical * 2;
146
+ return (rows * config.lineHeight) + paddingVertical;
147
+ };
148
+
149
+ // Get variant styles
150
+ const getVariantStyle = (): ViewStyle => {
151
+ const baseStyle: ViewStyle = {
152
+ backgroundColor: tokens.colors.surface,
153
+ borderRadius: tokens.borders.radius.md,
154
+ };
155
+
156
+ let borderColor = tokens.colors.border;
157
+ if (isFocused) borderColor = tokens.colors.primary;
158
+ if (hasError) borderColor = tokens.colors.error;
159
+ if (hasSuccess) borderColor = tokens.colors.success;
160
+ if (isDisabled) borderColor = tokens.colors.borderDisabled;
161
+
162
+ switch (variant) {
163
+ case 'outlined':
164
+ return {
165
+ ...baseStyle,
166
+ borderWidth: isFocused ? 2 : 1,
167
+ borderColor,
168
+ };
169
+
170
+ case 'filled':
171
+ return {
172
+ ...baseStyle,
173
+ backgroundColor: tokens.colors.surfaceSecondary,
174
+ borderWidth: 0,
175
+ borderBottomWidth: isFocused ? 2 : 1,
176
+ borderBottomColor: borderColor,
177
+ };
127
178
 
128
- // Base line height: 24px per row (approximate)
129
- const lineHeight = 24;
130
- const padding = 32; // Top and bottom padding
131
- return (rows * lineHeight) + padding;
179
+ case 'flat':
180
+ return {
181
+ ...baseStyle,
182
+ backgroundColor: 'transparent',
183
+ borderWidth: 0,
184
+ borderBottomWidth: 1,
185
+ borderBottomColor: borderColor,
186
+ borderRadius: 0,
187
+ };
188
+
189
+ default:
190
+ return baseStyle;
191
+ }
132
192
  };
133
193
 
134
194
  // Get text color based on state
135
195
  const getTextColor = () => {
136
- if (state === 'error') return tokens.colors.error;
137
- if (state === 'success') return tokens.colors.success;
138
- return tokens.colors.onSurface;
196
+ if (isDisabled) return tokens.colors.textDisabled;
197
+ if (hasError) return tokens.colors.error;
198
+ if (hasSuccess) return tokens.colors.success;
199
+ return tokens.colors.textPrimary;
139
200
  };
140
201
 
202
+ const containerStyle: StyleProp<ViewStyle> = [
203
+ styles.container,
204
+ getVariantStyle(),
205
+ {
206
+ paddingVertical: config.paddingVertical,
207
+ paddingHorizontal: config.paddingHorizontal,
208
+ height: getTextAreaHeight(),
209
+ opacity: isDisabled ? 0.5 : 1,
210
+ },
211
+ style,
212
+ ];
213
+
214
+ const textInputStyle: StyleProp<TextStyle> = [
215
+ styles.input,
216
+ {
217
+ fontSize: config.fontSize,
218
+ lineHeight: config.lineHeight,
219
+ color: getTextColor(),
220
+ },
221
+ inputStyle,
222
+ ];
223
+
141
224
  return (
142
- <View style={style} testID={testID}>
143
- <TextInput
144
- mode={getPaperMode()}
145
- label={label}
146
- value={value}
147
- onChangeText={onChangeText}
148
- placeholder={placeholder}
149
- error={hasError}
150
- disabled={isDisabled}
151
- maxLength={maxLength}
152
- autoCapitalize={autoCapitalize}
153
- autoCorrect={autoCorrect}
154
- multiline={true}
155
- numberOfLines={rows}
156
- style={[
157
- { height: getTextAreaHeight(), textAlignVertical: 'top' },
158
- inputStyle,
159
- ]}
160
- textColor={getTextColor()}
161
- onBlur={onBlur}
162
- onFocus={onFocus}
163
- testID={testID ? `${testID}-input` : undefined}
164
- />
225
+ <View testID={testID}>
226
+ {label && (
227
+ <AtomicText
228
+ type="labelMedium"
229
+ color={hasError ? 'error' : hasSuccess ? 'success' : 'secondary'}
230
+ style={styles.label}
231
+ >
232
+ {label}
233
+ </AtomicText>
234
+ )}
235
+
236
+ <View style={containerStyle}>
237
+ <TextInput
238
+ value={value}
239
+ onChangeText={onChangeText}
240
+ placeholder={placeholder}
241
+ placeholderTextColor={tokens.colors.textSecondary}
242
+ maxLength={maxLength}
243
+ autoCapitalize={autoCapitalize}
244
+ autoCorrect={autoCorrect}
245
+ editable={!isDisabled}
246
+ multiline={true}
247
+ numberOfLines={rows}
248
+ textAlignVertical="top"
249
+ style={textInputStyle}
250
+ onBlur={() => {
251
+ setIsFocused(false);
252
+ onBlur?.();
253
+ }}
254
+ onFocus={() => {
255
+ setIsFocused(true);
256
+ onFocus?.();
257
+ }}
258
+ testID={testID ? `${testID}-input` : undefined}
259
+ />
260
+ </View>
165
261
 
166
262
  {(helperText || showCharacterCount) && (
167
- <View style={{
168
- flexDirection: 'row',
169
- justifyContent: 'space-between',
170
- marginTop: tokens.spacing.xs,
171
- }}>
263
+ <View style={styles.helperRow}>
172
264
  {helperText && (
173
- <HelperText
174
- type={hasError ? 'error' : 'info'}
175
- visible={Boolean(helperText)}
176
- style={{ flex: 1 }}
265
+ <AtomicText
266
+ type="bodySmall"
267
+ color={hasError ? 'error' : 'secondary'}
268
+ style={styles.helperText}
177
269
  testID={testID ? `${testID}-helper` : undefined}
178
270
  >
179
271
  {helperText}
180
- </HelperText>
272
+ </AtomicText>
181
273
  )}
182
274
  {showCharacterCount && maxLength && (
183
- <HelperText
184
- type="info"
185
- visible={true}
186
- style={{ marginLeft: tokens.spacing.xs }}
275
+ <AtomicText
276
+ type="bodySmall"
277
+ color="secondary"
278
+ style={styles.characterCount}
187
279
  testID={testID ? `${testID}-count` : undefined}
188
280
  >
189
281
  {characterCount}/{maxLength}
190
- </HelperText>
282
+ </AtomicText>
191
283
  )}
192
284
  </View>
193
285
  )}
@@ -195,4 +287,27 @@ export const AtomicTextArea: React.FC<AtomicTextAreaProps> = ({
195
287
  );
196
288
  };
197
289
 
290
+ const styles = StyleSheet.create({
291
+ container: {
292
+ justifyContent: 'flex-start',
293
+ },
294
+ input: {
295
+ flex: 1,
296
+ },
297
+ label: {
298
+ marginBottom: 4,
299
+ },
300
+ helperRow: {
301
+ flexDirection: 'row',
302
+ justifyContent: 'space-between',
303
+ marginTop: 4,
304
+ },
305
+ helperText: {
306
+ flex: 1,
307
+ },
308
+ characterCount: {
309
+ marginLeft: 8,
310
+ },
311
+ });
312
+
198
313
  export type { AtomicTextAreaProps as TextAreaProps };
@@ -1,17 +1,16 @@
1
1
  /**
2
2
  * FormContainer Component
3
3
  *
4
- * A reusable container for forms with Material Design 3 integration,
5
- * proper keyboard handling, and responsive layout.
4
+ * A reusable container for forms with proper keyboard handling and responsive layout.
6
5
  *
7
6
  * Features:
8
- * - Material Design 3 Surface component for elevation and theme integration
7
+ * - Pure React Native implementation (no Paper dependency)
9
8
  * - Universal keyboard handling (no platform-specific code)
10
9
  * - ScrollView with automatic content padding
11
10
  * - Safe area insets for bottom tab navigation overlap
12
11
  * - Responsive max width for large screens (tablets)
13
12
  * - Consistent vertical spacing between form elements
14
- * - Theme-aware surface colors and elevation
13
+ * - Theme-aware surface colors
15
14
  * - Optimized performance with memoized styles
16
15
  *
17
16
  * Usage:
@@ -31,13 +30,12 @@
31
30
  * - Consistent form layout across all 100+ generated apps
32
31
  * - Responsive design for tablets (max 700px) and phones (full width)
33
32
  * - Automatic vertical spacing between form elements (no manual marginBottom)
34
- * - Material Design 3 surface with proper elevation
35
33
  * - Reduces boilerplate in form screens
36
34
  * - Universal code - no platform checks, works on iOS, Android, Web
37
35
  *
38
36
  * Technical Details:
39
37
  * - Uses ScrollView with contentContainerStyle for keyboard handling
40
- * - React Native Paper Surface for Material Design 3 integration
38
+ * - Pure React Native View for surface (lightweight)
41
39
  * - Vertical spacing via Children.map() wrapping (universal compatibility)
42
40
  * - Safe area insets from react-native-safe-area-context
43
41
  * - Responsive values from useResponsive hook
@@ -53,7 +51,6 @@ import {
53
51
  StyleProp,
54
52
  ViewStyle,
55
53
  } from 'react-native';
56
- import { Surface } from 'react-native-paper';
57
54
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
58
55
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
59
56
  import { useResponsive } from '../hooks/useResponsive';
@@ -72,15 +69,15 @@ export interface FormContainerProps {
72
69
  showsVerticalScrollIndicator?: boolean;
73
70
  /** Optional test ID for E2E testing */
74
71
  testID?: string;
75
- /** Disable Material Design elevation (default: false) */
76
- disableElevation?: boolean;
72
+ /** Show surface border (default: true) */
73
+ showBorder?: boolean;
77
74
  }
78
75
 
79
76
  /**
80
77
  * FormContainer - Universal form wrapper component
81
78
  *
82
79
  * Wraps forms with:
83
- * - Material Design 3 Surface component
80
+ * - Pure React Native surface
84
81
  * - Universal keyboard handling (no platform checks)
85
82
  * - ScrollView for content overflow
86
83
  * - Safe area insets (bottom tabs, notch)
@@ -93,7 +90,7 @@ export const FormContainer: React.FC<FormContainerProps> = ({
93
90
  contentContainerStyle,
94
91
  showsVerticalScrollIndicator = false,
95
92
  testID,
96
- disableElevation = false,
93
+ showBorder = true,
97
94
  }) => {
98
95
  const tokens = useAppDesignTokens();
99
96
  const insets = useSafeAreaInsets();
@@ -111,6 +108,9 @@ export const FormContainer: React.FC<FormContainerProps> = ({
111
108
  surface: {
112
109
  flex: 1,
113
110
  backgroundColor: tokens.colors.surface,
111
+ borderWidth: showBorder ? 1 : 0,
112
+ borderColor: tokens.colors.border,
113
+ borderRadius: tokens.borders.radius.md,
114
114
  },
115
115
  scrollView: {
116
116
  flex: 1,
@@ -135,12 +135,15 @@ export const FormContainer: React.FC<FormContainerProps> = ({
135
135
  [
136
136
  tokens.colors.backgroundPrimary,
137
137
  tokens.colors.surface,
138
+ tokens.colors.border,
139
+ tokens.borders.radius.md,
138
140
  tokens.spacing.lg,
139
141
  tokens.spacing.xl,
140
142
  formBottomPadding,
141
143
  formContentWidth,
142
144
  formElementSpacing,
143
145
  insets.bottom,
146
+ showBorder,
144
147
  ]
145
148
  );
146
149
 
@@ -160,10 +163,7 @@ export const FormContainer: React.FC<FormContainerProps> = ({
160
163
 
161
164
  return (
162
165
  <View style={[styles.container, containerStyle]} testID={testID}>
163
- <Surface
164
- style={styles.surface}
165
- elevation={disableElevation ? 0 : 1}
166
- >
166
+ <View style={styles.surface}>
167
167
  <ScrollView
168
168
  style={styles.scrollView}
169
169
  contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
@@ -174,7 +174,7 @@ export const FormContainer: React.FC<FormContainerProps> = ({
174
174
  >
175
175
  {childrenWithSpacing}
176
176
  </ScrollView>
177
- </Surface>
177
+ </View>
178
178
  </View>
179
179
  );
180
180
  };