@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,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 -
|
|
64
|
+
* AtomicInput - Pure React Native Text Input
|
|
65
65
|
*
|
|
66
66
|
* Features:
|
|
67
|
-
* - React Native Paper
|
|
67
|
+
* - Pure React Native implementation (no Paper dependency)
|
|
68
68
|
* - Lucide icons for password toggle and custom icons
|
|
69
|
-
* -
|
|
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
|
-
//
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
113
|
-
const hasError = state === 'error';
|
|
134
|
+
const config = sizeConfig[size];
|
|
114
135
|
|
|
115
|
-
// Get
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
);
|
|
149
|
+
switch (variant) {
|
|
150
|
+
case 'outlined':
|
|
151
|
+
return {
|
|
152
|
+
...baseStyle,
|
|
153
|
+
borderWidth: isFocused ? 2 : 1,
|
|
154
|
+
borderColor,
|
|
155
|
+
};
|
|
160
156
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
176
|
+
default:
|
|
177
|
+
return baseStyle;
|
|
178
|
+
}
|
|
167
179
|
};
|
|
168
180
|
|
|
169
181
|
// Get text color based on state
|
|
170
182
|
const getTextColor = () => {
|
|
171
|
-
if (
|
|
172
|
-
if (
|
|
173
|
-
return tokens.colors.
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
<
|
|
208
|
-
type=
|
|
209
|
-
|
|
210
|
-
style={
|
|
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
|
-
</
|
|
298
|
+
</AtomicText>
|
|
215
299
|
)}
|
|
216
300
|
{showCharacterCount && maxLength && (
|
|
217
|
-
<
|
|
218
|
-
type="
|
|
219
|
-
|
|
220
|
-
style={
|
|
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
|
-
</
|
|
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
|
|
3
|
-
import {
|
|
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
|
-
<
|
|
42
|
-
variant={type}
|
|
74
|
+
<Text
|
|
43
75
|
numberOfLines={numberOfLines}
|
|
44
|
-
style={
|
|
76
|
+
style={textStyle}
|
|
45
77
|
testID={testID}
|
|
46
78
|
>
|
|
47
79
|
{children}
|
|
48
|
-
</
|
|
80
|
+
</Text>
|
|
49
81
|
);
|
|
50
82
|
};
|