@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.
- package/README.md +16 -11
- package/package.json +1 -2
- package/src/domains/icons/domain/config/IconLibraryConfig.ts +93 -0
- package/src/domains/icons/domain/entities/Icon.ts +143 -0
- package/src/domains/icons/domain/interfaces/IIconAdapter.ts +144 -0
- package/src/domains/icons/index.ts +109 -0
- package/src/domains/icons/infrastructure/adapters/LucideAdapter.ts +100 -0
- package/src/domains/icons/infrastructure/registries/ExpoIconRegistry.ts +191 -0
- package/src/domains/icons/presentation/components/Icon.tsx +132 -0
- package/src/domains/icons/presentation/hooks/useIconLibrary.ts +141 -0
- package/src/index.ts +12 -0
- package/src/presentation/atoms/AtomicButton.tsx +188 -40
- package/src/presentation/atoms/AtomicCard.tsx +52 -29
- package/src/presentation/atoms/AtomicIcon.tsx +26 -117
- package/src/presentation/atoms/AtomicInput.tsx +217 -99
- package/src/presentation/atoms/AtomicText.tsx +38 -6
- package/src/presentation/atoms/AtomicTextArea.tsx +173 -58
- package/src/presentation/organisms/FormContainer.tsx +16 -16
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AtomicTextArea Component
|
|
3
3
|
*
|
|
4
|
-
* A multiline text input component with
|
|
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
|
|
9
|
-
* -
|
|
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 -
|
|
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
|
-
//
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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 (
|
|
137
|
-
if (
|
|
138
|
-
return tokens.colors.
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
<
|
|
174
|
-
type=
|
|
175
|
-
|
|
176
|
-
style={
|
|
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
|
-
</
|
|
272
|
+
</AtomicText>
|
|
181
273
|
)}
|
|
182
274
|
{showCharacterCount && maxLength && (
|
|
183
|
-
<
|
|
184
|
-
type="
|
|
185
|
-
|
|
186
|
-
style={
|
|
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
|
-
</
|
|
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
|
|
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
|
-
* -
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
76
|
-
|
|
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
|
-
* -
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
177
|
+
</View>
|
|
178
178
|
</View>
|
|
179
179
|
);
|
|
180
180
|
};
|